@nativescript/windows 0.1.0-alpha.7 → 0.1.0-alpha.70
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 +42 -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__/Package.appxmanifest +2 -5
- package/framework/__PROJECT_NAME__/RuntimeHost.cs +132 -22
- package/framework/__PROJECT_NAME__/__PROJECT_NAME__.csproj +49 -12
- package/framework/dotnet-bridge/BinaryProtocol.cs +145 -0
- package/framework/dotnet-bridge/Bridge.BinaryDispatch.cs +203 -0
- package/framework/dotnet-bridge/Bridge.Dispatch.cs +433 -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 +287 -286
- package/framework/dotnet-bridge/DispatchTypes.cs +304 -0
- 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/package.json +19 -19
- package/tools/dotnet-tool-arm64.exe +0 -0
- package/tools/dotnet-tool-x64.exe +0 -0
- package/tools/dotnet-tool.exe +0 -0
|
@@ -1,353 +1,354 @@
|
|
|
1
1
|
using System;
|
|
2
|
-
using System.Collections;
|
|
3
2
|
using System.Collections.Concurrent;
|
|
4
3
|
using System.Collections.Generic;
|
|
4
|
+
using System.IO;
|
|
5
5
|
using System.Linq;
|
|
6
6
|
using System.Reflection;
|
|
7
|
-
using System.Runtime.InteropServices;
|
|
8
|
-
using System.Text.Json;
|
|
9
|
-
using System.Text.Json.Nodes;
|
|
10
7
|
using System.Threading;
|
|
11
8
|
using System.Threading.Tasks;
|
|
9
|
+
using System.Runtime.InteropServices;
|
|
10
|
+
using System.Runtime.Loader;
|
|
11
|
+
using System.Text.Json;
|
|
12
|
+
|
|
13
|
+
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("DotNetBridgeTests")]
|
|
14
|
+
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("DotNetBridgeBenchmarks")]
|
|
12
15
|
|
|
13
16
|
namespace NativeScriptBridge;
|
|
14
17
|
|
|
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
|
|
18
|
+
public static partial class Bridge
|
|
40
19
|
{
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
20
|
+
internal static readonly ConcurrentDictionary<int, object?> s_handles = new();
|
|
21
|
+
internal static int s_nextHandle;
|
|
22
|
+
|
|
23
|
+
// Function pointer registered by the Rust runtime so managed delegates can
|
|
24
|
+
// call back into V8 without a JSON round-trip.
|
|
25
|
+
internal static unsafe delegate* unmanaged[Cdecl]<int, byte*, int, byte**, int*, void>
|
|
26
|
+
s_jsInvoker;
|
|
27
|
+
|
|
28
|
+
private static readonly ConcurrentDictionary<string, Type?> s_typeCache
|
|
29
|
+
= new(StringComparer.Ordinal);
|
|
30
|
+
private static readonly ConcurrentDictionary<MethodKey, DispatchEntry> s_methodCache = new();
|
|
31
|
+
private static readonly ConcurrentDictionary<PropKey, PropertyInfo?> s_propCache = new();
|
|
32
|
+
private static readonly ConcurrentDictionary<CtorKey, CtorEntry> s_ctorCache = new();
|
|
33
|
+
// Cache of attempted assembly loads (simple name -> Assembly or null if not found).
|
|
34
|
+
private static readonly ConcurrentDictionary<string, Assembly?> s_assemblyLoadCache
|
|
35
|
+
= new(StringComparer.OrdinalIgnoreCase);
|
|
36
|
+
// Directories to search for assemblies (initialized once in static ctor).
|
|
37
|
+
private static readonly string[] s_assemblySearchDirs;
|
|
38
|
+
|
|
39
|
+
private static readonly JsonSerializerOptions s_coerceOpts = new()
|
|
40
|
+
{
|
|
41
|
+
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
|
42
|
+
};
|
|
47
43
|
|
|
48
|
-
|
|
44
|
+
internal static void ClearCaches()
|
|
45
|
+
{
|
|
46
|
+
s_typeCache.Clear();
|
|
47
|
+
s_methodCache.Clear();
|
|
48
|
+
s_propCache.Clear();
|
|
49
|
+
s_ctorCache.Clear();
|
|
50
|
+
s_handles.Clear();
|
|
51
|
+
s_nextHandle = 0;
|
|
52
|
+
}
|
|
49
53
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
public static unsafe int Invoke(
|
|
55
|
-
byte* requestPtr, int requestLen,
|
|
56
|
-
byte** responsePtr, int* responseLenPtr)
|
|
54
|
+
// Returns a JSON object mapping top-level namespace roots (e.g. "NativeScript")
|
|
55
|
+
// to the assembly simple-name that most likely contains that namespace's types.
|
|
56
|
+
// Reuses s_assemblySearchDirs so plugin and NuGet assemblies are included.
|
|
57
|
+
public static string GetNamespaceAssemblyMapJson()
|
|
57
58
|
{
|
|
58
|
-
|
|
59
|
+
var map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
|
59
60
|
try
|
|
60
61
|
{
|
|
61
|
-
var
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
62
|
+
foreach (var dir in s_assemblySearchDirs.Where(Directory.Exists))
|
|
63
|
+
{
|
|
64
|
+
try
|
|
65
|
+
{
|
|
66
|
+
foreach (var file in Directory.EnumerateFiles(dir, "*.dll", SearchOption.TopDirectoryOnly))
|
|
67
|
+
{
|
|
68
|
+
try
|
|
69
|
+
{
|
|
70
|
+
var simple = Path.GetFileNameWithoutExtension(file);
|
|
71
|
+
if (string.IsNullOrEmpty(simple)) continue;
|
|
72
|
+
|
|
73
|
+
Assembly asm = AppDomain.CurrentDomain.GetAssemblies()
|
|
74
|
+
.FirstOrDefault(a => string.Equals(a.GetName().Name, simple, StringComparison.OrdinalIgnoreCase));
|
|
75
|
+
if (asm is null)
|
|
76
|
+
{
|
|
77
|
+
try { asm = AssemblyLoadContext.Default.LoadFromAssemblyPath(file); }
|
|
78
|
+
catch { continue; }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
Type[] types;
|
|
82
|
+
try { types = asm.GetExportedTypes(); }
|
|
83
|
+
catch { types = Array.Empty<Type>(); }
|
|
84
|
+
|
|
85
|
+
foreach (var t in types)
|
|
86
|
+
{
|
|
87
|
+
if (string.IsNullOrEmpty(t.Namespace)) continue;
|
|
88
|
+
var root = t.Namespace.Split('.')[0];
|
|
89
|
+
if (!map.ContainsKey(root)) map[root] = asm.GetName().Name;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch { }
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch { }
|
|
96
|
+
}
|
|
70
97
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
return 0;
|
|
98
|
+
catch { }
|
|
99
|
+
try { return JsonSerializer.Serialize(map); } catch { return "{}"; }
|
|
74
100
|
}
|
|
75
101
|
|
|
76
|
-
|
|
77
|
-
[UnmanagedCallersOnly(EntryPoint = "Free",
|
|
78
|
-
CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
|
|
79
|
-
public static unsafe void Free(byte* ptr)
|
|
102
|
+
static Bridge()
|
|
80
103
|
{
|
|
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)
|
|
104
|
+
try
|
|
90
105
|
{
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
106
|
+
var baseDir = AppContext.BaseDirectory ?? AppDomain.CurrentDomain.BaseDirectory;
|
|
107
|
+
var dirs = new List<string> { baseDir };
|
|
94
108
|
|
|
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
|
|
109
|
+
// libs/ subtree relative to the bridge's own directory.
|
|
110
|
+
var libs = Path.Combine(baseDir, "libs");
|
|
111
|
+
if (Directory.Exists(libs))
|
|
105
112
|
{
|
|
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
|
-
}
|
|
113
|
+
dirs.Add(libs);
|
|
114
|
+
try { dirs.AddRange(Directory.GetDirectories(libs, "*", SearchOption.AllDirectories)); } catch { }
|
|
115
|
+
}
|
|
113
116
|
|
|
114
|
-
|
|
115
|
-
|
|
117
|
+
// The bridge DLL lives at dotnet-bridge/publish/ inside the app output root.
|
|
118
|
+
// Plugin assemblies (added via plugin.props) land at plugins/**/*.dll and NuGet
|
|
119
|
+
// assemblies land at the app output root itself — both outside the bridge subtree.
|
|
120
|
+
// Use the host process directory to reach them.
|
|
121
|
+
try
|
|
122
|
+
{
|
|
123
|
+
var processDir = Path.GetDirectoryName(
|
|
124
|
+
System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName);
|
|
125
|
+
if (!string.IsNullOrEmpty(processDir))
|
|
126
|
+
{
|
|
127
|
+
dirs.Add(processDir);
|
|
128
|
+
|
|
129
|
+
// plugins/ subtree: CLI-managed plugin DLLs live here.
|
|
130
|
+
// Check both {processDir}/plugins and {processDir}/../plugins because
|
|
131
|
+
// the NS CLI places plugins/ as a sibling of bin/, not inside it.
|
|
132
|
+
var pluginsDir = Path.Combine(processDir, "plugins");
|
|
133
|
+
if (Directory.Exists(pluginsDir))
|
|
134
|
+
try { dirs.AddRange(Directory.GetDirectories(pluginsDir, "*", SearchOption.AllDirectories)); } catch { }
|
|
135
|
+
|
|
136
|
+
var parentDir = Path.GetDirectoryName(processDir);
|
|
137
|
+
if (!string.IsNullOrEmpty(parentDir))
|
|
138
|
+
{
|
|
139
|
+
var parentPlugins = Path.Combine(parentDir, "plugins");
|
|
140
|
+
if (Directory.Exists(parentPlugins))
|
|
141
|
+
{
|
|
142
|
+
dirs.Add(parentPlugins);
|
|
143
|
+
try { dirs.AddRange(Directory.GetDirectories(parentPlugins, "*", SearchOption.AllDirectories)); } catch { }
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// libs/ relative to the app root (alternative convention).
|
|
148
|
+
var processLibs = Path.Combine(processDir, "libs");
|
|
149
|
+
if (Directory.Exists(processLibs))
|
|
150
|
+
try { dirs.AddRange(Directory.GetDirectories(processLibs, "*", SearchOption.AllDirectories)); } catch { }
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch { }
|
|
116
154
|
|
|
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();
|
|
122
|
-
}
|
|
123
|
-
else
|
|
124
|
-
{
|
|
125
|
-
type = ResolveType(req.Assembly, req.TypeName);
|
|
126
|
-
if (type is null)
|
|
127
|
-
return new InvokeResult(null, $"Type not found: {req.TypeName}");
|
|
155
|
+
s_assemblySearchDirs = dirs.Where(d => !string.IsNullOrEmpty(d)).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
|
|
156
|
+
AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
|
|
128
157
|
}
|
|
129
|
-
|
|
130
|
-
if (type is null)
|
|
131
|
-
return new InvokeResult(null, "Cannot determine target type");
|
|
132
|
-
|
|
133
|
-
var method = req.Method ?? throw new ArgumentException("Method is required");
|
|
134
|
-
|
|
135
|
-
// ── Constructor ──────────────────────────────────────────────────────
|
|
136
|
-
if (method == ".ctor")
|
|
158
|
+
catch
|
|
137
159
|
{
|
|
138
|
-
|
|
139
|
-
var ctors = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance);
|
|
140
|
-
var ctor = ctors.FirstOrDefault(c => c.GetParameters().Length == argElements.Count)
|
|
141
|
-
?? ctors.FirstOrDefault();
|
|
142
|
-
if (ctor is null)
|
|
143
|
-
return new InvokeResult(null, $"No public constructor found on {type.FullName}");
|
|
144
|
-
var pars = ctor.GetParameters();
|
|
145
|
-
var ctorArgs = new object?[pars.Length];
|
|
146
|
-
for (int i = 0; i < pars.Length && i < argElements.Count; i++)
|
|
147
|
-
ctorArgs[i] = Coerce(argElements[i], pars[i].ParameterType);
|
|
148
|
-
return Box(ctor.Invoke(ctorArgs));
|
|
160
|
+
s_assemblySearchDirs = Array.Empty<string>();
|
|
149
161
|
}
|
|
162
|
+
}
|
|
150
163
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
// ── Property getter / setter ─────────────────────────────────────────
|
|
156
|
-
if (method.StartsWith("get_", StringComparison.Ordinal))
|
|
157
|
-
{
|
|
158
|
-
var prop = GetCachedProperty(type, method[4..], flags);
|
|
159
|
-
if (prop is not null)
|
|
160
|
-
return Box(prop.GetValue(target));
|
|
161
|
-
}
|
|
162
|
-
if (method.StartsWith("set_", StringComparison.Ordinal) && req.Args?.Count == 1)
|
|
164
|
+
public static object? RunOnUIThread(int callbackId)
|
|
165
|
+
{
|
|
166
|
+
var action = new Action(() =>
|
|
163
167
|
{
|
|
164
|
-
|
|
165
|
-
if (prop is not null)
|
|
168
|
+
unsafe
|
|
166
169
|
{
|
|
167
|
-
|
|
168
|
-
|
|
170
|
+
if (s_jsInvoker == null) return;
|
|
171
|
+
byte* respPtr = null;
|
|
172
|
+
int respLen = 0;
|
|
173
|
+
s_jsInvoker(callbackId, null, 0, &respPtr, &respLen);
|
|
174
|
+
if (respPtr != null && respLen > 0) Marshal.FreeHGlobal((IntPtr)respPtr);
|
|
169
175
|
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// ── Regular method ───────────────────────────────────────────────────
|
|
173
|
-
var argElems = req.Args ?? [];
|
|
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)));
|
|
185
|
-
}
|
|
176
|
+
});
|
|
186
177
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
private static object? AwaitIfTask(object? value)
|
|
190
|
-
{
|
|
191
|
-
if (value is null) return null;
|
|
192
|
-
var t = value.GetType();
|
|
193
|
-
|
|
194
|
-
if (value is Task task)
|
|
178
|
+
try
|
|
195
179
|
{
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
180
|
+
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
|
|
181
|
+
{
|
|
182
|
+
var coreAppType = asm.GetType("Windows.ApplicationModel.Core.CoreApplication");
|
|
183
|
+
if (coreAppType == null) continue;
|
|
184
|
+
var mainViewProp = coreAppType.GetProperty("MainView", BindingFlags.Public | BindingFlags.Static);
|
|
185
|
+
var mainView = mainViewProp?.GetValue(null);
|
|
186
|
+
if (mainView == null) continue;
|
|
187
|
+
var dispatcherProp = mainView.GetType().GetProperty("Dispatcher", BindingFlags.Public | BindingFlags.Instance);
|
|
188
|
+
var dispatcher = dispatcherProp?.GetValue(mainView);
|
|
189
|
+
if (dispatcher == null) continue;
|
|
190
|
+
|
|
191
|
+
var hasAccessProp = dispatcher.GetType().GetProperty("HasThreadAccess", BindingFlags.Public | BindingFlags.Instance);
|
|
192
|
+
if (hasAccessProp?.GetValue(dispatcher) is true)
|
|
193
|
+
{
|
|
194
|
+
action();
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
foreach (var m in dispatcher.GetType().GetMethods().Where(m => m.Name == "RunAsync"))
|
|
199
|
+
{
|
|
200
|
+
var parameters = m.GetParameters();
|
|
201
|
+
if (parameters.Length != 2) continue;
|
|
202
|
+
var enumType = parameters[0].ParameterType;
|
|
203
|
+
object priority = enumType.IsEnum ? Enum.ToObject(enumType, 0) : Activator.CreateInstance(enumType)!;
|
|
204
|
+
var handlerType = parameters[1].ParameterType;
|
|
205
|
+
var mre = new ManualResetEventSlim(false);
|
|
206
|
+
var wrapped = new Action(() => { try { action(); } finally { mre.Set(); } });
|
|
207
|
+
try
|
|
208
|
+
{
|
|
209
|
+
var d = Delegate.CreateDelegate(handlerType, wrapped.Target, wrapped.Method);
|
|
210
|
+
m.Invoke(dispatcher, new object[] { priority, d });
|
|
211
|
+
mre.Wait();
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
catch { }
|
|
215
|
+
}
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
200
218
|
}
|
|
219
|
+
catch { }
|
|
201
220
|
|
|
202
|
-
|
|
221
|
+
try
|
|
203
222
|
{
|
|
204
|
-
var
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
223
|
+
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
|
|
224
|
+
{
|
|
225
|
+
var dqType = asm.GetType("Microsoft.UI.Dispatching.DispatcherQueue");
|
|
226
|
+
if (dqType == null) continue;
|
|
227
|
+
var getForCurrent = dqType.GetMethod("GetForCurrentThread", BindingFlags.Public | BindingFlags.Static);
|
|
228
|
+
var dq = getForCurrent?.Invoke(null, null);
|
|
229
|
+
if (dq == null) continue;
|
|
230
|
+
|
|
231
|
+
if (dq.GetType().GetProperty("HasThreadAccess")?.GetValue(dq) is true)
|
|
232
|
+
{
|
|
233
|
+
action();
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
209
238
|
}
|
|
239
|
+
catch { }
|
|
210
240
|
|
|
211
|
-
|
|
241
|
+
try
|
|
212
242
|
{
|
|
213
|
-
|
|
214
|
-
|
|
243
|
+
var wpfDispatcherType = AppDomain.CurrentDomain.GetAssemblies()
|
|
244
|
+
.Select(a => a.GetType("System.Windows.Threading.Dispatcher"))
|
|
245
|
+
.FirstOrDefault(t => t != null);
|
|
246
|
+
if (wpfDispatcherType != null)
|
|
247
|
+
{
|
|
248
|
+
var currentDispatcherProp = wpfDispatcherType.GetProperty("CurrentDispatcher", BindingFlags.Public | BindingFlags.Static);
|
|
249
|
+
var dispatcher = currentDispatcherProp?.GetValue(null);
|
|
250
|
+
if (dispatcher != null)
|
|
251
|
+
{
|
|
252
|
+
var checkAccess = dispatcher.GetType().GetMethod("CheckAccess");
|
|
253
|
+
if (checkAccess?.Invoke(dispatcher, null) is true)
|
|
254
|
+
{
|
|
255
|
+
action();
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
var beginInvoke = dispatcher.GetType().GetMethod("BeginInvoke", new[] { typeof(Action) })
|
|
259
|
+
?? dispatcher.GetType().GetMethods().FirstOrDefault(m => m.Name == "BeginInvoke" && m.GetParameters().Length == 1);
|
|
260
|
+
if (beginInvoke != null)
|
|
261
|
+
{
|
|
262
|
+
var mre = new ManualResetEventSlim(false);
|
|
263
|
+
beginInvoke.Invoke(dispatcher, new object[] { new Action(() => { try { action(); } finally { mre.Set(); } }) });
|
|
264
|
+
mre.Wait();
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
215
269
|
}
|
|
270
|
+
catch { }
|
|
216
271
|
|
|
217
|
-
|
|
272
|
+
action();
|
|
273
|
+
return null;
|
|
218
274
|
}
|
|
219
275
|
|
|
220
|
-
private static
|
|
276
|
+
private static Assembly? OnAssemblyResolve(object? sender, ResolveEventArgs args)
|
|
221
277
|
{
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
278
|
+
try
|
|
279
|
+
{
|
|
280
|
+
var requested = new AssemblyName(args.Name).Name;
|
|
281
|
+
if (string.IsNullOrEmpty(requested)) return null;
|
|
226
282
|
|
|
227
|
-
|
|
228
|
-
{
|
|
229
|
-
var fqn = string.IsNullOrEmpty(assemblyName) ? typeName! : $"{typeName}, {assemblyName}";
|
|
230
|
-
var t = Type.GetType(fqn);
|
|
231
|
-
if (t is not null) return t;
|
|
283
|
+
if (s_assemblyLoadCache.TryGetValue(requested, out var cached)) return cached;
|
|
232
284
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
if (
|
|
237
|
-
|
|
285
|
+
// Check already-loaded assemblies first (avoid recursive Assembly.Load).
|
|
286
|
+
var loadedAsm = AppDomain.CurrentDomain.GetAssemblies()
|
|
287
|
+
.FirstOrDefault(a => string.Equals(a.GetName().Name, requested, StringComparison.OrdinalIgnoreCase));
|
|
288
|
+
if (loadedAsm is not null)
|
|
289
|
+
{
|
|
290
|
+
s_assemblyLoadCache[requested] = loadedAsm;
|
|
291
|
+
return loadedAsm;
|
|
292
|
+
}
|
|
238
293
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
try
|
|
294
|
+
// Try to locate a matching dll in known search directories.
|
|
295
|
+
foreach (var dir in s_assemblySearchDirs)
|
|
242
296
|
{
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
297
|
+
try
|
|
298
|
+
{
|
|
299
|
+
var candidate = Path.Combine(dir, requested + ".dll");
|
|
300
|
+
if (File.Exists(candidate))
|
|
301
|
+
{
|
|
302
|
+
try
|
|
303
|
+
{
|
|
304
|
+
var asm = AssemblyLoadContext.Default.LoadFromAssemblyPath(candidate);
|
|
305
|
+
s_assemblyLoadCache[requested] = asm;
|
|
306
|
+
return asm;
|
|
307
|
+
}
|
|
308
|
+
catch { }
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
catch { }
|
|
246
312
|
}
|
|
247
|
-
catch { }
|
|
248
|
-
}
|
|
249
313
|
|
|
314
|
+
s_assemblyLoadCache[requested] = null;
|
|
315
|
+
}
|
|
316
|
+
catch { }
|
|
250
317
|
return null;
|
|
251
318
|
}
|
|
252
319
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
private static MethodInfo? FindMethodCore(Type type, string name, int argCount, BindingFlags flags)
|
|
260
|
-
{
|
|
261
|
-
var match = Array.Find(
|
|
262
|
-
type.GetMethods(flags),
|
|
263
|
-
m => m.Name == name && !m.IsGenericMethod && m.GetParameters().Length == argCount);
|
|
264
|
-
if (match is not null) return match;
|
|
265
|
-
return type.GetMethod(name, flags);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
private static PropertyInfo? GetCachedProperty(Type type, string name, BindingFlags flags)
|
|
269
|
-
{
|
|
270
|
-
var key = $"{type.FullName}|{name}|{(int)flags}";
|
|
271
|
-
return s_propertyCache.GetOrAdd(key, _ => type.GetProperty(name, flags));
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
private static object? Coerce(JsonElement el, Type targetType)
|
|
320
|
+
[UnmanagedCallersOnly(EntryPoint = "RegisterJsCallback",
|
|
321
|
+
CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
|
|
322
|
+
public static unsafe int RegisterJsCallback(
|
|
323
|
+
delegate* unmanaged[Cdecl]<int, byte*, int, byte**, int*, void> callback)
|
|
275
324
|
{
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
{
|
|
279
|
-
s_handles.TryGetValue(h.GetInt32(), out var obj);
|
|
280
|
-
return obj;
|
|
281
|
-
}
|
|
282
|
-
return JsonSerializer.Deserialize(el.GetRawText(), targetType, JsonOptions.Default);
|
|
325
|
+
s_jsInvoker = callback;
|
|
326
|
+
return 0;
|
|
283
327
|
}
|
|
284
328
|
|
|
285
|
-
|
|
329
|
+
[UnmanagedCallersOnly(EntryPoint = "Invoke",
|
|
330
|
+
CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
|
|
331
|
+
public static unsafe int Invoke(
|
|
332
|
+
byte* requestPtr, int requestLen,
|
|
333
|
+
byte** responsePtr, int* responseLenPtr)
|
|
286
334
|
{
|
|
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))
|
|
335
|
+
try
|
|
294
336
|
{
|
|
295
|
-
|
|
296
|
-
|
|
337
|
+
var span = new ReadOnlySpan<byte>(requestPtr, requestLen);
|
|
338
|
+
var req = JsonSerializer.Deserialize(span, BridgeJsonContext.Default.InvokeRequest)!;
|
|
339
|
+
WriteResult(Dispatch(req), responsePtr, responseLenPtr);
|
|
297
340
|
}
|
|
298
|
-
|
|
299
|
-
// Arrays and IEnumerable — serialise as JSON arrays.
|
|
300
|
-
if (t.IsArray || (t != typeof(string) && value is IEnumerable enumerable))
|
|
341
|
+
catch (Exception ex)
|
|
301
342
|
{
|
|
302
|
-
|
|
303
|
-
{
|
|
304
|
-
var list = new List<object?>();
|
|
305
|
-
foreach (var item in (IEnumerable)value) list.Add(item);
|
|
306
|
-
return new InvokeResult(
|
|
307
|
-
JsonSerializer.SerializeToElement(list, typeof(List<object?>), JsonOptions.Default),
|
|
308
|
-
null);
|
|
309
|
-
}
|
|
310
|
-
catch { }
|
|
343
|
+
WriteError(Unwrap(ex).Message, responsePtr, responseLenPtr);
|
|
311
344
|
}
|
|
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);
|
|
345
|
+
return 0;
|
|
319
346
|
}
|
|
320
347
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
private static unsafe void WriteResponse(byte[] json, byte** responsePtr, int* responseLenPtr)
|
|
348
|
+
[UnmanagedCallersOnly(EntryPoint = "Free",
|
|
349
|
+
CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
|
|
350
|
+
public static unsafe void Free(byte* ptr)
|
|
325
351
|
{
|
|
326
|
-
|
|
327
|
-
Marshal.Copy(json, 0, (IntPtr)ptr, json.Length);
|
|
328
|
-
ptr[json.Length] = 0;
|
|
329
|
-
*responsePtr = ptr;
|
|
330
|
-
*responseLenPtr = json.Length;
|
|
352
|
+
if (ptr != null) Marshal.FreeHGlobal((IntPtr)ptr);
|
|
331
353
|
}
|
|
332
354
|
}
|
|
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
|
-
|
|
344
|
-
internal sealed record InvokeResult(JsonElement? Result, string? Error);
|
|
345
|
-
|
|
346
|
-
internal static class JsonOptions
|
|
347
|
-
{
|
|
348
|
-
internal static readonly JsonSerializerOptions Default = new()
|
|
349
|
-
{
|
|
350
|
-
PropertyNameCaseInsensitive = true,
|
|
351
|
-
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
|
352
|
-
};
|
|
353
|
-
}
|