简体   繁体   English

如何为包含可为空函数指针的 FFI 创建结构?

[英]How do I make a struct for FFI that contains a nullable function pointer?

I have an existing C program that loads shared library plugins.我有一个加载共享库插件的现有 C 程序。 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 程序通过包含整数、字符串、函数指针等的 C 结构与这些插件交互。如何从 Rust 创建这样的插件?

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 程序不能更改,API 也不能更改,这些是固定的、现有的东西,所以这不是“如何最好地支持 Rust 中的插件”的问题,而是 Rust 如何使*.so与现有 C 程序互操作的*.so文件。

Here's a simplified example of a C program + C plugin:下面是一个 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;
}

Here's what I wrote in Rust to try to get a plugin, but it doesn't compile:这是我在 Rust 中编写的尝试获取插件的内容,但它无法编译:

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?
}

This is compiled using Cargo.toml containing:这是使用Cargo.toml编译的, Cargo.toml包含:

[package]
name = "embed"
version = "0.1.0"

[dependencies]
libc = "0.2"

[lib]
name = "embed"
crate-type = ["cdylib"]

The error is:错误是:

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 Use Option to represent nullable function pointers and None for null. tl;dr使用Option表示可为空的函数指针,使用None表示空。

The error message is confusing, first, because std::ptr::null_mut isn't a pointer;错误消息令人困惑,首先,因为std::ptr::null_mut不是指针; 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.所以 Rust 看到你传递了一个具有错误签名和调用约定的函数,并对此抱怨。

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 _`

Function pointers and object pointers are not compatible (this is also the case in C), so you can't cast between them.函数指针和对象指针不兼容(在 C 中也是如此),因此您无法在它们之间进行转换。 null_mut returns an object pointer, so you need to find another way to create a null function pointer. null_mut返回一个对象指针,所以你需要找到另一种方法来创建一个空函数指针。

Function pointers (values of type fn(...) -> _ ) have another interesting property: unlike raw pointers ( *const _ and *mut _ ), they can't be null.函数指针( fn(...) -> _类型的值)有另一个有趣的特性:与原始指针( *const _*mut _ )不同,它们不能为空。 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.您不需要unsafe块来通过指针调用函数,因此创建空函数指针是不安全的,就像创建空引用一样。

How do you make something nullable?你如何使某些东西可以为空? Wrap it in Option :将它包装在Option

#[repr(C)]
pub struct api {
    // ...
    load: Option<extern fn ()>,
    hello: Option<extern fn ()>, // assuming hello can also be null
}

And populate it with Some(function) or None :并用Some(function)None填充它:

let api = Box::new (api {
    // ...
    load: Some(hello_load),
    hello: 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.repr(C)结构中使用enum s(包括Option通常不是一个好主意,因为 C 没有等效的enum ,因此您不知道在另一边会得到什么。 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.但是在Option<T>的情况下,其中T是不可为空的, None由空值表示,所以应该没问题。

The use of Option to represent a nullable function pointer for FFI is documented in the Unsafe Code Guidelines : 不安全代码指南中记录了使用Option来表示 FFI 的可为空的函数指针:

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. Rust 函数指针类型不支持 null 值——就像引用一样,期望您使用Option来创建可空指针。 Option<fn(Args...) -> Ret> will have the exact same ABI as fn(Args...) -> Ret , but additionally allows null pointer values. Option<fn(Args...) -> Ret>将具有完全相同的ABI为fn(Args...) -> Ret ,但还允许空指针值。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM