cgo笔记一:入门

前言:写代码最重要的原则就是用进废退。很多年以前还会写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对标准库的errno宏做的特殊支持,也即是说在c代码包含errno库的时候,会有两个返回值,对应go的v,err :=func()这种编程模式:

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包中使用。其他的类型转换对应如下


cgo笔记一:入门_第1张图片
来源《go语言高级编程》

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的方式来解决。

你可能感兴趣的:(cgo笔记一:入门)