CGO
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)
}