@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.
- package/framework/dotnet-bridge/BinaryProtocol.cs +74 -19
- package/framework/dotnet-bridge/Bridge.BinaryDispatch.cs +333 -27
- package/framework/dotnet-bridge/Bridge.Dispatch.cs +140 -27
- package/framework/dotnet-bridge/Bridge.JsDelegate.cs +2 -1
- package/framework/dotnet-bridge/Bridge.cs +15 -8
- package/framework/dotnet-bridge/DispatchTypes.cs +2 -2
- package/framework/libs/x64/nativescript.dll +0 -0
- package/framework/tools/ManifestMerger/ManifestMerger.dll +0 -0
- package/framework/tools/ManifestMerger/ManifestMerger.pdb +0 -0
- package/framework/tools/dotnet-tool-x64.exe +0 -0
- package/framework/tools/dotnet-tool.exe +0 -0
- package/framework/tools/dotnet-typings-gen/dotnet-typings-gen.dll +0 -0
- package/framework/tools/dotnet-typings-gen/dotnet-typings-gen.exe +0 -0
- package/framework/tools/dotnet-typings-gen/dotnet-typings-gen.pdb +0 -0
- package/framework/tools/nsbundle_pack-x64.exe +0 -0
- package/framework/tools/nsbundle_pack.exe +0 -0
- package/framework/tools/typings-generator-x64.exe +0 -0
- package/framework/tools/typings-generator.exe +0 -0
- package/package.json +1 -1
|
@@ -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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
|
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
|
-
|
|
105
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
73
|
-
var assembly = r.
|
|
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.
|
|
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.
|
|
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.
|
|
116
|
-
var typeName = r.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
140
|
-
var assemblyS = r.
|
|
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.
|
|
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 (
|
|
189
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
256
|
-
|
|
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,
|
|
351
|
+
private static readonly System.Collections.Concurrent.ConcurrentDictionary<MethodKey, OverloadSet> s_overloadCache = new();
|
|
279
352
|
|
|
280
|
-
private static
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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
|
|
284
|
-
//
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
return DispatchResult.Collection(
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
395
|
-
|
|
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
|
-
|
|
35
|
-
|
|
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
|
|
616
|
-
if (
|
|
620
|
+
var nativeObjProp = GetBoxInfo(obj.GetType()).WinRTNativeObject;
|
|
621
|
+
if (nativeObjProp != null)
|
|
617
622
|
{
|
|
618
|
-
var
|
|
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
|
|
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
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|