@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,724 @@
1
+ using System;
2
+ using System.Diagnostics;
3
+ using System.Collections.Concurrent;
4
+ using System.Collections.Generic;
5
+ using System.IO;
6
+ using System.Linq;
7
+ using System.Reflection;
8
+ using System.Threading;
9
+ using System.Threading.Tasks;
10
+ using System.Runtime.InteropServices;
11
+ using System.Runtime.Loader;
12
+ using System.Text.Json;
13
+
14
+ [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("DotNetBridgeTests")]
15
+ [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("DotNetBridgeBenchmarks")]
16
+
17
+ namespace NativeScriptBridge;
18
+
19
+ public static partial class Bridge
20
+ {
21
+ internal static readonly ConcurrentDictionary<int, object?> s_handles = new();
22
+ internal static int s_nextHandle;
23
+
24
+ // Cached WinUI3 UI-thread DispatcherQueue. Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread()
25
+ // returns null off the UI thread, so we capture the queue the first time we run on a thread that owns
26
+ // one (the UI thread during init/dispatch) and reuse it to marshal back from background threads.
27
+ private static object? s_uiDispatcherQueue;
28
+
29
+ // Function pointer registered by the Rust runtime so managed delegates can
30
+ // call back into V8 without a JSON round-trip.
31
+ internal static unsafe delegate* unmanaged[Cdecl]<int, byte*, int, byte**, int*, void>
32
+ s_jsInvoker;
33
+
34
+ private static readonly ConcurrentDictionary<string, Type?> s_typeCache
35
+ = new(StringComparer.Ordinal);
36
+ private static readonly ConcurrentDictionary<MethodKey, DispatchEntry> s_methodCache = new();
37
+ private static readonly ConcurrentDictionary<PropKey, PropertyInfo?> s_propCache = new();
38
+ private static readonly ConcurrentDictionary<CtorKey, CtorEntry> s_ctorCache = new();
39
+ // Cache of attempted assembly loads (simple name -> Assembly or null if not found).
40
+ private static readonly ConcurrentDictionary<string, Assembly?> s_assemblyLoadCache
41
+ = new(StringComparer.OrdinalIgnoreCase);
42
+ // Directories to search for assemblies (initialized once in static ctor).
43
+ private static readonly string[] s_assemblySearchDirs;
44
+
45
+ private static readonly JsonSerializerOptions s_coerceOpts = new()
46
+ {
47
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
48
+ };
49
+
50
+ internal static void ClearCaches()
51
+ {
52
+ s_typeCache.Clear();
53
+ s_methodCache.Clear();
54
+ s_propCache.Clear();
55
+ s_ctorCache.Clear();
56
+ s_handles.Clear();
57
+ s_nativePtrs.Clear();
58
+ s_nativePtrsReverse.Clear();
59
+ s_nextHandle = 0;
60
+ }
61
+
62
+ // Optional mapping from exported handle id -> native IUnknown pointer (IntPtr)
63
+ // Populated when a managed object can yield a native COM pointer via
64
+ // Marshal.GetIUnknownForObject. Cleared and released on __release.
65
+ internal static readonly ConcurrentDictionary<int, IntPtr> s_nativePtrs = new();
66
+
67
+ // Reverse index kept in lockstep with s_nativePtrs so CoerceBin can
68
+ // resolve identity round-trips without scanning every entry.
69
+ internal static readonly ConcurrentDictionary<IntPtr, int> s_nativePtrsReverse = new();
70
+
71
+ internal static void StoreNativePtr(int handleId, IntPtr ptr)
72
+ {
73
+ s_nativePtrs[handleId] = ptr;
74
+ s_nativePtrsReverse[ptr] = handleId;
75
+ }
76
+
77
+ internal static void RemoveNativePtr(int handleId, IntPtr ptr)
78
+ {
79
+ // Only drop the reverse entry if it still points at this handle —
80
+ // another handle may have re-registered the same pointer.
81
+ if (s_nativePtrsReverse.TryGetValue(ptr, out var owner) && owner == handleId)
82
+ s_nativePtrsReverse.TryRemove(ptr, out _);
83
+ }
84
+
85
+ // Runtime-configurable logging toggle. Default to true in DEBUG builds so
86
+ // developers get verbose diagnostics without setting environment vars.
87
+ #if DEBUG
88
+ internal static bool s_logToConsole = true;
89
+ #else
90
+ internal static bool s_logToConsole = false;
91
+ #endif
92
+
93
+ public static void SetLogToConsole(bool enabled) => s_logToConsole = enabled;
94
+
95
+ internal static bool IsLogToConsole() => s_logToConsole;
96
+
97
+ // Returns a JSON object mapping namespaces to the assembly simple-name that
98
+ // most likely contains those types. Exact namespaces are included so the
99
+ // runtime can prefer the longest matching prefix (for example
100
+ // Microsoft.UI.Xaml.Controls -> Microsoft.UI.Xaml) instead of relying only
101
+ // on a coarse top-level root such as Microsoft.
102
+ public static string GetNamespaceAssemblyMapJson()
103
+ {
104
+ var map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
105
+ try
106
+ {
107
+ foreach (var dir in s_assemblySearchDirs.Where(Directory.Exists))
108
+ {
109
+ try
110
+ {
111
+ foreach (var file in Directory.EnumerateFiles(dir, "*.dll", SearchOption.TopDirectoryOnly))
112
+ {
113
+ try
114
+ {
115
+ var simple = Path.GetFileNameWithoutExtension(file);
116
+ if (string.IsNullOrEmpty(simple)) continue;
117
+
118
+ Assembly asm = AppDomain.CurrentDomain.GetAssemblies()
119
+ .FirstOrDefault(a => string.Equals(a.GetName().Name, simple, StringComparison.OrdinalIgnoreCase));
120
+ if (asm is null)
121
+ {
122
+ try { asm = AssemblyLoadContext.Default.LoadFromAssemblyPath(file); }
123
+ catch { continue; }
124
+ }
125
+
126
+ Type[] types;
127
+ try { types = asm.GetExportedTypes(); }
128
+ catch { types = Array.Empty<Type>(); }
129
+
130
+ var assemblyName = asm.GetName().Name;
131
+ if (string.IsNullOrEmpty(assemblyName)) continue;
132
+
133
+ foreach (var t in types)
134
+ {
135
+ if (string.IsNullOrEmpty(t.Namespace)) continue;
136
+ map[t.Namespace] = assemblyName;
137
+ var root = t.Namespace.Split('.')[0];
138
+ if (!map.ContainsKey(root)) map[root] = assemblyName;
139
+ }
140
+ }
141
+ catch { }
142
+ }
143
+ }
144
+ catch { }
145
+ }
146
+ }
147
+ catch { }
148
+ try { return JsonSerializer.Serialize(map); } catch { return "{}"; }
149
+ }
150
+
151
+ static Bridge()
152
+ {
153
+ try
154
+ {
155
+ var baseDir = AppContext.BaseDirectory ?? AppDomain.CurrentDomain.BaseDirectory;
156
+ var dirs = new List<string> { baseDir };
157
+
158
+ // libs/ subtree relative to the bridge's own directory.
159
+ var libs = Path.Combine(baseDir, "libs");
160
+ if (Directory.Exists(libs))
161
+ {
162
+ dirs.Add(libs);
163
+ try { dirs.AddRange(Directory.GetDirectories(libs, "*", SearchOption.AllDirectories)); } catch { }
164
+ }
165
+
166
+ // The bridge DLL lives at dotnet-bridge/publish/ inside the app output root.
167
+ // Plugin assemblies (added via plugin.props) land at plugins/**/*.dll and NuGet
168
+ // assemblies land at the app output root itself — both outside the bridge subtree.
169
+ // Use the host process directory to reach them.
170
+ try
171
+ {
172
+ var processDir = Path.GetDirectoryName(
173
+ System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName);
174
+ if (!string.IsNullOrEmpty(processDir))
175
+ {
176
+ dirs.Add(processDir);
177
+
178
+ // plugins/ subtree: CLI-managed plugin DLLs live here.
179
+ // Check both {processDir}/plugins and {processDir}/../plugins because
180
+ // the NS CLI places plugins/ as a sibling of bin/, not inside it.
181
+ var pluginsDir = Path.Combine(processDir, "plugins");
182
+ if (Directory.Exists(pluginsDir))
183
+ try { dirs.AddRange(Directory.GetDirectories(pluginsDir, "*", SearchOption.AllDirectories)); } catch { }
184
+
185
+ var parentDir = Path.GetDirectoryName(processDir);
186
+ if (!string.IsNullOrEmpty(parentDir))
187
+ {
188
+ var parentPlugins = Path.Combine(parentDir, "plugins");
189
+ if (Directory.Exists(parentPlugins))
190
+ {
191
+ dirs.Add(parentPlugins);
192
+ try { dirs.AddRange(Directory.GetDirectories(parentPlugins, "*", SearchOption.AllDirectories)); } catch { }
193
+ }
194
+ }
195
+
196
+ // libs/ relative to the app root (alternative convention).
197
+ var processLibs = Path.Combine(processDir, "libs");
198
+ if (Directory.Exists(processLibs))
199
+ try { dirs.AddRange(Directory.GetDirectories(processLibs, "*", SearchOption.AllDirectories)); } catch { }
200
+ }
201
+ }
202
+ catch { }
203
+
204
+ s_assemblySearchDirs = dirs.Where(d => !string.IsNullOrEmpty(d)).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
205
+ AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
206
+ }
207
+ catch
208
+ {
209
+ s_assemblySearchDirs = Array.Empty<string>();
210
+ }
211
+ }
212
+
213
+ // Runs `action` via the WinUI3 (Windows App SDK) DispatcherQueue when this is a WinUI3 app.
214
+ // Returns true if it handled the call — either by running inline (already on the UI thread) or by
215
+ // enqueuing to the UI thread's queue from a background thread and waiting. Returns false when no
216
+ // DispatcherQueue type exists (classic UWP/WPF app) so the caller can try its other dispatchers.
217
+ // A single probed path keeps one bridge binary working for both classic UWP and WinUI3 — there is
218
+ // no separate method or "is this WinUI3?" flag; whichever dispatcher is actually present wins.
219
+ private static bool TryDispatcherQueueInvoke(Action action)
220
+ {
221
+ try
222
+ {
223
+ Type? dqType = null;
224
+ foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
225
+ {
226
+ dqType = asm.GetType("Microsoft.UI.Dispatching.DispatcherQueue");
227
+ if (dqType != null) break;
228
+ }
229
+ if (dqType == null) return false; // not a WinUI3 app
230
+
231
+ // Capture the UI queue whenever we're on a thread that owns one (the UI thread).
232
+ var current = dqType.GetMethod("GetForCurrentThread", BindingFlags.Public | BindingFlags.Static)?.Invoke(null, null);
233
+ if (current != null) s_uiDispatcherQueue = current;
234
+
235
+ var queue = current ?? s_uiDispatcherQueue;
236
+ if (queue == null) return false; // WinUI3 present, but the UI queue hasn't been seen yet
237
+
238
+ // On the UI thread → run inline.
239
+ if (queue.GetType().GetProperty("HasThreadAccess")?.GetValue(queue) is true)
240
+ {
241
+ action();
242
+ return true;
243
+ }
244
+
245
+ // Background thread → enqueue onto the UI queue and block until it runs.
246
+ var tryEnqueue = queue.GetType().GetMethods()
247
+ .FirstOrDefault(m => m.Name == "TryEnqueue" && m.GetParameters().Length == 1);
248
+ if (tryEnqueue == null) return false;
249
+ var handlerType = tryEnqueue.GetParameters()[0].ParameterType; // DispatcherQueueHandler
250
+ using var mre = new ManualResetEventSlim(false);
251
+ var wrapped = new Action(() => { try { action(); } finally { mre.Set(); } });
252
+ var del = Delegate.CreateDelegate(handlerType, wrapped.Target, wrapped.Method);
253
+ if (tryEnqueue.Invoke(queue, new object[] { del }) is false) return false;
254
+ mre.Wait();
255
+ return true;
256
+ }
257
+ catch { return false; }
258
+ }
259
+
260
+ public static object? RunOnUIThread(int callbackId)
261
+ {
262
+ var action = new Action(() =>
263
+ {
264
+ unsafe
265
+ {
266
+ if (s_jsInvoker == null) return;
267
+ byte* respPtr = null;
268
+ int respLen = 0;
269
+ s_jsInvoker(callbackId, null, 0, &respPtr, &respLen);
270
+ if (respPtr != null && respLen > 0) Marshal.FreeHGlobal((IntPtr)respPtr);
271
+ }
272
+ });
273
+
274
+ try
275
+ {
276
+ foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
277
+ {
278
+ var coreAppType = asm.GetType("Windows.ApplicationModel.Core.CoreApplication");
279
+ if (coreAppType == null) continue;
280
+ var mainViewProp = coreAppType.GetProperty("MainView", BindingFlags.Public | BindingFlags.Static);
281
+ var mainView = mainViewProp?.GetValue(null);
282
+ if (mainView == null) continue;
283
+ var dispatcherProp = mainView.GetType().GetProperty("Dispatcher", BindingFlags.Public | BindingFlags.Instance);
284
+ var dispatcher = dispatcherProp?.GetValue(mainView);
285
+ if (dispatcher == null) continue;
286
+
287
+ var hasAccessProp = dispatcher.GetType().GetProperty("HasThreadAccess", BindingFlags.Public | BindingFlags.Instance);
288
+ if (hasAccessProp?.GetValue(dispatcher) is true)
289
+ {
290
+ action();
291
+ return null;
292
+ }
293
+
294
+ foreach (var m in dispatcher.GetType().GetMethods().Where(m => m.Name == "RunAsync"))
295
+ {
296
+ var parameters = m.GetParameters();
297
+ if (parameters.Length != 2) continue;
298
+ var enumType = parameters[0].ParameterType;
299
+ object priority = enumType.IsEnum ? Enum.ToObject(enumType, 0) : Activator.CreateInstance(enumType)!;
300
+ var handlerType = parameters[1].ParameterType;
301
+ var mre = new ManualResetEventSlim(false);
302
+ var wrapped = new Action(() => { try { action(); } finally { mre.Set(); } });
303
+ try
304
+ {
305
+ var d = Delegate.CreateDelegate(handlerType, wrapped.Target, wrapped.Method);
306
+ m.Invoke(dispatcher, new object[] { priority, d });
307
+ mre.Wait();
308
+ return null;
309
+ }
310
+ catch { }
311
+ }
312
+ break;
313
+ }
314
+ }
315
+ catch { }
316
+
317
+ try { if (TryDispatcherQueueInvoke(action)) return null; } catch { }
318
+
319
+ try
320
+ {
321
+ var wpfDispatcherType = AppDomain.CurrentDomain.GetAssemblies()
322
+ .Select(a => a.GetType("System.Windows.Threading.Dispatcher"))
323
+ .FirstOrDefault(t => t != null);
324
+ if (wpfDispatcherType != null)
325
+ {
326
+ var currentDispatcherProp = wpfDispatcherType.GetProperty("CurrentDispatcher", BindingFlags.Public | BindingFlags.Static);
327
+ var dispatcher = currentDispatcherProp?.GetValue(null);
328
+ if (dispatcher != null)
329
+ {
330
+ var checkAccess = dispatcher.GetType().GetMethod("CheckAccess");
331
+ if (checkAccess?.Invoke(dispatcher, null) is true)
332
+ {
333
+ action();
334
+ return null;
335
+ }
336
+ var beginInvoke = dispatcher.GetType().GetMethod("BeginInvoke", new[] { typeof(Action) })
337
+ ?? dispatcher.GetType().GetMethods().FirstOrDefault(m => m.Name == "BeginInvoke" && m.GetParameters().Length == 1);
338
+ if (beginInvoke != null)
339
+ {
340
+ var mre = new ManualResetEventSlim(false);
341
+ beginInvoke.Invoke(dispatcher, new object[] { new Action(() => { try { action(); } finally { mre.Set(); } }) });
342
+ mre.Wait();
343
+ return null;
344
+ }
345
+ }
346
+ }
347
+ }
348
+ catch { }
349
+
350
+ action();
351
+ return null;
352
+ }
353
+
354
+ public static object? InvokeOnUIThread(Func<object?> callback)
355
+ {
356
+ var mre = new ManualResetEventSlim(false);
357
+ object? result = null;
358
+ var wrapped = new Action(() => { try { result = callback(); } finally { mre.Set(); } });
359
+
360
+ try
361
+ {
362
+ foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
363
+ {
364
+ var coreAppType = asm.GetType("Windows.ApplicationModel.Core.CoreApplication");
365
+ if (coreAppType == null) continue;
366
+ var mainViewProp = coreAppType.GetProperty("MainView", BindingFlags.Public | BindingFlags.Static);
367
+ var mainView = mainViewProp?.GetValue(null);
368
+ if (mainView == null) continue;
369
+ var dispatcherProp = mainView.GetType().GetProperty("Dispatcher", BindingFlags.Public | BindingFlags.Instance);
370
+ var dispatcher = dispatcherProp?.GetValue(mainView);
371
+ if (dispatcher == null) continue;
372
+
373
+ var hasAccessProp = dispatcher.GetType().GetProperty("HasThreadAccess", BindingFlags.Public | BindingFlags.Instance);
374
+ if (hasAccessProp?.GetValue(dispatcher) is true)
375
+ {
376
+ wrapped();
377
+ return result;
378
+ }
379
+
380
+ foreach (var m in dispatcher.GetType().GetMethods().Where(m => m.Name == "RunAsync"))
381
+ {
382
+ var parameters = m.GetParameters();
383
+ if (parameters.Length != 2) continue;
384
+ var enumType = parameters[0].ParameterType;
385
+ object priority = enumType.IsEnum ? Enum.ToObject(enumType, 0) : Activator.CreateInstance(enumType)!;
386
+ var handlerType = parameters[1].ParameterType;
387
+ try
388
+ {
389
+ var d = Delegate.CreateDelegate(handlerType, wrapped.Target, wrapped.Method);
390
+ m.Invoke(dispatcher, new object[] { priority, d });
391
+ mre.Wait();
392
+ return result;
393
+ }
394
+ catch { }
395
+ }
396
+ break;
397
+ }
398
+ }
399
+ catch { }
400
+
401
+ try { if (TryDispatcherQueueInvoke(wrapped)) return result; } catch { }
402
+
403
+ try
404
+ {
405
+ var wpfDispatcherType = AppDomain.CurrentDomain.GetAssemblies()
406
+ .Select(a => a.GetType("System.Windows.Threading.Dispatcher"))
407
+ .FirstOrDefault(t => t != null);
408
+ if (wpfDispatcherType != null)
409
+ {
410
+ var currentDispatcherProp = wpfDispatcherType.GetProperty("CurrentDispatcher", BindingFlags.Public | BindingFlags.Static);
411
+ var dispatcher = currentDispatcherProp?.GetValue(null);
412
+ if (dispatcher != null)
413
+ {
414
+ var checkAccess = dispatcher.GetType().GetMethod("CheckAccess");
415
+ if (checkAccess?.Invoke(dispatcher, null) is true)
416
+ {
417
+ wrapped();
418
+ return result;
419
+ }
420
+ var beginInvoke = dispatcher.GetType().GetMethod("BeginInvoke", new[] { typeof(Action) })
421
+ ?? dispatcher.GetType().GetMethods().FirstOrDefault(m => m.Name == "BeginInvoke" && m.GetParameters().Length == 1);
422
+ if (beginInvoke != null)
423
+ {
424
+ var mre2 = new ManualResetEventSlim(false);
425
+ beginInvoke.Invoke(dispatcher, new object[] { new Action(() => { try { wrapped(); } finally { mre2.Set(); } }) });
426
+ mre2.Wait();
427
+ return result;
428
+ }
429
+ }
430
+ }
431
+ }
432
+ catch { }
433
+
434
+ wrapped();
435
+ return result;
436
+ }
437
+
438
+ private static bool RequiresUiThread(Type t)
439
+ {
440
+ var ns = t.Namespace ?? string.Empty;
441
+ return ns.StartsWith("Windows.UI.Xaml", StringComparison.Ordinal) || ns.StartsWith("Microsoft.UI.Xaml", StringComparison.Ordinal);
442
+ }
443
+
444
+ internal static bool IsMarshaledForDifferentThread(Exception? ex)
445
+ {
446
+ if (ex == null) return false;
447
+ // Unwrap TargetInvocationException if present.
448
+ if (ex is TargetInvocationException tie) return IsMarshaledForDifferentThread(tie.InnerException);
449
+ if (ex is System.Runtime.InteropServices.COMException ce)
450
+ {
451
+ uint h = (uint)ce.HResult;
452
+ // RPC_E_WRONG_THREAD = 0x8001010E, E_FAIL = 0x80004005
453
+ if (h == 0x8001010E || h == 0x80004005) return true;
454
+ }
455
+ return IsMarshaledForDifferentThread(ex.InnerException);
456
+ }
457
+
458
+ private static Assembly? OnAssemblyResolve(object? sender, ResolveEventArgs args)
459
+ {
460
+ try
461
+ {
462
+ var requested = new AssemblyName(args.Name).Name;
463
+ if (string.IsNullOrEmpty(requested)) return null;
464
+
465
+ if (s_assemblyLoadCache.TryGetValue(requested, out var cached)) return cached;
466
+
467
+ // Check already-loaded assemblies first (avoid recursive Assembly.Load).
468
+ var loadedAsm = AppDomain.CurrentDomain.GetAssemblies()
469
+ .FirstOrDefault(a => string.Equals(a.GetName().Name, requested, StringComparison.OrdinalIgnoreCase));
470
+ if (loadedAsm is not null)
471
+ {
472
+ s_assemblyLoadCache[requested] = loadedAsm;
473
+ return loadedAsm;
474
+ }
475
+
476
+ // Try to locate a matching dll in known search directories.
477
+ foreach (var dir in s_assemblySearchDirs)
478
+ {
479
+ try
480
+ {
481
+ var candidate = Path.Combine(dir, requested + ".dll");
482
+ if (File.Exists(candidate))
483
+ {
484
+ try
485
+ {
486
+ var asm = AssemblyLoadContext.Default.LoadFromAssemblyPath(candidate);
487
+ s_assemblyLoadCache[requested] = asm;
488
+ return asm;
489
+ }
490
+ catch { }
491
+ }
492
+ }
493
+ catch { }
494
+ }
495
+
496
+ s_assemblyLoadCache[requested] = null;
497
+ }
498
+ catch { }
499
+ return null;
500
+ }
501
+
502
+ [UnmanagedCallersOnly(EntryPoint = "RegisterJsCallback",
503
+ CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
504
+ public static unsafe int RegisterJsCallback(
505
+ delegate* unmanaged[Cdecl]<int, byte*, int, byte**, int*, void> callback)
506
+ {
507
+ s_jsInvoker = callback;
508
+ return 0;
509
+ }
510
+
511
+ // Callback id registered by the runtime/JS that should receive unhandled
512
+ // managed exceptions and unobserved task exceptions.
513
+ private static int s_unhandledExceptionCallbackId = -1;
514
+
515
+ // Called from JS/Rust to request that managed unhandled exceptions be
516
+ // forwarded to the registered JS callback id. The callback id must have
517
+ // been created on the Rust side and point to a JS function stored in the
518
+ // runtime's `DOTNET_JS_CALLBACKS` map.
519
+ public static int RegisterUnhandledExceptionCallback(int callbackId)
520
+ {
521
+ s_unhandledExceptionCallbackId = callbackId;
522
+
523
+ // Subscribe once (idempotent).
524
+ try
525
+ {
526
+ AppDomain.CurrentDomain.UnhandledException -= OnAppDomainUnhandledException;
527
+ AppDomain.CurrentDomain.UnhandledException += OnAppDomainUnhandledException;
528
+
529
+ TaskScheduler.UnobservedTaskException -= OnUnobservedTaskException;
530
+ TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
531
+
532
+ // Try to subscribe to Windows.CoreApplication.UnhandledErrorDetected if available
533
+ foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
534
+ {
535
+ var coreAppType = asm.GetType("Windows.ApplicationModel.Core.CoreApplication");
536
+ if (coreAppType == null) continue;
537
+ var ev = coreAppType.GetEvent("UnhandledErrorDetected");
538
+ if (ev == null) continue;
539
+ try
540
+ {
541
+ var handlerType = ev.EventHandlerType;
542
+ var method = typeof(Bridge).GetMethod(nameof(CoreApplicationUnhandledErrorHandler), BindingFlags.NonPublic | BindingFlags.Static);
543
+ if (method != null)
544
+ {
545
+ var del = Delegate.CreateDelegate(handlerType, method);
546
+ ev.AddEventHandler(null, del);
547
+ }
548
+ }
549
+ catch { }
550
+ }
551
+ }
552
+ catch { }
553
+
554
+ return 0;
555
+ }
556
+
557
+ // IInspectable IID — the common interface for all WinRT objects.
558
+ // WinRT APIs typically QI for IInspectable (not just IUnknown) when they
559
+ // accept a polymorphic `object` parameter or need runtime class identity.
560
+ private static Guid s_iidIInspectable = new Guid("AF86E2E0-B12D-4C6A-9C5A-D7AA65101E90");
561
+
562
+ // Return a canonical native pointer (IUnknown / IInspectable) for an
563
+ // exported handle id. Returns 0 when no pointer is available. If a
564
+ // pointer isn't already cached in `s_nativePtrs`, attempt to obtain one
565
+ // via `Marshal.GetIUnknownForObject` and cache it for subsequent calls.
566
+ public static IntPtr GetNativePtrForHandle(int handleId)
567
+ {
568
+ if (handleId <= 0) return IntPtr.Zero;
569
+ if (s_nativePtrs.TryGetValue(handleId, out var p)) {
570
+ return p;
571
+ }
572
+
573
+ if (!s_handles.TryGetValue(handleId, out var obj) || obj == null)
574
+ return IntPtr.Zero;
575
+
576
+ try
577
+ {
578
+ var ip = ObtainNativePtr(obj);
579
+ if (ip != IntPtr.Zero)
580
+ {
581
+ StoreNativePtr(handleId, ip);
582
+ return ip;
583
+ }
584
+ }
585
+ catch
586
+ {
587
+ }
588
+
589
+ return IntPtr.Zero;
590
+ }
591
+
592
+ // Obtain a WinRT-usable native pointer for a managed object.
593
+ // Prefers IInspectable over bare IUnknown because WinRT runtime class
594
+ // identity and interface lookup go through IInspectable. If the managed
595
+ // object itself implements IWinRTObject (C#/WinRT projection), its inner
596
+ // native object is used directly so that QI succeeds for all inherited
597
+ // WinRT interfaces (e.g. IUIElement for a FlexboxLayout subclass).
598
+ private static IntPtr ObtainNativePtr(object obj)
599
+ {
600
+ // Path 1 — C#/WinRT IWinRTObject: use the projected inner object's pointer.
601
+ // This gives the real native WinRT pointer, not the managed CCW, so
602
+ // QueryInterface will succeed for all WinRT interfaces inherited by the type.
603
+ try
604
+ {
605
+ var winrtObjType = obj.GetType().GetInterface("WinRT.IWinRTObject");
606
+ if (winrtObjType != null)
607
+ {
608
+ var nativeObjProp = winrtObjType.GetProperty("NativeObject");
609
+ var nativeObj = nativeObjProp?.GetValue(obj);
610
+ if (nativeObj != null)
611
+ {
612
+ var thisPtrProp = nativeObj.GetType().GetProperty("ThisPtr");
613
+ if (thisPtrProp?.GetValue(nativeObj) is IntPtr thisPtr && thisPtr != IntPtr.Zero)
614
+ {
615
+ Marshal.AddRef(thisPtr);
616
+ return thisPtr;
617
+ }
618
+ }
619
+ }
620
+ }
621
+ catch { }
622
+
623
+ IntPtr iunknown = IntPtr.Zero;
624
+ try
625
+ {
626
+ iunknown = Marshal.GetIUnknownForObject(obj);
627
+ }
628
+ catch { }
629
+
630
+ if (iunknown == IntPtr.Zero) return IntPtr.Zero;
631
+
632
+ // Path 2 — QI for IInspectable so WinRT runtime class identity works.
633
+ try
634
+ {
635
+ var iidLocal = s_iidIInspectable;
636
+ var hr = Marshal.QueryInterface(iunknown, ref iidLocal, out var inspectable);
637
+ if (hr == 0 && inspectable != IntPtr.Zero)
638
+ {
639
+ Marshal.Release(iunknown);
640
+ return inspectable;
641
+ }
642
+ }
643
+ catch { }
644
+
645
+ // Path 3 — bare IUnknown fallback.
646
+ return iunknown;
647
+ }
648
+
649
+ private static void OnAppDomainUnhandledException(object? sender, UnhandledExceptionEventArgs e)
650
+ {
651
+ try
652
+ {
653
+ var ex = e.ExceptionObject as Exception;
654
+ var msg = ex?.ToString() ?? e.ExceptionObject?.ToString() ?? "(unknown)";
655
+ SendUnhandledToJs("unhandled", msg);
656
+ }
657
+ catch { }
658
+ }
659
+
660
+ private static void OnUnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
661
+ {
662
+ try
663
+ {
664
+ var msg = e.Exception?.ToString() ?? "(unobserved task exception)";
665
+ SendUnhandledToJs("unobservedTask", msg);
666
+ }
667
+ catch { }
668
+ }
669
+
670
+ // Fallback handler for platform-specific CoreApplication unhandled events.
671
+ private static void CoreApplicationUnhandledErrorHandler(object? sender, object? args)
672
+ {
673
+ try
674
+ {
675
+ var msg = args?.ToString() ?? "(core unhandled)";
676
+ SendUnhandledToJs("coreUnhandled", msg);
677
+ }
678
+ catch { }
679
+ }
680
+
681
+ private static unsafe void SendUnhandledToJs(string kind, string message)
682
+ {
683
+ try
684
+ {
685
+ if (s_jsInvoker == null || s_unhandledExceptionCallbackId <= 0) return;
686
+ var payload = JsonSerializer.Serialize(new { kind = kind, message = message });
687
+ var bytes = System.Text.Encoding.UTF8.GetBytes(payload);
688
+ fixed (byte* p = bytes)
689
+ {
690
+ byte* respPtr = null;
691
+ int respLen = 0;
692
+ s_jsInvoker(s_unhandledExceptionCallbackId, p, bytes.Length, &respPtr, &respLen);
693
+ if (respPtr != null && respLen > 0) Marshal.FreeHGlobal((IntPtr)respPtr);
694
+ }
695
+ }
696
+ catch { }
697
+ }
698
+
699
+ [UnmanagedCallersOnly(EntryPoint = "Invoke",
700
+ CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
701
+ public static unsafe int Invoke(
702
+ byte* requestPtr, int requestLen,
703
+ byte** responsePtr, int* responseLenPtr)
704
+ {
705
+ try
706
+ {
707
+ var span = new ReadOnlySpan<byte>(requestPtr, requestLen);
708
+ var req = JsonSerializer.Deserialize(span, BridgeJsonContext.Default.InvokeRequest)!;
709
+ WriteResult(Dispatch(req), responsePtr, responseLenPtr);
710
+ }
711
+ catch (Exception ex)
712
+ {
713
+ WriteError(Unwrap(ex).Message, responsePtr, responseLenPtr);
714
+ }
715
+ return 0;
716
+ }
717
+
718
+ [UnmanagedCallersOnly(EntryPoint = "Free",
719
+ CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
720
+ public static unsafe void Free(byte* ptr)
721
+ {
722
+ if (ptr != null) Marshal.FreeHGlobal((IntPtr)ptr);
723
+ }
724
+ }