本文主要记录一些之前在项目中使用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)找找方法。