rust - 在 C 库的回调函数中使用类型参数的引用
问题描述
我正在为 C 库编写一个 Rust 接口,该库具有以下签名的函数:
typedef int (*callback_t)(const int *a, void *user_data);
void execute(callback_t callback);
我想要的是 Rust 接口的用户可以传递任何类型T
for 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
但这不起作用。我还尝试创建一个包装闭包。两次尝试都没有奏效。
谁能帮我吗?
解决方案
您不应该在不同签名之间转换函数指针。这是灾难性的不安全并且会炸毁你的程序(如果你幸运的话)。函数指针是不可互换的,编译器不能神奇地使它们兼容。
你在这里有效地做的是接受用意大利语写的订单,划掉“语言=意大利语”,用“语言=俄语”替换它,并期待俄罗斯厨师能够理解它,因为,嘿,它说它是俄语!
首先,您对原始 C 类型的翻译可能是错误的。第一个论点是*const c_int
,不是*mut c_int
。C 确实允许你const
抛弃,但它很少是其他代码所期望的。
其次,您不应该将原始 C 指针转换为安全的 Rust 引用。如果 C 代码使用空指针调用,您的 Rust 代码将具有未定义的行为。除非 C 库保证两个指针永远不会为空,并且签订了一份与程序员的第一个孩子签订的合同,否则不要相信它:首先检查指针。
第三,c_int
和usize
不一样的类型。不要将它们混为一谈。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()
}
推荐阅读
- grails - 如何让 sonarqube 拾取 jacoco 测试覆盖率报告
- .net - 使用授权代码流和 PKCE 保护 .Net Core Web API
- nginx - Minikube Nginx Kubernetes Ingress 正在改变请求的方法
- facebook - 如何在 Facebook Pages API 中重置营业时间
- r - 对绘图的特定范围内的值进行分组
- c - 两条逻辑相同的 C 指令给出不同的结果
- vbscript - VBScript - 我们可以通过代码找出以秒为单位的持续时间吗
- python - Select column dynamically in Pandas dataframe based on values in a list or another column
- python - 如何使用pyqt5在python中制作每周时间表?
- javascript - fetch 在 await 之后仍然返回 Promise