How to return string value from a Rust FFI functio

2020-04-14 07:37发布

问题:

I want to generate 6 random numbers, push them onto a vector, then use rustc_serialize to encode that vector as a JSON string to be consumed by 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()
}

This code is compiled as a library generate_6_rand.dll. I have a separate binary file that I'm using to test this code.

If I run

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

Output:

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

I then use my .dll in a NodeJS program:

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());

Tests

console.log(lib.generate())

Output:

��.�
Is it an EcmaScript ArrayBuffer?

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

Output:

ArrayBuffer { byteLength: 0 }
What are it's proto chain properties?

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

Output:

[String: '']

Changed code to:

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()));

Output:

ArrayBuffer { byteLength: 0 }

Why does the FFI function not return a JSON string as I'm expecting?

回答1:

Thank you Wesley Wiser for giving me a big clue with CString. I found the answer in The Rust FFI Omnibus.

The memory for my expected JSON string was being deallocated before my NodeJS program could access it, whether I returned the JSON string or returned a CString.

Here's my solution based on that article. To other novice programmers, please keep in mind that my solution may or may not be ideal:

Rust

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�

I set up two console.logs to show what the output before and after deallocation.