Как мне сделать структуру для FFI, которая содержит указатель на функцию, допускающую значение NULL? - PullRequest
0 голосов
/ 07 февраля 2019

У меня есть программа на C, которая загружает подключаемые модули общей библиотеки.Основная программа C взаимодействует с этими плагинами через структуру C, содержащую целые числа, строки, указатели функций и т. Д. Как я могу создать такой плагин из Rust?

Обратите внимание, что (настоящую) программу C нельзя изменить,и API не может быть изменено, это исправлено, существующие вещи, так что вопрос не в том, «как лучше поддерживать плагины в Rust», а в том, как Rust может создавать *.so файлы, которые взаимодействуют с существующей программой на Си.

Вот упрощенный пример программы на 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;
}

Вот что я написал в 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?
}

Это скомпилировано с использованием 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

Я не смог загрузить модуль, но когдаЯ пытался сделать это раньше с реальной программой, все поля были неправильными, что указывало на что-то гораздо более фундаментальное.

1 Ответ

0 голосов
/ 07 февраля 2019

tl; dr Используйте 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 представляется нулевым значением, так что все должно быть в порядке.

Я нашел эту проблему в репозитории Rust, в комментариях к которому предлагается использовать Option, это намеченный способ отправки указателей обнуляемых функций через repr(C).Выпуск до 1.0, поэтому он может быть устаревшим;Я не смог найти другую документацию.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...