【问题标题】:How do I convert *mut *mut c_void to &str without Box::from_raw?如何在没有 Box::from_raw 的情况下将 *mut *mut c_void 转换为 &str?
【发布时间】:2020-04-01 03:15:11
【问题描述】:

我一直在尝试用 Rust 编写 Redis 模块。这是我第一次尝试使用 Rust FFI 和绑定。如何在不破坏 Redis 指针的情况下调用此方法并在 Rust 中得到一个数据值?

extern "C" {
    pub static mut RedisModule_GetTimerInfo: ::std::option::Option<
        unsafe extern "C" fn(
            ctx: *mut RedisModuleCtx,
            id: RedisModuleTimerID,
            remaining: *mut u64,
            data: *mut *mut ::std::os::raw::c_void,
        ) -> ::std::os::raw::c_int,
    >;
}

有关详细信息,请参阅RedisModule_GetTimerInfo API Docs。

我最终让它工作了,但如果我用相同的id 调用它两次,它会引发错误:

let mut ms = 0 as u64;
let val = "";
let ptr = Box::into_raw(Box::new(&mut val)) as *mut *mut c_void;
let ok = unsafe { RedisModule_GetTimerInfo.unwrap()(ctx, id, &mut ms, ptr) };
let mut data: Option<String> = None;
if ok == 0 {
    let val = unsafe { Box::from_raw(*ptr as *mut &str) };
    // trim nul bytes
    data = Some(val.trim_matches(char::from(0)).to_string());
}

这不起作用,因为Box::from_raw 拥有原始指针,并且当盒子被丢弃时指针被销毁。

在不使用Box::into_raw 和Box::from_raw 的情况下,我尝试了无数种方法来完成这项工作,而且它们总是导致Redis 崩溃或最终成为我不知道如何转换为&amp;str 的指针。

更新:我最初有一个使用 RedisModule_StopTimer 的示例,这是一个错误。更正为使用我询问的方法。

【问题讨论】:

标签: rust ffi


【解决方案1】:

我是redismodule-rs crate 的维护者之一,它提供了用于编写 Redis 模块的高级 Rust API。

根据您的问题,我考虑以安全的方式将这些计时器 API 添加到板条箱中,并在完成后将代码推送到存储库。

以下代码显示了如何安全地检索数据:

// Create local variables to hold the returned values
let mut remaining: u64 = 0;
let mut data: *mut c_void = std::ptr::null_mut();

// Call the API and retrieve the values into the local variables
let status = unsafe {
    RedisModule_GetTimerInfo.unwrap()(ctx, timer_id, &mut remaining, &mut data)
};

if status == REDISMODULE_OK {
    // Cast the *mut c_void supplied by the Redis API to 
    // a raw pointer of our custom type:
    let data = data as *mut T; // T is the type of the data, e.g. String

    // Dereference the raw pointer (we know this is safe,
    // since Redis should return our original pointer which
    // we know to be good), and turn in into a safe reference:
    let data = unsafe { &*data };

    println!("Remaining: {}, data: {:?}", remaining, data);
}

【讨论】:

  • Box::from_raw + Box::leak 当你已经有一个*mut String 时似乎有点迂回;有什么理由不简单地取消引用? let data: &amp;String = &amp;*(data as *mut _);
  • 这很好。我相信以这种方式编写它可以让代码的读者清楚地知道数据源自Box。我也猜想编译后的代码会很相似。
  • 在此之后,我想了更多,并正在考虑更改我的代码以使用常规引用而不是 Boxes,这只是一个特例。我会看看效果如何。
  • 在我看来,取消引用也比 Box::from_raw 有一个优势,因为您可以从同一个 *mut String 创建多个共享引用,而 Box 被别名是不合理的,所以当任何datas 仍在闲逛时,您必须小心不要再次致电Box::from_raw。 (当然这只适用于不需要&amp;mut 访问String 的情况。)
  • 是的,这确实是一个显着的优势。
【解决方案2】:

使用添加的links @Shepmaster 之一,我终于能够弄清楚这一点。我发誓我尝试了一些变体,但没想到要尝试双拳......

这就是我所做的:

    let val = Box::new(Box::new("") as Box<&str>);
    let ptr = Box::into_raw(val);
    let ok = unsafe { RedisModule_GetTimerInfo.unwrap()(ctx, id, &mut ms, ptr as *mut *mut c_void) };
    let mut data: Option<String> = None;
    if ok == 0 {
        let val = unsafe {**ptr as &str};
        data = Some(val.trim_matches(char::from(0)).to_string());
    }

感谢大家的帮助!

【讨论】:

    猜你喜欢
    • 2023-01-03
    • 1970-01-01
    • 2022-11-23
    • 1970-01-01
    • 2021-12-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-13
    相关资源
    最近更新 更多