我正在为(主要是 C 风格)C++ 插件 SDK 编写一个 Rust 包装器。插件主机是一个运行事件循环的图形桌面应用程序。该插件定期作为该事件循环的一部分被调用。每当这种情况发生时,插件就具有控制权并可以调用任意主机函数。
我想要包装的一个 C 函数返回一个原始指针。该函数返回后,该指针就保证是有效的 C 字符串,因此可以安全地取消引用它。但是,在插件回调返回后(从而将控制权交还给主机),指针可能会变得过时。我怎样才能为此编写一个符合人体工程学的函数包装器,它不会在某些时候导致未定义的行为,例如当消费者尝试在下一个事件循环周期中访问该字符串时?
我考虑过以下方法:
1.返回一个拥有的字符串
我可以立即取消引用指针并将内容复制到拥有的CString
:
pub fn get_string_from_host() -> CString {
let ptr: *const c_char = unsafe { ffi.get_string() };
unsafe { CStr::from_ptr(ptr).to_owned() }
}
这是自以为是的——也许我的包装器的消费者对获取拥有的字符串不感兴趣,因为他们只想进行比较(这甚至是我想说的主要用例)。那么复制字符串就完全是浪费了。
2.返回原始指针
pub fn get_string_from_host() -> *const c_char {
unsafe { ffi.get_string() }
}
这只是把问题转嫁给了消费者。
3.返回一个CStr
参考(不安全的方法)
pub unsafe fn get_string_from_host<'a>() -> &'a CStr {
let ptr: *const c_char = ffi.get_string();
CStr::from_ptr(ptr)
}
这是不安全的,因为引用的生命周期不准确。稍后访问该引用可能会导致未定义的行为。将问题转移给消费者的另一种方式。
4. 使用闭包而不是返回一些东西
pub fn with_string_from_host<T>(f: impl Fn(&CStr) -> T) -> T {
let ptr: *const c_char = unsafe { ffi.get_string() };
f(unsafe { CStr::from_ptr(ptr) })
}
pub fn consuming_function() {
let length = with_string_from_host(|s| s.to_bytes().len());
}
这确实有效,但确实需要习惯。
这些解决方案都不是真正令人满意的。
有没有办法确保“立即”使用返回值,这意味着它不会存储在任何地方或永远不会逃脱调用者的范围?
这听起来像是引用/生命周期的工作,但我不知道任何生命周期注释意味着“仅在当前堆栈帧中有效”。如果有的话,我会使用它(仅用于说明):
pub fn get_string_from_host() -> &'??? CStr {
let ptr: *const c_char = unsafe { ffi.get_string() };
unsafe { CStr::from_ptr(ptr) }
}
pub fn consuming_function() {
// For example, this shouldn't be possible in this case
let prolonged: &'static CStr = get_string_from_host();
// But this should
let owned = get_string_from_host().to_owned();
}