通过FFI获取和设置上一条错误消息的正确方法是什么?

Ehs*_*ani 2 runtime-error ffi backtrace rust

我正在包装一个C API(dylib),它为最后一条错误消息公开了一个setter和getter API:

extern "C" {
    /// GetLastError is thread-safe
    pub fn GetLastError() -> *const ::std::os::raw::c_char;
    pub fn SetLastError(msg: *const ::std::os::raw::c_char);
}
Run Code Online (Sandbox Code Playgroud)

包裹它们的最简单方法如下

use std::error::Error;
use std::ffi::CStr;
use std::fmt::{self, Display, Formatter};
use std::os::raw::c_char;

pub struct MyError;

impl MyError {
    pub fn get_last() -> &'static str {
        unsafe {
            match CStr::from_ptr(c_api::GetLastError()).to_str() {
                Ok(s) => s,
                Err(_) => "Invalid UTF-8 message",
            }
        }
    }

    pub fn set_last(msg: &'static str) {
        unsafe {
            c_api::SetLastError(msg.as_ptr() as *const c_char);
        }
    }
}

impl Display for MyError {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(f, "{}", MyError::get_last())
    }
}

impl Error for MyError {
    fn description(&self) -> &'static str {
        MyError::get_last()
    }

    fn cause(&self) -> Option<&Error> {
        None
    }
}
Run Code Online (Sandbox Code Playgroud)

这似乎正常工作获取最后一条错误消息.但是,设置上一条错误消息似乎很幼稚,它会混乱堆栈框架中的先前消息!

例如;

let msg: &'static str = "invalid";
MyError::set_last(msg);
println!("Last error msg: {}", MyError::get_last());
Run Code Online (Sandbox Code Playgroud)

输出Last error msg: invalidLast error msg,或

assert_eq!(MyError::get_last().trim(), msg);
Run Code Online (Sandbox Code Playgroud)

失败了

thread 'tests::set_error' panicked at 'assertion failed: `(left == right)`
  left: `"invalidassertion failed: `(left == right)`\n  left: ``,\n right: ``"`,
 right: `"invalid"`'
Run Code Online (Sandbox Code Playgroud)

这样做的正确方法是什么?

我想过使用回溯,但在箱子里找不到什么解释,它导致无处可去!

mca*_*ton 6

Rust字符串不是以0结尾的,但是,您认为它们是在您执行的操作时:

c_api::SetLastError(msg.as_ptr() as *const c_char);
Run Code Online (Sandbox Code Playgroud)

因为C API通过存在空字节来检测字符串的结尾.

一个正确的方法是:

let c_string = CString::new("message").unwrap(); // will add a null byte
unsafe {
    c_api::SetLastError(c_string.as_ptr());
}
Run Code Online (Sandbox Code Playgroud)

根据C API是否复制了字符串,您可能希望into_raw在必要时使用正确的取消初始化处理.