Rust:DLL 输出对象的生命周期管理
在 Rust 开发 DLL 时安全地将对象地址传递给 C 语言并保持对象不被析构,需要正确处理所有权和生命周期。以下是详细方案和代码示例:
核心思路
- 堆分配对象 - 使用
Box
在堆上分配对象 - 泄漏所有权 - 使用
Box::into_raw
防止 Rust 自动析构 - 明确所有权协议 - 提供销毁函数由 C 语言调用方管理生命周期
- 线程安全 - 使用
Send + Sync
确保跨线程安全
完整示例
Rust DLL 实现
// src/lib.rs
use std::sync::Mutex;#[repr(C)]
pub struct Counter {value: Mutex<i32>, // 内部可变性保证线程安全
}impl Counter {pub fn new(init: i32) -> Self {Counter {value: Mutex::new(init),}}pub fn increment(&self) {let mut lock = self.value.lock().unwrap();*lock += 1;}pub fn get_value(&self) -> i32 {*self.value.lock().unwrap()}
}// 创建对象并返回指针 (所有权转移给调用方)
#[no_mangle]
pub extern "C" fn counter_create(init: i32) -> *mut Counter {Box::into_raw(Box::new(Counter::new(init)))
}// 增加计数器值
#[no_mangle]
pub extern "C" fn counter_increment(counter: *mut Counter) {assert!(!counter.is_null());unsafe { &*counter }.increment();
}// 获取当前值
#[no_mangle]
pub extern "C" fn counter_get_value(counter: *const Counter) -> i32 {assert!(!counter.is_null());unsafe { &*counter }.get_value()
}// 销毁对象(必须由调用方显式调用)
#[no_mangle]
pub extern "C" fn counter_destroy(counter: *mut Counter) {if !counter.is_null() {unsafe { Box::from_raw(counter) }; // 转为 Box 后立即析构}
}
C 语言调用示例
#include <stdio.h>
#include <windows.h>typedef void* CounterHandle;// 声明 DLL 函数
CounterHandle counter_create(int init);
void counter_increment(CounterHandle counter);
int counter_get_value(CounterHandle counter);
void counter_destroy(CounterHandle counter);int main() {HMODULE dll = LoadLibrary("counter.dll");if (!dll) {fprintf(stderr, "Failed to load DLL\n");return 1;}// 获取函数指针CounterHandle (*create)(int) = (CounterHandle(*)(int))GetProcAddress(dll, "counter_create");void (*increment)(CounterHandle) = (void(*)(CounterHandle))GetProcAddress(dll, "counter_increment");int (*get_value)(CounterHandle) = (int(*)(CounterHandle))GetProcAddress(dll, "counter_get_value");void (*destroy)(CounterHandle) = (void(*)(CounterHandle))GetProcAddress(dll, "counter_destroy");// 创建计数器对象CounterHandle counter = create(10);// 使用计数器increment(counter);printf("Current value: %d\n", get_value(counter)); // 输出 11increment(counter);printf("Current value: %d\n", get_value(counter)); // 输出 12// 销毁对象destroy(counter);FreeLibrary(dll);return 0;
}
关键安全措施
-
所有权转移协议
counter_create
→ 将所有权转移给 C 调用方counter_destroy
→ C 调用方交回所有权,触发析构- 所有其他操作(
increment
/get_value
)只借用不获取所有权
-
空指针检查
assert!(!counter.is_null());
防止空指针解引用(在实际项目中可能需要更健壮的错误处理)
-
线程安全保证
use std::sync::Mutex;
使用互斥锁确保内部可变性是线程安全的
-
内存安全
Box::into_raw
转换后,Rust 停止管理生命周期Box::from_raw
恢复所有权后立即析构
-
FFI 安全
#[repr(C)] pub struct Counter { ... }
保证 C 兼容的内存布局
生命周期管理建议
-
强制销毁协议
- 在文档中明确要求 C 调用方必须调用
counter_destroy
- 可使用 RAII 封装(如 C++ 的
unique_ptr
)管理对象生命周期
- 在文档中明确要求 C 调用方必须调用
-
避免跨 DLL 边界析构
- 所有内存操作应在同一 DLL 模块中进行
- 建议对象创建和销毁都在 Rust DLL 中完成
-
版本兼容
- 当修改对象结构时:
#[repr(C)] pub struct CounterV2 {// 不要修改现有字段顺序value: Mutex<i32>,// 新增字段永远放在末尾max_value: i32, }
替代方案:引用计数
如果需要共享所有权,可考虑 Arc
:
#[no_mangle]
pub extern "C" fn counter_create_arc(init: i32) -> *const Counter {Arc::into_raw(Arc::new(Counter::new(init)))
}#[no_mangle]
pub extern "C" fn counter_clone(counter: *const Counter) -> *const Counter {unsafe { Arc::increment_strong_count(counter) };counter
}#[no_mangle]
pub extern "C" fn counter_destroy_arc(counter: *const Counter) {unsafe { Arc::decrement_strong_count(counter) };
}
警告:使用引用计数时,调用方必须严格配对 clone/destroy 调用
最佳实践
- 使用
abi_stable
库 - 提供更稳定的 ABI 接口 - 自动化测试 - 使用
cbindgen
生成头文件并编写 C/C++ 测试 - 内存分析 - 使用
Valgrind
或 Windows CRT 调试堆检测内存泄漏
通过这种方式,您可以安全地将 Rust 对象传递给 C,同时保持明确的所有权和生命周期管理。