@nativescript/windows-hermes 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 (50) hide show
  1. package/README.md +27 -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/hermes-icu.dll +0 -0
  31. package/framework/libs/x64/hermes.dll +0 -0
  32. package/framework/libs/x64/nativescript.dll +0 -0
  33. package/framework/tools/ManifestMerger/ManifestMerger.deps.json +24 -0
  34. package/framework/tools/ManifestMerger/ManifestMerger.dll +0 -0
  35. package/framework/tools/ManifestMerger/ManifestMerger.pdb +0 -0
  36. package/framework/tools/dotnet-tool-arm64.exe +0 -0
  37. package/framework/tools/dotnet-tool-x64.exe +0 -0
  38. package/framework/tools/dotnet-tool.exe +0 -0
  39. package/framework/tools/dotnet-typings-gen/dotnet-typings-gen.deps.json +23 -0
  40. package/framework/tools/dotnet-typings-gen/dotnet-typings-gen.dll +0 -0
  41. package/framework/tools/dotnet-typings-gen/dotnet-typings-gen.exe +0 -0
  42. package/framework/tools/dotnet-typings-gen/dotnet-typings-gen.pdb +0 -0
  43. package/framework/tools/dotnet-typings-gen/dotnet-typings-gen.runtimeconfig.json +13 -0
  44. package/framework/tools/sbg-arm64.exe +0 -0
  45. package/framework/tools/sbg-x64.exe +0 -0
  46. package/framework/tools/sbg.exe +0 -0
  47. package/framework/tools/typings-generator-arm64.exe +0 -0
  48. package/framework/tools/typings-generator-x64.exe +0 -0
  49. package/framework/tools/typings-generator.exe +0 -0
  50. package/package.json +43 -0
@@ -0,0 +1,573 @@
1
+ using System;
2
+ using System.Buffers;
3
+ using System.Collections.Concurrent;
4
+ using System.Collections.Generic;
5
+ using System.Linq;
6
+ using System.Reflection;
7
+ using System.Reflection.Emit;
8
+ using System.Runtime.CompilerServices;
9
+ using System.Runtime.InteropServices;
10
+ using System.Threading;
11
+ using System.Globalization;
12
+
13
+ namespace NativeScriptBridge;
14
+
15
+ public static partial class Bridge
16
+ {
17
+ // Cache for dynamically generated proxy types (dev-only fallback). Keyed by base type PLUS
18
+ // the exact set of interfaces/members a given JS `overrides` object asked for, so different
19
+ // JS subclasses of the same base type (different override sets) get distinct emitted types.
20
+ private readonly record struct ProxyTypeKey(Type BaseType, string InterfaceKey, string MemberKey);
21
+ private static readonly ConcurrentDictionary<ProxyTypeKey, Type> s_dynamicProxyCache = new();
22
+ private static int s_dynamicProxyTypeCounter = 0;
23
+ private static readonly AssemblyBuilder? s_dynamicAssembly = CreateDynamicAssembly();
24
+ private static readonly ModuleBuilder? s_dynamicModule = CreateDynamicModule(s_dynamicAssembly);
25
+
26
+ private static AssemblyBuilder? CreateDynamicAssembly()
27
+ {
28
+ try
29
+ {
30
+ var an = new AssemblyName("NSWinRTDynamicProxies");
31
+ var asm = AssemblyBuilder.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
32
+
33
+ return asm;
34
+ }
35
+ catch { return null; }
36
+ }
37
+
38
+ private static ModuleBuilder? CreateDynamicModule(AssemblyBuilder? asm)
39
+ {
40
+ try {
41
+ var m = asm?.DefineDynamicModule("NSWinRTDynamicProxiesModule");
42
+ return m;
43
+ } catch { return null; }
44
+ }
45
+
46
+ // Public helpers callable from runtime-generated types. Nested type so it can
47
+ // access Bridge's private proxy helpers (ProxyInvokeMethod, etc.) while
48
+ // exposing a stable public API for emitted proxies.
49
+ public static class ProxyRuntime
50
+ {
51
+ public static T InvokeMethodTyped<T>(object instance, string methodName, object[] args)
52
+ {
53
+ var result = ProxyInvokeMethod(instance, methodName, args);
54
+ if (result is null) return default!;
55
+ if (result is T t) return t;
56
+ return (T)Convert.ChangeType(result, typeof(T), CultureInfo.InvariantCulture);
57
+ }
58
+
59
+ public static object? InvokeMethod(object instance, string methodName, object[] args)
60
+ {
61
+ return ProxyInvokeMethod(instance, methodName, args);
62
+ }
63
+
64
+ public static void InvokeVoid(object instance, string methodName, object[] args)
65
+ {
66
+ ProxyInvokeVoid(instance, methodName, args);
67
+ }
68
+
69
+ public static T GetProperty<T>(object instance, string propertyName)
70
+ {
71
+ var result = ProxyGetProperty(instance, propertyName);
72
+ if (result is null) return default!;
73
+ if (result is T t) return t;
74
+ return (T)Convert.ChangeType(result, typeof(T), CultureInfo.InvariantCulture);
75
+ }
76
+
77
+ public static void SetProperty(object instance, string propertyName, object? value)
78
+ {
79
+ ProxySetProperty(instance, propertyName, value);
80
+ }
81
+ }
82
+ private sealed class ProxyCallbackHolder { public int CallbackId; public int HandleId; public ProxyCallbackHolder(int cb) { CallbackId = cb; HandleId = 0; } }
83
+
84
+ private static readonly ConditionalWeakTable<object, ProxyCallbackHolder> s_proxyCallbacks = new();
85
+ private static readonly ThreadLocal<int?> s_pendingProxyCallbackId = new(() => null);
86
+ private static readonly object s_proxyRegisterLock = new();
87
+ private static bool s_proxyDispatcherRegistered = false;
88
+
89
+ private static void EnsureProxyDispatcherCallbacksRegistered()
90
+ {
91
+ if (s_proxyDispatcherRegistered) return;
92
+ lock (s_proxyRegisterLock)
93
+ {
94
+ if (s_proxyDispatcherRegistered) return;
95
+
96
+ Type? pdType = null;
97
+ foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
98
+ {
99
+ try
100
+ {
101
+ pdType = asm.GetType("NativeScriptGeneratedProxies.ProxyDispatcher")
102
+ ?? asm.GetType("NSWinRTProxies.ProxyDispatcher")
103
+ ?? asm.GetType("NativeScriptGeneratedProxies.ProxyDispatcher");
104
+ if (pdType != null) break;
105
+ }
106
+ catch { }
107
+ }
108
+
109
+ if (pdType != null)
110
+ {
111
+ // Properties generated by SBG
112
+ var p1 = pdType.GetProperty("JsInvokeMethod", BindingFlags.Public | BindingFlags.Static);
113
+ p1?.SetValue(null, new Func<object, string, object[], object?>(ProxyInvokeMethod));
114
+
115
+ var p2 = pdType.GetProperty("JsInvokeVoid", BindingFlags.Public | BindingFlags.Static);
116
+ p2?.SetValue(null, new Action<object, string, object[]>(ProxyInvokeVoid));
117
+
118
+ var p3 = pdType.GetProperty("JsGetProperty", BindingFlags.Public | BindingFlags.Static);
119
+ p3?.SetValue(null, new Func<object, string, object?>(ProxyGetProperty));
120
+
121
+ var p4 = pdType.GetProperty("JsSetProperty", BindingFlags.Public | BindingFlags.Static);
122
+ p4?.SetValue(null, new Action<object, string, object?>(ProxySetProperty));
123
+
124
+ var p5 = pdType.GetProperty("JsInitializeInstance", BindingFlags.Public | BindingFlags.Static);
125
+ p5?.SetValue(null, new Action<object, string>(ProxyInitializeInstance));
126
+ }
127
+
128
+ s_proxyDispatcherRegistered = true;
129
+ }
130
+ }
131
+
132
+ private static bool DynamicProxiesEnabled()
133
+ {
134
+ // Always allow dynamic proxies in dev when Reflection.Emit is available.
135
+ return s_dynamicModule != null;
136
+ }
137
+
138
+ private static Type? FindGeneratedProxyType(string typeName)
139
+ {
140
+ if (string.IsNullOrEmpty(typeName)) return null;
141
+ var lastDot = typeName.LastIndexOf('.');
142
+ var shortName = lastDot >= 0 ? typeName[(lastDot + 1)..] : typeName;
143
+ foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
144
+ {
145
+ try
146
+ {
147
+ // Only consider assemblies that look like generated proxy assemblies
148
+ var asmName = asm.GetName().Name ?? "";
149
+ if (!string.Equals(asmName, "NativeScriptGeneratedProxies", StringComparison.Ordinal)
150
+ && !string.Equals(asmName, "NSWinRTProxies", StringComparison.Ordinal))
151
+ {
152
+ // Also check for candidate full-type names in any assembly
153
+ var candidates = new[] { $"NativeScriptGeneratedProxies.{shortName}", $"NSWinRTProxies.{shortName}" };
154
+ foreach (var cn in candidates)
155
+ {
156
+ var t = asm.GetType(cn);
157
+ if (t is not null) return t;
158
+ }
159
+ continue;
160
+ }
161
+
162
+ // If this is a generated proxies assembly, look up the proxy type name.
163
+ var genCandidates = new[] { $"NativeScriptGeneratedProxies.{shortName}", $"NSWinRTProxies.{shortName}" };
164
+ foreach (var cn in genCandidates)
165
+ {
166
+ var t = asm.GetType(cn);
167
+ if (t is not null) return t;
168
+ }
169
+ }
170
+ catch { }
171
+ }
172
+ return null;
173
+ }
174
+
175
+ private static Type GetOrCreateDynamicProxyType(Type baseType, string[] interfaceNames, string[] memberNames)
176
+ {
177
+ var interfaceKey = string.Join(",", interfaceNames.OrderBy(n => n, StringComparer.Ordinal));
178
+ var memberKey = string.Join(",", memberNames.OrderBy(n => n, StringComparer.Ordinal));
179
+ var key = new ProxyTypeKey(baseType, interfaceKey, memberKey);
180
+ return s_dynamicProxyCache.GetOrAdd(key, _ => CreateDynamicProxyType(baseType, interfaceNames, memberNames));
181
+ }
182
+
183
+ private static Type CreateDynamicProxyType(Type baseType, string[] interfaceNames, string[] memberNames)
184
+ {
185
+
186
+ if (s_dynamicModule == null)
187
+ throw new InvalidOperationException("Dynamic proxy generation not available in this environment");
188
+
189
+ if (baseType.IsSealed)
190
+ throw new InvalidOperationException($"Cannot derive from sealed type {baseType.FullName}");
191
+
192
+ var interfaceTypes = interfaceNames
193
+ .Select(n => ResolveType(null, n) ?? throw new TypeLoadException($"Interface type not found: {n}"))
194
+ .ToArray();
195
+
196
+ // Members JS actually overrides. Only base virtuals in this set get an IL override
197
+ // emitted — anything else is left alone so the real base implementation stays live in
198
+ // the vtable (no "call base" IL needed, the base method was simply never touched).
199
+ var memberSet = new HashSet<string>(memberNames, StringComparer.Ordinal);
200
+
201
+ var safeName = (baseType.FullName ?? Guid.NewGuid().ToString()).Replace('.', '_');
202
+ var uniqueSuffix = Interlocked.Increment(ref s_dynamicProxyTypeCounter);
203
+ var typeName = $"NSWinRTDynamicProxies.{safeName}_JsProxy_{uniqueSuffix}";
204
+
205
+ var tb = s_dynamicModule.DefineType(typeName, TypeAttributes.Public | TypeAttributes.Class, baseType);
206
+
207
+ // Mirror public base constructors and ensure at least a parameterless
208
+ // ctor exists on the derived type. If the base type has no parameterless
209
+ // ctor, emit a parameterless ctor that forwards default(T) values to the
210
+ // first available base ctor (dev-only convenience).
211
+ var baseCtors = baseType.GetConstructors(BindingFlags.Public | BindingFlags.Instance)
212
+ .Where(c => !c.IsGenericMethod)
213
+ .ToArray();
214
+
215
+ ConstructorInfo? chosenBaseForDefault = null;
216
+ foreach (var bc in baseCtors)
217
+ {
218
+ var parameters = bc.GetParameters();
219
+ if (parameters.Any(p => p.ParameterType.IsByRef)) continue;
220
+ chosenBaseForDefault ??= bc;
221
+
222
+ // Define a matching ctor on the derived type that forwards args to base.
223
+ var paramTypes = parameters.Select(p => p.ParameterType).ToArray();
224
+ var derivedCtor = tb.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, paramTypes);
225
+ var ilg = derivedCtor.GetILGenerator();
226
+ // Load 'this'
227
+ ilg.Emit(OpCodes.Ldarg_0);
228
+ // Load each argument and forward
229
+ for (int i = 0; i < paramTypes.Length; i++)
230
+ {
231
+ // ldarg_1..n
232
+ switch (i + 1)
233
+ {
234
+ case 1: ilg.Emit(OpCodes.Ldarg_1); break;
235
+ case 2: ilg.Emit(OpCodes.Ldarg_2); break;
236
+ case 3: ilg.Emit(OpCodes.Ldarg_3); break;
237
+ default: ilg.Emit(OpCodes.Ldarg, i + 1); break;
238
+ }
239
+ }
240
+ ilg.Emit(OpCodes.Call, bc);
241
+ ilg.Emit(OpCodes.Ret);
242
+ }
243
+
244
+ // If we didn't define a parameterless ctor, emit one that forwards
245
+ // default(T) values to the chosen base ctor (if any), otherwise call
246
+ // System.Object..ctor.
247
+ if (!baseCtors.Any(c => c.GetParameters().Length == 0))
248
+ {
249
+ var paramless = tb.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, Type.EmptyTypes);
250
+ var ilg = paramless.GetILGenerator();
251
+ if (chosenBaseForDefault is not null)
252
+ {
253
+ ilg.Emit(OpCodes.Ldarg_0);
254
+ var parameters = chosenBaseForDefault.GetParameters();
255
+ // For each param, emit default value (null for ref types, initobj local for value types)
256
+ for (int i = 0; i < parameters.Length; i++)
257
+ {
258
+ var ptype = parameters[i].ParameterType;
259
+ if (ptype.IsByRef) throw new InvalidOperationException("Cannot forward byref parameters in dynamic ctor");
260
+ if (ptype.IsValueType)
261
+ {
262
+ var temp = ilg.DeclareLocal(ptype);
263
+ ilg.Emit(OpCodes.Ldloca_S, temp);
264
+ ilg.Emit(OpCodes.Initobj, ptype);
265
+ ilg.Emit(OpCodes.Ldloc, temp);
266
+ }
267
+ else
268
+ {
269
+ ilg.Emit(OpCodes.Ldnull);
270
+ }
271
+ }
272
+ ilg.Emit(OpCodes.Call, chosenBaseForDefault);
273
+ ilg.Emit(OpCodes.Ret);
274
+ }
275
+ else
276
+ {
277
+ // No public base ctors (rare); call Object..ctor
278
+ var objCtor = typeof(object).GetConstructor(Type.EmptyTypes)!;
279
+ ilg.Emit(OpCodes.Ldarg_0);
280
+ ilg.Emit(OpCodes.Call, objCtor);
281
+ ilg.Emit(OpCodes.Ret);
282
+ }
283
+ }
284
+
285
+ // Shared by both loops below, keyed by "Name(Param1FullName,Param2FullName,...)" so a
286
+ // member that's both an overridden base virtual and a requested interface member (e.g.
287
+ // the base class already implements that interface) reuses one emitted body instead of
288
+ // tripping a "duplicate member" TypeBuilder error.
289
+ var definedSignatures = new Dictionary<string, MethodBuilder>(StringComparer.Ordinal);
290
+ static string SignatureKey(string name, Type[] paramTypes) =>
291
+ name + "(" + string.Join(",", paramTypes.Select(p => p.FullName)) + ")";
292
+
293
+ // Override only the base virtuals JS actually asked for (memberSet). Anything not in
294
+ // that set is simply never touched, so the base implementation stays live in the vtable —
295
+ // no "call base" IL needed. (Skip ref/out and generic methods — unsupported.)
296
+ var virtualMethods = baseType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
297
+ .Where(m => m.IsVirtual && !m.IsFinal && !m.IsGenericMethod && !m.IsConstructor && memberSet.Contains(m.Name))
298
+ .ToArray();
299
+
300
+ foreach (var mi in virtualMethods)
301
+ {
302
+ var parameters = mi.GetParameters();
303
+ if (parameters.Any(p => p.ParameterType.IsByRef)) continue;
304
+
305
+ var paramTypes = parameters.Select(p => p.ParameterType).ToArray();
306
+ var sigKey = SignatureKey(mi.Name, paramTypes);
307
+ if (!definedSignatures.TryGetValue(sigKey, out var mb))
308
+ {
309
+ mb = EmitProxyMethodBody(tb, mi.Name,
310
+ MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig,
311
+ mi.CallingConvention, mi.ReturnType, paramTypes, mi.Name);
312
+ definedSignatures[sigKey] = mb;
313
+ }
314
+
315
+ tb.DefineMethodOverride(mb, mi);
316
+ }
317
+
318
+ // Interfaces: every requested interface (plus any interfaces *those* extend, since
319
+ // AddInterfaceImplementation only registers the interface named, not its ancestors) must
320
+ // have every member implemented — the CLR requires it, unlike base-class virtuals where
321
+ // skipping is fine. Members JS doesn't override fall back to default/no-op via the same
322
+ // generic dispatcher (ProxyRuntime.InvokeMethodTyped/InvokeVoid already do this).
323
+ if (interfaceTypes.Length > 0)
324
+ {
325
+ var allInterfaceTypes = new List<Type>();
326
+ void CollectInterfaces(Type t)
327
+ {
328
+ if (allInterfaceTypes.Contains(t)) return;
329
+ allInterfaceTypes.Add(t);
330
+ foreach (var parent in t.GetInterfaces()) CollectInterfaces(parent);
331
+ }
332
+ foreach (var ifaceType in interfaceTypes) CollectInterfaces(ifaceType);
333
+
334
+ foreach (var ifaceType in allInterfaceTypes) tb.AddInterfaceImplementation(ifaceType);
335
+
336
+ foreach (var ifaceType in allInterfaceTypes)
337
+ {
338
+ foreach (var mi in ifaceType.GetMethods())
339
+ {
340
+ var parameters = mi.GetParameters();
341
+ if (parameters.Any(p => p.ParameterType.IsByRef)) continue;
342
+
343
+ var paramTypes = parameters.Select(p => p.ParameterType).ToArray();
344
+ var sigKey = SignatureKey(mi.Name, paramTypes);
345
+ if (!definedSignatures.TryGetValue(sigKey, out var mb))
346
+ {
347
+ mb = EmitProxyMethodBody(tb, mi.Name,
348
+ MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final
349
+ | MethodAttributes.HideBySig | MethodAttributes.NewSlot,
350
+ mi.CallingConvention, mi.ReturnType, paramTypes, mi.Name);
351
+ definedSignatures[sigKey] = mb;
352
+ }
353
+
354
+ tb.DefineMethodOverride(mb, mi);
355
+ }
356
+ }
357
+ }
358
+
359
+ return tb.CreateTypeInfo()!.AsType();
360
+ }
361
+
362
+ // Shared IL body for both base-virtual overrides and interface-member implementations: box
363
+ // args into an object[], forward to ProxyRuntime.InvokeVoid/InvokeMethodTyped<T> by name.
364
+ private static MethodBuilder EmitProxyMethodBody(
365
+ TypeBuilder tb, string emittedName, MethodAttributes attrs, CallingConventions callingConvention,
366
+ Type returnType, Type[] paramTypes, string dispatchName)
367
+ {
368
+ var mb = tb.DefineMethod(emittedName, attrs, callingConvention, returnType, paramTypes);
369
+ var ilg = mb.GetILGenerator();
370
+ var argsLocal = ilg.DeclareLocal(typeof(object[]));
371
+ ilg.Emit(OpCodes.Ldc_I4, paramTypes.Length);
372
+ ilg.Emit(OpCodes.Newarr, typeof(object));
373
+ ilg.Emit(OpCodes.Stloc, argsLocal);
374
+
375
+ for (int i = 0; i < paramTypes.Length; i++)
376
+ {
377
+ ilg.Emit(OpCodes.Ldloc, argsLocal);
378
+ ilg.Emit(OpCodes.Ldc_I4, i);
379
+ ilg.Emit(OpCodes.Ldarg, i + 1);
380
+ if (paramTypes[i].IsValueType) ilg.Emit(OpCodes.Box, paramTypes[i]);
381
+ ilg.Emit(OpCodes.Stelem_Ref);
382
+ }
383
+
384
+ if (returnType == typeof(void))
385
+ {
386
+ var invokeVoid = typeof(Bridge).GetNestedType("ProxyRuntime", BindingFlags.Public | BindingFlags.Static)!.GetMethod("InvokeVoid", BindingFlags.Public | BindingFlags.Static)!;
387
+ ilg.Emit(OpCodes.Ldarg_0);
388
+ ilg.Emit(OpCodes.Ldstr, dispatchName);
389
+ ilg.Emit(OpCodes.Ldloc, argsLocal);
390
+ ilg.Emit(OpCodes.Call, invokeVoid);
391
+ ilg.Emit(OpCodes.Ret);
392
+ }
393
+ else
394
+ {
395
+ var proxyRuntimeType = typeof(Bridge).GetNestedType("ProxyRuntime", BindingFlags.Public | BindingFlags.Static)!;
396
+ var gm = proxyRuntimeType.GetMethod("InvokeMethodTyped", BindingFlags.Public | BindingFlags.Static)!.MakeGenericMethod(returnType);
397
+ ilg.Emit(OpCodes.Ldarg_0);
398
+ ilg.Emit(OpCodes.Ldstr, dispatchName);
399
+ ilg.Emit(OpCodes.Ldloc, argsLocal);
400
+ ilg.Emit(OpCodes.Call, gm);
401
+ ilg.Emit(OpCodes.Ret);
402
+ }
403
+
404
+ return mb;
405
+ }
406
+
407
+ private static DispatchResult CreateJsSubclass(
408
+ string? assemblyName, string typeName, int callbackId, string[] interfaceNames, string[] memberNames)
409
+ {
410
+ EnsureProxyDispatcherCallbacksRegistered();
411
+
412
+ s_pendingProxyCallbackId.Value = callbackId;
413
+
414
+ // Try to find a statically-generated proxy type first (Mechanism A: sbg-compiled into
415
+ // the app itself — already has any requested interfaces baked into real C# source).
416
+ Type? t = FindGeneratedProxyType(typeName);
417
+ if (t is null)
418
+ {
419
+ // When typeName is an auto-generated proxy name (com.tns.gen.winrt.*),
420
+ // the caller puts the real base type name in assemblyName so we can
421
+ // look up or dynamically emit a proxy for the correct base class.
422
+ string? resolveFrom = typeName;
423
+ if (!string.IsNullOrEmpty(assemblyName)
424
+ && typeName != null
425
+ && typeName.StartsWith("com.tns.gen.winrt.", StringComparison.Ordinal))
426
+ {
427
+ resolveFrom = assemblyName;
428
+ }
429
+
430
+ // Resolve the WinRT/base type and optionally emit a dynamic proxy (Mechanism B fallback).
431
+ var baseType = ResolveType(null, resolveFrom)
432
+ ?? ResolveType(assemblyName, typeName)
433
+ ?? throw new TypeLoadException($"Type not found: {resolveFrom} (proxy: {typeName}, assembly: {assemblyName})");
434
+
435
+ if (baseType.IsClass && !baseType.IsSealed)
436
+ {
437
+ try { t = GetOrCreateDynamicProxyType(baseType, interfaceNames, memberNames); }
438
+ catch (Exception) { t = baseType; }
439
+ }
440
+ else
441
+ {
442
+ t = baseType;
443
+ }
444
+ }
445
+
446
+ // Create the managed instance. Generated classes should call
447
+ // ProxyDispatcher.InitializeInstance(this, typeName) in their constructor,
448
+ // which will invoke our ProxyInitializeInstance and capture the pending callback id.
449
+ object? instance = Activator.CreateInstance(t);
450
+ if (instance is null)
451
+ throw new InvalidOperationException($"Failed to instantiate proxy type {t.FullName}.");
452
+
453
+ // Ensure there's a mapping for this instance immediately to avoid any
454
+ // early virtual calls from constructors reaching the JS side before
455
+ // the handle is registered. Call our initializer directly to register
456
+ // the pending callback id when dynamic proxies don't call it.
457
+ try { ProxyInitializeInstance(instance, typeName); } catch { }
458
+
459
+ // Force COM CCW vtable creation for any implemented WinRT interfaces now, rather than
460
+ // relying on it happening implicitly the first time the instance crosses the ABI boundary.
461
+ // Best-effort/reflection-only — no-ops cleanly when CsWinRT isn't loaded (e.g. xunit host).
462
+ if (interfaceNames.Length > 0)
463
+ {
464
+ TryActivateWinRTInterfaces(instance);
465
+ }
466
+
467
+ // Box the instance into a handle to return to the runtime.
468
+ var result = Box(instance);
469
+
470
+ // Populate holder with handle id now that a handle exists.
471
+ if (s_proxyCallbacks.TryGetValue(instance, out var holder))
472
+ {
473
+ try { holder.HandleId = result.HandleId(); } catch { holder.HandleId = 0; }
474
+ }
475
+ else
476
+ {
477
+ // If the generated proxy did not call InitializeInstance, create mapping now.
478
+ int hid = 0;
479
+ try { hid = result.HandleId(); } catch { hid = 0; }
480
+ var newHolder = new ProxyCallbackHolder(callbackId) { HandleId = hid };
481
+ s_proxyCallbacks.Add(instance, newHolder);
482
+ }
483
+
484
+ s_pendingProxyCallbackId.Value = null;
485
+ return result;
486
+ }
487
+
488
+ // Locates CsWinRT's ComWrappersSupport via reflection (same style as the WinRT.IWinRTObject
489
+ // lookup in ObtainNativePtr — no compile-time reference needed, since DotNetBridge.csproj is a
490
+ // dependency-free net9.0 library and CsWinRT is only ever loaded into the process by the WinUI
491
+ // app itself) and asks it to build a real COM CCW for the instance's implemented WinRT
492
+ // interfaces now, via CsWinRT's JIT reflection-fallback vtable path (works for types it never
493
+ // saw at compile time — see plan doc for the CsWinRT version/AOT caveats). Best-effort: no-ops
494
+ // when CsWinRT isn't loaded, e.g. the xunit test host has no WinUI/CsWinRT at all.
495
+ private static void TryActivateWinRTInterfaces(object instance)
496
+ {
497
+ try
498
+ {
499
+ Type? supportType = null;
500
+ foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
501
+ {
502
+ try
503
+ {
504
+ supportType = asm.GetType("WinRT.ComWrappersSupport");
505
+ if (supportType != null) break;
506
+ }
507
+ catch { }
508
+ }
509
+ if (supportType == null) return;
510
+
511
+ foreach (var name in new[] { "CreateCCWForObject", "GetOrCreateComInterfaceForObject" })
512
+ {
513
+ foreach (var candidate in supportType.GetMethods(BindingFlags.Public | BindingFlags.Static)
514
+ .Where(m => m.Name == name))
515
+ {
516
+ try
517
+ {
518
+ var target = candidate.IsGenericMethodDefinition
519
+ ? candidate.MakeGenericMethod(instance.GetType())
520
+ : candidate;
521
+ var ps = target.GetParameters();
522
+ if (ps.Length == 1) { target.Invoke(null, [instance]); return; }
523
+ if (ps.Length == 2 && ps[1].ParameterType == typeof(Guid)) { target.Invoke(null, [instance, Guid.Empty]); return; }
524
+ }
525
+ catch { /* try the next overload/name */ }
526
+ }
527
+ }
528
+ }
529
+ catch { /* best-effort only */ }
530
+ }
531
+
532
+ private static void ProxyInitializeInstance(object instance, string typeName)
533
+ {
534
+ var cb = s_pendingProxyCallbackId.Value;
535
+ if (cb.HasValue)
536
+ {
537
+ var holder = new ProxyCallbackHolder(cb.Value);
538
+ s_proxyCallbacks.Add(instance, holder);
539
+ }
540
+ }
541
+
542
+ private static object? ProxyInvokeMethod(object instance, string methodName, object[] args)
543
+ {
544
+ if (!s_proxyCallbacks.TryGetValue(instance, out var holder)) throw new InvalidOperationException("No JS callback registered for proxy instance.");
545
+ var payload = new object?[] { new HandleRef(holder.HandleId), methodName, args };
546
+ return CallJsCallback(holder.CallbackId, payload);
547
+ }
548
+
549
+ private static void ProxyInvokeVoid(object instance, string methodName, object[] args)
550
+ {
551
+ if (!s_proxyCallbacks.TryGetValue(instance, out var holder)) throw new InvalidOperationException("No JS callback registered for proxy instance.");
552
+
553
+ var payload = new object?[] { new HandleRef(holder.HandleId), methodName, args };
554
+ CallJsCallback(holder.CallbackId, payload);
555
+ }
556
+
557
+ private static object? ProxyGetProperty(object instance, string propertyName)
558
+ {
559
+ if (!s_proxyCallbacks.TryGetValue(instance, out var holder)) throw new InvalidOperationException("No JS callback registered for proxy instance.");
560
+ // Tag "get:" prefix so the JS dispatcher can distinguish getter from method call.
561
+ var payload = new object?[] { new HandleRef(holder.HandleId), "get:" + propertyName };
562
+ return CallJsCallback(holder.CallbackId, payload);
563
+ }
564
+
565
+ private static void ProxySetProperty(object instance, string propertyName, object? value)
566
+ {
567
+ if (!s_proxyCallbacks.TryGetValue(instance, out var holder)) throw new InvalidOperationException("No JS callback registered for proxy instance.");
568
+ // Tag "set:" prefix so the JS dispatcher can distinguish setter from method call.
569
+ var payload = new object?[] { new HandleRef(holder.HandleId), "set:" + propertyName, value };
570
+ CallJsCallback(holder.CallbackId, payload);
571
+ }
572
+ }
573
+