Function calls#
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.
1const koffi = require('koffi');
2const lib = koffi.load('libc.so.6');
3
4const atoi = lib.func('int atoi(const char *str)');
5
6atoi.async('1257', (err, res) => {
7 console.log('Result:', res);
8})
9console.log('Hello World!');
10
11// This program will print:
12// Hello World!
13// 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.
Output parameters#
By default, Koffi will only forward arguments from Javascript to C. However, many C functions use pointer arguments for output values, or input/output values.
For simplicity, and because Javascript only has value semantics for primitive types, Koffi can marshal out (or in/out) two types of parameters:
Structs (to/from JS objects)
String buffers
In order to change an argument from input-only to output or input/output, use the following functions:
koffi.out()
on a pointer, e.g.koffi.out(koffi.pointer(timeval))
(where timeval is a struct type)koffi.inout()
for dual input/output parameters
The same can be done when declaring a function with a C-like prototype string, with the MSDN-like type qualifiers:
_Out_
for output parameters_Inout_
for dual input/output parameters
Primitive value#
This Windows example enumerate all Chrome windows along with their PID and their title. The GetWindowThreadProcessId()
function illustrates how to get a primitive value from an output argument.
1const koffi = require('koffi');
2const user32 = koffi.load('user32.dll');
3
4const DWORD = koffi.alias('DWORD', 'uint32_t');
5const HANDLE = koffi.pointer(koffi.opaque('HANDLE'));
6const HWND = koffi.alias('HWND', HANDLE);
7
8const FindWindowEx = user32.func('HWND __stdcall FindWindowExW(HWND hWndParent, HWND hWndChildAfter, const char16_t *lpszClass, const char16_t *lpszWindow)');
9const GetWindowThreadProcessId = user32.func('DWORD __stdcall GetWindowThreadProcessId(HWND hWnd, _Out_ DWORD *lpdwProcessId)');
10const GetWindowText = user32.func('int __stdcall GetWindowTextA(HWND hWnd, _Out_ uint8_t *lpString, int nMaxCount)');
11
12for (let hwnd = null;;) {
13 hwnd = FindWindowEx(0, hwnd, 'Chrome_WidgetWin_1', null);
14
15 if (!hwnd)
16 break;
17
18 // Get PID
19 let pid;
20 {
21 let ptr = [null];
22 let tid = GetWindowThreadProcessId(hwnd, ptr);
23
24 if (!tid) {
25 // Maybe the process ended in-between?
26 continue;
27 }
28
29 pid = ptr[0];
30 }
31
32 // Get window title
33 let title;
34 {
35 let buf = Buffer.allocUnsafe(1024);
36 let length = GetWindowText(hwnd, buf, buf.length);
37
38 if (!length) {
39 // Maybe the process ended in-between?
40 continue;
41 }
42
43 title = koffi.decode(buf, 'char', length);
44 }
45
46 console.log({ PID: pid, Title: title });
47}
Struct example#
This example calls the POSIX function gettimeofday()
, and uses the prototype-like syntax.
1const koffi = require('koffi');
2const lib = koffi.load('libc.so.6');
3
4const timeval = koffi.struct('timeval', {
5 tv_sec: 'unsigned int',
6 tv_usec: 'unsigned int'
7});
8const timezone = koffi.struct('timezone', {
9 tz_minuteswest: 'int',
10 tz_dsttime: 'int'
11});
12
13// The _Out_ qualifiers instruct Koffi to marshal out the values
14const gettimeofday = lib.func('int gettimeofday(_Out_ timeval *tv, _Out_ timezone *tz)');
15
16let tv = {};
17gettimeofday(tv, null);
18
19console.log(tv);
Opaque type example#
This example opens an in-memory SQLite database, and uses the node-ffi-style function declaration syntax.
1const koffi = require('koffi');
2const lib = koffi.load('sqlite3.so');
3
4const sqlite3 = koffi.opaque('sqlite3');
5
6// Use koffi.out() on a double pointer to copy out (from C to JS) after the call
7const sqlite3_open_v2 = lib.func('sqlite3_open_v2', 'int', ['str', koffi.out(koffi.pointer(sqlite3, 2)), 'int', 'str']);
8const sqlite3_close_v2 = lib.func('sqlite3_close_v2', 'int', [koffi.pointer(sqlite3)]);
9
10const SQLITE_OPEN_READWRITE = 0x2;
11const SQLITE_OPEN_CREATE = 0x4;
12
13let out = [null];
14if (sqlite3_open_v2(':memory:', out, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, null) != 0)
15 throw new Error('Failed to open database');
16let db = out[0];
17
18sqlite3_close_v2(db);
String buffer example#
New in Koffi 2.2
This example calls a C function to concatenate two strings to a pre-allocated string buffer. Since JS strings are immutable, you must pass an array with a single string instead.
1void ConcatToBuffer(const char *str1, const char *str2, char *out)
2{
3 size_t len = 0;
4
5 for (size_t i = 0; str1[i]; i++) {
6 out[len++] = str1[i];
7 }
8 for (size_t i = 0; str2[i]; i++) {
9 out[len++] = str2[i];
10 }
11
12 out[len] = 0;
13}
1const ConcatToBuffer = lib.func('void ConcatToBuffer(const char *str1, const char *str2, _Out_ char *out)');
2
3let str1 = 'Hello ';
4let str2 = 'Friends!';
5
6// We need to reserve space for the output buffer! Including the NUL terminator
7// because ConcatToBuffer() expects so, but Koffi can convert back to a JS string
8// without it (if we reserve the right size).
9let out = ['\0'.repeat(str1.length + str2.length + 1)];
10
11ConcatToBuffer(str1, str2, out);
12
13console.log(out[0]);
Polymorphic parameters#
Input polymorphism#
New in Koffi 2.1
Many C functions use void *
parameters in order to pass polymorphic objects and arrays, meaning that the data format changes can change depending on one other argument, or on some kind of struct tag member.
Koffi provides two features to deal with this:
Buffers and typed JS arrays can be used as values in place everywhere a pointer is expected. See dynamic arrays for more information, for input or output.
You can use
koffi.as(value, type)
to tell Koffi what kind of type is actually expected.
The example below shows the use of koffi.as()
to read the header of a PNG file with fread()
.
1const koffi = require('koffi');
2const lib = koffi.load('libc.so.6');
3
4const FILE = koffi.opaque('FILE');
5
6const PngHeader = koffi.pack('PngHeader', {
7 signature: koffi.array('uint8_t', 8),
8 ihdr: koffi.pack({
9 length: 'uint32_be_t',
10 chunk: koffi.array('char', 4),
11 width: 'uint32_be_t',
12 height: 'uint32_be_t',
13 depth: 'uint8_t',
14 color: 'uint8_t',
15 compression: 'uint8_t',
16 filter: 'uint8_t',
17 interlace: 'uint8_t',
18 crc: 'uint32_be_t'
19 })
20});
21
22const fopen = lib.func('FILE *fopen(const char *path, const char *mode)');
23const fclose = lib.func('int fclose(FILE *fp)');
24const fread = lib.func('size_t fread(_Out_ void *ptr, size_t size, size_t nmemb, FILE *fp)');
25
26let filename = process.argv[2];
27if (filename == null)
28 throw new Error('Usage: node png.js <image.png>');
29
30let hdr = {};
31{
32
33 let fp = fopen(filename, 'rb');
34 if (!fp)
35 throw new Error(`Failed to open '${filename}'`);
36
37 try {
38 let len = fread(koffi.as(hdr, 'PngHeader *'), 1, koffi.sizeof(PngHeader), fp);
39 if (len < koffi.sizeof(PngHeader))
40 throw new Error('Failed to read PNG header');
41 } finally {
42 fclose(fp);
43 }
44}
45
46console.log('PNG header:', hdr);
Output buffers#
New in Koffi 2.3
You can use buffers and typed arrays for output (and input/output) pointer parameters. Simply pass the buffer as an argument and the native function will receive a pointer to its contents.
Once the native function returns, you can decode the content with koffi.decode(value, type)
as in the following example:
1const koffi = require('koffi');
2const lib = koffi.load('libc.so.6');
3
4const Vec3 = koffi.struct('Vec3', {
5 x: 'float32',
6 y: 'float32',
7 z: 'float32'
8})
9
10const memcpy = lib.func('void *memcpy(_Out_ void *dest, const void *src, size_t size)');
11
12let vec1 = { x: 1, y: 2, z: 3 };
13let vec2 = null;
14
15// Copy the vector in a convoluted way through memcpy
16{
17 let src = koffi.as(vec1, 'Vec3 *');
18 let dest = Buffer.allocUnsafe(koffi.sizeof(Vec3));
19
20 memcpy(dest, src, koffi.sizeof(Vec3));
21
22 vec2 = koffi.decode(dest, Vec3);
23}
24
25// CHange vector1, leaving copy alone
26[vec1.x, vec1.y, vec1.z] = [vec1.z, vec1.y, vec1.x];
27
28console.log(vec1); // { x: 3, y: 2, z: 1 }
29console.log(vec2); // { x: 1, y: 2, z: 3 }
See pointer arguments for more information about the decode function.
Heap-allocated values#
New in Koffi 2.0
Some C functions return heap-allocated values directly or through output parameters. While Koffi automatically converts values from C to JS (to a string or an object), it does not know when something needs to be freed, or how.
For opaque types, such as FILE, this does not matter because you will explicitly call fclose()
on them. But some values (such as strings) get implicitly converted by Koffi, and you lose access to the original pointer. This creates a leak if the string is heap-allocated.
To avoid this, you can instruct Koffi to call a function on the original pointer once the conversion is done, by creating a disposable type with koffi.dispose(name, type, func)
. This creates a type derived from another type, the only difference being that func gets called with the original pointer once the value has been converted and is not needed anymore.
The name can be omitted to create an anonymous disposable type. If func is omitted or is null, Koffi will use koffi.free(ptr)
(which calls the standard C library free function under the hood).
1const AnonHeapStr = koffi.disposable('str'); // Anonymous type (cannot be used in function prototypes)
2const NamedHeapStr = koffi.disposable('HeapStr', 'str'); // Same thing, but named so usable in function prototypes
3const ExplicitFree = koffi.disposable('HeapStr16', 'str16', koffi.free); // You can specify any other JS function
The following example illustrates the use of a disposable type derived from str.
1const koffi = require('koffi');
2const lib = koffi.load('libc.so.6');
3
4const HeapStr = koffi.disposable('str');
5const strdup = lib.cdecl('strdup', HeapStr, ['str']);
6
7let copy = strdup('Hello!');
8console.log(copy); // Prints Hello!
When you declare functions with the prototype-like syntax, you can either use named disposable types or use the ‘!’ shortcut qualifier with compatibles types, as shown in the example below. This qualifier creates an anonymous disposable type that calls koffi.free(ptr)
.
1const koffi = require('koffi');
2const lib = koffi.load('libc.so.6');
3
4// You can also use: const strdup = lib.func('const char *! strdup(const char *str)')
5const strdup = lib.func('str! strdup(const char *str)');
6
7let copy = strdup('World!');
8console.log(copy); // Prints World!
Disposable types can only be created from pointer or string types.
Warning
Be careful on Windows: if your shared library uses a different CRT (such as msvcrt), the memory could have been allocated by a different malloc/free implementation or heap, resulting in undefined behavior if you use koffi.free()
.
Thread safety#
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!