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

2023-11-23

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

请注意,(真正的)C 程序不能更改,API 也不能更改,这些都是固定的、现有的东西,所以这不是一个关于“如何最好地支持 Rust 中的插件”的问题,而是 Rust 如何使*.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;
}

这是我在 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

我没有尝试加载模块,但是当我之前使用真实程序尝试此操作时,字段全部错误,表明更根本的问题是错误的。


tl;dr Use Option表示可为空的函数指针和None为空。

错误消息令人困惑,首先是因为std::ptr::null_mut不是一个指针;这是一个通用函数returns一个指针,而你还没有调用它。因此,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 _ and *mut _),它们不能为空。你不需要一个unsafe阻止通过指针调用函数,因此创建空函数指针是不安全的,就像创建空引用一样。

如何使某些东西可以为空?把它包起来Option:

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

并填充它Some(function) or None:

let api = Box::new (api {
    // ...
    load: Some(hello_load),
    hello: None,
});

使用通常不是一个好主意enums,包括Option, in a repr(C)struct,因为 C 没有enum等价的,所以你不知道你会在另一边得到什么。但在这种情况下Option<T> where T是不可为空的东西,None由空值表示,所以应该没问题。

指某东西的用途Option表示 FFI 的可空函数指针记录在不安全代码指南:

Rust 函数指针类型不支持 null 值——就像引用一样,期望您使用Option创建可为空的指针。Option<fn(Args...) -> Ret>将具有与完全相同的 ABIfn(Args...) -> Ret,但还允许空指针值。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何为 FFI 创建一个包含可为空函数指针的结构? 的相关文章

  • 没有强命名的代码签名是否会让您的应用程序容易被滥用?

    尝试了解authenticode代码签名和强命名 我是否正确地认为 如果我对引用一些 dll 非强命名 的 exe 进行代码签名 恶意用户就可以替换我的 DLL 并以看似由我签名但正在运行的方式分发应用程序他们的代码 假设这是真的 那么您似
  • WCF RIA 服务 - 加载多个实体

    我正在寻找一种模式来解决以下问题 我认为这很常见 我正在使用 WCF RIA 服务在初始加载时将多个实体返回给客户端 我希望两个实体异步加载 以免锁定 UI 并且我想利用 RIA 服务来执行此操作 我的解决方案如下 似乎有效 这种方法会遇到
  • 用于检查类是否具有运算符/成员的 C++ 类型特征[重复]

    这个问题在这里已经有答案了 可能的重复 是否可以编写一个 C 模板来检查函数是否存在 https stackoverflow com questions 257288 is it possible to write a c template
  • 查找c中结构元素的偏移量

    struct a struct b int i float j x struct c int k float l y z 谁能解释一下如何找到偏移量int k这样我们就可以找到地址int i Use offsetof 找到从开始处的偏移量z
  • 如何调用为 &str 实现的关联函数?

    我正在尝试写一个自定义Deserialize可以对我的枚举执行不区分大小写的反序列化的实现 use serde Deserialize Deserializer 1 0 120 use serde json 1 0 61 derive De
  • 使用实体框架模型输入安全密钥

    这是我今天的完美想法 Entity Framework 中的强类型 ID 动机 比较 ModelTypeA ID 和 ModelTypeB ID 总是 至少几乎 错误 为什么编译时不处理它 如果您使用每个请求示例 DbContext 那么很
  • 从Web API同步调用外部api

    我需要从我的 Web API 2 控制器调用外部 api 类似于此处的要求 使用 HttpClient 从 Web API 操作调用外部 HTTP 服务 https stackoverflow com questions 13222998
  • 如何使用 ICU 解析汉字数字字符?

    我正在编写一个使用 ICU 来解析由汉字数字字符组成的 Unicode 字符串的函数 并希望返回该字符串的整数值 五 gt 5 三十一 gt 31 五千九百七十二 gt 5972 我将区域设置设置为 Locale getJapan 并使用
  • HTTPWebResponse 响应字符串被截断

    应用程序正在与 REST 服务通信 Fiddler 显示作为 Apps 响应传入的完整良好 XML 响应 该应用程序位于法属波利尼西亚 在新西兰也有一个相同的副本 因此主要嫌疑人似乎在编码 但我们已经检查过 但空手而归 查看流读取器的输出字
  • 如何从 appsettings.json 文件中的对象数组读取值

    我的 appsettings json 文件 StudentBirthdays Anne 01 11 2000 Peter 29 07 2001 Jane 15 10 2001 John Not Mentioned 我有一个单独的配置类 p
  • 关于 C++ 转换:参数 1 从“[some_class]”到“[some_class]&”没有已知的转换

    我正在研究 C 并且遇到了一个错误 我不知道确切的原因 我已经找到了解决方案 但仍然想知道原因 class Base public void something Base b int main Base b b something Base
  • 重载<<的返回值

    include
  • WPF/C# 将自定义对象列表数据绑定到列表框?

    我在将自定义对象列表的数据绑定到ListBox in WPF 这是自定义对象 public class FileItem public string Name get set public string Path get set 这是列表
  • 向现有 TCP 和 UDP 代码添加 SSL 支持?

    这是我的问题 现在我有一个 Linux 服务器应用程序 使用 C gcc 编写 它与 Windows C 客户端应用程序 Visual Studio 9 Qt 4 5 进行通信 是什么very在不完全破坏现有协议的情况下向双方添加 SSL
  • 通过指向其基址的指针删除 POD 对象是否安全?

    事实上 我正在考虑那些微不足道的可破坏物体 而不仅仅是POD http en wikipedia org wiki Plain old data structure 我不确定 POD 是否可以有基类 当我读到这个解释时is triviall
  • cmake 将标头包含到每个源文件中

    其实我有一个简单的问题 但找不到答案 也许你可以给我指一个副本 所以 问题是 是否可以告诉 cmake 指示编译器在每个源文件的开头自动包含一些头文件 这样就不需要放置 include foo h 了 谢谢 CMake 没有针对此特定用例的
  • 在不是结构方法的函数上实现缓存的惯用方法是什么?

    我有一个像这样的昂贵的功能 pub fn get expensive value n u64 u64 let ret 0 for 0 n expensive stuff ret 并且它经常被用相同的参数调用 它是纯粹的 这意味着它将返回相同
  • Rust 中声明变量的宏?

    在 C 中 可以编写声明变量的宏 如下所示 define VARS a b c int a b c 当然 这不是您通常想要做的事情 在实际的例子中 我希望开始工作 但它并不那么简单 define VARS data stride a b c
  • 混合 ExecutionContext.SuppressFlow 和任务时 AsyncLocal.Value 出现意外值

    在应用程序中 由于 AsyncLocal 的错误 意外值 我遇到了奇怪的行为 尽管我抑制了执行上下文的流程 但 AsyncLocal Value 属性有时不会在新生成的任务的执行范围内重置 下面我创建了一个最小的可重现示例来演示该问题 pr
  • C++ 中类级 new 删除运算符的线程安全

    我在我的一门课程中重新实现了新 删除运算符 现在我正在使我的代码成为多线程 并想了解这些运算符是否也需要线程安全 我在某处读到 Visual Studio 中默认的 new delete 运算符是线程安全的 但这对于我的类的自定义 new

随机推荐

  • 在 IE8 及更低版本中 CSS 旋转 90 度

    如何在 IE 8 及更低版本中仅使用 CSS 旋转 90 度 horizontal display block width 300px height 100px height background FF0000 margin auto ma
  • 如何在没有任何库的情况下使用jquery swipe?

    我需要创建像滑动手势这样的 jQuery 移动设备 slider ul li div swipeleft 使用核心 jQuery 不使用任何库或插件 甚至不使用 jQuery mobile 我知道 jQuery 移动小部件现在将被解耦 以便
  • 如何按照 MVVM 为 WPF 构建通用/可重用模式对话框

    我想构建一个通用 可重用的模式对话框 可以在我们的 WPF MVVM WCF LOB 应用程序中使用 我有一个视图和关联的视图模型 我想使用对话框显示它们 视图和视图模型之间的绑定是使用针对类型的数据模板完成的 以下是我能够起草的一些要求
  • pandas statsmodels 中的多元线性回归:ValueError

    Data https courses edx org c4x MITx 15 071x 2 asset NBA train csv 我知道如何使用这些数据将其拟合到多元线性回归模型中statsmodels formula api impor
  • Python:从集合中检索项目

    一般来说 Python 集似乎并不是为通过键检索项目而设计的 显然这就是词典的用途 但是 无论如何 给定一个键 您可以从等于该键的集合中检索一个实例吗 再说一次 我知道这正是字典的用途 但据我所知 有合理的理由想要用字典来做到这一点 假设您
  • 如何模拟缓慢的 Meteor 发布?

    我正在尝试模拟出版物执行大量工作并花费很长时间才能返回光标 我的发布方法有强制睡眠 使用未来 但应用程序始终只显示 加载中 这是出版物 Meteor publish people function Future Npm require fi
  • SQL Server Management Studio 2008 计划导出到 MS Access

    专家 我不熟悉 SQL Server Management Studio 也从来不需要在 SQL Server 上安排任务 我每天都会将数据库导出到 MS Access 我需要 Management Studio 每天凌晨 2 00 自动执
  • Kiosk 应用程序 - OS X 编程 - 多显示器

    我学习了 Cocoa Objective C 主要用于 iPhone 开发 我需要利用这些技能在几天内为 OS X 构建一个非常基本的信息亭应用程序 申请基本如下 该设置有两个触摸屏显示器 应用程序必须运行全屏模式 右侧的监视器充当左侧选项
  • 为什么我的 jest.mock 中的 Promisereject() 会转到 then() 而不是 catch()?

    我有两个文件 getItemInfo js进行 API 调用并getItemInfo test js这是相应的 Jest 测试文件 在测试文件中 我模拟了由节点模块触发的http调用request promise 问题是在第二个代码块上 周
  • 如何将 Firebase 数据转换为 Java 对象...?

    使用Firebase库将数据以表单形式发送到服务器Message String String 添加到HashMap
  • Azure Devops 中的秘密管道参数

    我有一个用例 我想在 yaml 管道中使用用户名和密码的管道参数 对于用户名来说很简单 因为我只需在参数部分添加以下内容 parameters name Username type string displayName Username E
  • 为什么带有默认模板参数的模板不能用作模板模板参数中带有较少模板参数的模板

    myTemplateTemplate 期望第二个模板参数是具有一个参数的模板 myDefaultTemplate 是一个有两个参数的模板 第二个参数的默认类型为 int 在VS2008中 我得到编译错误 类模板 myDefaultTempl
  • 地图聚类 - 最大缩放标记仍然聚类

    我正在使用 android 地图 utils 对 google 地图 api v2 上的标记进行聚类 它工作得很好 但是当我添加 2000 多个标记时 在最大缩放下它仍然聚集在一起 标记仍然有数字 这是我用标记填充地图的方法 public
  • 如何获取方法参数的名称?

    如果我有一个方法 例如 public void MyMethod int arg1 string arg2 我将如何获取参数的实际名称 我似乎在 MethodInfo 中找不到任何可以实际提供参数名称的内容 我想写一个如下所示的方法 pub
  • 未找到 GLIBC_2.33 - 在为 Linux 构建 Flutter 时

    我试图在 Ubuntu 22 04 LTS 上运行 flutter 应用程序 一切都工作正常 但是 今天运行应用程序时出现了这个问题 Flutter SDK 无法构建应用程序并抛出以下错误 snap flutter 130 usr lib
  • 09 未被识别,而 9 被识别[重复]

    这个问题在这里已经有答案了 我正在使用石英进行调度 TriggerUtils getDateOf 0 40 18 09 06 它接受 5 个参数 秒 分钟 小时 月份 月份 当我将第四个参数传递为 09 时 Eclipse 给我错误 int
  • 如何在 MS SQL 存储函数中将日期时间转换为时间戳

    有一个从表更新触发器调用的存储函数 像这样的SM FUNCTION dbo DateTime2ToBigInt dt DATETIME2 7 RETURNS BIGINT 需要将输入日期时间转换为 unix 时间戳 Tried CONVER
  • 如何为匿名对象的属性设置值?

    这是我的代码 例如 var output new NetSessionId string Empty foreach var property in output GetType GetProperties property SetValu
  • 使用正则表达式验证 IPv4 地址

    我一直在尝试获得一个有效的正则表达式来进行 IPv4 验证 但运气不佳 似乎有一次我已经受够了 25 0 5 2 0 4 0 9 01 0 9 0 9 4 但它会产生一些奇怪的结果 grep version grep GNU grep 2
  • 如何为 FFI 创建一个包含可为空函数指针的结构?

    我有一个现有的 C 程序 可以加载共享库插件 主 C 程序通过包含整数 字符串 函数指针等的 C 结构与这些插件交互 如何从 Rust 创建这样的插件 请注意 真正的 C 程序不能更改 API 也不能更改 这些都是固定的 现有的东西 所以这