FFI中不能直接传递std::shared_ptr或std::unique_ptr,因其含虚表、引用计数块等非ABI稳定内容,跨语言将导致崩溃;应使用void*或opaque struct配合句柄映射表,并严格管理生命周期。

FFI 中传递 C++ 对象指针为什么不能直接用 std::shared_ptr 或 std::unique_ptr
因为大多数 FFI(如 Rust、Python 的 C API、Go 的 cgo)只认裸指针 void* 或 C 兼容结构体,C++ 智能指针包含虚表、引用计数控制块、模板特化等非 ABI 稳定内容,跨语言时会立即崩溃或未定义行为。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 对外暴露的函数参数/返回值中,一律用
void*或自定义的 opaque struct(如struct MyObject;)作为句柄 - 在 C++ 侧维护一个全局或线程局部的句柄映射表(如
std::unordered_map<uint64_t std::shared_ptr>></uint64_t>),把整数 ID 或指针地址转为安全可查的智能指针 - 避免把
this直接转成void*后传给外部——若对象被 move 或析构,外部再解引用就会 dangling - 若必须传裸指针,确保生命周期由 C++ 侧严格管理:提供
myobject_destroy()显式释放,并文档注明“调用者不负责内存”
如何让 C++ 类在 FFI 中支持多态和虚函数调用
虚函数表(vtable)是编译器实现细节,跨语言不可靠。Rust 或 Python 不知道你的 MyBase::process() 在 vtable 第几个槽位,也不能安全 call。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 用 C 风格函数指针表模拟虚函数:定义
struct MyObjectVTable { void (*process)(void* self); int (*get_id)(void* self); };,C++ 构造时填充对应静态函数 - 每个类实例绑定一个
const MyObjectVTable*,通过vtable->process(self)调用,完全绕过 C++ vtable - 禁止在 FFI 接口里出现
virtual、dynamic_cast、typeid—— 这些在 C ABI 下无意义 - 如果目标语言支持回调(如 Rust 的
extern "C" fn),可反向注册函数指针到 C++,但注意调用栈跨越时不能抛 C++ 异常
extern "C" 函数里访问指针成员变量为何常崩在 Release 模式
常见现象:Debug 下正常,Release 下读 ((MyClass*)ptr)->data 返回乱码或段错误。根本原因是编译器优化了内联、重排或去除了未使用的字段,而 FFI 客户端按固定 offset 访问内存布局。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 所有被 FFI 使用的 C++ 类必须用
struct+public:声明,且加上#pragma pack(1)或[[gnu::packed]](GCC/Clang)强制紧凑布局 - 禁用继承、虚函数、非 POD 成员(如
std::string、std::vector);改用 C 风格数组 + size 字段(char name[256]; uint32_t name_len;) - 用
static_assert(std::is_standard_layout_v<mystruct>)</mystruct>和static_assert(std::is_trivially_copyable_v<mystruct>)</mystruct>在编译期卡住非法定义 - 不要依赖
sizeof(MyClass)—— 改用显式MYSTRUCT_SIZE宏,在头文件中与客户端同步定义
Python ctypes 或 Rust bindgen 怎么安全拿到 C++ 对象指针并防止 double-free
典型错误是 Python 侧用 ctypes.cast(ptr, ctypes.py_object) 强转,或 Rust 用 Box::from_raw() 后忘记标记原始指针为已转移,导致 C++ 和目标语言各自析构同一块内存。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- C++ 侧导出两个函数:
MyObject* myobject_new()(返回裸指针)和void myobject_free(MyObject* obj)(唯一合法释放入口) - Python 用
ctypes.CDLL(...).myobject_new.restype = ctypes.c_void_p,之后全当c_void_p处理,绝不用cast转成任意 Python 类型 - Rust 中用
extern "C" { fn myobject_free(obj: *mut std::ffi::c_void); },并在Drop实现里调用它;用std::mem::forget()防止 Box 自动 drop - 加一层引用计数包装:C++ 提供
myobject_ref()/myobject_unref(),客户端每次“持有”都 ref,drop 时 unref,底层用std::shared_ptr管理真实对象
最易被忽略的一点:FFI 接口的线程安全性不是默认的。即使你用了 std::shared_ptr,它的引用计数原子操作在不同编译器/STL 版本下可能不一致。务必在文档里写清“所有函数是否可重入”,并在关键路径加 std::mutex 或用 std::atomic 手动管理计数 —— 别依赖 STL 的“应该线程安全”这种模糊承诺。


















