Как мне конвертировать * mut * mut c_void в & str без Box :: from_raw? - PullRequest
1 голос
/ 01 апреля 2020

Я играл с написанием модулей Redis в Rust. Это моя первая попытка использования Rust FFI и привязок. Как мне вызвать этот метод и получить значение данных в Rust, не уничтожая указатель Redis?

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,
    >;
}

См. Документацию API RedisModule_GetTimerInfo для получения более подробной информации.

Я закончил тем, что заставил это работать, но он выдает ошибку, если я вызываю это с тем же 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, либо оказывались указателем что я не знаю, как конвертировать в &str.

Обновление: у меня изначально был пример использования RedisModule_StopTimer, что было ошибкой. Исправлено использование метода, о котором я спрашивал.

Ответы [ 2 ]

2 голосов
/ 02 апреля 2020

Я один из сопровождающих ящика redismodule-rs , который предоставляет высокоуровневый Rust API для написания модулей Redis.

В ответ на ваш вопрос я рассмотрел, как безопасно добавить эти API-интерфейсы таймера в ящик, и выложу sh код в репозиторий, как только я закончу с ним.

Следующий код показывает, как безопасно получить данные:

// 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);
}
1 голос
/ 02 апреля 2020

Используя одну из ссылок @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());
    }

Спасибо всем за помощь!

...