@nativescript/windows 0.1.0-alpha.6 → 0.1.0-alpha.61
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 +4 -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 +249 -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 +121 -0
- package/framework/dotnet-bridge/Bridge.cs +185 -298
- 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
|
@@ -1,353 +1,240 @@
|
|
|
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
7
|
using System.Runtime.InteropServices;
|
|
8
|
+
using System.Runtime.Loader;
|
|
8
9
|
using System.Text.Json;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
|
|
11
|
+
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("DotNetBridgeTests")]
|
|
12
|
+
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("DotNetBridgeBenchmarks")]
|
|
12
13
|
|
|
13
14
|
namespace NativeScriptBridge;
|
|
14
15
|
|
|
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
|
|
16
|
+
public static partial class Bridge
|
|
40
17
|
{
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
18
|
+
internal static readonly ConcurrentDictionary<int, object?> s_handles = new();
|
|
19
|
+
internal static int s_nextHandle;
|
|
20
|
+
|
|
21
|
+
// Function pointer registered by the Rust runtime so managed delegates can
|
|
22
|
+
// call back into V8 without a JSON round-trip.
|
|
23
|
+
internal static unsafe delegate* unmanaged[Cdecl]<int, byte*, int, byte**, int*, void>
|
|
24
|
+
s_jsInvoker;
|
|
25
|
+
|
|
26
|
+
private static readonly ConcurrentDictionary<string, Type?> s_typeCache
|
|
27
|
+
= new(StringComparer.Ordinal);
|
|
28
|
+
private static readonly ConcurrentDictionary<MethodKey, DispatchEntry> s_methodCache = new();
|
|
29
|
+
private static readonly ConcurrentDictionary<PropKey, PropertyInfo?> s_propCache = new();
|
|
30
|
+
private static readonly ConcurrentDictionary<CtorKey, CtorEntry> s_ctorCache = new();
|
|
31
|
+
// Cache of attempted assembly loads (simple name -> Assembly or null if not found).
|
|
32
|
+
private static readonly ConcurrentDictionary<string, Assembly?> s_assemblyLoadCache
|
|
33
|
+
= new(StringComparer.OrdinalIgnoreCase);
|
|
34
|
+
// Directories to search for assemblies (initialized once in static ctor).
|
|
35
|
+
private static readonly string[] s_assemblySearchDirs;
|
|
36
|
+
|
|
37
|
+
private static readonly JsonSerializerOptions s_coerceOpts = new()
|
|
57
38
|
{
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
{
|
|
61
|
-
var requestSpan = new ReadOnlySpan<byte>(requestPtr, requestLen);
|
|
62
|
-
var req = JsonSerializer.Deserialize<InvokeRequest>(requestSpan, JsonOptions.Default)!;
|
|
63
|
-
var result = Dispatch(req);
|
|
64
|
-
responseBytes = JsonSerializer.SerializeToUtf8Bytes(result, JsonOptions.Default);
|
|
65
|
-
}
|
|
66
|
-
catch (Exception ex)
|
|
67
|
-
{
|
|
68
|
-
var errResult = new InvokeResult(null, Unwrap(ex).Message);
|
|
69
|
-
responseBytes = JsonSerializer.SerializeToUtf8Bytes(errResult, JsonOptions.Default);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
WriteResponse(responseBytes, responsePtr, responseLenPtr);
|
|
73
|
-
return 0;
|
|
74
|
-
}
|
|
39
|
+
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
|
40
|
+
};
|
|
75
41
|
|
|
76
|
-
|
|
77
|
-
[UnmanagedCallersOnly(EntryPoint = "Free",
|
|
78
|
-
CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
|
|
79
|
-
public static unsafe void Free(byte* ptr)
|
|
42
|
+
internal static void ClearCaches()
|
|
80
43
|
{
|
|
81
|
-
|
|
82
|
-
|
|
44
|
+
s_typeCache.Clear();
|
|
45
|
+
s_methodCache.Clear();
|
|
46
|
+
s_propCache.Clear();
|
|
47
|
+
s_ctorCache.Clear();
|
|
48
|
+
s_handles.Clear();
|
|
49
|
+
s_nextHandle = 0;
|
|
83
50
|
}
|
|
84
51
|
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
52
|
+
// Returns a JSON object mapping top-level namespace roots (e.g. "NativeScript")
|
|
53
|
+
// to the assembly simple-name that most likely contains that namespace's types.
|
|
54
|
+
// Reuses s_assemblySearchDirs so plugin and NuGet assemblies are included.
|
|
55
|
+
public static string GetNamespaceAssemblyMapJson()
|
|
88
56
|
{
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
s_handles.TryRemove(req.Handle.Value, out _);
|
|
92
|
-
return new InvokeResult(null, null);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Return reflection metadata so JS can distinguish methods from properties.
|
|
96
|
-
if (req.Method == "__members__")
|
|
57
|
+
var map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
|
58
|
+
try
|
|
97
59
|
{
|
|
98
|
-
var
|
|
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
|
|
60
|
+
foreach (var dir in s_assemblySearchDirs.Where(Directory.Exists))
|
|
105
61
|
{
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
62
|
+
try
|
|
63
|
+
{
|
|
64
|
+
foreach (var file in Directory.EnumerateFiles(dir, "*.dll", SearchOption.TopDirectoryOnly))
|
|
65
|
+
{
|
|
66
|
+
try
|
|
67
|
+
{
|
|
68
|
+
var simple = Path.GetFileNameWithoutExtension(file);
|
|
69
|
+
if (string.IsNullOrEmpty(simple)) continue;
|
|
70
|
+
|
|
71
|
+
Assembly asm = AppDomain.CurrentDomain.GetAssemblies()
|
|
72
|
+
.FirstOrDefault(a => string.Equals(a.GetName().Name, simple, StringComparison.OrdinalIgnoreCase));
|
|
73
|
+
if (asm is null)
|
|
74
|
+
{
|
|
75
|
+
try { asm = AssemblyLoadContext.Default.LoadFromAssemblyPath(file); }
|
|
76
|
+
catch { continue; }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
Type[] types;
|
|
80
|
+
try { types = asm.GetExportedTypes(); }
|
|
81
|
+
catch { types = Array.Empty<Type>(); }
|
|
82
|
+
|
|
83
|
+
foreach (var t in types)
|
|
84
|
+
{
|
|
85
|
+
if (string.IsNullOrEmpty(t.Namespace)) continue;
|
|
86
|
+
var root = t.Namespace.Split('.')[0];
|
|
87
|
+
if (!map.ContainsKey(root)) map[root] = asm.GetName().Name;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch { }
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch { }
|
|
94
|
+
}
|
|
128
95
|
}
|
|
96
|
+
catch { }
|
|
97
|
+
try { return JsonSerializer.Serialize(map); } catch { return "{}"; }
|
|
98
|
+
}
|
|
129
99
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
var method = req.Method ?? throw new ArgumentException("Method is required");
|
|
134
|
-
|
|
135
|
-
// ── Constructor ──────────────────────────────────────────────────────
|
|
136
|
-
if (method == ".ctor")
|
|
100
|
+
static Bridge()
|
|
101
|
+
{
|
|
102
|
+
try
|
|
137
103
|
{
|
|
138
|
-
var
|
|
139
|
-
var
|
|
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));
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
var isStatic = target is null;
|
|
152
|
-
var flags = (isStatic ? BindingFlags.Static : BindingFlags.Instance)
|
|
153
|
-
| BindingFlags.Public;
|
|
104
|
+
var baseDir = AppContext.BaseDirectory ?? AppDomain.CurrentDomain.BaseDirectory;
|
|
105
|
+
var dirs = new List<string> { baseDir };
|
|
154
106
|
|
|
155
|
-
|
|
156
|
-
|
|
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)
|
|
163
|
-
{
|
|
164
|
-
var prop = GetCachedProperty(type, method[4..], flags);
|
|
165
|
-
if (prop is not null)
|
|
107
|
+
// libs/ subtree relative to the bridge's own directory.
|
|
108
|
+
var libs = Path.Combine(baseDir, "libs");
|
|
109
|
+
if (Directory.Exists(libs))
|
|
166
110
|
{
|
|
167
|
-
|
|
168
|
-
|
|
111
|
+
dirs.Add(libs);
|
|
112
|
+
try { dirs.AddRange(Directory.GetDirectories(libs, "*", SearchOption.AllDirectories)); } catch { }
|
|
169
113
|
}
|
|
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
114
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
115
|
+
// The bridge DLL lives at dotnet-bridge/publish/ inside the app output root.
|
|
116
|
+
// Plugin assemblies (added via plugin.props) land at plugins/**/*.dll and NuGet
|
|
117
|
+
// assemblies land at the app output root itself — both outside the bridge subtree.
|
|
118
|
+
// Use the host process directory to reach them.
|
|
119
|
+
try
|
|
120
|
+
{
|
|
121
|
+
var processDir = Path.GetDirectoryName(
|
|
122
|
+
System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName);
|
|
123
|
+
if (!string.IsNullOrEmpty(processDir))
|
|
124
|
+
{
|
|
125
|
+
dirs.Add(processDir);
|
|
126
|
+
|
|
127
|
+
// plugins/ subtree: CLI-managed plugin DLLs live here.
|
|
128
|
+
// Check both {processDir}/plugins and {processDir}/../plugins because
|
|
129
|
+
// the NS CLI places plugins/ as a sibling of bin/, not inside it.
|
|
130
|
+
var pluginsDir = Path.Combine(processDir, "plugins");
|
|
131
|
+
if (Directory.Exists(pluginsDir))
|
|
132
|
+
try { dirs.AddRange(Directory.GetDirectories(pluginsDir, "*", SearchOption.AllDirectories)); } catch { }
|
|
133
|
+
|
|
134
|
+
var parentDir = Path.GetDirectoryName(processDir);
|
|
135
|
+
if (!string.IsNullOrEmpty(parentDir))
|
|
136
|
+
{
|
|
137
|
+
var parentPlugins = Path.Combine(parentDir, "plugins");
|
|
138
|
+
if (Directory.Exists(parentPlugins))
|
|
139
|
+
{
|
|
140
|
+
dirs.Add(parentPlugins);
|
|
141
|
+
try { dirs.AddRange(Directory.GetDirectories(parentPlugins, "*", SearchOption.AllDirectories)); } catch { }
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// libs/ relative to the app root (alternative convention).
|
|
146
|
+
var processLibs = Path.Combine(processDir, "libs");
|
|
147
|
+
if (Directory.Exists(processLibs))
|
|
148
|
+
try { dirs.AddRange(Directory.GetDirectories(processLibs, "*", SearchOption.AllDirectories)); } catch { }
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch { }
|
|
193
152
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
task.Wait();
|
|
197
|
-
if (task.IsFaulted)
|
|
198
|
-
throw task.Exception!.InnerException ?? task.Exception;
|
|
199
|
-
return t.GetProperty("Result")?.GetValue(value);
|
|
153
|
+
s_assemblySearchDirs = dirs.Where(d => !string.IsNullOrEmpty(d)).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
|
|
154
|
+
AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve;
|
|
200
155
|
}
|
|
201
|
-
|
|
202
|
-
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(ValueTask<>))
|
|
156
|
+
catch
|
|
203
157
|
{
|
|
204
|
-
|
|
205
|
-
innerTask.Wait();
|
|
206
|
-
if (innerTask.IsFaulted)
|
|
207
|
-
throw innerTask.Exception!.InnerException ?? innerTask.Exception;
|
|
208
|
-
return innerTask.GetType().GetProperty("Result")?.GetValue(innerTask);
|
|
158
|
+
s_assemblySearchDirs = Array.Empty<string>();
|
|
209
159
|
}
|
|
210
|
-
|
|
211
|
-
if (value is System.Threading.Tasks.ValueTask vt)
|
|
212
|
-
{
|
|
213
|
-
vt.AsTask().Wait();
|
|
214
|
-
return null;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
return value;
|
|
218
160
|
}
|
|
219
161
|
|
|
220
|
-
private static
|
|
162
|
+
private static Assembly? OnAssemblyResolve(object? sender, ResolveEventArgs args)
|
|
221
163
|
{
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
164
|
+
try
|
|
165
|
+
{
|
|
166
|
+
var requested = new AssemblyName(args.Name).Name;
|
|
167
|
+
if (string.IsNullOrEmpty(requested)) return null;
|
|
226
168
|
|
|
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;
|
|
169
|
+
if (s_assemblyLoadCache.TryGetValue(requested, out var cached)) return cached;
|
|
232
170
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
if (
|
|
237
|
-
|
|
171
|
+
// Check already-loaded assemblies first (avoid recursive Assembly.Load).
|
|
172
|
+
var loadedAsm = AppDomain.CurrentDomain.GetAssemblies()
|
|
173
|
+
.FirstOrDefault(a => string.Equals(a.GetName().Name, requested, StringComparison.OrdinalIgnoreCase));
|
|
174
|
+
if (loadedAsm is not null)
|
|
175
|
+
{
|
|
176
|
+
s_assemblyLoadCache[requested] = loadedAsm;
|
|
177
|
+
return loadedAsm;
|
|
178
|
+
}
|
|
238
179
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
try
|
|
180
|
+
// Try to locate a matching dll in known search directories.
|
|
181
|
+
foreach (var dir in s_assemblySearchDirs)
|
|
242
182
|
{
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
183
|
+
try
|
|
184
|
+
{
|
|
185
|
+
var candidate = Path.Combine(dir, requested + ".dll");
|
|
186
|
+
if (File.Exists(candidate))
|
|
187
|
+
{
|
|
188
|
+
try
|
|
189
|
+
{
|
|
190
|
+
var asm = AssemblyLoadContext.Default.LoadFromAssemblyPath(candidate);
|
|
191
|
+
s_assemblyLoadCache[requested] = asm;
|
|
192
|
+
return asm;
|
|
193
|
+
}
|
|
194
|
+
catch { }
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
catch { }
|
|
246
198
|
}
|
|
247
|
-
catch { }
|
|
248
|
-
}
|
|
249
199
|
|
|
200
|
+
s_assemblyLoadCache[requested] = null;
|
|
201
|
+
}
|
|
202
|
+
catch { }
|
|
250
203
|
return null;
|
|
251
204
|
}
|
|
252
205
|
|
|
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)
|
|
206
|
+
[UnmanagedCallersOnly(EntryPoint = "RegisterJsCallback",
|
|
207
|
+
CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
|
|
208
|
+
public static unsafe int RegisterJsCallback(
|
|
209
|
+
delegate* unmanaged[Cdecl]<int, byte*, int, byte**, int*, void> callback)
|
|
275
210
|
{
|
|
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);
|
|
211
|
+
s_jsInvoker = callback;
|
|
212
|
+
return 0;
|
|
283
213
|
}
|
|
284
214
|
|
|
285
|
-
|
|
215
|
+
[UnmanagedCallersOnly(EntryPoint = "Invoke",
|
|
216
|
+
CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
|
|
217
|
+
public static unsafe int Invoke(
|
|
218
|
+
byte* requestPtr, int requestLen,
|
|
219
|
+
byte** responsePtr, int* responseLenPtr)
|
|
286
220
|
{
|
|
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))
|
|
221
|
+
try
|
|
294
222
|
{
|
|
295
|
-
|
|
296
|
-
|
|
223
|
+
var span = new ReadOnlySpan<byte>(requestPtr, requestLen);
|
|
224
|
+
var req = JsonSerializer.Deserialize(span, BridgeJsonContext.Default.InvokeRequest)!;
|
|
225
|
+
WriteResult(Dispatch(req), responsePtr, responseLenPtr);
|
|
297
226
|
}
|
|
298
|
-
|
|
299
|
-
// Arrays and IEnumerable — serialise as JSON arrays.
|
|
300
|
-
if (t.IsArray || (t != typeof(string) && value is IEnumerable enumerable))
|
|
227
|
+
catch (Exception ex)
|
|
301
228
|
{
|
|
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 { }
|
|
229
|
+
WriteError(Unwrap(ex).Message, responsePtr, responseLenPtr);
|
|
311
230
|
}
|
|
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);
|
|
231
|
+
return 0;
|
|
319
232
|
}
|
|
320
233
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
private static unsafe void WriteResponse(byte[] json, byte** responsePtr, int* responseLenPtr)
|
|
234
|
+
[UnmanagedCallersOnly(EntryPoint = "Free",
|
|
235
|
+
CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
|
|
236
|
+
public static unsafe void Free(byte* ptr)
|
|
325
237
|
{
|
|
326
|
-
|
|
327
|
-
Marshal.Copy(json, 0, (IntPtr)ptr, json.Length);
|
|
328
|
-
ptr[json.Length] = 0;
|
|
329
|
-
*responsePtr = ptr;
|
|
330
|
-
*responseLenPtr = json.Length;
|
|
238
|
+
if (ptr != null) Marshal.FreeHGlobal((IntPtr)ptr);
|
|
331
239
|
}
|
|
332
240
|
}
|
|
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
|
-
}
|