koffi 2.5.21-beta.4 → 2.6.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,26 @@
2
2
 
3
3
  ## Version history
4
4
 
5
+ ### Koffi 2.6
6
+
7
+ #### Koffi 2.6.1 (2023-09-18)
8
+
9
+ - Support string direction qualifiers in classic function definitions
10
+ - Fix possible off-by-one array access when parsing type name
11
+
12
+ #### Koffi 2.6.0 (2023-09-13)
13
+
14
+ **New features:**
15
+
16
+ - Use [koffi.symbol()](variables.md#variable-definitions) to make pointers to exported variables (or other symbols)
17
+ - Use [koffi.encode()](variables.md#encode-to-c-memory) to explictly encode data from JS to C memory
18
+ - Use shared library [lazy-loading](functions.md#loading-options) (RTLD_LAZY) on POSIX platforms
19
+
20
+ **Other changes:**
21
+
22
+ - Print more helpful error for Win32/Win64 DLL mismatches
23
+ - Add missing 'Union' primitive value in TS definition file
24
+
5
25
  ### Koffi 2.5
6
26
 
7
27
  #### Koffi 2.5.20 (2023-08-31)
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/callbacks.md CHANGED
@@ -153,14 +153,9 @@ let cb2 = koffi.register(store, store.get, 'IntCallback *'); // However in this
153
153
 
154
154
  Koffi does not have enough information to convert callback pointer arguments to an appropriate JS value. In this case, your JS function will receive an opaque *External* object.
155
155
 
156
- You can pass this value through to another C function that expects a pointer of the same type, or you can use `koffi.decode()` to decode it into something you can use in Javascript.
156
+ You can pass this value through to another C function that expects a pointer of the same type, or you can use [koffi.decode()](variables.md#decode-to-js-values) function to decode pointer arguments.
157
157
 
158
- Some arguments are optional and this function can be called in several ways:
159
-
160
- - `koffi.decode(value, type)`: no offset, expect NUL-terminated strings
161
- - `koffi.decode(value, offset, type)`: explicit offset to add to the pointer before decoding
162
-
163
- The following example sorts an array of strings (in-place) with `qsort()`:
158
+ The following examples uses it to sort an array of strings in-place with the standard C function `qsort()`:
164
159
 
165
160
  ```js
166
161
  // ES6 syntax: import koffi from 'koffi';
@@ -183,15 +178,6 @@ qsort(koffi.as(array, 'char **'), array.length, koffi.sizeof('void *'), (ptr1, p
183
178
  console.log(array); // Prints ['123', 'bar', 'foo', 'foobar']
184
179
  ```
185
180
 
186
- There is also an optional ending `length` argument that you can use in two cases:
187
-
188
- - Use it to give the number of bytes to decode in non-NUL terminated strings: `koffi.decode(value, 'char *', 5)`
189
- - Decode consecutive values into an array. For example, here is how you can decode an array with 3 float values: `koffi.decode(value, 'float', 3)`. This is equivalent to `koffi.decode(value, koffi.array('float', 3))`.
190
-
191
- ```{note}
192
- In Koffi 2.2 and earlier versions, the length argument is only used to decode strings and is ignored otherwise.
193
- ```
194
-
195
181
  ### Asynchronous callbacks
196
182
 
197
183
  *New in Koffi 2.2.2*
package/doc/functions.md CHANGED
@@ -19,6 +19,22 @@ Starting with *Koffi 2.3.20*, you can explicitly unload a library by calling `li
19
19
  On some platforms (such as with the [musl C library on Linux](https://wiki.musl-libc.org/functional-differences-from-glibc.html#Unloading-libraries)), shared libraries cannot be unloaded, so the library will remain loaded and memory mapped after the call to `lib.unload()`.
20
20
  ```
21
21
 
22
+ ## Loading options
23
+
24
+ *New in Koffi 2.6*
25
+
26
+ The `load` function can take an optional object argument, with the following options:
27
+
28
+ ```js
29
+ const options = {
30
+ lazy: true // Use RTLD_LAZY (lazy-binding) on POSIX platforms (by default, use RTLD_NOW)
31
+ };
32
+
33
+ const lib = koffi.load('/path/to/shared/library.so', options);
34
+ ```
35
+
36
+ More options may be added if needed.
37
+
22
38
  ## Function definitions
23
39
 
24
40
  ### Definition syntax
package/doc/index.rst CHANGED
@@ -31,6 +31,7 @@ Table of contents
31
31
  output
32
32
  polymorphism
33
33
  unions
34
+ variables
34
35
  callbacks
35
36
  misc
36
37
  packaging
@@ -105,4 +105,4 @@ console.log(vec1); // { x: 3, y: 2, z: 1 }
105
105
  console.log(vec2); // { x: 1, y: 2, z: 3 }
106
106
  ```
107
107
 
108
- See [pointer arguments](callbacks.md#decoding-pointer-arguments) for another example with the decode function.
108
+ See [decoding variables](variables.md#decode-to-js-values) for more information about the decode function.
@@ -0,0 +1,100 @@
1
+ # Exported variables
2
+
3
+ ## Variable definitions
4
+
5
+ *New in Koffi 2.6*
6
+
7
+ To find an exported and declare a variable, use `lib.symbol(name, type)`. You need to specify its name and its type.
8
+
9
+ ```c
10
+ int my_int = 42;
11
+ const char *my_string = NULL;
12
+ ```
13
+
14
+ ```js
15
+ const my_int = lib.symbol('my_int', 'int');
16
+ const my_string = lib.symbol('my_string', 'const char *');
17
+ ```
18
+
19
+ You cannot directly manipulate these variables, use:
20
+
21
+ - [koffi.decode()](#decode-to-js-values) to read their value
22
+ - [koffi.encode()](#encode-to-c-memory) to change their valuèe
23
+
24
+ ## Decode to JS values
25
+
26
+ *New in Koffi 2.2, changed in Koffi 2.3*
27
+
28
+ Use `koffi.decode()` to decode C pointers, wrapped as external objects or as simple numbers.
29
+
30
+ Some arguments are optional and this function can be called in several ways:
31
+
32
+ - `koffi.decode(value, type)`: no offset
33
+ - `koffi.decode(value, offset, type)`: explicit offset to add to the pointer before decoding
34
+
35
+ By default, Koffi expects NUL terminated strings when decoding them. See below if you need to specify the string length.
36
+
37
+ The following example illustrates how to decode an integer and a C string variable.
38
+
39
+ ```c
40
+ int my_int = 42;
41
+ const char *my_string = "foobar";
42
+ ```
43
+
44
+ ```js
45
+ const my_int = lib.symbol('my_int', 'int');
46
+ const my_string = lib.symbol('my_string', 'const char *');
47
+
48
+ console.log(koffi.decode(my_int, 'int')) // Prints 42
49
+ console.log(koffi.decode(my_string, 'const char *')) // Prints "foobar"
50
+ ```
51
+
52
+ There is also an optional ending `length` argument that you can use in two cases:
53
+
54
+ - Use it to give the number of bytes to decode in non-NUL terminated strings: `koffi.decode(value, 'char *', 5)`
55
+ - Decode consecutive values into an array. For example, here is how you can decode an array with 3 float values: `koffi.decode(value, 'float', 3)`. This is equivalent to `koffi.decode(value, koffi.array('float', 3))`.
56
+
57
+ Thge example below will decode the symbol `my_string` defined above but only the first three bytes.
58
+
59
+ ```js
60
+ // Only decode 3 bytes from the C string my_string
61
+ console.log(koffi.decode(my_string, 'const char *', 3)) // Prints "foo"
62
+ ```
63
+
64
+ ```{note}
65
+ In Koffi 2.2 and earlier versions, the length argument is only used to decode strings and is ignored otherwise.
66
+ ```
67
+
68
+ ## Encode to C memory
69
+
70
+ *New in Koffi 2.6*
71
+
72
+ Use `koffi.encode()` to encode C pointers, wrapped as external objects or as simple numbers.
73
+
74
+ Some arguments are optional and this function can be called in several ways:
75
+
76
+ - `koffi.encode(ref, type, value)`: no offset
77
+ - `koffi.encode(ref, offset, type, value)`: explicit offset to add to the pointer before encoding
78
+
79
+ We'll reuse the examples shown above and change the variable values with `koffi.encode()`.
80
+
81
+ ```c
82
+ int my_int = 42;
83
+ const char *my_string = NULL;
84
+ ```
85
+
86
+ ```js
87
+ const my_int = lib.symbol('my_int', 'int');
88
+ const my_string = lib.symbol('my_string', 'const char *');
89
+
90
+ console.log(koffi.decode(my_int, 'int')) // Prints 42
91
+ console.log(koffi.decode(my_string, 'const char *')) // Prints null
92
+
93
+ koffi.encode(my_int, 'int', -1);
94
+ koffi.encode(my_string, 'const char *', 'Hello World!');
95
+
96
+ console.log(koffi.decode(my_int, 'int')) // Prints -1
97
+ console.log(koffi.decode(my_string, 'const char *')) // Prints "Hello World!"
98
+ ```
99
+
100
+ When encoding strings (either directly or embedded in arrays or structs), the memory will be bound to the raw pointer value and managed by Koffi. You can assign to the same string again and again without any leak or risk of use-after-free.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koffi",
3
- "version": "2.5.21-beta.4",
4
- "stable": "2.5.20",
3
+ "version": "2.6.1",
4
+ "stable": "2.6.1",
5
5
  "description": "Fast and simple C FFI (foreign function interface) for Node.js",
6
6
  "keywords": [
7
7
  "foreign",
package/src/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.5.21-beta.4",
382
- stable: "2.5.20",
381
+ version: "2.6.1",
382
+ stable: "2.6.1",
383
383
  description: "Fast and simple C FFI (foreign function interface) for Node.js",
384
384
  keywords: [
385
385
  "foreign",
@@ -35,7 +35,7 @@ bool PrototypeParser::Parse(const char *str, FunctionInfo *out_func)
35
35
 
36
36
  Tokenize(str);
37
37
 
38
- out_func->ret.type = ParseType();
38
+ out_func->ret.type = ParseType(nullptr);
39
39
  if (!CanReturnType(out_func->ret.type)) {
40
40
  MarkError("You are not allowed to directly return %1 values (maybe try %1 *)", out_func->ret.type->name);
41
41
  return false;
@@ -62,17 +62,7 @@ bool PrototypeParser::Parse(const char *str, FunctionInfo *out_func)
62
62
  break;
63
63
  }
64
64
 
65
- if (Match("_In_")) {
66
- param.directions = 1;
67
- } else if (Match("_Out_")) {
68
- param.directions = 2;
69
- } else if (Match("_Inout_")) {
70
- param.directions = 3;
71
- } else {
72
- param.directions = 1;
73
- }
74
-
75
- param.type = ParseType();
65
+ param.type = ParseType(&param.directions);
76
66
 
77
67
  if (!CanPassType(param.type, param.directions)) {
78
68
  MarkError("Type %1 cannot be used as a parameter", param.type->name);
@@ -145,7 +135,7 @@ void PrototypeParser::Tokenize(const char *str)
145
135
  }
146
136
  }
147
137
 
148
- const TypeInfo *PrototypeParser::ParseType()
138
+ const TypeInfo *PrototypeParser::ParseType(int *out_directions)
149
139
  {
150
140
  Size start = offset;
151
141
 
@@ -166,7 +156,7 @@ const TypeInfo *PrototypeParser::ParseType()
166
156
 
167
157
  while (offset >= start) {
168
158
  Span<const char> str = MakeSpan(tokens[start].ptr, tokens[offset].end() - tokens[start].ptr);
169
- const TypeInfo *type = ResolveType(env, str);
159
+ const TypeInfo *type = ResolveType(env, str, out_directions);
170
160
 
171
161
  if (type) {
172
162
  offset++;
@@ -49,7 +49,7 @@ public:
49
49
  private:
50
50
  void Tokenize(const char *str);
51
51
 
52
- const TypeInfo *ParseType();
52
+ const TypeInfo *ParseType(int *out_directions);
53
53
  const char *ParseIdentifier();
54
54
 
55
55
  bool Consume(const char *expect);
@@ -153,6 +153,22 @@ static inline bool IsIdentifierChar(char c)
153
153
  return IsAsciiAlphaOrDigit(c) || c == '_';
154
154
  }
155
155
 
156
+ static inline Span<const char> SplitIdentifier(Span<const char> str)
157
+ {
158
+ Size offset = 0;
159
+
160
+ if (str.len && IsIdentifierStart(str[0])) {
161
+ offset++;
162
+
163
+ while (offset < str.len && IsIdentifierChar(str[offset])) {
164
+ offset++;
165
+ }
166
+ }
167
+
168
+ Span<const char> token = str.Take(0, offset);
169
+ return token;
170
+ }
171
+
156
172
  const TypeInfo *ResolveType(Napi::Env env, Span<const char> str, int *out_directions)
157
173
  {
158
174
  InstanceData *instance = env.GetInstanceData<InstanceData>();
@@ -160,8 +176,31 @@ const TypeInfo *ResolveType(Napi::Env env, Span<const char> str, int *out_direct
160
176
  int indirect = 0;
161
177
  uint8_t disposables = 0;
162
178
 
179
+ // Consume parameter direction qualifier
180
+ if (out_directions) {
181
+ if (str.len && str[0] == '_') {
182
+ Span<const char> qualifier = SplitIdentifier(str);
183
+
184
+ if (qualifier == "_In_") {
185
+ *out_directions = 1;
186
+ str = str.Take(5, str.len - 5);
187
+ } else if (qualifier == "_Out_") {
188
+ *out_directions = 2;
189
+ str = str.Take(6, str.len - 6);
190
+ } else if (qualifier == "_Inout_") {
191
+ *out_directions = 3;
192
+ str = str.Take(8, str.len - 8);
193
+ } else {
194
+ *out_directions = 1;
195
+ }
196
+ } else {
197
+ *out_directions = 1;
198
+ }
199
+ }
200
+
163
201
  // Skip initial const qualifiers
164
- while (str.len >= 6 && StartsWith(str, "const") && IsAsciiWhite(str[5])) {
202
+ str = TrimStr(str);
203
+ while (SplitIdentifier(str) == "const") {
165
204
  str = str.Take(6, str.len - 6);
166
205
  str = TrimStr(str);
167
206
  }
@@ -173,13 +212,10 @@ const TypeInfo *ResolveType(Napi::Env env, Span<const char> str, int *out_direct
173
212
  for (;;) {
174
213
  remain = TrimStr(remain);
175
214
 
176
- if (!remain.len || !IsIdentifierStart(remain[0]))
215
+ Span<const char> token = SplitIdentifier(remain);
216
+ if (!token.len)
177
217
  break;
178
-
179
- do {
180
- remain.ptr++;
181
- remain.len--;
182
- } while (remain.len && IsIdentifierChar(remain[0]));
218
+ remain = remain.Take(token.len, remain.len - token.len);
183
219
  }
184
220
 
185
221
  Span<const char> name = TrimStr(MakeSpan(str.ptr, remain.ptr - str.ptr));
@@ -197,7 +233,7 @@ const TypeInfo *ResolveType(Napi::Env env, Span<const char> str, int *out_direct
197
233
  } else if (remain[0] == '!') {
198
234
  remain = remain.Take(1, remain.len - 1);
199
235
  disposables |= (1u << indirect);
200
- } else if (remain.len >= 6 && StartsWith(remain, "const") && !IsIdentifierStart(remain[6])) {
236
+ } else if (SplitIdentifier(remain) == "const") {
201
237
  remain = remain.Take(6, remain.len - 6);
202
238
  } else {
203
239
  break;
@@ -263,9 +299,6 @@ const TypeInfo *ResolveType(Napi::Env env, Span<const char> str, int *out_direct
263
299
  RG_ASSERT(type);
264
300
  }
265
301
 
266
- if (out_directions) {
267
- *out_directions = 1;
268
- }
269
302
  return type;
270
303
  }
271
304