Skip to content
Rodrigo Pandini edited this page Mar 21, 2016 · 19 revisions

Overview

node-ffi provides a powerful set of tools for interfacing with dynamic libraries using pure JavaScript in the Node.js environment. It can be used to build interface bindings for libraries without using any C++ code.

The ref module

Central to the node-ffi infrastructure is the ref module, which extends node's built-in Buffer class with some useful native extensions that make them act more like "pointers". While we try our best to hide the gory details of dealing with pointers, in many cases libraries use complex memory structures that require access to allocation and manipulation of raw memory. See its documentation for more details about working with "pointers".

The Library function

The primary API for node-ffi is through the Library function. It is used to specify a dynamic library to link with, as well as a list of functions that should be available for that library. After instantiation, the returned object will have a method for each library function specified in the function, which can be used to easily call the library code.

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

// typedef
var sqlite3 = ref.types.void; // we don't know what the layout of "sqlite3" looks like
var sqlite3Ptr = ref.refType(sqlite3);
var sqlite3PtrPtr = ref.refType(sqlite3Ptr);
var stringPtr = ref.refType(ref.types.CString);

// binding to a few "libsqlite3" functions...
var libsqlite3 = ffi.Library('libsqlite3', {
  'sqlite3_open': [ 'int', [ 'string', sqlite3PtrPtr ] ],
  'sqlite3_close': [ 'int', [ sqlite3PtrPtr ] ],
  'sqlite3_exec': [ 'int', [ sqlite3PtrPtr, 'string', 'pointer', 'pointer', stringPtr ] ],
  'sqlite3_changes': [ 'int', [ sqlite3PtrPtr ]]
});

// now use them:
var dbPtrPtr = ref.alloc(sqlite3PtrPtr);
libsqlite3.sqlite3_open("test.sqlite3", dbPtrPtr);
var dbHandle = dbPtrPtr.deref();

signature:

ffi.Library(libraryFile, { functionSymbol: [ returnType, [ arg1Type, arg2Type, ... ], ... ]);

To construct a usable Library object, a "libraryFile" String and at least one function must be defined in the specification.

Common Usage

For the purposes of this explanation, we are going to use a fictitious interface specification for "libmylibrary." Here's the C interface we've seen in its .h header file:

double    do_some_number_fudging(double a, int b);
myobj *   create_object();
double    do_stuff_with_object(myobj *obj);
void      use_string_with_object(myobj *obj, char *value);
void      delete_object(myobj *obj);

Our C code would be something like this:

#include "mylibrary.h"
int main()
{
    myobj *fun_object;
    double res, fun;

    res = do_some_number_fudging(1.5, 5);
    fun_object = create_object();

    if (fun_object == NULL) {
      printf("Oh no! Couldn't create object!\n");
      exit(2);
    }

    use_string_with_object(fun_object, "Hello World!");
    fun = do_stuff_with_object(fun_object);
    delete_object(fun_object);
}

The JavaScript code to wrap this library would be:

var ref = require("ref");
var ffi = require("ffi");

// typedefs
var myobj = ref.types.void // we don't know what the layout of "myobj" looks like

var MyLibrary = ffi.Library('libmylibrary', {
  "do_some_number_fudging": [ 'double', [ 'double', 'int' ] ],
  "create_object": [ "pointer", [] ],
  "do_stuff_with_object": [ "double", [ "pointer" ] ],
  "use_string_with_object": [ "void", [ "pointer", "string" ] ],
  "delete_object": [ "void", [ "pointer" ] ]
});

We could then use it from JavaScript:

var res = MyLibrary.do_some_number_fudging(1.5, 5);
var fun_object = MyLibrary.create_object();

if (fun_object.isNull()) {
    console.log("Oh no! Couldn't create object!\n");
} else {
    MyLibrary.use_string_with_object(fun_object, "Hello World!");
    var fun = MyLibrary.do_stuff_with_object(fun_object);
    MyLibrary.delete_object(fun_object);
}

Output Parameters

Sometimes C APIs will actually return things using parameters. Passing a pointer allows the called function to manipulate memory that has been passed to it.

Let's imagine our fictitious library has an additional function:

void manipulate_number(int *out_number);

Notice that the out_number parameter is an int *, not an int. This means that we're only going to pass a pointer to a value (or passing by reference), not the actual value itself. In C, we'd do the following to call this method:

int outNumber = 0;
manipulate_number(&outNumber);

The & (lvalue) operator extracts a pointer for the outNumber variable. How do we do this in JavaScript? Let's define the wrapper:

var intPtr = ref.refType('int');

var libmylibrary = ffi.Library('libmylibrary', { ...,
  'manipulate_number': [ 'void', [ intPtr ] ]
});

Note how we've actually defined this method as taking a int * parameter, not an int as we would if we were passing by value. To call the method, we must first allocate space to store the output data using the ref.alloc() function, then call the function with the returned Buffer instance.

var outNumber = ref.alloc('int'); // allocate a 4-byte (32-bit) chunk for the output data
libmylibrary.manipulate_number(outNumber);
var actualNumber = outNumber.deref();

Once we've called the function, our value is now stored in the memory we've allocated in outNumber. To extract it, we have to read the 32-bit signed integer value into a JavaScript Number value by calling the .deref() function.

Async Library Calls

node-ffi supports the ability to execute library calls in a different thread using the libuv library. To use the async support, you invoke the .async() function on any returned FFI'd function.

var libmylibrary = ffi.Library('libmylibrary', {
  'mycall': [ 'int', [ 'int' ] ]
});

libmylibrary.mycall.async(1234, function (err, res) {});

Now a call to the function runs on the thread pool and invokes the supplied callback function when completed. Following the node convention, an err argument is passed to the callback first, followed by the res containing the result of the function call.

libmylibrary.mycall.async(1234, function (err, res) {
  if (err) throw err;
  console.log("mycall returned " + res);
});

Structs

To provide the ability to read and write C-style data structures, node-ffi is compatible with the ref-struct module. See its documentation for more information about defining Struct types. The returned Struct constructors are valid "types" for use in FFI'd functions, for example gettimeofday():

var ref = require('ref');
var FFI = require('ffi');
var Struct = require('ref-struct');

var TimeVal = Struct({
  'tv_sec': 'long',
  'tv_usec': 'long'
});
var TimeValPtr = ref.refType(TimeVal);

var lib = new FFI.Library(null, { 'gettimeofday': [ 'int', [ TimeValPtr, "pointer" ] ]});
var tv = new TimeVal();
lib.gettimeofday(tv.ref(), null);
console.log("Seconds since epoch: " + tv.tv_sec);
Clone this wiki locally