Function calls#

Loading libraries#

To declare functions, start by loading the shared library with koffi.load(filename).

1// ES6 syntax: import koffi from 'koffi';
2const koffi = require('koffi');
3
4const lib = koffi.load('/path/to/shared/library'); // File extension depends on platforms: .so, .dll, .dylib, etc.

This library will be automatically unloaded once all references to it are gone (including all the functions that use it, as described below).

Starting with Koffi 2.3.20, you can explicitly unload a library by calling lib.unload(). Any attempt to find or call a function from this library after unloading it will crash.

Note

On some platforms (such as with the musl C library on Linux), shared libraries cannot be unloaded, so the library will remain loaded and memory mapped after the call to lib.unload().

Loading options#

New in Koffi 2.6, changed in Koffi 2.8.2 and Koffi 2.8.6

The load function can take an optional object argument, with the following options:

1const options = {
2    lazy: true, // Use RTLD_LAZY (lazy-binding) on POSIX platforms (by default, use RTLD_NOW)
3    global: true, // Use RTLD_GLOBAL on POSIX platforms (by default, use RTLD_LOCAL)
4    deep: true // Use RTLD_DEEPBIND if supported (Linux, FreeBSD)
5};
6
7const lib = koffi.load('/path/to/shared/library.so', options);

More options may be added if needed.

Function definitions#

Definition syntax#

Use the object returned by koffi.load() to load C functions from the library. To do so, you can use two syntaxes:

  • The classic syntax, inspired by node-ffi

  • C-like prototypes

Classic syntax#

To declare a function, you need to specify its non-mangled name, its return type, and its parameters. Use an ellipsis as the last parameter for variadic functions.

1const printf = lib.func('printf', 'int', ['str', '...']);
2const atoi = lib.func('atoi', 'int', ['str']);

Koffi automatically tries mangled names for non-standard x86 calling conventions. See the section on calling conventions for more information on this subject.

C-like prototypes#

If you prefer, you can declare functions using simple C-like prototype strings, as shown below:

1const printf = lib.func('int printf(const char *fmt, ...)');
2const atoi = lib.func('int atoi(str)'); // The parameter name is not used by Koffi, and optional

You can use () or (void) for functions that take no argument.

Variadic functions#

Variadic functions are declared with an ellipsis as the last argument.

In order to call a variadic function, you must provide two Javascript arguments for each additional C parameter, the first one is the expected type and the second one is the value.

1const printf = lib.func('printf', 'int', ['str', '...']);
2
3// The variadic arguments are: 6 (int), 8.5 (double), 'THE END' (const char *)
4printf('Integer %d, double %g, str %s', 'int', 6, 'double', 8.5, 'str', 'THE END');

On x86 platforms, only the Cdecl convention can be used for variadic functions.

Calling conventions#

Changed in Koffi 2.7

By default, calling a C function happens synchronously.

Most architectures only support one procedure call standard per process. The 32-bit x86 platform is an exception to this, and Koffi supports several x86 conventions:

Convention

Classic form

Prototype form

Description

Cdecl

koffi.func(name, ret, params)

(default)

This is the default convention, and the only one on other platforms

Stdcall

koffi.func('__stdcall', name, ret, params)

__stdcall

This convention is used extensively within the Win32 API

Fastcall

koffi.func('__fastcall', name, ret, params)

__fastcall

Rarely used, uses ECX and EDX for first two parameters

Thiscall

koffi.func('__thiscall', name, ret, params)

__thiscall

Rarely used, uses ECX for first parameter

You can safely use these on non-x86 platforms, they are simply ignored.

Note

Support for specifying the convention as the first argument of the classic form was introduced in Koffi 2.7.

In earlier versions, you had to use koffi.stdcall() and similar functions. These functions are still supported but deprecated, and will be removed in Koffi 3.0.

Below you can find a small example showing how to use a non-default calling convention, with the two syntaxes:

1// ES6 syntax: import koffi from 'koffi';
2const koffi = require('koffi');
3
4const lib = koffi.load('user32.dll');
5
6// The following two declarations are equivalent, and use stdcall on x86 (and the default ABI on other platforms)
7const MessageBoxA_1 = lib.func('__stdcall', 'MessageBoxA', 'int', ['void *', 'str', 'str', 'uint']);
8const MessageBoxA_2 = lib.func('int __stdcall MessageBoxA(void *hwnd, str text, str caption, uint type)');

Call types#

Synchronous calls#

Once a native function has been declared, you can simply call it as you would any other JS function.

1const atoi = lib.func('int atoi(const char *str)');
2
3let value = atoi('1257');
4console.log(value);

For variadic functions, you msut specificy the type and the value for each additional argument.

1const printf = lib.func('printf', 'int', ['str', '...']);
2
3// The variadic arguments are: 6 (int), 8.5 (double), 'THE END' (const char *)
4printf('Integer %d, double %g, str %s', 'int', 6, 'double', 8.5, 'str', 'THE END');

Asynchronous calls#

You can issue asynchronous calls by calling the function through its async member. In this case, you need to provide a callback function as the last argument, with (err, res) parameters.

 1// ES6 syntax: import koffi from 'koffi';
 2const koffi = require('koffi');
 3
 4const lib = koffi.load('libc.so.6');
 5
 6const atoi = lib.func('int atoi(const char *str)');
 7
 8atoi.async('1257', (err, res) => {
 9    console.log('Result:', res);
10})
11console.log('Hello World!');
12
13// This program will print:
14//   Hello World!
15//   Result: 1257

These calls are executed by worker threads. It is your responsibility to deal with data sharing issues in the native code that may be caused by multi-threading.

You can easily convert this callback-style async function to a promise-based version with util.promisify() from the Node.js standard library.

Variadic functions cannot be called asynchronously.

Warning

Asynchronous functions run on worker threads. You need to deal with thread safety issues if you share data between threads.

Callbacks must be called from the main thread, or more precisely from the same thread as the V8 intepreter. Calling a callback from another thread is undefined behavior, and will likely lead to a crash or a big mess. You’ve been warned!

Function pointers#

New in Koffi 2.4

You can call a function pointer in two ways:

  • Directly call the function pointer with koffi.call(ptr, type, ...)

  • Decode the function pointer to an actual function with koffi.decode(ptr, type)

The example below shows how to call an int (*)(int, int) C function pointer both ways, based on the following native C library:

 1typedef int BinaryIntFunc(int a, int b);
 2
 3static int AddInt(int a, int b) { return a + b; }
 4static int SubstractInt(int a, int b) { return a - b; }
 5
 6BinaryIntFunc *GetBinaryIntFunction(const char *type)
 7{
 8    if (!strcmp(type, "add")) {
 9        return AddInt;
10    } else if (!strcmp(type, "substract")) {
11        return SubstractInt;
12    } else {
13        return NULL;
14    }
15}

Call pointer directly#

Use koffi.call(ptr, type, ...) to call a function pointer. The first two arguments are the pointer itself and the type of the function you are trying to call (declared with koffi.proto() as shown below), and the remaining arguments are used for the call.

 1// Declare function type
 2const BinaryIntFunc = koffi.proto('int BinaryIntFunc(int a, int b)');
 3
 4const GetBinaryIntFunction = lib.func('BinaryIntFunc *GetBinaryIntFunction(const char *name)');
 5
 6const add_ptr = GetBinaryIntFunction('add');
 7const substract_ptr = GetBinaryIntFunction('substract');
 8
 9let sum = koffi.call(add_ptr, BinaryIntFunc, 4, 5);
10let delta = koffi.call(substract_ptr, BinaryIntFunc, 100, 58);
11
12console.log(sum, delta); // Prints 9 and 42

Decode pointer to function#

Use koffi.decode(ptr, type) to get back a JS function, which you can then use like any other Koffi function.

This method also allows you to perform an asynchronous call with the async member of the decoded function.

 1// Declare function type
 2const BinaryIntFunc = koffi.proto('int BinaryIntFunc(int a, int b)');
 3
 4const GetBinaryIntFunction = lib.func('BinaryIntFunc *GetBinaryIntFunction(const char *name)');
 5
 6const add = koffi.decode(GetBinaryIntFunction('add'), BinaryIntFunc);
 7const substract = koffi.decode(GetBinaryIntFunction('substract'), BinaryIntFunc);
 8
 9let sum = add(4, 5);
10let delta = substract(100, 58);
11
12console.log(sum, delta); // Prints 9 and 42

Conversion of parameters#

By default, Koffi will only forward and translate arguments from Javascript to C. However, many C functions use pointer arguments for output values, or input/output values.

Among other thing, in the the following pages you will learn more about: