如何为包含可为空函数指针的 FFI 创建结构? [英] How do I make a struct for FFI that contains a nullable function pointer?
问题描述
我有一个加载共享库插件的现有 C 程序.主 C 程序通过包含整数、字符串、函数指针等的 C 结构与这些插件交互.我如何从 Rust 创建这样的插件?
I have an existing C program that loads shared library plugins. The main C program interacts with those plugins through a C struct containing integers, strings, function pointers, etc. How can I create such a plugin from Rust?
请注意,(真实的)C 程序不能更改,API 也不能更改,那些是固定的、现有的东西,所以这不是如何最好地支持 Rust 中的插件"的问题,而是 Rust 如何可以制作与现有 C 程序互操作的 *.so
文件.
Note that the (real) C program cannot be changed, nor can the API be changed, those are fixed, existing things, so this is not a question about "how best to support plugins in Rust", it's how can Rust make *.so
files which interoperate with an existing C program.
这是一个 C 程序 + C 插件的简化示例:
Here's a simplified example of a C program + C plugin:
/* gcc -g -Wall test.c -o test -ldl
./test ./test-api.so
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <dlfcn.h>
struct api {
uint64_t i64;
int i;
const char *name; /* can be NULL */
void (*load) (void); /* must not be NULL */
void (*hello) (const char *str); /* can be NULL */
};
int
main (int argc, char *argv[])
{
void *dl = dlopen (argv[1], RTLD_NOW);
if (!dl) { fprintf (stderr, "%s: %s\n", argv[1], dlerror ()); exit (1); }
struct api *(*get_api) (void) = dlsym (dl, "get_api");
printf ("calling get_api ...\n");
struct api *api = get_api ();
printf ("api->i64 = %" PRIi64 "\n", api->i64);
printf ("api->i = %d\n", api->i);
if (api->name)
printf ("api->name = %s\n", api->name);
printf ("calling api->load ...\n");
api->load ();
if (api->hello) {
printf ("calling api->hello ...\n");
api->hello ("world");
}
printf ("exiting\n");
exit (0);
}
/* gcc -g -shared -fPIC -Wall test-api.c -o test-api.so */
#include <stdio.h>
#include <stdint.h>
static void
load (void)
{
printf ("this is the load function in the plugin\n");
}
static void
hello (const char *str)
{
printf ("hello %s\n", str);
}
static struct api {
uint64_t i64;
int i;
const char *name;
void (*load) (void);
void (*hello) (const char *str);
} api = {
1042,
42,
"this is the plugin",
load,
hello,
};
struct api *
get_api (void)
{
return &api;
}
这是我在 Rust 中编写的尝试获取插件的内容,但它无法编译:
Here's what I wrote in Rust to try to get a plugin, but it doesn't compile:
extern crate libc;
use libc::*;
use std::ffi::*;
use std::ptr;
use std::os::raw::c_int;
#[repr(C)]
pub struct api {
i64: uint64_t,
i: c_int,
name: *const c_char,
load: extern fn (),
hello: extern fn (), // XXX
}
extern fn hello_load () {
println! ("hello this is the load method");
}
#[no_mangle]
pub extern fn get_api () -> *const api {
println! ("hello from the plugin");
let api = Box::new (api {
i64: 4201,
i: 24,
name: CString::new("hello").unwrap().into_raw(), // XXX memory leak?
load: hello_load,
hello: std::ptr::null_mut,
});
return Box::into_raw(api); // XXX memory leak?
}
这是使用 Cargo.toml
编译的,其中包含:
This is compiled using Cargo.toml
containing:
[package]
name = "embed"
version = "0.1.0"
[dependencies]
libc = "0.2"
[lib]
name = "embed"
crate-type = ["cdylib"]
错误是:
error[E0308]: mismatched types
--> src/lib.rs:32:16
|
32 | hello: std::ptr::null_mut,
| ^^^^^^^^^^^^^^^^^^ expected "C" fn, found "Rust" fn
|
= note: expected type `extern "C" fn()`
found type `fn() -> *mut _ {std::ptr::null_mut::<_>}`
error: aborting due to previous error
我没有尝试加载模块,但是当我之前在实际程序中尝试加载模块时,字段全都出错了,表明一些更基本的东西出错了.
I didn't get to try loading the module but when I tried this before with the real program the fields were all wrong indicating something much more fundamental was wrong.
推荐答案
tl;dr 使用 Option
表示可空函数指针和 None
为空.
tl;dr Use Option
to represent nullable function pointers and None
for null.
错误信息令人困惑,首先是因为 std::ptr::null_mut
不是指针;它是一个返回一个指针的通用函数,而你还没有调用它.因此,Rust 看到您传递了一个具有错误签名和调用约定的函数,并对此进行了抱怨.
The error message is confusing, first, because std::ptr::null_mut
isn't a pointer; it's a generic function that returns a pointer, and you haven't called it. So Rust is seeing you pass a function that has the wrong signature and calling convention, and complaining about that.
但是一旦你解决了这个问题,你就会得到这个错误:
But once you fix that, you'll get this error instead:
error[E0308]: mismatched types
--> src/lib.rs:29:16
|
29 | hello: std::ptr::null_mut(),
| ^^^^^^^^^^^^^^^^^^^^ expected fn pointer, found *-ptr
|
= note: expected type `extern "C" fn()`
found type `*mut _`
函数指针和对象指针不兼容(C 中也是这种情况),所以你不能在它们之间进行转换.null_mut
返回的是一个对象指针,所以需要另辟蹊径来创建一个空函数指针.
Function pointers and object pointers are not compatible (this is also the case in C), so you can't cast between them. null_mut
returns an object pointer, so you need to find another way to create a null function pointer.
函数指针(fn(...) -> _
类型的值)还有一个有趣的特性:不同于原始指针(*const _
和 *mut _
),它们不能为空.您不需要 unsafe
块来通过指针调用函数,因此创建空函数指针是不安全的,就像创建空引用一样.
Function pointers (values of type fn(...) -> _
) have another interesting property: unlike raw pointers (*const _
and *mut _
), they can't be null. You don't need an unsafe
block to call a function via pointer, and so creating a null function pointer is unsafe, like creating a null reference.
你如何让一些东西可以为空?将其包裹在 Option
中:
How do you make something nullable? Wrap it in Option
:
#[repr(C)]
pub struct api {
// ...
load: Option<extern fn ()>,
hello: Option<extern fn ()>, // assuming hello can also be null
}
并用 Some(function)
或 None
填充它:
And populate it with Some(function)
or None
:
let api = Box::new (api {
// ...
load: Some(hello_load),
hello: None,
});
在 repr(C)
结构中使用 enum
s,包括 Option
通常不是一个好主意,因为 C 不t 有一个 enum
等价物,所以你不知道你会在另一边得到什么.但是在 Option
的情况下,T
是不可为空的,None
由空值表示,所以它应该是好的.
It's not usually a good idea to use enum
s, including Option
, in a repr(C)
struct, because C doesn't have an enum
equivalent and so you don't know what you're going to get on the other side. But in the case of Option<T>
where T
is something non-nullable, None
is represented by the null value, so it should be okay.
The use of Option
to represent a nullable function pointer for FFI is documented in the Unsafe Code Guidelines:
Rust 函数指针类型不支持空值——就像引用一样,期望您使用 Option
来创建可空指针.Option
将具有与 fn(Args...) -> 完全相同的 ABI返回
,但另外允许空指针值.
null values are not supported by the Rust function pointer types -- just like references, the expectation is that you use
Option
to create nullable pointers.Option<fn(Args...) -> Ret>
will have the exact same ABI asfn(Args...) -> Ret
, but additionally allows null pointer values.
这篇关于如何为包含可为空函数指针的 FFI 创建结构?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!