3

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

1 Answer 1

6

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.