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 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()](polymorphism.md#input-polymorphism)
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](polymorphism.md#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()](polymorphism.md#input-polymorphism) to support polymorphic APIs based on `void *` parameters
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
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
@@ -29,7 +29,6 @@ Table of contents
29
29
  input
30
30
  pointers
31
31
  output
32
- polymorphism
33
32
  unions
34
33
  variables
35
34
  callbacks
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#array-pointers-dynamic-arrays) in the relevant section.
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('HANDLE'));
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
- ### Array pointers (dynamic arrays)
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 (err.code != "EBUSY")
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.7.4",
382
- stable: "2.7.4",
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 (err.code != "EBUSY")
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.7.4",
382
- stable: "2.7.4",
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koffi",
3
- "version": "2.7.4",
4
- "stable": "2.7.4",
3
+ "version": "2.8.1",
4
+ "stable": "2.8.1",
5
5
  "description": "Fast and simple C FFI (foreign function interface) for Node.js",
6
6
  "keywords": [
7
7
  "foreign",
@@ -66,7 +66,7 @@ async function download_http(url, dest) {
66
66
  try {
67
67
  fs.renameSync(file.path, dest);
68
68
  } catch (err) {
69
- if (err.code != 'EBUSY')
69
+ if (!fs.existsSync(dest))
70
70
  reject(err);
71
71
  }
72
72
 
@@ -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__), __VA_ARGS__)
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...) {}
@@ -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")
@@ -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
- ThrowError<Napi::TypeError>(env, "Unexpected %1 value, expected string", GetValueType(instance, value));
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
- ThrowError<Napi::TypeError>(env, "Unexpected %1 value, expected string", GetValueType(instance, value));
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) &&
@@ -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
 
@@ -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
 
@@ -579,12 +579,22 @@ const char *GetValueType(const InstanceData *instance, Napi::Value value)
579
579
  return "Unknown";
580
580
  }
581
581
 
582
- void SetValueTag(const InstanceData *instance, Napi::Value value, const void *marker)
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 = { instance->tag_lower, (uint64_t)marker };
587
- napi_status status = napi_type_tag_object(value.Env(), value, &tag);
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
 
@@ -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(const InstanceData *instance, Napi::Value value, const void *marker);
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)
@@ -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.