@nativescript/windows 0.1.0-alpha.5 → 0.1.0-alpha.50
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 +124 -0
- package/framework/dotnet-bridge/Bridge.BinaryDispatch.cs +177 -0
- package/framework/dotnet-bridge/Bridge.Dispatch.cs +404 -0
- package/framework/dotnet-bridge/Bridge.JsDelegate.cs +100 -0
- package/framework/dotnet-bridge/Bridge.cs +44 -320
- package/framework/dotnet-bridge/DispatchTypes.cs +281 -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,77 @@
|
|
|
1
1
|
using System;
|
|
2
|
-
using System.Collections;
|
|
3
2
|
using System.Collections.Concurrent;
|
|
4
|
-
using System.Collections.Generic;
|
|
5
|
-
using System.Linq;
|
|
6
3
|
using System.Reflection;
|
|
7
4
|
using System.Runtime.InteropServices;
|
|
8
5
|
using System.Text.Json;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
|
|
7
|
+
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("DotNetBridgeTests")]
|
|
8
|
+
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("DotNetBridgeBenchmarks")]
|
|
12
9
|
|
|
13
10
|
namespace NativeScriptBridge;
|
|
14
11
|
|
|
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
|
|
12
|
+
public static partial class Bridge
|
|
40
13
|
{
|
|
41
|
-
|
|
42
|
-
|
|
14
|
+
internal static readonly ConcurrentDictionary<int, object?> s_handles = new();
|
|
15
|
+
internal static int s_nextHandle;
|
|
16
|
+
|
|
17
|
+
// Function pointer registered by the Rust runtime so managed delegates can
|
|
18
|
+
// call back into V8 without a JSON round-trip.
|
|
19
|
+
internal static unsafe delegate* unmanaged[Cdecl]<int, byte*, int, byte**, int*, void>
|
|
20
|
+
s_jsInvoker;
|
|
21
|
+
|
|
22
|
+
private static readonly ConcurrentDictionary<string, Type?> s_typeCache
|
|
23
|
+
= new(StringComparer.Ordinal);
|
|
24
|
+
private static readonly ConcurrentDictionary<MethodKey, DispatchEntry> s_methodCache = new();
|
|
25
|
+
private static readonly ConcurrentDictionary<PropKey, PropertyInfo?> s_propCache = new();
|
|
26
|
+
private static readonly ConcurrentDictionary<CtorKey, CtorEntry> s_ctorCache = new();
|
|
27
|
+
|
|
28
|
+
private static readonly JsonSerializerOptions s_coerceOpts = new()
|
|
29
|
+
{
|
|
30
|
+
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
|
31
|
+
};
|
|
43
32
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
33
|
+
internal static void ClearCaches()
|
|
34
|
+
{
|
|
35
|
+
s_typeCache.Clear();
|
|
36
|
+
s_methodCache.Clear();
|
|
37
|
+
s_propCache.Clear();
|
|
38
|
+
s_ctorCache.Clear();
|
|
39
|
+
s_handles.Clear();
|
|
40
|
+
s_nextHandle = 0;
|
|
41
|
+
}
|
|
47
42
|
|
|
48
|
-
|
|
43
|
+
[UnmanagedCallersOnly(EntryPoint = "RegisterJsCallback",
|
|
44
|
+
CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
|
|
45
|
+
public static unsafe int RegisterJsCallback(
|
|
46
|
+
delegate* unmanaged[Cdecl]<int, byte*, int, byte**, int*, void> callback)
|
|
47
|
+
{
|
|
48
|
+
s_jsInvoker = callback;
|
|
49
|
+
return 0;
|
|
50
|
+
}
|
|
49
51
|
|
|
50
|
-
/// Called by the Rust runtime. Request arrives as a raw UTF-8 byte slice;
|
|
51
|
-
/// response is written as a UTF-8 byte buffer allocated with Marshal.AllocHGlobal.
|
|
52
52
|
[UnmanagedCallersOnly(EntryPoint = "Invoke",
|
|
53
53
|
CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
|
|
54
54
|
public static unsafe int Invoke(
|
|
55
55
|
byte* requestPtr, int requestLen,
|
|
56
56
|
byte** responsePtr, int* responseLenPtr)
|
|
57
57
|
{
|
|
58
|
-
byte[] responseBytes;
|
|
59
58
|
try
|
|
60
59
|
{
|
|
61
|
-
var
|
|
62
|
-
var req
|
|
63
|
-
|
|
64
|
-
responseBytes = JsonSerializer.SerializeToUtf8Bytes(result, JsonOptions.Default);
|
|
60
|
+
var span = new ReadOnlySpan<byte>(requestPtr, requestLen);
|
|
61
|
+
var req = JsonSerializer.Deserialize(span, BridgeJsonContext.Default.InvokeRequest)!;
|
|
62
|
+
WriteResult(Dispatch(req), responsePtr, responseLenPtr);
|
|
65
63
|
}
|
|
66
64
|
catch (Exception ex)
|
|
67
65
|
{
|
|
68
|
-
|
|
69
|
-
responseBytes = JsonSerializer.SerializeToUtf8Bytes(errResult, JsonOptions.Default);
|
|
66
|
+
WriteError(Unwrap(ex).Message, responsePtr, responseLenPtr);
|
|
70
67
|
}
|
|
71
|
-
|
|
72
|
-
WriteResponse(responseBytes, responsePtr, responseLenPtr);
|
|
73
68
|
return 0;
|
|
74
69
|
}
|
|
75
70
|
|
|
76
|
-
/// Frees the response buffer allocated by Invoke.
|
|
77
71
|
[UnmanagedCallersOnly(EntryPoint = "Free",
|
|
78
72
|
CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
|
|
79
73
|
public static unsafe void Free(byte* ptr)
|
|
80
74
|
{
|
|
81
|
-
if (ptr != null)
|
|
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)
|
|
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__")
|
|
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
|
|
105
|
-
{
|
|
106
|
-
methods = t.GetMethods(inst).Where(m => !m.IsSpecialName).Select(m => m.Name).Distinct().ToArray(),
|
|
107
|
-
properties = t.GetProperties(inst).Select(p => p.Name).Distinct().ToArray(),
|
|
108
|
-
staticMethods = t.GetMethods(stat).Where(m => !m.IsSpecialName).Select(m => m.Name).Distinct().ToArray(),
|
|
109
|
-
staticProperties = t.GetProperties(stat).Select(p => p.Name).Distinct().ToArray(),
|
|
110
|
-
};
|
|
111
|
-
return new InvokeResult(JsonSerializer.SerializeToElement(members, JsonOptions.Default), null);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
object? target = null;
|
|
115
|
-
Type? type = null;
|
|
116
|
-
|
|
117
|
-
if (req.Handle.HasValue)
|
|
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}");
|
|
128
|
-
}
|
|
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")
|
|
137
|
-
{
|
|
138
|
-
var argElements = req.Args ?? [];
|
|
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));
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
var isStatic = target is null;
|
|
152
|
-
var flags = (isStatic ? BindingFlags.Static : BindingFlags.Instance)
|
|
153
|
-
| BindingFlags.Public;
|
|
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)
|
|
163
|
-
{
|
|
164
|
-
var prop = GetCachedProperty(type, method[4..], flags);
|
|
165
|
-
if (prop is not null)
|
|
166
|
-
{
|
|
167
|
-
prop.SetValue(target, Coerce(req.Args[0], prop.PropertyType));
|
|
168
|
-
return new InvokeResult(null, null);
|
|
169
|
-
}
|
|
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
|
-
}
|
|
186
|
-
|
|
187
|
-
// ── helpers ───────────────────────────────────────────────────────────────
|
|
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)
|
|
195
|
-
{
|
|
196
|
-
task.Wait();
|
|
197
|
-
if (task.IsFaulted)
|
|
198
|
-
throw task.Exception!.InnerException ?? task.Exception;
|
|
199
|
-
return t.GetProperty("Result")?.GetValue(value);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(ValueTask<>))
|
|
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
|
-
}
|
|
210
|
-
|
|
211
|
-
if (value is System.Threading.Tasks.ValueTask vt)
|
|
212
|
-
{
|
|
213
|
-
vt.AsTask().Wait();
|
|
214
|
-
return null;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
return value;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
private static Type? ResolveType(string? assemblyName, string? typeName)
|
|
221
|
-
{
|
|
222
|
-
if (string.IsNullOrEmpty(typeName)) return null;
|
|
223
|
-
var key = string.IsNullOrEmpty(assemblyName) ? typeName : $"{assemblyName}|{typeName}";
|
|
224
|
-
return s_typeCache.GetOrAdd(key, _ => ResolveTypeCore(assemblyName, typeName));
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
private static Type? ResolveTypeCore(string? assemblyName, string? typeName)
|
|
228
|
-
{
|
|
229
|
-
var fqn = string.IsNullOrEmpty(assemblyName) ? typeName! : $"{typeName}, {assemblyName}";
|
|
230
|
-
var t = Type.GetType(fqn);
|
|
231
|
-
if (t is not null) return t;
|
|
232
|
-
|
|
233
|
-
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
|
|
234
|
-
{
|
|
235
|
-
t = asm.GetType(typeName!);
|
|
236
|
-
if (t is not null) return t;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
if (!string.IsNullOrEmpty(assemblyName))
|
|
240
|
-
{
|
|
241
|
-
try
|
|
242
|
-
{
|
|
243
|
-
var asm = Assembly.Load(assemblyName);
|
|
244
|
-
t = asm.GetType(typeName!);
|
|
245
|
-
if (t is not null) return t;
|
|
246
|
-
}
|
|
247
|
-
catch { }
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
return null;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
private static MethodInfo? FindMethod(Type type, string name, int argCount, BindingFlags flags)
|
|
254
|
-
{
|
|
255
|
-
var key = $"{type.FullName}|{name}|{argCount}|{(int)flags}";
|
|
256
|
-
return s_methodCache.GetOrAdd(key, _ => FindMethodCore(type, name, argCount, flags));
|
|
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)
|
|
275
|
-
{
|
|
276
|
-
if (el.ValueKind == JsonValueKind.Null) return null;
|
|
277
|
-
if (el.ValueKind == JsonValueKind.Object && el.TryGetProperty("__handle", out var h))
|
|
278
|
-
{
|
|
279
|
-
s_handles.TryGetValue(h.GetInt32(), out var obj);
|
|
280
|
-
return obj;
|
|
281
|
-
}
|
|
282
|
-
return JsonSerializer.Deserialize(el.GetRawText(), targetType, JsonOptions.Default);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
private static InvokeResult Box(object? value)
|
|
286
|
-
{
|
|
287
|
-
if (value is null) return new InvokeResult(null, null);
|
|
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))
|
|
294
|
-
{
|
|
295
|
-
return new InvokeResult(
|
|
296
|
-
JsonSerializer.SerializeToElement(value, t, JsonOptions.Default), null);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// Arrays and IEnumerable — serialise as JSON arrays.
|
|
300
|
-
if (t.IsArray || (t != typeof(string) && value is IEnumerable enumerable))
|
|
301
|
-
{
|
|
302
|
-
try
|
|
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 { }
|
|
311
|
-
}
|
|
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);
|
|
75
|
+
if (ptr != null) Marshal.FreeHGlobal((IntPtr)ptr);
|
|
319
76
|
}
|
|
320
|
-
|
|
321
|
-
private static Exception Unwrap(Exception ex) =>
|
|
322
|
-
ex is TargetInvocationException { InnerException: { } inner } ? Unwrap(inner) : ex;
|
|
323
|
-
|
|
324
|
-
private static unsafe void WriteResponse(byte[] json, byte** responsePtr, int* responseLenPtr)
|
|
325
|
-
{
|
|
326
|
-
var ptr = (byte*)Marshal.AllocHGlobal(json.Length + 1);
|
|
327
|
-
Marshal.Copy(json, 0, (IntPtr)ptr, json.Length);
|
|
328
|
-
ptr[json.Length] = 0;
|
|
329
|
-
*responsePtr = ptr;
|
|
330
|
-
*responseLenPtr = json.Length;
|
|
331
|
-
}
|
|
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
|
-
|
|
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
77
|
}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Buffers;
|
|
3
|
+
using System.Collections;
|
|
4
|
+
using System.Collections.Concurrent;
|
|
5
|
+
using System.Collections.Generic;
|
|
6
|
+
using System.Linq.Expressions;
|
|
7
|
+
using System.Reflection;
|
|
8
|
+
using System.Text.Json;
|
|
9
|
+
using System.Text.Json.Serialization;
|
|
10
|
+
using System.Threading;
|
|
11
|
+
using System.Threading.Tasks;
|
|
12
|
+
|
|
13
|
+
namespace NativeScriptBridge;
|
|
14
|
+
|
|
15
|
+
// ── JSON source-gen context ───────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
[JsonSerializable(typeof(InvokeRequest))]
|
|
18
|
+
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
|
|
19
|
+
internal partial class BridgeJsonContext : JsonSerializerContext { }
|
|
20
|
+
|
|
21
|
+
// ── JSON wire type ────────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
internal sealed record InvokeRequest(
|
|
24
|
+
string? Assembly,
|
|
25
|
+
string? TypeName,
|
|
26
|
+
string? Method,
|
|
27
|
+
int? Handle,
|
|
28
|
+
JsonElement[]? Args
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
// ── cache key structs ─────────────────────────────────────────────────────────
|
|
32
|
+
//
|
|
33
|
+
// Using Type as the key field (rather than Type.FullName) is correct and faster:
|
|
34
|
+
// Type instances are singletons within an AssemblyLoadContext, so reference
|
|
35
|
+
// equality and reference hash codes are sufficient and allocation-free.
|
|
36
|
+
|
|
37
|
+
internal readonly record struct MethodKey(Type Type, string Name, int ArgCount, BindingFlags Flags);
|
|
38
|
+
internal readonly record struct PropKey(Type Type, string Name, BindingFlags Flags);
|
|
39
|
+
internal readonly record struct CtorKey(Type Type, int ArgCount);
|
|
40
|
+
|
|
41
|
+
// ── cache value types ─────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
internal readonly struct DispatchEntry(Func<object?, object?[], object?>? invoke, ParameterInfo[] parameters)
|
|
44
|
+
{
|
|
45
|
+
public readonly Func<object?, object?[], object?>? Invoke = invoke;
|
|
46
|
+
public readonly ParameterInfo[] Parameters = parameters;
|
|
47
|
+
public static readonly DispatchEntry Empty = new(null, []);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
internal readonly struct CtorEntry(ConstructorInfo? ctor, ParameterInfo[] parameters)
|
|
51
|
+
{
|
|
52
|
+
public readonly ConstructorInfo? Ctor = ctor;
|
|
53
|
+
public readonly ParameterInfo[] Parameters = parameters;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ── dispatch result ───────────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
internal enum DispatchKind : byte { Void, Primitive, Handle, Collection, Members }
|
|
59
|
+
|
|
60
|
+
internal readonly struct DispatchResult
|
|
61
|
+
{
|
|
62
|
+
public static readonly DispatchResult Void =
|
|
63
|
+
new(DispatchKind.Void, null, null, 0, null, null, null, null, null);
|
|
64
|
+
|
|
65
|
+
private readonly DispatchKind _kind;
|
|
66
|
+
private readonly object? _value;
|
|
67
|
+
private readonly Type? _type;
|
|
68
|
+
private readonly int _handle;
|
|
69
|
+
private readonly string? _typeName;
|
|
70
|
+
private readonly string[]? _methods;
|
|
71
|
+
private readonly string[]? _properties;
|
|
72
|
+
private readonly string[]? _staticMethods;
|
|
73
|
+
private readonly string[]? _staticProperties;
|
|
74
|
+
|
|
75
|
+
private DispatchResult(DispatchKind kind, object? value, Type? type, int handle,
|
|
76
|
+
string? typeName, string[]? methods, string[]? props,
|
|
77
|
+
string[]? staticMethods, string[]? staticProps)
|
|
78
|
+
{
|
|
79
|
+
_kind = kind; _value = value; _type = type; _handle = handle;
|
|
80
|
+
_typeName = typeName; _methods = methods; _properties = props;
|
|
81
|
+
_staticMethods = staticMethods; _staticProperties = staticProps;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
public static DispatchResult Primitive(object value, Type type)
|
|
85
|
+
=> new(DispatchKind.Primitive, value, type, 0, null, null, null, null, null);
|
|
86
|
+
|
|
87
|
+
public static DispatchResult Handle(int id, string typeName)
|
|
88
|
+
=> new(DispatchKind.Handle, null, null, id, typeName, null, null, null, null);
|
|
89
|
+
|
|
90
|
+
public static DispatchResult Collection(IEnumerable items)
|
|
91
|
+
=> new(DispatchKind.Collection, items, null, 0, null, null, null, null, null);
|
|
92
|
+
|
|
93
|
+
public static DispatchResult Members(
|
|
94
|
+
string[] methods, string[] props, string[] staticMethods, string[] staticProps)
|
|
95
|
+
=> new(DispatchKind.Members, null, null, 0, null, methods, props, staticMethods, staticProps);
|
|
96
|
+
|
|
97
|
+
internal int HandleId() => _handle;
|
|
98
|
+
|
|
99
|
+
// ── JSON serialisation ────────────────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
public void WriteTo(Utf8JsonWriter w, JsonSerializerOptions opts)
|
|
102
|
+
{
|
|
103
|
+
switch (_kind)
|
|
104
|
+
{
|
|
105
|
+
case DispatchKind.Void:
|
|
106
|
+
w.WriteNullValue();
|
|
107
|
+
break;
|
|
108
|
+
|
|
109
|
+
case DispatchKind.Primitive:
|
|
110
|
+
JsonSerializer.Serialize(w, _value, _type!, opts);
|
|
111
|
+
break;
|
|
112
|
+
|
|
113
|
+
case DispatchKind.Handle:
|
|
114
|
+
w.WriteStartObject();
|
|
115
|
+
w.WriteNumber("__handle"u8, _handle);
|
|
116
|
+
w.WriteString("__type"u8, _typeName);
|
|
117
|
+
w.WriteEndObject();
|
|
118
|
+
break;
|
|
119
|
+
|
|
120
|
+
case DispatchKind.Collection:
|
|
121
|
+
w.WriteStartArray();
|
|
122
|
+
foreach (var item in (IEnumerable)_value!)
|
|
123
|
+
JsonSerializer.Serialize(w, item, item?.GetType() ?? typeof(object), opts);
|
|
124
|
+
w.WriteEndArray();
|
|
125
|
+
break;
|
|
126
|
+
|
|
127
|
+
case DispatchKind.Members:
|
|
128
|
+
w.WriteStartObject();
|
|
129
|
+
WriteStringArray(w, "methods"u8, _methods!);
|
|
130
|
+
WriteStringArray(w, "properties"u8, _properties!);
|
|
131
|
+
WriteStringArray(w, "staticMethods"u8, _staticMethods!);
|
|
132
|
+
WriteStringArray(w, "staticProperties"u8, _staticProperties!);
|
|
133
|
+
w.WriteEndObject();
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private static void WriteStringArray(Utf8JsonWriter w, ReadOnlySpan<byte> name, string[] arr)
|
|
139
|
+
{
|
|
140
|
+
w.WriteStartArray(name);
|
|
141
|
+
foreach (var s in arr) w.WriteStringValue(s);
|
|
142
|
+
w.WriteEndArray();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ── binary serialisation ──────────────────────────────────────────────────
|
|
146
|
+
//
|
|
147
|
+
// Response tags:
|
|
148
|
+
// 0x00 = null 0x01 = false 0x02 = true
|
|
149
|
+
// 0x03 = i32[4] 0x04 = f64[8] 0x05 = string[u32+utf8]
|
|
150
|
+
// 0x06 = handle[i32 + u16+utf8 type]
|
|
151
|
+
// 0x07 = array[u32 count + N tagged items]
|
|
152
|
+
// 0x08 = members{ methods, props, staticMethods, staticProps }
|
|
153
|
+
// 0xFF = error[u32+utf8]
|
|
154
|
+
|
|
155
|
+
public void WriteAsBin(ArrayBufferWriter<byte> outBuf)
|
|
156
|
+
{
|
|
157
|
+
var w = new BinWriter(outBuf);
|
|
158
|
+
switch (_kind)
|
|
159
|
+
{
|
|
160
|
+
case DispatchKind.Void:
|
|
161
|
+
w.WriteByte(0x00);
|
|
162
|
+
break;
|
|
163
|
+
|
|
164
|
+
case DispatchKind.Primitive:
|
|
165
|
+
WritePrimitiveBin(ref w, _value!);
|
|
166
|
+
break;
|
|
167
|
+
|
|
168
|
+
case DispatchKind.Handle:
|
|
169
|
+
w.WriteByte(0x06);
|
|
170
|
+
w.WriteI32(_handle);
|
|
171
|
+
w.WriteString16(_typeName ?? "");
|
|
172
|
+
break;
|
|
173
|
+
|
|
174
|
+
case DispatchKind.Collection:
|
|
175
|
+
var items = new List<object?>();
|
|
176
|
+
foreach (var item in (IEnumerable)_value!) items.Add(item);
|
|
177
|
+
w.WriteByte(0x07);
|
|
178
|
+
w.WriteU32((uint)items.Count);
|
|
179
|
+
foreach (var item in items) WriteValueBin(ref w, item);
|
|
180
|
+
break;
|
|
181
|
+
|
|
182
|
+
case DispatchKind.Members:
|
|
183
|
+
w.WriteByte(0x08);
|
|
184
|
+
WriteStringArrayBin(ref w, _methods!);
|
|
185
|
+
WriteStringArrayBin(ref w, _properties!);
|
|
186
|
+
WriteStringArrayBin(ref w, _staticMethods!);
|
|
187
|
+
WriteStringArrayBin(ref w, _staticProperties!);
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private static void WritePrimitiveBin(ref BinWriter w, object value)
|
|
193
|
+
{
|
|
194
|
+
switch (value)
|
|
195
|
+
{
|
|
196
|
+
case bool b: w.WriteByte(b ? (byte)0x02 : (byte)0x01); break;
|
|
197
|
+
case sbyte v: w.WriteByte(0x03); w.WriteI32(v); break;
|
|
198
|
+
case byte v: w.WriteByte(0x03); w.WriteI32(v); break;
|
|
199
|
+
case short v: w.WriteByte(0x03); w.WriteI32(v); break;
|
|
200
|
+
case ushort v: w.WriteByte(0x03); w.WriteI32(v); break;
|
|
201
|
+
case int v: w.WriteByte(0x03); w.WriteI32(v); break;
|
|
202
|
+
case uint v: w.WriteByte(0x04); w.WriteF64(v); break;
|
|
203
|
+
case long v: w.WriteByte(0x04); w.WriteF64(v); break;
|
|
204
|
+
case ulong v: w.WriteByte(0x04); w.WriteF64(v); break;
|
|
205
|
+
case float v: w.WriteByte(0x04); w.WriteF64(v); break;
|
|
206
|
+
case double v: w.WriteByte(0x04); w.WriteF64(v); break;
|
|
207
|
+
case decimal v: w.WriteByte(0x04); w.WriteF64((double)v); break;
|
|
208
|
+
case string s: w.WriteByte(0x05); w.WriteString32(s); break;
|
|
209
|
+
default: w.WriteByte(0x05); w.WriteString32(value.ToString() ?? ""); break;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private static void WriteValueBin(ref BinWriter w, object? value)
|
|
214
|
+
{
|
|
215
|
+
if (value is null) { w.WriteByte(0x00); return; }
|
|
216
|
+
var t = value.GetType();
|
|
217
|
+
if (t.IsPrimitive || t == typeof(string) || t == typeof(decimal))
|
|
218
|
+
{
|
|
219
|
+
WritePrimitiveBin(ref w, value);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (value is IEnumerable enumerable)
|
|
223
|
+
{
|
|
224
|
+
var items = new List<object?>();
|
|
225
|
+
foreach (var item in enumerable) items.Add(item);
|
|
226
|
+
w.WriteByte(0x07);
|
|
227
|
+
w.WriteU32((uint)items.Count);
|
|
228
|
+
foreach (var item in items) WriteValueBin(ref w, item);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
var id = Interlocked.Increment(ref Bridge.s_nextHandle);
|
|
232
|
+
Bridge.s_handles[id] = value;
|
|
233
|
+
w.WriteByte(0x06);
|
|
234
|
+
w.WriteI32(id);
|
|
235
|
+
w.WriteString16(t.FullName ?? t.Name);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private static void WriteStringArrayBin(ref BinWriter w, string[] arr)
|
|
239
|
+
{
|
|
240
|
+
w.WriteU16((ushort)arr.Length);
|
|
241
|
+
foreach (var s in arr) w.WriteString16(s);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ── async result helpers ──────────────────────────────────────────────────────
|
|
246
|
+
|
|
247
|
+
internal static class TaskResultCache
|
|
248
|
+
{
|
|
249
|
+
private static readonly ConcurrentDictionary<Type, Func<Task, object?>> s_cache = new();
|
|
250
|
+
|
|
251
|
+
public static object? GetResult(Task task)
|
|
252
|
+
{
|
|
253
|
+
var taskType = task.GetType();
|
|
254
|
+
if (!taskType.IsGenericType) return null;
|
|
255
|
+
var fn = s_cache.GetOrAdd(taskType, static t =>
|
|
256
|
+
{
|
|
257
|
+
var param = Expression.Parameter(typeof(Task));
|
|
258
|
+
Expression body = Expression.Property(Expression.Convert(param, t), "Result");
|
|
259
|
+
if (body.Type.IsValueType) body = Expression.Convert(body, typeof(object));
|
|
260
|
+
return Expression.Lambda<Func<Task, object?>>(body, param).Compile();
|
|
261
|
+
});
|
|
262
|
+
return fn(task);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
internal static class ValueTaskAsTaskCache
|
|
267
|
+
{
|
|
268
|
+
private static readonly ConcurrentDictionary<Type, Func<object, Task>> s_cache = new();
|
|
269
|
+
|
|
270
|
+
public static Task AsTask(Type valueTaskType, object vt)
|
|
271
|
+
{
|
|
272
|
+
var fn = s_cache.GetOrAdd(valueTaskType, static t =>
|
|
273
|
+
{
|
|
274
|
+
var param = Expression.Parameter(typeof(object));
|
|
275
|
+
var cast = Expression.Convert(param, t);
|
|
276
|
+
var call = Expression.Call(cast, t.GetMethod("AsTask")!);
|
|
277
|
+
return Expression.Lambda<Func<object, Task>>(call, param).Compile();
|
|
278
|
+
});
|
|
279
|
+
return fn(vt);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|