koffi 2.14.0-beta.3 → 2.14.0

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
@@ -5,6 +5,17 @@
5
5
 
6
6
  ## Koffi 2
7
7
 
8
+ ### Koffi 2.14
9
+
10
+ #### Koffi 2.14.0
11
+
12
+ *Released on 2025-08-17*
13
+
14
+ - Improve support for structs with [flexible array member](input#flexible-arrays)
15
+ - Automatically encode/decode dynamic arrays pointers when length is known though struct member
16
+ - Fix parser crash when direction qualifier is followed by unknown type
17
+ - Add missing TypeScript types and arguments
18
+
8
19
  ### Koffi 2.13
9
20
 
10
21
  #### Koffi 2.13.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
@@ -396,6 +396,72 @@ Koffi can also convert JS strings to fixed-sized arrays in the following cases:
396
396
 
397
397
  The reverse case is also true, Koffi can convert a C fixed-size buffer to a JS string. This happens by default for char, char16_t and char32_t arrays, but you can also explicitly ask for this with the `String` array hint (e.g. `koffi.array('char', 8, 'String')`).
398
398
 
399
+ ## Flexible arrays
400
+
401
+ *Added in Koffi 2.14.0*
402
+
403
+ C structs ending with a flexible array member are often used for variable-sized structs, and are generally paired with dynamic memory allocation. In many cases, the number of elements is described by another struct member.
404
+
405
+ Use `koffi.array(type, countedBy, maxLen)` to make a flexible array type, for which the array length is determined by the struct member indicated by the `countedBy` parameter. Flexible array types can only be used as the last member of a struct, as shown below:
406
+
407
+ ```js
408
+ const FlexibleArray = koffi.struct('FlexibleArray', {
409
+ count: 'size_t',
410
+ numbers: koffi.array('int', 'count', 128)
411
+ });
412
+ ````
413
+
414
+ For various reasons, Koffi requires you to specify an upper bound (`maxLen`) for the number of elements in the flexible array member.
415
+
416
+ > [!WARNING]
417
+ > Also, unlike C flexible arrays, the struct size will expand to accomodate the maximum size of the flexible array.
418
+
419
+ The following example illustrates how to use a flexible array API in C from Koffi.
420
+
421
+ ```c
422
+ // Build with: clang -fPIC -o flexible.so -shared flexible.c -Wall -O2
423
+
424
+ #include <stddef.h>
425
+
426
+ struct FlexibleArray {
427
+ size_t count;
428
+ int numbers[];
429
+ };
430
+
431
+ void AppendValues(struct FlexibleArray *arr, size_t count, int start, int step)
432
+ {
433
+ for (size_t i = 0; i < count; i++) {
434
+ arr->numbers[arr->count + i] = start + i * step;
435
+ }
436
+ arr->count += count;
437
+ }
438
+ ```
439
+
440
+ ```js
441
+ // ES6 syntax: import koffi from 'koffi';
442
+ const koffi = require('koffi');
443
+
444
+ const lib = koffi.load('./flexible.so');
445
+
446
+ const FlexibleArray = koffi.struct('FlexibleArray', {
447
+ count: 'size_t',
448
+ numbers: koffi.array('int', 'count', 256, 'Array')
449
+ });
450
+
451
+ const AppendValues = lib.func('void AppendValues(_Inout_ FlexibleArray *arr, int count, int start, int step)');
452
+
453
+ let array = { count: 0, numbers: [] };
454
+
455
+ AppendValues(array, 5, 1, 1);
456
+ console.log(array); // Prints { count: 5, numbers: [1, 2, 3, 4, 5] }
457
+
458
+ AppendValues(array, 3, 10, 2);
459
+ console.log(array); // Prints { count: 8, numbers: [1, 2, 3, 4, 5, 10, 12, 14] }
460
+ ```
461
+
462
+ > [!NOTE]
463
+ > This is frequently used in the Win32 API, an exemple of this is [AllocateAndInitializeSid()](https://learn.microsoft.com/windows/win32/api/securitybaseapi/nf-securitybaseapi-allocateandinitializesid).
464
+
399
465
  ## Dynamic arrays (pointers)
400
466
 
401
467
  In C, dynamically-sized arrays are usually passed around as pointers. Read more about [array pointers](pointers#dynamic-arrays) in the relevant section.
package/index.d.ts CHANGED
@@ -105,7 +105,6 @@ export class Union {
105
105
  }
106
106
 
107
107
  export function array(ref: TypeSpec, len: number, hint?: ArrayHint | null): IKoffiCType;
108
- export function array(ref: TypeSpec, countedBy: string, hint?: ArrayHint | null): IKoffiCType;
109
108
  export function array(ref: TypeSpec, countedBy: string, maxLen: number, hint?: ArrayHint | null): IKoffiCType;
110
109
 
111
110
  export function opaque(name: string | null | undefined): IKoffiCType;
package/index.js CHANGED
@@ -4,9 +4,9 @@ var __commonJS = (cb, mod3) => function __require() {
4
4
  return mod3 || (0, cb[__getOwnPropNames(cb)[0]])((mod3 = { exports: {} }).exports, mod3), mod3.exports;
5
5
  };
6
6
 
7
- // bin/Koffi/package/src/cnoke/src/tools.js
7
+ // ../../../bin/Koffi/package/src/cnoke/src/tools.js
8
8
  var require_tools = __commonJS({
9
- "bin/Koffi/package/src/cnoke/src/tools.js"(exports2, module2) {
9
+ "../../../bin/Koffi/package/src/cnoke/src/tools.js"(exports2, module2) {
10
10
  "use strict";
11
11
  var crypto = require("crypto");
12
12
  var fs2 = require("fs");
@@ -397,12 +397,12 @@ var require_tools = __commonJS({
397
397
  }
398
398
  });
399
399
 
400
- // bin/Koffi/package/src/koffi/package.json
400
+ // ../../../bin/Koffi/package/src/koffi/package.json
401
401
  var require_package = __commonJS({
402
- "bin/Koffi/package/src/koffi/package.json"(exports2, module2) {
402
+ "../../../bin/Koffi/package/src/koffi/package.json"(exports2, module2) {
403
403
  module2.exports = {
404
404
  name: "koffi",
405
- version: "2.14.0-beta.3",
405
+ version: "2.14.0",
406
406
  description: "Fast and simple C FFI (foreign function interface) for Node.js",
407
407
  keywords: [
408
408
  "foreign",
@@ -443,9 +443,9 @@ var require_package = __commonJS({
443
443
  }
444
444
  });
445
445
 
446
- // bin/Koffi/package/src/koffi/src/init.js
446
+ // ../../../bin/Koffi/package/src/koffi/src/init.js
447
447
  var require_init = __commonJS({
448
- "bin/Koffi/package/src/koffi/src/init.js"(exports, module) {
448
+ "../../../bin/Koffi/package/src/koffi/src/init.js"(exports, module) {
449
449
  var fs = require("fs");
450
450
  var path = require("path");
451
451
  var util = require("util");
@@ -528,7 +528,7 @@ var require_init = __commonJS({
528
528
  }
529
529
  });
530
530
 
531
- // bin/Koffi/package/src/koffi/index.js
531
+ // ../../../bin/Koffi/package/src/koffi/index.js
532
532
  var { detect: detect2, init: init2 } = require_init();
533
533
  var triplet2 = detect2();
534
534
  var native2 = null;
package/indirect.js CHANGED
@@ -4,9 +4,9 @@ var __commonJS = (cb, mod3) => function __require() {
4
4
  return mod3 || (0, cb[__getOwnPropNames(cb)[0]])((mod3 = { exports: {} }).exports, mod3), mod3.exports;
5
5
  };
6
6
 
7
- // bin/Koffi/package/src/cnoke/src/tools.js
7
+ // ../../../bin/Koffi/package/src/cnoke/src/tools.js
8
8
  var require_tools = __commonJS({
9
- "bin/Koffi/package/src/cnoke/src/tools.js"(exports2, module2) {
9
+ "../../../bin/Koffi/package/src/cnoke/src/tools.js"(exports2, module2) {
10
10
  "use strict";
11
11
  var crypto = require("crypto");
12
12
  var fs2 = require("fs");
@@ -397,12 +397,12 @@ var require_tools = __commonJS({
397
397
  }
398
398
  });
399
399
 
400
- // bin/Koffi/package/src/koffi/package.json
400
+ // ../../../bin/Koffi/package/src/koffi/package.json
401
401
  var require_package = __commonJS({
402
- "bin/Koffi/package/src/koffi/package.json"(exports2, module2) {
402
+ "../../../bin/Koffi/package/src/koffi/package.json"(exports2, module2) {
403
403
  module2.exports = {
404
404
  name: "koffi",
405
- version: "2.14.0-beta.3",
405
+ version: "2.14.0",
406
406
  description: "Fast and simple C FFI (foreign function interface) for Node.js",
407
407
  keywords: [
408
408
  "foreign",
@@ -443,9 +443,9 @@ var require_package = __commonJS({
443
443
  }
444
444
  });
445
445
 
446
- // bin/Koffi/package/src/koffi/src/init.js
446
+ // ../../../bin/Koffi/package/src/koffi/src/init.js
447
447
  var require_init = __commonJS({
448
- "bin/Koffi/package/src/koffi/src/init.js"(exports, module) {
448
+ "../../../bin/Koffi/package/src/koffi/src/init.js"(exports, module) {
449
449
  var fs = require("fs");
450
450
  var path = require("path");
451
451
  var util = require("util");
@@ -528,7 +528,7 @@ var require_init = __commonJS({
528
528
  }
529
529
  });
530
530
 
531
- // bin/Koffi/package/src/koffi/indirect.js
531
+ // ../../../bin/Koffi/package/src/koffi/indirect.js
532
532
  var { detect: detect2, init: init2 } = require_init();
533
533
  var triplet2 = detect2();
534
534
  var mod2 = init2(triplet2, null);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koffi",
3
- "version": "2.14.0-beta.3",
3
+ "version": "2.14.0",
4
4
  "description": "Fast and simple C FFI (foreign function interface) for Node.js",
5
5
  "keywords": [
6
6
  "foreign",
@@ -854,6 +854,31 @@ TimeSpec DecomposeTimeLocal(int64_t time)
854
854
  return spec;
855
855
  }
856
856
 
857
+ int64_t ComposeTimeUTC(const TimeSpec &spec)
858
+ {
859
+ RG_ASSERT(!spec.offset);
860
+
861
+ struct tm ti = {};
862
+
863
+ ti.tm_year = spec.year - 1900;
864
+ ti.tm_mon = spec.month - 1;
865
+ ti.tm_mday = spec.day;
866
+ ti.tm_hour = spec.hour;
867
+ ti.tm_min = spec.min;
868
+ ti.tm_sec = spec.sec;
869
+
870
+ #if defined(_WIN32)
871
+ int64_t time = (int64_t)_mkgmtime64(&ti);
872
+ #else
873
+ int64_t time = (int64_t)timegm(&ti);
874
+ #endif
875
+
876
+ time *= 1000;
877
+ time += spec.msec;
878
+
879
+ return time;
880
+ }
881
+
857
882
  // ------------------------------------------------------------------------
858
883
  // Strings
859
884
  // ------------------------------------------------------------------------
@@ -3669,6 +3669,7 @@ struct TimeSpec {
3669
3669
 
3670
3670
  TimeSpec DecomposeTimeUTC(int64_t time);
3671
3671
  TimeSpec DecomposeTimeLocal(int64_t time);
3672
+ int64_t ComposeTimeUTC(const TimeSpec &spec);
3672
3673
 
3673
3674
  // ------------------------------------------------------------------------
3674
3675
  // Format
@@ -517,7 +517,7 @@ bool CallData::PushObject(Napi::Object obj, const TypeInfo *type, uint8_t *origi
517
517
  RG_UNREACHABLE();
518
518
  }
519
519
 
520
- memset(origin, 0, type->size);
520
+ MemSet(origin, 0, type->size);
521
521
 
522
522
  for (Size i = 0; i < members.len; i++) {
523
523
  const RecordMember &member = members[i];
@@ -156,7 +156,7 @@ inline bool CallData::AllocStack(Size size, Size align, T **out_ptr)
156
156
  }
157
157
 
158
158
  #if defined(RG_DEBUG)
159
- memset(ptr, 0, delta);
159
+ MemSet(ptr, 0, delta);
160
160
  #endif
161
161
 
162
162
  mem->stack.len -= delta;
@@ -173,7 +173,7 @@ inline T *CallData::AllocHeap(Size size, Size align)
173
173
 
174
174
  if (size < 4096 && delta <= mem->heap.len) [[likely]] {
175
175
  #if defined(RG_DEBUG)
176
- memset(mem->heap.ptr, 0, (size_t)delta);
176
+ MemSet(mem->heap.ptr, 0, delta);
177
177
  #endif
178
178
 
179
179
  mem->heap.ptr += delta;
@@ -986,7 +986,11 @@ static Napi::Value CreateArrayType(const Napi::CallbackInfo &info)
986
986
  return env.Null();
987
987
  }
988
988
  if (!info[1 + dynamic].IsNumber()) {
989
- ThrowError<Napi::TypeError>(env, "Unexpected %1 value for %2, expected integer", GetValueType(instance, info[1]), dynamic ? "maxLen" : "len");
989
+ if (info.Length() == 2 && info[1].IsString()) {
990
+ ThrowError<Napi::TypeError>(env, "Missing maxLen argument");
991
+ } else {
992
+ ThrowError<Napi::TypeError>(env, "Unexpected %1 value for %2, expected integer", GetValueType(instance, info[1]), dynamic ? "maxLen" : "len");
993
+ }
990
994
  return env.Null();
991
995
  }
992
996
 
@@ -1003,7 +1007,7 @@ static Napi::Value CreateArrayType(const Napi::CallbackInfo &info)
1003
1007
 
1004
1008
  TypeInfo *type = nullptr;
1005
1009
 
1006
- if (info.Length() >= 3 + dynamic && !IsNullOrUndefined(info[2 + dynamic])) {
1010
+ if (info.Length() >= 3u + dynamic && !IsNullOrUndefined(info[2 + dynamic])) {
1007
1011
  if (!info[2 + dynamic].IsString()) {
1008
1012
  ThrowError<Napi::TypeError>(env, "Unexpected %1 value for hint, expected string", GetValueType(instance, info[2]));
1009
1013
  return env.Null();
@@ -2087,7 +2091,7 @@ static Napi::Value DecodeValue(const Napi::CallbackInfo &info)
2087
2091
  Napi::Env env = info.Env();
2088
2092
 
2089
2093
  bool has_offset = (info.Length() >= 2 && info[1].IsNumber());
2090
- bool has_len = (info.Length() >= 3u + has_offset && info[2u + has_offset].IsNumber());
2094
+ bool has_len = (info.Length() >= 3u + has_offset && info[2 + has_offset].IsNumber());
2091
2095
 
2092
2096
  if (info.Length() < 2u + has_offset) [[unlikely]] {
2093
2097
  ThrowError<Napi::TypeError>(env, "Expected %1 to 4 arguments, got %2", 2 + has_offset, info.Length());
@@ -2102,7 +2106,7 @@ static Napi::Value DecodeValue(const Napi::CallbackInfo &info)
2102
2106
  int64_t offset = has_offset ? info[1].As<Napi::Number>().Int64Value() : 0;
2103
2107
 
2104
2108
  if (has_len) {
2105
- Size len = info[2u + has_offset].As<Napi::Number>();
2109
+ Size len = info[2 + has_offset].As<Napi::Number>();
2106
2110
 
2107
2111
  Napi::Value ret = Decode(value, offset, type, &len);
2108
2112
  return ret;
@@ -2164,7 +2168,7 @@ static Napi::Value EncodeValue(const Napi::CallbackInfo &info)
2164
2168
  Napi::Env env = info.Env();
2165
2169
 
2166
2170
  bool has_offset = (info.Length() >= 2 && info[1].IsNumber());
2167
- bool has_len = (info.Length() >= 4u + has_offset && info[3u + has_offset].IsNumber());
2171
+ bool has_len = (info.Length() >= 4u + has_offset && info[3 + has_offset].IsNumber());
2168
2172
 
2169
2173
  if (info.Length() < 3u + has_offset) [[unlikely]] {
2170
2174
  ThrowError<Napi::TypeError>(env, "Expected %1 to 5 arguments, got %2", 3 + has_offset, info.Length());
@@ -2177,10 +2181,10 @@ static Napi::Value EncodeValue(const Napi::CallbackInfo &info)
2177
2181
 
2178
2182
  Napi::Value ref = info[0];
2179
2183
  int64_t offset = has_offset ? info[1].As<Napi::Number>().Int64Value() : 0;
2180
- Napi::Value value = info[2u + has_offset];
2184
+ Napi::Value value = info[2 + has_offset];
2181
2185
 
2182
2186
  if (has_len) {
2183
- Size len = info[3u + has_offset].As<Napi::Number>();
2187
+ Size len = info[3 + has_offset].As<Napi::Number>();
2184
2188
 
2185
2189
  if (!Encode(ref, offset, value, type, &len))
2186
2190
  return env.Null();