本文主要记录一些之前在项目中使用cgo调用C语言的方法和遇到的一些问题。
在go文件中写c代码
使用cgo时需要先import “C”,然后在上面的注释部分内(/…/)写C代码。其中可以通过CFLAGS和LDFLAGS来指定编译参数。如下代码。
package main
/*
#cgo CFLAGS: -I.
#cgo LDFLAGS: -L.
#include <stdio.h>
void print_something()
{
printf("hello world\n");
}
*/
import "C"
func main(){
C.print_something()
}
go调用c动态库
代码如下。
@func.h
#ifndef _FUN_H_
#define _FUN_H_
void print_something();
#endif
@func.c
#include <stdio.h>
void print_something()
{
printf("hello world.\n");
}
将c代码编译生成动态库,由go代码通过头文件进行调用。
gcc -fPIC -shared fun.c -o libfun.so
package main
/*
#cgo CFLAGS: -I.
#cgo LDFLAGS: -L. -lfun
#include "fun.h"
*/
import "C"
func main(){
C.print_something();
}
其中有一点需要注意,cgo是不可以直接调用c++的代码的。所以如果需要调用,必须在c的层面上进行一次封装,go调c,c调c++。
go和c之间的类型转换
c中拥有的类型都可以以C.type的形式提供给go进行传参时的转换,比如。
char --> C.char --> byte
signed char --> C.schar --> int8
unsigned char --> C.uchar --> uint8
short int --> C.short --> int16
short unsigned int --> C.ushort --> uint16
int --> C.int --> int
unsigned int --> C.uint --> uint32
long int --> C.long --> int32 or int64
long unsigned int --> C.ulong --> uint32 or uint64
long long int --> C.longlong --> int64
long long unsigned int --> C.ulonglong --> uint64
float --> C.float --> float32
double --> C.double --> float64
wchar_t --> C.wchar_t -->
void * -> unsafe.Pointer
上述是一些基本类型的转换,如果需要传递指针类型,需要先进行unsafe.Pointer的转化,再进行强转。
将[]byte作为char*传递
#将[]byte作为char*传递
str := []byte("hello world")
tmp := (*C.char)(unsafe.Pointer(&str[0]))
由于slice自身是一个结构体,我们需要传递的只是其中的data部分,所以需要取数据部分的首地址。这里还有个地方需要注意,由于我们传递的是数据段的首地址,所以这段数据并没有结束符,所以需要我们手动添加结束符,或者同时传递slice的真实长度,避免内存越界。
传递函数指针
package main
/*
#cgo CFLAGS: -I.
#cgo LDFLAGS: -L.
typedef void (*callback)();
static void do_cb(callback fun)
{
fun();
}
void cb();
*/
import "C"
import "fmt"
//export cb
func cb(){
fmt.Println("hello world")
}
func main(){
C.do_cb(C.callback(C.cb))
}
这里的方式比较特别,先在c代码中声明函数类型,再export到go代码中进行定义,个人推测可能是由于编译出的函数名不同,导致不能直接进行强制转换。注意要在export的函数名上进行注释说明,不然编译器会认定两个cb是不同的函数。
上述方法只是总结了作者在项目中使用的一些方法。如果还有其他需求,可以尝试在源码中($gopath/cmd/cgo)找找方法。