@nativescript/windows-hermes 1.0.0-beta.8 → 1.0.0-beta.9

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.
@@ -31,6 +31,13 @@ internal sealed class JsJsonValue(string json)
31
31
  public readonly string Json = json;
32
32
  }
33
33
 
34
+ // A JS array (tag 0x07): its items, each read like an argument, converted to the array or
35
+ // collection type the parameter expects.
36
+ internal sealed class JsArrayValue(object?[] items)
37
+ {
38
+ public readonly object?[] Items = items;
39
+ }
40
+
34
41
  internal ref struct BinReader(ReadOnlySpan<byte> buf)
35
42
  {
36
43
  private readonly ReadOnlySpan<byte> _buf = buf;
@@ -73,9 +80,17 @@ internal ref struct BinReader(ReadOnlySpan<byte> buf)
73
80
  var len = ReadU16();
74
81
  var s = Encoding.UTF8.GetString(_buf.Slice(_pos, len));
75
82
  _pos += len;
76
- // Intern so repeated method/type names reuse the same heap string.
77
- // Eliminates the allocation on every subsequent warm-path call.
78
- return string.Intern(s);
83
+ return s;
84
+ }
85
+
86
+ // A type, assembly or member name: the same few names arrive on every call, so they come from
87
+ // NameCache instead of being decoded (and allocated) again.
88
+ public string ReadName16()
89
+ {
90
+ var len = ReadU16();
91
+ var s = NameCache.Get(_buf.Slice(_pos, len));
92
+ _pos += len;
93
+ return s;
79
94
  }
80
95
 
81
96
  public string ReadString32()
@@ -84,7 +99,7 @@ internal ref struct BinReader(ReadOnlySpan<byte> buf)
84
99
  _pos += 4;
85
100
  var s = Encoding.UTF8.GetString(_buf.Slice(_pos, (int)len));
86
101
  _pos += (int)len;
87
- return string.Intern(s);
102
+ return s;
88
103
  }
89
104
 
90
105
  public uint ReadU32()
@@ -100,24 +115,64 @@ internal ref struct BinReader(ReadOnlySpan<byte> buf)
100
115
  if (count == 0) return [];
101
116
  var args = new object?[count];
102
117
  for (int i = 0; i < count; i++)
118
+ args[i] = ReadArg();
119
+ return args;
120
+ }
121
+
122
+ private object? ReadArg()
123
+ {
124
+ var tag = ReadByte();
125
+ switch (tag)
103
126
  {
104
- var tag = ReadByte();
105
- args[i] = tag switch
127
+ case 0x00: return null;
128
+ case 0x01: return false;
129
+ case 0x02: return true;
130
+ case 0x03: return ReadI32();
131
+ case 0x04: return ReadF64();
132
+ case 0x05: return ReadString16();
133
+ case 0x06: return new HandleRef(ReadI32());
134
+ case 0x07:
106
135
  {
107
- 0x00 => null,
108
- 0x01 => (object)false,
109
- 0x02 => (object)true,
110
- 0x03 => (object)ReadI32(),
111
- 0x04 => (object)ReadF64(),
112
- 0x05 => (object)ReadString16(),
113
- 0x06 => (object)new HandleRef(ReadI32()),
114
- 0x0A => (object)new WinRtRef(ReadI64()),
115
- 0x0C => (object)new JsFunctionRef(ReadI32()),
116
- 0x0D => (object)new JsJsonValue(ReadString32()),
117
- _ => null,
118
- };
136
+ var items = new object?[ReadU32()];
137
+ for (int i = 0; i < items.Length; i++) items[i] = ReadArg();
138
+ return new JsArrayValue(items);
139
+ }
140
+ case 0x0A: return new WinRtRef(ReadI64());
141
+ case 0x0C: return new JsFunctionRef(ReadI32());
142
+ case 0x0D: return new JsJsonValue(ReadString32());
143
+ default: return null;
119
144
  }
120
- return args;
145
+ }
146
+ }
147
+
148
+ // Decoded names by their UTF-8 bytes. Per thread and direct-mapped: a lookup takes no lock and a
149
+ // collision just decodes the name again. Only names land here, never argument values, so unlike
150
+ // string.Intern it can't grow without bound.
151
+ internal static class NameCache
152
+ {
153
+ private const int Size = 512;
154
+
155
+ [ThreadStatic]
156
+ private static Entry[]? t_entries;
157
+
158
+ private sealed class Entry(byte[] utf8, string value)
159
+ {
160
+ public readonly byte[] Utf8 = utf8;
161
+ public readonly string Value = value;
162
+ }
163
+
164
+ public static string Get(ReadOnlySpan<byte> utf8)
165
+ {
166
+ if (utf8.IsEmpty) return string.Empty;
167
+ var entries = t_entries ??= new Entry[Size];
168
+ var hash = new HashCode();
169
+ hash.AddBytes(utf8);
170
+ ref var slot = ref entries[hash.ToHashCode() & (Size - 1)];
171
+ var entry = slot;
172
+ if (entry is not null && utf8.SequenceEqual(entry.Utf8)) return entry.Value;
173
+ var value = Encoding.UTF8.GetString(utf8);
174
+ slot = new Entry(utf8.ToArray(), value);
175
+ return value;
121
176
  }
122
177
  }
123
178
 
@@ -27,7 +27,7 @@ public static partial class Bridge
27
27
  var buf = t_responseWriter ??= new ArrayBufferWriter<byte>(256);
28
28
  buf.ResetWrittenCount();
29
29
  res.WriteAsBin(buf);
30
- WriteUnmanaged(buf.WrittenSpan, responsePtr, responseLenPtr);
30
+ WriteResponse(buf.WrittenSpan, responsePtr, responseLenPtr);
31
31
  }
32
32
  catch (Exception ex)
33
33
  {
@@ -36,6 +36,39 @@ public static partial class Bridge
36
36
  return 0;
37
37
  }
38
38
 
39
+ // Per-thread unmanaged buffer InvokeBinary responses are written to, so a call allocates none.
40
+ // The runtime reads a response and hands it to Free (which leaves this buffer alone) before it
41
+ // makes another call on the thread, including a nested one from a JS callback, so reusing it
42
+ // is safe. Large responses still get their own allocation, keeping the buffer small.
43
+ [ThreadStatic]
44
+ private static unsafe byte* t_responseBuffer;
45
+ [ThreadStatic]
46
+ private static int t_responseCapacity;
47
+ private const int MaxReusedResponse = 64 * 1024;
48
+
49
+ private static unsafe void WriteResponse(ReadOnlySpan<byte> bytes, byte** outPtr, int* outLen)
50
+ {
51
+ var needed = bytes.Length + 1;
52
+ if (needed > MaxReusedResponse)
53
+ {
54
+ WriteUnmanaged(bytes, outPtr, outLen);
55
+ return;
56
+ }
57
+ if (needed > t_responseCapacity)
58
+ {
59
+ var capacity = Math.Max(256, (int)System.Numerics.BitOperations.RoundUpToPowerOf2((uint)needed));
60
+ t_responseBuffer = (byte*)NativeMemory.Realloc(t_responseBuffer, (nuint)capacity);
61
+ t_responseCapacity = capacity;
62
+ }
63
+ var p = t_responseBuffer;
64
+ bytes.CopyTo(new Span<byte>(p, bytes.Length));
65
+ p[bytes.Length] = 0;
66
+ *outPtr = p;
67
+ *outLen = bytes.Length;
68
+ }
69
+
70
+ internal static unsafe bool IsResponseBuffer(byte* ptr) => ptr == t_responseBuffer;
71
+
39
72
  internal static DispatchResult DispatchBin(ref BinReader r)
40
73
  {
41
74
  var op = r.ReadByte();
@@ -69,8 +102,8 @@ public static partial class Bridge
69
102
 
70
103
  if (op == 0x06) // members by type
71
104
  {
72
- var typeName = r.ReadString16();
73
- var assembly = r.ReadString16();
105
+ var typeName = r.ReadName16();
106
+ var assembly = r.ReadName16();
74
107
  var type = ResolveType(NullIfEmpty(assembly), typeName)
75
108
  ?? throw new TypeLoadException($"Type not found: {typeName} (assembly: {assembly})");
76
109
  return BuildMembersResult(type);
@@ -82,7 +115,7 @@ public static partial class Bridge
82
115
  if (!s_handles.TryGetValue(handle, out var target))
83
116
  throw new KeyNotFoundException($"Invalid handle {handle}");
84
117
  var type = target?.GetType() ?? throw new InvalidOperationException("Handle is null");
85
- var method = r.ReadString16();
118
+ var method = r.ReadName16();
86
119
  var args = r.ReadArgs();
87
120
 
88
121
  if (method == "__dotnet_await__" && args.Length == 2
@@ -105,23 +138,23 @@ public static partial class Bridge
105
138
 
106
139
  if (op == 0x09) // create JS delegate
107
140
  {
108
- var delTypeName = r.ReadString16(); // "" → System.Action
141
+ var delTypeName = r.ReadName16(); // "" → System.Action
109
142
  var callbackId = r.ReadI32();
110
143
  return CreateJsDelegate(delTypeName, callbackId);
111
144
  }
112
145
 
113
146
  if (op == 0x0A) // create JS-backed subclass instance
114
147
  {
115
- var assembly = r.ReadString16();
116
- var typeName = r.ReadString16();
148
+ var assembly = r.ReadName16();
149
+ var typeName = r.ReadName16();
117
150
 
118
151
  var interfaceCount = r.ReadI32();
119
152
  var interfaceNames = interfaceCount > 0 ? new string[interfaceCount] : [];
120
- for (int i = 0; i < interfaceCount; i++) interfaceNames[i] = r.ReadString16();
153
+ for (int i = 0; i < interfaceCount; i++) interfaceNames[i] = r.ReadName16();
121
154
 
122
155
  var memberCount = r.ReadI32();
123
156
  var memberNames = memberCount > 0 ? new string[memberCount] : [];
124
- for (int i = 0; i < memberCount; i++) memberNames[i] = r.ReadString16();
157
+ for (int i = 0; i < memberCount; i++) memberNames[i] = r.ReadName16();
125
158
 
126
159
  var callbackId = r.ReadI32();
127
160
  var ctorArgs = r.HasMore ? r.ReadArgs() : [];
@@ -131,19 +164,34 @@ public static partial class Bridge
131
164
  if (op == 0x0B) // get CLR-only property by raw IInspectable ptr (CLR reflection fallback)
132
165
  {
133
166
  var instancePtr = new IntPtr(r.ReadI64());
134
- var propName = r.ReadString16();
167
+ var propName = r.ReadName16();
135
168
  return ClrGetProperty(instancePtr, propName);
136
169
  }
137
170
 
138
171
  // Static ops: 0x02 = call, 0x03 = constructor
139
- var typeNameS = r.ReadString16();
140
- var assemblyS = r.ReadString16();
172
+ var typeNameS = r.ReadName16();
173
+ var assemblyS = r.ReadName16();
141
174
  var typeS = ResolveType(NullIfEmpty(assemblyS), typeNameS)
142
175
  ?? throw new TypeLoadException($"Type not found: {typeNameS} (assembly: {assemblyS})");
143
176
 
144
177
  if (op == 0x03) // constructor
145
178
  {
146
179
  var args = r.ReadArgs();
180
+ // Constructors with the same parameter count (StringBuilder(int) / StringBuilder(string)):
181
+ // the one whose parameter types best fit the arguments.
182
+ var ctors = GetCtorOverloads(typeS, args.Length);
183
+ if (ctors.Ctors.Length > 1)
184
+ {
185
+ var best = SelectOverload(ctors.Parameters, args);
186
+ object?[]? built = null;
187
+ if (best >= 0)
188
+ {
189
+ try { built = BuildArgsBinExact(args, ctors.Parameters[best]); }
190
+ catch { /* fall back to the first constructor by arity below */ }
191
+ }
192
+ if (built is not null && ArgsFit(built, ctors.Parameters[best]))
193
+ return Box(ctors.Ctors[best].Invoke(built));
194
+ }
147
195
  var entry = GetCachedCtor(typeS, args.Length);
148
196
  if (entry.Ctor is null)
149
197
  throw new MissingMethodException(
@@ -154,7 +202,7 @@ public static partial class Bridge
154
202
  }
155
203
 
156
204
  // op == 0x02: static call
157
- var methodS = r.ReadString16();
205
+ var methodS = r.ReadName16();
158
206
  var argsS = r.ReadArgs();
159
207
  return DispatchCallBin(null, typeS, methodS, argsS, isStatic: true);
160
208
  }
@@ -166,7 +214,7 @@ public static partial class Bridge
166
214
 
167
215
 
168
216
 
169
- if (method.Length > 4
217
+ if (method.Length > 4 && args.Length == 0
170
218
  && method[0] == 'g' && method[1] == 'e' && method[2] == 't' && method[3] == '_')
171
219
  {
172
220
  var prop = GetCachedProp(type, method, 4, flags);
@@ -185,12 +233,31 @@ public static partial class Bridge
185
233
  }
186
234
  }
187
235
 
188
- // Overloads with the same parameter count (Describe(Animal) / Describe(Shape)): pick the
189
- // first whose parameter types the arguments convert to.
236
+ // Overloads with the same parameter count (Abs(int) / Abs(double), Describe(Animal) /
237
+ // Describe(Shape)): the one whose parameter types best fit the arguments.
190
238
  var overloads = GetOverloads(type, method, args.Length, flags);
191
- if (overloads.Length > 1)
239
+ var best = overloads.Methods.Length > 0 ? overloads.Select(args) : -1;
240
+ // No method with this many parameters takes these arguments: a params method may
241
+ // (String.Join(",", "a", "b", "c")).
242
+ if (best < 0 && TryParamsCall(target, type, method, args, flags, out var paramsResult))
243
+ return paramsResult;
244
+ if (overloads.Methods.Length > 1)
192
245
  {
193
- foreach (var overload in overloads)
246
+ if (best >= 0)
247
+ {
248
+ var chosen = overloads.Entry(best, type);
249
+ object?[]? built = null;
250
+ try { built = BuildArgsBin(args, chosen.Parameters); }
251
+ catch { /* an argument didn't convert: try the overloads one by one below */ }
252
+ if (built is not null)
253
+ {
254
+ if (ArgsFit(built, chosen.Parameters))
255
+ return InvokeBuilt(target, type, method, chosen, built);
256
+ if (built.Length > 0) ReturnArgs(built);
257
+ }
258
+ }
259
+ // Nothing fits by type: the first overload the arguments convert to.
260
+ foreach (var overload in overloads.Methods)
194
261
  {
195
262
  var ps = overload.GetParameters();
196
263
  object?[] built;
@@ -252,36 +319,216 @@ public static partial class Bridge
252
319
  $"Method '{method}' ({args.Length} args) not found on {type.FullName}");
253
320
  }
254
321
 
255
- var builtArgs = BuildArgsBin(args, entry.Parameters);
256
- try {
322
+ return InvokeBuilt(target, type, method, entry, BuildArgsBin(args, entry.Parameters));
323
+ }
324
+
325
+ // Runs a compiled invoker on arguments from BuildArgsBin (returned to the pool afterwards),
326
+ // retrying on the UI thread when a COM object rejects the calling thread.
327
+ private static DispatchResult InvokeBuilt(object? target, Type type, string method, DispatchEntry entry, object?[] builtArgs)
328
+ {
329
+ try {
257
330
  try
258
331
  {
259
- var res = (entry.Invoke(target, builtArgs));
332
+ var res = (entry.Invoke!(target, builtArgs));
260
333
  return Box(res);
261
334
  }
262
335
  catch (TargetInvocationException tie) when (IsMarshaledForDifferentThread(tie.InnerException))
263
336
  {
264
337
  if (Bridge.IsLogToConsole()) Console.Error.WriteLine($"[Bridge] Detected wrong-thread COM error; retrying {type.FullName}.{method} on UI thread");
265
- var res = InvokeOnUIThread(() => (entry.Invoke(target, builtArgs)));
338
+ var res = InvokeOnUIThread(() => (entry.Invoke!(target, builtArgs)));
266
339
  return Box(res);
267
340
  }
268
341
  catch (System.Runtime.InteropServices.COMException ce) when (IsMarshaledForDifferentThread(ce))
269
342
  {
270
343
  if (Bridge.IsLogToConsole()) Console.Error.WriteLine($"[Bridge] Detected COMException wrong-thread; retrying {type.FullName}.{method} on UI thread");
271
- var res = InvokeOnUIThread(() => (entry.Invoke(target, builtArgs)));
344
+ var res = InvokeOnUIThread(() => (entry.Invoke!(target, builtArgs)));
272
345
  return Box(res);
273
346
  }
274
347
  }
275
348
  finally { if (builtArgs.Length > 0) ReturnArgs(builtArgs); }
276
349
  }
277
350
 
278
- private static readonly System.Collections.Concurrent.ConcurrentDictionary<MethodKey, MethodInfo[]> s_overloadCache = new();
351
+ private static readonly System.Collections.Concurrent.ConcurrentDictionary<MethodKey, OverloadSet> s_overloadCache = new();
279
352
 
280
- private static MethodInfo[] GetOverloads(Type type, string name, int argCount, BindingFlags flags)
353
+ private static OverloadSet GetOverloads(Type type, string name, int argCount, BindingFlags flags)
281
354
  => s_overloadCache.GetOrAdd(new MethodKey(type, name, argCount, flags), static k =>
282
- k.Type.GetMethods(k.Flags)
355
+ new OverloadSet(k.Type.GetMethods(k.Flags)
283
356
  .Where(m => m.Name == k.Name && !m.IsGenericMethodDefinition && m.GetParameters().Length == k.ArgCount)
357
+ .ToArray()));
358
+
359
+ // The public methods of a type sharing a name and parameter count, with their parameters and
360
+ // (built on first use) compiled invokers.
361
+ private sealed class OverloadSet(MethodInfo[] methods)
362
+ {
363
+ public readonly MethodInfo[] Methods = methods;
364
+ private readonly ParameterInfo[][] _parameters = Array.ConvertAll(methods, m => m.GetParameters());
365
+ private readonly DispatchEntry?[] _entries = new DispatchEntry?[methods.Length];
366
+
367
+ public DispatchEntry Entry(int index, Type type) => _entries[index] ??= BuildDispatchEntry(type, Methods[index]);
368
+
369
+ public int Select(object?[] args) => SelectOverload(_parameters, args);
370
+ }
371
+
372
+ // The public constructors of a type with a given parameter count.
373
+ private sealed class CtorSet(ConstructorInfo[] ctors)
374
+ {
375
+ public readonly ConstructorInfo[] Ctors = ctors;
376
+ public readonly ParameterInfo[][] Parameters = Array.ConvertAll(ctors, c => c.GetParameters());
377
+ }
378
+
379
+ private static readonly System.Collections.Concurrent.ConcurrentDictionary<CtorKey, CtorSet> s_ctorOverloadCache = new();
380
+
381
+ private static CtorSet GetCtorOverloads(Type type, int argCount)
382
+ => s_ctorOverloadCache.GetOrAdd(new CtorKey(type, argCount), static k =>
383
+ new CtorSet(k.Type.GetConstructors(BindingFlags.Public | BindingFlags.Instance)
384
+ .Where(c => c.GetParameters().Length == k.ArgCount)
385
+ .ToArray()));
386
+
387
+ // The candidate (by its parameter list) the arguments fit best, or -1 when none can take them.
388
+ // Ties go to the first.
389
+ private static int SelectOverload(ParameterInfo[][] candidates, object?[] args)
390
+ {
391
+ int best = -1, bestScore = -1;
392
+ for (int m = 0; m < candidates.Length; m++)
393
+ {
394
+ var ps = candidates[m];
395
+ int total = 0;
396
+ for (int i = 0; i < ps.Length; i++)
397
+ {
398
+ var score = MatchScore(i < args.Length ? args[i] : null, ps[i].ParameterType);
399
+ if (score < 0) { total = -1; break; }
400
+ total += score;
401
+ }
402
+ if (total > bestScore) { bestScore = total; best = m; }
403
+ }
404
+ return best;
405
+ }
406
+
407
+ // How well a bridged argument fits a parameter type: higher is better, -1 when it can't be
408
+ // passed. An exact type beats a widening conversion, which beats a narrowing one the value
409
+ // fits, which beats object. So Abs(-0.5) is Abs(double) and Abs(-128) is Abs(int), never
410
+ // Abs(sbyte).
411
+ private static int MatchScore(object? arg, Type p)
412
+ {
413
+ if (p.IsByRef || p.IsByRefLike || p.IsPointer) return -1;
414
+ var underlying = Nullable.GetUnderlyingType(p);
415
+ if (arg is null) return !p.IsValueType || underlying is not null ? 1 : -1;
416
+ if (underlying is not null) p = underlying;
417
+ switch (arg)
418
+ {
419
+ case int i:
420
+ if (p == typeof(int)) return 10;
421
+ if (p == typeof(long)) return 9;
422
+ if (p == typeof(double)) return 8;
423
+ if (p == typeof(float) || p == typeof(decimal)) return 7;
424
+ if (p.IsEnum) return 6;
425
+ if (IsIntegerType(p)) return FitsInteger(i, p) ? 4 : -1;
426
+ return p.IsInstanceOfType(arg) ? 2 : -1;
427
+ case double d:
428
+ if (p == typeof(double)) return 10;
429
+ if (p == typeof(float)) return 8;
430
+ if (p == typeof(decimal)) return 7;
431
+ // Integral numbers that fit an int arrive as int; this is a larger one (3e9).
432
+ if (IsIntegerType(p)) return Math.Floor(d) == d && FitsInteger(d, p) ? 4 : -1;
433
+ return p.IsInstanceOfType(arg) ? 2 : -1;
434
+ case bool:
435
+ return p == typeof(bool) ? 10 : p.IsInstanceOfType(arg) ? 2 : -1;
436
+ case string s:
437
+ if (p == typeof(string)) return 10;
438
+ if (p == typeof(char)) return s.Length == 1 ? 6 : -1;
439
+ if (IsParsedFromString(p)) return 5;
440
+ return p.IsInstanceOfType(arg) ? 2 : -1;
441
+ case JsArrayValue:
442
+ if (p.IsArray) return 8;
443
+ if (p == typeof(string)) return -1;
444
+ if (typeof(System.Collections.IEnumerable).IsAssignableFrom(p)) return 6;
445
+ return p == typeof(object) ? 1 : -1;
446
+ case HandleRef h:
447
+ s_handles.TryGetValue(h.Id, out var obj);
448
+ if (obj is null) return p.IsValueType ? -1 : 1;
449
+ if (obj.GetType() == p) return 10;
450
+ return p.IsInstanceOfType(obj) ? (p == typeof(object) ? 2 : 8) : -1;
451
+ case JsFunctionRef:
452
+ if (typeof(Delegate).IsAssignableFrom(p))
453
+ return p == typeof(Delegate) || p == typeof(MulticastDelegate) ? 4 : 8;
454
+ return p == typeof(object) ? 1 : -1;
455
+ case WinRtRef:
456
+ if (p == typeof(object)) return 2;
457
+ return p.IsInterface || (p.IsClass && p != typeof(string)) ? 4 : -1;
458
+ case JsJsonValue:
459
+ if (p == typeof(object)) return 1;
460
+ if (p == typeof(string)) return 2;
461
+ return p.IsPrimitive || p.IsEnum || typeof(Delegate).IsAssignableFrom(p) ? -1 : 5;
462
+ default:
463
+ return p.IsInstanceOfType(arg) ? 2 : -1;
464
+ }
465
+ }
466
+
467
+ // Methods whose last parameter is a params array, by type, name and binding flags.
468
+ private static readonly System.Collections.Concurrent.ConcurrentDictionary<(Type, string, BindingFlags), MethodInfo[]> s_paramsCache = new();
469
+
470
+ // Calls the params method (`Join(string, params string[])`) the arguments fit best, the
471
+ // trailing ones packed into its params array. False when there is none they fit.
472
+ private static bool TryParamsCall(object? target, Type type, string method, object?[] args, BindingFlags flags, out DispatchResult result)
473
+ {
474
+ result = default;
475
+ var candidates = s_paramsCache.GetOrAdd((type, method, flags), static k =>
476
+ k.Item1.GetMethods(k.Item3)
477
+ .Where(m => m.Name == k.Item2 && !m.IsGenericMethodDefinition
478
+ && m.GetParameters() is { Length: > 0 } ps
479
+ && ps[^1].ParameterType.IsArray
480
+ && ps[^1].IsDefined(typeof(ParamArrayAttribute), false))
284
481
  .ToArray());
482
+ if (candidates.Length == 0) return false;
483
+
484
+ MethodInfo? best = null;
485
+ int bestScore = -1;
486
+ foreach (var m in candidates)
487
+ {
488
+ var ps = m.GetParameters();
489
+ var fixedCount = ps.Length - 1;
490
+ if (args.Length < fixedCount) continue;
491
+ var elementType = ps[^1].ParameterType.GetElementType()!;
492
+ int total = 0;
493
+ for (int i = 0; i < args.Length && total >= 0; i++)
494
+ {
495
+ var score = MatchScore(args[i], i < fixedCount ? ps[i].ParameterType : elementType);
496
+ total = score < 0 ? -1 : total + score;
497
+ }
498
+ if (total > bestScore) { bestScore = total; best = m; }
499
+ }
500
+ if (best is null) return false;
501
+
502
+ var parameters = best.GetParameters();
503
+ var fixedParams = parameters.Length - 1;
504
+ var element = parameters[^1].ParameterType.GetElementType()!;
505
+ var built = new object?[parameters.Length];
506
+ for (int i = 0; i < fixedParams; i++)
507
+ built[i] = CoerceBin(args[i], parameters[i].ParameterType);
508
+ var rest = Array.CreateInstance(element, args.Length - fixedParams);
509
+ for (int i = 0; i < rest.Length; i++)
510
+ rest.SetValue(CoerceBin(args[fixedParams + i], element), i);
511
+ built[^1] = rest;
512
+ result = Box(best.Invoke(target, built));
513
+ return true;
514
+ }
515
+
516
+ private static bool IsIntegerType(Type t) => Type.GetTypeCode(t) is
517
+ TypeCode.SByte or TypeCode.Byte or TypeCode.Int16 or TypeCode.UInt16 or
518
+ TypeCode.Int32 or TypeCode.UInt32 or TypeCode.Int64 or TypeCode.UInt64;
519
+
520
+ private static bool FitsInteger(double v, Type t) => Type.GetTypeCode(t) switch
521
+ {
522
+ TypeCode.SByte => v >= sbyte.MinValue && v <= sbyte.MaxValue,
523
+ TypeCode.Byte => v >= 0 && v <= byte.MaxValue,
524
+ TypeCode.Int16 => v >= short.MinValue && v <= short.MaxValue,
525
+ TypeCode.UInt16 => v >= 0 && v <= ushort.MaxValue,
526
+ TypeCode.Int32 => v >= int.MinValue && v <= int.MaxValue,
527
+ TypeCode.UInt32 => v >= 0 && v <= uint.MaxValue,
528
+ TypeCode.Int64 => v >= long.MinValue && v < 9223372036854775808.0,
529
+ TypeCode.UInt64 => v >= 0 && v < 18446744073709551616.0,
530
+ _ => false,
531
+ };
285
532
 
286
533
  private static object?[] BuildArgsBin(object?[] binArgs, ParameterInfo[] parameters)
287
534
  {
@@ -380,6 +627,10 @@ public static partial class Bridge
380
627
  var target = targetType == typeof(object) ? typeof(System.Text.Json.JsonElement) : targetType;
381
628
  return System.Text.Json.JsonSerializer.Deserialize(json.Json, target, s_jsJsonOptions);
382
629
  }
630
+ if (value is JsArrayValue array)
631
+ return CoerceArray(array.Items, targetType);
632
+ if (value is string text && IsParsedFromString(Nullable.GetUnderlyingType(targetType) ?? targetType))
633
+ return ParseString(text, Nullable.GetUnderlyingType(targetType) ?? targetType);
383
634
  if (value is JsFunctionRef fn)
384
635
  {
385
636
  var delegateType = typeof(Delegate).IsAssignableFrom(targetType)
@@ -388,7 +639,7 @@ public static partial class Bridge
388
639
  : typeof(Action);
389
640
  return MakeJsDelegate(delegateType, fn.Id);
390
641
  }
391
- if (value.GetType() == targetType) return value;
642
+ if (value.GetType() == targetType || targetType.IsInstanceOfType(value)) return value;
392
643
  var underlying = Nullable.GetUnderlyingType(targetType) ?? targetType;
393
644
  if (underlying.IsEnum)
394
645
  {
@@ -399,6 +650,61 @@ public static partial class Bridge
399
650
  catch { return value; }
400
651
  }
401
652
 
653
+ private static bool IsParsedFromString(Type t) =>
654
+ t == typeof(DateTime) || t == typeof(DateTimeOffset) || t == typeof(TimeSpan) || t == typeof(Guid);
655
+
656
+ // A string for a DateTime, DateTimeOffset, TimeSpan or Guid parameter, parsed culture-invariantly
657
+ // (a JS Date is sent as its ISO 8601 string). Left as is when it doesn't parse.
658
+ private static object ParseString(string text, Type t)
659
+ {
660
+ var invariant = System.Globalization.CultureInfo.InvariantCulture;
661
+ var roundtrip = System.Globalization.DateTimeStyles.RoundtripKind;
662
+ if (t == typeof(DateTime) && DateTime.TryParse(text, invariant, roundtrip, out var dt)) return dt;
663
+ if (t == typeof(DateTimeOffset) && DateTimeOffset.TryParse(text, invariant, roundtrip, out var dto)) return dto;
664
+ if (t == typeof(TimeSpan) && TimeSpan.TryParse(text, invariant, out var ts)) return ts;
665
+ if (t == typeof(Guid) && Guid.TryParse(text, out var g)) return g;
666
+ return text;
667
+ }
668
+
669
+ // A JS array's items as the array or collection type a parameter expects: T[], a collection
670
+ // interface (IEnumerable<T>, IList<T>, IReadOnlyList<T>) or a concrete collection with Add
671
+ // (List<T>, ObservableCollection<T>). Anything else gets object[].
672
+ private static object? CoerceArray(object?[] items, Type targetType)
673
+ {
674
+ var target = Nullable.GetUnderlyingType(targetType) ?? targetType;
675
+ var element = target.IsArray ? target.GetElementType()! : CollectionElementType(target);
676
+ if (target.IsArray || (target.IsInterface && element is not null))
677
+ {
678
+ var array = Array.CreateInstance(element!, items.Length);
679
+ for (int i = 0; i < items.Length; i++) array.SetValue(CoerceBin(items[i], element!), i);
680
+ return array;
681
+ }
682
+ if (element is not null && !target.IsAbstract && target.GetConstructor(Type.EmptyTypes) is not null)
683
+ {
684
+ var collection = Activator.CreateInstance(target)!;
685
+ var add = target.GetMethod("Add", [element]);
686
+ if (add is not null)
687
+ {
688
+ foreach (var item in items) add.Invoke(collection, [CoerceBin(item, element)]);
689
+ return collection;
690
+ }
691
+ }
692
+ var objects = new object?[items.Length];
693
+ for (int i = 0; i < items.Length; i++) objects[i] = CoerceBin(items[i], typeof(object));
694
+ return objects;
695
+ }
696
+
697
+ // T of the IEnumerable<T> a type is or implements, or null.
698
+ internal static Type? CollectionElementType(Type t)
699
+ {
700
+ if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>))
701
+ return t.GetGenericArguments()[0];
702
+ foreach (var i in t.GetInterfaces())
703
+ if (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>))
704
+ return i.GetGenericArguments()[0];
705
+ return null;
706
+ }
707
+
402
708
  // CLR reflection fallback for properties that exist only in managed code and are
403
709
  // therefore invisible to the WinRT metadata layer (e.g. App.MainWindow on a class
404
710
  // that derives from Microsoft.UI.Xaml.Application but adds CLR-only members).
@@ -175,8 +175,11 @@ public static partial class Bridge
175
175
  private static DispatchEntry BuildDispatchEntry(Type type, string name, int argCount, BindingFlags flags)
176
176
  {
177
177
  var mi = FindMethodCore(type, name, argCount, flags);
178
- if (mi is null) return DispatchEntry.Empty;
178
+ return mi is null ? DispatchEntry.Empty : BuildDispatchEntry(type, mi);
179
+ }
179
180
 
181
+ internal static DispatchEntry BuildDispatchEntry(Type type, MethodInfo mi)
182
+ {
180
183
  var parameters = mi.GetParameters();
181
184
 
182
185
  try
@@ -222,10 +225,12 @@ public static partial class Bridge
222
225
  return new CtorEntry(ctor, ctor?.GetParameters() ?? []);
223
226
  });
224
227
 
228
+ // Keyed by the accessor name as called ("get_Length") plus the prefix length, so a warm lookup
229
+ // allocates no property-name substring.
225
230
  private static PropertyInfo? GetCachedProp(Type type, string method, int prefixLen, BindingFlags flags)
226
231
  => s_propCache.GetOrAdd(
227
- new PropKey(type, method[prefixLen..], flags),
228
- static k => k.Type.GetProperty(k.Name, k.Flags));
232
+ new PropKey(type, method, prefixLen, flags),
233
+ static k => k.Type.GetProperty(k.Name[k.PrefixLen..], k.Flags));
229
234
 
230
235
  // Pooled: rented array passed to the compiled delegate (which accesses by index,
231
236
  // not by Length). Caller must return via ReturnArgs immediately after invoke.
@@ -274,24 +279,19 @@ public static partial class Bridge
274
279
  return DispatchResult.Primitive(unchecked((long)up.ToUInt64()), typeof(long));
275
280
 
276
281
  var t = value.GetType();
282
+ var info = GetBoxInfo(t);
277
283
 
278
- if (t.IsPrimitive || t == typeof(string) || t == typeof(decimal)
279
- || t == typeof(DateTime) || t == typeof(DateTimeOffset)
280
- || t == typeof(TimeSpan) || t == typeof(Guid))
284
+ if (info.Primitive)
281
285
  return DispatchResult.Primitive(value, t);
282
286
 
283
- // Arrays and other enumerable results should be marshalled as Collections
284
- // (0x07) so the runtime receives the items directly instead of a handle.
285
- if (value is System.Collections.IEnumerable enumerable && !(value is string))
286
- {
287
- return DispatchResult.Collection(enumerable);
288
- }
287
+ // Arrays are marshalled as Collections (0x07) so the runtime receives the items
288
+ // directly. Other collections (List<T>, Dictionary, ObservableCollection) stay .NET
289
+ // objects, so their methods work and native code sees the same instance.
290
+ if (info.Collection)
291
+ return DispatchResult.Collection((System.Collections.IEnumerable)value);
289
292
 
290
- if (t.IsEnum)
291
- {
292
- var ut = Enum.GetUnderlyingType(t);
293
+ if (info.EnumUnderlying is { } ut)
293
294
  return DispatchResult.Primitive(Convert.ChangeType(value, ut), ut);
294
- }
295
295
 
296
296
  // An instance of a JS subclass goes back to JS as the handle its JS object holds, so the
297
297
  // runtime hands out that same JS object. If that object was collected (its handle released),
@@ -312,9 +312,12 @@ public static partial class Bridge
312
312
  // the C#/WinRT inner object for managed WinRT subclasses (e.g. FlexboxLayout
313
313
  // subclasses), ensuring QI succeeds for all inherited WinRT interfaces.
314
314
  // The pointer is addref'd here and released on __release.
315
+ // C#/WinRT objects and JS subclass instances get it here, since the runtime wraps them
316
+ // as WinRT objects right away. Other objects get one when first passed to a WinRT API
317
+ // (GetNativePtrForHandle): making a COM wrapper costs more than the rest of the call.
315
318
  try
316
319
  {
317
- if (value != null)
320
+ if (info.WinRTNativeObject is not null || jsHolder is not null)
318
321
  {
319
322
  var p = ObtainNativePtr(value);
320
323
  if (p != IntPtr.Zero)
@@ -325,15 +328,52 @@ public static partial class Bridge
325
328
  {
326
329
  // Not a COM object or failed to obtain native pointer; ignore.
327
330
  }
328
- var typeName = t.FullName ?? t.Name;
329
- return IsAwaitable(value, t)
330
- ? DispatchResult.TaskHandle(id, typeName)
331
- : DispatchResult.Handle(id, typeName);
331
+ return info.Awaitable
332
+ ? DispatchResult.TaskHandle(id, info.TypeName)
333
+ : DispatchResult.Handle(id, info.TypeName);
332
334
  }
333
335
 
334
- private static bool IsAwaitable(object? value, Type t)
336
+ // What Box needs to know about a returned value's type, worked out once per type rather than
337
+ // by reflection on every call.
338
+ internal sealed class BoxInfo
339
+ {
340
+ public string TypeName = "";
341
+ public bool Primitive;
342
+ public bool Collection;
343
+ public Type? EnumUnderlying;
344
+ public bool Awaitable;
345
+ // WinRT.IWinRTObject.NativeObject when the type is a C#/WinRT projection (see ObtainNativePtr).
346
+ public PropertyInfo? WinRTNativeObject;
347
+ }
348
+
349
+ private static readonly System.Collections.Concurrent.ConcurrentDictionary<Type, BoxInfo> s_boxInfo = new();
350
+
351
+ internal static BoxInfo GetBoxInfo(Type t) => s_boxInfo.GetOrAdd(t, static t =>
335
352
  {
336
- if (value is Task || value is ValueTask) return true;
353
+ var info = new BoxInfo { TypeName = t.FullName ?? t.Name };
354
+ if (t.IsPrimitive || t == typeof(string) || t == typeof(decimal) || t == typeof(Guid))
355
+ {
356
+ info.Primitive = true;
357
+ return info;
358
+ }
359
+ if (t.IsArray)
360
+ {
361
+ info.Collection = true;
362
+ return info;
363
+ }
364
+ if (t.IsEnum)
365
+ {
366
+ info.EnumUnderlying = Enum.GetUnderlyingType(t);
367
+ return info;
368
+ }
369
+ info.Awaitable = IsAwaitable(t);
370
+ try { info.WinRTNativeObject = t.GetInterface("WinRT.IWinRTObject")?.GetProperty("NativeObject"); } catch { }
371
+ return info;
372
+ });
373
+
374
+ private static bool IsAwaitable(Type t)
375
+ {
376
+ if (typeof(Task).IsAssignableFrom(t) || t == typeof(ValueTask)) return true;
337
377
  if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(ValueTask<>)) return true;
338
378
  if ((t.FullName ?? "").StartsWith("Windows.Foundation.IAsync", StringComparison.Ordinal)) return true;
339
379
  foreach (var iface in t.GetInterfaces())
@@ -360,6 +400,79 @@ public static partial class Bridge
360
400
  return "class";
361
401
  }
362
402
 
403
+ // What a .NET collection supports, for the JS side to make it array-like: 1 = enumerable
404
+ // (for...of, map, ...), 2 = has a count (length), 4 = indexed by position (list[0]).
405
+ public static int CollectionKind(object? value)
406
+ {
407
+ if (value is null or string || value is not System.Collections.IEnumerable) return 0;
408
+ var info = GetCollectionInfo(value.GetType());
409
+ return 1 | (info.Count is not null ? 2 : 0) | (info.Indexer is not null ? 4 : 0);
410
+ }
411
+
412
+ // A collection's items, as an array the runtime copies into a JS array.
413
+ public static object?[] CollectionItems(object? value)
414
+ {
415
+ if (value is not System.Collections.IEnumerable items) return [];
416
+ var list = new List<object?>();
417
+ foreach (var item in items) list.Add(item);
418
+ return list.ToArray();
419
+ }
420
+
421
+ public static int CollectionCount(object? value)
422
+ {
423
+ if (value is System.Collections.ICollection c) return c.Count;
424
+ var count = value is null ? null : GetCollectionInfo(value.GetType()).Count;
425
+ return count?.GetValue(value) is int n ? n : CollectionItems(value).Length;
426
+ }
427
+
428
+ // The item at `index`, or null past either end (as a JS array gives undefined).
429
+ public static object? ItemAt(object? value, int index)
430
+ {
431
+ if (index < 0 || index >= CollectionCount(value)) return null;
432
+ if (value is System.Collections.IList list) return list[index];
433
+ var indexer = value is null ? null : GetCollectionInfo(value.GetType()).Indexer;
434
+ if (indexer is null) throw new NotSupportedException($"{value?.GetType().FullName} is not indexed by position");
435
+ return indexer.GetValue(value, [index]);
436
+ }
437
+
438
+ public static void SetItemAt(object? value, int index, object? item)
439
+ {
440
+ var info = value is null ? null : GetCollectionInfo(value.GetType());
441
+ if (info?.Indexer is not { CanWrite: true } indexer)
442
+ throw new NotSupportedException($"{value?.GetType().FullName} has no settable items");
443
+ indexer.SetValue(value, CoerceBin(item, indexer.PropertyType), [index]);
444
+ }
445
+
446
+ // The count property and positional indexer of a collection type, from IList<T> /
447
+ // IReadOnlyList<T> / ICollection<T> / IReadOnlyCollection<T> (or their non-generic forms).
448
+ internal sealed class CollectionInfo
449
+ {
450
+ public PropertyInfo? Count;
451
+ public PropertyInfo? Indexer;
452
+ }
453
+
454
+ private static readonly System.Collections.Concurrent.ConcurrentDictionary<Type, CollectionInfo> s_collectionInfo = new();
455
+
456
+ private static CollectionInfo GetCollectionInfo(Type t) => s_collectionInfo.GetOrAdd(t, static t =>
457
+ {
458
+ var info = new CollectionInfo();
459
+ Type[] interfaces = t.IsInterface ? [t, .. t.GetInterfaces()] : t.GetInterfaces();
460
+ foreach (var i in interfaces)
461
+ {
462
+ var definition = i.IsGenericType ? i.GetGenericTypeDefinition() : i;
463
+ if (info.Indexer is null && (definition == typeof(IList<>) || definition == typeof(IReadOnlyList<>) || definition == typeof(System.Collections.IList)))
464
+ info.Indexer = i.GetProperty("Item");
465
+ if (info.Count is null && (definition == typeof(ICollection<>) || definition == typeof(IReadOnlyCollection<>) || definition == typeof(System.Collections.ICollection)))
466
+ info.Count = i.GetProperty("Count");
467
+ }
468
+ // A writable IList<T> indexer beats a read-only IReadOnlyList<T> one.
469
+ if (info.Indexer is { CanWrite: false })
470
+ foreach (var i in interfaces)
471
+ if (i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IList<>))
472
+ info.Indexer = i.GetProperty("Item");
473
+ return info;
474
+ });
475
+
363
476
  /// `value instanceof SomeType` for a .NET object that was not constructed from JS.
364
477
  public static bool IsInstanceOf(object? value, string typeName)
365
478
  {
@@ -373,8 +486,8 @@ public static partial class Bridge
373
486
  const BindingFlags inst = BindingFlags.Public | BindingFlags.Instance;
374
487
  const BindingFlags stat = BindingFlags.Public | BindingFlags.Static;
375
488
 
376
- var instProps = t.GetProperties(inst);
377
- var statProps = t.GetProperties(stat);
489
+ var instProps = Array.FindAll(t.GetProperties(inst), p => p.GetIndexParameters().Length == 0);
490
+ var statProps = Array.FindAll(t.GetProperties(stat), p => p.GetIndexParameters().Length == 0);
378
491
 
379
492
  return DispatchResult.Members(
380
493
  methods: t.GetMethods(inst).Where(m => !m.IsSpecialName).Select(m => m.Name).Distinct().ToArray(),
@@ -391,8 +504,8 @@ public static partial class Bridge
391
504
  internal static Type? ResolveType(string? assemblyName, string? typeName)
392
505
  {
393
506
  if (string.IsNullOrEmpty(typeName)) return null;
394
- var key = string.IsNullOrEmpty(assemblyName) ? typeName : $"{assemblyName}|{typeName}";
395
- return s_typeCache.GetOrAdd(key, _ => ResolveTypeCore(assemblyName, typeName));
507
+ return s_typeCache.GetOrAdd((assemblyName ?? "", typeName),
508
+ static k => ResolveTypeCore(k.Assembly.Length == 0 ? null : k.Assembly, k.Type));
396
509
  }
397
510
 
398
511
  private static Type? ResolveTypeInAssembly(Assembly asm, string typeName, int lastDot, string shortName)
@@ -222,7 +222,8 @@ public static partial class Bridge
222
222
  if (arg is Enum e) { w.WriteByte(0x04); w.WriteF64(Convert.ToDouble(e)); return; }
223
223
  if (arg is decimal m){ w.WriteByte(0x04); w.WriteF64((double)m); return; }
224
224
  // A struct (Windows.Foundation.Size, a C# record struct, ...) arrives as a plain JS object.
225
- if (arg.GetType().IsValueType && !arg.GetType().IsPrimitive)
225
+ if (arg.GetType().IsValueType && !arg.GetType().IsPrimitive
226
+ && arg is not DateTime && arg is not DateTimeOffset && arg is not TimeSpan)
226
227
  {
227
228
  string? json = null;
228
229
  try { json = System.Text.Json.JsonSerializer.Serialize(arg, arg.GetType(), s_jsJsonOptions); } catch { }
@@ -31,8 +31,8 @@ public static partial class Bridge
31
31
  internal static unsafe delegate* unmanaged[Cdecl]<int, byte*, int, byte**, int*, void>
32
32
  s_jsInvoker;
33
33
 
34
- private static readonly ConcurrentDictionary<string, Type?> s_typeCache
35
- = new(StringComparer.Ordinal);
34
+ // Keyed by (assembly name or "", type name): no key string is built per lookup.
35
+ private static readonly ConcurrentDictionary<(string Assembly, string Type), Type?> s_typeCache = new();
36
36
  private static readonly ConcurrentDictionary<MethodKey, DispatchEntry> s_methodCache = new();
37
37
  private static readonly ConcurrentDictionary<PropKey, PropertyInfo?> s_propCache = new();
38
38
  private static readonly ConcurrentDictionary<CtorKey, CtorEntry> s_ctorCache = new();
@@ -53,6 +53,11 @@ public static partial class Bridge
53
53
  s_methodCache.Clear();
54
54
  s_propCache.Clear();
55
55
  s_ctorCache.Clear();
56
+ s_overloadCache.Clear();
57
+ s_ctorOverloadCache.Clear();
58
+ s_boxInfo.Clear();
59
+ s_paramsCache.Clear();
60
+ s_collectionInfo.Clear();
56
61
  s_handles.Clear();
57
62
  s_nativePtrs.Clear();
58
63
  s_nativePtrsReverse.Clear();
@@ -612,14 +617,13 @@ public static partial class Bridge
612
617
  // QueryInterface will succeed for all WinRT interfaces inherited by the type.
613
618
  try
614
619
  {
615
- var winrtObjType = obj.GetType().GetInterface("WinRT.IWinRTObject");
616
- if (winrtObjType != null)
620
+ var nativeObjProp = GetBoxInfo(obj.GetType()).WinRTNativeObject;
621
+ if (nativeObjProp != null)
617
622
  {
618
- var nativeObjProp = winrtObjType.GetProperty("NativeObject");
619
- var nativeObj = nativeObjProp?.GetValue(obj);
623
+ var nativeObj = nativeObjProp.GetValue(obj);
620
624
  if (nativeObj != null)
621
625
  {
622
- var thisPtrProp = nativeObj.GetType().GetProperty("ThisPtr");
626
+ var thisPtrProp = s_thisPtrProps.GetOrAdd(nativeObj.GetType(), static t => t.GetProperty("ThisPtr"));
623
627
  if (thisPtrProp?.GetValue(nativeObj) is IntPtr thisPtr && thisPtr != IntPtr.Zero)
624
628
  {
625
629
  Marshal.AddRef(thisPtr);
@@ -656,6 +660,9 @@ public static partial class Bridge
656
660
  return iunknown;
657
661
  }
658
662
 
663
+ // ThisPtr of C#/WinRT's IObjectReference types, by type.
664
+ private static readonly ConcurrentDictionary<Type, PropertyInfo?> s_thisPtrProps = new();
665
+
659
666
  private static MethodInfo? s_marshalInspectableFromManaged;
660
667
 
661
668
  // WinRT.MarshalInspectable<object>.FromManaged(obj), found by reflection (the bridge has no
@@ -759,6 +766,6 @@ public static partial class Bridge
759
766
  CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
760
767
  public static unsafe void Free(byte* ptr)
761
768
  {
762
- if (ptr != null) Marshal.FreeHGlobal((IntPtr)ptr);
769
+ if (ptr != null && !IsResponseBuffer(ptr)) Marshal.FreeHGlobal((IntPtr)ptr);
763
770
  }
764
771
  }
@@ -30,7 +30,7 @@ internal sealed record InvokeRequest(
30
30
  // equality and reference hash codes are sufficient and allocation-free.
31
31
 
32
32
  internal readonly record struct MethodKey(Type Type, string Name, int ArgCount, BindingFlags Flags);
33
- internal readonly record struct PropKey(Type Type, string Name, BindingFlags Flags);
33
+ internal readonly record struct PropKey(Type Type, string Name, int PrefixLen, BindingFlags Flags);
34
34
  internal readonly record struct CtorKey(Type Type, int ArgCount);
35
35
 
36
36
  internal readonly struct DispatchEntry(Func<object?, object?[], object?>? invoke, ParameterInfo[] parameters)
@@ -272,7 +272,7 @@ internal readonly struct DispatchResult
272
272
  WritePrimitiveBin(ref w, value);
273
273
  return;
274
274
  }
275
- if (value is IEnumerable enumerable)
275
+ if (value is Array enumerable)
276
276
  {
277
277
  var items = new List<object?>();
278
278
  foreach (var item in enumerable) items.Add(item);
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nativescript/windows-hermes",
3
- "version": "1.0.0-beta.8",
3
+ "version": "1.0.0-beta.9",
4
4
  "description": "NativeScript Windows runtime (Hermes engine) with a WinUI 3 app template",
5
5
  "keywords": [
6
6
  "NativeScript",