使用go与c进行混合编程。
cgo是外部函数接口(FFI),用来为C函数创建Go绑定的工具,除了cgo之外还有SWIG。
这里使用cgo构建一个简单的数据压缩程序。
有一个C语言实现的bzip2算法的压缩库,来自bzip.org的libbzip2包。C库如果小的话,可以使用纯Go进行移植,性能不关注的话可以使用os/exec以辅助子进程的方式来调用C程序。
但是当C库的API优先并且性能关键,实现复杂的时候使用cgo非常有意义。
在C库libbzip2中,需要结构体类型bz_stream,这个结构体包含输入和输出缓冲区以及三个C函数:BZ2_bzCompressInit分配流的缓冲区;BZ2_bzCompress压缩输入缓冲区的数据并写到输出缓冲区;BZ2_bzCompressEnd释放缓冲区。
我们要做的就是从Go语言中直接调用C函数BZ2_bzCompressInit和BZ2_bzCompress,而BZ2_bzCompress使用C语言先包装一下,如下:
#include <bzlib.h>
int bz2compress(bz_stream *s, int action, char *int, unsigned *inlen, char *out, unsigned *outlen) {
s->next_in = in;
s->avail_in = *inlen;
s->next_out = out;
s->avail_out = *outlen;
int r = BZ2_bzCompress(s, action)
*inlen -= s->avail_in;
*outlen -= s->avail_out;
return r;
}Go文件里有关C的代码写在注释里,然后import “C”,这个导入会在Go编译器看到它之前促使go build利用cgo工作预处理文件。
package bzip
/*
#cgo CFLAGS: -I/usr/include
#go LDFLAGS: -L/usr/lib -lbz2
#include <bzlib.h>
int bz2compress(bz_stream *s, int action, char *int, unsigned *inlen, char *out, unsigned *outlen);
*/
import "C"预处理过程中,cgo产生一个临时包,这个包里面包含了所有C函数和类型对应的Go语言声明,例如C.bz_stream和c.BZ2_bzCompressInit。cgo工具通过特殊的方式调用C编译器import “C”声明之前的注释来发现这些类型。 还可以通过#cgo指令来指定C工具链中其它的选项。
package bzip
/*
#cgo CFLAGS: -I/usr/include
#go LDFLAGS: -L/usr/lib -lbz2
#include <bzlib.h>
int bz2compress(bz_stream *s, int action, char *int, unsigned *inlen, char *out, unsigned *outlen);
*/
import "C"
import (
"io"
"unsafe"
)
type writer struct {
w io.Writer
stream *C.bz_stream
outbuf [64*1024]byte
}
// NewWriter 对于bzip2压缩的流返回一个writer
fun NewWriter(out io.Writer) io.WriterCloser {
const (
blockSize = 9
verbosity = 0
workFactor = 30
)
w := &writer{w:out, stream: C.bz2alloc()}
C.BZ2_bzCompressInit(w.stream, blockSize, verbosity, workFactor)
return w
}NewWriter嗲用C函数BZ2_bzCompressInit来初始化流的缓冲区。下面的writer掉哟个bz2compress函数机型数据压缩
func (w *writer) Write(data []byte) (int, error) {
if w.stream == nil {
panic("closed")
}
var total int
for len(data) > 0 {
inlen, outlen := C.uint(len(data)), C.uint(cap(w.outbuf))
C.zb2compress(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
}Close方法与Writer类似,将剩余数据清空并关闭数据流:
func (w *writer) Close() error {
if w.stream == nil {
panic("closed")
}
defer func() {
C.B2_bzCompressEnd(w.stream)
C.bz2free(w.stream)
w.stream = nil
}()
for {
inlen, outlen := C.uint(0), C.uint(cap(w.outbuf))
r := C.bz2compress(w.stream, C.BZ_FINISH, nil, &inlen, (*C.char)(unsafe.Pointer(&w.outbuf)), &outlen)
if _, err := w.w.Write(w.outbuf[:outlen]); err != nil {
return err
}
if r == C.BZ_STREAM_END {
return nil
}
}
}一个调用C语言的Go程序就完成了:
func main() {
w := bzip.NewWriter(os.Stdout)
if _, err := io.Copy(w, os.Stdin); err != nil {
log.Fatalf("bzipper: %v\n", err)
}
if err := w.Close(); err != nil {
log.Fatalf("bzipper: close %v\n", err)
}
}