在 C/Rust 代码中为 Node-FFI 创建结构

Create struct in C/Rust code for Node-FFI

我正在尝试 return 从 Rust 函数到 Node.js 的结构,该结构是嵌套的,并且包含一个数组,所以它非常复杂,我想在 Rust 中进行分配并让 Node.JS 收到一个完整的对象,这里是简短的 Rust 代码:

一个 State 是 returned,其中包含许多 Config、一条消息(例如错误消息或一些警告)和许多配置。

use std::ffi::CStr;
use std::os::raw::c_char;

#[derive(Debug)]
#[repr(C)]
pub struct Config {
    pub some_fields_here: String,
    // pub ...
}

#[derive(Debug)]
#[repr(C)]
pub struct State {
    pub message: String, // Even changing this to *mut c_char doesn't help the immediate problem
    pub configs: *mut Config,
    pub num_configs: usize,
}

#[no_mangle]
pub extern "C" fn config_from_file(_not_really_used_in_example: *const c_char) -> *mut Configuration {
    let mut configs: Vec<Config> = vec![];
    configs.push(Config {
      some_fields_here: String::from("hello world"), // should maybe be CString::from(...).into_raw()
    });
    Box::into_raw(Box::new(State {
        message: String::from("a message here"),
        configs: Box::into_raw(configs.into_boxed_slice()) as *mut Config,
        num_configs: 1,
    }))
}

从 Node 方面来看,我们发现的所有示例和文档仅使用 StructType 来准备要传递给 FFI 的东西,或者透明地传递而不用询问它。

我们想做的是:

var ffi = require("ffi-napi");
var ref = require("ref-napi");
var StructType = require("ref-struct-di")(ref);
var ArrayType = require("ref-array-di")(ref);

const Config = StructType({
  some_fields_here: ref.types.CString,
});

const State = StructType({
  message: ref.types.CString,
  configs: ref.refType(ArrayType(Config)),
  num_configs: ref.types.size_t,
});

const StatePtr = ref.refType(StatePtr);

var ourlib = ffi.Library("./target/debug/ourlib", {
  config_from_file: [StatePtr, [ref.types.CString]],
});

const ffiResult = ourlib.config_from_file("a file path, in the real code");
console.log(ffiResult)
// => <Buffer@0x47ce560 20 e2 83 04 00 00 00 00 57 00 00 00 00 00 00 00 57 00 00 00 00 00 00 00, 
type: { [Function: StructType] defineProperty: [Function: defineProperty], toString: [Function: 
toString], fields: { message: [Object], configs: [Object], num_configs: [Object] }, size: 24, 
alignment: 8, indirection: 1, isPacked: false, get: [Function: get], set: [Function: set] }>

在这里,我们需要得到一个类似上面结构的Javscript对象,访问 到任何字段,或将其视为对象。我们没有在 https://tootallnate.github.io/ref, nor in https://github.com/node-ffi-napi/ref-struct-di

的测试或文档中找到合适的示例

我不希望从 Buffer() 支持的 C 对象到 JavaScript 有一个干净的传递,当然我们必须解码和移动东西,但我找不到任何 方式从JavaScript 访问该结构中包含的信息。任何 deref()toObject() 的组合或获取单个字段似乎都没有帮助。

我预计这样的事情可能会成功,但它 either/or 会出现段错误或打印垃圾,这取决于我以某种方式进行的一些小调整:

console.log(ffiResult);
console.log(ref.deref().readCString(ffiResult, 0));

我知道在某种程度上在库中进行分配,并且在宿主语言中解压缩这些结构是不合常理的,但是由于数据的形状,并且我们无法更改库代码,所以我们不这样做有很多选择。

and contains an array so it is sufficiently complicated that I want to do the allocation in Rust and have Node.JS receive a complete object,

包含数组的是 tricky/trap 部分。 C 中的数组(ergo FFI)往往以零终止,但不能保证结构数组以特定方式工作。

这是我们必须做的:

  1. 在 Rust 中,公开一个带有长度成员的结构和一个指向单个项目的指针。
  2. 在 Rust 中,分配我们事物的向量并使用 into_boxed_slice() 忘记它,这给出了指向第一个的指针。
  3. 在我们的结构
  4. 上填充长度属性

一切都像我们首先发布的示例。

虽然在 node.js 中有点不同,但工作代码与我们的代码几乎相同,类型如下:

const Config = StructType({
  some_fields_here: ref.types.CString,
});

const State = StructType({
  message: ref.types.CString,
  configs: ArrayType(Config), // we had refType(ArrayType(Config))
  num_configs: ref.types.size_t,
});

const StatePtr = ref.refType(StatePtr);

var ourlib = ffi.Library("./target/debug/ourlib", {
  config_f...

进行了更改后,唯一的问题是数组在长度初始化之前无法使用,因此稍后我们只需要这样做:

const ffiResult = ourlib.config_from_file("a file path, in the real code");
const ffiState = ffiResult.deref(); // it's a pointer, remember.
ffiState.configs.length = ffiState.num_configs; // see comment below

设置长度后ArrayType就可以像普通数组一样使用了。

踢球者是 this commit (dc0d7296) from about a decade ago!