作为直接继承于C的语言,Go提供了与C语言交互的功能,称为Cgo。先来看看一个例子:
Go 语言交互性 - 图1
Go 语言交互性 - 图2

直接运行 go run命令即可.

事实上,根本就不存在一个名为C的包。这个import语句其实就是一个信号,告诉Cgo它应该开始工作了。做什么事情呢?就是对应这条import语句之前的块注释中的C源代码自动生成包装性质的Go代码。

这时候我们该注意到import语句前紧跟的注释了。这个注释的内容是有意义的,而不是传统意义上的注释作用。这个例子里用的是一个块注释,实际上用行注释也是没问题的,只要是紧贴在import语句之前即可。比如下面也是正确的Cgo写法:
Go 语言交互性 - 图3

类型映射

在跨语言交互中,比较复杂的问题有两个:类型映射以及跨越调用边界传递指针所带来的对象生命周期和内存管理的问题。对于C语言的原生类型,Cgo都会将其映射为Go语言中的类型:

  • 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=>float double

C语言中的viod*指针类型在Go语言中则用特殊的unsafe.Pointer类型来对应。
C语言中的struct、 union和enum类型,对应到Go语言中都会变成带这样前缀的类型名称:struct、 union和enum_。比如一个 在C语言中叫做person的struct会被Cgo翻译为C.struct_person。
如果C语言中的类型名称或变量名称与Go语言的关键字相同, Cgo会自动给这些名字加上下划线前缀。

字符串映射

Cgo提供了一系列函数来提供支持:C.CString、C.GoString和C.GoStringN。需要注意的是,每次转换都将导致一次内存复制,因此字符串内容其实是不可以修改的(实际上,Go语言的string也不允许对其中的内容进行修改)。

由于C.CString的内存管理方式与Go语言自身的内存管理方式不兼容,我们设法期待Go语言可以帮助我们做垃圾收集,因此在使用完成后必须显示释放调用C.CString所生成的内存块,否则将导致严重的内存泄漏。结合我们之前学过的defer用法,所有用到C.CString的代码大致都可以写成如下的风格:
Go 语言交互性 - 图4

C程序

在import “C”之前的注释块中,可以写任意合法的C源代码,而Cgo都会进行相应的处理并生成对应的Go代码。如下:
Go 语言交互性 - 图5

这个块注释里就直接写了个C函数,它使用C标准库里的printf()打印了一句话。

还有另外一个问题,那就是如果这里的C代码需要依赖一个非C标准库的第三方库,怎么办呢?如果不解决的话必然会有链接时错误。Cgo提供了#cgo这样的伪C文法,让开发者有机会指定依赖的第三方库和编译选项。

下面的例子示范了#cgo的第一种用法:
Go 语言交互性 - 图6
这个例子示范了如何使用CFLAGS来传入编译选项,使用LDFLAGS来传入链接选项。 #cgo还有另外一种更简便一些的用法,如下所示:
Go 语言交互性 - 图7

函数调用

对于常规的函数调用,开发者只要在运行cgo指令后查看一下生成的Go代码,就可以知道如何写对应的调用代码。对于常规返回了一个值的函数,调用者可以用以下的方式顺便得到错误码:
Go 语言交互性 - 图8

在传递数组类型的参数时需要注意,在Go语言中将第一个元素的地址作为整个数组的起始地址传入,这一点就不如C语言本身直接传入数组名字那么方便了。下面为一个传递数组的例子:
Go 语言交互性 - 图9

编译Cgo

编译Cgo代码非常容易,我们不需要做任何特殊的处理。 Go安装后,会自带一个cgo命令行工具,它用于处理所有带有Cgo代码的Go文件,生成Go语言版本的调用封装代码。而Go工具集对cgo命令行工具再次进行了良好的封装,使构建过程能够自动识别和处理带有Cgo代码的Go源代码文件,完全不给用户增加额外的工作负担。


Go 语言交互性 - 图10