目录 [−]
本章介绍Go如何调用C代码,以及如何调用动态链接库。
如果你正准备使用Go开发你的程序,或者你正将一个C构建的项目转换成Go项目,请尽量使用Go构建你的项目,而不是偷巧的导入C代码,尽量保持Go项目的纯粹,原因可以查看cgo 和 Go 语言是两码事,文末的参考文档中也有这篇文章的原始英文。
但是,有些情况下,我们不得不使用C代码构建,那么我们就可以使用cgo技术。
Go代码调用C函数
cgo可以让Go代码调用C代码。
C代码被封装进“package C”中,你可以访问C实现的类型C.size_t
、 变量C.stdout
和 方法C.putchar
,即使它们的首字母是小写的。
在代码import "C"
之前有注释(紧接着这个import),那么这个注释称之为preamble
(序言、开场白)。它可以包含编译C package的头文件:
|
|
preamble还可以包含C代码,你可以在C代码中定义变量和函数,它们可以在Go代码中通过包C来引用。C代码中的静态变量不能在G中使用,但是静态函数可以。
|
|
你可以在Go官方代码库中看到这样的例子, 比如misc/cgo/stdio。
工具cmd/tool将包含导入包C的Go文件转换成几个Go文件和C文件。如果你运行go tool cgo main1.go
转换上面的例子,你会发现在本地文件夹下生成了一个_obj的文件夹:
|
|
它会包含一个编译器在编译这些C文件后生成的目标文件cgo.o。
在实际开发中,我们不会直接调用cgo工具,因为go build
会自动完成这一切,让我们编译这个程序go build main1.go
或者直接运行go run main1.go
:
|
|
这是引用C的标准库,我们不需要额外的编译参数设置,要引入特定的库,我们还需要设置一些额外的参数。
我们可以使用#cgo
指令符(directive)为C/C++编译器提供 CFLAGS、 CPPFLAGS、CXXFLAGS 和 LDFLAGS 设置,同时也可以提供一些编译的约束,比如为特定的平台的参数:
|
|
开发C/C++程序的程序员和经常使用make工具链的开发者应该对这些参数很熟悉了, flags给编译器提供开关,比如指定头文件的位置等, ldflags提供链接选项,比如提供库的位置。
CFLAGS
用来给 C 编译器提供开关。CXXFLAGS
用来给 C++ 编译器提供开关。CPPFLAGS
用来给C预处理提供开关,对 C / C++ 都有效。LDFLAGS
用来指定链接选项,比如链接库的位置,以及使用哪些链接库。
我们在编译C文件的时候,一般会经过四个步骤: 预处理、编译、汇编和链接,你可以看到这些开发参数的用处:
|
|
gcc可用的开关可以查看它的文档: Invoking-GCC。
CPPFLAGS
、LDFLAGS
可以通过 pkg-config 工具获得:
|
|
编译的时候,四个环境变量会增加它们的flag到编译参数中,这适合设置通用的,包无关的编译参数。
还有一个变量 ${SRCDIR} 用来指代原文件所在的文件夹的绝对路径,这允许你将预先编译好的静态库放在本地文件夹中,让编译器可以找到这些库以便正确的链接。比如包foo在文件夹/go/src/foo下:
|
|
上面的指令等价于:
|
|
可以看一个使用libsqlite3库的例子:
|
|
实际上,你不使用#cgo pkg-config: sqlite3
也可以,因为在我们的机器上(Mac OS X),libsqlite3被安装在标准的路径中,库在/usr/lib中,头文件安装在/usr/include文件下,如果你为PKG_CONFIG_PATH指定了特殊的文件夹,你可以使用这个指令:
|
|
当Go工具访问一个或者多个Go文件导入包C的时候, 它也会查找其它的非Go的文件并把它们编译到Go包中 以 .c
, .s
, .S
结尾的C文件或者汇编文件使用C编译器编译,以.cc
, .cpp
, .cxx
结尾的文件以C++编译器编译以.h
, .hh
, .hpp
, .hxx
文件不会独立编译,但是这些头文件如果有改动,相应的C和C++文件会重新被编译。默认的C和C++编译器可以通过CC 和 CXX 环境变量改变。
所以文件夹下的汇编语言也可以被编译。
交叉编译的时候cgo被禁止,如果想启用,设置CGO_ENABLED=1。还需要额外的设置,比如C交叉编译器。
下面以一个计算圆周率的前1000位的例子看看我们自己实现的C库如何被我们的 Go代码实现 (假定所有的文件都在同一个文件夹下,这样编译和使用动态库时比较方便):
首先是计算Pi的C代码 pi.c
,函数calc用来计算Pi的值,返回结果是一个C的字符串:
|
|
编译成动态库:
|
|
定义一个头文件pi.h
:
|
|
我们可以写一个C程序 test.c
调用这个动态库,测试一下:
|
|
编译执行一下,确保动态库没有问题:
|
|
现在就可以在Go代码中使用这个库了。写一个Go文件 main3.go
:
|
|
编译:go build main3.go
,因为动态库和生成的可执行文件main3
在同一个目录下,没有问题,执行main3:
|
|
上面这个计算Pi的例子我们将C的字符串转换成Go的字符串。 cgo定义了Go和C之间的类型对应关系。
- 如果C的struct的字段类型是Go的关键字,如
type
, 那么在Go代码中可以在字段前加关键字如x._type
- C中的整数类型已经在包C中定义,如
C.char
、C.short
、C.ushort
、C.int
、C.uint
、C.longlong
、C.float
,不一一列举,请看参考文档1 - 访问C的
struct
、union
、enum
类型需要加类型前缀struct_
、union_
、enum_
,如C.struct_stat - 访问C中的类型T的size用 C.sizeof_T,如C.sizeof_struct_stat
- Go不支持C的union的概念,只是把它作为相同长度的字节数组
- Go的Struct不能嵌入C的类型
- Go的API不应该再暴露C的类型给外部
- 调用C的函数可以进行多值赋值,一个值作为返回值,一个作为errno
- 当前不支持C的函数指针
- C中参数是固定长度的数组,可以把数组名传递给函数,但是Go代码调用中必须显示地将指针指向数组的第一个元素,如C.f(&C.x[0])
对应的类型转换:
char --> C.char --> byte signed char --> C.schar --> int8 unsigned char --> C.uchar --> uint8 short int --> C.short --> int16 short unsigned int --> C.ushort --> uint16 int --> C.int --> int unsigned int --> C.uint --> uint32 long int --> C.long --> int32 or int64 long unsigned int --> C.ulong --> uint32 or uint64 long long int --> C.longlong --> int64 long long unsigned int --> C.ulonglong --> uint64 float --> C.float --> float32 double --> C.double --> float64 wchar_t --> C.wchar_t --> void * -> unsafe.Pointer
项目giorgisio/cgo提供了一些Go调用C代码各种类型的例子。
调用动态链接库
对于Windows环境,Go提供了直接加载动态链接库的方法。 首先syscall包下实现了LoadDLL
、FindProc
、Release
方法,可以加载动态链接库以及得到相应的函数。
另外包golang.org/x/sys/windows
提供了更多的方法,如LoadLibrary
、LoadLibraryEx
、 DLL
、 LazyDLL
等方法和类型。
举个栗子:
|
|
其它平台我还没有发现官方的调用.so或者.dylib的方法, 但是我看到有第三方的作者写了相应的库,提供类似C中的dlopen和dlsym方法:
Runtime dynamic library loader
还有go-ffi,也提供了dlopen和dlsym的功能。
|
|
参考
- https://golang.org/cmd/cgo/
- https://github.com/golang/go/wiki/cgo
- http://akrennmair.github.io/golang-cgo-slides/#1
- http://dave.cheney.net/2016/01/18/cgo-is-not-go
- http://dominik.honnef.co/posts/2015/06/statically_compiled_go_programs__always__even_with_cgo__using_musl/
- http://blog.giorgis.io/cgo-examples
- http://blog.madewithdrew.com/post/statically-linking-c-to-go/
- https://github.com/hyper-carrot/go_command_tutorial/blob/master/0.13.md
- https://www.goinggo.net/2013/08/using-c-dynamic-libraries-in-go-programs.html