Union values#

New in Koffi 2.5

Union definition#

You can declare unions with a syntax similar to structs, but with the koffi.union() function. This function takes two arguments: the first one is the name of the type, and the second one is an object containing the union member names and types. You can omit the first argument to declare an anonymous union.

The following example illustrates how to declare the same union in C and in JS with Koffi:

1typedef union IntOrDouble {
2    int64_t i;
3    double d;
4} IntOrDouble;
1const IntOrDouble = koffi.union('IntOrDouble', {
2    i: 'int64_t',
3    d: 'double'
4});

Input unions#

Passing union values to C#

You can instantiate an union object with koffi.Union(type). This will create a special object that contains at most one active member.

Once you have created an instance of your union, you can simply set the member with the dot operator as you would with a basic object. Then, simply pass your union value to the C function you wish.

1const U = koffi.union('U', { i: 'int', str: 'char *' });
2
3const DoSomething = lib.func('void DoSomething(const char *type, U u)');
4
5const u1 = new koffi.Union('U'); u1.i = 42;
6const u2 = new koffi.Union('U'); u2.str = 'Hello!';
7
8DoSomething('int', u1);
9DoSomething('string', u2);

For simplicity, Koffi also accepts object literals with one property (no more, no less) setting the corresponding union member. The example belows uses this to simplify the code shown above:

1const U = koffi.union('U', { i: 'int', str: 'char *' });
2
3const DoSomething = lib.func('void DoSomething(const char *type, U u)');
4
5DoSomething('int', { i: 42 });
6DoSomething('string', { str: 'Hello!' });

Win32 example#

The following example uses the SendInput Win32 API to emit the Win+D shortcut and hide windows (show the desktop).

 1// ES6 syntax: import koffi from 'koffi';
 2const koffi = require('koffi');
 3
 4// Win32 type and functions
 5
 6const user32 = koffi.load('user32.dll');
 7
 8const INPUT_MOUSE = 0;
 9const INPUT_KEYBOARD = 1;
10const INPUT_HARDWARE = 2;
11
12const KEYEVENTF_KEYUP = 0x2;
13const KEYEVENTF_SCANCODE = 0x8;
14
15const VK_LWIN = 0x5B;
16const VK_D = 0x44;
17
18const MOUSEINPUT = koffi.struct('MOUSEINPUT', {
19    dx: 'long',
20    dy: 'long',
21    mouseData: 'uint32_t',
22    dwFlags: 'uint32_t',
23    time: 'uint32_t',
24    dwExtraInfo: 'uintptr_t'
25});
26const KEYBDINPUT = koffi.struct('KEYBDINPUT', {
27    wVk: 'uint16_t',
28    wScan: 'uint16_t',
29    dwFlags: 'uint32_t',
30    time: 'uint32_t',
31    dwExtraInfo: 'uintptr_t'
32});
33const HARDWAREINPUT = koffi.struct('HARDWAREINPUT', {
34    uMsg: 'uint32_t',
35    wParamL: 'uint16_t',
36    wParamH: 'uint16_t'
37});
38
39const INPUT = koffi.struct('INPUT', {
40    type: 'uint32_t',
41    u: koffi.union({
42        mi: MOUSEINPUT,
43        ki: KEYBDINPUT,
44        hi: HARDWAREINPUT
45    })
46});
47
48const SendInput = user32.func('unsigned int __stdcall SendInput(unsigned int cInputs, INPUT *pInputs, int cbSize)');
49
50// Show/hide desktop with Win+D shortcut
51
52let events = [
53    make_keyboard_event(VK_LWIN, true),
54    make_keyboard_event(VK_D, true),
55    make_keyboard_event(VK_D, false),
56    make_keyboard_event(VK_LWIN, false)
57];
58
59SendInput(events.length, events, koffi.sizeof(INPUT));
60
61// Utility
62
63function make_keyboard_event(vk, down) {
64    let event = {
65        type: INPUT_KEYBOARD,
66        u: {
67            ki: {
68                wVk: vk,
69                wScan: 0,
70                dwFlags: down ? 0 : KEYEVENTF_KEYUP,
71                time: 0,
72                dwExtraInfo: 0
73            }
74        }
75    };
76
77    return event;
78}

Output unions#

Unlike structs, Koffi does not know which union member is valid, and it cannot decode it automatically. You can however use special koffi.Union objects for output parameters, and decode the memory after the call.

To decode an output union pointer parameter, create a placeholder object with new koffi.Union(type) and pass the resulting object to the function.

After the call, you can dereference the member value you want on this object and Koffi will decode it at this moment.

The following example illustrates the use of koffi.Union() to decode output unions after the call.

 1#include <stdint.h>
 2
 3typedef union IntOrDouble {
 4    int64_t i;
 5    double d;
 6} IntOrDouble;
 7
 8void SetUnionInt(int64_t i, IntOrDouble *out)
 9{
10    out->i = i;
11}
12
13void SetUnionDouble(double d, IntOrDouble *out)
14{
15    out->d = d;
16}
 1const IntOrDouble = koffi.union('IntOrDouble', {
 2    i: 'int64_t',
 3    d: 'double',
 4    raw: koffi.array('uint8_t', 8)
 5});
 6
 7const SetUnionInt = lib.func('void SetUnionInt(int64_t i, _Out_ IntOrDouble *out)');
 8const SetUnionDouble = lib.func('void SetUnionDouble(double d, _Out_ IntOrDouble *out)');
 9
10let u1 = new koffi.Union('IntOrDouble');
11let u2 = new koffi.Union('IntOrDouble');
12
13SetUnionInt(123, u1);
14SetUnionDouble(123, u2);
15
16console.log(u1.i, '---', u1.raw); // Prints 123 --- Uint8Array(8) [123, 0, 0, 0, 0, 0, 0, 0]
17console.log(u2.d, '---', u2.raw); // Prints 123 --- Uint8Array(8) [0, 0, 0, 0, 0, 0, 69, 64]