如何将任意Go变量传入接受void*类型参数的C函数?
结论先行
不能直接将interface{}类型参数转换为unsafe.Pointer传给C的set函数,会导致C侧拿到错误的内存地址。interface{}在Go底层是包含类型指针、值指针的二元结构体,直接取interface{}变量的地址传递的是这个结构体本身的地址,而非你要传入的Go变量的实际地址。
正确实现方案
需要结合reflect反射包获取interface{}中存储的实际值指针,再传递给C函数,完整实现如下:
1. CGO基础引入配置
首先在Go代码中通过注释引入C的set函数,导入必要的依赖包:
/* void set(void *data) { // do some work with data (which points to a Go variable) } */ import "C" import ( "errors" "reflect" "runtime" "unsafe" )
2. 实现Go包装函数
func InvokeSet(data interface{}) error { // 校验入参必须为指针类型,否则无法获取有效原始地址 val := reflect.ValueOf(data) if val.Kind() != reflect.Ptr { return errors.New("入参必须为指针类型,避免值拷贝导致C侧修改不生效") } // 取出interface中存储的实际值指针 rawPtr := unsafe.Pointer(val.Pointer()) // 调用C侧set函数 C.set(rawPtr) // 保证Go变量在C函数执行完成前不会被GC回收 runtime.KeepAlive(data) return nil }
注意事项
- 如果C侧需要长期持有传入的指针,不能仅依赖
runtime.KeepAlive,需要在Go侧主动持有该变量的引用直到C侧不再使用,避免GC回收后C侧访问野指针崩溃。 - 如果要传递Go字符串、切片等特殊类型给C侧,不要直接传变量指针:传字符串底层数组可使用
unsafe.Pointer(unsafe.StringData(str)),传切片底层数组可使用unsafe.Pointer(&slice[0]),同时要把长度也同步传给C侧。 - 不要传递Go栈上分配的变量指针给C侧长期使用,Go栈动态扩容时会移动栈变量地址,导致C侧指针失效。
内容的提问来源于stack exchange,提问作者user7698505
相关产品推荐
相关产品推荐

