如何从 NodeJS 中的 Rust FFI 函数返回字符串值?

Posted

技术标签:

【中文标题】如何从 NodeJS 中的 Rust FFI 函数返回字符串值?【英文标题】:How to return string value from a Rust FFI function in NodeJS? 【发布时间】:2017-07-18 16:51:15 【问题描述】:

我想生成 6 个随机数,将它们推送到一个向量上,然后使用 rustc_serialize 将该向量编码为 JSON 字符串以供 NodeJS 使用。

extern crate rand;
extern crate rustc_serialize;

use rand::OsRng, Rng;
use rustc_serialize::json::self, Json, ToJson;

#[no_mangle]
pub extern "C" fn generate() -> String 
    let choices: [u8; 6] = [1, 2, 3, 4, 5, 6];

    let mut rand_vec: Vec<u8> = Vec::new();

    let mut rng = match OsRng::new() 
        Ok(t) => t,
        Err(e) => panic!("Failed to create OsRng!, ", e),
    ;

    for _ in 0..5 
        rand_vec.push(*rng.choose(&choices).unwrap());
    

    json::encode(&rand_vec).unwrap()

此代码编译为库generate_6_rand.dll。我有一个单独的二进制文件,用于测试此代码。

如果我跑了

println!(":?", &json::encode(&rand_vec).unwrap());

输出:

"[5,4,3,4,1,3]" //as expected

然后我在 NodeJS 程序中使用我的.dll

var ffi = require('ffi');
var path = require('path');

var lib = ffi.Library(path.join(__dirname,
  './ffi/generate_6_rand.dll'), 
    generate: [ 'string', [ ]]
  );

console.log(lib.generate());

测试

console.log(lib.generate())

输出:

��.�
它是 EcmaScript ArrayBuffer 吗?

console.log(new ArrayBuffer(lib.generate())

输出:

ArrayBuffer  byteLength: 0 
它的原链属性是什么?

console.log(lib.generate().__proto__)

输出:

[String: '']

将代码更改为:

var ref = require('ref');
var ArrayType = require('ref-array');
var Int32Array = ArrayType(ref.types.int32);


var lib = ffi.Library(path.join(__dirname,
  '../dice_lib/target/release/generate_6_rand.dll'), 
    generate: [ Int32Array, [ ]]
  );

console.log(new ArrayBuffer(lib.generate()));

输出:

ArrayBuffer  byteLength: 0 

为什么 FFI 函数没有像我预期的那样返回 JSON 字符串?

【问题讨论】:

Strings 在 Rust 中并不意味着跨越 FFI 边界。您可能想要的是让您的 generate 函数返回 CString doc.rust-lang.org/std/ffi/struct.CString.html 【参考方案1】:

感谢 Wesley Wiser 为我提供了 CString 的重要线索。我在The Rust FFI Omnibus找到了答案。

我的预期 JSON 字符串的内存在我的 NodeJS 程序可以访问之前被释放,无论我返回 JSON 字符串还是返回 CString

这是我基于那篇文章的解决方案。 对于其他新手程序员,请记住,我的解决方案可能是理想的,也可能不是理想的:

extern crate rand;
extern crate rustc_serialize;
extern crate libc;

use libc::c_char;
use rand::OsRng, Rng;
use std::ffi::CString;
use rustc_serialize::json;

#[no_mangle]
pub extern "C" fn generate() -> *mut c_char 
    let choices: [u8; 6] = [1, 2, 3, 4, 5, 6];

    let mut rand_vec: Vec<u8> = Vec::new();

    let mut rng = match OsRng::new() 
        Ok(t) => t,
        Err(e) => panic!("Failed to create OsRng!, ", e),
    ;

    for _ in 0..6 
        rand_vec.push(*rng.choose(&choices).unwrap());
    

    let json_string = CString::new(json::encode(&rand_vec).unwrap()).unwrap();

    json_string.into_raw()


#[no_mangle]
pub extern "C" fn free_memory(pointer: *mut c_char) 
    unsafe 
        if pointer.is_null() 
            return;
        
        CString::from_raw(pointer)
    ;

NodeJS

var ffi = require('ffi');
var path = require('path');

var lib = ffi.Library(path.join(__dirname,
  './ffi/generate_6_rand.dll'), 
    generate: [ 'char *' , [ ]],
    free_memory: ['void', ['char *']]
  );

var json_string = lib.generate();

var save_json = JSON.parse(json_string.readCString());

console.log( json_string.readCString()); // Output: [6,1,6,4,1,4]
lib.free_memory(json_string);
console.log(json_string.readCString()); // Output: ��x�

我设置了两个console.logs 来显示释放前后的输出。

【讨论】:

以上是关于如何从 NodeJS 中的 Rust FFI 函数返回字符串值?的主要内容,如果未能解决你的问题,请参考以下文章

如何将 Rust `Vec<T>` 暴露给 FFI?

通过 FFI 调用 Rust 函数时访问冲突

为啥 Rust 函数和 FFI C++ 函数以相反的顺序执行?

如何使用字符串作为参数从 Go 调用 Rust 函数?

抓狂!当 Rust 从 C FFI 调用时,没有产生线程

如何在 Rust 的 FFI 中使用 C 预处理器宏?