C++函数不能直接导出为C API,因name mangling导致符号不一致,且C++特性(如类、模板、STL)破坏C ABI稳定性;必须用opaque pointer、extern "C"、纯C头文件及显式符号控制来保障二进制兼容。

为什么不能直接导出 C++ 函数作为 C API
因为 C++ 编译器会对函数名做 name mangling(如 _Z12myFunctionv),不同编译器、甚至同一编译器不同版本生成的符号都不一致;而 C ABI 要求符号名与源码中声明完全一致(如 my_function)。直接 extern "C" 包裹类成员或模板函数会编译失败,且无法隐藏 C++ 对象布局细节——这直接破坏 ABI 稳定性。
必须用 opaque pointer 模式隔离 C++ 实现
对外只暴露不透明指针类型(如 struct MyHandle;),所有对象生命周期和操作都通过 C 函数控制。这是维持 ABI 兼容的唯一可靠方式:即使内部 class MyClass 增加成员变量、改用 std::vector 替代裸数组,只要 MyHandle* 仍是 void* 或前向声明结构体,调用方二进制无需重编译。
-
MyHandle必须是不完整类型(仅前向声明),不能定义在头文件中 - 构造函数封装为
my_handle_create(),返回MyHandle* - 析构封装为
my_handle_destroy(MyHandle*),内部delete static_cast(h) - 所有方法调用都转为
my_handle_do_something(MyHandle*, ...),内部做static_cast
头文件里禁止出现任何 C++ 关键字和 STL 类型
C API 头文件(如 my_api.h)必须能被纯 C 编译器(gcc -x c)成功解析。这意味着:
- 不能有
class、template、std::string、std::vector、throw、constexpr - 字符串参数统一用
const char*,长度由调用方保证以\0结尾或额外传size_t len -
回调函数使用函数指针类型,如
typedef void (*my_callback_t)(int code, void* user_data) - 枚举必须显式指定底层类型并避免
enum class:enum my_error_t : int { MY_OK = 0, MY_ERROR = -1 };
链接与符号导出需显式控制
Windows 下默认不导出符号,Linux/macOS 默认导出所有全局符号——但你不希望用户链接到未文档化的内部函数。必须精确控制可见性:
立即学习“C++免费学习笔记(深入)”;
- Windows:在函数声明前加
__declspec(dllexport)(构建 DLL 时),头文件中用宏切换:#ifdef BUILDING_MY_API #define MY_API __declspec(dllexport) #else #define MY_API __declspec(dllimport) #endif
- Linux/macOS:用
__attribute__((visibility("default")))标记导出函数,并编译时加-fvisibility=hidden,否则所有符号都暴露 - 验证导出符号:Linux 用
nm -D libmy.so | grep my_;Windows 用dumpbin /exports my.dll
ABI 稳定性不是“加个 extern "C" 就完事”,而是从头文件设计、内存管理、错误传递到符号导出,每一步都得切断 C++ 实现细节的泄漏。最常被忽略的是:把 std::shared_ptr 的裸指针传给 C 层,或者在头文件里不小心 #include —— 这些都会让 ABI 在你没意识到时就碎掉。











