@nativescript/windows 0.1.0-alpha.8 → 0.1.0-alpha.81
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/build.ps1 +44 -2
- package/framework/__PROJECT_NAME__/App.xaml.cs +67 -22
- 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 +262 -66
- package/framework/__PROJECT_NAME__/Directory.Build.props +15 -0
- package/framework/__PROJECT_NAME__/Directory.Build.targets +153 -0
- package/framework/__PROJECT_NAME__/Package.appxmanifest +2 -5
- package/framework/__PROJECT_NAME__/RuntimeHost.cs +97 -10
- package/framework/__PROJECT_NAME__/__PROJECT_NAME__.csproj +90 -17
- package/framework/dotnet-bridge/BinaryProtocol.cs +145 -0
- package/framework/dotnet-bridge/Bridge.BinaryDispatch.cs +216 -0
- package/framework/dotnet-bridge/Bridge.Dispatch.cs +468 -0
- package/framework/dotnet-bridge/Bridge.JsDelegate.cs +96 -0
- package/framework/dotnet-bridge/Bridge.TaskHelper.cs +207 -0
- package/framework/dotnet-bridge/Bridge.cs +402 -263
- package/framework/dotnet-bridge/DispatchTypes.cs +317 -0
- package/framework/dotnet-bridge/DotNetBridge.csproj +7 -1
- package/framework/libs/arm64/nativescript.dll +0 -0
- package/framework/libs/devtools/arm64/nativescript.dll +0 -0
- package/framework/libs/devtools/x64/nativescript.dll +0 -0
- package/framework/libs/x64/nativescript.dll +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/package.json +19 -19
|
@@ -1,353 +1,492 @@
|
|
|
1
1
|
using System;
|
|
2
|
-
using System.
|
|
2
|
+
using System.Diagnostics;
|
|
3
3
|
using System.Collections.Concurrent;
|
|
4
4
|
using System.Collections.Generic;
|
|
5
|
+
using System.IO;
|
|
5
6
|
using System.Linq;
|
|
6
7
|
using System.Reflection;
|
|
7
|
-
using System.Runtime.InteropServices;
|
|
8
|
-
using System.Text.Json;
|
|
9
|
-
using System.Text.Json.Nodes;
|
|
10
8
|
using System.Threading;
|
|
11
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")]
|
|
12
16
|
|
|
13
17
|
namespace NativeScriptBridge;
|
|
14
18
|
|
|
15
|
-
|
|
16
|
-
/// Reflection-based .NET dispatch bridge — UTF-8 ABI edition.
|
|
17
|
-
///
|
|
18
|
-
/// Invoke() receives a UTF-8 byte slice (no string allocation on the Rust side),
|
|
19
|
-
/// dispatches via reflection, and serialises the response directly to a UTF-8
|
|
20
|
-
/// byte array via JsonSerializer.SerializeToUtf8Bytes(). This removes the
|
|
21
|
-
/// UTF-16 encode/decode round-trip of the original char* ABI.
|
|
22
|
-
///
|
|
23
|
-
/// Request JSON schema
|
|
24
|
-
/// -------------------
|
|
25
|
-
/// Static call: { "assembly": "System", "typeName": "System.Diagnostics.Stopwatch", "method": "StartNew", "args": [] }
|
|
26
|
-
/// Constructor: { "assembly": "System", "typeName": "System.Text.StringBuilder", "method": ".ctor", "args": [128] }
|
|
27
|
-
/// Instance call: { "handle": 3, "method": "Stop", "args": [] }
|
|
28
|
-
/// Property get: { "assembly": "...", "typeName": "...", "method": "get_Now", "args": [] }
|
|
29
|
-
/// Property set: { "handle": 3, "method": "set_IsEnabled", "args": [true] }
|
|
30
|
-
/// Release: { "handle": 3, "method": "__release", "args": [] }
|
|
31
|
-
///
|
|
32
|
-
/// Response JSON schema
|
|
33
|
-
/// --------------------
|
|
34
|
-
/// Primitive / string: { "result": 42.5 }
|
|
35
|
-
/// Managed object: { "result": { "__handle": 7, "__type": "System.Diagnostics.Stopwatch" } }
|
|
36
|
-
/// Array / enumerable: { "result": [1, 2, 3] }
|
|
37
|
-
/// Error: { "error": "Method not found" }
|
|
38
|
-
/// </summary>
|
|
39
|
-
public static class Bridge
|
|
19
|
+
public static partial class Bridge
|
|
40
20
|
{
|
|
41
|
-
|
|
42
|
-
|
|
21
|
+
internal static readonly ConcurrentDictionary<int, object?> s_handles = new();
|
|
22
|
+
internal static int s_nextHandle;
|
|
23
|
+
|
|
24
|
+
// Function pointer registered by the Rust runtime so managed delegates can
|
|
25
|
+
// call back into V8 without a JSON round-trip.
|
|
26
|
+
internal static unsafe delegate* unmanaged[Cdecl]<int, byte*, int, byte**, int*, void>
|
|
27
|
+
s_jsInvoker;
|
|
28
|
+
|
|
29
|
+
private static readonly ConcurrentDictionary<string, Type?> s_typeCache
|
|
30
|
+
= new(StringComparer.Ordinal);
|
|
31
|
+
private static readonly ConcurrentDictionary<MethodKey, DispatchEntry> s_methodCache = new();
|
|
32
|
+
private static readonly ConcurrentDictionary<PropKey, PropertyInfo?> s_propCache = new();
|
|
33
|
+
private static readonly ConcurrentDictionary<CtorKey, CtorEntry> s_ctorCache = new();
|
|
34
|
+
// Cache of attempted assembly loads (simple name -> Assembly or null if not found).
|
|
35
|
+
private static readonly ConcurrentDictionary<string, Assembly?> s_assemblyLoadCache
|
|
36
|
+
= new(StringComparer.OrdinalIgnoreCase);
|
|
37
|
+
// Directories to search for assemblies (initialized once in static ctor).
|
|
38
|
+
private static readonly string[] s_assemblySearchDirs;
|
|
39
|
+
|
|
40
|
+
private static readonly JsonSerializerOptions s_coerceOpts = new()
|
|
41
|
+
{
|
|
42
|
+
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
|
43
|
+
};
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
internal static void ClearCaches()
|
|
46
|
+
{
|
|
47
|
+
s_typeCache.Clear();
|
|
48
|
+
s_methodCache.Clear();
|
|
49
|
+
s_propCache.Clear();
|
|
50
|
+
s_ctorCache.Clear();
|
|
51
|
+
s_handles.Clear();
|
|
52
|
+
s_nativePtrs.Clear();
|
|
53
|
+
s_nextHandle = 0;
|
|
54
|
+
}
|
|
47
55
|
|
|
48
|
-
//
|
|
56
|
+
// Optional mapping from exported handle id -> native IUnknown pointer (Int64)
|
|
57
|
+
// Populated when a managed object can yield a native COM pointer via
|
|
58
|
+
// Marshal.GetIUnknownForObject. Cleared and released on __release.
|
|
59
|
+
internal static readonly ConcurrentDictionary<int, long> s_nativePtrs = new();
|
|
49
60
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
public static unsafe int Invoke(
|
|
55
|
-
byte* requestPtr, int requestLen,
|
|
56
|
-
byte** responsePtr, int* responseLenPtr)
|
|
61
|
+
// Returns a JSON object mapping top-level namespace roots (e.g. "NativeScript")
|
|
62
|
+
// to the assembly simple-name that most likely contains that namespace's types.
|
|
63
|
+
// Reuses s_assemblySearchDirs so plugin and NuGet assemblies are included.
|
|
64
|
+
public static string GetNamespaceAssemblyMapJson()
|
|
57
65
|
{
|
|
58
|
-
|
|
66
|
+
var map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
|
59
67
|
try
|
|
60
68
|
{
|
|
61
|
-
var
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
foreach (var dir in s_assemblySearchDirs.Where(Directory.Exists))
|
|
70
|
+
{
|
|
71
|
+
try
|
|
72
|
+
{
|
|
73
|
+
foreach (var file in Directory.EnumerateFiles(dir, "*.dll", SearchOption.TopDirectoryOnly))
|
|
74
|
+
{
|
|
75
|
+
try
|
|
76
|
+
{
|
|
77
|
+
var simple = Path.GetFileNameWithoutExtension(file);
|
|
78
|
+
if (string.IsNullOrEmpty(simple)) continue;
|
|
79
|
+
|
|
80
|
+
Assembly asm = AppDomain.CurrentDomain.GetAssemblies()
|
|
81
|
+
.FirstOrDefault(a => string.Equals(a.GetName().Name, simple, StringComparison.OrdinalIgnoreCase));
|
|
82
|
+
if (asm is null)
|
|
83
|
+
{
|
|
84
|
+
try { asm = AssemblyLoadContext.Default.LoadFromAssemblyPath(file); }
|
|
85
|
+
catch { continue; }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
Type[] types;
|
|
89
|
+
try { types = asm.GetExportedTypes(); }
|
|
90
|
+
catch { types = Array.Empty<Type>(); }
|
|
91
|
+
|
|
92
|
+
foreach (var t in types)
|
|
93
|
+
{
|
|
94
|
+
if (string.IsNullOrEmpty(t.Namespace)) continue;
|
|
95
|
+
var root = t.Namespace.Split('.')[0];
|
|
96
|
+
if (!map.ContainsKey(root)) map[root] = asm.GetName().Name;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch { }
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch { }
|
|
103
|
+
}
|
|
70
104
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return 0;
|
|
105
|
+
catch { }
|
|
106
|
+
try { return JsonSerializer.Serialize(map); } catch { return "{}"; }
|
|
74
107
|
}
|
|
75
108
|
|
|
76
|
-
|
|
77
|
-
[UnmanagedCallersOnly(EntryPoint = "Free",
|
|
78
|
-
CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
|
|
79
|
-
public static unsafe void Free(byte* ptr)
|
|
109
|
+
static Bridge()
|
|
80
110
|
{
|
|
81
|
-
|
|
82
|
-
Marshal.FreeHGlobal((IntPtr)ptr);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// ── dispatch ─────────────────────────────────────────────────────────────
|
|
86
|
-
|
|
87
|
-
private static InvokeResult Dispatch(InvokeRequest req)
|
|
88
|
-
{
|
|
89
|
-
if (req.Method == "__release" && req.Handle.HasValue)
|
|
111
|
+
try
|
|
90
112
|
{
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
113
|
+
var baseDir = AppContext.BaseDirectory ?? AppDomain.CurrentDomain.BaseDirectory;
|
|
114
|
+
var dirs = new List<string> { baseDir };
|
|
94
115
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
var t = req.Handle.HasValue
|
|
99
|
-
? (s_handles.TryGetValue(req.Handle.Value, out var h) ? h?.GetType() : null)
|
|
100
|
-
: ResolveType(req.Assembly, req.TypeName);
|
|
101
|
-
if (t is null) return new InvokeResult(null, $"Type not found: {req.TypeName}");
|
|
102
|
-
var inst = BindingFlags.Public | BindingFlags.Instance;
|
|
103
|
-
var stat = BindingFlags.Public | BindingFlags.Static;
|
|
104
|
-
var members = new
|
|
116
|
+
// libs/ subtree relative to the bridge's own directory.
|
|
117
|
+
var libs = Path.Combine(baseDir, "libs");
|
|
118
|
+
if (Directory.Exists(libs))
|
|
105
119
|
{
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
staticProperties = t.GetProperties(stat).Select(p => p.Name).Distinct().ToArray(),
|
|
110
|
-
};
|
|
111
|
-
return new InvokeResult(JsonSerializer.SerializeToElement(members, JsonOptions.Default), null);
|
|
112
|
-
}
|
|
120
|
+
dirs.Add(libs);
|
|
121
|
+
try { dirs.AddRange(Directory.GetDirectories(libs, "*", SearchOption.AllDirectories)); } catch { }
|
|
122
|
+
}
|
|
113
123
|
|
|
114
|
-
|
|
115
|
-
|
|
124
|
+
// The bridge DLL lives at dotnet-bridge/publish/ inside the app output root.
|
|
125
|
+
// Plugin assemblies (added via plugin.props) land at plugins/**/*.dll and NuGet
|
|
126
|
+
// assemblies land at the app output root itself — both outside the bridge subtree.
|
|
127
|
+
// Use the host process directory to reach them.
|
|
128
|
+
try
|
|
129
|
+
{
|
|
130
|
+
var processDir = Path.GetDirectoryName(
|
|
131
|
+
System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName);
|
|
132
|
+
if (!string.IsNullOrEmpty(processDir))
|
|
133
|
+
{
|
|
134
|
+
dirs.Add(processDir);
|
|
135
|
+
|
|
136
|
+
// plugins/ subtree: CLI-managed plugin DLLs live here.
|
|
137
|
+
// Check both {processDir}/plugins and {processDir}/../plugins because
|
|
138
|
+
// the NS CLI places plugins/ as a sibling of bin/, not inside it.
|
|
139
|
+
var pluginsDir = Path.Combine(processDir, "plugins");
|
|
140
|
+
if (Directory.Exists(pluginsDir))
|
|
141
|
+
try { dirs.AddRange(Directory.GetDirectories(pluginsDir, "*", SearchOption.AllDirectories)); } catch { }
|
|
142
|
+
|
|
143
|
+
var parentDir = Path.GetDirectoryName(processDir);
|
|
144
|
+
if (!string.IsNullOrEmpty(parentDir))
|
|
145
|
+
{
|
|
146
|
+
var parentPlugins = Path.Combine(parentDir, "plugins");
|
|
147
|
+
if (Directory.Exists(parentPlugins))
|
|
148
|
+
{
|
|
149
|
+
dirs.Add(parentPlugins);
|
|
150
|
+
try { dirs.AddRange(Directory.GetDirectories(parentPlugins, "*", SearchOption.AllDirectories)); } catch { }
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// libs/ relative to the app root (alternative convention).
|
|
155
|
+
var processLibs = Path.Combine(processDir, "libs");
|
|
156
|
+
if (Directory.Exists(processLibs))
|
|
157
|
+
try { dirs.AddRange(Directory.GetDirectories(processLibs, "*", SearchOption.AllDirectories)); } catch { }
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
catch { }
|
|
116
161
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
if (!s_handles.TryGetValue(req.Handle.Value, out target))
|
|
120
|
-
return new InvokeResult(null, $"Invalid handle {req.Handle.Value}");
|
|
121
|
-
type = target?.GetType();
|
|
162
|
+
s_assemblySearchDirs = dirs.Where(d => !string.IsNullOrEmpty(d)).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
|
|
163
|
+
AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
|
|
122
164
|
}
|
|
123
|
-
|
|
165
|
+
catch
|
|
124
166
|
{
|
|
125
|
-
|
|
126
|
-
if (type is null)
|
|
127
|
-
return new InvokeResult(null, $"Type not found: {req.TypeName}");
|
|
167
|
+
s_assemblySearchDirs = Array.Empty<string>();
|
|
128
168
|
}
|
|
169
|
+
}
|
|
129
170
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
171
|
+
public static object? RunOnUIThread(int callbackId)
|
|
172
|
+
{
|
|
173
|
+
var action = new Action(() =>
|
|
174
|
+
{
|
|
175
|
+
unsafe
|
|
176
|
+
{
|
|
177
|
+
if (s_jsInvoker == null) return;
|
|
178
|
+
byte* respPtr = null;
|
|
179
|
+
int respLen = 0;
|
|
180
|
+
s_jsInvoker(callbackId, null, 0, &respPtr, &respLen);
|
|
181
|
+
if (respPtr != null && respLen > 0) Marshal.FreeHGlobal((IntPtr)respPtr);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
134
184
|
|
|
135
|
-
|
|
136
|
-
if (method == ".ctor")
|
|
185
|
+
try
|
|
137
186
|
{
|
|
138
|
-
var
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
187
|
+
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
|
|
188
|
+
{
|
|
189
|
+
var coreAppType = asm.GetType("Windows.ApplicationModel.Core.CoreApplication");
|
|
190
|
+
if (coreAppType == null) continue;
|
|
191
|
+
var mainViewProp = coreAppType.GetProperty("MainView", BindingFlags.Public | BindingFlags.Static);
|
|
192
|
+
var mainView = mainViewProp?.GetValue(null);
|
|
193
|
+
if (mainView == null) continue;
|
|
194
|
+
var dispatcherProp = mainView.GetType().GetProperty("Dispatcher", BindingFlags.Public | BindingFlags.Instance);
|
|
195
|
+
var dispatcher = dispatcherProp?.GetValue(mainView);
|
|
196
|
+
if (dispatcher == null) continue;
|
|
197
|
+
|
|
198
|
+
var hasAccessProp = dispatcher.GetType().GetProperty("HasThreadAccess", BindingFlags.Public | BindingFlags.Instance);
|
|
199
|
+
if (hasAccessProp?.GetValue(dispatcher) is true)
|
|
200
|
+
{
|
|
201
|
+
action();
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
foreach (var m in dispatcher.GetType().GetMethods().Where(m => m.Name == "RunAsync"))
|
|
206
|
+
{
|
|
207
|
+
var parameters = m.GetParameters();
|
|
208
|
+
if (parameters.Length != 2) continue;
|
|
209
|
+
var enumType = parameters[0].ParameterType;
|
|
210
|
+
object priority = enumType.IsEnum ? Enum.ToObject(enumType, 0) : Activator.CreateInstance(enumType)!;
|
|
211
|
+
var handlerType = parameters[1].ParameterType;
|
|
212
|
+
var mre = new ManualResetEventSlim(false);
|
|
213
|
+
var wrapped = new Action(() => { try { action(); } finally { mre.Set(); } });
|
|
214
|
+
try
|
|
215
|
+
{
|
|
216
|
+
var d = Delegate.CreateDelegate(handlerType, wrapped.Target, wrapped.Method);
|
|
217
|
+
m.Invoke(dispatcher, new object[] { priority, d });
|
|
218
|
+
mre.Wait();
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
catch { }
|
|
222
|
+
}
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
149
225
|
}
|
|
226
|
+
catch { }
|
|
150
227
|
|
|
151
|
-
|
|
152
|
-
var flags = (isStatic ? BindingFlags.Static : BindingFlags.Instance)
|
|
153
|
-
| BindingFlags.Public;
|
|
154
|
-
|
|
155
|
-
// ── Property getter / setter ─────────────────────────────────────────
|
|
156
|
-
if (method.StartsWith("get_", StringComparison.Ordinal))
|
|
228
|
+
try
|
|
157
229
|
{
|
|
158
|
-
var
|
|
159
|
-
|
|
160
|
-
|
|
230
|
+
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
|
|
231
|
+
{
|
|
232
|
+
var dqType = asm.GetType("Microsoft.UI.Dispatching.DispatcherQueue");
|
|
233
|
+
if (dqType == null) continue;
|
|
234
|
+
var getForCurrent = dqType.GetMethod("GetForCurrentThread", BindingFlags.Public | BindingFlags.Static);
|
|
235
|
+
var dq = getForCurrent?.Invoke(null, null);
|
|
236
|
+
if (dq == null) continue;
|
|
237
|
+
|
|
238
|
+
if (dq.GetType().GetProperty("HasThreadAccess")?.GetValue(dq) is true)
|
|
239
|
+
{
|
|
240
|
+
action();
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
161
245
|
}
|
|
162
|
-
|
|
246
|
+
catch { }
|
|
247
|
+
|
|
248
|
+
try
|
|
163
249
|
{
|
|
164
|
-
var
|
|
165
|
-
|
|
250
|
+
var wpfDispatcherType = AppDomain.CurrentDomain.GetAssemblies()
|
|
251
|
+
.Select(a => a.GetType("System.Windows.Threading.Dispatcher"))
|
|
252
|
+
.FirstOrDefault(t => t != null);
|
|
253
|
+
if (wpfDispatcherType != null)
|
|
166
254
|
{
|
|
167
|
-
|
|
168
|
-
|
|
255
|
+
var currentDispatcherProp = wpfDispatcherType.GetProperty("CurrentDispatcher", BindingFlags.Public | BindingFlags.Static);
|
|
256
|
+
var dispatcher = currentDispatcherProp?.GetValue(null);
|
|
257
|
+
if (dispatcher != null)
|
|
258
|
+
{
|
|
259
|
+
var checkAccess = dispatcher.GetType().GetMethod("CheckAccess");
|
|
260
|
+
if (checkAccess?.Invoke(dispatcher, null) is true)
|
|
261
|
+
{
|
|
262
|
+
action();
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
var beginInvoke = dispatcher.GetType().GetMethod("BeginInvoke", new[] { typeof(Action) })
|
|
266
|
+
?? dispatcher.GetType().GetMethods().FirstOrDefault(m => m.Name == "BeginInvoke" && m.GetParameters().Length == 1);
|
|
267
|
+
if (beginInvoke != null)
|
|
268
|
+
{
|
|
269
|
+
var mre = new ManualResetEventSlim(false);
|
|
270
|
+
beginInvoke.Invoke(dispatcher, new object[] { new Action(() => { try { action(); } finally { mre.Set(); } }) });
|
|
271
|
+
mre.Wait();
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
169
275
|
}
|
|
170
276
|
}
|
|
277
|
+
catch { }
|
|
171
278
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
var mi = FindMethod(type, method, argElems.Count, flags);
|
|
175
|
-
if (mi is null)
|
|
176
|
-
return new InvokeResult(null,
|
|
177
|
-
$"Method '{method}' ({argElems.Count} args) not found on {type.FullName}");
|
|
178
|
-
|
|
179
|
-
var parameters = mi.GetParameters();
|
|
180
|
-
var callArgs = new object?[parameters.Length];
|
|
181
|
-
for (int i = 0; i < parameters.Length && i < argElems.Count; i++)
|
|
182
|
-
callArgs[i] = Coerce(argElems[i], parameters[i].ParameterType);
|
|
183
|
-
|
|
184
|
-
return Box(AwaitIfTask(mi.Invoke(target, callArgs)));
|
|
279
|
+
action();
|
|
280
|
+
return null;
|
|
185
281
|
}
|
|
186
282
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
private static object? AwaitIfTask(object? value)
|
|
283
|
+
private static Assembly? OnAssemblyResolve(object? sender, ResolveEventArgs args)
|
|
190
284
|
{
|
|
191
|
-
|
|
192
|
-
var t = value.GetType();
|
|
193
|
-
|
|
194
|
-
if (value is Task task)
|
|
285
|
+
try
|
|
195
286
|
{
|
|
196
|
-
|
|
197
|
-
if (
|
|
198
|
-
throw task.Exception!.InnerException ?? task.Exception;
|
|
199
|
-
return t.GetProperty("Result")?.GetValue(value);
|
|
200
|
-
}
|
|
287
|
+
var requested = new AssemblyName(args.Name).Name;
|
|
288
|
+
if (string.IsNullOrEmpty(requested)) return null;
|
|
201
289
|
|
|
202
|
-
|
|
203
|
-
{
|
|
204
|
-
var innerTask = (Task)t.GetMethod("AsTask")!.Invoke(value, null)!;
|
|
205
|
-
innerTask.Wait();
|
|
206
|
-
if (innerTask.IsFaulted)
|
|
207
|
-
throw innerTask.Exception!.InnerException ?? innerTask.Exception;
|
|
208
|
-
return innerTask.GetType().GetProperty("Result")?.GetValue(innerTask);
|
|
209
|
-
}
|
|
290
|
+
if (s_assemblyLoadCache.TryGetValue(requested, out var cached)) return cached;
|
|
210
291
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
292
|
+
// Check already-loaded assemblies first (avoid recursive Assembly.Load).
|
|
293
|
+
var loadedAsm = AppDomain.CurrentDomain.GetAssemblies()
|
|
294
|
+
.FirstOrDefault(a => string.Equals(a.GetName().Name, requested, StringComparison.OrdinalIgnoreCase));
|
|
295
|
+
if (loadedAsm is not null)
|
|
296
|
+
{
|
|
297
|
+
s_assemblyLoadCache[requested] = loadedAsm;
|
|
298
|
+
return loadedAsm;
|
|
299
|
+
}
|
|
216
300
|
|
|
217
|
-
|
|
301
|
+
// Try to locate a matching dll in known search directories.
|
|
302
|
+
foreach (var dir in s_assemblySearchDirs)
|
|
303
|
+
{
|
|
304
|
+
try
|
|
305
|
+
{
|
|
306
|
+
var candidate = Path.Combine(dir, requested + ".dll");
|
|
307
|
+
if (File.Exists(candidate))
|
|
308
|
+
{
|
|
309
|
+
try
|
|
310
|
+
{
|
|
311
|
+
var asm = AssemblyLoadContext.Default.LoadFromAssemblyPath(candidate);
|
|
312
|
+
s_assemblyLoadCache[requested] = asm;
|
|
313
|
+
return asm;
|
|
314
|
+
}
|
|
315
|
+
catch { }
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
catch { }
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
s_assemblyLoadCache[requested] = null;
|
|
322
|
+
}
|
|
323
|
+
catch { }
|
|
324
|
+
return null;
|
|
218
325
|
}
|
|
219
326
|
|
|
220
|
-
|
|
327
|
+
[UnmanagedCallersOnly(EntryPoint = "RegisterJsCallback",
|
|
328
|
+
CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
|
|
329
|
+
public static unsafe int RegisterJsCallback(
|
|
330
|
+
delegate* unmanaged[Cdecl]<int, byte*, int, byte**, int*, void> callback)
|
|
221
331
|
{
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
return s_typeCache.GetOrAdd(key, _ => ResolveTypeCore(assemblyName, typeName));
|
|
332
|
+
s_jsInvoker = callback;
|
|
333
|
+
return 0;
|
|
225
334
|
}
|
|
226
335
|
|
|
227
|
-
|
|
336
|
+
// Callback id registered by the runtime/JS that should receive unhandled
|
|
337
|
+
// managed exceptions and unobserved task exceptions.
|
|
338
|
+
private static int s_unhandledExceptionCallbackId = -1;
|
|
339
|
+
|
|
340
|
+
// Called from JS/Rust to request that managed unhandled exceptions be
|
|
341
|
+
// forwarded to the registered JS callback id. The callback id must have
|
|
342
|
+
// been created on the Rust side and point to a JS function stored in the
|
|
343
|
+
// runtime's `DOTNET_JS_CALLBACKS` map.
|
|
344
|
+
public static int RegisterUnhandledExceptionCallback(int callbackId)
|
|
228
345
|
{
|
|
229
|
-
|
|
230
|
-
var t = Type.GetType(fqn);
|
|
231
|
-
if (t is not null) return t;
|
|
346
|
+
s_unhandledExceptionCallbackId = callbackId;
|
|
232
347
|
|
|
233
|
-
|
|
348
|
+
// Subscribe once (idempotent).
|
|
349
|
+
try
|
|
234
350
|
{
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
351
|
+
AppDomain.CurrentDomain.UnhandledException -= OnAppDomainUnhandledException;
|
|
352
|
+
AppDomain.CurrentDomain.UnhandledException += OnAppDomainUnhandledException;
|
|
238
353
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
354
|
+
TaskScheduler.UnobservedTaskException -= OnUnobservedTaskException;
|
|
355
|
+
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
|
|
356
|
+
|
|
357
|
+
// Try to subscribe to Windows.CoreApplication.UnhandledErrorDetected if available
|
|
358
|
+
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
|
|
242
359
|
{
|
|
243
|
-
var
|
|
244
|
-
|
|
245
|
-
|
|
360
|
+
var coreAppType = asm.GetType("Windows.ApplicationModel.Core.CoreApplication");
|
|
361
|
+
if (coreAppType == null) continue;
|
|
362
|
+
var ev = coreAppType.GetEvent("UnhandledErrorDetected");
|
|
363
|
+
if (ev == null) continue;
|
|
364
|
+
try
|
|
365
|
+
{
|
|
366
|
+
var handlerType = ev.EventHandlerType;
|
|
367
|
+
var method = typeof(Bridge).GetMethod(nameof(CoreApplicationUnhandledErrorHandler), BindingFlags.NonPublic | BindingFlags.Static);
|
|
368
|
+
if (method != null)
|
|
369
|
+
{
|
|
370
|
+
var del = Delegate.CreateDelegate(handlerType, method);
|
|
371
|
+
ev.AddEventHandler(null, del);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
catch { }
|
|
246
375
|
}
|
|
247
|
-
catch { }
|
|
248
376
|
}
|
|
377
|
+
catch { }
|
|
249
378
|
|
|
250
|
-
return
|
|
379
|
+
return 0;
|
|
251
380
|
}
|
|
252
381
|
|
|
253
|
-
|
|
382
|
+
// Return a canonical native pointer (IUnknown / IInspectable) for an
|
|
383
|
+
// exported handle id. Returns 0 when no pointer is available. If a
|
|
384
|
+
// pointer isn't already cached in `s_nativePtrs`, attempt to obtain one
|
|
385
|
+
// via `Marshal.GetIUnknownForObject` and cache it for subsequent calls.
|
|
386
|
+
public static long GetNativePtrForHandle(int handleId)
|
|
254
387
|
{
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
388
|
+
if (handleId <= 0) return 0;
|
|
389
|
+
if (s_nativePtrs.TryGetValue(handleId, out var p)) {
|
|
390
|
+
try { Debug.WriteLine($"[Bridge] GetNativePtrForHandle({handleId}) -> 0x{p:x} (cached)"); } catch { }
|
|
391
|
+
return p;
|
|
392
|
+
}
|
|
258
393
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
394
|
+
if (!s_handles.TryGetValue(handleId, out var obj) || obj == null)
|
|
395
|
+
return 0;
|
|
396
|
+
|
|
397
|
+
try
|
|
398
|
+
{
|
|
399
|
+
var ip = Marshal.GetIUnknownForObject(obj);
|
|
400
|
+
if (ip != IntPtr.Zero)
|
|
401
|
+
{
|
|
402
|
+
var val = ip.ToInt64();
|
|
403
|
+
s_nativePtrs[handleId] = val;
|
|
404
|
+
try { Debug.WriteLine($"[Bridge] GetNativePtrForHandle({handleId}) -> 0x{val:x} (new)"); } catch { }
|
|
405
|
+
return val;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
catch (Exception ex)
|
|
409
|
+
{
|
|
410
|
+
try { Debug.WriteLine($"[Bridge] GetNativePtrForHandle({handleId}) threw: {ex}"); } catch { }
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
try { Debug.WriteLine($"[Bridge] GetNativePtrForHandle({handleId}) -> 0 (none)"); } catch { }
|
|
414
|
+
return 0;
|
|
266
415
|
}
|
|
267
416
|
|
|
268
|
-
private static
|
|
417
|
+
private static void OnAppDomainUnhandledException(object? sender, UnhandledExceptionEventArgs e)
|
|
269
418
|
{
|
|
270
|
-
|
|
271
|
-
|
|
419
|
+
try
|
|
420
|
+
{
|
|
421
|
+
var ex = e.ExceptionObject as Exception;
|
|
422
|
+
var msg = ex?.ToString() ?? e.ExceptionObject?.ToString() ?? "(unknown)";
|
|
423
|
+
SendUnhandledToJs("unhandled", msg);
|
|
424
|
+
}
|
|
425
|
+
catch { }
|
|
272
426
|
}
|
|
273
427
|
|
|
274
|
-
private static object?
|
|
428
|
+
private static void OnUnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
|
|
275
429
|
{
|
|
276
|
-
|
|
277
|
-
if (el.ValueKind == JsonValueKind.Object && el.TryGetProperty("__handle", out var h))
|
|
430
|
+
try
|
|
278
431
|
{
|
|
279
|
-
|
|
280
|
-
|
|
432
|
+
var msg = e.Exception?.ToString() ?? "(unobserved task exception)";
|
|
433
|
+
SendUnhandledToJs("unobservedTask", msg);
|
|
281
434
|
}
|
|
282
|
-
|
|
435
|
+
catch { }
|
|
283
436
|
}
|
|
284
437
|
|
|
285
|
-
|
|
438
|
+
// Fallback handler for platform-specific CoreApplication unhandled events.
|
|
439
|
+
private static void CoreApplicationUnhandledErrorHandler(object? sender, object? args)
|
|
286
440
|
{
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
var t = value.GetType();
|
|
290
|
-
|
|
291
|
-
if (t.IsPrimitive || t == typeof(string) || t == typeof(decimal)
|
|
292
|
-
|| t == typeof(DateTime) || t == typeof(DateTimeOffset)
|
|
293
|
-
|| t == typeof(TimeSpan) || t == typeof(Guid))
|
|
441
|
+
try
|
|
294
442
|
{
|
|
295
|
-
|
|
296
|
-
|
|
443
|
+
var msg = args?.ToString() ?? "(core unhandled)";
|
|
444
|
+
SendUnhandledToJs("coreUnhandled", msg);
|
|
297
445
|
}
|
|
446
|
+
catch { }
|
|
447
|
+
}
|
|
298
448
|
|
|
299
|
-
|
|
300
|
-
|
|
449
|
+
private static unsafe void SendUnhandledToJs(string kind, string message)
|
|
450
|
+
{
|
|
451
|
+
try
|
|
301
452
|
{
|
|
302
|
-
|
|
453
|
+
if (s_jsInvoker == null || s_unhandledExceptionCallbackId <= 0) return;
|
|
454
|
+
var payload = JsonSerializer.Serialize(new { kind = kind, message = message });
|
|
455
|
+
var bytes = System.Text.Encoding.UTF8.GetBytes(payload);
|
|
456
|
+
fixed (byte* p = bytes)
|
|
303
457
|
{
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
null);
|
|
458
|
+
byte* respPtr = null;
|
|
459
|
+
int respLen = 0;
|
|
460
|
+
s_jsInvoker(s_unhandledExceptionCallbackId, p, bytes.Length, &respPtr, &respLen);
|
|
461
|
+
if (respPtr != null && respLen > 0) Marshal.FreeHGlobal((IntPtr)respPtr);
|
|
309
462
|
}
|
|
310
|
-
catch { }
|
|
311
463
|
}
|
|
312
|
-
|
|
313
|
-
var id = Interlocked.Increment(ref s_nextHandle);
|
|
314
|
-
s_handles[id] = value;
|
|
315
|
-
return new InvokeResult(
|
|
316
|
-
JsonSerializer.SerializeToElement(
|
|
317
|
-
new { __handle = id, __type = t.FullName }, JsonOptions.Default),
|
|
318
|
-
null);
|
|
464
|
+
catch { }
|
|
319
465
|
}
|
|
320
466
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
467
|
+
[UnmanagedCallersOnly(EntryPoint = "Invoke",
|
|
468
|
+
CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
|
|
469
|
+
public static unsafe int Invoke(
|
|
470
|
+
byte* requestPtr, int requestLen,
|
|
471
|
+
byte** responsePtr, int* responseLenPtr)
|
|
325
472
|
{
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
473
|
+
try
|
|
474
|
+
{
|
|
475
|
+
var span = new ReadOnlySpan<byte>(requestPtr, requestLen);
|
|
476
|
+
var req = JsonSerializer.Deserialize(span, BridgeJsonContext.Default.InvokeRequest)!;
|
|
477
|
+
WriteResult(Dispatch(req), responsePtr, responseLenPtr);
|
|
478
|
+
}
|
|
479
|
+
catch (Exception ex)
|
|
480
|
+
{
|
|
481
|
+
WriteError(Unwrap(ex).Message, responsePtr, responseLenPtr);
|
|
482
|
+
}
|
|
483
|
+
return 0;
|
|
331
484
|
}
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
// ── JSON types ────────────────────────────────────────────────────────────────
|
|
335
|
-
|
|
336
|
-
internal sealed record InvokeRequest(
|
|
337
|
-
string? Assembly,
|
|
338
|
-
string? TypeName,
|
|
339
|
-
string? Method,
|
|
340
|
-
int? Handle,
|
|
341
|
-
List<JsonElement>? Args
|
|
342
|
-
);
|
|
343
485
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
{
|
|
348
|
-
internal static readonly JsonSerializerOptions Default = new()
|
|
486
|
+
[UnmanagedCallersOnly(EntryPoint = "Free",
|
|
487
|
+
CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
|
|
488
|
+
public static unsafe void Free(byte* ptr)
|
|
349
489
|
{
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
};
|
|
490
|
+
if (ptr != null) Marshal.FreeHGlobal((IntPtr)ptr);
|
|
491
|
+
}
|
|
353
492
|
}
|