@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.
@@ -0,0 +1,404 @@
1
+ using System;
2
+ using System.Buffers;
3
+ using System.Collections;
4
+ using System.Collections.Generic;
5
+ using System.Linq;
6
+ using System.Linq.Expressions;
7
+ using System.Reflection;
8
+ using System.Runtime.InteropServices;
9
+ using System.Text.Json;
10
+ using System.Threading;
11
+ using System.Threading.Tasks;
12
+
13
+ namespace NativeScriptBridge;
14
+
15
+ public static partial class Bridge
16
+ {
17
+ internal static DispatchResult Dispatch(InvokeRequest req)
18
+ {
19
+ var method = req.Method ?? throw new ArgumentException("Method is required");
20
+
21
+ if (method == "__release" && req.Handle.HasValue)
22
+ {
23
+ s_handles.TryRemove(req.Handle.Value, out _);
24
+ return DispatchResult.Void;
25
+ }
26
+
27
+ object? target = null;
28
+ Type type;
29
+
30
+ if (req.Handle.HasValue)
31
+ {
32
+ if (!s_handles.TryGetValue(req.Handle.Value, out target))
33
+ throw new KeyNotFoundException($"Invalid handle {req.Handle.Value}");
34
+ type = target?.GetType() ?? throw new InvalidOperationException("Handle refers to null");
35
+ }
36
+ else
37
+ {
38
+ type = ResolveType(req.Assembly, req.TypeName)
39
+ ?? throw new TypeLoadException($"Type not found: {req.TypeName} (assembly: {req.Assembly})");
40
+ }
41
+
42
+ if (method == "__members__")
43
+ return BuildMembersResult(type);
44
+
45
+ if (method == ".ctor")
46
+ {
47
+ var argElems = req.Args ?? [];
48
+ var entry = GetCachedCtor(type, argElems.Length);
49
+ if (entry.Ctor is null)
50
+ throw new MissingMethodException(
51
+ $"No public constructor on {type.FullName} for {argElems.Length} args");
52
+ // ConstructorInfo.Invoke validates args.Length == paramCount exactly — no pool.
53
+ return Box(entry.Ctor.Invoke(BuildArgsExact(argElems, entry.Parameters)));
54
+ }
55
+
56
+ var isStatic = target is null;
57
+ var flags = (isStatic ? BindingFlags.Static : BindingFlags.Instance) | BindingFlags.Public;
58
+
59
+ if (method.Length > 4)
60
+ {
61
+ if (method[0] == 'g' && method[1] == 'e' && method[2] == 't' && method[3] == '_')
62
+ {
63
+ var prop = GetCachedProp(type, method, 4, flags);
64
+ if (prop is not null) return Box(prop.GetValue(target));
65
+ }
66
+ else if (method[0] == 's' && method[1] == 'e' && method[2] == 't' && method[3] == '_'
67
+ && req.Args?.Length == 1)
68
+ {
69
+ var prop = GetCachedProp(type, method, 4, flags);
70
+ if (prop is not null)
71
+ {
72
+ prop.SetValue(target, Coerce(req.Args[0], prop.PropertyType));
73
+ return DispatchResult.Void;
74
+ }
75
+ }
76
+ }
77
+
78
+ var args = req.Args ?? [];
79
+ var dispEntry = GetCachedMethod(type, method, args.Length, flags);
80
+ if (dispEntry.Invoke is null)
81
+ throw new MissingMethodException(
82
+ $"Method '{method}' ({args.Length} args) not found on {type.FullName}");
83
+
84
+ var builtArgs = BuildArgs(args, dispEntry.Parameters);
85
+ try { return Box(AwaitIfTask(dispEntry.Invoke(target, builtArgs))); }
86
+ finally { if (builtArgs.Length > 0) ReturnArgs(builtArgs); }
87
+ }
88
+
89
+ // ── compiled dispatch cache ───────────────────────────────────────────────
90
+
91
+ private static DispatchEntry GetCachedMethod(Type type, string name, int argCount, BindingFlags flags)
92
+ => s_methodCache.GetOrAdd(
93
+ new MethodKey(type, name, argCount, flags),
94
+ static k => BuildDispatchEntry(k.Type, k.Name, k.ArgCount, k.Flags));
95
+
96
+ private static DispatchEntry BuildDispatchEntry(Type type, string name, int argCount, BindingFlags flags)
97
+ {
98
+ var mi = FindMethodCore(type, name, argCount, flags);
99
+ if (mi is null) return DispatchEntry.Empty;
100
+
101
+ var parameters = mi.GetParameters();
102
+
103
+ try
104
+ {
105
+ var targetParam = Expression.Parameter(typeof(object), "t");
106
+ var argsParam = Expression.Parameter(typeof(object?[]), "a");
107
+
108
+ var typedArgs = parameters.Select((p, i) =>
109
+ (Expression)Expression.Convert(
110
+ Expression.ArrayIndex(argsParam, Expression.Constant(i)),
111
+ p.ParameterType)).ToArray();
112
+
113
+ Expression body = mi.IsStatic
114
+ ? Expression.Call(mi, typedArgs)
115
+ : Expression.Call(Expression.Convert(targetParam, type), mi, typedArgs);
116
+
117
+ if (body.Type == typeof(void))
118
+ body = Expression.Block(typeof(object), body, Expression.Constant(null, typeof(object)));
119
+ else if (body.Type != typeof(object))
120
+ body = Expression.Convert(body, typeof(object));
121
+
122
+ var fn = Expression.Lambda<Func<object?, object?[], object?>>(
123
+ body, targetParam, argsParam).Compile();
124
+
125
+ return new DispatchEntry(fn, parameters);
126
+ }
127
+ catch
128
+ {
129
+ // mi.Invoke validates args.Length == paramCount; slice the rented buffer if needed.
130
+ int pc = parameters.Length;
131
+ return new DispatchEntry(
132
+ (t, a) => mi.Invoke(t, a.Length == pc ? a : a[..pc]),
133
+ parameters);
134
+ }
135
+ }
136
+
137
+ private static CtorEntry GetCachedCtor(Type type, int argCount)
138
+ => s_ctorCache.GetOrAdd(new CtorKey(type, argCount), static k =>
139
+ {
140
+ var ctors = k.Type.GetConstructors(BindingFlags.Public | BindingFlags.Instance);
141
+ var ctor = Array.Find(ctors, c => c.GetParameters().Length == k.ArgCount)
142
+ ?? ctors.FirstOrDefault();
143
+ return new CtorEntry(ctor, ctor?.GetParameters() ?? []);
144
+ });
145
+
146
+ private static PropertyInfo? GetCachedProp(Type type, string method, int prefixLen, BindingFlags flags)
147
+ => s_propCache.GetOrAdd(
148
+ new PropKey(type, method[prefixLen..], flags),
149
+ static k => k.Type.GetProperty(k.Name, k.Flags));
150
+
151
+ // ── argument coercion ─────────────────────────────────────────────────────
152
+
153
+ // Pooled: rented array passed to the compiled delegate (which accesses by index,
154
+ // not by Length). Caller must return via ReturnArgs immediately after invoke.
155
+ private static object?[] BuildArgs(JsonElement[] elements, ParameterInfo[] parameters)
156
+ {
157
+ if (parameters.Length == 0) return [];
158
+ var args = ArrayPool<object?>.Shared.Rent(parameters.Length);
159
+ for (int i = 0; i < parameters.Length && i < elements.Length; i++)
160
+ args[i] = Coerce(elements[i], parameters[i].ParameterType);
161
+ return args;
162
+ }
163
+
164
+ // Exact: required by ConstructorInfo.Invoke / MethodInfo.Invoke which validate
165
+ // args.Length == paramCount and would throw on an oversized rented buffer.
166
+ private static object?[] BuildArgsExact(JsonElement[] elements, ParameterInfo[] parameters)
167
+ {
168
+ if (parameters.Length == 0) return [];
169
+ var args = new object?[parameters.Length];
170
+ for (int i = 0; i < parameters.Length && i < elements.Length; i++)
171
+ args[i] = Coerce(elements[i], parameters[i].ParameterType);
172
+ return args;
173
+ }
174
+
175
+ private static void ReturnArgs(object?[] args) =>
176
+ ArrayPool<object?>.Shared.Return(args, clearArray: true);
177
+
178
+ private static object? Coerce(JsonElement el, Type targetType)
179
+ {
180
+ if (el.ValueKind == JsonValueKind.Null) return null;
181
+ if (el.ValueKind == JsonValueKind.Object && el.TryGetProperty("__handle", out var h))
182
+ {
183
+ s_handles.TryGetValue(h.GetInt32(), out var obj);
184
+ return obj;
185
+ }
186
+ return el.Deserialize(targetType, s_coerceOpts);
187
+ }
188
+
189
+ // ── async unwrapping ──────────────────────────────────────────────────────
190
+
191
+ private static object? AwaitIfTask(object? value)
192
+ {
193
+ if (value is null) return null;
194
+
195
+ if (value is Task task)
196
+ {
197
+ task.GetAwaiter().GetResult();
198
+ return TaskResultCache.GetResult(task);
199
+ }
200
+
201
+ if (value is ValueTask vt)
202
+ {
203
+ vt.AsTask().GetAwaiter().GetResult();
204
+ return null;
205
+ }
206
+
207
+ var type = value.GetType();
208
+ if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ValueTask<>))
209
+ {
210
+ var innerTask = ValueTaskAsTaskCache.AsTask(type, value);
211
+ innerTask.GetAwaiter().GetResult();
212
+ if (innerTask.IsFaulted) throw innerTask.Exception!.InnerException ?? innerTask.Exception;
213
+ return TaskResultCache.GetResult(innerTask);
214
+ }
215
+
216
+ return value;
217
+ }
218
+
219
+ // ── result boxing ─────────────────────────────────────────────────────────
220
+
221
+ private static DispatchResult Box(object? value)
222
+ {
223
+ if (value is null) return DispatchResult.Void;
224
+ var t = value.GetType();
225
+
226
+ if (t.IsPrimitive || t == typeof(string) || t == typeof(decimal)
227
+ || t == typeof(DateTime) || t == typeof(DateTimeOffset)
228
+ || t == typeof(TimeSpan) || t == typeof(Guid))
229
+ return DispatchResult.Primitive(value, t);
230
+
231
+ if (t.IsArray || (t != typeof(string) && value is IEnumerable))
232
+ {
233
+ try { return DispatchResult.Collection((IEnumerable)value); }
234
+ catch { }
235
+ }
236
+
237
+ var id = Interlocked.Increment(ref s_nextHandle);
238
+ s_handles[id] = value;
239
+ return DispatchResult.Handle(id, t.FullName ?? t.Name);
240
+ }
241
+
242
+ // ── type/member resolution ────────────────────────────────────────────────
243
+
244
+ internal static DispatchResult BuildMembersResult(Type t)
245
+ {
246
+ const BindingFlags inst = BindingFlags.Public | BindingFlags.Instance;
247
+ const BindingFlags stat = BindingFlags.Public | BindingFlags.Static;
248
+ return DispatchResult.Members(
249
+ t.GetMethods(inst).Where(m => !m.IsSpecialName).Select(m => m.Name).Distinct().ToArray(),
250
+ t.GetProperties(inst).Select(p => p.Name).Distinct().ToArray(),
251
+ t.GetMethods(stat).Where(m => !m.IsSpecialName).Select(m => m.Name).Distinct().ToArray(),
252
+ t.GetProperties(stat).Select(p => p.Name).Distinct().ToArray());
253
+ }
254
+
255
+ internal static Type? ResolveType(string? assemblyName, string? typeName)
256
+ {
257
+ if (string.IsNullOrEmpty(typeName)) return null;
258
+ var key = string.IsNullOrEmpty(assemblyName) ? typeName : $"{assemblyName}|{typeName}";
259
+ return s_typeCache.GetOrAdd(key, _ => ResolveTypeCore(assemblyName, typeName));
260
+ }
261
+
262
+ private static Type? ResolveTypeCore(string? assemblyName, string? typeName)
263
+ {
264
+ if (string.IsNullOrEmpty(typeName)) return null;
265
+
266
+ // Fast/normal lookup first (assembly-qualified or plain type name).
267
+ var fqn = string.IsNullOrEmpty(assemblyName) ? typeName! : $"{typeName}, {assemblyName}";
268
+ var t = Type.GetType(fqn);
269
+ if (t is not null) return t;
270
+
271
+ t = Type.GetType(typeName);
272
+ if (t is not null) return t;
273
+
274
+ // Last segment (type name without namespace) used for loose matching below.
275
+ var lastDot = typeName.LastIndexOf('.');
276
+ var shortName = lastDot >= 0 ? typeName[(lastDot + 1)..] : typeName;
277
+
278
+ // Search loaded assemblies quickly; prefer asm.GetType which is inexpensive.
279
+ foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
280
+ {
281
+ try
282
+ {
283
+ t = asm.GetType(typeName);
284
+ if (t is not null) return t;
285
+
286
+ // Fall back to scanning exported types only when necessary. Some
287
+ // assemblies throw on GetTypes() (ReflectionTypeLoadException) so
288
+ // we catch and continue.
289
+ foreach (var ty in asm.GetTypes())
290
+ {
291
+ if (string.Equals(ty.FullName, typeName, StringComparison.Ordinal)
292
+ || string.Equals(ty.Name, shortName, StringComparison.Ordinal)
293
+ || (lastDot < 0 && ty.Namespace != null && ty.Namespace == typeName))
294
+ {
295
+ return ty;
296
+ }
297
+ }
298
+ }
299
+ catch (ReflectionTypeLoadException) { }
300
+ catch { }
301
+ }
302
+
303
+ // If an assembly name was provided try loading it explicitly and repeat
304
+ // the above search inside that assembly only (less work than scanning all).
305
+ if (!string.IsNullOrEmpty(assemblyName))
306
+ {
307
+ try
308
+ {
309
+ var asm = Assembly.Load(assemblyName);
310
+ if (asm is not null)
311
+ {
312
+ t = asm.GetType(typeName!);
313
+ if (t is not null) return t;
314
+ try
315
+ {
316
+ foreach (var ty in asm.GetTypes())
317
+ {
318
+ if (string.Equals(ty.FullName, typeName, StringComparison.Ordinal)
319
+ || string.Equals(ty.Name, shortName, StringComparison.Ordinal)
320
+ || (lastDot < 0 && ty.Namespace != null && ty.Namespace == typeName))
321
+ {
322
+ return ty;
323
+ }
324
+ }
325
+ }
326
+ catch (ReflectionTypeLoadException) { }
327
+ }
328
+ }
329
+ catch { }
330
+ }
331
+
332
+ return null;
333
+ }
334
+
335
+ private static MethodInfo? FindMethodCore(Type type, string name, int argCount, BindingFlags flags)
336
+ {
337
+ var match = Array.Find(type.GetMethods(flags),
338
+ m => m.Name == name && !m.IsGenericMethod && m.GetParameters().Length == argCount);
339
+ return match ?? type.GetMethod(name, flags);
340
+ }
341
+
342
+ internal static Exception Unwrap(Exception ex) =>
343
+ ex is TargetInvocationException { InnerException: { } inner } ? Unwrap(inner) : ex;
344
+
345
+ // ── full-pipeline helpers (parse + dispatch + serialise) ──────────────────
346
+ // Used by benchmarks to give a fair end-to-end comparison of protocols.
347
+
348
+ internal static byte[] PipelineJson(ReadOnlySpan<byte> jsonRequest)
349
+ {
350
+ var req = JsonSerializer.Deserialize(jsonRequest, BridgeJsonContext.Default.InvokeRequest)!;
351
+ var result = Dispatch(req);
352
+ var buf = new ArrayBufferWriter<byte>(128);
353
+ using var w = new Utf8JsonWriter(buf);
354
+ w.WriteStartObject();
355
+ w.WritePropertyName("result"u8);
356
+ result.WriteTo(w, s_coerceOpts);
357
+ w.WriteEndObject();
358
+ w.Flush();
359
+ return buf.WrittenSpan.ToArray();
360
+ }
361
+
362
+ internal static byte[] PipelineBinary(ReadOnlySpan<byte> binRequest)
363
+ {
364
+ var r = new BinReader(binRequest);
365
+ var result = DispatchBin(ref r);
366
+ var buf = new ArrayBufferWriter<byte>(128);
367
+ result.WriteAsBin(buf);
368
+ return buf.WrittenSpan.ToArray();
369
+ }
370
+
371
+ // ── JSON response writers ─────────────────────────────────────────────────
372
+
373
+ private static unsafe void WriteResult(DispatchResult res, byte** outPtr, int* outLen)
374
+ {
375
+ var buf = new ArrayBufferWriter<byte>(256);
376
+ using var w = new Utf8JsonWriter(buf);
377
+ w.WriteStartObject();
378
+ w.WritePropertyName("result"u8);
379
+ res.WriteTo(w, s_coerceOpts);
380
+ w.WriteEndObject();
381
+ w.Flush();
382
+ WriteUnmanaged(buf.WrittenSpan, outPtr, outLen);
383
+ }
384
+
385
+ private static unsafe void WriteError(string msg, byte** outPtr, int* outLen)
386
+ {
387
+ var buf = new ArrayBufferWriter<byte>(128);
388
+ using var w = new Utf8JsonWriter(buf);
389
+ w.WriteStartObject();
390
+ w.WriteString("error"u8, msg);
391
+ w.WriteEndObject();
392
+ w.Flush();
393
+ WriteUnmanaged(buf.WrittenSpan, outPtr, outLen);
394
+ }
395
+
396
+ internal static unsafe void WriteUnmanaged(ReadOnlySpan<byte> bytes, byte** outPtr, int* outLen)
397
+ {
398
+ var p = (byte*)Marshal.AllocHGlobal(bytes.Length + 1);
399
+ bytes.CopyTo(new Span<byte>(p, bytes.Length));
400
+ p[bytes.Length] = 0;
401
+ *outPtr = p;
402
+ *outLen = bytes.Length;
403
+ }
404
+ }
@@ -0,0 +1,100 @@
1
+ using System;
2
+ using System.Buffers;
3
+ using System.Linq;
4
+ using System.Linq.Expressions;
5
+ using System.Reflection;
6
+ using System.Runtime.InteropServices;
7
+ using System.Threading;
8
+
9
+ namespace NativeScriptBridge;
10
+
11
+ public static partial class Bridge
12
+ {
13
+ // ── JS delegate creation ──────────────────────────────────────────────────
14
+ //
15
+ // opcode 0x09: given a delegate type name (or "" for System.Action) and a
16
+ // JS callback id, compile a .NET delegate that serialises its parameters as
17
+ // binary and calls back into V8 via the s_jsInvoker function pointer.
18
+
19
+ private static DispatchResult CreateJsDelegate(string typeName, int callbackId)
20
+ {
21
+ var delegateType = string.IsNullOrEmpty(typeName)
22
+ ? typeof(Action)
23
+ : ResolveType(null, typeName)
24
+ ?? throw new TypeLoadException($"Delegate type not found: {typeName}");
25
+
26
+ var invokeMethod = delegateType.GetMethod("Invoke")
27
+ ?? throw new MissingMethodException($"No Invoke method on {delegateType}");
28
+
29
+ var parameters = invokeMethod.GetParameters();
30
+ var returnType = invokeMethod.ReturnType;
31
+
32
+ var paramExprs = parameters
33
+ .Select((p, i) => Expression.Parameter(p.ParameterType, p.Name ?? $"p{i}"))
34
+ .ToArray();
35
+
36
+ // object?[] args = new object?[] { (object?)p0, (object?)p1, … }
37
+ var objArgs = paramExprs.Select(p => (Expression)Expression.Convert(p, typeof(object)));
38
+ var argsArray = Expression.NewArrayInit(typeof(object), objArgs);
39
+
40
+ var callMethod = typeof(Bridge).GetMethod(
41
+ nameof(CallJsCallback),
42
+ BindingFlags.Static | BindingFlags.NonPublic)!;
43
+
44
+ var callExpr = Expression.Call(callMethod, Expression.Constant(callbackId), argsArray);
45
+
46
+ Expression body = returnType == typeof(void)
47
+ ? callExpr
48
+ : (Expression)Expression.Block(returnType, callExpr, Expression.Default(returnType));
49
+
50
+ var del = Expression.Lambda(delegateType, body, paramExprs).Compile();
51
+ return Box(del);
52
+ }
53
+
54
+ // ── Callback invocation helper ────────────────────────────────────────────
55
+
56
+ internal static unsafe void CallJsCallback(int id, object?[] args)
57
+ {
58
+ if (s_jsInvoker == null) return;
59
+
60
+ var buf = new ArrayBufferWriter<byte>(64);
61
+ var w = new BinWriter(buf);
62
+ w.WriteByte((byte)Math.Min(args.Length, 255));
63
+ foreach (var arg in args)
64
+ WriteCallbackArg(ref w, arg);
65
+
66
+ var bytes = buf.WrittenSpan;
67
+ byte* respPtr = null;
68
+ int respLen = 0;
69
+ fixed (byte* p = bytes)
70
+ s_jsInvoker(id, p, bytes.Length, &respPtr, &respLen);
71
+
72
+ // Response is only set for non-void delegates; free it when present.
73
+ if (respPtr != null && respLen > 0)
74
+ Marshal.FreeHGlobal((IntPtr)respPtr);
75
+ }
76
+
77
+ // Serialises a single delegate argument in response-binary tag format so
78
+ // the Rust side can reuse its existing bin_read_value parser.
79
+ private static void WriteCallbackArg(ref BinWriter w, object? arg)
80
+ {
81
+ if (arg is null) { w.WriteByte(0x00); return; }
82
+ if (arg is bool b) { w.WriteByte(b ? (byte)0x02 : (byte)0x01); return; }
83
+ if (arg is int i) { w.WriteByte(0x03); w.WriteI32(i); return; }
84
+ if (arg is uint u) { w.WriteByte(0x03); w.WriteI32((int)u); return; }
85
+ if (arg is long l) { w.WriteByte(0x04); w.WriteF64((double)l); return; }
86
+ if (arg is float f) { w.WriteByte(0x04); w.WriteF64((double)f); return; }
87
+ if (arg is double d){ w.WriteByte(0x04); w.WriteF64(d); return; }
88
+ if (arg is string s){ w.WriteByte(0x05); w.WriteString32(s); return; }
89
+
90
+ // Object: box in the handle map and send as a handle reference.
91
+ // The JS side receives {__handle, __type} which can be turned into a
92
+ // proxy via NSWinRT.dotnet.fromHandle(...).
93
+ var handleId = Interlocked.Increment(ref s_nextHandle);
94
+ s_handles[handleId] = arg;
95
+ var typeName = arg.GetType().FullName ?? arg.GetType().Name;
96
+ w.WriteByte(0x06);
97
+ w.WriteI32(handleId);
98
+ w.WriteString16(typeName); // u16 length prefix (matches bin_read_value tag 0x06)
99
+ }
100
+ }