如何为FFI制作一个包含可为空的函数指针的结构?

时间:2019-02-07 12:06:23

标签: c rust

我有一个现有的C程序,用于加载共享库插件。主C程序通过包含整数,字符串,函数指针等的C结构与这些插件进行交互。如何从Rust中创建这样的插件?

请注意,(真实的)C程序不能更改,API也不能更改,因为这些都是固定的,已有的东西,因此这不是“如何在Rust中最好地支持插件”的问题,而是Rust如何制作与现有C程序互操作的 public class NumbersAdapter extends ArrayAdapter<Numbers> { private Context mContext; private ArrayList<Numbers>list; public NumbersAdapter(Context context, ArrayList<Numbers> englishNumbers) { super(context, 0, englishNumbers); this.mContext=context; this.list = englishNumbers; } @NonNull @Override public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { View listItemView = convertView; if (listItemView == null) { listItemView = LayoutInflater.from(mContext).inflate(R.layout.listitem, parent, false); } Numbers numbers = list.get(position); System.out.println(position); TextView tamazight_item = (TextView) listItemView.findViewById(R.id.tamazight_item); TextView english_item = (TextView) listItemView.findViewById(R.id.english_item); tamazight_item.setText(numbers.getEnglishTranslation()); System.out.println(numbers.getEnglishTranslation()); english_item.setText(numbers.getTamazightTranslation()); System.out.println(numbers.getTamazightTranslation()); return listItemView; } } 文件。

这是C程序+ C插件的简化示例:

*.so
/* 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);
}

这是我在Rust中尝试获取插件的内容,但无法编译:

/* 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"]

我没有尝试加载模块,但是当我之前在真实程序中尝试加载该模块时,所有字段均错误,这表明更根本的问题是错误的。

1 个答案:

答案 0 :(得分:3)

tl; dr 使用Option表示可为空的函数指针,使用None表示为空。

首先,错误消息令人困惑,因为std::ptr::null_mut不是指针。这是一个返回指针的通用函数,您尚未调用它。因此,Rust看到您传递的函数签名和调用约定错误,并且对此表示抱怨。

但是,一旦您解决了该问题,就会收到此错误:

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 _)不同,它们不能为null。您不需要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结构中使用Option,包括repr(C)通常不是一个好主意,因为C没有等效的enum,所以您不知道你会在另一边得到什么。但是,在Option<T>是不可为空的T的情况下,None由null值表示,因此应该可以。

我在Rust存储库中发现了this issue,其中的注释建议使用Option是通过repr(C)发送可空函数指针的预期方式。问题是1.0之前的版本,因此可能已过时;我找不到其他任何文档。