💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
>[info]原文链接:https://golang.org/cmd/cgo/ [TOC] Cgo可以创建出能够调用C代码的Go包。 #### 在go命令行中使用cgo 使用cgo书写普通的Go代码,导入一个伪包“C”。Go代码就可以关联到如C.size_t的类型,如C.stdout的变量,或者如C.putchar的方法。 如果“C”包的导入紧接在注释之后,那个这个注释,被称为前导码【preamble】,就会作为编译Go包中的C语言部分的头文件。例如: ~~~ // #include <stdio.h> // #include <errno.h> import "C" ~~~ 前导码【preamble】可以包含任何C语言代码,包括方法和变量声明及定义。这些代码后续可能会由Go代码引用,就像它们定义在了名为“C”的Go包中。所有在前导码中声明的名字都可能被使用,即使它们以小写字母开头。有一个例外:前导码中的static变量不能被Go代码引用;而static方法则可以被Go引用。 可以查看`$GOROOT/misc/cgo/stdio`和`$GOROOT/misc/cgo/gmp`中的例子。也可以在https://golang.org/doc/articles/c_go_cgo.html 中了解使用cgo的介绍。 `CFLAGS`, `CPPFLAGS`, `CXXFLAGS`, `FFLAGS`,和`LDFLAGS`可以在注释区域中,使用#cgo伪指令来定义,以改变C,C++,或Fortran编译器的行为。被多个指令定义的值会被联系在一起。指令还可以包含一个构建约束的列表,将其对系统的影响限制为满足其中一个约束条件。可以查看https://golang.org/pkg/go/build/#hdr-Build_Constraints 了解约束语法的详情。例如: ~~~ // #cgo CFLAGS: -DPNG_DEBUG=1 // #cgo amd64 386 CFLAGS: -DX86=1 // #cgo LDFLAGS: -lpng // #include <png.h> import "C" ~~~ 或者,`CPPFLAGS`和`LDFLAGS`也可通过`pkg-config`工具获取,使用`#cgo pkg-config:`指令,后面跟上包名即可。比如: ~~~ // #cgo pkg-config: png cairo // #include <png.h> import "C" ~~~ 默认的`pkg-config`工具可以通过设置`PKG_CONFIG`环境变量来改变。 当编译时,`CGO_CFLAGS`,`CGO_CPPFLAGS`,`CGO_CXXFLAGS`,`CGO_FFLAGS`和`CGO_LDFLAGS`这些环境变量都会从指令中提取出来,并加入到flags中。包特定的flags需要使用指令来设置,而不是通过环境变量,所以这些构建可以在未更改的环境中也能正常运行。 一个包中的所有cgo CPPFLAGS和CFLAGS指令会被连接起来,并用来编译包中的C文件。一个包中的所有CPPFLAGS和CXXFLAGS指令会被连接起来,并用来编译包中的C++文件。一个包中的所有CPPFLAGS和FFLAGS指令会被连接起来,并用来编译包中的Fortran文件。在这个程序中任何包内的所有LDFLAGS指令会被连接起来,并在链接时使用。所有的pkg-config指令会被连接起来,并同时发送给pkg-config,以添加到每个适当的命令行标志集中。 当cgo指令被转化【parse】时,任何出现${SRCDIR}字符串的地方,都会被替换为包含源文件的目录的绝对路径。这就允许预编译的静态库包含在包目录中,并能够正确的链接。例如,如果foo包在/go/src/foo目录下: ~~~ // #cgo LDFLAGS: -L${SRCDIR}/libs -lfoo ~~~ 就会被展开为: ~~~ // #cgo LDFLAGS: -L/go/src/foo/libs -lfoo ~~~ >[info]心得: `// #cgo LDFLAGS:` 可用来链接静态库。`-L`指定静态库所在目录,`-l`指定静态库文件名,注意静态库文件名必须有`lib`前缀,但是这里不需要写,比如上面的-lfoo实际找的是libfoo.a文件 当Go tool发现一个或多个Go文件使用了特殊导入“C”包,它就会在目录中寻找其它非Go文件,并将其编译为Go包的一部分。任何.c, .s, 或者.S文件会被C编译器编译。任何.cc, .cpp, 或者.cxx文件会被C++编译器编译。任何.f, .F, .for或者.f90文件会被fortran编译器编译。任何.h, .hh, .hpp或者.hxx文件不会被分开编译,但是,如果这些头文件修改了,那么C和C++文件就会被重新编译。默认的C和C++编译器有可能会分别被CC和CXX环境变量改变,那些环境变量可能包含命令行选项。 cgo tool对于本地构建默认是启用的。而在交叉编译时默认是禁用的。你可以在运行go tool时,通过设置CGO_ENABLED环境变量来控制它:设为1表示启用cgo, 设为0关闭它。如果cgo启用,go tool将会设置构建约束“cgo”。 在交叉编译时,你必须为cgo指定一个C语言交叉编译器。你可以在使用make.bash构建工具链时,设置CC_FOR_TARGET环境变量来指定,或者是在你运行go tool时设置CC环境变量来指定。与此相似的还有作用于C++代码的CXX_FOR_TARGET和CXX环境变量。 #### Go引用C代码 在Go文件中,C的结构体属性名是Go的关键字,可以加上下划线前缀来访问它们:如果x指向一个拥有属性名"type"的C结构体,那么就可以用`x._type`来访问这个属性。对于那些不能在Go中表达的C结构体字段(例如位字段或者未对齐的数据),会在Go结构体中被省略,而替换为适当的填充,以到达下一个属性,或者结构体的结尾。 标准C数字类型,可以通过如下名字访问: ~~~ C.char, C.schar(signed char), C.uchar(unsigned char), C.short, C.ushort(unsigned short), C.int, C.uint(unsigned int), C.long, C.ulong(unsigned long), C.longlong(long long), C.ulonglong(unsigned long long), C.float, C.double, C.complexfloat(complex float), C.complexdouble(complex double) ~~~ C的`void*`类型由Go的`unsafe.Pointer`表示。C的`__int128_t`和`__uint128_t`由[16]byte表示。 如果想直接访问一个结构体,联合体,或者枚举类型,请加上如下前缀:`struct_`, `union_`, 或者`enum_`。比如`C.struct_stat`。 >[warning]心得:但是如果C结构体用了typedef struct设置了别名,则就不需要加上前缀,可以直接C.alias访问该类型。 任何一个C类型T的尺寸大小,都可以通过`C.sizeof_T`获取,比如`C.sizeof_struct_stat`。 因为在通常情况下Go并不支持C的联合体类型【union type】,所以C的联合体类型,由一个等长的Go byte数组来表示。 Go结构体不能嵌入具有C类型的字段。 Go代码不能引用发生在非空C结构体末尾的零尺寸字段。如果要获取这个字段的地址(这也是对于零大小字段唯一能做的操作),你必须传入结构体的地址,并加上结构体的大小,才能算出这个零大小字段的地址。 cgo会将C类型转换为对应的,非导出的的Go类型。因为转换是非导出的,一个Go包就不应该在它的导出API中暴露C的类型:在一个Go包中使用的C类型,不同于在其它包中使用的同样C类型。 可以在多个赋值语境中,调用任何C函数(甚至是void函数),来获取返回值(如果有的话),以及C errno变量作为Go error(如果方法返回void,则使用 _ 来跳过返回值)。例如: ~~~ n, err = C.sqrt(-1) _, err := C.voidFunc() var n, err = C.sqrt(1) ~~~ 调用C的方法指针目前还不支持,然而你可以声明Go变量来引用C的方法指针,然后在Go和C之间来回传递它。C代码可以调用来自Go的方法指针。例如: ~~~ package main // typedef int (*intFunc) (); // // int // bridge_int_func(intFunc f) // { // return f(); // } // // int fortytwo() // { // return 42; // } import "C" import "fmt" func main() { f := C.intFunc(C.fortytwo) fmt.Println(int(C.bridge_int_func(f))) // Output: 42 } ~~~ 在C中,一个方法参数被写为一个固定大小的数组,实际上需要的是一个指向数组第一个元素的指针。C编译器很清楚这个调用习惯,并相应的调整这个调用,但是Go不能这样做。在Go中,你必须显式的传入指向第一个元素的指针:C.f(&C.x[0])。 在Go和C类型之间,通过拷贝数据,还有一些特殊的方法转换。用Go伪代码定义如下: ~~~ // Go string to C string // The C string is allocated in the C heap using malloc. // It is the caller's responsibility to arrange for it to be // freed, such as by calling C.free (be sure to include stdlib.h // if C.free is needed). func C.CString(string) *C.char // Go []byte slice to C array // The C array is allocated in the C heap using malloc. // It is the caller's responsibility to arrange for it to be // freed, such as by calling C.free (be sure to include stdlib.h // if C.free is needed). func C.CBytes([]byte) unsafe.Pointer // C string to Go string func C.GoString(*C.char) string // C data with explicit length to Go string func C.GoStringN(*C.char, C.int) string // C data with explicit length to Go []byte func C.GoBytes(unsafe.Pointer, C.int) []byte ~~~ 作为一个特殊例子,C.malloc并不是直接调用C的库函数malloc,而是调用一个Go的帮助函数【helper function】,该函数包装了C的库函数malloc,并且保证不会返回nil。如果C的malloc指示内存溢出,这个帮助函数会崩溃掉程序,就像Go自己运行时内存溢出一样。因为C.malloc从不失败,所以它不会返回包含errno的2值格式。