c++oding="utf-8" ?>
extern "c" 仅禁用名称修饰,无法解决类布局、stl实现差异、异常传播等abi不兼容问题;必须采用opaque pointer、纯c头文件、显式调用约定及内存/错误管理隔离方案。

不能靠 extern "C" 包一层 C++ 类或模板就完事——这只会让链接器看似成功、运行时在 std::string::~string() 里崩溃。
为什么 extern "C" 单独用不行
它只禁掉 name mangling,但完全不管 ABI 的其他致命部分:类内存布局随编译器/标准库版本变、std::vector 内部结构不统一、虚表偏移不可控、异常传播机制跨语言直接中断。更隐蔽的是,std::string 在 libstdc++ 和 libc++ 下的 SSO 实现不同,C 端传进一个指针,C++ 端一析构就 double-free。
常见错误现象:
-
nm -D libmy.so看到符号是my_create,但dlsym(handle, "my_create")返回NULL(实际是符号被隐式隐藏或未导出) - C 端传
const char* s = "hello",C++ 端用std::string(s)构造后立即释放——C 端没分配堆内存,C++ 端却误以为要接管所有权 - Windows 下用 MSVC 编译的 DLL,被 MinGW 编译的程序
LoadLibrary成功,但调用函数立刻访问违规
必须用 opaque pointer + 纯 C 头文件
对外头文件里不能出现任何 C++ 关键字、STL 类型、类定义、模板、引用。它得能被 gcc -x c 直接编译通过,这是 ABI 稳定性的最低门槛。
实操要点:
- 头文件中只写
typedef struct MyHandle MyHandle;,不定义结构体内容 - 构造函数封装为
MyHandle* my_handle_create(void),内部用new MyClass并返回static_cast<void>(ptr)</void> - 析构函数必须显式调用:
void my_handle_destroy(MyHandle* h),内部做delete static_cast<myclass>(h)</myclass> - 所有方法都带
MyHandle*参数,如int my_handle_process(MyHandle*, const char*, size_t),内部再转换类型 - 字符串统一用
const char*,长度由调用方保证以\0结尾;如需带长度,额外加size_t len参数 - 枚举必须指定底层类型:
enum my_error_t : int { MY_OK = 0, MY_ERROR = -1 };,禁用enum class
导出控制与调用约定必须显式声明
Linux 下默认 __attribute__((visibility("default"))) 不够稳,Windows 下 __declspec(dllexport) 必须和调用约定配对,否则跨语言调用会因栈不平衡崩溃。
关键配置:
- Linux:编译时加
-fvisibility=hidden,仅对导出函数加__attribute__((visibility("default"))) - Windows:函数声明前加
MYAPI __cdecl(推荐),避免__stdcall导致参数顺序错乱 - 头文件里统一定义宏:
#define MYAPI __attribute__((visibility("default"))) __cdecl(GCC/Clang)或#define MYAPI __declspec(dllexport) __cdecl(MSVC) - 禁止使用
thiscall或成员函数指针——它们根本不是 C ABI 范畴
异常、RTTI、STL 必须彻底隔离
所有 C++ 实现代码里不能抛异常,不能用 dynamic_cast、typeid,不能把 std::vector、std::string 作为函数参数或返回值暴露给头文件。
替代方案:
- 错误用返回码(
enum my_error_t)+ 可选的const char** error_msg输出描述 - 动态数组用
void*+size_t*输出长度,由调用方申请和释放内存(或提供my_free()函数) - 回调函数用纯函数指针,参数全为 C 基本类型或 opaque handle,禁止传 lambda 或 std::function
- 构建时关闭 RTTI:
-fno-rtti,关闭异常:-fno-exceptions,并确保链接时不带-lstdc++(除非你明确控制了 stdlib 版本)
真正难的不是写那几个 my_create 函数,而是坚持不在头文件里加哪怕一个 class、一个 std::、一个 &——只要破一次戒,ABI 就可能在下一个编译器更新后无声失效。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











