Создание структуры в коде C / Rust для узла-FFI

#javascript #node.js #c #rust #ffi

#javascript #node.js #c #Ржавчина #ffi

Вопрос:

Я пытаюсь вернуть структуру из функции Rust в Node.js , структура является вложенной и содержит массив, поэтому она достаточно сложна, поэтому я хочу выполнить выделение в Rust и иметь Node.JS получает полный объект, вот краткий код Rust:

State Возвращается A, содержащий число Config s, сообщение (например, сообщение об ошибке или какое-либо предупреждение) и несколько конфигураций.

 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,
    }))
}
 

Со стороны узла все примеры и документы, которые мы нашли, используют только 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 , ни в https://github.com/node-ffi-napi/ref-struct-di

Я не ожидаю, что у меня будет чистый переход от объектов C, поддерживаемых Buffer() в JavaScript, конечно, мы должны декодировать и перемещать вещи, но я не могу найти никакого способа получить доступ к информации, содержащейся в этой структуре, из JavaScript. Кажется, что никакая комбинация каких-либо deref() или toObject() или получение отдельных полей вообще не помогает.

Я ожидал, что что-то подобное может это сделать, но это либо / или ошибки seg, либо выводит мусор в зависимости от некоторых незначительных настроек, которые я делаю так или иначе:

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

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

Комментарии:

1. Вы смотрели на neon ?

2. Действительно, мы решили эту проблему, и ваше приглашение напоминает мне вернуться и ответить на мой собственный вопрос. Короче говоря, нет, мы этого не сделали, потому что мы специально ориентируемся на FFI, потому что наша вещь должна работать с Ruby, Node.js и Python (и, другие). Neon — это генератор привязки NAPI только между Node и Rust, поэтому он не соответствует нашим потребностям.

Ответ №1:

и содержит массив, поэтому он достаточно сложный, поэтому я хочу выполнить выделение в Rust и иметь Node.JS получает полный объект,

Содержание массива было сложной / сложной частью. Массивы в C (следовательно, FFI), как правило, заканчиваются нулем, но нет никакой гарантии, что все работает определенным образом для массивов структур.

Вот что нам нужно было сделать:

  1. В Rust предоставьте структуру с элементом длины и указателем на один элемент.
  2. В Rust выделите вектор наших вещей и используйте into_boxed_slice() , чтобы забыть его, это дает указатель на первый.
  3. Заполните свойство length в нашей структуре

Все, как в примере, который мы опубликовали первым.

В 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 then его можно использовать более или менее точно так же, как обычный массив.

Кикером был этот commit ( dc0d7296 ) примерно десятилетней давности!.