koffi 2.5.10 → 2.5.12

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.
Files changed (38) hide show
  1. package/CHANGELOG.md +78 -68
  2. package/build/koffi/darwin_arm64/koffi.node +0 -0
  3. package/build/koffi/darwin_x64/koffi.node +0 -0
  4. package/build/koffi/freebsd_arm64/koffi.node +0 -0
  5. package/build/koffi/freebsd_ia32/koffi.node +0 -0
  6. package/build/koffi/freebsd_x64/koffi.node +0 -0
  7. package/build/koffi/linux_arm32hf/koffi.node +0 -0
  8. package/build/koffi/linux_arm64/koffi.node +0 -0
  9. package/build/koffi/linux_ia32/koffi.node +0 -0
  10. package/build/koffi/linux_riscv64hf64/koffi.node +0 -0
  11. package/build/koffi/linux_x64/koffi.node +0 -0
  12. package/build/koffi/openbsd_ia32/koffi.node +0 -0
  13. package/build/koffi/openbsd_x64/koffi.node +0 -0
  14. package/build/koffi/win32_arm64/koffi.node +0 -0
  15. package/build/koffi/win32_ia32/koffi.node +0 -0
  16. package/build/koffi/win32_x64/koffi.node +0 -0
  17. package/doc/callbacks.md +1 -1
  18. package/doc/conf.py +2 -1
  19. package/doc/functions.md +11 -0
  20. package/doc/index.rst +5 -2
  21. package/doc/{types.md → input.md} +43 -39
  22. package/doc/migration.md +4 -4
  23. package/doc/output.md +169 -0
  24. package/doc/packaging.md +1 -1
  25. package/doc/pointers.md +52 -2
  26. package/doc/polymorphism.md +108 -0
  27. package/package.json +7 -3
  28. package/src/cnoke/package.json +5 -1
  29. package/src/core/libcc/libcc.cc +114 -4
  30. package/src/core/libcc/libcc.hh +44 -16
  31. package/src/index.js +2 -2
  32. package/src/koffi/CMakeLists.txt +1 -1
  33. package/src/koffi/src/call.cc +16 -6
  34. package/src/koffi/src/call.hh +1 -1
  35. package/src/koffi/src/ffi.cc +6 -5
  36. package/src/koffi/src/ffi.hh +1 -1
  37. package/src/koffi/src/util.cc +1 -1
  38. package/doc/parameters.md +0 -336
@@ -272,6 +272,7 @@ constexpr Size Kilobytes(Size len) { return len * 1000; }
272
272
  #define RG_SIZE(Type) ((RG::Size)sizeof(Type))
273
273
  template <typename T, unsigned N>
274
274
  char (&ComputeArraySize(T const (&)[N]))[N];
275
+ #define RG_BITS(Type) (8 * RG_SIZE(Type))
275
276
  #define RG_LEN(Array) RG_SIZE(RG::ComputeArraySize(Array))
276
277
  #define RG_OFFSET_OF(Type, Member) ((Size)__builtin_offsetof(Type, Member))
277
278
 
@@ -1856,7 +1857,7 @@ public:
1856
1857
  typedef Iterator<Bitset> iterator_type;
1857
1858
 
1858
1859
  static constexpr Size Bits = N;
1859
- size_t data[(N + RG_SIZE(size_t) - 1) / RG_SIZE(size_t)] = {};
1860
+ size_t data[(N + RG_BITS(size_t) - 1) / RG_BITS(size_t)] = {};
1860
1861
 
1861
1862
  void Clear()
1862
1863
  {
@@ -3315,41 +3316,38 @@ static inline int CmpStr(const char *str1, Span<const char> str2)
3315
3316
  static inline int CmpStr(const char *str1, const char *str2)
3316
3317
  { return strcmp(str1, str2); }
3317
3318
 
3318
- static inline Size StartsWith(Span<const char> str, Span<const char> prefix)
3319
+ static inline bool StartsWith(Span<const char> str, Span<const char> prefix)
3319
3320
  {
3320
3321
  Size i = 0;
3321
3322
  while (i < str.len && i < prefix.len) {
3322
3323
  if (str[i] != prefix[i])
3323
- return 0;
3324
-
3324
+ return false;
3325
3325
  i++;
3326
3326
  }
3327
3327
 
3328
- return (i == prefix.len) ? i : 0;
3328
+ return (i == prefix.len);
3329
3329
  }
3330
- static inline Size StartsWith(Span<const char> str, const char *prefix)
3330
+ static inline bool StartsWith(Span<const char> str, const char *prefix)
3331
3331
  {
3332
3332
  Size i = 0;
3333
3333
  while (i < str.len && prefix[i]) {
3334
3334
  if (str[i] != prefix[i])
3335
- return 0;
3336
-
3335
+ return false;
3337
3336
  i++;
3338
3337
  }
3339
3338
 
3340
- return !prefix[i] ? i : 0;
3339
+ return !prefix[i];
3341
3340
  }
3342
- static inline Size StartsWith(const char *str, const char *prefix)
3341
+ static inline bool StartsWith(const char *str, const char *prefix)
3343
3342
  {
3344
3343
  Size i = 0;
3345
3344
  while (str[i] && prefix[i]) {
3346
3345
  if (str[i] != prefix[i])
3347
- return 0;
3348
-
3346
+ return false;
3349
3347
  i++;
3350
3348
  }
3351
3349
 
3352
- return !prefix[i] ? i : 0;
3350
+ return !prefix[i];
3353
3351
  }
3354
3352
 
3355
3353
  static inline bool EndsWith(Span<const char> str, const char *suffix)
@@ -3703,6 +3701,31 @@ overflow:
3703
3701
  bool ParseBool(Span<const char> str, bool *out_value, unsigned int flags = RG_DEFAULT_PARSE_FLAGS,
3704
3702
  Span<const char> *out_remaining = nullptr);
3705
3703
 
3704
+ bool ParseSize(Span<const char> str, int64_t *out_size, unsigned int flags = RG_DEFAULT_PARSE_FLAGS,
3705
+ Span<const char> *out_remaining = nullptr);
3706
+ #if RG_SIZE_MAX < INT64_MAX
3707
+ static inline bool ParseSize(Span<const char> str, Size *out_size,
3708
+ unsigned int flags = RG_DEFAULT_PARSE_FLAGS, Span<const char> *out_remaining = nullptr)
3709
+ {
3710
+ int64_t size = 0;
3711
+ if (!ParseSize(str, &size, flags, out_remaining))
3712
+ return false;
3713
+
3714
+ if (size > RG_SIZE_MAX) [[unlikely]] {
3715
+ if (flags & (int)ParseFlag::Log) {
3716
+ LogError("Size value is too high");
3717
+ }
3718
+ return false;
3719
+ }
3720
+
3721
+ *out_size = (Size)size;
3722
+ return true;
3723
+ }
3724
+ #endif
3725
+
3726
+ bool ParseDuration(Span<const char> str, int64_t *out_duration, unsigned int flags = RG_DEFAULT_PARSE_FLAGS,
3727
+ Span<const char> *out_remaining = nullptr);
3728
+
3706
3729
  static inline Size EncodeUtf8(int32_t c, char out_buf[4])
3707
3730
  {
3708
3731
  if (c < 0x80) {
@@ -3806,10 +3829,12 @@ static inline Size DecodeUtf8(Span<const char> str, Size offset, int32_t *out_c)
3806
3829
  #ifdef _WIN32
3807
3830
  #define RG_PATH_SEPARATORS "\\/"
3808
3831
  #define RG_PATH_DELIMITER ';'
3832
+ #define RG_EXECUTABLE_EXTENSION ".exe"
3809
3833
  #define RG_SHARED_LIBRARY_EXTENSION ".dll"
3810
3834
  #else
3811
3835
  #define RG_PATH_SEPARATORS "/"
3812
3836
  #define RG_PATH_DELIMITER ':'
3837
+ #define RG_EXECUTABLE_EXTENSION ""
3813
3838
  #define RG_SHARED_LIBRARY_EXTENSION ".so"
3814
3839
  #endif
3815
3840
 
@@ -4096,11 +4121,11 @@ bool NotifySystemd();
4096
4121
  })()
4097
4122
  #endif
4098
4123
 
4124
+ void InitRG();
4125
+ int Main(int argc, char **argv);
4126
+
4099
4127
  static inline int RunApp(int argc, char **argv)
4100
4128
  {
4101
- void InitRG();
4102
- int Main(int argc, char **argv);
4103
-
4104
4129
  InitRG();
4105
4130
  return Main(argc, argv);
4106
4131
  }
@@ -4658,6 +4683,9 @@ public:
4658
4683
 
4659
4684
  bool SpliceStream(StreamReader *reader, int64_t max_len, StreamWriter *writer);
4660
4685
 
4686
+ bool IsCompressorAvailable(CompressionType compression_type);
4687
+ bool IsDecompressorAvailable(CompressionType compression_type);
4688
+
4661
4689
  // For convenience, don't close them
4662
4690
  extern StreamReader stdin_st;
4663
4691
  extern StreamWriter stdout_st;
package/src/index.js CHANGED
@@ -47,13 +47,13 @@ try {
47
47
  switch (triplet) {
48
48
  case 'darwin_arm64': { native = require('../build/koffi/darwin_arm64/koffi.node'); } break;
49
49
  case 'darwin_x64': { native = require('../build/koffi/darwin_x64/koffi.node'); } break;
50
- case 'freebsd_arm46': { native = require('../build/koffi/freebsd_arm46/koffi.node'); } break;
50
+ case 'freebsd_arm64': { native = require('../build/koffi/freebsd_arm64/koffi.node'); } break;
51
51
  case 'freebsd_ia32': { native = require('../build/koffi/freebsd_ia32/koffi.node'); } break;
52
52
  case 'freebsd_x64': { native = require('../build/koffi/freebsd_x64/koffi.node'); } break;
53
53
  case 'linux_arm32hf': { native = require('../build/koffi/linux_arm32hf/koffi.node'); } break;
54
54
  case 'linux_arm64': { native = require('../build/koffi/linux_arm64/koffi.node'); } break;
55
55
  case 'linux_ia32': { native = require('../build/koffi/linux_ia32/koffi.node'); } break;
56
- case 'linux_risc64hf64': { native = require('../build/koffi/linux_risc64hf64/koffi.node'); } break;
56
+ case 'linux_riscv64hf64': { native = require('../build/koffi/linux_riscv64hf64/koffi.node'); } break;
57
57
  case 'linux_x64': { native = require('../build/koffi/linux_x64/koffi.node'); } break;
58
58
  case 'openbsd_ia32': { native = require('../build/koffi/openbsd_ia32/koffi.node'); } break;
59
59
  case 'openbsd_x64': { native = require('../build/koffi/openbsd_x64/koffi.node'); } break;
@@ -28,7 +28,7 @@ include(CheckCXXCompilerFlag)
28
28
 
29
29
  find_package(CNoke)
30
30
 
31
- set(CMAKE_CXX_STANDARD 17)
31
+ set(CMAKE_CXX_STANDARD 20)
32
32
  if(MSVC)
33
33
  set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded)
34
34
 
@@ -1043,12 +1043,6 @@ bool CallData::PushPointer(Napi::Value value, const TypeInfo *type, int directio
1043
1043
  out_kind = OutArgument::Kind::Buffer;
1044
1044
  } else if (type->ref.type->primitive == PrimitiveKind::Record ||
1045
1045
  type->ref.type->primitive == PrimitiveKind::Union) [[likely]] {
1046
- if (!type->ref.type->size) [[unlikely]] {
1047
- ThrowError<Napi::TypeError>(env, "Cannot pass %1 value to %2, use koffi.as()",
1048
- type->ref.type != instance->void_type ? "opaque" : "ambiguous", type->name);
1049
- return false;
1050
- }
1051
-
1052
1046
  Napi::Object obj = value.As<Napi::Object>();
1053
1047
  RG_ASSERT(IsObject(value));
1054
1048
 
@@ -1108,6 +1102,22 @@ bool CallData::PushPointer(Napi::Value value, const TypeInfo *type, int directio
1108
1102
  }
1109
1103
  } break;
1110
1104
 
1105
+ case napi_function: {
1106
+ if (type->primitive != PrimitiveKind::Callback) [[unlikely]] {
1107
+ ThrowError<Napi::TypeError>(env, "Cannot pass function to type %1", type->name);
1108
+ return false;
1109
+ }
1110
+
1111
+ Napi::Function func = value.As<Napi::Function>();
1112
+
1113
+ void *ptr = ReserveTrampoline(type->ref.proto, func);
1114
+ if (!ptr) [[unlikely]]
1115
+ return false;
1116
+
1117
+ *out_ptr = (void *)ptr;
1118
+ return true;
1119
+ } break;
1120
+
1111
1121
  case napi_number: {
1112
1122
  Napi::Number number = value.As<Napi::Number>();
1113
1123
  intptr_t ptr = (intptr_t)number.Int32Value();
@@ -81,7 +81,7 @@ class alignas(8) CallData {
81
81
  uint8_t *return_ptr = nullptr;
82
82
 
83
83
  LocalArray<int16_t, 16> used_trampolines;
84
- LocalArray<OutArgument, MaxParameters> out_arguments;
84
+ HeapArray<OutArgument> out_arguments;
85
85
 
86
86
  BlockAllocator call_alloc;
87
87
 
@@ -233,7 +233,7 @@ static Napi::Value CreateStructType(const Napi::CallbackInfo &info, bool pad)
233
233
  Napi::Array keys = obj.GetPropertyNames();
234
234
 
235
235
  type->name = named ? DuplicateString(name.Utf8Value().c_str(), &instance->str_alloc).ptr
236
- : Fmt(&instance->str_alloc, "<type_%1>", instance->types.len).ptr;
236
+ : Fmt(&instance->str_alloc, "<anonymous_%1>", instance->types.len).ptr;
237
237
 
238
238
  type->primitive = PrimitiveKind::Record;
239
239
  type->align = 1;
@@ -362,7 +362,7 @@ static Napi::Value CreateUnionType(const Napi::CallbackInfo &info)
362
362
  Napi::Array keys = obj.GetPropertyNames();
363
363
 
364
364
  type->name = named ? DuplicateString(name.Utf8Value().c_str(), &instance->str_alloc).ptr
365
- : Fmt(&instance->str_alloc, "<type_%1>", instance->types.len).ptr;
365
+ : Fmt(&instance->str_alloc, "<anonymous_%1>", instance->types.len).ptr;
366
366
 
367
367
  type->primitive = PrimitiveKind::Union;
368
368
  type->align = 1;
@@ -493,7 +493,7 @@ static Napi::Value CreateOpaqueType(const Napi::CallbackInfo &info)
493
493
  RG_DEFER_N(err_guard) { instance->types.RemoveLast(1); };
494
494
 
495
495
  type->name = named ? DuplicateString(name.Utf8Value().c_str(), &instance->str_alloc).ptr
496
- : Fmt(&instance->str_alloc, "<type_%1>", instance->types.len).ptr;
496
+ : Fmt(&instance->str_alloc, "<anonymous_%1>", instance->types.len).ptr;
497
497
 
498
498
  type->primitive = PrimitiveKind::Void;
499
499
  type->size = 0;
@@ -699,7 +699,7 @@ static Napi::Value CreateDisposableType(const Napi::CallbackInfo &info)
699
699
  type->members.allocator = GetNullAllocator();
700
700
 
701
701
  type->name = named ? DuplicateString(name.Utf8Value().c_str(), &instance->str_alloc).ptr
702
- : Fmt(&instance->str_alloc, "<type_%1>", instance->types.len).ptr;
702
+ : Fmt(&instance->str_alloc, "<anonymous_%1>", instance->types.len).ptr;
703
703
 
704
704
  type->dispose = dispose;
705
705
  type->dispose_ref = Napi::Persistent(dispose_func);
@@ -1775,8 +1775,9 @@ static Napi::Value CastValue(const Napi::CallbackInfo &info)
1775
1775
  if (!type) [[unlikely]]
1776
1776
  return env.Null();
1777
1777
  if (type->primitive != PrimitiveKind::Pointer &&
1778
+ type->primitive != PrimitiveKind::Callback &&
1778
1779
  type->primitive != PrimitiveKind::String &&
1779
- type->primitive != PrimitiveKind::String16) {
1780
+ type->primitive != PrimitiveKind::String16) [[unlikely]] {
1780
1781
  ThrowError<Napi::TypeError>(env, "Only pointer or string types can be used for casting");
1781
1782
  return env.Null();
1782
1783
  }
@@ -36,7 +36,7 @@ static const int DefaultMaxAsyncCalls = 64;
36
36
  static const Size DefaultMaxTypeSize = Mebibytes(64);
37
37
 
38
38
  static const int MaxAsyncCalls = 256;
39
- static const Size MaxParameters = 32;
39
+ static const Size MaxParameters = 64;
40
40
  static const Size MaxTrampolines = 8192;
41
41
 
42
42
  enum class PrimitiveKind {
@@ -243,7 +243,7 @@ const TypeInfo *ResolveType(Napi::Env env, Span<const char> str, int *out_direct
243
243
  TypeInfo *copy = instance->types.AppendDefault();
244
244
 
245
245
  memcpy((void *)copy, (const void *)type, RG_SIZE(*type));
246
- copy->name = Fmt(&instance->str_alloc, "<type_%1>", instance->types.len).ptr;
246
+ copy->name = Fmt(&instance->str_alloc, "<anonymous_%1>", instance->types.len).ptr;
247
247
  copy->members.allocator = GetNullAllocator();
248
248
 
249
249
  copy->dispose = [](Napi::Env env, const TypeInfo *, const void *ptr) {
package/doc/parameters.md DELETED
@@ -1,336 +0,0 @@
1
- # Special parameters
2
-
3
- ## Direction
4
-
5
- By default, Koffi will only forward arguments from Javascript to C. However, many C functions use pointer arguments for output values, or input/output values.
6
-
7
- ## Output parameters
8
-
9
- For simplicity, and because Javascript only has value semantics for primitive types, Koffi can marshal out (or in/out) two types of parameters:
10
-
11
- - [Structs](types.md#struct-types) (to/from JS objects)
12
- - [Unions](unions.md)
13
- - [Opaque types](types.md#opaque-types)
14
- - String buffers
15
-
16
- In order to change an argument from input-only to output or input/output, use the following functions:
17
-
18
- - `koffi.out()` on a pointer, e.g. `koffi.out(koffi.pointer(timeval))` (where timeval is a struct type)
19
- - `koffi.inout()` for dual input/output parameters
20
-
21
- The same can be done when declaring a function with a C-like prototype string, with the MSDN-like type qualifiers:
22
-
23
- - `_Out_` for output parameters
24
- - `_Inout_` for dual input/output parameters
25
-
26
- ### Primitive value
27
-
28
- This Windows example enumerate all Chrome windows along with their PID and their title. The `GetWindowThreadProcessId()` function illustrates how to get a primitive value from an output argument.
29
-
30
- ```js
31
- // ES6 syntax: import koffi from 'koffi';
32
- const koffi = require('koffi');
33
-
34
- const user32 = koffi.load('user32.dll');
35
-
36
- const DWORD = koffi.alias('DWORD', 'uint32_t');
37
- const HANDLE = koffi.pointer(koffi.opaque('HANDLE'));
38
- const HWND = koffi.alias('HWND', HANDLE);
39
-
40
- const FindWindowEx = user32.func('HWND __stdcall FindWindowExW(HWND hWndParent, HWND hWndChildAfter, const char16_t *lpszClass, const char16_t *lpszWindow)');
41
- const GetWindowThreadProcessId = user32.func('DWORD __stdcall GetWindowThreadProcessId(HWND hWnd, _Out_ DWORD *lpdwProcessId)');
42
- const GetWindowText = user32.func('int __stdcall GetWindowTextA(HWND hWnd, _Out_ uint8_t *lpString, int nMaxCount)');
43
-
44
- for (let hwnd = null;;) {
45
- hwnd = FindWindowEx(0, hwnd, 'Chrome_WidgetWin_1', null);
46
-
47
- if (!hwnd)
48
- break;
49
-
50
- // Get PID
51
- let pid;
52
- {
53
- let ptr = [null];
54
- let tid = GetWindowThreadProcessId(hwnd, ptr);
55
-
56
- if (!tid) {
57
- // Maybe the process ended in-between?
58
- continue;
59
- }
60
-
61
- pid = ptr[0];
62
- }
63
-
64
- // Get window title
65
- let title;
66
- {
67
- let buf = Buffer.allocUnsafe(1024);
68
- let length = GetWindowText(hwnd, buf, buf.length);
69
-
70
- if (!length) {
71
- // Maybe the process ended in-between?
72
- continue;
73
- }
74
-
75
- title = koffi.decode(buf, 'char', length);
76
- }
77
-
78
- console.log({ PID: pid, Title: title });
79
- }
80
- ```
81
-
82
- ### Struct example
83
-
84
- This example calls the POSIX function `gettimeofday()`, and uses the prototype-like syntax.
85
-
86
- ```js
87
- // ES6 syntax: import koffi from 'koffi';
88
- const koffi = require('koffi');
89
-
90
- const lib = koffi.load('libc.so.6');
91
-
92
- const timeval = koffi.struct('timeval', {
93
- tv_sec: 'unsigned int',
94
- tv_usec: 'unsigned int'
95
- });
96
- const timezone = koffi.struct('timezone', {
97
- tz_minuteswest: 'int',
98
- tz_dsttime: 'int'
99
- });
100
-
101
- // The _Out_ qualifiers instruct Koffi to marshal out the values
102
- const gettimeofday = lib.func('int gettimeofday(_Out_ timeval *tv, _Out_ timezone *tz)');
103
-
104
- let tv = {};
105
- gettimeofday(tv, null);
106
-
107
- console.log(tv);
108
- ```
109
-
110
- ### Opaque type example
111
-
112
- This example opens an in-memory SQLite database, and uses the node-ffi-style function declaration syntax.
113
-
114
- ```js
115
- // ES6 syntax: import koffi from 'koffi';
116
- const koffi = require('koffi');
117
-
118
- const lib = koffi.load('sqlite3.so');
119
-
120
- const sqlite3 = koffi.opaque('sqlite3');
121
-
122
- // Use koffi.out() on a double pointer to copy out (from C to JS) after the call
123
- const sqlite3_open_v2 = lib.func('sqlite3_open_v2', 'int', ['str', koffi.out(koffi.pointer(sqlite3, 2)), 'int', 'str']);
124
- const sqlite3_close_v2 = lib.func('sqlite3_close_v2', 'int', [koffi.pointer(sqlite3)]);
125
-
126
- const SQLITE_OPEN_READWRITE = 0x2;
127
- const SQLITE_OPEN_CREATE = 0x4;
128
-
129
- let out = [null];
130
- if (sqlite3_open_v2(':memory:', out, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, null) != 0)
131
- throw new Error('Failed to open database');
132
- let db = out[0];
133
-
134
- sqlite3_close_v2(db);
135
- ```
136
-
137
- ### String buffer example
138
-
139
- *New in Koffi 2.2*
140
-
141
- This example calls a C function to concatenate two strings to a pre-allocated string buffer. Since JS strings are immutable, you must pass an array with a single string instead.
142
-
143
- ```c
144
- void ConcatToBuffer(const char *str1, const char *str2, char *out)
145
- {
146
- size_t len = 0;
147
-
148
- for (size_t i = 0; str1[i]; i++) {
149
- out[len++] = str1[i];
150
- }
151
- for (size_t i = 0; str2[i]; i++) {
152
- out[len++] = str2[i];
153
- }
154
-
155
- out[len] = 0;
156
- }
157
- ```
158
-
159
- ```js
160
- const ConcatToBuffer = lib.func('void ConcatToBuffer(const char *str1, const char *str2, _Out_ char *out)');
161
-
162
- let str1 = 'Hello ';
163
- let str2 = 'Friends!';
164
-
165
- // We need to reserve space for the output buffer! Including the NUL terminator
166
- // because ConcatToBuffer() expects so, but Koffi can convert back to a JS string
167
- // without it (if we reserve the right size).
168
- let out = ['\0'.repeat(str1.length + str2.length + 1)];
169
-
170
- ConcatToBuffer(str1, str2, out);
171
-
172
- console.log(out[0]);
173
- ```
174
-
175
- ## Polymorphic parameters
176
-
177
- ### Input polymorphism
178
-
179
- *New in Koffi 2.1*
180
-
181
- 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.
182
-
183
- Koffi provides two features to deal with this:
184
-
185
- - 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.
186
- - You can use `koffi.as(value, type)` to tell Koffi what kind of type is actually expected.
187
-
188
- The example below shows the use of `koffi.as()` to read the header of a PNG file with `fread()`.
189
-
190
- ```js
191
- // ES6 syntax: import koffi from 'koffi';
192
- const koffi = require('koffi');
193
-
194
- const lib = koffi.load('libc.so.6');
195
-
196
- const FILE = koffi.opaque('FILE');
197
-
198
- const PngHeader = koffi.pack('PngHeader', {
199
- signature: koffi.array('uint8_t', 8),
200
- ihdr: koffi.pack({
201
- length: 'uint32_be_t',
202
- chunk: koffi.array('char', 4),
203
- width: 'uint32_be_t',
204
- height: 'uint32_be_t',
205
- depth: 'uint8_t',
206
- color: 'uint8_t',
207
- compression: 'uint8_t',
208
- filter: 'uint8_t',
209
- interlace: 'uint8_t',
210
- crc: 'uint32_be_t'
211
- })
212
- });
213
-
214
- const fopen = lib.func('FILE *fopen(const char *path, const char *mode)');
215
- const fclose = lib.func('int fclose(FILE *fp)');
216
- const fread = lib.func('size_t fread(_Out_ void *ptr, size_t size, size_t nmemb, FILE *fp)');
217
-
218
- let filename = process.argv[2];
219
- if (filename == null)
220
- throw new Error('Usage: node png.js <image.png>');
221
-
222
- let hdr = {};
223
- {
224
- let fp = fopen(filename, 'rb');
225
- if (!fp)
226
- throw new Error(`Failed to open '${filename}'`);
227
-
228
- try {
229
- let len = fread(koffi.as(hdr, 'PngHeader *'), 1, koffi.sizeof(PngHeader), fp);
230
- if (len < koffi.sizeof(PngHeader))
231
- throw new Error('Failed to read PNG header');
232
- } finally {
233
- fclose(fp);
234
- }
235
- }
236
-
237
- console.log('PNG header:', hdr);
238
- ```
239
-
240
- ### Output buffers
241
-
242
- *New in Koffi 2.3*
243
-
244
- 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.
245
-
246
- Once the native function returns, you can decode the content with `koffi.decode(value, type)` as in the following example:
247
-
248
- ```js
249
- // ES6 syntax: import koffi from 'koffi';
250
- const koffi = require('koffi');
251
-
252
- const lib = koffi.load('libc.so.6');
253
-
254
- const Vec3 = koffi.struct('Vec3', {
255
- x: 'float32',
256
- y: 'float32',
257
- z: 'float32'
258
- })
259
-
260
- const memcpy = lib.func('void *memcpy(_Out_ void *dest, const void *src, size_t size)');
261
-
262
- let vec1 = { x: 1, y: 2, z: 3 };
263
- let vec2 = null;
264
-
265
- // Copy the vector in a convoluted way through memcpy
266
- {
267
- let src = koffi.as(vec1, 'Vec3 *');
268
- let dest = Buffer.allocUnsafe(koffi.sizeof(Vec3));
269
-
270
- memcpy(dest, src, koffi.sizeof(Vec3));
271
-
272
- vec2 = koffi.decode(dest, Vec3);
273
- }
274
-
275
- // CHange vector1, leaving copy alone
276
- [vec1.x, vec1.y, vec1.z] = [vec1.z, vec1.y, vec1.x];
277
-
278
- console.log(vec1); // { x: 3, y: 2, z: 1 }
279
- console.log(vec2); // { x: 1, y: 2, z: 3 }
280
- ```
281
-
282
- See [pointer arguments](callbacks.md#decoding-pointer-arguments) for more information about the decode function.
283
-
284
- ## Heap-allocated values
285
-
286
- *New in Koffi 2.0*
287
-
288
- Some C functions return heap-allocated values directly or through output parameters. While Koffi automatically converts values from C to JS (to a string or an object), it does not know when something needs to be freed, or how.
289
-
290
- For opaque types, such as FILE, this does not matter because you will explicitly call `fclose()` on them. But some values (such as strings) get implicitly converted by Koffi, and you lose access to the original pointer. This creates a leak if the string is heap-allocated.
291
-
292
- To avoid this, you can instruct Koffi to call a function on the original pointer once the conversion is done, by creating a **disposable type** with `koffi.dispose(name, type, func)`. This creates a type derived from another type, the only difference being that *func* gets called with the original pointer once the value has been converted and is not needed anymore.
293
-
294
- The *name* can be omitted to create an anonymous disposable type. If *func* is omitted or is null, Koffi will use `koffi.free(ptr)` (which calls the standard C library *free* function under the hood).
295
-
296
- ```js
297
- const AnonHeapStr = koffi.disposable('str'); // Anonymous type (cannot be used in function prototypes)
298
- const NamedHeapStr = koffi.disposable('HeapStr', 'str'); // Same thing, but named so usable in function prototypes
299
- const ExplicitFree = koffi.disposable('HeapStr16', 'str16', koffi.free); // You can specify any other JS function
300
- ```
301
-
302
- The following example illustrates the use of a disposable type derived from *str*.
303
-
304
- ```js
305
- // ES6 syntax: import koffi from 'koffi';
306
- const koffi = require('koffi');
307
-
308
- const lib = koffi.load('libc.so.6');
309
-
310
- const HeapStr = koffi.disposable('str');
311
- const strdup = lib.cdecl('strdup', HeapStr, ['str']);
312
-
313
- let copy = strdup('Hello!');
314
- console.log(copy); // Prints Hello!
315
- ```
316
-
317
- When you declare functions with the [prototype-like syntax](functions.md#definition-syntax), you can either use named disposable types or use the '!' shortcut qualifier with compatibles types, as shown in the example below. This qualifier creates an anonymous disposable type that calls `koffi.free(ptr)`.
318
-
319
- ```js
320
- // ES6 syntax: import koffi from 'koffi';
321
- const koffi = require('koffi');
322
-
323
- const lib = koffi.load('libc.so.6');
324
-
325
- // You can also use: const strdup = lib.func('const char *! strdup(const char *str)')
326
- const strdup = lib.func('str! strdup(const char *str)');
327
-
328
- let copy = strdup('World!');
329
- console.log(copy); // Prints World!
330
- ```
331
-
332
- Disposable types can only be created from pointer or string types.
333
-
334
- ```{warning}
335
- Be careful on Windows: if your shared library uses a different CRT (such as msvcrt), the memory could have been allocated by a different malloc/free implementation or heap, resulting in undefined behavior if you use `koffi.free()`.
336
- ```