@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.
- package/README.md +27 -0
- package/framework/__PROJECT_NAME__/App.xaml +13 -0
- package/framework/__PROJECT_NAME__/App.xaml.cs +240 -0
- package/framework/__PROJECT_NAME__/Assets/SplashScreen.png +0 -0
- package/framework/__PROJECT_NAME__/Assets/Square150x150Logo.png +0 -0
- package/framework/__PROJECT_NAME__/Assets/Square44x44Logo.png +0 -0
- package/framework/__PROJECT_NAME__/Assets/StoreLogo.png +0 -0
- package/framework/__PROJECT_NAME__/Assets/Wide310x150Logo.png +0 -0
- package/framework/__PROJECT_NAME__/CrashDiagnostics.cs +281 -0
- package/framework/__PROJECT_NAME__/Directory.Build.props +15 -0
- package/framework/__PROJECT_NAME__/Directory.Build.targets +106 -0
- package/framework/__PROJECT_NAME__/Package.appxmanifest +35 -0
- package/framework/__PROJECT_NAME__/Program.cs +34 -0
- package/framework/__PROJECT_NAME__/Properties/Default.rd.xml +9 -0
- package/framework/__PROJECT_NAME__/RuntimeHost.cs +359 -0
- package/framework/__PROJECT_NAME__/__PROJECT_NAME__.csproj +263 -0
- package/framework/dotnet-bridge/BinaryProtocol.cs +161 -0
- package/framework/dotnet-bridge/Bridge.BinaryDispatch.cs +373 -0
- package/framework/dotnet-bridge/Bridge.Dispatch.cs +597 -0
- package/framework/dotnet-bridge/Bridge.JsDelegate.cs +244 -0
- package/framework/dotnet-bridge/Bridge.Proxy.cs +573 -0
- package/framework/dotnet-bridge/Bridge.TaskHelper.cs +207 -0
- package/framework/dotnet-bridge/Bridge.cs +724 -0
- package/framework/dotnet-bridge/DispatchTypes.cs +333 -0
- package/framework/dotnet-bridge/DotNetBridge.csproj +30 -0
- package/framework/dotnet-bridge/publish_build/DotNetBridge.deps.json +23 -0
- package/framework/dotnet-bridge/publish_build/DotNetBridge.dll +0 -0
- package/framework/dotnet-bridge/publish_build/DotNetBridge.pdb +0 -0
- package/framework/dotnet-bridge/publish_build/DotNetBridge.runtimeconfig.json +14 -0
- package/framework/libs/x64/hermes-icu.dll +0 -0
- package/framework/libs/x64/hermes.dll +0 -0
- package/framework/libs/x64/nativescript.dll +0 -0
- package/framework/tools/ManifestMerger/ManifestMerger.deps.json +24 -0
- package/framework/tools/ManifestMerger/ManifestMerger.dll +0 -0
- package/framework/tools/ManifestMerger/ManifestMerger.pdb +0 -0
- package/framework/tools/dotnet-tool-arm64.exe +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.deps.json +23 -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/dotnet-typings-gen/dotnet-typings-gen.runtimeconfig.json +13 -0
- package/framework/tools/sbg-arm64.exe +0 -0
- package/framework/tools/sbg-x64.exe +0 -0
- package/framework/tools/sbg.exe +0 -0
- package/framework/tools/typings-generator-arm64.exe +0 -0
- package/framework/tools/typings-generator-x64.exe +0 -0
- package/framework/tools/typings-generator.exe +0 -0
- 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
|
+
}
|