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

编程入门 行业动态 更新时间:2024-10-23 08:30:54
本文介绍了如何为包含可为空函数指针的 FFI 创建结构?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧! 问题描述

我有一个加载共享库插件的现有 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) 结构中使用 enums,包括 Option 通常不是一个好主意,因为 C 不t 有一个 enum 等价物,所以你不知道你会在另一边得到什么.但是在 Option 的情况下,T 是不可为空的,None 由空值表示,所以它应该是好的.

It's not usually a good idea to use enums, 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.

Option 来表示 FFI 的可空函数指针/function-pointers.html" rel="nofollow noreferrer">不安全代码指南:

The use of Option to represent a nullable function pointer for FFI is documented in the Unsafe Code Guidelines:

Rust 函数指针类型不支持空值——就像引用一样,期望您使用 Option 来创建可空指针.OptionRet> 将具有与 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 as fn(Args...) -> Ret, but additionally allows null pointer values.

更多推荐

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

本文发布于:2023-07-29 11:03:58,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1240115.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:指针   何为   为空   函数   结构

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!