koffi 2.7.4-beta.1 → 2.8.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
@@ -2,8 +2,22 @@
2
2
 
3
3
  ## Version history
4
4
 
5
+ ### Koffi 2.8
6
+
7
+ #### Koffi 2.8.0 (2024-02-12)
8
+
9
+ - Support pushing pointers for string arguments
10
+ - Add `koffi.alloc()` for [stable pointers](output.md#stable-pointers)
11
+
5
12
  ### Koffi 2.7
6
13
 
14
+ #### Koffi 2.7.4 (2024-02-04)
15
+
16
+ - Support callbacks happening on main thread but outside JS call (such as during event loop)
17
+ - Improve stability after `koffi.reset()`
18
+ - Expose internal Node/NAPI `env` pointer
19
+ - Name main Koffi API functions
20
+
7
21
  #### Koffi 2.7.3 (2024-01-26)
8
22
 
9
23
  - Support decoding NULL terminated arrays
@@ -123,7 +137,7 @@ Pre-built binaries don't work correctly in Koffi 2.5.13 to 2.5.15, skip those ve
123
137
 
124
138
  #### Koffi 2.5.11 (2023-08-03)
125
139
 
126
- - Support casting function pointers with [koffi.as()](polymorphism.md#input-polymorphism)
140
+ - Support casting function pointers with [koffi.as()](pointers.md#handling-void-pointers)
127
141
  - Build in C++20 mode
128
142
 
129
143
  #### Koffi 2.5.10 (2023-08-01)
@@ -331,7 +345,7 @@ Pre-built binaries don't work correctly in Koffi 2.5.13 to 2.5.15, skip those ve
331
345
  **Main changes:**
332
346
 
333
347
  - Allow buffers (TypedArray or ArrayBuffer) values for input and/or output pointer arguments (for polymorphic arguments)
334
- - Support opaque buffers (TypedArray or ArrayBuffer) values in `koffi.decode()` to [decode output buffers](polymorphism.md#output-buffers)
348
+ - Support opaque buffers (TypedArray or ArrayBuffer) values in `koffi.decode()` to [decode output buffers](output.md#output-buffers)
335
349
  - Decode non-string types as arrays when an [explicit length is passed to koffi.decode()](callbacks.md#decoding-pointer-arguments)
336
350
 
337
351
  **Other changes:**
@@ -424,7 +438,7 @@ Pre-built binaries don't work correctly in Koffi 2.5.13 to 2.5.15, skip those ve
424
438
 
425
439
  **Main changes:**
426
440
 
427
- - Add [koffi.as()](polymorphism.md#input-polymorphism) to support polymorphic APIs based on `void *` parameters
441
+ - Add [koffi.as()](pointers.md#handling-void-pointers) to support polymorphic APIs based on `void *` parameters
428
442
  - Add [endian-sensitive integer types](input.md#endian-sensitive-integers): `intX_le_t`, `intX_be_t`, `uintX_le_t`, `uintX_be_t`
429
443
  - Accept typed arrays for `void *` parameters
430
444
  - 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
@@ -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
@@ -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-beta.1",
382
- stable: "2.7.3",
381
+ version: "2.8.0",
382
+ stable: "2.8.0",
383
383
  description: "Fast and simple C FFI (foreign function interface) for Node.js",
384
384
  keywords: [
385
385
  "foreign",
package/indirect.js CHANGED
@@ -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-beta.1",
382
- stable: "2.7.3",
381
+ version: "2.8.0",
382
+ stable: "2.8.0",
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-beta.1",
4
- "stable": "2.7.3",
3
+ "version": "2.8.0",
4
+ "stable": "2.8.0",
5
5
  "description": "Fast and simple C FFI (foreign function interface) for Node.js",
6
6
  "keywords": [
7
7
  "foreign",
@@ -30,12 +30,16 @@ function(add_node_addon)
30
30
  cmake_parse_arguments(ARG "" "NAME" "SOURCES" ${ARGN})
31
31
 
32
32
  add_library(${ARG_NAME} SHARED ${ARG_SOURCES} ${NODE_JS_SOURCES})
33
+ target_link_node(${ARG_NAME})
33
34
  set_target_properties(${ARG_NAME} PROPERTIES PREFIX "" SUFFIX ".node")
34
- target_include_directories(${ARG_NAME} PRIVATE ${NODE_JS_INCLUDE_DIRS})
35
- target_link_libraries(${ARG_NAME} PRIVATE ${NODE_JS_LIBRARIES})
36
- target_compile_options(${ARG_NAME} PRIVATE ${NODE_JS_COMPILE_FLAGS})
35
+ endfunction()
36
+
37
+ function(target_link_node TARGET)
38
+ target_include_directories(${TARGET} PRIVATE ${NODE_JS_INCLUDE_DIRS})
39
+ target_link_libraries(${TARGET} PRIVATE ${NODE_JS_LIBRARIES})
40
+ target_compile_options(${TARGET} PRIVATE ${NODE_JS_COMPILE_FLAGS})
37
41
  if (NODE_JS_LINK_FLAGS)
38
- target_link_options(${ARG_NAME} PRIVATE ${NODE_JS_LINK_FLAGS})
42
+ target_link_options(${TARGET} PRIVATE ${NODE_JS_LINK_FLAGS})
39
43
  endif()
40
44
  endfunction()
41
45
 
@@ -21,17 +21,16 @@
21
21
 
22
22
  cmake_minimum_required(VERSION 3.6)
23
23
  cmake_policy(SET CMP0091 NEW)
24
-
25
24
  project(koffi C CXX ASM)
26
25
 
27
- include(CheckCXXCompilerFlag)
28
-
29
26
  find_package(CNoke)
30
27
 
28
+ include(CheckCXXCompilerFlag)
29
+
31
30
  set(CMAKE_CXX_STANDARD 20)
32
31
  if(MSVC)
33
32
  set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded)
34
- 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")
35
34
 
36
35
  # ASM_MASM does not (yet) work on Windows ARM64
37
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();
@@ -1997,6 +2046,40 @@ static Napi::Value EncodeValue(const Napi::CallbackInfo &info)
1997
2046
  return env.Undefined();
1998
2047
  }
1999
2048
 
2049
+ static Napi::Value ResetKoffi(const Napi::CallbackInfo &info)
2050
+ {
2051
+ Napi::Env env = info.Env();
2052
+ InstanceData *instance = env.GetInstanceData<InstanceData>();
2053
+
2054
+ if (instance->broker) {
2055
+ napi_release_threadsafe_function(instance->broker, napi_tsfn_abort);
2056
+ instance->broker = nullptr;
2057
+ }
2058
+
2059
+ instance->types.RemoveFrom(instance->base_types_len);
2060
+
2061
+ {
2062
+ HashSet<const void *> base_types;
2063
+ HashMap<const char *, const TypeInfo *> new_map;
2064
+
2065
+ for (const TypeInfo &type: instance->types) {
2066
+ base_types.Set(&type);
2067
+ }
2068
+
2069
+ for (const auto &bucket: instance->types_map.table) {
2070
+ if (base_types.Find(bucket.value)) {
2071
+ new_map.Set(bucket.key, bucket.value);
2072
+ }
2073
+ }
2074
+
2075
+ std::swap(instance->types_map, new_map);
2076
+ }
2077
+
2078
+ instance->callbacks.Clear();
2079
+
2080
+ return env.Undefined();
2081
+ }
2082
+
2000
2083
  void LibraryHolder::Unload()
2001
2084
  {
2002
2085
  #ifdef _WIN32
@@ -2153,83 +2236,6 @@ static inline PrimitiveKind GetBigEndianPrimitive(PrimitiveKind kind)
2153
2236
  #endif
2154
2237
  }
2155
2238
 
2156
- static Napi::Object InitBaseTypes(Napi::Env env)
2157
- {
2158
- InstanceData *instance = env.GetInstanceData<InstanceData>();
2159
-
2160
- Napi::Object types = Napi::Object::New(env);
2161
-
2162
- RegisterPrimitiveType(env, types, {"void"}, PrimitiveKind::Void, 0, 0);
2163
- RegisterPrimitiveType(env, types, {"bool"}, PrimitiveKind::Bool, RG_SIZE(bool), alignof(bool));
2164
- RegisterPrimitiveType(env, types, {"int8_t", "int8"}, PrimitiveKind::Int8, 1, 1);
2165
- RegisterPrimitiveType(env, types, {"uint8_t", "uint8"}, PrimitiveKind::UInt8, 1, 1);
2166
- RegisterPrimitiveType(env, types, {"char"}, PrimitiveKind::Int8, 1, 1);
2167
- RegisterPrimitiveType(env, types, {"unsigned char", "uchar"}, PrimitiveKind::UInt8, 1, 1);
2168
- RegisterPrimitiveType(env, types, {"char16_t", "char16"}, PrimitiveKind::Int16, 2, 2);
2169
- RegisterPrimitiveType(env, types, {"int16_t", "int16"}, PrimitiveKind::Int16, 2, 2);
2170
- RegisterPrimitiveType(env, types, {"int16_le_t", "int16_le"}, GetLittleEndianPrimitive(PrimitiveKind::Int16), 2, 2);
2171
- RegisterPrimitiveType(env, types, {"int16_be_t", "int16_be"}, GetBigEndianPrimitive(PrimitiveKind::Int16), 2, 2);
2172
- RegisterPrimitiveType(env, types, {"uint16_t", "uint16"}, PrimitiveKind::UInt16, 2, 2);
2173
- RegisterPrimitiveType(env, types, {"uint16_le_t", "uint16_le"}, GetLittleEndianPrimitive(PrimitiveKind::UInt16), 2, 2);
2174
- RegisterPrimitiveType(env, types, {"uint16_be_t", "uint16_be"}, GetBigEndianPrimitive(PrimitiveKind::UInt16), 2, 2);
2175
- RegisterPrimitiveType(env, types, {"short"}, PrimitiveKind::Int16, 2, 2);
2176
- RegisterPrimitiveType(env, types, {"unsigned short", "ushort"}, PrimitiveKind::UInt16, 2, 2);
2177
- RegisterPrimitiveType(env, types, {"int32_t", "int32"}, PrimitiveKind::Int32, 4, 4);
2178
- RegisterPrimitiveType(env, types, {"int32_le_t", "int32_le"}, GetLittleEndianPrimitive(PrimitiveKind::Int32), 4, 4);
2179
- RegisterPrimitiveType(env, types, {"int32_be_t", "int32_be"}, GetBigEndianPrimitive(PrimitiveKind::Int32), 4, 4);
2180
- RegisterPrimitiveType(env, types, {"uint32_t", "uint32"}, PrimitiveKind::UInt32, 4, 4);
2181
- RegisterPrimitiveType(env, types, {"uint32_le_t", "uint32_le"}, GetLittleEndianPrimitive(PrimitiveKind::UInt32), 4, 4);
2182
- RegisterPrimitiveType(env, types, {"uint32_be_t", "uint32_be"}, GetBigEndianPrimitive(PrimitiveKind::UInt32), 4, 4);
2183
- RegisterPrimitiveType(env, types, {"int"}, PrimitiveKind::Int32, 4, 4);
2184
- RegisterPrimitiveType(env, types, {"unsigned int", "uint"}, PrimitiveKind::UInt32, 4, 4);
2185
- RegisterPrimitiveType(env, types, {"int64_t", "int64"}, PrimitiveKind::Int64, 8, alignof(int64_t));
2186
- RegisterPrimitiveType(env, types, {"int64_le_t", "int64_le"}, GetLittleEndianPrimitive(PrimitiveKind::Int64), 8, alignof(int64_t));
2187
- RegisterPrimitiveType(env, types, {"int64_be_t", "int64_be"}, GetBigEndianPrimitive(PrimitiveKind::Int64), 8, alignof(int64_t));
2188
- RegisterPrimitiveType(env, types, {"uint64_t", "uint64"}, PrimitiveKind::UInt64, 8, alignof(int64_t));
2189
- RegisterPrimitiveType(env, types, {"uint64_le_t", "uint64_le"}, GetLittleEndianPrimitive(PrimitiveKind::UInt64), 8, alignof(int64_t));
2190
- RegisterPrimitiveType(env, types, {"uint64_be_t", "uint64_be"}, GetBigEndianPrimitive(PrimitiveKind::UInt64), 8, alignof(int64_t));
2191
- RegisterPrimitiveType(env, types, {"intptr_t", "intptr"}, GetSignPrimitive(RG_SIZE(intptr_t), true), RG_SIZE(intptr_t), alignof(intptr_t));
2192
- RegisterPrimitiveType(env, types, {"uintptr_t", "uintptr"}, GetSignPrimitive(RG_SIZE(intptr_t), false), RG_SIZE(intptr_t), alignof(intptr_t));
2193
- RegisterPrimitiveType(env, types, {"size_t"}, GetSignPrimitive(RG_SIZE(size_t), false), RG_SIZE(size_t), alignof(size_t));
2194
- RegisterPrimitiveType(env, types, {"long"}, GetSignPrimitive(RG_SIZE(long), true), RG_SIZE(long), alignof(long));
2195
- RegisterPrimitiveType(env, types, {"unsigned long", "ulong"}, GetSignPrimitive(RG_SIZE(long), false), RG_SIZE(long), alignof(long));
2196
- RegisterPrimitiveType(env, types, {"long long", "longlong"}, PrimitiveKind::Int64, RG_SIZE(int64_t), alignof(int64_t));
2197
- RegisterPrimitiveType(env, types, {"unsigned long long", "ulonglong"}, PrimitiveKind::UInt64, RG_SIZE(uint64_t), alignof(uint64_t));
2198
- RegisterPrimitiveType(env, types, {"float", "float32"}, PrimitiveKind::Float32, 4, alignof(float));
2199
- RegisterPrimitiveType(env, types, {"double", "float64"}, PrimitiveKind::Float64, 8, alignof(double));
2200
- RegisterPrimitiveType(env, types, {"char *", "str", "string"}, PrimitiveKind::String, RG_SIZE(void *), alignof(void *), "char");
2201
- RegisterPrimitiveType(env, types, {"char16_t *", "char16 *", "str16", "string16"}, PrimitiveKind::String16, RG_SIZE(void *), alignof(void *), "char16_t");
2202
-
2203
- instance->void_type = instance->types_map.FindValue("void", nullptr);
2204
- instance->char_type = instance->types_map.FindValue("char", nullptr);
2205
- instance->char16_type = instance->types_map.FindValue("char16", nullptr);
2206
-
2207
- instance->active_symbol = Napi::Symbol::New(env, "active");
2208
-
2209
- types.Freeze();
2210
-
2211
- return types;
2212
- }
2213
-
2214
- static Napi::Value ResetKoffi(const Napi::CallbackInfo &info)
2215
- {
2216
- Napi::Env env = info.Env();
2217
- InstanceData *instance = env.GetInstanceData<InstanceData>();
2218
-
2219
- if (instance->broker) {
2220
- napi_release_threadsafe_function(instance->broker, napi_tsfn_abort);
2221
- instance->broker = nullptr;
2222
- }
2223
-
2224
- instance->types.Clear();
2225
- instance->types_map.Clear();
2226
- instance->callbacks.Clear();
2227
-
2228
- InitBaseTypes(env);
2229
-
2230
- return env.Undefined();
2231
- }
2232
-
2233
2239
  static InstanceData *CreateInstance()
2234
2240
  {
2235
2241
  InstanceData *instance = new InstanceData();
@@ -2258,52 +2264,53 @@ static Napi::Object InitModule(Napi::Env env, Napi::Object exports)
2258
2264
 
2259
2265
  env.SetInstanceData(instance);
2260
2266
 
2261
- exports.Set("config", Napi::Function::New(env, GetSetConfig));
2262
- exports.Set("stats", Napi::Function::New(env, GetStats));
2263
-
2264
- exports.Set("struct", Napi::Function::New(env, CreatePaddedStructType));
2265
- exports.Set("pack", Napi::Function::New(env, CreatePackedStructType));
2266
- exports.Set("union", Napi::Function::New(env, CreateUnionType));
2267
- exports.Set("Union", Napi::Function::New(env, InstantiateUnion));
2268
- exports.Set("opaque", Napi::Function::New(env, CreateOpaqueType));
2269
- exports.Set("pointer", Napi::Function::New(env, CreatePointerType));
2270
- exports.Set("array", Napi::Function::New(env, CreateArrayType));
2271
- exports.Set("proto", Napi::Function::New(env, CreateFunctionType));
2272
- exports.Set("alias", Napi::Function::New(env, CreateTypeAlias));
2267
+ exports.Set("config", Napi::Function::New(env, GetSetConfig, "config"));
2268
+ exports.Set("stats", Napi::Function::New(env, GetStats, "stats"));
2273
2269
 
2274
- exports.Set("sizeof", Napi::Function::New(env, GetTypeSize));
2275
- exports.Set("alignof", Napi::Function::New(env, GetTypeAlign));
2276
- exports.Set("offsetof", Napi::Function::New(env, GetMemberOffset));
2277
- exports.Set("resolve", Napi::Function::New(env, GetResolvedType));
2278
- exports.Set("introspect", Napi::Function::New(env, GetTypeDefinition));
2270
+ exports.Set("struct", Napi::Function::New(env, CreatePaddedStructType, "struct"));
2271
+ exports.Set("pack", Napi::Function::New(env, CreatePackedStructType, "pack"));
2272
+ exports.Set("union", Napi::Function::New(env, CreateUnionType, "union"));
2273
+ exports.Set("Union", Napi::Function::New(env, InstantiateUnion, "Union"));
2274
+ exports.Set("opaque", Napi::Function::New(env, CreateOpaqueType, "opaque"));
2275
+ exports.Set("pointer", Napi::Function::New(env, CreatePointerType, "pointer"));
2276
+ exports.Set("array", Napi::Function::New(env, CreateArrayType, "array"));
2277
+ exports.Set("proto", Napi::Function::New(env, CreateFunctionType, "proto"));
2278
+ exports.Set("alias", Napi::Function::New(env, CreateTypeAlias, "alias"));
2279
2279
 
2280
- exports.Set("load", Napi::Function::New(env, LoadSharedLibrary));
2280
+ exports.Set("sizeof", Napi::Function::New(env, GetTypeSize, "sizeof"));
2281
+ exports.Set("alignof", Napi::Function::New(env, GetTypeAlign, "alignof"));
2282
+ exports.Set("offsetof", Napi::Function::New(env, GetMemberOffset, "offsetof"));
2283
+ exports.Set("resolve", Napi::Function::New(env, GetResolvedType, "resolve"));
2284
+ exports.Set("introspect", Napi::Function::New(env, GetTypeDefinition, "introspect"));
2281
2285
 
2282
- exports.Set("in", Napi::Function::New(env, MarkIn));
2283
- exports.Set("out", Napi::Function::New(env, MarkOut));
2284
- exports.Set("inout", Napi::Function::New(env, MarkInOut));
2286
+ exports.Set("load", Napi::Function::New(env, LoadSharedLibrary, "load"));
2285
2287
 
2286
- exports.Set("disposable", Napi::Function::New(env, CreateDisposableType));
2287
- exports.Set("free", Napi::Function::New(env, CallFree));
2288
+ exports.Set("in", Napi::Function::New(env, MarkIn, "in"));
2289
+ exports.Set("out", Napi::Function::New(env, MarkOut, "out"));
2290
+ exports.Set("inout", Napi::Function::New(env, MarkInOut, "inout"));
2288
2291
 
2289
- exports.Set("register", Napi::Function::New(env, RegisterCallback));
2290
- exports.Set("unregister", Napi::Function::New(env, UnregisterCallback));
2292
+ exports.Set("disposable", Napi::Function::New(env, CreateDisposableType, "disposable"));
2293
+ exports.Set("alloc", Napi::Function::New(env, CallAlloc, "alloc"));
2294
+ exports.Set("free", Napi::Function::New(env, CallFree, "free"));
2291
2295
 
2292
- exports.Set("as", Napi::Function::New(env, CastValue));
2293
- exports.Set("decode", Napi::Function::New(env, DecodeValue));
2294
- exports.Set("address", Napi::Function::New(env, GetPointerAddress));
2295
- exports.Set("call", Napi::Function::New(env, CallPointerSync));
2296
- exports.Set("encode", Napi::Function::New(env, EncodeValue));
2296
+ exports.Set("register", Napi::Function::New(env, RegisterCallback, "register"));
2297
+ exports.Set("unregister", Napi::Function::New(env, UnregisterCallback, "unregister"));
2297
2298
 
2298
- exports.Set("reset", Napi::Function::New(env, ResetKoffi));
2299
+ exports.Set("as", Napi::Function::New(env, CastValue, "as"));
2300
+ exports.Set("decode", Napi::Function::New(env, DecodeValue, "decode"));
2301
+ exports.Set("address", Napi::Function::New(env, GetPointerAddress, "address"));
2302
+ exports.Set("call", Napi::Function::New(env, CallPointerSync, "call"));
2303
+ exports.Set("encode", Napi::Function::New(env, EncodeValue, "encode"));
2299
2304
 
2300
- exports.Set("errno", Napi::Function::New(env, GetOrSetErrNo));
2305
+ exports.Set("reset", Napi::Function::New(env, ResetKoffi, "reset"));
2301
2306
 
2302
- Napi::Object os = Napi::Object::New(env);
2303
- exports.Set("os", os);
2307
+ exports.Set("errno", Napi::Function::New(env, GetOrSetErrNo, "errno"));
2304
2308
 
2305
- // Init constants mapping
2309
+ // Export useful OS info
2306
2310
  {
2311
+ Napi::Object os = Napi::Object::New(env);
2312
+ exports.Set("os", os);
2313
+
2307
2314
  Napi::Object codes = Napi::Object::New(env);
2308
2315
 
2309
2316
  for (const ErrnoCodeInfo &info: ErrnoCodes) {
@@ -2321,8 +2328,73 @@ static Napi::Object InitModule(Napi::Env env, Napi::Object exports)
2321
2328
  exports.Set("extension", Napi::String::New(env, ".so"));
2322
2329
  #endif
2323
2330
 
2324
- Napi::Object types = InitBaseTypes(env);
2325
- exports.Set("types", types);
2331
+ // Init base types
2332
+ {
2333
+ Napi::Object types = Napi::Object::New(env);
2334
+ exports.Set("types", types);
2335
+
2336
+ RegisterPrimitiveType(env, types, {"void"}, PrimitiveKind::Void, 0, 0);
2337
+ RegisterPrimitiveType(env, types, {"bool"}, PrimitiveKind::Bool, RG_SIZE(bool), alignof(bool));
2338
+ RegisterPrimitiveType(env, types, {"int8_t", "int8"}, PrimitiveKind::Int8, 1, 1);
2339
+ RegisterPrimitiveType(env, types, {"uint8_t", "uint8"}, PrimitiveKind::UInt8, 1, 1);
2340
+ RegisterPrimitiveType(env, types, {"char"}, PrimitiveKind::Int8, 1, 1);
2341
+ RegisterPrimitiveType(env, types, {"unsigned char", "uchar"}, PrimitiveKind::UInt8, 1, 1);
2342
+ RegisterPrimitiveType(env, types, {"char16_t", "char16"}, PrimitiveKind::Int16, 2, 2);
2343
+ RegisterPrimitiveType(env, types, {"int16_t", "int16"}, PrimitiveKind::Int16, 2, 2);
2344
+ RegisterPrimitiveType(env, types, {"int16_le_t", "int16_le"}, GetLittleEndianPrimitive(PrimitiveKind::Int16), 2, 2);
2345
+ RegisterPrimitiveType(env, types, {"int16_be_t", "int16_be"}, GetBigEndianPrimitive(PrimitiveKind::Int16), 2, 2);
2346
+ RegisterPrimitiveType(env, types, {"uint16_t", "uint16"}, PrimitiveKind::UInt16, 2, 2);
2347
+ RegisterPrimitiveType(env, types, {"uint16_le_t", "uint16_le"}, GetLittleEndianPrimitive(PrimitiveKind::UInt16), 2, 2);
2348
+ RegisterPrimitiveType(env, types, {"uint16_be_t", "uint16_be"}, GetBigEndianPrimitive(PrimitiveKind::UInt16), 2, 2);
2349
+ RegisterPrimitiveType(env, types, {"short"}, PrimitiveKind::Int16, 2, 2);
2350
+ RegisterPrimitiveType(env, types, {"unsigned short", "ushort"}, PrimitiveKind::UInt16, 2, 2);
2351
+ RegisterPrimitiveType(env, types, {"int32_t", "int32"}, PrimitiveKind::Int32, 4, 4);
2352
+ RegisterPrimitiveType(env, types, {"int32_le_t", "int32_le"}, GetLittleEndianPrimitive(PrimitiveKind::Int32), 4, 4);
2353
+ RegisterPrimitiveType(env, types, {"int32_be_t", "int32_be"}, GetBigEndianPrimitive(PrimitiveKind::Int32), 4, 4);
2354
+ RegisterPrimitiveType(env, types, {"uint32_t", "uint32"}, PrimitiveKind::UInt32, 4, 4);
2355
+ RegisterPrimitiveType(env, types, {"uint32_le_t", "uint32_le"}, GetLittleEndianPrimitive(PrimitiveKind::UInt32), 4, 4);
2356
+ RegisterPrimitiveType(env, types, {"uint32_be_t", "uint32_be"}, GetBigEndianPrimitive(PrimitiveKind::UInt32), 4, 4);
2357
+ RegisterPrimitiveType(env, types, {"int"}, PrimitiveKind::Int32, 4, 4);
2358
+ RegisterPrimitiveType(env, types, {"unsigned int", "uint"}, PrimitiveKind::UInt32, 4, 4);
2359
+ RegisterPrimitiveType(env, types, {"int64_t", "int64"}, PrimitiveKind::Int64, 8, alignof(int64_t));
2360
+ RegisterPrimitiveType(env, types, {"int64_le_t", "int64_le"}, GetLittleEndianPrimitive(PrimitiveKind::Int64), 8, alignof(int64_t));
2361
+ RegisterPrimitiveType(env, types, {"int64_be_t", "int64_be"}, GetBigEndianPrimitive(PrimitiveKind::Int64), 8, alignof(int64_t));
2362
+ RegisterPrimitiveType(env, types, {"uint64_t", "uint64"}, PrimitiveKind::UInt64, 8, alignof(int64_t));
2363
+ RegisterPrimitiveType(env, types, {"uint64_le_t", "uint64_le"}, GetLittleEndianPrimitive(PrimitiveKind::UInt64), 8, alignof(int64_t));
2364
+ RegisterPrimitiveType(env, types, {"uint64_be_t", "uint64_be"}, GetBigEndianPrimitive(PrimitiveKind::UInt64), 8, alignof(int64_t));
2365
+ RegisterPrimitiveType(env, types, {"intptr_t", "intptr"}, GetSignPrimitive(RG_SIZE(intptr_t), true), RG_SIZE(intptr_t), alignof(intptr_t));
2366
+ RegisterPrimitiveType(env, types, {"uintptr_t", "uintptr"}, GetSignPrimitive(RG_SIZE(intptr_t), false), RG_SIZE(intptr_t), alignof(intptr_t));
2367
+ RegisterPrimitiveType(env, types, {"size_t"}, GetSignPrimitive(RG_SIZE(size_t), false), RG_SIZE(size_t), alignof(size_t));
2368
+ RegisterPrimitiveType(env, types, {"long"}, GetSignPrimitive(RG_SIZE(long), true), RG_SIZE(long), alignof(long));
2369
+ RegisterPrimitiveType(env, types, {"unsigned long", "ulong"}, GetSignPrimitive(RG_SIZE(long), false), RG_SIZE(long), alignof(long));
2370
+ RegisterPrimitiveType(env, types, {"long long", "longlong"}, PrimitiveKind::Int64, RG_SIZE(int64_t), alignof(int64_t));
2371
+ RegisterPrimitiveType(env, types, {"unsigned long long", "ulonglong"}, PrimitiveKind::UInt64, RG_SIZE(uint64_t), alignof(uint64_t));
2372
+ RegisterPrimitiveType(env, types, {"float", "float32"}, PrimitiveKind::Float32, 4, alignof(float));
2373
+ RegisterPrimitiveType(env, types, {"double", "float64"}, PrimitiveKind::Float64, 8, alignof(double));
2374
+ RegisterPrimitiveType(env, types, {"char *", "str", "string"}, PrimitiveKind::String, RG_SIZE(void *), alignof(void *), "char");
2375
+ RegisterPrimitiveType(env, types, {"char16_t *", "char16 *", "str16", "string16"}, PrimitiveKind::String16, RG_SIZE(void *), alignof(void *), "char16_t");
2376
+
2377
+ instance->void_type = instance->types_map.FindValue("void", nullptr);
2378
+ instance->char_type = instance->types_map.FindValue("char", nullptr);
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);
2382
+
2383
+ instance->active_symbol = Napi::Symbol::New(env, "active");
2384
+
2385
+ instance->base_types_len = instance->types.len;
2386
+ }
2387
+
2388
+ // Expose internal Node stuff
2389
+ {
2390
+ Napi::Object node = Napi::Object::New(env);
2391
+ exports.Set("node", node);
2392
+
2393
+ Napi::External<void> external = Napi::External<void>::New(env, (napi_env)env);
2394
+ SetValueTag(instance, external, instance->void_type);
2395
+
2396
+ node.Set("env", external);
2397
+ }
2326
2398
 
2327
2399
  exports.Set("version", Napi::String::New(env, RG_STRINGIFY(VERSION)));
2328
2400
 
@@ -269,6 +269,7 @@ struct InstanceData {
269
269
  BucketArray<TypeInfo> types;
270
270
  HashMap<const char *, const TypeInfo *> types_map;
271
271
  BucketArray<FunctionInfo> callbacks;
272
+ Size base_types_len;
272
273
 
273
274
  bool debug;
274
275
  uint64_t tag_lower;
@@ -276,6 +277,8 @@ struct InstanceData {
276
277
  const TypeInfo *void_type;
277
278
  const TypeInfo *char_type;
278
279
  const TypeInfo *char16_type;
280
+ const TypeInfo *str_type;
281
+ const TypeInfo *str16_type;
279
282
 
280
283
  Napi::Symbol active_symbol;
281
284
 
@@ -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.