koffi 2.0.1 → 2.1.0-beta.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.
Files changed (78) hide show
  1. package/CMakeLists.txt +1 -8
  2. package/benchmark/atoi_koffi.js +12 -8
  3. package/benchmark/atoi_napi.js +12 -8
  4. package/benchmark/atoi_node_ffi.js +11 -10
  5. package/benchmark/raylib_cc.cc +12 -9
  6. package/benchmark/raylib_koffi.js +15 -13
  7. package/benchmark/raylib_node_ffi.js +15 -13
  8. package/benchmark/raylib_node_raylib.js +14 -11
  9. package/build/qemu/2.1.0-beta.1/koffi_darwin_arm64.tar.gz +0 -0
  10. package/build/qemu/2.1.0-beta.1/koffi_darwin_x64.tar.gz +0 -0
  11. package/build/qemu/2.1.0-beta.1/koffi_freebsd_arm64.tar.gz +0 -0
  12. package/build/qemu/2.1.0-beta.1/koffi_freebsd_ia32.tar.gz +0 -0
  13. package/build/qemu/2.1.0-beta.1/koffi_freebsd_x64.tar.gz +0 -0
  14. package/build/qemu/2.1.0-beta.1/koffi_linux_arm32hf.tar.gz +0 -0
  15. package/build/qemu/2.1.0-beta.1/koffi_linux_arm64.tar.gz +0 -0
  16. package/build/qemu/2.1.0-beta.1/koffi_linux_ia32.tar.gz +0 -0
  17. package/build/qemu/2.1.0-beta.1/koffi_linux_riscv64hf64.tar.gz +0 -0
  18. package/build/qemu/2.1.0-beta.1/koffi_linux_x64.tar.gz +0 -0
  19. package/build/qemu/2.1.0-beta.1/koffi_openbsd_ia32.tar.gz +0 -0
  20. package/build/qemu/2.1.0-beta.1/koffi_openbsd_x64.tar.gz +0 -0
  21. package/build/qemu/2.1.0-beta.1/koffi_win32_arm64.tar.gz +0 -0
  22. package/build/qemu/2.1.0-beta.1/koffi_win32_ia32.tar.gz +0 -0
  23. package/build/qemu/2.1.0-beta.1/koffi_win32_x64.tar.gz +0 -0
  24. package/doc/changes.md +12 -8
  25. package/doc/dist/doctrees/changes.doctree +0 -0
  26. package/doc/dist/doctrees/environment.pickle +0 -0
  27. package/doc/dist/doctrees/functions.doctree +0 -0
  28. package/doc/dist/doctrees/index.doctree +0 -0
  29. package/doc/dist/doctrees/types.doctree +0 -0
  30. package/doc/dist/html/_sources/changes.md.txt +12 -8
  31. package/doc/dist/html/_sources/functions.md.txt +4 -4
  32. package/doc/dist/html/_sources/types.md.txt +69 -34
  33. package/doc/dist/html/benchmarks.html +1 -1
  34. package/doc/dist/html/changes.html +44 -33
  35. package/doc/dist/html/contribute.html +1 -1
  36. package/doc/dist/html/functions.html +7 -7
  37. package/doc/dist/html/genindex.html +1 -1
  38. package/doc/dist/html/index.html +3 -3
  39. package/doc/dist/html/memory.html +1 -1
  40. package/doc/dist/html/objects.inv +0 -0
  41. package/doc/dist/html/platforms.html +2 -2
  42. package/doc/dist/html/search.html +1 -1
  43. package/doc/dist/html/searchindex.js +1 -1
  44. package/doc/dist/html/start.html +1 -1
  45. package/doc/dist/html/types.html +77 -40
  46. package/doc/functions.md +4 -4
  47. package/doc/types.md +68 -33
  48. package/package.json +2 -2
  49. package/qemu/qemu.js +1 -1
  50. package/src/call.cc +42 -26
  51. package/src/call.hh +15 -3
  52. package/src/ffi.cc +70 -13
  53. package/src/ffi.hh +7 -0
  54. package/src/index.js +4 -2
  55. package/src/parser.cc +3 -5
  56. package/src/util.cc +44 -1
  57. package/src/util.hh +4 -0
  58. package/test/async.js +1 -2
  59. package/test/callbacks.js +2 -3
  60. package/test/misc.c +21 -1
  61. package/test/raylib.js +1 -1
  62. package/test/sqlite.js +3 -3
  63. package/test/sync.js +27 -3
  64. package/build/qemu/2.0.1/koffi_darwin_arm64.tar.gz +0 -0
  65. package/build/qemu/2.0.1/koffi_darwin_x64.tar.gz +0 -0
  66. package/build/qemu/2.0.1/koffi_freebsd_arm64.tar.gz +0 -0
  67. package/build/qemu/2.0.1/koffi_freebsd_ia32.tar.gz +0 -0
  68. package/build/qemu/2.0.1/koffi_freebsd_x64.tar.gz +0 -0
  69. package/build/qemu/2.0.1/koffi_linux_arm32hf.tar.gz +0 -0
  70. package/build/qemu/2.0.1/koffi_linux_arm64.tar.gz +0 -0
  71. package/build/qemu/2.0.1/koffi_linux_ia32.tar.gz +0 -0
  72. package/build/qemu/2.0.1/koffi_linux_riscv64hf64.tar.gz +0 -0
  73. package/build/qemu/2.0.1/koffi_linux_x64.tar.gz +0 -0
  74. package/build/qemu/2.0.1/koffi_openbsd_ia32.tar.gz +0 -0
  75. package/build/qemu/2.0.1/koffi_openbsd_x64.tar.gz +0 -0
  76. package/build/qemu/2.0.1/koffi_win32_arm64.tar.gz +0 -0
  77. package/build/qemu/2.0.1/koffi_win32_ia32.tar.gz +0 -0
  78. package/build/qemu/2.0.1/koffi_win32_x64.tar.gz +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koffi",
3
- "version": "2.0.1",
3
+ "version": "2.1.0-beta.1",
4
4
  "description": "Fast and simple C FFI (foreign function interface) for Node.js",
5
5
  "keywords": [
6
6
  "foreign",
@@ -25,7 +25,7 @@
25
25
  },
26
26
  "license": "AGPL-3.0",
27
27
  "dependencies": {
28
- "cnoke": "^3.1.4"
28
+ "cnoke": "^3.2.0"
29
29
  },
30
30
  "devDependencies": {
31
31
  "chalk": "^4.1.2",
package/qemu/qemu.js CHANGED
@@ -42,7 +42,7 @@ let qemu_prefix = null;
42
42
  main();
43
43
 
44
44
  async function main() {
45
- script_dir = fs.realpathSync(path.dirname(__filename));
45
+ script_dir = fs.realpathSync(__dirname);
46
46
  root_dir = fs.realpathSync(script_dir + '/../..');
47
47
 
48
48
  // All the code assumes we are working from the script directory
package/src/call.cc CHANGED
@@ -567,24 +567,26 @@ bool CallData::PushTypedArray(Napi::TypedArray array, Size len, const TypeInfo *
567
567
  return false;
568
568
  }
569
569
 
570
- Size offset = 0;
571
570
  const uint8_t *buf = (const uint8_t *)array.ArrayBuffer().Data();
572
571
 
573
- if (RG_UNLIKELY(array.TypedArrayType() != GetTypedArrayType(ref))) {
572
+ if (RG_UNLIKELY(array.TypedArrayType() != GetTypedArrayType(ref) &&
573
+ ref != instance->void_type)) {
574
574
  ThrowError<Napi::TypeError>(env, "Cannot use %1 value for %2 array", GetValueType(instance, array), ref->name);
575
575
  return false;
576
576
  }
577
577
 
578
578
  if (realign) {
579
+ Size offset = 0;
580
+ Size size = (Size)array.ElementSize();
581
+
579
582
  for (uint32_t i = 0; i < len; i++) {
580
- int16_t align = std::max(ref->align, realign);
581
- offset = AlignLen(offset, align);
583
+ offset = AlignLen(offset, realign);
582
584
 
583
585
  uint8_t *dest = origin + offset;
586
+ const uint8_t *src = buf + i * size;
584
587
 
585
- memcpy(dest, buf + i * ref->size, ref->size);
586
-
587
- offset += ref->size;
588
+ memcpy(dest, src, size);
589
+ offset += size;
588
590
  }
589
591
  } else {
590
592
  memcpy_safe(origin, buf, (size_t)array.ByteLength());
@@ -625,6 +627,16 @@ bool CallData::PushStringArray(Napi::Value obj, const TypeInfo *type, uint8_t *o
625
627
 
626
628
  bool CallData::PushPointer(Napi::Value value, const ParameterInfo &param, void **out_ptr)
627
629
  {
630
+ const TypeInfo *type = param.type;
631
+
632
+ if (CheckValueTag(instance, value, &CastMarker)) {
633
+ Napi::External<ValueCast> external = value.As<Napi::External<ValueCast>>();
634
+ ValueCast *cast = external.Data();
635
+
636
+ value = cast->ref.Value();
637
+ type = cast->type;
638
+ }
639
+
628
640
  switch (value.Type()) {
629
641
  case napi_undefined:
630
642
  case napi_null: {
@@ -633,9 +645,11 @@ bool CallData::PushPointer(Napi::Value value, const ParameterInfo &param, void *
633
645
  } break;
634
646
 
635
647
  case napi_external: {
636
- RG_ASSERT(param.type->primitive == PrimitiveKind::Pointer);
648
+ RG_ASSERT(type->primitive == PrimitiveKind::Pointer);
637
649
 
638
- if (RG_UNLIKELY(!CheckValueTag(instance, value, param.type->ref.marker)))
650
+ if (RG_UNLIKELY(!CheckValueTag(instance, value, type->ref.marker) &&
651
+ !CheckValueTag(instance, value, instance->void_type) &&
652
+ type->ref.type != instance->void_type))
639
653
  goto unexpected;
640
654
 
641
655
  *out_ptr = value.As<Napi::External<uint8_t>>().Data();
@@ -649,12 +663,12 @@ bool CallData::PushPointer(Napi::Value value, const ParameterInfo &param, void *
649
663
  Napi::Array array = value.As<Napi::Array>();
650
664
 
651
665
  Size len = (Size)array.Length();
652
- Size size = len * param.type->ref.type->size;
666
+ Size size = len * type->ref.type->size;
653
667
 
654
668
  ptr = AllocHeap(size, 16);
655
669
 
656
670
  if (param.directions & 1) {
657
- if (!PushNormalArray(array, len, param.type->ref.type, ptr))
671
+ if (!PushNormalArray(array, len, type->ref.type, ptr))
658
672
  return false;
659
673
  } else {
660
674
  memset(ptr, 0, size);
@@ -668,27 +682,28 @@ bool CallData::PushPointer(Napi::Value value, const ParameterInfo &param, void *
668
682
  ptr = AllocHeap(size, 16);
669
683
 
670
684
  if (param.directions & 1) {
671
- if (!PushTypedArray(array, len, param.type->ref.type, ptr))
685
+ if (!PushTypedArray(array, len, type->ref.type, ptr))
672
686
  return false;
673
687
  } else {
674
- if (RG_UNLIKELY(array.TypedArrayType() != GetTypedArrayType(param.type->ref.type))) {
675
- ThrowError<Napi::TypeError>(env, "Cannot use %1 value for %2 array", GetValueType(instance, array), param.type->ref.type->name);
688
+ if (RG_UNLIKELY(array.TypedArrayType() != GetTypedArrayType(type->ref.type) &&
689
+ type->ref.type != instance->void_type)) {
690
+ ThrowError<Napi::TypeError>(env, "Cannot use %1 value for %2 array", GetValueType(instance, array), type->ref.type->name);
676
691
  return false;
677
692
  }
678
693
 
679
694
  memset(ptr, 0, size);
680
695
  }
681
- } else if (RG_LIKELY(param.type->ref.type->primitive == PrimitiveKind::Record)) {
696
+ } else if (RG_LIKELY(type->ref.type->primitive == PrimitiveKind::Record)) {
682
697
  Napi::Object obj = value.As<Napi::Object>();
683
698
  RG_ASSERT(IsObject(value));
684
699
 
685
- ptr = AllocHeap(param.type->ref.type->size, 16);
700
+ ptr = AllocHeap(type->ref.type->size, 16);
686
701
 
687
702
  if (param.directions & 1) {
688
- if (!PushObject(obj, param.type->ref.type, ptr))
703
+ if (!PushObject(obj, type->ref.type, ptr))
689
704
  return false;
690
705
  } else {
691
- memset(ptr, 0, param.type->size);
706
+ memset(ptr, 0, type->size);
692
707
  }
693
708
  } else {
694
709
  goto unexpected;
@@ -701,7 +716,7 @@ bool CallData::PushPointer(Napi::Value value, const ParameterInfo &param, void *
701
716
  RG_ASSERT(status == napi_ok);
702
717
 
703
718
  out->ptr = ptr;
704
- out->type = param.type->ref.type;
719
+ out->type = type->ref.type;
705
720
  }
706
721
 
707
722
  *out_ptr = ptr;
@@ -712,7 +727,7 @@ bool CallData::PushPointer(Napi::Value value, const ParameterInfo &param, void *
712
727
  }
713
728
 
714
729
  unexpected:
715
- ThrowError<Napi::TypeError>(env, "Unexpected %1 value for argument %2, expected %3", GetValueType(instance, value), param.offset + 1, param.type->name);
730
+ ThrowError<Napi::TypeError>(env, "Unexpected %1 value for argument %2, expected %3", GetValueType(instance, value), param.offset + 1, type->name);
716
731
  return false;
717
732
  }
718
733
 
@@ -1016,24 +1031,25 @@ void CallData::PopNormalArray(Napi::Array array, const uint8_t *origin, const Ty
1016
1031
  void CallData::PopTypedArray(Napi::TypedArray array, const uint8_t *origin, const TypeInfo *ref, int16_t realign)
1017
1032
  {
1018
1033
  RG_ASSERT(array.IsTypedArray());
1019
- RG_ASSERT(GetTypedArrayType(ref) == array.TypedArrayType());
1034
+ RG_ASSERT(GetTypedArrayType(ref) == array.TypedArrayType() ||
1035
+ ref == instance->void_type);
1020
1036
 
1021
1037
  uint8_t *buf = (uint8_t *)array.ArrayBuffer().Data();
1022
1038
 
1023
1039
  if (realign) {
1024
1040
  Size offset = 0;
1025
1041
  Size len = (Size)array.ElementLength();
1042
+ Size size = (Size)array.ElementSize();
1026
1043
 
1027
1044
  for (Size i = 0; i < len; i++) {
1028
- int16_t align = std::max(ref->align, realign);
1029
- offset = AlignLen(offset, align);
1045
+ offset = AlignLen(offset, realign);
1030
1046
 
1031
- uint8_t *dest = buf + i * ref->size;
1047
+ uint8_t *dest = buf + i * size;
1032
1048
  const uint8_t *src = origin + offset;
1033
1049
 
1034
- memcpy(dest, src, ref->size);
1050
+ memcpy(dest, src, size);
1035
1051
 
1036
- offset += ref->size;
1052
+ offset += size;
1037
1053
  }
1038
1054
  } else {
1039
1055
  memcpy_safe(buf, origin, (size_t)array.ByteLength());
package/src/call.hh CHANGED
@@ -71,9 +71,21 @@ public:
71
71
  CallData(Napi::Env env, InstanceData *instance, const FunctionInfo *func, InstanceMemory *mem);
72
72
  ~CallData();
73
73
 
74
- bool Prepare(const Napi::CallbackInfo &info);
75
- void Execute();
76
- Napi::Value Complete();
74
+ #ifdef UNITY_BUILD
75
+ #ifdef _MSC_VER
76
+ #define INLINE_IF_UNITY __forceinline
77
+ #else
78
+ #define INLINE_IF_UNITY __attribute__((always_inline)) inline
79
+ #endif
80
+ #else
81
+ #define INLINE_IF_UNITY
82
+ #endif
83
+
84
+ INLINE_IF_UNITY bool Prepare(const Napi::CallbackInfo &info);
85
+ INLINE_IF_UNITY void Execute();
86
+ INLINE_IF_UNITY Napi::Value Complete();
87
+
88
+ #undef INLINE_IF_UNITY
77
89
 
78
90
  void Relay(Size idx, uint8_t *own_sp, uint8_t *caller_sp, BackRegisters *out_reg);
79
91
 
package/src/ffi.cc CHANGED
@@ -42,6 +42,7 @@ namespace RG {
42
42
 
43
43
  // Value does not matter, the tag system uses memory addresses
44
44
  const int TypeInfoMarker = 0xDEADBEEF;
45
+ const int CastMarker = 0xDEADBEEF;
45
46
 
46
47
  static bool ChangeMemorySize(const char *name, Napi::Value value, Size *out_size)
47
48
  {
@@ -209,18 +210,40 @@ static Napi::Value CreateStructType(const Napi::CallbackInfo &info, bool pad)
209
210
 
210
211
  std::string key = ((Napi::Value)keys[i]).As<Napi::String>();
211
212
  Napi::Value value = obj[key];
213
+ int16_t align = 0;
212
214
 
213
215
  member.name = DuplicateString(key.c_str(), &instance->str_alloc).ptr;
216
+
217
+ if (value.IsArray()) {
218
+ Napi::Array array = value.As<Napi::Array>();
219
+
220
+ if (array.Length() != 2 || !((Napi::Value)array[0u]).IsNumber()) {
221
+ ThrowError<Napi::Error>(env, "Member specifier array must contain alignement value and type");
222
+ return env.Null();
223
+ }
224
+
225
+ int64_t align64 = ((Napi::Value)array[0u]).As<Napi::Number>().Int64Value();
226
+
227
+ if (align64 < 1 || align64 > 64) {
228
+ ThrowError<Napi::Error>(env, "Alignment value must be between 1 and 64");
229
+ return env.Null();
230
+ }
231
+
232
+ value = array[1u];
233
+ align = (int16_t)align64;
234
+ }
235
+
214
236
  member.type = ResolveType(value);
215
237
  if (!member.type)
216
238
  return env.Null();
217
- if (member.type->primitive == PrimitiveKind::Void ||
218
- member.type->primitive == PrimitiveKind::Prototype) {
239
+ if (!CanStoreType(member.type)) {
219
240
  ThrowError<Napi::TypeError>(env, "Type %1 cannot be used as a member (maybe try %1 *)", member.type->name);
220
241
  return env.Null();
221
242
  }
222
243
 
223
- int16_t align = pad ? member.type->align : 1;
244
+ if (!align) {
245
+ align = pad ? member.type->align : 1;
246
+ }
224
247
  member.offset = (int16_t)AlignLen(type->size, align);
225
248
 
226
249
  type->size = (int16_t)(member.offset + member.type->size);
@@ -264,7 +287,7 @@ static Napi::Value CreatePackedStructType(const Napi::CallbackInfo &info)
264
287
  return CreateStructType(info, false);
265
288
  }
266
289
 
267
- static Napi::Value CreateHandleType(const Napi::CallbackInfo &info)
290
+ static Napi::Value CreateOpaqueType(const Napi::CallbackInfo &info)
268
291
  {
269
292
  Napi::Env env = info.Env();
270
293
  InstanceData *instance = env.GetInstanceData<InstanceData>();
@@ -618,8 +641,8 @@ static bool ParseClassicFunction(Napi::Env env, Napi::String name, Napi::Value r
618
641
  func->ret.type = ResolveType(ret);
619
642
  if (!func->ret.type)
620
643
  return false;
621
- if (func->ret.type->primitive == PrimitiveKind::Array) {
622
- ThrowError<Napi::Error>(env, "You are not allowed to directly return fixed-size arrays");
644
+ if (!CanReturnType(func->ret.type)) {
645
+ ThrowError<Napi::TypeError>(env, "You are not allowed to directly return %1 values (maybe try %1 *)", func->ret.type->name);
623
646
  return false;
624
647
  }
625
648
 
@@ -645,9 +668,7 @@ static bool ParseClassicFunction(Napi::Env env, Napi::String name, Napi::Value r
645
668
  param.type = ResolveType(parameters[j], &param.directions);
646
669
  if (!param.type)
647
670
  return false;
648
- if (param.type->primitive == PrimitiveKind::Void ||
649
- param.type->primitive == PrimitiveKind::Array ||
650
- param.type->primitive == PrimitiveKind::Prototype) {
671
+ if (!CanPassType(param.type)) {
651
672
  ThrowError<Napi::TypeError>(env, "Type %1 cannot be used as a parameter (maybe try %1 *)", param.type->name);
652
673
  return false;
653
674
  }
@@ -988,9 +1009,7 @@ static Napi::Value TranslateVariadicCall(const Napi::CallbackInfo &info)
988
1009
  param.type = ResolveType(info[i], &param.directions);
989
1010
  if (RG_UNLIKELY(!param.type))
990
1011
  return env.Null();
991
- if (RG_UNLIKELY(param.type->primitive == PrimitiveKind::Void ||
992
- param.type->primitive == PrimitiveKind::Array ||
993
- param.type->primitive == PrimitiveKind::Prototype)) {
1012
+ if (RG_UNLIKELY(!CanPassType(param.type))) {
994
1013
  ThrowError<Napi::TypeError>(env, "Type %1 cannot be used as a parameter (maybe try %1 *)", PrimitiveKindNames[(int)param.type->primitive]);
995
1014
  return env.Null();
996
1015
  }
@@ -1434,6 +1453,8 @@ static void RegisterPrimitiveType(Napi::Env env, Napi::Object map, std::initiali
1434
1453
 
1435
1454
  static Napi::Object InitBaseTypes(Napi::Env env)
1436
1455
  {
1456
+ InstanceData *instance = env.GetInstanceData<InstanceData>();
1457
+
1437
1458
  Napi::Object types = Napi::Object::New(env);
1438
1459
 
1439
1460
  RegisterPrimitiveType(env, types, {"void"}, PrimitiveKind::Void, 0, 0);
@@ -1464,6 +1485,9 @@ static Napi::Object InitBaseTypes(Napi::Env env)
1464
1485
  RegisterPrimitiveType(env, types, {"char *", "str", "string"}, PrimitiveKind::String, RG_SIZE(void *), alignof(void *), "char");
1465
1486
  RegisterPrimitiveType(env, types, {"char16_t *", "char16 *", "str16", "string16"}, PrimitiveKind::String16, RG_SIZE(void *), alignof(void *), "char16_t");
1466
1487
 
1488
+ instance->void_type = instance->types_map.FindValue("void", nullptr);
1489
+ RG_ASSERT(instance->void_type);
1490
+
1467
1491
  types.Freeze();
1468
1492
 
1469
1493
  return types;
@@ -1515,6 +1539,37 @@ InstanceData::~InstanceData()
1515
1539
  }
1516
1540
  }
1517
1541
 
1542
+ static Napi::Value CastValue(const Napi::CallbackInfo &info)
1543
+ {
1544
+ Napi::Env env = info.Env();
1545
+ InstanceData *instance = env.GetInstanceData<InstanceData>();
1546
+
1547
+ if (RG_UNLIKELY(info.Length() < 2)) {
1548
+ ThrowError<Napi::TypeError>(env, "Expected 2 arguments, got %1", info.Length());
1549
+ return env.Null();
1550
+ }
1551
+
1552
+ Napi::Value value = info[0];
1553
+
1554
+ const TypeInfo *type = ResolveType(info[1]);
1555
+ if (RG_UNLIKELY(!type))
1556
+ return env.Null();
1557
+ if (type->primitive != PrimitiveKind::Pointer) {
1558
+ ThrowError<Napi::TypeError>(env, "Only pointer types can be used for casting");
1559
+ return env.Null();
1560
+ }
1561
+
1562
+ ValueCast *cast = new ValueCast;
1563
+
1564
+ cast->ref.Reset(value, 1);
1565
+ cast->type = type;
1566
+
1567
+ Napi::External<ValueCast> external = Napi::External<ValueCast>::New(env, cast, [](Napi::Env, ValueCast *cast) { delete cast; });
1568
+ SetValueTag(instance, external, &CastMarker);
1569
+
1570
+ return external;
1571
+ }
1572
+
1518
1573
  template <typename Func>
1519
1574
  static void SetExports(Napi::Env env, Func func)
1520
1575
  {
@@ -1522,7 +1577,7 @@ static void SetExports(Napi::Env env, Func func)
1522
1577
 
1523
1578
  func("struct", Napi::Function::New(env, CreatePaddedStructType));
1524
1579
  func("pack", Napi::Function::New(env, CreatePackedStructType));
1525
- func("handle", Napi::Function::New(env, CreateHandleType));
1580
+ func("opaque", Napi::Function::New(env, CreateOpaqueType));
1526
1581
  func("pointer", Napi::Function::New(env, CreatePointerType));
1527
1582
  func("array", Napi::Function::New(env, CreateArrayType));
1528
1583
  func("callback", Napi::Function::New(env, CreateCallbackType));
@@ -1545,6 +1600,8 @@ static void SetExports(Napi::Env env, Func func)
1545
1600
  func("register", Napi::Function::New(env, RegisterCallback));
1546
1601
  func("unregister", Napi::Function::New(env, UnregisterCallback));
1547
1602
 
1603
+ func("as", Napi::Function::New(env, CastValue));
1604
+
1548
1605
  #if defined(_WIN32)
1549
1606
  func("extension", Napi::String::New(env, ".dll"));
1550
1607
  #elif defined(__APPLE__)
package/src/ffi.hh CHANGED
@@ -32,6 +32,7 @@ static const Size MaxOutParameters = 4;
32
32
  static const Size MaxTrampolines = 16;
33
33
 
34
34
  extern const int TypeInfoMarker;
35
+ extern const int CastMarker;
35
36
 
36
37
  enum class PrimitiveKind {
37
38
  Void,
@@ -171,6 +172,11 @@ struct ParameterInfo {
171
172
  #endif
172
173
  };
173
174
 
175
+ struct ValueCast {
176
+ Napi::Reference<Napi::Value> ref;
177
+ const TypeInfo *type;
178
+ };
179
+
174
180
  // Also used for callbacks, even though many members are not used in this case
175
181
  struct FunctionInfo {
176
182
  mutable std::atomic_int refcount {1};
@@ -230,6 +236,7 @@ struct InstanceData {
230
236
 
231
237
  bool debug;
232
238
  uint64_t tag_lower;
239
+ const TypeInfo *void_type;
233
240
 
234
241
  LocalArray<InstanceMemory *, 9> memories;
235
242
  int temporaries = 0;
package/src/index.js CHANGED
@@ -15,7 +15,9 @@
15
15
 
16
16
  'use strict';
17
17
 
18
- const path = require('path');
18
+ const util = require('util');
19
19
 
20
- let filename = path.dirname(__filename) + '/../build/koffi.node';
20
+ let filename = __dirname + '/../build/koffi.node';
21
21
  module.exports = require(filename);
22
+
23
+ module.exports.handle = util.deprecate(module.exports.opaque, 'The koffi.handle() function was deprecated in Koffi 2.1, use koffi.opaque() instead', 'KOFFI001');
package/src/parser.cc CHANGED
@@ -28,8 +28,8 @@ bool PrototypeParser::Parse(const char *str, FunctionInfo *out_func)
28
28
  Tokenize(str);
29
29
 
30
30
  out_func->ret.type = ParseType();
31
- if (out_func->ret.type->primitive == PrimitiveKind::Array) {
32
- MarkError("You are not allowed to directly return C arrays");
31
+ if (!CanReturnType(out_func->ret.type)) {
32
+ MarkError("You are not allowed to directly return %1 values (maybe try %1 *)", out_func->ret.type->name);
33
33
  return false;
34
34
  }
35
35
  if (Match("__cdecl")) {
@@ -65,9 +65,7 @@ bool PrototypeParser::Parse(const char *str, FunctionInfo *out_func)
65
65
  }
66
66
 
67
67
  param.type = ParseType();
68
- if (param.type->primitive == PrimitiveKind::Void ||
69
- param.type->primitive == PrimitiveKind::Array ||
70
- param.type->primitive == PrimitiveKind::Prototype) {
68
+ if (!CanPassType(param.type)) {
71
69
  MarkError("Type %1 cannot be used as a parameter (maybe try %1 *)", param.type->name);
72
70
  return false;
73
71
  }
package/src/util.cc CHANGED
@@ -170,10 +170,53 @@ const TypeInfo *MakePointerType(InstanceData *instance, const TypeInfo *ref, int
170
170
  return ref;
171
171
  }
172
172
 
173
+ bool CanPassType(const TypeInfo *type)
174
+ {
175
+ if (type->primitive == PrimitiveKind::Void)
176
+ return false;
177
+ if (type->primitive == PrimitiveKind::Array)
178
+ return false;
179
+ if (type->primitive == PrimitiveKind::Prototype)
180
+ return false;
181
+
182
+ return true;
183
+ }
184
+
185
+ bool CanReturnType(const TypeInfo *type)
186
+ {
187
+ if (type->primitive == PrimitiveKind::Void && !TestStr(type->name, "void"))
188
+ return false;
189
+ if (type->primitive == PrimitiveKind::Array)
190
+ return false;
191
+ if (type->primitive == PrimitiveKind::Prototype)
192
+ return false;
193
+
194
+ return true;
195
+ }
196
+
197
+ bool CanStoreType(const TypeInfo *type)
198
+ {
199
+ if (type->primitive == PrimitiveKind::Void)
200
+ return false;
201
+ if (type->primitive == PrimitiveKind::Prototype)
202
+ return false;
203
+
204
+ return true;
205
+ }
206
+
173
207
  const char *GetValueType(const InstanceData *instance, Napi::Value value)
174
208
  {
209
+ if (CheckValueTag(instance, value, &CastMarker)) {
210
+ Napi::External<ValueCast> external = value.As<Napi::External<ValueCast>>();
211
+ ValueCast *cast = external.Data();
212
+
213
+ return cast->type->name;
214
+ }
215
+
216
+ if (CheckValueTag(instance, value, &TypeInfoMarker))
217
+ return "Type";
175
218
  for (const TypeInfo &type: instance->types) {
176
- if (CheckValueTag(instance, value, type.ref.marker))
219
+ if (type.ref.marker && CheckValueTag(instance, value, type.ref.marker))
177
220
  return type.name;
178
221
  }
179
222
 
package/src/util.hh CHANGED
@@ -71,6 +71,10 @@ const TypeInfo *ResolveType(Napi::Value value, int *out_directions = nullptr);
71
71
  const TypeInfo *ResolveType(InstanceData *instance, Span<const char> str, int *out_directions = nullptr);
72
72
  const TypeInfo *MakePointerType(InstanceData *instance, const TypeInfo *type, int count = 1);
73
73
 
74
+ bool CanPassType(const TypeInfo *type);
75
+ bool CanReturnType(const TypeInfo *type);
76
+ bool CanStoreType(const TypeInfo *type);
77
+
74
78
  // Can be slow, only use for error messages
75
79
  const char *GetValueType(const InstanceData *instance, Napi::Value value);
76
80
 
package/test/async.js CHANGED
@@ -15,7 +15,6 @@
15
15
 
16
16
  const koffi = require('./build/koffi.node');
17
17
  const assert = require('assert');
18
- const path = require('path');
19
18
 
20
19
  const PackedBFG = koffi.pack('PackedBFG', {
21
20
  a: 'int8_t',
@@ -42,7 +41,7 @@ async function main() {
42
41
  }
43
42
 
44
43
  async function test() {
45
- const lib_filename = path.dirname(__filename) + '/build/misc' + koffi.extension;
44
+ const lib_filename = __dirname + '/build/misc' + koffi.extension;
46
45
  const lib = koffi.load(lib_filename);
47
46
 
48
47
  const ConcatenateToInt1 = lib.func('ConcatenateToInt1', 'int64_t', Array(12).fill('int8_t'));
package/test/callbacks.js CHANGED
@@ -15,11 +15,10 @@
15
15
 
16
16
  const koffi = require('./build/koffi.node');
17
17
  const assert = require('assert');
18
- const path = require('path');
19
18
 
20
19
  const BFG = koffi.struct('BFG', {
21
20
  a: 'int8_t',
22
- b: 'int64_t',
21
+ b: [16, 'int64_t'],
23
22
  c: 'char',
24
23
  d: 'str',
25
24
  e: 'short',
@@ -55,7 +54,7 @@ async function main() {
55
54
  }
56
55
 
57
56
  async function test() {
58
- const lib_filename = path.dirname(__filename) + '/build/misc' + koffi.extension;
57
+ const lib_filename = __dirname + '/build/misc' + koffi.extension;
59
58
  const lib = koffi.load(lib_filename);
60
59
 
61
60
  const CallJS = lib.func('int CallJS(const char *str, SimpleCallback *cb)');
package/test/misc.c CHANGED
@@ -90,7 +90,7 @@ typedef struct IJK8 { int64_t i; int64_t j; int64_t k; } IJK8;
90
90
 
91
91
  typedef struct BFG {
92
92
  int8_t a;
93
- int64_t b;
93
+ char _pad1[15]; int64_t b;
94
94
  signed char c;
95
95
  const char *d;
96
96
  short e;
@@ -399,6 +399,15 @@ EXPORT PackedBFG FASTCALL MakePackedBFG(int x, double y, PackedBFG *p, const cha
399
399
  return bfg;
400
400
  }
401
401
 
402
+ EXPORT void MakePolymorphBFG(int type, int x, double y, const char *str, void *p)
403
+ {
404
+ if (type == 0) {
405
+ MakeBFG(p, x, y, str);
406
+ } else if (type == 1) {
407
+ MakePackedBFG(x, y, p, str);
408
+ }
409
+ }
410
+
402
411
  #ifdef _WIN32
403
412
  // Exported by DEF file
404
413
  const char *ReturnBigString(const char *str)
@@ -633,3 +642,14 @@ EXPORT int CallCallback(int x)
633
642
  {
634
643
  return callback(x);
635
644
  }
645
+
646
+ EXPORT void ReverseBytes(void *p, int len)
647
+ {
648
+ uint8_t *bytes = (uint8_t *)p;
649
+
650
+ for (int i = 0; i < len / 2; i++) {
651
+ uint8_t tmp = bytes[i];
652
+ bytes[i] = bytes[len - i - 1];
653
+ bytes[len - i - 1] = tmp;
654
+ }
655
+ }
package/test/raylib.js CHANGED
@@ -83,7 +83,7 @@ async function main() {
83
83
  }
84
84
 
85
85
  async function test() {
86
- let lib_filename = path.dirname(__filename) + '/build/raylib' + koffi.extension;
86
+ let lib_filename = __dirname + '/build/raylib' + koffi.extension;
87
87
  let lib = koffi.load(lib_filename);
88
88
 
89
89
  const InitWindow = lib.func('InitWindow', 'void', ['int', 'int', 'str']);
package/test/sqlite.js CHANGED
@@ -20,8 +20,8 @@ const fs = require('fs');
20
20
  const os = require('os');
21
21
  const path = require('path');
22
22
 
23
- const sqlite3 = koffi.handle('sqlite3');
24
- const sqlite3_stmt = koffi.handle('sqlite3_stmt');
23
+ const sqlite3 = koffi.opaque('sqlite3');
24
+ const sqlite3_stmt = koffi.opaque('sqlite3_stmt');
25
25
 
26
26
  main();
27
27
 
@@ -36,7 +36,7 @@ async function main() {
36
36
  }
37
37
 
38
38
  async function test() {
39
- let lib_filename = path.dirname(__filename) + '/build/sqlite3' + koffi.extension;
39
+ let lib_filename = __dirname + '/build/sqlite3' + koffi.extension;
40
40
  let lib = koffi.load(lib_filename);
41
41
 
42
42
  const sqlite3_open_v2 = lib.func('sqlite3_open_v2', 'int', ['str', koffi.out(koffi.pointer(sqlite3, 2)), 'int', 'str']);