Использование ссылки на параметр типа в функции обратного вызова библиотеки C - PullRequest
0 голосов
/ 16 октября 2018

Я пишу интерфейс Rust в библиотеку C, в которой есть функция со следующей сигнатурой:

typedef int (*callback_t)(const int *a, void *user_data);
void execute(callback_t callback);

Я хотел бы, чтобы пользователи интерфейса Rust могли передавать любой тип Tдля user_data (user_data не используется в библиотеке C).Другими словами, на стороне Rust я хотел бы:

type Callback<T> = fn(a: &mut usize, user_data: &mut T) -> usize;

Я пытался привести пользовательскую функцию Rust типа Callback<T> к

extern "C" fn callback(a: *mut c_int, user_data: *mut c_void) -> c_int

с as,но это не работает.Я также пытался создать закрывающую оболочку.Обе попытки не сработали.

Кто-нибудь может мне помочь?

1 Ответ

0 голосов
/ 16 октября 2018

Вы не должны приводить указатели функций между разными сигнатурами.Это катастрофически небезопасно и взорвет вашу программу (если вам повезет).Указатели на функции не взаимозаменяемы, и компилятор не может волшебным образом сделать их совместимыми.

То, что вы эффективно делаете здесь, - это принять заказ, написанный на итальянском языке, вычеркивая "language = Italian",заменив его на «language = Russian», и ожидая, что русский шеф поймет его, потому что, эй, он говорит, что он на русском!Первый аргумент *const c_int, а не *mut c_int.C позволяет вам отбрасывать const, но другой код редко ожидает этого.

Во-вторых, вы не должны переводить необработанные указатели C как безопасные ссылки на Rust.Если код C вызывает нулевой указатель, ваш код Rust будет иметь неопределенное поведение.Если библиотека C не гарантирует, что оба указателя никогда не будут нулевыми, если контракт подписан кровью и гарантирован первичным ребенком программиста, не верьте этому: сначала проверьте указатели.

В-третьих, c_int и usize не того же типа.Не путайте их.Правильный тип для интерфейса Rust - c_int.

Таким образом, фактический тип обратного вызова C в Rust:

type CCallback = Option<extern "C" fn(a: *const c_int, user_data: *mut c_void) -> c_int>;

Option существует, поскольку указатели на функции C могутбыть нулевым, в Rust они не могут.

Наконец, Callback<T> не отмечен extern "C".Точное определение соглашения о вызовах имеет решающее значение.

Подпись любой функции, которую вы намереваетесь привести к типу обратного вызова C, должна быть точно сигнатура обратного вызова C.То есть:

extern "C" fn a_callback(a: *const c_int, user_data: *mut c_void) -> c_int {
    ::std::process::abort();
}

Теперь, вы могли бы быть в состоянии обойтись с этим:

extern "C" fn a_callback<T>(a: *const c_int, user_data: *mut T) -> c_int {
    ::std::process::abort();
}

И принудительно Some(a_callback) к CCallback.Тем не менее, я не могу гарантировать, что это верно для всех возможных T.

Чтобы быть в безопасности, вы должны явно обернуть все функции обратного вызова Rust в функцию перевода.Это легче всего сделать с помощью макроса, который, учитывая имя функции Rust, генерирует шим C.

macro_rules! shim {
    ($c:ident => $r:ident) => {
        extern "C" fn $c(a: *const c_int, user_data: *mut c_void) -> c_int {
            if a.is_null() {
                ::std::process::abort()
            }
            if user_data.is_null() {
                ::std::process::abort()
            }
            // NOTE: You need to make *absolutely certain* that this cast
            // of user_data is valid.
            let res: i32 = $r(&*a, &mut *(user_data as *mut _));
            res as c_int
        }
    };
}

shim!(another_callback_c => another_callback);

fn another_callback(a: &c_int, user_data: &mut u8) -> i32 {
    // Do something...
    ::std::process::abort()
}
...