本地的 go build 编译的时候默认是启用 C/C++ 支持的,对应的编译开关是 CGO_ENABLED,交叉构建需要使用 CGO_ENABLED=1,最简单的 CGO 程序如下:
package main
// cgo enabled default
import "C"
func main() {}
没错,import “C” 可以导入 C 的类型,表明需要调用 C 库,CGO 的特色之一就可以直接在 go 文件里编写 C 语言的模块,编写的 C 代码部分必须在 import “C” 之上,import 语句之前不能有空行,下面的例子调用了 C 的 puts 函数:
package main
//#include <stdio.h>
import "C"
func main() {
C.puts(C.CString("hi, cgo lib"))
}
C.puts 调用 puts 函数,它的原型是:
int puts(const char *str)
C.CString 是把 go 的 string 转换到 C 的 *char,不过这个转换不会自动释放内存,需用调用 free 释放,下面是 CString 的签名:
// Go string to C string
// The C string is allocated in the C heap using malloc.
// It is the caller's responsibility to arrange for it to be
// freed, such as by calling C.free (be sure to include stdlib.h
// if C.free is needed).
func C.CString(string) *C.char
和 C.CString 相似是的 C.CBytes([]byte) unsafe.Pointer 函数用于转换 go 的 []byte 切片,也需要自行 free,上面的例子完整的代码是:
//#include <stdio.h>
//#include <stdlib.h>
import "C"
import "unsafe"
func main() {
cstr := C.CString("hi, cgo lib")
C.puts(cstr)
C.free(unsafe.Pointer(cstr))
}
unsafe.Pointer 获得指针的值,传递给 C 的 free 函数,free 函数 stdlib.h 中,所以需要包含。反过来,如果把 C 的 *char 换换成 go 的 string,可以使用 C.GoString 函数,类似的也有 func C.GoBytes(unsafe.Pointer, C.int) []byte。
str := C.GoString(cstr)
fmt.Println(str)
CGO 会自动构建当前目录下的 C 源文件,所以把可以把上面 C 的部分独立出来,这样可以实现 C 的模块化:
// say.h
void Say(const char* s);
// say.c
#include "say.h"
#include <stdio.h>
void Say(const char* s) {
puts(s);
}
// main.go
package main
//#include "say.h"
import "C"
func main() {
C.Say(C.CString("hi, cgo"))
}
请注意,不能使用 go run main.go 来运行了,要使用 go run . 来运行,不仅 go 可以调用 C 模块的函数,C 的函数也可以由 go 来实现:
// say.h
void Say(char* s);
// say.go
package main
import "C"
import "fmt"
//export Say
func Say(s *C.char) {
fmt.Print(C.GoString(s))
}
// main.go
package main
//#include <say.h>
import "C"
func main() {
C.Say(C.CString("hi, cgo"))
}
say.h 头文件申明了 Say 函数的签名,但是它使用 say.go 来实现的函数体,注意两点:
1. // export Say 是必须的,表示把 go 的函数和导出为 C 的函数
2. 和 C 模块化一样,也得使用 go run . 运行否则链接失败
当然也可以把这三个文件合并到一个 go 文件里,又可以使用 go run main.go 运行了。
package main
//void Say(char* s);
import "C"
import (
"fmt"
)
func main() {
C.Say(C.CString("hi, cgo"))
}
//export Say
func Say(s *C.char) {
fmt.Println(C.GoString(s))
}
CGO 看似实现了 C 和 Go 的直接调用,实施上并非如此,当发现 import “C” 时,编译会产生 C 和 Go 之间的中介代码,执行 go tool cgo main.go 可以查看到中间代码:
27-call-c-module[master*] go tool cgo main.go
27-call-c-module[master*] tree _obj
_obj
├── _cgo_export.c
├── _cgo_export.h
├── _cgo_flags
├── _cgo_gotypes.go
├── _cgo_main.c
├── main.cgo1.go
└── main.cgo2.c
CGO 还支持 # cgo CFLAGS LDFLAGS 设定编译连接参数,更多 CGO 的知识参考官网:
1.
2.
本章节的代码