Go函数导出为C可调用符号需用//export标记且签名仅含C兼容类型;传函数指针给C回调时参数类型须严格匹配;Go回调函数必须由全局变量持有以防GC回收;动态库加载需用RTLD_GLOBAL确保符号可见。

Go函数怎么导出成C能调用的符号
必须用 //export 注释标记函数,且函数签名只能含 C 兼容类型(int、*C.char、C.int 等),不能出现 string、[]byte、interface{}。Go 编译器会自动生成对应 C 函数声明并写入 _cgo_export.c,C 侧不可再手动声明同名函数,否则触发 conflicting types 编译错误。
传函数指针给C回调时,参数类型要严格匹配
C 头文件中定义的函数指针类型(如 typedef int (*fnc)(int))和 Go 中传入的函数签名必须完全一致:返回值、参数个数、每个参数类型都要一一对应。常见坑是 C 期望 char*,但 Go 传了 *C.char —— 这看似合理,实则错:Go 函数参数应直接写 p *C.char,而非 p string 或 p []byte;否则 C 调用时解引用会崩溃。
- ✅ 正确:
func notify(p *C.char) C.int { ... }→ 传给typedef int (*fnc2)(char*) - ❌ 错误:
func notify(s string) C.int { ... }→ C 传的是char*地址,Go 函数却按 string 解析,内存布局不匹配
调用C回调函数前,必须确保Go函数不会被GC回收
Go 函数本身是运行时对象,若仅临时传入 C 并存储其地址,后续 C 异步调用时该函数可能已被 GC 清理——这不是“悬空指针”,而是整个函数代码段失效。解决方法只有一种:全局变量持有函数引用。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 声明一个包级变量:
var globalNotify func(*C.char) C.int - 赋值:
globalNotify = notify - 再把
unsafe.Pointer(C.funcptr(&globalNotify))传给 C(需配合runtime.SetFinalizer或显式管理生命周期) - ⚠️ 注意:
unsafe.Pointer(C.funcptr(...))是唯一合法获取 Go 函数地址的方式,直接对函数名取地址(¬ify)非法
C动态库加载后,如何验证函数指针已正确绑定
动态加载(如 dlopen + dlsym)场景下,C 侧拿到的函数指针是否有效,不能仅靠编译通过判断。最简验证是在 Go 回调函数第一行加 fmt.Printf("callback entered\n") 并确保它能输出;更稳妥的做法是让回调返回一个固定值(如 C.int(42)),并在 C 侧检查返回值是否符合预期。
立即学习“go语言免费学习笔记(深入)”;
容易被忽略的是:C 动态库若使用 RTLD_LOCAL 加载,其内部符号默认不可被外部回调函数访问;必须用 RTLD_GLOBAL,否则 Go 函数地址虽传过去,C 执行时仍可能报 undefined symbol 或静默失败。

















