通过cgo调用C代码| 青训营;

141 阅读2分钟

Go程序可能会遇到要访问C语言的某些硬件驱动函数的场景,或者是从一个C++语言实现的 嵌入式数据库查询记录的场景,或者是使用Fortran语言实现的一些线性代数库的场景。C语 言作为一个通用语言,很多库会选择提供一个C兼容的API,然后用其他不同的编程语言实现

使用了一个Go语言自带的叫cgo的用于支 援C语言函数调用的工具。这类工具一般被称为 foreign-function interfaces (简称ffi), 并且 在类似工具中cgo也不是唯一的。SWIG( swig.org )是另一个类似的且被广泛使用的 工具,SWIG提供了很多复杂特性以支援C++的特性

在标准库的 compress/... 子包有很多流行的压缩算法的编码和解码实现,包括流行的LZW压 缩算法(Unix的compress命令用的算法)和DEFLATE压缩算法(GNU gzip命令用的算 法)。这些包的API的细节虽然有些差异,但是它们都提供了针对 io.Writer类型输出的压缩接 口和提供了针对io.Reader类型输入的解压缩接口。例如:


package gzip // compress/gzip 
func NewWriter(w io.Writer) io.WriteCloser 
func NewReader(r io.Reader) (io.ReadCloser, error)

如果是比较小的C语言库,我们完全可以用纯Go语言重新实现一遍。如果我们对性能也没有 特殊要求的话,我们还可以用os/exec包的方法将C编写的应用程序作为一个子进程运行。只 有当你需要使用复杂而且性能更高的底层C接口时,就是使用cgo的场景了

在预处理过程中,cgo工具为生成一个临时包用于包含所有在Go语言中访问的C语言的函数或 类型。

在cgo注释中还可以包含#cgo指令,用于给C语言工具链指定特殊的参数。

下面是Write方法的实现,返回成功压缩数据的大小,主体是一个循环中调用C语言的 bz2compress函数实现的。从代码可以看到,Go程序可以访问C语言的bz_stream、char和 uint类型,还可以访问bz2compress等函数,甚至可以访问C语言中像BZ_RUN那样的宏定 义,全部都是以C.x语法访问。其中C.uint类型和Go语言的uint类型并不相同,即使它们具有 相同的大小也是不同的类型。

func (w *writer) Write(data []byte) (int, error) {
if w.stream == nil { 
panic("closed") 
} 
var total int // uncompressed bytes written 
for len(data) > 0 { inlen, outlen := C.uint(len(data)), C.uint(cap(w.outbuf))
C.bz2compress(w.stream, C.BZ_RUN,
(*C.char)(unsafe.Pointer(&data[0])), &inlen,
(*C.char)(unsafe.Pointer(&w.outbuf)), &outlen)
total += int(inlen) 
data = data[inlen:] 
if _, err := w.w.Write(w.outbuf[:outlen]); err != nil {
return total, err 
} 
}
return total, nil 
}