介绍
cgo 是 go 语言里面的一个特性,属于 go 的高级用法,我们可以使用 cgo 来实现 go 语言调用 c 语言程序。
1. 简单示例:
packagemain/*#include static void Hello(const char* s) {puts(s);}*/import"C"funcmain() { hi :=C.CString("Hello World!") C.Hello(hi) deferC.free(unsafe.Pointer(hi)) // 释放分配在C中的内存,否则会造成内存泄露}
- import "C" 语句启用 CGO 特性
- 引入 C 语言的 头文件
- CGO 包的 C.CString 函数将 Go 语言字符串转为 C 语言字符串
- C.CString 创建的 C 语言字符串如果没有被正确释放,会导致内存泄漏,另外程序退出后操作系统也会自动回收程序的所有资源。
2. 工具链
要使用 CGO 特性,需要安装 C/C++ 构建工具链,在 macOS 和 Linux 下是要安装 GCC,在 windows 下是需要安装 MinGW 工具。同时需要保证环境变量 CGO_ENABLED 被设置为 1,这表示 CGO 是被启用的状态。在本地构建时 CGO_ENABLED 默认是启用的,当交叉构建时 CGO 默认是禁止的。比如要交叉构建 ARM 环境运行的 Go 程序,需要手工设置好 C/C++ 交叉构建的工具链,同时开启 CGO_ENABLED 环境变量。然后通过 import "C" 语句启用 CGO 特性。
动态库路径
1. 编译目标代码时指定的动态库搜索路径(指的是用-wl,rpath或-R选项而不是-L);
example: gcc -Wl,-rpath,/home/arc/test,-rpath,/lib/,-rpath,/usr/lib/,-rpath,/usr/local/lib test.c
2. 环境变量LD_LIBRARY_PATH指定的动态库搜索路径;(例如:export LD_LIBRARY_PATH=/root/test/env/lib)
3. 配置文件/etc/ld.so.conf中指定的动态库搜索路径;(更改/etc/ld.so.conf文件后记得一定要执行命令:ldconfig,该命令会将/etc/ld.so.conf文件中所有路径下的库载入内存中)
4. 默认的动态库搜索路径/lib;
5. 默认的动态库搜索路径/usr/lib。
类型转换
// Go 字符串转换为 C 字符串// C 字符串使用 malloc 来分配到 C 堆里。// 调用方需要把它释放掉,比如调用 C.free (如果需要 C.free,确保程序包含 stdlib.h)funcC.CString(string) *C.char// 这里同样需要使用C.free释放内存funcC.CBytes([]byte) unsafe.Pointer// C 字符串转换为 Go 字符串funcC.GoString(*C.char) string// 有明确长度的 C 数据转换为 Go 字符串funcC.GoStringN(*C.char, C.int) string// 有明确长度的 C 数据转换为 Go []bytefuncC.GoBytes(unsafe.Pointer, C.int) []byte
官方提供的函数**计算字符串的长度** 和 **获取字符串的地址**:
size_t_GoStringLen(_GoString_s); constchar*_GoStringPtr(_GoString_s);
参数类型是_GoString_ s,第一个方法返回Go字符串的长度,第二个方法返回指向这个字符串的char*指针,下面为示例代码 :
packagemain/*#include #include #include // 这两个函数要声明在序言中size_t _GoStringLen(_GoString_ s);const char *_GoStringPtr(_GoString_ s);void PrintGoStringInfo(char* s, size_t count){// 注意,s尾部没有"\0", 在C中直接当字符串处理会出错char* buf = malloc((count + 1) * sizeof(char)); //memset(buf, 0, count + 1);memcpy(buf, s, count);printf("%s\n", buf);printf("sizeof goString: %ld\n", count);free(buf);}*/import"C"funcmain() { str :="hello world"C.PrintGoStringInfo(C._GoStringPtr(str), C._GoStringLen(str)) }
- 需要注意的是 _GoStringPtr 返回的 char* 尾部是不包含的`\0` 的,在 C 中直接当字符串处理会出错
- 这两个函数仅可在序言中使用,不能在其他的C文件中使用,C代码绝不能通过 _GoStringPtr 返回的指针修改其指向的内容
注意:这两个函数可以很方便的将Go string转换为C的char*,如果使用 _GoStringPtr 传入一个临时的 string 到C中,在C中应拷贝一份副本到C内存中,尤其是在一些异步调用的过程中,从官方关于cgo的文档看来,这两个函数似乎并不保证传入的临时Go string类型不会被gc回收
GO 语言中访问 C 语言的 struct ,union,enum,对应关系:
示例
1. go 字符串类型 与 c++ char* 互转使用
typedefunsignedcharu_char; externintg_loadCout; //extern"C" { ///** 对数据进行 sm4 加密* _out 解密的数据* _outLen 输入时,为_out缓冲区的长度。输出时,为_out中返回的数据的长度* _plaint 明文,被加密的数据* _plaintLen _plaint数据的长度* _key 加密密钥,长度固定为 32 byte*/voidencryptData(char*_out, int*_outLen, constchar*_plaint, int_plaintLen, char*_key, int_keyLen); //encryptData() 函数的,解密操作voiddecryptData(char*_out, int*_outLen, constchar*_cipher, int_cipherLen, char*_key, int_keyLen); //} //// SM4_EX_H
packagemain/*#cgo CFLAGS: -I./#cgo LDFLAGS: -L./ -lsm4 -lstdc++ -lcurl //-lsm4 引入对应C程序的动态库文件#include "sm4.h" //引入对应C程序的头文件#include #include */import"C"import ( "encoding/base64""encoding/hex""fmt""unsafe") funcmain() { txt :="123"in :=C.CString(txt) deferC.free(unsafe.Pointer(in)) // 释放分配在C中的内存,否则会造成内存泄露inlen :=C.int(len(txt)) keytxt :="123456789"b, _ :=hex.DecodeString(keytxt) keylen :=C.int(len(keytxt)) key := (*C.char)(unsafe.Pointer(&b[0])) len1 :=len(txt) +100outlen :=C.int(len1) outtxt :=make([]byte, len1) out := ((*C.char)(unsafe.Pointer(&outtxt[0]))) C.encryptData(out, &outlen, in, inlen, key, keylen/2) fmt.Printf("明文:<%s>长度<%d>;加密后密文:<%v>长度<%d>\n", txt, int(inlen), C.GoStringN(out, outlen), int(outlen)) encoded :=base64.StdEncoding.EncodeToString([]byte(C.GoStringN(out, outlen))) fmt.Printf("加密后密文base64:<%s>\n", encoded) d :=hex.EncodeToString([]byte(C.GoStringN(out, outlen))) fmt.Printf("加密后密文转16进制<%s>\n:", d) //----------------解密-----------------------------fmt.Println("------------------") entxt, _ :=base64.StdEncoding.DecodeString(encoded) //密文base64解码entxt2 :=C.CString(string(entxt)) deferC.free(unsafe.Pointer(entxt2)) // 释放分配在C中的内存,否则会造成内存泄露entxtlen :=C.int(len(string(entxt))) len2 :=len(string(entxt)) +100out2len :=C.int(len2) outtxt2 :=make([]byte, len2) out2 := ((*C.char)(unsafe.Pointer(&outtxt2[0]))) C.decryptData(out2, &out2len, entxt2, entxtlen, key, keylen/2) fmt.Println("输入密文长度:", int(entxtlen), entxtlen) fmt.Printf("解密后明文:<%s>,长度<%d>\n\n", C.GoStringN(out2, out2len), int(out2len)) }
2. go 字符串与 c++ unsigned char 互转
voidsm4_ecb(constunsignedchar*in, unsignedchar*out, constunsignedchar*keyValue, intlength, intenc);
packagemain/*#cgo CFLAGS: -I./#cgo LDFLAGS: -L./ -lsm4#include "sm4.h"*/import"C"import ( "fmt""unsafe") funcmain() { in := []byte("hello world!") key := []byte("1234567890abcdef") varout []byteC.sm4_ecb((*C.uchar)(unsafe.Pointer(&in[0])), (*C.uchar)(unsafe.Pointer(&out)), (*C.uchar)(unsafe.Pointer(&key[0])), (C.int)(len(in)), (C.int)(0), ) fmt.Println("out:", out) }
3. 释放 C 中的内存
packageprint// #include // #include import"C"import"unsafe"funcPrint(sstring) { cs :=C.CString(s) deferC.free(unsafe.Pointer(cs)) // 释放分配在C中的内存,否则会造成内存泄露C.fputs(cs, (*C.FILE)(C.stdout)) }



