Вы не должны приводить указатели функций между разными сигнатурами.Это катастрофически небезопасно и взорвет вашу программу (если вам повезет).Указатели на функции не взаимозаменяемы, и компилятор не может волшебным образом сделать их совместимыми.
То, что вы эффективно делаете здесь, - это принять заказ, написанный на итальянском языке, вычеркивая "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()
}