@nativescript/windows-jsc 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +55 -0
  2. package/framework/__PROJECT_NAME__/App.xaml +13 -0
  3. package/framework/__PROJECT_NAME__/App.xaml.cs +240 -0
  4. package/framework/__PROJECT_NAME__/Assets/SplashScreen.png +0 -0
  5. package/framework/__PROJECT_NAME__/Assets/Square150x150Logo.png +0 -0
  6. package/framework/__PROJECT_NAME__/Assets/Square44x44Logo.png +0 -0
  7. package/framework/__PROJECT_NAME__/Assets/StoreLogo.png +0 -0
  8. package/framework/__PROJECT_NAME__/Assets/Wide310x150Logo.png +0 -0
  9. package/framework/__PROJECT_NAME__/CrashDiagnostics.cs +281 -0
  10. package/framework/__PROJECT_NAME__/Directory.Build.props +15 -0
  11. package/framework/__PROJECT_NAME__/Directory.Build.targets +106 -0
  12. package/framework/__PROJECT_NAME__/Package.appxmanifest +35 -0
  13. package/framework/__PROJECT_NAME__/Program.cs +34 -0
  14. package/framework/__PROJECT_NAME__/Properties/Default.rd.xml +9 -0
  15. package/framework/__PROJECT_NAME__/RuntimeHost.cs +359 -0
  16. package/framework/__PROJECT_NAME__/__PROJECT_NAME__.csproj +263 -0
  17. package/framework/dotnet-bridge/BinaryProtocol.cs +161 -0
  18. package/framework/dotnet-bridge/Bridge.BinaryDispatch.cs +373 -0
  19. package/framework/dotnet-bridge/Bridge.Dispatch.cs +597 -0
  20. package/framework/dotnet-bridge/Bridge.JsDelegate.cs +244 -0
  21. package/framework/dotnet-bridge/Bridge.Proxy.cs +573 -0
  22. package/framework/dotnet-bridge/Bridge.TaskHelper.cs +207 -0
  23. package/framework/dotnet-bridge/Bridge.cs +724 -0
  24. package/framework/dotnet-bridge/DispatchTypes.cs +333 -0
  25. package/framework/dotnet-bridge/DotNetBridge.csproj +30 -0
  26. package/framework/dotnet-bridge/publish_build/DotNetBridge.deps.json +23 -0
  27. package/framework/dotnet-bridge/publish_build/DotNetBridge.dll +0 -0
  28. package/framework/dotnet-bridge/publish_build/DotNetBridge.pdb +0 -0
  29. package/framework/dotnet-bridge/publish_build/DotNetBridge.runtimeconfig.json +14 -0
  30. package/framework/libs/x64/JavaScriptCore.dll +0 -0
  31. package/framework/libs/x64/icudt77.dll +0 -0
  32. package/framework/libs/x64/icuin77.dll +0 -0
  33. package/framework/libs/x64/icuuc77.dll +0 -0
  34. package/framework/libs/x64/nativescript.dll +0 -0
  35. package/framework/tools/ManifestMerger/ManifestMerger.deps.json +24 -0
  36. package/framework/tools/ManifestMerger/ManifestMerger.dll +0 -0
  37. package/framework/tools/ManifestMerger/ManifestMerger.pdb +0 -0
  38. package/framework/tools/dotnet-tool-arm64.exe +0 -0
  39. package/framework/tools/dotnet-tool-x64.exe +0 -0
  40. package/framework/tools/dotnet-tool.exe +0 -0
  41. package/framework/tools/dotnet-typings-gen/dotnet-typings-gen.deps.json +23 -0
  42. package/framework/tools/dotnet-typings-gen/dotnet-typings-gen.dll +0 -0
  43. package/framework/tools/dotnet-typings-gen/dotnet-typings-gen.exe +0 -0
  44. package/framework/tools/dotnet-typings-gen/dotnet-typings-gen.pdb +0 -0
  45. package/framework/tools/dotnet-typings-gen/dotnet-typings-gen.runtimeconfig.json +13 -0
  46. package/framework/tools/sbg-arm64.exe +0 -0
  47. package/framework/tools/sbg-x64.exe +0 -0
  48. package/framework/tools/sbg.exe +0 -0
  49. package/framework/tools/typings-generator-arm64.exe +0 -0
  50. package/framework/tools/typings-generator-x64.exe +0 -0
  51. package/framework/tools/typings-generator.exe +0 -0
  52. package/package.json +43 -0
@@ -0,0 +1,597 @@
1
+ using System;
2
+ using System.Buffers;
3
+ using System.Collections;
4
+ using System.Collections.Generic;
5
+ using System.IO;
6
+ using System.Linq;
7
+ using System.Linq.Expressions;
8
+ using System.Reflection;
9
+ using System.Runtime.Loader;
10
+ using System.Runtime.InteropServices;
11
+ using System.Text.Json;
12
+ using System.Threading;
13
+ using System.Threading.Tasks;
14
+
15
+ namespace NativeScriptBridge;
16
+
17
+ public static partial class Bridge
18
+ {
19
+ internal static DispatchResult Dispatch(InvokeRequest req)
20
+ {
21
+ var method = req.Method ?? throw new ArgumentException("Method is required");
22
+
23
+ if (method == "__release" && req.Handle.HasValue)
24
+ {
25
+ var id = req.Handle.Value;
26
+ s_handles.TryRemove(id, out _);
27
+ if (s_nativePtrs.TryRemove(id, out var nativePtr))
28
+ {
29
+ RemoveNativePtr(id, nativePtr);
30
+ try
31
+ {
32
+ Marshal.Release(nativePtr);
33
+ }
34
+ catch
35
+ {
36
+ }
37
+ }
38
+
39
+ return DispatchResult.Void;
40
+ }
41
+
42
+ object? target = null;
43
+ Type type;
44
+
45
+ if (req.Handle.HasValue)
46
+ {
47
+ if (!s_handles.TryGetValue(req.Handle.Value, out target))
48
+ throw new KeyNotFoundException($"Invalid handle {req.Handle.Value}");
49
+ type = target?.GetType() ?? throw new InvalidOperationException("Handle refers to null");
50
+ }
51
+ else
52
+ {
53
+ type = ResolveType(req.Assembly, req.TypeName)
54
+ ?? throw new TypeLoadException($"Type not found: {req.TypeName} (assembly: {req.Assembly})");
55
+ }
56
+
57
+ if (method == "__members__")
58
+ return BuildMembersResult(type);
59
+
60
+ if (method == "__dotnet_await__" && req.Handle.HasValue)
61
+ {
62
+ var a = req.Args ?? [];
63
+ if (a.Length >= 2)
64
+ {
65
+ var resolveId = (int)a[0].GetInt64();
66
+ var rejectId = (int)a[1].GetInt64();
67
+ ScheduleTaskContinuation(req.Handle.Value, resolveId, rejectId);
68
+ }
69
+ return DispatchResult.Void;
70
+ }
71
+
72
+ if (method == ".ctor")
73
+ {
74
+ var argElems = req.Args ?? [];
75
+ var entry = GetCachedCtor(type, argElems.Length);
76
+ if (entry.Ctor is null)
77
+ throw new MissingMethodException(
78
+ $"No public constructor on {type.FullName} for {argElems.Length} args");
79
+ // ConstructorInfo.Invoke validates args.Length == paramCount exactly — no pool.
80
+ var ctorArgs = BuildArgsExact(argElems, entry.Parameters);
81
+ return Box(entry.Ctor.Invoke(ctorArgs));
82
+ }
83
+
84
+ var isStatic = target is null;
85
+ var flags = (isStatic ? BindingFlags.Static : BindingFlags.Instance) | BindingFlags.Public;
86
+
87
+ if (method.Length > 4)
88
+ {
89
+ if (method[0] == 'g' && method[1] == 'e' && method[2] == 't' && method[3] == '_')
90
+ {
91
+ var prop = GetCachedProp(type, method, 4, flags);
92
+ if (prop is not null) return Box(prop.GetValue(target));
93
+ }
94
+ else if (method[0] == 's' && method[1] == 'e' && method[2] == 't' && method[3] == '_'
95
+ && req.Args?.Length == 1)
96
+ {
97
+ var prop = GetCachedProp(type, method, 4, flags);
98
+ if (prop is not null)
99
+ {
100
+ prop.SetValue(target, Coerce(req.Args[0], prop.PropertyType));
101
+ return DispatchResult.Void;
102
+ }
103
+ }
104
+ }
105
+
106
+ var args = req.Args ?? [];
107
+ var dispEntry = GetCachedMethod(type, method, args.Length, flags);
108
+ if (dispEntry.Invoke is null)
109
+ {
110
+ // Fallback: try methods with the same name (different signatures).
111
+ var candidates = type.GetMethods(flags).Where(m => m.Name == method && !m.IsSpecialName);
112
+ foreach (var m in candidates)
113
+ {
114
+ var parameters = m.GetParameters();
115
+ var built = BuildArgs(args, parameters);
116
+ try
117
+ {
118
+ var res = AwaitIfTask(m.Invoke(target, built));
119
+ return Box(res);
120
+ }
121
+ catch (TargetInvocationException tie) when (IsMarshaledForDifferentThread(tie.InnerException))
122
+ {
123
+ if (Bridge.IsLogToConsole()) Console.Error.WriteLine($"[Bridge] Detected wrong-thread COM error; retrying {type.FullName}.{m.Name} on UI thread");
124
+ try
125
+ {
126
+ var res = InvokeOnUIThread(() => AwaitIfTask(m.Invoke(target, built)));
127
+ return Box(res);
128
+ }
129
+ catch { /* retry failed — try next candidate */ }
130
+ }
131
+ catch (System.Runtime.InteropServices.COMException ce) when (IsMarshaledForDifferentThread(ce))
132
+ {
133
+ if (Bridge.IsLogToConsole()) Console.Error.WriteLine($"[Bridge] Detected COMException wrong-thread; retrying {type.FullName}.{m.Name} on UI thread");
134
+ try
135
+ {
136
+ var res = InvokeOnUIThread(() => AwaitIfTask(m.Invoke(target, built)));
137
+ return Box(res);
138
+ }
139
+ catch { /* retry failed — try next candidate */ }
140
+ }
141
+ finally { if (built.Length > 0) ReturnArgs(built); }
142
+ }
143
+ throw new MissingMethodException(
144
+ $"Method '{method}' ({args.Length} args) not found on {type.FullName}");
145
+ }
146
+
147
+ var builtArgs = BuildArgs(args, dispEntry.Parameters);
148
+ try {
149
+ try
150
+ {
151
+ var res = AwaitIfTask(dispEntry.Invoke(target, builtArgs));
152
+ return Box(res);
153
+ }
154
+ catch (TargetInvocationException tie) when (IsMarshaledForDifferentThread(tie.InnerException))
155
+ {
156
+ if (Bridge.IsLogToConsole()) Console.Error.WriteLine($"[Bridge] Detected wrong-thread COM error; retrying {type.FullName}.{method} on UI thread");
157
+ var res = InvokeOnUIThread(() => AwaitIfTask(dispEntry.Invoke(target, builtArgs)));
158
+ return Box(res);
159
+ }
160
+ catch (System.Runtime.InteropServices.COMException ce) when (IsMarshaledForDifferentThread(ce))
161
+ {
162
+ if (Bridge.IsLogToConsole()) Console.Error.WriteLine($"[Bridge] Detected COMException wrong-thread; retrying {type.FullName}.{method} on UI thread");
163
+ var res = InvokeOnUIThread(() => AwaitIfTask(dispEntry.Invoke(target, builtArgs)));
164
+ return Box(res);
165
+ }
166
+ }
167
+ finally { if (builtArgs.Length > 0) ReturnArgs(builtArgs); }
168
+ }
169
+
170
+ private static DispatchEntry GetCachedMethod(Type type, string name, int argCount, BindingFlags flags)
171
+ => s_methodCache.GetOrAdd(
172
+ new MethodKey(type, name, argCount, flags),
173
+ static k => BuildDispatchEntry(k.Type, k.Name, k.ArgCount, k.Flags));
174
+
175
+ private static DispatchEntry BuildDispatchEntry(Type type, string name, int argCount, BindingFlags flags)
176
+ {
177
+ var mi = FindMethodCore(type, name, argCount, flags);
178
+ if (mi is null) return DispatchEntry.Empty;
179
+
180
+ var parameters = mi.GetParameters();
181
+
182
+ try
183
+ {
184
+ var targetParam = Expression.Parameter(typeof(object), "t");
185
+ var argsParam = Expression.Parameter(typeof(object?[]), "a");
186
+
187
+ var typedArgs = parameters.Select((p, i) =>
188
+ (Expression)Expression.Convert(
189
+ Expression.ArrayIndex(argsParam, Expression.Constant(i)),
190
+ p.ParameterType)).ToArray();
191
+
192
+ Expression body = mi.IsStatic
193
+ ? Expression.Call(mi, typedArgs)
194
+ : Expression.Call(Expression.Convert(targetParam, type), mi, typedArgs);
195
+
196
+ if (body.Type == typeof(void))
197
+ body = Expression.Block(typeof(object), body, Expression.Constant(null, typeof(object)));
198
+ else if (body.Type != typeof(object))
199
+ body = Expression.Convert(body, typeof(object));
200
+
201
+ var fn = Expression.Lambda<Func<object?, object?[], object?>>(
202
+ body, targetParam, argsParam).Compile();
203
+
204
+ return new DispatchEntry(fn, parameters);
205
+ }
206
+ catch
207
+ {
208
+ // mi.Invoke validates args.Length == paramCount; slice the rented buffer if needed.
209
+ int pc = parameters.Length;
210
+ return new DispatchEntry(
211
+ (t, a) => mi.Invoke(t, a.Length == pc ? a : a[..pc]),
212
+ parameters);
213
+ }
214
+ }
215
+
216
+ private static CtorEntry GetCachedCtor(Type type, int argCount)
217
+ => s_ctorCache.GetOrAdd(new CtorKey(type, argCount), static k =>
218
+ {
219
+ var ctors = k.Type.GetConstructors(BindingFlags.Public | BindingFlags.Instance);
220
+ var ctor = Array.Find(ctors, c => c.GetParameters().Length == k.ArgCount)
221
+ ?? ctors.FirstOrDefault();
222
+ return new CtorEntry(ctor, ctor?.GetParameters() ?? []);
223
+ });
224
+
225
+ private static PropertyInfo? GetCachedProp(Type type, string method, int prefixLen, BindingFlags flags)
226
+ => s_propCache.GetOrAdd(
227
+ new PropKey(type, method[prefixLen..], flags),
228
+ static k => k.Type.GetProperty(k.Name, k.Flags));
229
+
230
+ // Pooled: rented array passed to the compiled delegate (which accesses by index,
231
+ // not by Length). Caller must return via ReturnArgs immediately after invoke.
232
+ private static object?[] BuildArgs(JsonElement[] elements, ParameterInfo[] parameters)
233
+ {
234
+ if (parameters.Length == 0) return [];
235
+ var args = ArrayPool<object?>.Shared.Rent(parameters.Length);
236
+ for (int i = 0; i < parameters.Length && i < elements.Length; i++)
237
+ args[i] = Coerce(elements[i], parameters[i].ParameterType);
238
+ return args;
239
+ }
240
+
241
+ // Exact: required by ConstructorInfo.Invoke / MethodInfo.Invoke which validate
242
+ // args.Length == paramCount and would throw on an oversized rented buffer.
243
+ private static object?[] BuildArgsExact(JsonElement[] elements, ParameterInfo[] parameters)
244
+ {
245
+ if (parameters.Length == 0) return [];
246
+ var args = new object?[parameters.Length];
247
+ for (int i = 0; i < parameters.Length && i < elements.Length; i++)
248
+ args[i] = Coerce(elements[i], parameters[i].ParameterType);
249
+ return args;
250
+ }
251
+
252
+ private static void ReturnArgs(object?[] args) =>
253
+ ArrayPool<object?>.Shared.Return(args, clearArray: true);
254
+
255
+ private static object? Coerce(JsonElement el, Type targetType)
256
+ {
257
+ if (el.ValueKind == JsonValueKind.Null) return null;
258
+ if (el.ValueKind == JsonValueKind.Object && el.TryGetProperty("__handle", out var h))
259
+ {
260
+ s_handles.TryGetValue(h.GetInt32(), out var obj);
261
+ return obj;
262
+ }
263
+ return el.Deserialize(targetType, s_coerceOpts);
264
+ }
265
+
266
+ private static object? AwaitIfTask(object? value)
267
+ {
268
+ if (value is null) return null;
269
+
270
+ if (value is Task task)
271
+ {
272
+ task.GetAwaiter().GetResult();
273
+ return TaskResultCache.GetResult(task);
274
+ }
275
+
276
+ if (value is ValueTask vt)
277
+ {
278
+ vt.AsTask().GetAwaiter().GetResult();
279
+ return null;
280
+ }
281
+
282
+ var type = value.GetType();
283
+ if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ValueTask<>))
284
+ {
285
+ var innerTask = ValueTaskAsTaskCache.AsTask(type, value);
286
+ innerTask.GetAwaiter().GetResult();
287
+ if (innerTask.IsFaulted) throw innerTask.Exception!.InnerException ?? innerTask.Exception;
288
+ return TaskResultCache.GetResult(innerTask);
289
+ }
290
+
291
+ return value;
292
+ }
293
+
294
+ private static DispatchResult Box(object? value)
295
+ {
296
+ if (value is null) return DispatchResult.Void;
297
+ // Treat pointer-sized IntPtr/UIntPtr as primitive numeric returns so
298
+ // the runtime receives a numeric pointer value instead of a handle.
299
+ if (value is IntPtr ip)
300
+ return DispatchResult.Primitive(ip.ToInt64(), typeof(long));
301
+ if (value is UIntPtr up)
302
+ return DispatchResult.Primitive(unchecked((long)up.ToUInt64()), typeof(long));
303
+
304
+ var t = value.GetType();
305
+
306
+ if (t.IsPrimitive || t == typeof(string) || t == typeof(decimal)
307
+ || t == typeof(DateTime) || t == typeof(DateTimeOffset)
308
+ || t == typeof(TimeSpan) || t == typeof(Guid))
309
+ return DispatchResult.Primitive(value, t);
310
+
311
+ // Arrays and other enumerable results should be marshalled as Collections
312
+ // (0x07) so the runtime receives the items directly instead of a handle.
313
+ if (value is System.Collections.IEnumerable enumerable && !(value is string))
314
+ {
315
+ return DispatchResult.Collection(enumerable);
316
+ }
317
+
318
+ if (t.IsEnum)
319
+ {
320
+ var ut = Enum.GetUnderlyingType(t);
321
+ return DispatchResult.Primitive(Convert.ChangeType(value, ut), ut);
322
+ }
323
+
324
+
325
+
326
+ var id = Interlocked.Increment(ref s_nextHandle);
327
+ s_handles[id] = value;
328
+
329
+ // Try to obtain a canonical IInspectable/IUnknown pointer for COM/WinRT objects
330
+ // so the runtime can call native vtable methods directly.
331
+ // ObtainNativePtr prefers IInspectable for WinRT-compatible types and uses
332
+ // the C#/WinRT inner object for managed WinRT subclasses (e.g. FlexboxLayout
333
+ // subclasses), ensuring QI succeeds for all inherited WinRT interfaces.
334
+ // The pointer is addref'd here and released on __release.
335
+ try
336
+ {
337
+ if (value != null)
338
+ {
339
+ var p = ObtainNativePtr(value);
340
+ if (p != IntPtr.Zero)
341
+ StoreNativePtr(id, p);
342
+ }
343
+ }
344
+ catch
345
+ {
346
+ // Not a COM object or failed to obtain native pointer; ignore.
347
+ }
348
+ var typeName = t.FullName ?? t.Name;
349
+ return IsAwaitable(value, t)
350
+ ? DispatchResult.TaskHandle(id, typeName)
351
+ : DispatchResult.Handle(id, typeName);
352
+ }
353
+
354
+ private static bool IsAwaitable(object value, Type t)
355
+ {
356
+ if (value is Task || value is ValueTask) return true;
357
+ if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(ValueTask<>)) return true;
358
+ if ((t.FullName ?? "").StartsWith("Windows.Foundation.IAsync", StringComparison.Ordinal)) return true;
359
+ foreach (var iface in t.GetInterfaces())
360
+ if ((iface.FullName ?? "").StartsWith("Windows.Foundation.IAsync", StringComparison.Ordinal)) return true;
361
+ return t.GetMethod("GetAwaiter",
362
+ BindingFlags.Public | BindingFlags.Instance,
363
+ null, Type.EmptyTypes, null) is not null;
364
+ }
365
+
366
+ internal static DispatchResult BuildMembersResult(Type t)
367
+ {
368
+ const BindingFlags inst = BindingFlags.Public | BindingFlags.Instance;
369
+ const BindingFlags stat = BindingFlags.Public | BindingFlags.Static;
370
+
371
+ var instProps = t.GetProperties(inst);
372
+ var statProps = t.GetProperties(stat);
373
+
374
+ return DispatchResult.Members(
375
+ methods: t.GetMethods(inst).Where(m => !m.IsSpecialName).Select(m => m.Name).Distinct().ToArray(),
376
+ props: instProps.Where(p => p.GetGetMethod() != null).Select(p => p.Name).Distinct().ToArray(),
377
+ staticMethods: t.GetMethods(stat).Where(m => !m.IsSpecialName).Select(m => m.Name).Distinct().ToArray(),
378
+ staticProps: statProps.Where(p => p.GetGetMethod() != null).Select(p => p.Name).Distinct().ToArray(),
379
+ readonlyProps: instProps.Where(p => p.GetGetMethod() != null && p.GetSetMethod() == null).Select(p => p.Name).Distinct().ToArray(),
380
+ readonlyStaticProps: statProps.Where(p => p.GetGetMethod() != null && p.GetSetMethod() == null).Select(p => p.Name).Distinct().ToArray(),
381
+ writeonlyProps: instProps.Where(p => p.GetGetMethod() == null && p.GetSetMethod() != null).Select(p => p.Name).Distinct().ToArray(),
382
+ writeonlyStaticProps: statProps.Where(p => p.GetGetMethod() == null && p.GetSetMethod() != null).Select(p => p.Name).Distinct().ToArray()
383
+ );
384
+ }
385
+
386
+ internal static Type? ResolveType(string? assemblyName, string? typeName)
387
+ {
388
+ if (string.IsNullOrEmpty(typeName)) return null;
389
+ var key = string.IsNullOrEmpty(assemblyName) ? typeName : $"{assemblyName}|{typeName}";
390
+ return s_typeCache.GetOrAdd(key, _ => ResolveTypeCore(assemblyName, typeName));
391
+ }
392
+
393
+ private static Type? ResolveTypeInAssembly(Assembly asm, string typeName, int lastDot, string shortName)
394
+ {
395
+ var t = asm.GetType(typeName);
396
+ if (t is not null) return t;
397
+
398
+ foreach (var ty in asm.GetTypes())
399
+ {
400
+ if (string.Equals(ty.FullName, typeName, StringComparison.Ordinal))
401
+ return ty;
402
+ if (lastDot < 0 && string.Equals(ty.Name, shortName, StringComparison.Ordinal))
403
+ return ty;
404
+ }
405
+
406
+ return null;
407
+ }
408
+
409
+ private static Type? ResolveTypeFromKnownAssemblies(string typeName, int lastDot, string shortName, string? skipAssemblyName = null)
410
+ {
411
+ foreach (var dir in s_assemblySearchDirs.Where(Directory.Exists))
412
+ {
413
+ IEnumerable<string> candidates;
414
+ try
415
+ {
416
+ candidates = Directory.EnumerateFiles(dir, "*.dll", SearchOption.TopDirectoryOnly);
417
+ }
418
+ catch
419
+ {
420
+ continue;
421
+ }
422
+
423
+ foreach (var file in candidates)
424
+ {
425
+ try
426
+ {
427
+ var simple = Path.GetFileNameWithoutExtension(file);
428
+ if (string.IsNullOrEmpty(simple) || string.Equals(simple, skipAssemblyName, StringComparison.OrdinalIgnoreCase))
429
+ continue;
430
+
431
+ var asm = AppDomain.CurrentDomain.GetAssemblies()
432
+ .FirstOrDefault(a => string.Equals(a.GetName().Name, simple, StringComparison.OrdinalIgnoreCase));
433
+ if (asm is null)
434
+ {
435
+ try { asm = AssemblyLoadContext.Default.LoadFromAssemblyPath(file); }
436
+ catch { continue; }
437
+ }
438
+
439
+ var t = ResolveTypeInAssembly(asm, typeName, lastDot, shortName);
440
+ if (t is not null) return t;
441
+ }
442
+ catch (ReflectionTypeLoadException) { }
443
+ catch { }
444
+ }
445
+ }
446
+
447
+ return null;
448
+ }
449
+
450
+ private static Type? ResolveTypeCore(string? assemblyName, string? typeName)
451
+ {
452
+ if (string.IsNullOrEmpty(typeName)) return null;
453
+
454
+ // Fast/normal lookup first (assembly-qualified or plain type name).
455
+ var fqn = string.IsNullOrEmpty(assemblyName) ? typeName! : $"{typeName}, {assemblyName}";
456
+ var t = Type.GetType(fqn);
457
+ if (t is not null) return t;
458
+
459
+ t = Type.GetType(typeName);
460
+ if (t is not null) return t;
461
+
462
+ // Last segment (type name without namespace) used for loose matching below.
463
+ var lastDot = typeName.LastIndexOf('.');
464
+ var shortName = lastDot >= 0 ? typeName[(lastDot + 1)..] : typeName;
465
+
466
+ // Search loaded assemblies quickly; prefer asm.GetType which is inexpensive.
467
+ foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
468
+ {
469
+ try
470
+ {
471
+ t = ResolveTypeInAssembly(asm, typeName, lastDot, shortName);
472
+ if (t is not null) return t;
473
+ }
474
+ catch (ReflectionTypeLoadException) { }
475
+ catch { }
476
+ }
477
+
478
+ // If an assembly name was provided try loading it explicitly and repeat
479
+ // the above search inside that assembly only (less work than scanning all).
480
+ if (!string.IsNullOrEmpty(assemblyName))
481
+ {
482
+ try
483
+ {
484
+ var asm = Assembly.Load(assemblyName);
485
+ if (asm is not null)
486
+ {
487
+ t = ResolveTypeInAssembly(asm, typeName!, lastDot, shortName);
488
+ if (t is not null) return t;
489
+ }
490
+ }
491
+ catch { }
492
+ }
493
+
494
+ return ResolveTypeFromKnownAssemblies(typeName!, lastDot, shortName, assemblyName);
495
+ }
496
+
497
+ private static MethodInfo? FindMethodCore(Type type, string name, int argCount, BindingFlags flags)
498
+ {
499
+ // Exact match first: same name and parameter count.
500
+ var match = Array.Find(type.GetMethods(flags),
501
+ m => m.Name == name && !m.IsGenericMethod && m.GetParameters().Length == argCount);
502
+ if (match is not null) return match;
503
+
504
+ // Fallback to GetMethod which may handle binding differently.
505
+ var gm = type.GetMethod(name, flags);
506
+ if (gm is not null && !gm.IsGenericMethod && gm.GetParameters().Length == argCount) return gm;
507
+
508
+ // WinRT method names sometimes project to different CLR names
509
+ // (e.g. WinRT's `Append` → CLR `Add`). Try a small alias map
510
+ // before giving up so common collection methods work through
511
+ // the dotnet bridge without extra bridge-side shims.
512
+ var aliases = new Dictionary<string, string[]>()
513
+ {
514
+ ["Append"] = new[] { "Add" },
515
+ ["InsertAt"] = new[] { "Insert" },
516
+ ["GetAt"] = new[] { "get_Item" , "ElementAt" },
517
+ ["SetAt"] = new[] { "set_Item" },
518
+ ["RemoveAt"] = new[] { "RemoveAt" },
519
+ ["ReplaceAll"] = new[] { "Clear" },
520
+ };
521
+
522
+ if (aliases.TryGetValue(name, out var candidates))
523
+ {
524
+ foreach (var cand in candidates)
525
+ {
526
+ var candMatch = Array.Find(type.GetMethods(flags),
527
+ m => m.Name == cand && !m.IsGenericMethod && m.GetParameters().Length == argCount);
528
+ if (candMatch is not null) return candMatch;
529
+
530
+ var gm2 = type.GetMethod(cand, flags);
531
+ if (gm2 is not null && !gm2.IsGenericMethod && gm2.GetParameters().Length == argCount) return gm2;
532
+ }
533
+ }
534
+
535
+ return null;
536
+ }
537
+
538
+ internal static Exception Unwrap(Exception ex) =>
539
+ ex is TargetInvocationException { InnerException: { } inner } ? Unwrap(inner) : ex;
540
+
541
+ // Used by benchmarks to give a fair end-to-end comparison of protocols.
542
+
543
+ internal static byte[] PipelineJson(ReadOnlySpan<byte> jsonRequest)
544
+ {
545
+ var req = JsonSerializer.Deserialize(jsonRequest, BridgeJsonContext.Default.InvokeRequest)!;
546
+ var result = Dispatch(req);
547
+ var buf = new ArrayBufferWriter<byte>(128);
548
+ using var w = new Utf8JsonWriter(buf);
549
+ w.WriteStartObject();
550
+ w.WritePropertyName("result"u8);
551
+ result.WriteTo(w, s_coerceOpts);
552
+ w.WriteEndObject();
553
+ w.Flush();
554
+ return buf.WrittenSpan.ToArray();
555
+ }
556
+
557
+ internal static byte[] PipelineBinary(ReadOnlySpan<byte> binRequest)
558
+ {
559
+ var r = new BinReader(binRequest);
560
+ var result = DispatchBin(ref r);
561
+ var buf = new ArrayBufferWriter<byte>(128);
562
+ result.WriteAsBin(buf);
563
+ return buf.WrittenSpan.ToArray();
564
+ }
565
+
566
+ private static unsafe void WriteResult(DispatchResult res, byte** outPtr, int* outLen)
567
+ {
568
+ var buf = new ArrayBufferWriter<byte>(256);
569
+ using var w = new Utf8JsonWriter(buf);
570
+ w.WriteStartObject();
571
+ w.WritePropertyName("result"u8);
572
+ res.WriteTo(w, s_coerceOpts);
573
+ w.WriteEndObject();
574
+ w.Flush();
575
+ WriteUnmanaged(buf.WrittenSpan, outPtr, outLen);
576
+ }
577
+
578
+ private static unsafe void WriteError(string msg, byte** outPtr, int* outLen)
579
+ {
580
+ var buf = new ArrayBufferWriter<byte>(128);
581
+ using var w = new Utf8JsonWriter(buf);
582
+ w.WriteStartObject();
583
+ w.WriteString("error"u8, msg);
584
+ w.WriteEndObject();
585
+ w.Flush();
586
+ WriteUnmanaged(buf.WrittenSpan, outPtr, outLen);
587
+ }
588
+
589
+ internal static unsafe void WriteUnmanaged(ReadOnlySpan<byte> bytes, byte** outPtr, int* outLen)
590
+ {
591
+ var p = (byte*)Marshal.AllocHGlobal(bytes.Length + 1);
592
+ bytes.CopyTo(new Span<byte>(p, bytes.Length));
593
+ p[bytes.Length] = 0;
594
+ *outPtr = p;
595
+ *outLen = bytes.Length;
596
+ }
597
+ }