cgo 从 go传递 slice 到 C
假设有一个Go [] int,并想要将它传递给C * int,你可以这样做:
package main
/*
#include<stdio.h>
void slice(int *a){
for(int i=0;i<4;i++){
printf("%d\n",a[i]);
}
}
*/
import "C"
import (
"fmt"
)
func main() {
intSlice := []C.int{108880, 18, 28, 83, 488} //使用cgo类型C.int
fmt.Println(&intSlice[0])
C.slice(&intSlice[0])
}
我们可以看到当我们把Go切片中第一个元素的地址传给C中的slice(int *a)
函数,我们就可以打印出原来Go切片中的所有元素,因此我们可以知道其实Go切片中第一个元素也就是C中Array的第一个元素,内存没有重新分配。下面我们通过一个案例来证实一下:
package main
import "C"
import (
"fmt"
"reflect"
"unsafe"
)
func main() {
var c int = 1
intSlice := []int{100, 1, 2, 3, 4}
newSlice := intSlice[c:]
fmt.Printf("切片的指针 %p\n", &intSlice) //0xc000004460
fmt.Printf("指向底层数组的第一个元素: %d\n", &intSlice[0]) //824633770800
fmt.Printf("指向newSlice第一个元素而不是数组: %d\n", &newSlice[0]) //824633770808
fmt.Printf("%v \n", newSlice[0]) //1
ref := reflect.ValueOf(newSlice)
t := reflect.TypeOf(newSlice)
//基础数组的起始地址
addr := int(ref.Pointer()) - (t.Align() * c) //824633770800
fmt.Printf("基础数据的地址: %d\n", addr) //824633770800
underArray := (*[5]int)(unsafe.Pointer(uintptr(addr)))
fmt.Println(*underArray) //[100 1 2 3 4]
}
cgo 从 C 传递 slice 到 go
这里一般会用在c的callback中。
需要加一个wrapper,比直接调用go函数中间多了一个转换步骤,但方便了许多。
执行流程为,c调用发起 -> c wrapper -> go export
.go:
//export a_function_callback
func a_function_callback(args []C.astruct) {}
.c:
extern void a_function_callback(GoSlice args);
void a_function_callback_c(args *C.astruct) {
GoSlice args_go;
args_go.data = args;
args_go.len = nargs;
args_go.cap = nargs;
return a_function_callback(args_go);
}
这里的GoSlice需要从 export 出来的 .h 中获取,或者 include 该 .h文件。 也可以直接拷贝过来,注意不要产生类型冲突。
#ifndef GO_CGO_PROLOGUE_H
#define GO_CGO_PROLOGUE_H
typedef long long GoInt64;
typedef GoInt64 GoInt;
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
#endif
C.CString 的自动化回收方法
一般使用 C.CString 时,定义一个临时变量存储结果,使用 defer C.free 回收这个临时的变量。
在 go 的 runtime 包中,有一个SetFinalizer方法,可以在回收对象的时候调用一个处理函数。
那么我们可以考虑使用它来自动化回收 C.CString产生的临时变量。
type CString struct {
Ptr *C.char
}
func CCString(s string) *CString {
p := C.CString(s)
this := &CString{Ptr: p}
runtime.SetFinalizer(this, freeCString)
return this
}
func freeCString(cs *CString) {
C.free(unsafe.Pointer(cs.Ptr))
}
在使用的时候,基本能嵌入到调用的位置,不需要显式定义一个临时变量并执行 defer C.free。
C.somefunc(CCString(s).Ptr)
注意,只在somefunc不接管传递的参数的所有权时调用这种方式,如果somefunc接管了所有权, 而在未来某时间内存被回收了,则会出现程序SEGFAULT崩溃。
这种可能会产生一点runtime效率损失,另外还有额外的内存分配,看情况使用。
bool 类型的转换处理
在 C 语言中,c99之前 bool 都是定义为 int的,要想转换为go的bool类型,比较曲折。
var bval_c C.GBoolean
var bval_go bool
if int(bval) == 1 {
bval_go = true
} else {
bval_go = false
}
如果go有三元运算符的话,也可一行代码实现该转换,问题是go没有三元运算符。
一般来说,对其包装一个简单的转换函数比较好,比如 c2gobool(C.GBoolean).
有时候不同的 C 项目中 bool 定义不同,所以一般需要为每个项目定义这么一个函数,而不能放在一个库中共用。
在 c99 之后,则没有这个问题,cgo能够直接判断是内置的bool类型,能够直接转换为go的bool。
var bval_c C._Bool
var bval_go = bool(bval_c)