koffi 1.2.0-alpha.3 → 1.2.0-alpha.4

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/src/call.hh CHANGED
@@ -70,7 +70,7 @@ public:
70
70
 
71
71
  void Relay(Size idx, uint8_t *own_sp, uint8_t *caller_sp, BackRegisters *out_reg);
72
72
 
73
- void DumpDebug() const;
73
+ void DumpForward() const;
74
74
 
75
75
  private:
76
76
  template <typename T = void>
@@ -80,12 +80,12 @@ private:
80
80
 
81
81
  const char *PushString(const Napi::Value &value);
82
82
  const char16_t *PushString16(const Napi::Value &value);
83
- bool PushObject(const Napi::Object &obj, const TypeInfo *type, uint8_t *dest, int16_t realign = 0);
84
- bool PushArray(const Napi::Value &obj, const TypeInfo *type, uint8_t *dest, int16_t realign = 0);
83
+ bool PushObject(const Napi::Object &obj, const TypeInfo *type, uint8_t *origin, int16_t realign = 0);
84
+ bool PushArray(const Napi::Value &obj, const TypeInfo *type, uint8_t *origin, int16_t realign = 0);
85
85
 
86
- void PopObject(Napi::Object obj, const uint8_t *src, const TypeInfo *type, int16_t realign = 0);
87
- Napi::Object PopObject(const uint8_t *src, const TypeInfo *type, int16_t realign = 0);
88
- Napi::Value PopArray(const uint8_t *src, const TypeInfo *type, int16_t realign = 0);
86
+ void PopObject(Napi::Object obj, const uint8_t *origin, const TypeInfo *type, int16_t realign = 0);
87
+ Napi::Object PopObject(const uint8_t *origin, const TypeInfo *type, int16_t realign = 0);
88
+ Napi::Value PopArray(const uint8_t *origin, const TypeInfo *type, int16_t realign = 0);
89
89
 
90
90
  Size ReserveTrampoline(const FunctionInfo *proto, Napi::Function func);
91
91
  };
package/src/ffi.cc CHANGED
@@ -565,7 +565,7 @@ static Napi::Value TranslateNormalCall(const Napi::CallbackInfo &info)
565
565
  return env.Null();
566
566
 
567
567
  if (instance->debug) {
568
- call.DumpDebug();
568
+ call.DumpForward();
569
569
  }
570
570
  call.Execute();
571
571
 
@@ -633,7 +633,7 @@ static Napi::Value TranslateVariadicCall(const Napi::CallbackInfo &info)
633
633
  return env.Null();
634
634
 
635
635
  if (instance->debug) {
636
- call.DumpDebug();
636
+ call.DumpForward();
637
637
  }
638
638
  call.Execute();
639
639
 
@@ -664,7 +664,7 @@ public:
664
664
 
665
665
  return prepared;
666
666
  }
667
- void DumpDebug() { call.DumpDebug(); }
667
+ void DumpForward() { call.DumpForward(); }
668
668
 
669
669
  void Execute() override;
670
670
  void OnOK() override;
@@ -714,7 +714,7 @@ static Napi::Value TranslateAsyncCall(const Napi::CallbackInfo &info)
714
714
  AsyncCall *async = new AsyncCall(env, instance, func, mem, callback);
715
715
 
716
716
  if (async->Prepare(info) && instance->debug) {
717
- async->DumpDebug();
717
+ async->DumpForward();
718
718
  }
719
719
  async->Queue();
720
720
 
package/src/util.cc CHANGED
@@ -167,4 +167,21 @@ int IsHFA(const TypeInfo *type, int min, int max)
167
167
  return hfa ? count : 0;
168
168
  }
169
169
 
170
+ void DumpMemory(const char *type, Span<const uint8_t> bytes)
171
+ {
172
+ if (bytes.len) {
173
+ PrintLn(stderr, "%1 at 0x%2 (%3):", type, bytes.ptr, FmtMemSize(bytes.len));
174
+
175
+ for (const uint8_t *ptr = bytes.begin(); ptr < bytes.end();) {
176
+ Print(stderr, " [0x%1 %2 %3] ", FmtArg(ptr).Pad0(-16),
177
+ FmtArg((ptr - bytes.begin()) / sizeof(void *)).Pad(-4),
178
+ FmtArg(ptr - bytes.begin()).Pad(-4));
179
+ for (int i = 0; ptr < bytes.end() && i < (int)sizeof(void *); i++, ptr++) {
180
+ Print(stderr, " %1", FmtHex(*ptr).Pad0(-2));
181
+ }
182
+ PrintLn(stderr);
183
+ }
184
+ }
185
+ }
186
+
170
187
  }
package/src/util.hh CHANGED
@@ -107,4 +107,6 @@ int AnalyseFlat(const TypeInfo *type, FunctionRef<void(const TypeInfo *type, int
107
107
 
108
108
  int IsHFA(const TypeInfo *type, int min, int max);
109
109
 
110
+ void DumpMemory(const char *type, Span<const uint8_t> bytes);
111
+
110
112
  }
package/test/async.js ADDED
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env node
2
+
3
+ // This program is free software: you can redistribute it and/or modify
4
+ // it under the terms of the GNU Affero General Public License as published by
5
+ // the Free Software Foundation, either version 3 of the License, or
6
+ // (at your option) any later version.
7
+ //
8
+ // This program is distributed in the hope that it will be useful,
9
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ // GNU Affero General Public License for more details.
12
+ //
13
+ // You should have received a copy of the GNU Affero General Public License
14
+ // along with this program. If not, see https://www.gnu.org/licenses/.
15
+
16
+ const koffi = require('./build/koffi.node');
17
+ const assert = require('assert');
18
+ const path = require('path');
19
+
20
+ const PackedBFG = koffi.pack('PackedBFG', {
21
+ a: 'int8_t',
22
+ b: 'int64_t',
23
+ c: 'char',
24
+ d: 'string',
25
+ e: 'short',
26
+ inner: koffi.pack({
27
+ f: 'float',
28
+ g: 'double'
29
+ })
30
+ });
31
+
32
+ main();
33
+
34
+ async function main() {
35
+ try {
36
+ await test();
37
+ console.log('Success!');
38
+ } catch (err) {
39
+ console.error(err);
40
+ process.exit(1);
41
+ }
42
+ }
43
+
44
+ async function test() {
45
+ const lib_filename = path.dirname(__filename) + '/build/misc' + koffi.extension;
46
+ const lib = koffi.load(lib_filename);
47
+
48
+ const ConcatenateToInt1 = lib.func('ConcatenateToInt1', 'int64_t', Array(12).fill('int8_t'));
49
+ const MakePackedBFG = lib.func('PackedBFG __fastcall MakePackedBFG(int x, double y, _Out_ PackedBFG *p, const char *str)');
50
+
51
+ let promises = [];
52
+
53
+ // Issue several async calls
54
+ for (let i = 0; i < 64; i++) {
55
+ let p = new Promise((resolve, reject) => {
56
+ try {
57
+ ConcatenateToInt1.async(5, 6, 1, 2, 3, 9, 4, 4, 0, 6, 8, 7, (err, res) => {
58
+ assert.equal(err, null);
59
+ assert.equal(res, 561239440687n);
60
+ });
61
+ resolve();
62
+ } catch (err) {
63
+ reject(err);
64
+ }
65
+ });
66
+ promises.push(p);
67
+ }
68
+
69
+ // Make a few synchronous calls while the asynchronous ones are still in the air
70
+ assert.equal(ConcatenateToInt1(5, 6, 1, 2, 3, 9, 4, 4, 0, 6, 8, 7), 561239440687n);
71
+ assert.equal(ConcatenateToInt1(5, 6, 1, 2, 3, 9, 4, 4, 0, 6, 8, 7), 561239440687n);
72
+ assert.equal(ConcatenateToInt1(5, 6, 1, 2, 3, 9, 4, 4, 0, 6, 8, 7), 561239440687n);
73
+
74
+ // Async complex call
75
+ {
76
+ let out = {};
77
+ let p = new Promise((resolve, reject) => {
78
+ try {
79
+ MakePackedBFG.async(2, 7, out, '__Hello123456789++++foobarFOOBAR!__', (err, res) => {
80
+ assert.deepEqual(res, { a: 2, b: 4, c: -25, d: 'X/__Hello123456789++++foobarFOOBAR!__/X', e: 54, inner: { f: 14, g: 5 } });
81
+ assert.deepEqual(out, res);
82
+ });
83
+ resolve();
84
+ } catch (err) {
85
+ reject(err);
86
+ }
87
+ });
88
+ promises.push(p);
89
+ }
90
+
91
+ await Promise.all(promises);
92
+ }
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env node
2
+
3
+ // This program is free software: you can redistribute it and/or modify
4
+ // it under the terms of the GNU Affero General Public License as published by
5
+ // the Free Software Foundation, either version 3 of the License, or
6
+ // (at your option) any later version.
7
+ //
8
+ // This program is distributed in the hope that it will be useful,
9
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ // GNU Affero General Public License for more details.
12
+ //
13
+ // You should have received a copy of the GNU Affero General Public License
14
+ // along with this program. If not, see https://www.gnu.org/licenses/.
15
+
16
+ const koffi = require('./build/koffi.node');
17
+ const assert = require('assert');
18
+ const path = require('path');
19
+
20
+ const BFG = koffi.struct('BFG', {
21
+ a: 'int8_t',
22
+ b: 'int64_t',
23
+ c: 'char',
24
+ d: 'string',
25
+ e: 'short',
26
+ inner: koffi.struct({
27
+ f: 'float',
28
+ g: 'double'
29
+ })
30
+ });
31
+
32
+ const SimpleCallback = koffi.callback('int SimpleCallback(const char *str)');
33
+ const RecursiveCallback = koffi.callback('RecursiveCallback', 'float', ['int', 'string', 'double']);
34
+ const BigCallback = koffi.callback('BFG BigCallback(BFG bfg)');
35
+
36
+ main();
37
+
38
+ async function main() {
39
+ try {
40
+ await test();
41
+ console.log('Success!');
42
+ } catch (err) {
43
+ console.error(err);
44
+ process.exit(1);
45
+ }
46
+ }
47
+
48
+ async function test() {
49
+ const lib_filename = path.dirname(__filename) + '/build/misc' + koffi.extension;
50
+ const lib = koffi.load(lib_filename);
51
+
52
+ const CallJS = lib.func('int CallJS(const char *str, SimpleCallback cb)');
53
+ const CallRecursiveJS = lib.func('float CallRecursiveJS(int i, RecursiveCallback func)');
54
+ const ModifyBFG = lib.func('BFG ModifyBFG(int x, double y, const char *str, BigCallback func, _Out_ BFG *p)');
55
+
56
+ // Simple test similar to README example
57
+ {
58
+ let ret = CallJS('Niels', str => {
59
+ assert.equal(str, 'Hello Niels!');
60
+ return 42;
61
+ });
62
+ assert.equal(ret, 42);
63
+ }
64
+
65
+ // Test with recursion
66
+ {
67
+ let recurse = (i, str, d) => {
68
+ assert.equal(str, 'Hello!');
69
+ assert.equal(d, 42.0);
70
+ return i + (i ? CallRecursiveJS(i - 1, recurse) : 0);
71
+ };
72
+ let total = CallRecursiveJS(9, recurse);
73
+ assert.equal(total, 45);
74
+ }
75
+
76
+ // And now, with a complex struct
77
+ {
78
+ let out = {};
79
+ let bfg = ModifyBFG(2, 5, "Yo!", bfg => {
80
+ bfg.inner.f *= -1;
81
+ bfg.d = "New!";
82
+ return bfg;
83
+ }, out);
84
+ assert.deepEqual(bfg, { a: 2, b: 4, c: -25, d: 'New!', e: 54, inner: { f: -10, g: 3 } });
85
+ assert.deepEqual(out, { a: 2, b: 4, c: -25, d: 'X/Yo!/X', e: 54, inner: { f: 10, g: 3 } });
86
+ }
87
+ }
package/test/misc.c CHANGED
@@ -472,15 +472,35 @@ EXPORT int64_t ThroughInt64IS(SingleI64 s)
472
472
  return s.v;
473
473
  }
474
474
 
475
- EXPORT float CallSimpleJS(int i, float (*func)(int i, const char *str, double d))
475
+ EXPORT int CallJS(const char *str, int (*cb)(const char *str))
476
+ {
477
+ char buf[64];
478
+ snprintf(buf, sizeof(buf), "Hello %s!", str);
479
+ return cb(buf);
480
+ }
481
+
482
+ EXPORT float CallRecursiveJS(int i, float (*func)(int i, const char *str, double d))
476
483
  {
477
484
  float f = func(i, "Hello!", 42.0);
478
485
  return f;
479
486
  }
480
487
 
481
- EXPORT int TransferToJS(const char *str, int (*cb)(const char *str))
488
+ EXPORT BFG ModifyBFG(int x, double y, const char *str, BFG (*func)(BFG bfg), BFG *p)
482
489
  {
483
- char buf[64];
484
- snprintf(buf, sizeof(buf), "Hello %s!", str);
485
- return cb(buf);
490
+ BFG bfg;
491
+
492
+ static char buf[64];
493
+ snprintf(buf, sizeof(buf), "X/%s/X", str);
494
+
495
+ bfg.a = x;
496
+ bfg.b = x * 2;
497
+ bfg.c = x - 27;
498
+ bfg.d = buf;
499
+ bfg.e = x * 27;
500
+ bfg.inner.f = (float)y * x;
501
+ bfg.inner.g = (double)y - x;
502
+ *p = bfg;
503
+
504
+ bfg = func(bfg);
505
+ return bfg;
486
506
  }
package/test/sqlite.js CHANGED
@@ -45,32 +45,61 @@ async function test() {
45
45
  const sqlite3_reset = lib.func('sqlite3_reset', 'int', [sqlite3_stmt]);
46
46
  const sqlite3_bind_text = lib.func('sqlite3_bind_text', 'int', [sqlite3_stmt, 'int', 'string', 'int', 'void *']);
47
47
  const sqlite3_bind_int = lib.func('sqlite3_bind_int', 'int', [sqlite3_stmt, 'int', 'int']);
48
+ const sqlite3_column_text = lib.func('sqlite3_column_text', 'string', [sqlite3_stmt, 'int']);
49
+ const sqlite3_column_int = lib.func('sqlite3_column_int', 'int', [sqlite3_stmt, 'int']);
48
50
  const sqlite3_step = lib.func('sqlite3_step', 'int', [sqlite3_stmt]);
49
51
  const sqlite3_finalize = lib.func('sqlite3_finalize', 'int', [sqlite3_stmt]);
50
52
  const sqlite3_close_v2 = lib.func('sqlite3_close_v2', 'int', [sqlite3_db]);
51
53
 
54
+ const SQLITE_OPEN_READWRITE = 0x2;
55
+ const SQLITE_OPEN_CREATE = 0x4;
56
+ const SQLITE_ROW = 100;
57
+ const SQLITE_DONE = 101;
58
+
52
59
  let filename = await create_temporary_file(path.join(os.tmpdir(), 'test_sqlite'));
53
60
  let db = {};
54
61
 
62
+ let expected = Array.from(Array(200).keys()).map(i => [`TXT ${i}`, i % 7]);
63
+
55
64
  try {
56
- if (sqlite3_open_v2(filename, db, 0x2 | 0x4, null) != 0)
65
+ if (sqlite3_open_v2(filename, db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, null) != 0)
57
66
  throw new Error('Failed to open database');
58
67
  if (sqlite3_exec(db, 'CREATE TABLE foo (id INTEGER PRIMARY KEY, bar TEXT, value INT);', null, null, null) != 0)
59
68
  throw new Error('Failed to create table');
60
69
 
61
70
  let stmt = {};
71
+
62
72
  if (sqlite3_prepare_v2(db, "INSERT INTO foo (bar, value) VALUES (?1, ?2)", -1, stmt, null) != 0)
63
73
  throw new Error('Failed to prepare insert statement for table foo');
64
- for (let i = 0; i < 200; i++) {
74
+ for (let it of expected) {
65
75
  sqlite3_reset(stmt);
66
76
 
67
- sqlite3_bind_text(stmt, 1, `TXT ${i}`, -1, null);
68
- sqlite3_bind_int(stmt, 2, i * 2);
77
+ sqlite3_bind_text(stmt, 1, it[0], -1, null);
78
+ sqlite3_bind_int(stmt, 2, it[1]);
69
79
 
70
- if (sqlite3_step(stmt) != 101)
80
+ if (sqlite3_step(stmt) != SQLITE_DONE)
71
81
  throw new Erorr('Failed to insert new test row');
72
82
  }
73
83
  sqlite3_finalize(stmt);
84
+
85
+ if (sqlite3_prepare_v2(db, "SELECT id, bar, value FROM foo ORDER BY id", -1, stmt, null) != 0)
86
+ throw new Error('Failed to prepare select statement for table foo');
87
+ for (let i = 0; i < expected.length; i++) {
88
+ let it = expected[i];
89
+
90
+ if (sqlite3_step(stmt) != SQLITE_ROW)
91
+ throw new Error('Missing row');
92
+
93
+ if (sqlite3_column_int(stmt, 0) != i + 1)
94
+ throw new Error('Invalid data');
95
+ if (sqlite3_column_text(stmt, 1) != it[0])
96
+ throw new Error('Invalid data');
97
+ if (sqlite3_column_int(stmt, 2) != it[1])
98
+ throw new Error('Invalid data');
99
+ }
100
+ if (sqlite3_step(stmt) != SQLITE_DONE)
101
+ throw new Error('Unexpected end of statement');
102
+ sqlite3_finalize(stmt);
74
103
  } finally {
75
104
  sqlite3_close_v2(db);
76
105
  fs.unlinkSync(filename);