Polymorphic arguments#
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()
.
1// ES6 syntax: import koffi from 'koffi';
2const koffi = require('koffi');
3
4const lib = koffi.load('libc.so.6');
5
6const FILE = koffi.opaque('FILE');
7
8const PngHeader = koffi.pack('PngHeader', {
9 signature: koffi.array('uint8_t', 8),
10 ihdr: koffi.pack({
11 length: 'uint32_be_t',
12 chunk: koffi.array('char', 4),
13 width: 'uint32_be_t',
14 height: 'uint32_be_t',
15 depth: 'uint8_t',
16 color: 'uint8_t',
17 compression: 'uint8_t',
18 filter: 'uint8_t',
19 interlace: 'uint8_t',
20 crc: 'uint32_be_t'
21 })
22});
23
24const fopen = lib.func('FILE *fopen(const char *path, const char *mode)');
25const fclose = lib.func('int fclose(FILE *fp)');
26const fread = lib.func('size_t fread(_Out_ void *ptr, size_t size, size_t nmemb, FILE *fp)');
27
28let filename = process.argv[2];
29if (filename == null)
30 throw new Error('Usage: node png.js <image.png>');
31
32let hdr = {};
33{
34 let fp = fopen(filename, 'rb');
35 if (!fp)
36 throw new Error(`Failed to open '${filename}'`);
37
38 try {
39 let len = fread(koffi.as(hdr, 'PngHeader *'), 1, koffi.sizeof(PngHeader), fp);
40 if (len < koffi.sizeof(PngHeader))
41 throw new Error('Failed to read PNG header');
42 } finally {
43 fclose(fp);
44 }
45}
46
47console.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:
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.