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:
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.
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(koffi.opaque('HANDLE'));
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]);