Output parameters#

Output and input/output#

For simplicity, and because Javascript only has value semantics for primitive types, Koffi can marshal out (or in/out) multiple types of parameters:

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.

 1// ES6 syntax: import koffi from 'koffi';
 2const koffi = require('koffi');
 3
 4const user32 = koffi.load('user32.dll');
 5
 6const DWORD = koffi.alias('DWORD', 'uint32_t');
 7const HANDLE = koffi.pointer('HANDLE', koffi.opaque());
 8const HWND = koffi.alias('HWND', HANDLE);
 9
10const FindWindowEx = user32.func('HWND __stdcall FindWindowExW(HWND hWndParent, HWND hWndChildAfter, const char16_t *lpszClass, const char16_t *lpszWindow)');
11const GetWindowThreadProcessId = user32.func('DWORD __stdcall GetWindowThreadProcessId(HWND hWnd, _Out_ DWORD *lpdwProcessId)');
12const GetWindowText = user32.func('int __stdcall GetWindowTextA(HWND hWnd, _Out_ uint8_t *lpString, int nMaxCount)');
13
14for (let hwnd = null;;) {
15    hwnd = FindWindowEx(0, hwnd, 'Chrome_WidgetWin_1', null);
16
17    if (!hwnd)
18        break;
19
20    // Get PID
21    let pid;
22    {
23        let ptr = [null];
24        let tid = GetWindowThreadProcessId(hwnd, ptr);
25
26        if (!tid) {
27            // Maybe the process ended in-between?
28            continue;
29        }
30
31        pid = ptr[0];
32    }
33
34    // Get window title
35    let title;
36    {
37        let buf = Buffer.allocUnsafe(1024);
38        let length = GetWindowText(hwnd, buf, buf.length);
39
40        if (!length) {
41            // Maybe the process ended in-between?
42            continue;
43        }
44
45        title = koffi.decode(buf, 'char', length);
46    }
47
48    console.log({ PID: pid, Title: title });
49}

Struct example#

This example calls the POSIX function gettimeofday(), and uses the prototype-like syntax.

 1// ES6 syntax: import koffi from 'koffi';
 2const koffi = require('koffi');
 3
 4const lib = koffi.load('libc.so.6');
 5
 6const timeval = koffi.struct('timeval', {
 7    tv_sec: 'unsigned int',
 8    tv_usec: 'unsigned int'
 9});
10const timezone = koffi.struct('timezone', {
11    tz_minuteswest: 'int',
12    tz_dsttime: 'int'
13});
14
15// The _Out_ qualifiers instruct Koffi to marshal out the values
16const gettimeofday = lib.func('int gettimeofday(_Out_ timeval *tv, _Out_ timezone *tz)');
17
18let tv = {};
19gettimeofday(tv, null);
20
21console.log(tv);

Opaque type example#

This example opens an in-memory SQLite database, and uses the node-ffi-style function declaration syntax.

 1// ES6 syntax: import koffi from 'koffi';
 2const koffi = require('koffi');
 3
 4const lib = koffi.load('sqlite3.so');
 5
 6const sqlite3 = koffi.opaque('sqlite3');
 7
 8// Use koffi.out() on a double pointer to copy out (from C to JS) after the call
 9const sqlite3_open_v2 = lib.func('sqlite3_open_v2', 'int', ['str', koffi.out(koffi.pointer(sqlite3, 2)), 'int', 'str']);
10const sqlite3_close_v2 = lib.func('sqlite3_close_v2', 'int', [koffi.pointer(sqlite3)]);
11
12const SQLITE_OPEN_READWRITE = 0x2;
13const SQLITE_OPEN_CREATE = 0x4;
14
15let out = [null];
16if (sqlite3_open_v2(':memory:', out, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, null) != 0)
17    throw new Error('Failed to open database');
18let db = out[0];
19
20sqlite3_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]);

Output buffers#

In most cases, you can use buffers and typed arrays to provide output buffers. This works as long as the buffer only gets used while the native C function is being called. See transient pointers below for an example.

Warning

It is unsafe to keep the pointer around in the native code, or to change the contents outside of the function call where it is provided.

If you need to provide a pointer that will be kept around, allocate memory with koffi.alloc() instead.

Transient pointers#

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:

 1// ES6 syntax: import koffi from 'koffi';
 2const koffi = require('koffi');
 3
 4const lib = koffi.load('libc.so.6');
 5
 6const Vec3 = koffi.struct('Vec3', {
 7    x: 'float32',
 8    y: 'float32',
 9    z: 'float32'
10})
11
12const memcpy = lib.func('void *memcpy(_Out_ void *dest, const void *src, size_t size)');
13
14let vec1 = { x: 1, y: 2, z: 3 };
15let vec2 = null;
16
17// Copy the vector in a convoluted way through memcpy
18{
19    let src = koffi.as(vec1, 'Vec3 *');
20    let dest = Buffer.allocUnsafe(koffi.sizeof(Vec3));
21
22    memcpy(dest, src, koffi.sizeof(Vec3));
23
24    vec2 = koffi.decode(dest, Vec3);
25}
26
27// CHange vector1, leaving copy alone
28[vec1.x, vec1.y, vec1.z] = [vec1.z, vec1.y, vec1.x];
29
30console.log(vec1); // { x: 3, y: 2, z: 1 }
31console.log(vec2); // { x: 1, y: 2, z: 3 }

See decoding variables for more information about the decode function.

Stable pointers#

New in Koffi 2.8

In some cases, the native code may need to change the output buffer at a later time, maybe during a later call or from another thread.

In this case, it is not safe to use buffers or typed arrays!

However, you can use koffi.alloc(type, len) to allocate memory and get a pointer that won’t move, and can be safely used at any time by the native code. Use koffi.decode() to read data from the pointer when needed.

The example below sets up some memory to be used as an output buffer where a concatenation function appends a string on each call.

 1#include <assert.h>
 2#include <stddef.h>
 3
 4static char *buf_ptr;
 5static size_t buf_len;
 6static size_t buf_size;
 7
 8void reset_buffer(char *buf, size_t size)
 9{
10    assert(size > 1);
11
12    buf_ptr = buf;
13    buf_len = 0;
14    buf_size = size - 1; // Keep space for trailing NUL
15
16    buf_ptr[0] = 0;
17}
18
19void append_str(const char *str)
20{
21    for (size_t i = 0; str[i] && buf_len < buf_size; i++, buf_len++) {
22        buf_ptr[buf_len] = str[i];
23    }
24    buf_ptr[buf_len] = 0;
25}
 1const reset_buffer = lib.func('void reset_buffer(char *buf, size_t size)');
 2const append_str = lib.func('void append_str(const char *str)');
 3
 4let output = koffi.alloc('char', 64);
 5reset_buffer(output, 64);
 6
 7append_str('Hello');
 8console.log(koffi.decode(output, 'char', -1)); // Prints Hello
 9
10append_str(' World!');
11console.log(koffi.decode(output, 'char', -1)); // Prints Hello World!