koffi 2.7.4 → 2.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -3
- package/build/koffi/darwin_arm64/koffi.node +0 -0
- package/build/koffi/darwin_x64/koffi.node +0 -0
- package/build/koffi/freebsd_arm64/koffi.node +0 -0
- package/build/koffi/freebsd_ia32/koffi.node +0 -0
- package/build/koffi/freebsd_x64/koffi.node +0 -0
- package/build/koffi/linux_arm32hf/koffi.node +0 -0
- package/build/koffi/linux_arm64/koffi.node +0 -0
- package/build/koffi/linux_ia32/koffi.node +0 -0
- package/build/koffi/linux_riscv64hf64/koffi.node +0 -0
- package/build/koffi/linux_x64/koffi.node +0 -0
- package/build/koffi/openbsd_ia32/koffi.node +0 -0
- package/build/koffi/openbsd_x64/koffi.node +0 -0
- package/build/koffi/win32_arm64/koffi.node +0 -0
- package/build/koffi/win32_ia32/koffi.node +0 -0
- package/build/koffi/win32_x64/koffi.node +0 -0
- package/doc/functions.md +0 -1
- package/doc/index.rst +0 -1
- package/doc/input.md +1 -1
- package/doc/output.md +109 -1
- package/doc/pointers.md +64 -1
- package/index.js +3 -3
- package/indirect.js +3 -3
- package/package.json +2 -2
- package/src/cnoke/src/tools.js +1 -1
- package/src/core/libcc/libcc.hh +4 -4
- package/src/koffi/CMakeLists.txt +1 -1
- package/src/koffi/src/call.cc +5 -5
- package/src/koffi/src/ffi.cc +52 -0
- package/src/koffi/src/ffi.hh +5 -0
- package/src/koffi/src/util.cc +13 -3
- package/src/koffi/src/util.hh +1 -1
- package/doc/polymorphism.md +0 -108
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
## Version history
|
|
4
4
|
|
|
5
|
+
### Koffi 2.8
|
|
6
|
+
|
|
7
|
+
#### Koffi 2.8.1 (2024-04-04)
|
|
8
|
+
|
|
9
|
+
- Fix incompatibility with Node 20.12+ and 21.6+
|
|
10
|
+
|
|
11
|
+
#### Koffi 2.8.0 (2024-02-12)
|
|
12
|
+
|
|
13
|
+
- Support pushing pointers for string arguments
|
|
14
|
+
- Add `koffi.alloc()` for [stable pointers](output.md#stable-pointers)
|
|
15
|
+
|
|
5
16
|
### Koffi 2.7
|
|
6
17
|
|
|
7
18
|
#### Koffi 2.7.4 (2024-02-04)
|
|
@@ -130,7 +141,7 @@ Pre-built binaries don't work correctly in Koffi 2.5.13 to 2.5.15, skip those ve
|
|
|
130
141
|
|
|
131
142
|
#### Koffi 2.5.11 (2023-08-03)
|
|
132
143
|
|
|
133
|
-
- Support casting function pointers with [koffi.as()](
|
|
144
|
+
- Support casting function pointers with [koffi.as()](pointers.md#handling-void-pointers)
|
|
134
145
|
- Build in C++20 mode
|
|
135
146
|
|
|
136
147
|
#### Koffi 2.5.10 (2023-08-01)
|
|
@@ -338,7 +349,7 @@ Pre-built binaries don't work correctly in Koffi 2.5.13 to 2.5.15, skip those ve
|
|
|
338
349
|
**Main changes:**
|
|
339
350
|
|
|
340
351
|
- Allow buffers (TypedArray or ArrayBuffer) values for input and/or output pointer arguments (for polymorphic arguments)
|
|
341
|
-
- Support opaque buffers (TypedArray or ArrayBuffer) values in `koffi.decode()` to [decode output buffers](
|
|
352
|
+
- Support opaque buffers (TypedArray or ArrayBuffer) values in `koffi.decode()` to [decode output buffers](output.md#output-buffers)
|
|
342
353
|
- Decode non-string types as arrays when an [explicit length is passed to koffi.decode()](callbacks.md#decoding-pointer-arguments)
|
|
343
354
|
|
|
344
355
|
**Other changes:**
|
|
@@ -431,7 +442,7 @@ Pre-built binaries don't work correctly in Koffi 2.5.13 to 2.5.15, skip those ve
|
|
|
431
442
|
|
|
432
443
|
**Main changes:**
|
|
433
444
|
|
|
434
|
-
- Add [koffi.as()](
|
|
445
|
+
- Add [koffi.as()](pointers.md#handling-void-pointers) to support polymorphic APIs based on `void *` parameters
|
|
435
446
|
- Add [endian-sensitive integer types](input.md#endian-sensitive-integers): `intX_le_t`, `intX_be_t`, `uintX_le_t`, `uintX_be_t`
|
|
436
447
|
- Accept typed arrays for `void *` parameters
|
|
437
448
|
- Introduce `koffi.opaque()` to replace `koffi.handle()` (which remains supported until Koffi 3.0)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/doc/functions.md
CHANGED
|
@@ -251,4 +251,3 @@ Among other thing, in the the following pages you will learn more about:
|
|
|
251
251
|
- How Koffi translates [input parameters](input.md) to C
|
|
252
252
|
- How you can [define and use pointers](pointers.md)
|
|
253
253
|
- How to deal with [output parameters](output.md)
|
|
254
|
-
- How to handle [polymorphic API](polymorphism.md)
|
package/doc/index.rst
CHANGED
package/doc/input.md
CHANGED
|
@@ -393,7 +393,7 @@ The reverse case is also true, Koffi can convert a C fixed-size buffer to a JS s
|
|
|
393
393
|
|
|
394
394
|
### Dynamic arrays (pointers)
|
|
395
395
|
|
|
396
|
-
In C, dynamically-sized arrays are usually passed around as pointers. Read more about [array pointers](pointers.md#
|
|
396
|
+
In C, dynamically-sized arrays are usually passed around as pointers. Read more about [array pointers](pointers.md#dynamic-arrays) in the relevant section.
|
|
397
397
|
|
|
398
398
|
## Union types
|
|
399
399
|
|
package/doc/output.md
CHANGED
|
@@ -30,7 +30,7 @@ const koffi = require('koffi');
|
|
|
30
30
|
const user32 = koffi.load('user32.dll');
|
|
31
31
|
|
|
32
32
|
const DWORD = koffi.alias('DWORD', 'uint32_t');
|
|
33
|
-
const HANDLE = koffi.pointer(koffi.opaque(
|
|
33
|
+
const HANDLE = koffi.pointer('HANDLE', koffi.opaque());
|
|
34
34
|
const HWND = koffi.alias('HWND', HANDLE);
|
|
35
35
|
|
|
36
36
|
const FindWindowEx = user32.func('HWND __stdcall FindWindowExW(HWND hWndParent, HWND hWndChildAfter, const char16_t *lpszClass, const char16_t *lpszWindow)');
|
|
@@ -167,3 +167,111 @@ ConcatToBuffer(str1, str2, out);
|
|
|
167
167
|
|
|
168
168
|
console.log(out[0]);
|
|
169
169
|
```
|
|
170
|
+
|
|
171
|
+
## Output buffers
|
|
172
|
+
|
|
173
|
+
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](#transient-pointers) below for an example.
|
|
174
|
+
|
|
175
|
+
```{warning}
|
|
176
|
+
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.
|
|
177
|
+
|
|
178
|
+
If you need to provide a pointer that will be kept around, allocate memory with [koffi.alloc()](#stable-pointers) instead.
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Transient pointers
|
|
182
|
+
|
|
183
|
+
*New in Koffi 2.3*
|
|
184
|
+
|
|
185
|
+
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.
|
|
186
|
+
|
|
187
|
+
Once the native function returns, you can decode the content with `koffi.decode(value, type)` as in the following example:
|
|
188
|
+
|
|
189
|
+
```js
|
|
190
|
+
// ES6 syntax: import koffi from 'koffi';
|
|
191
|
+
const koffi = require('koffi');
|
|
192
|
+
|
|
193
|
+
const lib = koffi.load('libc.so.6');
|
|
194
|
+
|
|
195
|
+
const Vec3 = koffi.struct('Vec3', {
|
|
196
|
+
x: 'float32',
|
|
197
|
+
y: 'float32',
|
|
198
|
+
z: 'float32'
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
const memcpy = lib.func('void *memcpy(_Out_ void *dest, const void *src, size_t size)');
|
|
202
|
+
|
|
203
|
+
let vec1 = { x: 1, y: 2, z: 3 };
|
|
204
|
+
let vec2 = null;
|
|
205
|
+
|
|
206
|
+
// Copy the vector in a convoluted way through memcpy
|
|
207
|
+
{
|
|
208
|
+
let src = koffi.as(vec1, 'Vec3 *');
|
|
209
|
+
let dest = Buffer.allocUnsafe(koffi.sizeof(Vec3));
|
|
210
|
+
|
|
211
|
+
memcpy(dest, src, koffi.sizeof(Vec3));
|
|
212
|
+
|
|
213
|
+
vec2 = koffi.decode(dest, Vec3);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// CHange vector1, leaving copy alone
|
|
217
|
+
[vec1.x, vec1.y, vec1.z] = [vec1.z, vec1.y, vec1.x];
|
|
218
|
+
|
|
219
|
+
console.log(vec1); // { x: 3, y: 2, z: 1 }
|
|
220
|
+
console.log(vec2); // { x: 1, y: 2, z: 3 }
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
See [decoding variables](variables.md#decode-to-js-values) for more information about the decode function.
|
|
224
|
+
|
|
225
|
+
### Stable pointers
|
|
226
|
+
|
|
227
|
+
*New in Koffi 2.8*
|
|
228
|
+
|
|
229
|
+
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.
|
|
230
|
+
|
|
231
|
+
In this case, it is **not safe to use buffers or typed arrays**!
|
|
232
|
+
|
|
233
|
+
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()](variables.md#decode-to-js-values) to read data from the pointer when needed.
|
|
234
|
+
|
|
235
|
+
The example below sets up some memory to be used as an output buffer where a concatenation function appends a string on each call.
|
|
236
|
+
|
|
237
|
+
```c
|
|
238
|
+
#include <assert.h>
|
|
239
|
+
#include <stddef.h>
|
|
240
|
+
|
|
241
|
+
static char *buf_ptr;
|
|
242
|
+
static size_t buf_len;
|
|
243
|
+
static size_t buf_size;
|
|
244
|
+
|
|
245
|
+
void reset_buffer(char *buf, size_t size)
|
|
246
|
+
{
|
|
247
|
+
assert(size > 1);
|
|
248
|
+
|
|
249
|
+
buf_ptr = buf;
|
|
250
|
+
buf_len = 0;
|
|
251
|
+
buf_size = size - 1; // Keep space for trailing NUL
|
|
252
|
+
|
|
253
|
+
buf_ptr[0] = 0;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
void append_str(const char *str)
|
|
257
|
+
{
|
|
258
|
+
for (size_t i = 0; str[i] && buf_len < buf_size; i++, buf_len++) {
|
|
259
|
+
buf_ptr[buf_len] = str[i];
|
|
260
|
+
}
|
|
261
|
+
buf_ptr[buf_len] = 0;
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
```js
|
|
266
|
+
const reset_buffer = lib.func('void reset_buffer(char *buf, size_t size)');
|
|
267
|
+
const append_str = lib.func('void append_str(const char *str)');
|
|
268
|
+
|
|
269
|
+
let output = koffi.alloc('char', 64);
|
|
270
|
+
reset_buffer(output, 64);
|
|
271
|
+
|
|
272
|
+
append_str('Hello');
|
|
273
|
+
console.log(koffi.decode(output, 'char', -1)); // Prints Hello
|
|
274
|
+
|
|
275
|
+
append_str(' World!');
|
|
276
|
+
console.log(koffi.decode(output, 'char', -1)); // Prints Hello World!
|
|
277
|
+
```
|
package/doc/pointers.md
CHANGED
|
@@ -88,7 +88,7 @@ AddInt(sum, 6);
|
|
|
88
88
|
console.log(sum[0]); // Prints 42
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
-
###
|
|
91
|
+
### Dynamic arrays
|
|
92
92
|
|
|
93
93
|
In C, dynamically-sized arrays are usually passed around as pointers. The length is either passed as an additional argument, or inferred from the array content itself, for example with a terminating sentinel value (such as a NULL pointers in the case of an array of strings).
|
|
94
94
|
|
|
@@ -132,6 +132,69 @@ console.log(total); // Prints 14
|
|
|
132
132
|
|
|
133
133
|
By default, just like for objects, array arguments are copied from JS to C but not vice-versa. You can however change the direction as documented in the section on [output parameters](output.md).
|
|
134
134
|
|
|
135
|
+
## Handling void pointers
|
|
136
|
+
|
|
137
|
+
*New in Koffi 2.1*
|
|
138
|
+
|
|
139
|
+
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.
|
|
140
|
+
|
|
141
|
+
Koffi provides two features to deal with this:
|
|
142
|
+
|
|
143
|
+
- You can use `koffi.as(value, type)` to tell Koffi what kind of type is actually expected, as shown in the example below.
|
|
144
|
+
- Buffers and typed JS arrays can be used as values in place everywhere a pointer is expected. See [dynamic arrays](#dynamic-arrays) for more information, for input or output.
|
|
145
|
+
|
|
146
|
+
The example below shows the use of `koffi.as()` to read the header of a PNG file with `fread()` directly to a JS object.
|
|
147
|
+
|
|
148
|
+
```js
|
|
149
|
+
// ES6 syntax: import koffi from 'koffi';
|
|
150
|
+
const koffi = require('koffi');
|
|
151
|
+
|
|
152
|
+
const lib = koffi.load('libc.so.6');
|
|
153
|
+
|
|
154
|
+
const FILE = koffi.opaque('FILE');
|
|
155
|
+
|
|
156
|
+
const PngHeader = koffi.pack('PngHeader', {
|
|
157
|
+
signature: koffi.array('uint8_t', 8),
|
|
158
|
+
ihdr: koffi.pack({
|
|
159
|
+
length: 'uint32_be_t',
|
|
160
|
+
chunk: koffi.array('char', 4),
|
|
161
|
+
width: 'uint32_be_t',
|
|
162
|
+
height: 'uint32_be_t',
|
|
163
|
+
depth: 'uint8_t',
|
|
164
|
+
color: 'uint8_t',
|
|
165
|
+
compression: 'uint8_t',
|
|
166
|
+
filter: 'uint8_t',
|
|
167
|
+
interlace: 'uint8_t',
|
|
168
|
+
crc: 'uint32_be_t'
|
|
169
|
+
})
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const fopen = lib.func('FILE *fopen(const char *path, const char *mode)');
|
|
173
|
+
const fclose = lib.func('int fclose(FILE *fp)');
|
|
174
|
+
const fread = lib.func('size_t fread(_Out_ void *ptr, size_t size, size_t nmemb, FILE *fp)');
|
|
175
|
+
|
|
176
|
+
let filename = process.argv[2];
|
|
177
|
+
if (filename == null)
|
|
178
|
+
throw new Error('Usage: node png.js <image.png>');
|
|
179
|
+
|
|
180
|
+
let hdr = {};
|
|
181
|
+
{
|
|
182
|
+
let fp = fopen(filename, 'rb');
|
|
183
|
+
if (!fp)
|
|
184
|
+
throw new Error(`Failed to open '${filename}'`);
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
let len = fread(koffi.as(hdr, 'PngHeader *'), 1, koffi.sizeof(PngHeader), fp);
|
|
188
|
+
if (len < koffi.sizeof(PngHeader))
|
|
189
|
+
throw new Error('Failed to read PNG header');
|
|
190
|
+
} finally {
|
|
191
|
+
fclose(fp);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
console.log('PNG header:', hdr);
|
|
196
|
+
```
|
|
197
|
+
|
|
135
198
|
## Disposable types
|
|
136
199
|
|
|
137
200
|
*New in Koffi 2.0*
|
package/index.js
CHANGED
|
@@ -43,7 +43,7 @@ var require_tools = __commonJS({
|
|
|
43
43
|
try {
|
|
44
44
|
fs2.renameSync(file.path, dest);
|
|
45
45
|
} catch (err) {
|
|
46
|
-
if (
|
|
46
|
+
if (!fs2.existsSync(dest))
|
|
47
47
|
reject(err);
|
|
48
48
|
}
|
|
49
49
|
resolve();
|
|
@@ -378,8 +378,8 @@ var require_package = __commonJS({
|
|
|
378
378
|
"build/dist/src/koffi/package.json"(exports2, module2) {
|
|
379
379
|
module2.exports = {
|
|
380
380
|
name: "koffi",
|
|
381
|
-
version: "2.
|
|
382
|
-
stable: "2.
|
|
381
|
+
version: "2.8.1",
|
|
382
|
+
stable: "2.8.1",
|
|
383
383
|
description: "Fast and simple C FFI (foreign function interface) for Node.js",
|
|
384
384
|
keywords: [
|
|
385
385
|
"foreign",
|
package/indirect.js
CHANGED
|
@@ -43,7 +43,7 @@ var require_tools = __commonJS({
|
|
|
43
43
|
try {
|
|
44
44
|
fs2.renameSync(file.path, dest);
|
|
45
45
|
} catch (err) {
|
|
46
|
-
if (
|
|
46
|
+
if (!fs2.existsSync(dest))
|
|
47
47
|
reject(err);
|
|
48
48
|
}
|
|
49
49
|
resolve();
|
|
@@ -378,8 +378,8 @@ var require_package = __commonJS({
|
|
|
378
378
|
"build/dist/src/koffi/package.json"(exports2, module2) {
|
|
379
379
|
module2.exports = {
|
|
380
380
|
name: "koffi",
|
|
381
|
-
version: "2.
|
|
382
|
-
stable: "2.
|
|
381
|
+
version: "2.8.1",
|
|
382
|
+
stable: "2.8.1",
|
|
383
383
|
description: "Fast and simple C FFI (foreign function interface) for Node.js",
|
|
384
384
|
keywords: [
|
|
385
385
|
"foreign",
|
package/package.json
CHANGED
package/src/cnoke/src/tools.js
CHANGED
package/src/core/libcc/libcc.hh
CHANGED
|
@@ -3142,10 +3142,10 @@ static inline void Log(LogLevel level, const char *ctx, const char *fmt, Args...
|
|
|
3142
3142
|
#ifdef RG_DEBUG
|
|
3143
3143
|
const char *DebugLogContext(const char *filename, int line);
|
|
3144
3144
|
|
|
3145
|
-
#define LogDebug(...) Log(LogLevel::Debug, DebugLogContext(__FILE__, __LINE__),
|
|
3146
|
-
#define LogInfo(...) Log(LogLevel::Info, nullptr, __VA_ARGS__)
|
|
3147
|
-
#define LogWarning(...) Log(LogLevel::Warning, DebugLogContext(__FILE__, __LINE__), __VA_ARGS__)
|
|
3148
|
-
#define LogError(...) Log(LogLevel::Error, DebugLogContext(__FILE__, __LINE__), __VA_ARGS__)
|
|
3145
|
+
#define LogDebug(...) Log(LogLevel::Debug, DebugLogContext(__FILE__, __LINE__) __VA_OPT__(,) __VA_ARGS__)
|
|
3146
|
+
#define LogInfo(...) Log(LogLevel::Info, nullptr __VA_OPT__(,) __VA_ARGS__)
|
|
3147
|
+
#define LogWarning(...) Log(LogLevel::Warning, DebugLogContext(__FILE__, __LINE__) __VA_OPT__(,) __VA_ARGS__)
|
|
3148
|
+
#define LogError(...) Log(LogLevel::Error, DebugLogContext(__FILE__, __LINE__) __VA_OPT__(,) __VA_ARGS__)
|
|
3149
3149
|
#else
|
|
3150
3150
|
template <typename... Args>
|
|
3151
3151
|
static inline void LogDebug(Args...) {}
|
package/src/koffi/CMakeLists.txt
CHANGED
|
@@ -30,7 +30,7 @@ include(CheckCXXCompilerFlag)
|
|
|
30
30
|
set(CMAKE_CXX_STANDARD 20)
|
|
31
31
|
if(MSVC)
|
|
32
32
|
set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded)
|
|
33
|
-
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zc:__cplusplus /W4 /wd4200 /wd4201 /wd4127 /wd4458 /wd4706 /wd4702 /wd4324")
|
|
33
|
+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zc:__cplusplus /Zc:preprocessor /W4 /wd4200 /wd4201 /wd4127 /wd4458 /wd4706 /wd4702 /wd4324")
|
|
34
34
|
|
|
35
35
|
# ASM_MASM does not (yet) work on Windows ARM64
|
|
36
36
|
if(NOT CMAKE_GENERATOR_PLATFORM MATCHES "ARM64")
|
package/src/koffi/src/call.cc
CHANGED
|
@@ -209,8 +209,7 @@ bool CallData::PushString(Napi::Value value, int directions, const char **out_st
|
|
|
209
209
|
|
|
210
210
|
return true;
|
|
211
211
|
} else {
|
|
212
|
-
|
|
213
|
-
return false;
|
|
212
|
+
return PushPointer(value, instance->str_type, directions, (void **)out_str);
|
|
214
213
|
}
|
|
215
214
|
}
|
|
216
215
|
|
|
@@ -311,8 +310,7 @@ bool CallData::PushString16(Napi::Value value, int directions, const char16_t **
|
|
|
311
310
|
|
|
312
311
|
return true;
|
|
313
312
|
} else {
|
|
314
|
-
|
|
315
|
-
return false;
|
|
313
|
+
return PushPointer(value, instance->str16_type, directions, (void **)out_str16);
|
|
316
314
|
}
|
|
317
315
|
}
|
|
318
316
|
|
|
@@ -983,7 +981,9 @@ bool CallData::PushPointer(Napi::Value value, const TypeInfo *type, int directio
|
|
|
983
981
|
} break;
|
|
984
982
|
|
|
985
983
|
case napi_external: {
|
|
986
|
-
RG_ASSERT(type->primitive == PrimitiveKind::Pointer
|
|
984
|
+
RG_ASSERT(type->primitive == PrimitiveKind::Pointer ||
|
|
985
|
+
type->primitive == PrimitiveKind::String ||
|
|
986
|
+
type->primitive == PrimitiveKind::String16);
|
|
987
987
|
|
|
988
988
|
if (!CheckValueTag(instance, value, type->ref.marker) &&
|
|
989
989
|
!CheckValueTag(instance, value, instance->void_type) &&
|
package/src/koffi/src/ffi.cc
CHANGED
|
@@ -770,6 +770,55 @@ static inline bool GetExternalPointer(Napi::Env env, Napi::Value value, void **o
|
|
|
770
770
|
}
|
|
771
771
|
}
|
|
772
772
|
|
|
773
|
+
static Napi::Value CallAlloc(const Napi::CallbackInfo &info)
|
|
774
|
+
{
|
|
775
|
+
Napi::Env env = info.Env();
|
|
776
|
+
InstanceData *instance = env.GetInstanceData<InstanceData>();
|
|
777
|
+
|
|
778
|
+
if (info.Length() < 2) {
|
|
779
|
+
ThrowError<Napi::TypeError>(env, "Expected 2 arguments, got %1", info.Length());
|
|
780
|
+
return env.Null();
|
|
781
|
+
}
|
|
782
|
+
if (!info[1].IsNumber()) {
|
|
783
|
+
ThrowError<Napi::TypeError>(env, "Unexpected %1 value for length, expected number", GetValueType(instance, info[1]));
|
|
784
|
+
return env.Null();
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
const TypeInfo *type = ResolveType(info[0]);
|
|
788
|
+
if (!type)
|
|
789
|
+
return env.Null();
|
|
790
|
+
|
|
791
|
+
if (!type->size) [[unlikely]] {
|
|
792
|
+
ThrowError<Napi::TypeError>(env, "Cannot allocate memory for zero-sized type %1", type->name);
|
|
793
|
+
return env.Null();
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
int32_t len = info[1].As<Napi::Number>();
|
|
797
|
+
|
|
798
|
+
if (len <= 0) [[unlikely]] {
|
|
799
|
+
ThrowError<Napi::Error>(env, "Size must be greater than 0");
|
|
800
|
+
return env.Null();
|
|
801
|
+
}
|
|
802
|
+
if (len > INT32_MAX / type->size) [[unlikely]] {
|
|
803
|
+
ThrowError<Napi::Error>(env, "Cannot allocate more than %1 objects of type %2", INT32_MAX / type->size, type->name);
|
|
804
|
+
return env.Null();
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
void *ptr = calloc((size_t)len, (size_t)type->size);
|
|
808
|
+
|
|
809
|
+
if (!ptr) [[unlikely]] {
|
|
810
|
+
Size size = (Size)(len * type->size);
|
|
811
|
+
|
|
812
|
+
ThrowError<Napi::Error>(env, "Failed to allocate %1 of memory", FmtMemSize((Size)size));
|
|
813
|
+
return env.Null();
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
Napi::External<void> external = Napi::External<void>::New(env, ptr);
|
|
817
|
+
SetValueTag(instance, external, type);
|
|
818
|
+
|
|
819
|
+
return external;
|
|
820
|
+
}
|
|
821
|
+
|
|
773
822
|
static Napi::Value CallFree(const Napi::CallbackInfo &info)
|
|
774
823
|
{
|
|
775
824
|
Napi::Env env = info.Env();
|
|
@@ -2241,6 +2290,7 @@ static Napi::Object InitModule(Napi::Env env, Napi::Object exports)
|
|
|
2241
2290
|
exports.Set("inout", Napi::Function::New(env, MarkInOut, "inout"));
|
|
2242
2291
|
|
|
2243
2292
|
exports.Set("disposable", Napi::Function::New(env, CreateDisposableType, "disposable"));
|
|
2293
|
+
exports.Set("alloc", Napi::Function::New(env, CallAlloc, "alloc"));
|
|
2244
2294
|
exports.Set("free", Napi::Function::New(env, CallFree, "free"));
|
|
2245
2295
|
|
|
2246
2296
|
exports.Set("register", Napi::Function::New(env, RegisterCallback, "register"));
|
|
@@ -2327,6 +2377,8 @@ static Napi::Object InitModule(Napi::Env env, Napi::Object exports)
|
|
|
2327
2377
|
instance->void_type = instance->types_map.FindValue("void", nullptr);
|
|
2328
2378
|
instance->char_type = instance->types_map.FindValue("char", nullptr);
|
|
2329
2379
|
instance->char16_type = instance->types_map.FindValue("char16", nullptr);
|
|
2380
|
+
instance->str_type = instance->types_map.FindValue("char *", nullptr);
|
|
2381
|
+
instance->str16_type = instance->types_map.FindValue("char16_t *", nullptr);
|
|
2330
2382
|
|
|
2331
2383
|
instance->active_symbol = Napi::Symbol::New(env, "active");
|
|
2332
2384
|
|
package/src/koffi/src/ffi.hh
CHANGED
|
@@ -272,11 +272,16 @@ struct InstanceData {
|
|
|
272
272
|
Size base_types_len;
|
|
273
273
|
|
|
274
274
|
bool debug;
|
|
275
|
+
|
|
275
276
|
uint64_t tag_lower;
|
|
277
|
+
BucketArray<napi_type_tag> tags;
|
|
278
|
+
HashMap<const void *, napi_type_tag *> tags_map;
|
|
276
279
|
|
|
277
280
|
const TypeInfo *void_type;
|
|
278
281
|
const TypeInfo *char_type;
|
|
279
282
|
const TypeInfo *char16_type;
|
|
283
|
+
const TypeInfo *str_type;
|
|
284
|
+
const TypeInfo *str16_type;
|
|
280
285
|
|
|
281
286
|
Napi::Symbol active_symbol;
|
|
282
287
|
|
package/src/koffi/src/util.cc
CHANGED
|
@@ -579,12 +579,22 @@ const char *GetValueType(const InstanceData *instance, Napi::Value value)
|
|
|
579
579
|
return "Unknown";
|
|
580
580
|
}
|
|
581
581
|
|
|
582
|
-
void SetValueTag(
|
|
582
|
+
void SetValueTag(InstanceData *instance, Napi::Value value, const void *marker)
|
|
583
583
|
{
|
|
584
584
|
RG_ASSERT(marker);
|
|
585
585
|
|
|
586
|
-
napi_type_tag tag =
|
|
587
|
-
|
|
586
|
+
napi_type_tag *tag = instance->tags_map.FindValue(marker, nullptr);
|
|
587
|
+
|
|
588
|
+
if (!tag) {
|
|
589
|
+
tag = instance->tags.AppendDefault();
|
|
590
|
+
|
|
591
|
+
tag->lower = instance->tag_lower;
|
|
592
|
+
tag->upper = (uint64_t)marker;
|
|
593
|
+
|
|
594
|
+
instance->tags_map.Set(marker, tag);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
napi_status status = napi_type_tag_object(value.Env(), value, tag);
|
|
588
598
|
RG_ASSERT(status == napi_ok);
|
|
589
599
|
}
|
|
590
600
|
|
package/src/koffi/src/util.hh
CHANGED
|
@@ -102,7 +102,7 @@ bool CanStoreType(const TypeInfo *type);
|
|
|
102
102
|
// Can be slow, only use for error messages
|
|
103
103
|
const char *GetValueType(const InstanceData *instance, Napi::Value value);
|
|
104
104
|
|
|
105
|
-
void SetValueTag(
|
|
105
|
+
void SetValueTag(InstanceData *instance, Napi::Value value, const void *marker);
|
|
106
106
|
bool CheckValueTag(const InstanceData *instance, Napi::Value value, const void *marker);
|
|
107
107
|
|
|
108
108
|
static inline bool IsNullOrUndefined(Napi::Value value)
|
package/doc/polymorphism.md
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
# Polymorphic arguments
|
|
2
|
-
|
|
3
|
-
## Input polymorphism
|
|
4
|
-
|
|
5
|
-
*New in Koffi 2.1*
|
|
6
|
-
|
|
7
|
-
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.
|
|
8
|
-
|
|
9
|
-
Koffi provides two features to deal with this:
|
|
10
|
-
|
|
11
|
-
- Buffers and typed JS arrays can be used as values in place everywhere a pointer is expected. See [dynamic arrays](pointers.md#array-pointers-dynamic-arrays) for more information, for input or output.
|
|
12
|
-
- You can use `koffi.as(value, type)` to tell Koffi what kind of type is actually expected.
|
|
13
|
-
|
|
14
|
-
The example below shows the use of `koffi.as()` to read the header of a PNG file with `fread()`.
|
|
15
|
-
|
|
16
|
-
```js
|
|
17
|
-
// ES6 syntax: import koffi from 'koffi';
|
|
18
|
-
const koffi = require('koffi');
|
|
19
|
-
|
|
20
|
-
const lib = koffi.load('libc.so.6');
|
|
21
|
-
|
|
22
|
-
const FILE = koffi.opaque('FILE');
|
|
23
|
-
|
|
24
|
-
const PngHeader = koffi.pack('PngHeader', {
|
|
25
|
-
signature: koffi.array('uint8_t', 8),
|
|
26
|
-
ihdr: koffi.pack({
|
|
27
|
-
length: 'uint32_be_t',
|
|
28
|
-
chunk: koffi.array('char', 4),
|
|
29
|
-
width: 'uint32_be_t',
|
|
30
|
-
height: 'uint32_be_t',
|
|
31
|
-
depth: 'uint8_t',
|
|
32
|
-
color: 'uint8_t',
|
|
33
|
-
compression: 'uint8_t',
|
|
34
|
-
filter: 'uint8_t',
|
|
35
|
-
interlace: 'uint8_t',
|
|
36
|
-
crc: 'uint32_be_t'
|
|
37
|
-
})
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
const fopen = lib.func('FILE *fopen(const char *path, const char *mode)');
|
|
41
|
-
const fclose = lib.func('int fclose(FILE *fp)');
|
|
42
|
-
const fread = lib.func('size_t fread(_Out_ void *ptr, size_t size, size_t nmemb, FILE *fp)');
|
|
43
|
-
|
|
44
|
-
let filename = process.argv[2];
|
|
45
|
-
if (filename == null)
|
|
46
|
-
throw new Error('Usage: node png.js <image.png>');
|
|
47
|
-
|
|
48
|
-
let hdr = {};
|
|
49
|
-
{
|
|
50
|
-
let fp = fopen(filename, 'rb');
|
|
51
|
-
if (!fp)
|
|
52
|
-
throw new Error(`Failed to open '${filename}'`);
|
|
53
|
-
|
|
54
|
-
try {
|
|
55
|
-
let len = fread(koffi.as(hdr, 'PngHeader *'), 1, koffi.sizeof(PngHeader), fp);
|
|
56
|
-
if (len < koffi.sizeof(PngHeader))
|
|
57
|
-
throw new Error('Failed to read PNG header');
|
|
58
|
-
} finally {
|
|
59
|
-
fclose(fp);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
console.log('PNG header:', hdr);
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
## Output buffers
|
|
67
|
-
|
|
68
|
-
*New in Koffi 2.3*
|
|
69
|
-
|
|
70
|
-
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.
|
|
71
|
-
|
|
72
|
-
Once the native function returns, you can decode the content with `koffi.decode(value, type)` as in the following example:
|
|
73
|
-
|
|
74
|
-
```js
|
|
75
|
-
// ES6 syntax: import koffi from 'koffi';
|
|
76
|
-
const koffi = require('koffi');
|
|
77
|
-
|
|
78
|
-
const lib = koffi.load('libc.so.6');
|
|
79
|
-
|
|
80
|
-
const Vec3 = koffi.struct('Vec3', {
|
|
81
|
-
x: 'float32',
|
|
82
|
-
y: 'float32',
|
|
83
|
-
z: 'float32'
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
const memcpy = lib.func('void *memcpy(_Out_ void *dest, const void *src, size_t size)');
|
|
87
|
-
|
|
88
|
-
let vec1 = { x: 1, y: 2, z: 3 };
|
|
89
|
-
let vec2 = null;
|
|
90
|
-
|
|
91
|
-
// Copy the vector in a convoluted way through memcpy
|
|
92
|
-
{
|
|
93
|
-
let src = koffi.as(vec1, 'Vec3 *');
|
|
94
|
-
let dest = Buffer.allocUnsafe(koffi.sizeof(Vec3));
|
|
95
|
-
|
|
96
|
-
memcpy(dest, src, koffi.sizeof(Vec3));
|
|
97
|
-
|
|
98
|
-
vec2 = koffi.decode(dest, Vec3);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// CHange vector1, leaving copy alone
|
|
102
|
-
[vec1.x, vec1.y, vec1.z] = [vec1.z, vec1.y, vec1.x];
|
|
103
|
-
|
|
104
|
-
console.log(vec1); // { x: 3, y: 2, z: 1 }
|
|
105
|
-
console.log(vec2); // { x: 1, y: 2, z: 3 }
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
See [decoding variables](variables.md#decode-to-js-values) for more information about the decode function.
|