Migration guide#

Koffi 1.x to 2.x#

The API was changed in 2.x in a few ways, in order to reduce some excessively “magic” behavior and reduce the syntax differences between C and the C-like prototypes.

You may need to change your code if you use:

  • Callback functions

  • Opaque types

  • koffi.introspect()

Callback type changes#

In Koffi 1.x, callbacks were defined in a way that made them usable directly as parameter and return types, obscuring the underlying pointer. Now, you must use them through a pointer: void CallIt(CallbackType func) in Koffi 1.x becomes void CallIt(CallbackType *func) in version 2.0 and newer.

Given the following C code:

1#include <string.h>
2
3int TransferToJS(const char *name, int age, int (*cb)(const char *str, int age))
4{
5    char buf[64];
6    snprintf(buf, sizeof(buf), "Hello %s!", str);
7    return cb(buf, age);
8}

The two versions below illustrate the API difference between Koffi 1.x and Koffi 2.x:

 1// Koffi 1.x
 2
 3const TransferCallback = koffi.proto('int TransferCallback(const char *str, int age)');
 4
 5const TransferToJS = lib.func('TransferToJS', 'int', ['str', 'int', TransferCallback]);
 6// Equivalent to: const TransferToJS = lib.func('int TransferToJS(str s, int x, TransferCallback cb)');
 7
 8let ret = TransferToJS('Niels', 27, (str, age) => {
 9    console.log(str);
10    console.log('Your age is:', age);
11    return 42;
12});
13console.log(ret);
 1// Koffi 2.x
 2
 3const TransferCallback = koffi.proto('int TransferCallback(const char *str, int age)');
 4
 5const TransferToJS = lib.func('TransferToJS', 'int', ['str', 'int', koffi.pointer(TransferCallback)]);
 6// Equivalent to: const TransferToJS = lib.func('int TransferToJS(str s, int x, TransferCallback *cb)');
 7
 8let ret = TransferToJS('Niels', 27, (str, age) => {
 9    console.log(str);
10    console.log('Your age is:', age);
11    return 42;
12});
13console.log(ret);

Koffi 1.x only supported transient callbacks, you must use Koffi 2.x for registered callbacks.

Note

The function koffi.proto() was introduced in Koffi 2.4, it was called koffi.callback() in earlier versions.

Opaque type changes#

In Koffi 1.x, opaque handles were defined in a way that made them usable directly as parameter and return types, obscuring the underlying pointer. Now, in Koffi 2.0, you must use them through a pointer, and use an array for output parameters.

In addition to that, koffi.handle() has been deprecated in Koffi 2.1 and replaced with koffi.opaque(). They work the same but new code should use koffi.opaque(), the former one will eventually be removed in Koffi 3.0.

For functions that return opaque pointers or pass them by parameter:

 1// Koffi 1.x
 2
 3const FILE = koffi.handle('FILE');
 4const fopen = lib.func('fopen', 'FILE', ['str', 'str']);
 5const fopen = lib.func('fclose', 'int', ['FILE']);
 6
 7let fp = fopen('EMPTY', 'wb');
 8if (!fp)
 9    throw new Error('Failed to open file');
10fclose(fp);
 1// Koffi 2.1
 2
 3// If you use Koffi 2.0: const FILE = koffi.handle('FILE');
 4const FILE = koffi.opaque('FILE');
 5const fopen = lib.func('fopen', 'FILE *', ['str', 'str']);
 6const fopen = lib.func('fclose', 'int', ['FILE *']);
 7
 8let fp = fopen('EMPTY', 'wb');
 9if (!fp)
10    throw new Error('Failed to open file');
11fclose(fp);

For functions that set opaque handles through output parameters (such as sqlite3_open_v2), you must now use a single element array as shown below:

 1// Koffi 1.x
 2
 3const sqlite3 = koffi.handle('sqlite3');
 4
 5const sqlite3_open_v2 = lib.func('int sqlite3_open_v2(const char *, _Out_ sqlite3 *db, int, const char *)');
 6const sqlite3_close_v2 = lib.func('int sqlite3_close_v2(sqlite3 db)');
 7
 8const SQLITE_OPEN_READWRITE = 0x2;
 9const SQLITE_OPEN_CREATE = 0x4;
10
11let db = {};
12
13if (sqlite3_open_v2(':memory:', db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, null) != 0)
14    throw new Error('Failed to open database');
15
16sqlite3_close_v2(db);
 1// Koffi 2.1
 2
 3// If you use Koffi 2.0: const sqlite3 = koffi.handle('sqlite3');
 4const sqlite3 = koffi.opaque('sqlite3');
 5
 6const sqlite3_open_v2 = lib.func('int sqlite3_open_v2(const char *, _Out_ sqlite3 **db, int, const char *)');
 7const sqlite3_close_v2 = lib.func('int sqlite3_close_v2(sqlite3 *db)');
 8
 9const SQLITE_OPEN_READWRITE = 0x2;
10const SQLITE_OPEN_CREATE = 0x4;
11
12let db = null;
13
14let ptr = [null];
15if (sqlite3_open_v2(':memory:', ptr, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, null) != 0)
16    throw new Error('Failed to open database');
17db = ptr[0];
18
19sqlite3_close_v2(db);

New koffi.introspect()#

In Koffi 1.x, koffi.introspect() would only work with struct types, and return the object passed to koffi.struct() to initialize the type. Now this function works with all types.

You can still get the list of struct members:

1const StructType = koffi.struct('StructType', { dummy: 'int' });
2
3// Koffi 1.x
4let members = koffi.introspect(StructType);
5
6// Koffi 2.x
7let members = koffi.introspect(StructType).members;