主机构造体含构造函数时,DLL返回结构体读取垃圾值或触发段错误
问题触发原因
- 核心原因是C++ 结构体的 ABI 兼容性被自定义构造函数破坏:当你为 C++ 结构体添加自定义构造函数、自定义拷贝构造函数后,该结构体不再符合 标准布局类型(Standard Layout Type) / POD 类型的要求,跨 FFI 调用时的内存布局、参数传递规则都会和 C ABI 不兼容。
- 你使用的 Windows 平台 MSVC 编译器下,对于符合 C ABI 的 POD 小结构体(比如你这个只有两个float、总大小8字节的Vec2),传值调用时会直接将结构体塞进寄存器传递;但非 POD 类型的结构体哪怕大小很小,传值时会被编译器隐式转为指针传递。而 Rust 侧用
#[repr(C)]定义的同结构是按 C ABI 规则接收两个按值传递的结构体,两端参数传递规则不匹配,相当于把野指针/非法地址传给了 Rust 侧,触发 Segmentation fault。
解决方案
你可以选择以下任意一种方案修复:
方案1:保持 C++ 结构体为标准布局类型
移除自定义构造函数和拷贝构造函数,若需要便捷构造能力,改用静态工厂方法实现即可,此时结构体仍符合 C ABI 要求,和 Rust 侧 #[repr(C)] 定义的结构体完全兼容:
struct Vec2 { float X; float Y; // 用静态工厂方法替代自定义构造函数,不破坏标准布局特性 static Vec2 make(float x, float y) { Vec2 res; res.X = x; res.Y = y; return res; } }; // 使用方式 Vec2 a = Vec2::make(1.0, 2.0); Vec2 b = Vec2::make(3.0, 4.0);
你可以通过静态断言验证结构体是否符合标准布局要求:
#include <type_traits> static_assert(std::is_standard_layout_v<Vec2>, "Vec2 must be standard layout for FFI");
方案2:FFI 接口统一用指针传递结构体
如果你不想修改 C++ 侧的构造函数定义,可以将跨 DLL 调用的接口参数、返回值都改为结构体指针传递,避开值传递的 ABI 差异:
- C++ 侧函数指针声明改为:
Vec2* (*)(Vec2*, Vec2*) - Rust 侧导出函数签名改为对应指针类型即可:
extern "C" fn rust(a: *const Vec2, b: *const Vec2) -> *mut Vec2
额外注意点
Rust 侧的结构体必须加 #[repr(C)] 强制指定 C 布局,同时导出函数必须标记 extern "C" 明确使用 C 调用约定,C++ 侧的函数指针也建议加 extern "C" 修饰,避免不同调用约定带来的额外问题。
内容的提问来源于stack exchange,提问作者Danguafer
相关产品推荐
相关产品推荐

