首页 > 解决方案 > 在 C 库的回调函数中使用类型参数的引用

问题描述

我正在为 C 库编写一个 Rust 接口,该库具有以下签名的函数:

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

我想要的是 Rust 接口的用户可以传递任何类型Tfor 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但这不起作用。我还尝试创建一个包装闭包。两次尝试都没有奏效。

谁能帮我吗?

标签: rustffi

解决方案


您不应该在不同签名之间转换函数指针。这是灾难性的不安全并且会炸毁你的程序(如果你幸运的话)。函数指针是不可互换的,编译器不能神奇地使它们兼容。

你在这里有效地做的是接受用意大利语写的订单,划掉“语言=意大利语”,用“语言=俄语”替换它,并期待俄罗斯厨师能够理解它,因为,嘿,它说它是俄语!

首先,您对原始 C 类型的翻译可能是错误的。第一个论点是*const c_int,不是*mut c_int。C 确实允许你const抛弃,但它很少是其他代码所期望的。

其次,您不应该将原始 C 指针转换为安全的 Rust 引用。如果 C 代码使用空指针调用,您的 Rust 代码将具有未定义的行为。除非 C 库保证两个指针永远不会为空,并且签订了一份与程序员的第一个孩子签订的合同,否则不要相信它:首先检查指针。

第三,c_intusize不一样的类型。不要将它们混为一谈。Rust 接口使用的正确类型是c_int.

所以 Rust 中实际的 C 回调类型是:

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 shim。

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()
}

推荐阅读