本章介绍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的头文件:
package main
/*
include
include
include
cgo pkg-config: sqlite3
include
include
include
include”pi.h”
include
cgo CFLAGS: -I${SRCDIR}
cgo LDFLAGS: -L${SRCDIR} -lpi
include “pi.h”
*/
import”C”
import”fmt”
funcmain() {
fmt.Println(“计算PI值:”)
v := C.GoString(C.calc())
fmt.Println(v)
}
编译: go build main3.go ,因为动态库和生成的可执行文件 main3 在同一个目录下,没有问题,执行main3:
smallnestMBP:ch9 smallnest$./main3 计算PI值:
31415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744623799627495673518857527248912279381830119491298336733624406566430860213949463952247371907021798609437027705392171762931767523846748184676694051320005681271452635608277857713427577896091736371787214684409012249534301465495853710507922796892589235420199561121290219608640344181598136297747713099605187072113499999983729780499510597317328160963185
上面这个计算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.sizeofT,如C.sizeofstruct_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 等方法和类型。
举个栗子:
h, err := windows.LoadLibrary(“kernel32.dll”)
iferr !=nil{
abort(“LoadLibrary”, err)
}
deferwindows.FreeLibrary(h)
proc, err := windows.GetProcAddress(h, “GetVersion”)
iferr !=nil{
abort(“GetProcAddress”, err)
}
r, , := syscall.Syscall(uintptr(proc),0,0,0,0)
major := byte(r)
minor := uint8(r >>8)
build := uint16(r >>16)
print(“windows version “, major,”.”, minor,” (Build “, build,”)\n”)
其它平台我还没有发现官方的调用.so或者.dylib的方法, 但是我看到有第三方的作者写了相应的库,提供类似C中的dlopen和dlsym方法: Runtime dynamic library loader
参考
- 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/staticallycompiledgoprogramsalwaysevenwithcgousingmusl/
- http://blog.giorgis.io/cgo-examples
- http://blog.madewithdrew.com/post/statically-linking-c-to-go/
- https://github.com/hyper-carrot/gocommandtutorial/blob/master/0.13.md
- https://www.goinggo.net/2013/08/using-c-dynamic-libraries-in-go-programs.html
有疑问加站长微信联系(非本文作者)