前言:写代码最重要的原则就是用进废退。很多年以前还会写Java和sql存储,现在基本上忘记干净了,最近因为工作需要,要用到cgo。看的《go语言高级编程》,其实这本书当年就看过。作者功力很深,不过多年不用也不会用了,这里仅仅做一个笔记。用于cgo快速入门
什么情况下会用到cgo? golang这个语言在2015年就很火了,对我来说,不陡峭的学习曲线是我当年主要青睐的原因,但是老实说当年我的小伙伴之所以青睐go完全是因为cgo!公司的资深开发都是用c写核心代码,业务程序员就要用go写逻辑,加快项目进度。
真实的CGO程序一般都比较复杂。其实个人也不觉得c代码和go代码做深度交互有什么好处,还会增加调试难度。但是现实情况偶尔需要我们进行简单的互相调用,就像我偶尔需要调用公司同事写的c库(c小白)。
快速入门
//《go语言高级编程》中例
// hello.go
package main
//#include
import "C"
func main() {
C.puts(C.CString("Hello, World\n"))
}
代码通过import "C"语句启用CGO特性,代码中只要有Import "C",那么在go build的时候就已经会调用gcc,感兴趣可以手动test一下,这么几行代码编译还是要花费不少时间的。
c代码要用"//"开头,并放在import "C"相邻的上面才行,本例中main函数调用c代码来实现字符串输出(cgo没有fputs)
cgo的函数调用
通过CGO技术我们不仅仅可以在Go语言中调用C语言函数,也可以将Go语言函数导出为C语言函数。看一个简单的调用例子:
//《go语言高级编程》中例
/*
static int add(int a, int b) {
return a+b;
}
*/
import "C"
func main() {
C.add(1, 1)
}
c代码块通过" /.../"进行标记。 CGO会构造一个虚拟的C包。通过这个虚拟的C包可以调用C语言函数,当然在实际使用中,大概率是需要返回值的。看如下例子:
package main
/*
static int sum(int a, int b) {
return a+b;
}
*/
import "C"
import "fmt"
func main() {
v := C.sum(6, 3)
fmt.Println(v)
}
golang会自动推导C包中的函数返回数据类型,当然鉴于c和go类型差别较大,还不知道特殊类型会如何,感兴趣的可以自己测试一下。
按照《go语言高级编程》作者的说法,cgo对
package main
/*
#include
static int div(int a, int b) {
if(b == 0) {
errno = EINVAL;
return 0;
}
return a/b;
}
*/
import "C"
import "fmt"
func main() {
v0, err0 := C.div(2, 1)
fmt.Println(v0, err0)
v1, err1 := C.div(1, 0)
fmt.Println(v1, err1)
}
输出结果如下,这里可以把div函数近似的看成func C.div(a, b C.int) (C.int, [error])这种类型,第二个返回值是可忽略的error接口类型,底层对应 syscall.Errno 错误类型。
PS C:\Users\xx\go\src\test> go run .\hello.go
2
0 The device does not recognize the command.
cgo对errno做了特殊处理,对于c语言void类型函数依然有效,作为第二参数出现:
package main
/*
#include
static void ferr(int a) {
if(a == 0) {
errno = EINVAL;
}
}
*/
import "C"
import "fmt"
func main() {
_, err1 := C.ferr(0)
fmt.Println(err1)
}
输出结果如下,当然这个错误没有自定义错误看着清晰
The device does not recognize the command.
类型转换
在将go的变量作为cgo的变量使用时是要做类型转换的,看如下代码:
package main
/*
#include
void printint(int v) {
printf("printint: %d\n", v);
}
*/
import "C"
func main() {
v := 42
C.printint(C.int(v))
}
golang的变量v必须经过C包的C.int强制转换才可以在C包中使用。其他的类型转换对应如下
C语言的结构体、联合、枚举类型不能作为匿名成员被嵌入到Go语言的结构体中。在Go语言中,我们可以通过C.struct_xxx来访问C语言中定义的struct xxx结构体类型。
/*
struct A {
int i;
float f;
};
*/
import "C"
import "fmt"
func main() {
var a C.struct_A
fmt.Println(a.i)
fmt.Println(a.f)
}
如果结构体的成员名字中碰巧是Go语言的关键字,可以通过在成员名开头添加下划线来访问:
/*
struct A {
int type; // type 是 Go 语言的关键字
};
*/
import "C"
import "fmt"
func main() {
var a C.struct_A
fmt.Println(a._type) // _type 对应 type
}
CGO的C虚拟包提供了以下一组函数,用于Go语言和C语言之间数组和字符串的双向转换,注意写大点的程序时需要主动c.free释放内存,避免内存暴增:
// 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
// Go []byte slice to C array
// The C array 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.CBytes([]byte) unsafe.Pointer
// C string to Go string
func C.GoString(*C.char) string
// C data with explicit length to Go string
func C.GoStringN(*C.char, C.int) string
// C data with explicit length to Go []byte
func C.GoBytes(unsafe.Pointer, C.int) []byte
cgo还有其他类型的转换,建议看原作者文章,对于我来说,这样已经足够,我并不喜欢c和golang之间有太多耦合。混合编码本来就不应该存在(我更认为这是一种妥协模式),如果需要,还是用进程通信或者api的方式来解决。