我有一个加载共享库插件现有的C程序。主要的C程序通过包含整数,字符串函数指针,等我如何创建锈菌这样的插件C结构与插件进行交互?
需要注意的是,(真正的)C程序不能改变,也不能在API加以改变,这些都是固定的,存在的事物,所以这不是一个关于“如何更好地支持插件,锈”的问题,它的锈如何才能让*.so
文件该互操作与现有的C程序。
这里的一个C程序+ C插件的简化示例:
/* 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;
}
下面是我在写锈试图获得一个插件,但它不会编译:
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
编译:
[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
我没有得到尝试加载模块,但是当我尝试这样做之前与真正的程序中的字段都错了东西表明更为基本的是错误的。
TL;博士用Option
代表为空的函数指针和None
为空。
该错误消息是混乱的,首先是因为std::ptr::null_mut
不是指针;它是一个通用的函数返回一个指针,而你还没有说法。所以锈看到你传递的签名错误和调用约定的函数,并抱怨说。
但是,一旦你解决这个问题,你会得到这个错误,而不是:
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
返回一个对象的指针,所以你需要找到另一种方式来创建一个空的函数指针。
函数指针(类型fn(...) -> _
的值),还有一个有趣的属性:不同于原始指针(*const _
和*mut _
),他们不能为空。你并不需要一个unsafe
块通过指针调用的函数,所以创建一个空函数指针是不安全的,像创建一个空引用。
你怎么做的东西可空?敷在Option
:
#[repr(C)]
pub struct api {
// ...
load: Option<extern fn ()>,
hello: Option<extern fn ()>, // assuming hello can also be null
}
并与Some(function)
或None
填充它:
let api = Box::new (api {
// ...
load: Some(hello_load),
hello: None,
});
这通常不是一个好主意,用enum
s,包括Option
,在repr(C)
结构,因为C没有一个enum
等同,所以你不知道你会得到另一边的东西。但在Option<T>
的情况下T
东西非空的,None
由空值表示,因此它应该没问题。
我发现this issue在锈库,其中建议使用Option
的意见是通过repr(C)
发送为空的函数指针的预期方式。问题是预先1.0,因此它可能是过时的;我无法找到任何其他文件。