CGO

在Go中调用C的代码

//include "xxx.h"
import "C"
C.puts(helloworld)

.h文件中声明函数,.c/.cpp文件对应函数的定义

import “C”前面的注释即为要运行的C代码

默认C,如果需要用C++(cpp文件),要声明:

#include <iostream>
export "C"{
    #include <xxx.h>
}

Go生成C函数

通过使用extern "C",可以确保函数在C链接下可用,从而允许从C代码中调用它,而不会受到C++名称重整的影响。

//export funct
func funct (){
    ...
}
...
func main(){
    C.funct()
}

使用go重新实现C语言函数。

go函数C.CSstring()将字符串转化为(const)char*

上述函数运行过程:

Go的main函数执行,到CGO自动生成的C语言版本的funct桥接函数,回到GO中的funct函数。

C语言可以调用GO生成的函数,CGO省的”_cgi_export.h”包含了导出后的C语言函数声明

类型转化

数字类型转化:

C 语言类型 CGO 类型 Go 语言类型
char C.char byte
signed char C.schar int8
unsigned char C.uchar uint8
short C.short int16
unsigned short C.ushort uint16
int C.int int32
unsigned int C.uint uint32
long C.long int32
unsigned long C.ulong uint32
long long int C.longlong int64
unsigned long long int C.ulonglong uint64
float C.float float32
double C.double float64
size_t C.size_t uint

在CGO生成的_cgo_export.h头文件中,会为go的字符串,切片,字典,接口,管道等特殊数据结构生成对应的C语言类型。(只有切片和字符串有一定的调用价值,因CGO对他们的某些操作函数生成了对应的C语言版本)

typedef struct {const char *p; GoInt n;} GoString;
typedef void *GoMap;
typedef void *GoChan;
typedef struct {void *t; void *v;} GoInterface;
typedef struct {void *data; GoInt len; GoInt cap;} GoSlice;

结构体 通过C.struct_xxx访问C中定义的struct xxx的结构体类型,结构体内存布局按照C语言通用对齐规则(32/64位),若结构体的成员的名字是go的关键字,可以在成员名开头添加_来访问

/*
struct A {
    int type; // type 是 Go 语言的关键字
};
*/
import "C"
import "fmt"

func main() {
    var a C.struct_A
    fmt.Println(a._type) // _type 对应 type
}

如果恰好有两个成员 type,_type,则type将被屏蔽。

C语言中无法直接访问Go定义的结构体类型。

切片和字符串

Go中的切片和C中的指向一定长度内存的指针(slice实际上是简化版的动态数字)

go中的字符串是只读的

指针

C语言中的指针可以进行显示或隐式切换(隐式会在编译阶段给出warnings),go对不同类型的转换非常严格,无法自由转换。如果格式一致,指针可以通用。CGO解决了GO无法自由转换和进行指针运算的限制。

var p *X
var q *Y

q = (*Y)(unsafe.Pointer(p)) // *X => *Y
p = (*X)(unsafe.Pointer(q)) // *Y => *X

使用unsafe.Pointer作为中间桥接类型实现不同类型指针的转换,转换流程:*X->unsafe.pointer->*Y

同样,go也无法实现数值类型转化为指针类型,采用uintptr作为中间类型实现,转化过程:int32->uintptr->unsafe.pointer->char*

函数调用

C不支持多返回值,但Go需要error返回报错信息(如div除数为0),解决方案:

引入errno包

#include <errno.h>

int div(int a, int b) {
    if(b == 0) {
        errno = EINVAL;
        return 0;
    }
    return a/b;
}

CGO的处理:在CGO中如果有两个返回值,那么第二个返回值为errno,可以近似地将函数看作以下类型:

func C.div(a,b C.int)(C.int,[error])

void函数返回值

按照前文所述,对于没有返回值的void函数,如果出现errno,也只有一个返回值,CGO无法直接判断错误状态,CGO的处理如下:

import "C"
import "fmt"
func main() {
    _, err := C.noreturn()
    fmt.Println(err)
}