c++oding="utf-8" ?>

Go 可通过 CGO 将函数指针导出为 C 可调用符号,从而支持 Windows 下 C++ DLL 的反向回调;关键在于正确声明 //export 函数、禁用 Go 垃圾回收器对回调参数的干扰,并确保调用约定(如 __stdcall)与 C++ 端严格一致。
go 可通过 cgo 将函数指针导出为 c 可调用符号,从而支持 windows 下 c++ dll 的反向回调;关键在于正确声明 `//export` 函数、禁用 go 垃圾回收器对回调参数的干扰,并确保调用约定(如 `__stdcall`)与 c++ 端严格一致。
在 Windows 平台使用 CGO 实现 C++ DLL 对 Go 函数的回调,是跨语言集成中的典型需求。虽然 Go 默认不支持将闭包或方法直接作为 C 函数指针传递,但可通过 //export 机制导出顶层、无闭包捕获、C 调用约定兼容的 Go 函数,供 C/C++ 动态链接库安全调用。
✅ 正确实现步骤
-
在 Go 侧定义并导出回调函数
使用//export注释声明函数,并确保其签名完全符合 C ABI(例如使用C.int、*C.char等类型)。避免返回 Go 内存(如string、[]byte),若需返回字符串,应转换为*C.char并由 C 端负责释放(或使用C.CString+ 显式C.free管理)。
package main
/*
#include <stdio.h>
// 声明 C++ DLL 中注册回调的函数原型(示例)
extern void register_callback(void (*cb)(int, const char*));
*/
import "C"
import (
"C"
"unsafe"
)
//export goCallback
func goCallback(code C.int, msg *C.char) {
// 注意:msg 是 C 字符串,需转为 Go string(仅读取,不修改)
s := C.GoString(msg)
println("Go callback received:", int(code), s)
}
// 主调用入口:向 DLL 注册回调
func init() {
C.register_callback((*[0]byte)(unsafe.Pointer(C.goCallback)))
}</stdio.h>
-
C++ DLL 端需匹配调用约定
Windows 下常见 DLL 导出函数默认为__cdecl,但若 Go 使用//export(CGO 自动生成__cdecl),则 C++ 端注册函数也应使用extern "C"+__cdecl。若 DLL 强制要求__stdcall(如某些 COM 或旧 WinAPI 风格),需在 Go 侧通过汇编 stub 或 C wrapper 适配——标准 CGO 不直接支持__stdcall导出,推荐统一使用__cdecl。
C++ 示例(DLL 导出头文件):
// callback.h
#ifdef EXPORTING_DLL
#define DLL_API __declspec(dllexport)
#else
#define DLL_API __declspec(dllimport)
#endif
extern "C" {
typedef void (__cdecl *CallbackFunc)(int, const char*);
DLL_API void register_callback(CallbackFunc cb);
}
- 关键注意事项
- ❗ 禁止在回调中调用 Go 运行时阻塞操作(如
time.Sleep, channel 操作未加锁等),否则可能引发调度死锁; - ❗ *所有传入的 Go 指针(如 `C.char`)必须由 C 分配或明确生命周期可控**;Go 不能直接持有 C 分配内存的长期引用;
- ❗ 回调函数必须为包级顶层函数,不可是方法或闭包(因无法生成稳定函数地址);
- ✅ 使用
runtime.LockOSThread()可在回调内绑定 OS 线程(如需调用 Windows GUI API),但需成对调用runtime.UnlockOSThread(); - ✅ 推荐在 Go 初始化阶段(如
init())完成回调注册,避免竞态。
总结
Go 与 C++ DLL 的双向交互完全可行,核心在于:导出纯 C 兼容函数 + 显式内存/线程管理 + 调用约定对齐。官方文档 cgo Function Pointer Callbacks 提供了权威指导和最小可行示例,建议结合 go tool cgo -godefs 检查类型映射,并使用 go build -buildmode=c-shared 验证符号导出(适用于 DLL 依赖 Go 运行时场景)。实际项目中,建议封装一层 C bridge 头文件,提升可维护性与跨平台迁移能力。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











