Go使用CGO调用C语言
C语言作为一个通用语言,很多库会选择提供一个C兼容的API,然后用其他不同的编程语言实现。
Go语言通过自带的一个叫CGO的工具来支持C语言函数调用,同时我们可以用Go语言导出C动态库接口给其它语言使用。
要使用CGO特性,需要安装C/C++构建工具链,在macOS和Linux下是要安装GCC,在windows下是需要安装MinGW工具。同时需要保证环境变量CGO_ENABLED
被设置为1,这表示CGO是被启用的状态。在本地构建时CGO_ENABLED
默认是启用的,当交叉构建时CGO默认是禁止的。比如要交叉构建ARM环境运行的Go程序,需要手工设置好C/C++交叉构建的工具链,同时开启CGO_ENABLED
环境变量。然后通过import "C"
语句启用CGO特性。
调用C标准库函数
1 | package main |
通过import "C"
语句启用CGO特性,同时包含C语言的<stdio.h>
头文件。然后通过CGO包的C.CString
函数将Go语言字符串转为C语言字符串,最后调用CGO包的C.puts
函数向标准输出窗口打印转换后的C字符串。
调用C自定义函数
1 | package main |
定义一个叫SayHello
的C函数来实现打印,然后从Go语言环境中调用这个SayHello
函数
也可以将SayHello
的C函数放在当前目录的C语言源文件中(后缀名必须是.c
)。因为是编写在独立的C文件中,为了允许外部引用,所以需要去掉函数的static
修饰符。
hello.c
文件
1 | // hello.c |
hello.go
1 | // hello.go |
注意 调用 不是用 go run hello.go
而是 go run "your/package"
或go build "your/package"
才可以。
1 | go run . |
SayHello
函数已经放到独立的C文件中了,我们自然可以将对应的C文件编译打包为静态库或动态库文件供使用。
用Go实现C函数
hello.h
1 | // hello.h |
hello.go
用Go语言重新实现C语言接口的SayHello函数:
1 | // hello.go |
通过CGO的//export SayHello
指令将Go语言实现的函数SayHello
导出为C语言函数。为了适配CGO导出的C语言函数,我们禁止了在函数的声明语句中的const修饰符。需要注意的是,这里其实有两个版本的SayHello
函数:一个Go语言环境的;另一个是C语言环境的。cgo生成的C语言版本SayHello函数最终会通过桥接代码调用Go语言版本的SayHello函数。
main.go
1 | package main |
调用
1 | go run . |
面向C接口的Go编程
我们现在尝试将例子中的几个文件重新合并到一个Go文件
main.go
1 | package main |
调用
1 | go run main.go |
现在版本的CGO代码中C语言代码的比例已经很少了,但是我们依然可以进一步以Go语言的思维来提炼我们的CGO代码。通过分析可以发现SayHello
函数的参数如果可以直接使用Go字符串是最直接的。在Go1.10中CGO新增加了一个_GoString_
预定义的C语言类型,用来表示Go语言字符串。下面是改进后的代码:
1 | package main |
虽然看起来全部是Go语言代码,但是执行的时候是先从Go语言的main
函数,到CGO自动生成的C语言版本SayHello
桥接函数,最后又回到了Go语言环境的SayHello
函数。