@nativescript/windows 0.1.0-alpha.8 → 0.1.0-alpha.80

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.
Files changed (29) hide show
  1. package/build.ps1 +44 -2
  2. package/framework/__PROJECT_NAME__/App.xaml.cs +67 -22
  3. package/framework/__PROJECT_NAME__/Assets/SplashScreen.png +0 -0
  4. package/framework/__PROJECT_NAME__/Assets/Square150x150Logo.png +0 -0
  5. package/framework/__PROJECT_NAME__/Assets/Square44x44Logo.png +0 -0
  6. package/framework/__PROJECT_NAME__/Assets/StoreLogo.png +0 -0
  7. package/framework/__PROJECT_NAME__/Assets/Wide310x150Logo.png +0 -0
  8. package/framework/__PROJECT_NAME__/CrashDiagnostics.cs +262 -66
  9. package/framework/__PROJECT_NAME__/Directory.Build.props +15 -0
  10. package/framework/__PROJECT_NAME__/Directory.Build.targets +153 -0
  11. package/framework/__PROJECT_NAME__/Package.appxmanifest +2 -5
  12. package/framework/__PROJECT_NAME__/RuntimeHost.cs +97 -10
  13. package/framework/__PROJECT_NAME__/__PROJECT_NAME__.csproj +85 -12
  14. package/framework/dotnet-bridge/BinaryProtocol.cs +145 -0
  15. package/framework/dotnet-bridge/Bridge.BinaryDispatch.cs +216 -0
  16. package/framework/dotnet-bridge/Bridge.Dispatch.cs +468 -0
  17. package/framework/dotnet-bridge/Bridge.JsDelegate.cs +96 -0
  18. package/framework/dotnet-bridge/Bridge.TaskHelper.cs +207 -0
  19. package/framework/dotnet-bridge/Bridge.cs +402 -263
  20. package/framework/dotnet-bridge/DispatchTypes.cs +317 -0
  21. package/framework/dotnet-bridge/DotNetBridge.csproj +7 -1
  22. package/framework/libs/arm64/nativescript.dll +0 -0
  23. package/framework/libs/devtools/arm64/nativescript.dll +0 -0
  24. package/framework/libs/devtools/x64/nativescript.dll +0 -0
  25. package/framework/libs/x64/nativescript.dll +0 -0
  26. package/framework/tools/dotnet-tool-arm64.exe +0 -0
  27. package/framework/tools/dotnet-tool-x64.exe +0 -0
  28. package/framework/tools/dotnet-tool.exe +0 -0
  29. package/package.json +19 -19
@@ -0,0 +1,216 @@
1
+ using System;
2
+ using System.Buffers;
3
+ using System.Collections.Generic;
4
+ using System.Reflection;
5
+ using System.Runtime.InteropServices;
6
+
7
+ namespace NativeScriptBridge;
8
+
9
+ public static partial class Bridge
10
+ {
11
+ [UnmanagedCallersOnly(EntryPoint = "InvokeBinary",
12
+ CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
13
+ public static unsafe int InvokeBinary(
14
+ byte* requestPtr, int requestLen,
15
+ byte** responsePtr, int* responseLenPtr)
16
+ {
17
+ try
18
+ {
19
+ var r = new BinReader(new ReadOnlySpan<byte>(requestPtr, requestLen));
20
+ var res = DispatchBin(ref r);
21
+ var buf = new ArrayBufferWriter<byte>(128);
22
+ res.WriteAsBin(buf);
23
+ WriteUnmanaged(buf.WrittenSpan, responsePtr, responseLenPtr);
24
+ }
25
+ catch (Exception ex)
26
+ {
27
+ WriteBinError(Unwrap(ex).Message, responsePtr, responseLenPtr);
28
+ }
29
+ return 0;
30
+ }
31
+
32
+ internal static DispatchResult DispatchBin(ref BinReader r)
33
+ {
34
+ var op = r.ReadByte();
35
+
36
+ if (op == 0x04) // release
37
+ {
38
+ var handleToRemove = r.ReadI32();
39
+ s_handles.TryRemove(handleToRemove, out _);
40
+ if (s_nativePtrs.TryRemove(handleToRemove, out var nativePtr))
41
+ {
42
+ try
43
+ {
44
+ Marshal.Release(new IntPtr(nativePtr));
45
+ try { System.Diagnostics.Debug.WriteLine($"[Bridge] Binary __release: Released native ptr for handle {handleToRemove} -> 0x{nativePtr:x}"); } catch { }
46
+ }
47
+ catch
48
+ {
49
+ // best-effort
50
+ }
51
+ }
52
+ return DispatchResult.Void;
53
+ }
54
+
55
+ if (op == 0x05) // members by handle
56
+ {
57
+ var h = r.ReadI32();
58
+ if (!s_handles.TryGetValue(h, out var obj))
59
+ throw new KeyNotFoundException($"Invalid handle {h}");
60
+ return BuildMembersResult(
61
+ obj?.GetType() ?? throw new InvalidOperationException("Handle is null"));
62
+ }
63
+
64
+ if (op == 0x06) // members by type
65
+ {
66
+ var typeName = r.ReadString16();
67
+ var assembly = r.ReadString16();
68
+ var type = ResolveType(NullIfEmpty(assembly), typeName)
69
+ ?? throw new TypeLoadException($"Type not found: {typeName} (assembly: {assembly})");
70
+ return BuildMembersResult(type);
71
+ }
72
+
73
+ if (op == 0x01) // instance call
74
+ {
75
+ var handle = r.ReadI32();
76
+ if (!s_handles.TryGetValue(handle, out var target))
77
+ throw new KeyNotFoundException($"Invalid handle {handle}");
78
+ var type = target?.GetType() ?? throw new InvalidOperationException("Handle is null");
79
+ var method = r.ReadString16();
80
+ var args = r.ReadArgs();
81
+
82
+ if (method == "__dotnet_await__" && args.Length == 2
83
+ && args[0] is int resolveId && args[1] is int rejectId)
84
+ {
85
+ ScheduleTaskContinuation(handle, resolveId, rejectId);
86
+ return DispatchResult.Void;
87
+ }
88
+
89
+ return DispatchCallBin(target, type, method, args, isStatic: false);
90
+ }
91
+
92
+ if (op == 0x09) // create JS delegate
93
+ {
94
+ var delTypeName = r.ReadString16(); // "" → System.Action
95
+ var callbackId = r.ReadI32();
96
+ return CreateJsDelegate(delTypeName, callbackId);
97
+ }
98
+
99
+ // Static ops: 0x02 = call, 0x03 = constructor
100
+ var typeNameS = r.ReadString16();
101
+ var assemblyS = r.ReadString16();
102
+ var typeS = ResolveType(NullIfEmpty(assemblyS), typeNameS)
103
+ ?? throw new TypeLoadException($"Type not found: {typeNameS} (assembly: {assemblyS})");
104
+
105
+ if (op == 0x03) // constructor
106
+ {
107
+ var args = r.ReadArgs();
108
+ var entry = GetCachedCtor(typeS, args.Length);
109
+ if (entry.Ctor is null)
110
+ throw new MissingMethodException(
111
+ $"No public ctor on {typeS.FullName} for {args.Length} args");
112
+ // ConstructorInfo.Invoke requires exact arg count — build a precise array.
113
+ return Box(entry.Ctor.Invoke(BuildArgsBinExact(args, entry.Parameters)));
114
+ }
115
+
116
+ // op == 0x02: static call
117
+ var methodS = r.ReadString16();
118
+ var argsS = r.ReadArgs();
119
+ return DispatchCallBin(null, typeS, methodS, argsS, isStatic: true);
120
+ }
121
+
122
+ private static DispatchResult DispatchCallBin(
123
+ object? target, Type type, string method, object?[] args, bool isStatic)
124
+ {
125
+ var flags = (isStatic ? BindingFlags.Static : BindingFlags.Instance) | BindingFlags.Public;
126
+
127
+ if (method.Length > 4
128
+ && method[0] == 'g' && method[1] == 'e' && method[2] == 't' && method[3] == '_')
129
+ {
130
+ var prop = GetCachedProp(type, method, 4, flags);
131
+ if (prop is not null) return Box(prop.GetValue(target));
132
+ }
133
+
134
+ if (method.Length > 4
135
+ && method[0] == 's' && method[1] == 'e' && method[2] == 't' && method[3] == '_'
136
+ && args.Length == 1)
137
+ {
138
+ var prop = GetCachedProp(type, method, 4, flags);
139
+ if (prop is not null)
140
+ {
141
+ prop.SetValue(target, CoerceBin(args[0], prop.PropertyType));
142
+ return DispatchResult.Void;
143
+ }
144
+ }
145
+
146
+ var entry = GetCachedMethod(type, method, args.Length, flags);
147
+ if (entry.Invoke is null)
148
+ throw new MissingMethodException(
149
+ $"Method '{method}' ({args.Length} args) not found on {type.FullName}");
150
+
151
+ var builtArgs = BuildArgsBin(args, entry.Parameters);
152
+ try { return Box(AwaitIfTask(entry.Invoke(target, builtArgs))); }
153
+ finally { if (builtArgs.Length > 0) ReturnArgs(builtArgs); }
154
+ }
155
+
156
+ private static object?[] BuildArgsBin(object?[] binArgs, ParameterInfo[] parameters)
157
+ {
158
+ if (parameters.Length == 0) return [];
159
+ var result = ArrayPool<object?>.Shared.Rent(parameters.Length);
160
+ for (int i = 0; i < parameters.Length && i < binArgs.Length; i++)
161
+ result[i] = CoerceBin(binArgs[i], parameters[i].ParameterType);
162
+ return result;
163
+ }
164
+
165
+ private static object?[] BuildArgsBinExact(object?[] binArgs, ParameterInfo[] parameters)
166
+ {
167
+ if (parameters.Length == 0) return [];
168
+ var result = new object?[parameters.Length];
169
+ for (int i = 0; i < parameters.Length && i < binArgs.Length; i++)
170
+ result[i] = CoerceBin(binArgs[i], parameters[i].ParameterType);
171
+ return result;
172
+ }
173
+
174
+ private static object? CoerceBin(object? value, Type targetType)
175
+ {
176
+ if (value is null) return null;
177
+ if (value is HandleRef hr)
178
+ {
179
+ s_handles.TryGetValue(hr.Id, out var obj);
180
+ return obj;
181
+ }
182
+ if (value is WinRtRef wr)
183
+ {
184
+ if (wr.Ptr == 0) return null;
185
+ var nativePtr = new IntPtr((long)wr.Ptr);
186
+ // 1. Typed QI first: works for COM/CsWinRT interface types that carry a
187
+ // [Guid] attribute. More precise than a generic RCW for strongly-typed
188
+ // parameters such as Windows.UI.Xaml.UIElement.
189
+ if (targetType != typeof(object) && targetType.GUID != Guid.Empty)
190
+ {
191
+ try { return Marshal.GetTypedObjectForIUnknown(nativePtr, targetType); }
192
+ catch { }
193
+ }
194
+ // 2. Generic RCW: .NET WinRT interop calls IInspectable::GetRuntimeClassName
195
+ // and projects to the appropriate CsWinRT type automatically.
196
+ // Do NOT swallow the exception — a null return silently breaks the call
197
+ // downstream; a thrown exception surfaces a meaningful error instead.
198
+ return Marshal.GetObjectForIUnknown(nativePtr);
199
+ }
200
+ if (value.GetType() == targetType) return value;
201
+ try { return Convert.ChangeType(value, targetType); }
202
+ catch { return value; }
203
+ }
204
+
205
+ private static string? NullIfEmpty(string s) => s.Length == 0 ? null : s;
206
+
207
+ private static unsafe void WriteBinError(string msg, byte** outPtr, int* outLen)
208
+ {
209
+ var msgBytes = System.Text.Encoding.UTF8.GetByteCount(msg);
210
+ var buf = new ArrayBufferWriter<byte>(5 + msgBytes);
211
+ var w = new BinWriter(buf);
212
+ w.WriteByte(0xFF);
213
+ w.WriteString32(msg);
214
+ WriteUnmanaged(buf.WrittenSpan, outPtr, outLen);
215
+ }
216
+ }
@@ -0,0 +1,468 @@
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
+ var id = req.Handle.Value;
24
+ s_handles.TryRemove(id, out _);
25
+ if (s_nativePtrs.TryRemove(id, out var nativePtr))
26
+ {
27
+ try
28
+ {
29
+ Marshal.Release(new IntPtr(nativePtr));
30
+ try { System.Diagnostics.Debug.WriteLine($"[Bridge] __release: Released native ptr for handle {id} -> 0x{nativePtr:x}"); } catch { }
31
+ }
32
+ catch
33
+ {
34
+ // swallow - best-effort release
35
+ }
36
+ }
37
+
38
+ return DispatchResult.Void;
39
+ }
40
+
41
+ object? target = null;
42
+ Type type;
43
+
44
+ if (req.Handle.HasValue)
45
+ {
46
+ if (!s_handles.TryGetValue(req.Handle.Value, out target))
47
+ throw new KeyNotFoundException($"Invalid handle {req.Handle.Value}");
48
+ type = target?.GetType() ?? throw new InvalidOperationException("Handle refers to null");
49
+ }
50
+ else
51
+ {
52
+ type = ResolveType(req.Assembly, req.TypeName)
53
+ ?? throw new TypeLoadException($"Type not found: {req.TypeName} (assembly: {req.Assembly})");
54
+ }
55
+
56
+ if (method == "__members__")
57
+ return BuildMembersResult(type);
58
+
59
+ if (method == "__dotnet_await__" && req.Handle.HasValue)
60
+ {
61
+ var a = req.Args ?? [];
62
+ if (a.Length >= 2)
63
+ {
64
+ var resolveId = (int)a[0].GetInt64();
65
+ var rejectId = (int)a[1].GetInt64();
66
+ ScheduleTaskContinuation(req.Handle.Value, resolveId, rejectId);
67
+ }
68
+ return DispatchResult.Void;
69
+ }
70
+
71
+ if (method == ".ctor")
72
+ {
73
+ var argElems = req.Args ?? [];
74
+ var entry = GetCachedCtor(type, argElems.Length);
75
+ if (entry.Ctor is null)
76
+ throw new MissingMethodException(
77
+ $"No public constructor on {type.FullName} for {argElems.Length} args");
78
+ // ConstructorInfo.Invoke validates args.Length == paramCount exactly — no pool.
79
+ return Box(entry.Ctor.Invoke(BuildArgsExact(argElems, entry.Parameters)));
80
+ }
81
+
82
+ var isStatic = target is null;
83
+ var flags = (isStatic ? BindingFlags.Static : BindingFlags.Instance) | BindingFlags.Public;
84
+
85
+ if (method.Length > 4)
86
+ {
87
+ if (method[0] == 'g' && method[1] == 'e' && method[2] == 't' && method[3] == '_')
88
+ {
89
+ var prop = GetCachedProp(type, method, 4, flags);
90
+ if (prop is not null) return Box(prop.GetValue(target));
91
+ }
92
+ else if (method[0] == 's' && method[1] == 'e' && method[2] == 't' && method[3] == '_'
93
+ && req.Args?.Length == 1)
94
+ {
95
+ var prop = GetCachedProp(type, method, 4, flags);
96
+ if (prop is not null)
97
+ {
98
+ prop.SetValue(target, Coerce(req.Args[0], prop.PropertyType));
99
+ return DispatchResult.Void;
100
+ }
101
+ }
102
+ }
103
+
104
+ var args = req.Args ?? [];
105
+ var dispEntry = GetCachedMethod(type, method, args.Length, flags);
106
+ if (dispEntry.Invoke is null)
107
+ throw new MissingMethodException(
108
+ $"Method '{method}' ({args.Length} args) not found on {type.FullName}");
109
+
110
+ var builtArgs = BuildArgs(args, dispEntry.Parameters);
111
+ try { return Box(AwaitIfTask(dispEntry.Invoke(target, builtArgs))); }
112
+ finally { if (builtArgs.Length > 0) ReturnArgs(builtArgs); }
113
+ }
114
+
115
+ private static DispatchEntry GetCachedMethod(Type type, string name, int argCount, BindingFlags flags)
116
+ => s_methodCache.GetOrAdd(
117
+ new MethodKey(type, name, argCount, flags),
118
+ static k => BuildDispatchEntry(k.Type, k.Name, k.ArgCount, k.Flags));
119
+
120
+ private static DispatchEntry BuildDispatchEntry(Type type, string name, int argCount, BindingFlags flags)
121
+ {
122
+ var mi = FindMethodCore(type, name, argCount, flags);
123
+ if (mi is null) return DispatchEntry.Empty;
124
+
125
+ var parameters = mi.GetParameters();
126
+
127
+ try
128
+ {
129
+ var targetParam = Expression.Parameter(typeof(object), "t");
130
+ var argsParam = Expression.Parameter(typeof(object?[]), "a");
131
+
132
+ var typedArgs = parameters.Select((p, i) =>
133
+ (Expression)Expression.Convert(
134
+ Expression.ArrayIndex(argsParam, Expression.Constant(i)),
135
+ p.ParameterType)).ToArray();
136
+
137
+ Expression body = mi.IsStatic
138
+ ? Expression.Call(mi, typedArgs)
139
+ : Expression.Call(Expression.Convert(targetParam, type), mi, typedArgs);
140
+
141
+ if (body.Type == typeof(void))
142
+ body = Expression.Block(typeof(object), body, Expression.Constant(null, typeof(object)));
143
+ else if (body.Type != typeof(object))
144
+ body = Expression.Convert(body, typeof(object));
145
+
146
+ var fn = Expression.Lambda<Func<object?, object?[], object?>>(
147
+ body, targetParam, argsParam).Compile();
148
+
149
+ return new DispatchEntry(fn, parameters);
150
+ }
151
+ catch
152
+ {
153
+ // mi.Invoke validates args.Length == paramCount; slice the rented buffer if needed.
154
+ int pc = parameters.Length;
155
+ return new DispatchEntry(
156
+ (t, a) => mi.Invoke(t, a.Length == pc ? a : a[..pc]),
157
+ parameters);
158
+ }
159
+ }
160
+
161
+ private static CtorEntry GetCachedCtor(Type type, int argCount)
162
+ => s_ctorCache.GetOrAdd(new CtorKey(type, argCount), static k =>
163
+ {
164
+ var ctors = k.Type.GetConstructors(BindingFlags.Public | BindingFlags.Instance);
165
+ var ctor = Array.Find(ctors, c => c.GetParameters().Length == k.ArgCount)
166
+ ?? ctors.FirstOrDefault();
167
+ return new CtorEntry(ctor, ctor?.GetParameters() ?? []);
168
+ });
169
+
170
+ private static PropertyInfo? GetCachedProp(Type type, string method, int prefixLen, BindingFlags flags)
171
+ => s_propCache.GetOrAdd(
172
+ new PropKey(type, method[prefixLen..], flags),
173
+ static k => k.Type.GetProperty(k.Name, k.Flags));
174
+
175
+ // Pooled: rented array passed to the compiled delegate (which accesses by index,
176
+ // not by Length). Caller must return via ReturnArgs immediately after invoke.
177
+ private static object?[] BuildArgs(JsonElement[] elements, ParameterInfo[] parameters)
178
+ {
179
+ if (parameters.Length == 0) return [];
180
+ var args = ArrayPool<object?>.Shared.Rent(parameters.Length);
181
+ for (int i = 0; i < parameters.Length && i < elements.Length; i++)
182
+ args[i] = Coerce(elements[i], parameters[i].ParameterType);
183
+ return args;
184
+ }
185
+
186
+ // Exact: required by ConstructorInfo.Invoke / MethodInfo.Invoke which validate
187
+ // args.Length == paramCount and would throw on an oversized rented buffer.
188
+ private static object?[] BuildArgsExact(JsonElement[] elements, ParameterInfo[] parameters)
189
+ {
190
+ if (parameters.Length == 0) return [];
191
+ var args = new object?[parameters.Length];
192
+ for (int i = 0; i < parameters.Length && i < elements.Length; i++)
193
+ args[i] = Coerce(elements[i], parameters[i].ParameterType);
194
+ return args;
195
+ }
196
+
197
+ private static void ReturnArgs(object?[] args) =>
198
+ ArrayPool<object?>.Shared.Return(args, clearArray: true);
199
+
200
+ private static object? Coerce(JsonElement el, Type targetType)
201
+ {
202
+ if (el.ValueKind == JsonValueKind.Null) return null;
203
+ if (el.ValueKind == JsonValueKind.Object && el.TryGetProperty("__handle", out var h))
204
+ {
205
+ s_handles.TryGetValue(h.GetInt32(), out var obj);
206
+ return obj;
207
+ }
208
+ return el.Deserialize(targetType, s_coerceOpts);
209
+ }
210
+
211
+ private static object? AwaitIfTask(object? value)
212
+ {
213
+ if (value is null) return null;
214
+
215
+ if (value is Task task)
216
+ {
217
+ task.GetAwaiter().GetResult();
218
+ return TaskResultCache.GetResult(task);
219
+ }
220
+
221
+ if (value is ValueTask vt)
222
+ {
223
+ vt.AsTask().GetAwaiter().GetResult();
224
+ return null;
225
+ }
226
+
227
+ var type = value.GetType();
228
+ if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ValueTask<>))
229
+ {
230
+ var innerTask = ValueTaskAsTaskCache.AsTask(type, value);
231
+ innerTask.GetAwaiter().GetResult();
232
+ if (innerTask.IsFaulted) throw innerTask.Exception!.InnerException ?? innerTask.Exception;
233
+ return TaskResultCache.GetResult(innerTask);
234
+ }
235
+
236
+ return value;
237
+ }
238
+
239
+ private static DispatchResult Box(object? value)
240
+ {
241
+ if (value is null) return DispatchResult.Void;
242
+ var t = value.GetType();
243
+
244
+ if (t.IsPrimitive || t == typeof(string) || t == typeof(decimal)
245
+ || t == typeof(DateTime) || t == typeof(DateTimeOffset)
246
+ || t == typeof(TimeSpan) || t == typeof(Guid))
247
+ return DispatchResult.Primitive(value, t);
248
+
249
+ if (t.IsEnum)
250
+ {
251
+ var ut = Enum.GetUnderlyingType(t);
252
+ return DispatchResult.Primitive(Convert.ChangeType(value, ut), ut);
253
+ }
254
+
255
+ if (t.IsArray || (t != typeof(string) && value is IEnumerable))
256
+ {
257
+ try { return DispatchResult.Collection((IEnumerable)value); }
258
+ catch { }
259
+ }
260
+
261
+ var id = Interlocked.Increment(ref s_nextHandle);
262
+ s_handles[id] = value;
263
+
264
+ // Try to obtain a canonical IUnknown pointer for COM/WinRT objects so the
265
+ // runtime can call native vtable methods directly. We addref via
266
+ // Marshal.GetIUnknownForObject and store the raw pointer; it will be
267
+ // released on __release.
268
+ try
269
+ {
270
+ if (value != null)
271
+ {
272
+ var p = Marshal.GetIUnknownForObject(value);
273
+ if (p != IntPtr.Zero)
274
+ {
275
+ s_nativePtrs[id] = p.ToInt64();
276
+ try { System.Diagnostics.Debug.WriteLine($"[Bridge] Box: handle {id}, type={t.FullName}, native=0x{p.ToInt64():x}"); } catch { }
277
+ }
278
+ }
279
+ }
280
+ catch
281
+ {
282
+ // Not a COM object or failed to obtain native pointer; ignore.
283
+ }
284
+ var typeName = t.FullName ?? t.Name;
285
+ return IsAwaitable(value, t)
286
+ ? DispatchResult.TaskHandle(id, typeName)
287
+ : DispatchResult.Handle(id, typeName);
288
+ }
289
+
290
+ private static bool IsAwaitable(object value, Type t)
291
+ {
292
+ if (value is Task || value is ValueTask) return true;
293
+ if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(ValueTask<>)) return true;
294
+ if ((t.FullName ?? "").StartsWith("Windows.Foundation.IAsync", StringComparison.Ordinal)) return true;
295
+ foreach (var iface in t.GetInterfaces())
296
+ if ((iface.FullName ?? "").StartsWith("Windows.Foundation.IAsync", StringComparison.Ordinal)) return true;
297
+ return t.GetMethod("GetAwaiter",
298
+ BindingFlags.Public | BindingFlags.Instance,
299
+ null, Type.EmptyTypes, null) is not null;
300
+ }
301
+
302
+ internal static DispatchResult BuildMembersResult(Type t)
303
+ {
304
+ const BindingFlags inst = BindingFlags.Public | BindingFlags.Instance;
305
+ const BindingFlags stat = BindingFlags.Public | BindingFlags.Static;
306
+
307
+ var instProps = t.GetProperties(inst);
308
+ var statProps = t.GetProperties(stat);
309
+
310
+ return DispatchResult.Members(
311
+ methods: t.GetMethods(inst).Where(m => !m.IsSpecialName).Select(m => m.Name).Distinct().ToArray(),
312
+ props: instProps.Where(p => p.GetGetMethod() != null).Select(p => p.Name).Distinct().ToArray(),
313
+ staticMethods: t.GetMethods(stat).Where(m => !m.IsSpecialName).Select(m => m.Name).Distinct().ToArray(),
314
+ staticProps: statProps.Where(p => p.GetGetMethod() != null).Select(p => p.Name).Distinct().ToArray(),
315
+ readonlyProps: instProps.Where(p => p.GetGetMethod() != null && p.GetSetMethod() == null).Select(p => p.Name).Distinct().ToArray(),
316
+ readonlyStaticProps: statProps.Where(p => p.GetGetMethod() != null && p.GetSetMethod() == null).Select(p => p.Name).Distinct().ToArray(),
317
+ writeonlyProps: instProps.Where(p => p.GetGetMethod() == null && p.GetSetMethod() != null).Select(p => p.Name).Distinct().ToArray(),
318
+ writeonlyStaticProps: statProps.Where(p => p.GetGetMethod() == null && p.GetSetMethod() != null).Select(p => p.Name).Distinct().ToArray()
319
+ );
320
+ }
321
+
322
+ internal static Type? ResolveType(string? assemblyName, string? typeName)
323
+ {
324
+ if (string.IsNullOrEmpty(typeName)) return null;
325
+ var key = string.IsNullOrEmpty(assemblyName) ? typeName : $"{assemblyName}|{typeName}";
326
+ return s_typeCache.GetOrAdd(key, _ => ResolveTypeCore(assemblyName, typeName));
327
+ }
328
+
329
+ private static Type? ResolveTypeCore(string? assemblyName, string? typeName)
330
+ {
331
+ if (string.IsNullOrEmpty(typeName)) return null;
332
+
333
+ // Fast/normal lookup first (assembly-qualified or plain type name).
334
+ var fqn = string.IsNullOrEmpty(assemblyName) ? typeName! : $"{typeName}, {assemblyName}";
335
+ var t = Type.GetType(fqn);
336
+ if (t is not null) return t;
337
+
338
+ t = Type.GetType(typeName);
339
+ if (t is not null) return t;
340
+
341
+ // Last segment (type name without namespace) used for loose matching below.
342
+ var lastDot = typeName.LastIndexOf('.');
343
+ var shortName = lastDot >= 0 ? typeName[(lastDot + 1)..] : typeName;
344
+
345
+ // Search loaded assemblies quickly; prefer asm.GetType which is inexpensive.
346
+ foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
347
+ {
348
+ try
349
+ {
350
+ t = asm.GetType(typeName);
351
+ if (t is not null) return t;
352
+
353
+ // Fall back to scanning exported types only when necessary. Some
354
+ // assemblies throw on GetTypes() (ReflectionTypeLoadException) so
355
+ // we catch and continue.
356
+ foreach (var ty in asm.GetTypes())
357
+ {
358
+ if (string.Equals(ty.FullName, typeName, StringComparison.Ordinal))
359
+ return ty;
360
+ // Short-name match only when input has no namespace prefix (e.g. "Stopwatch").
361
+ // Guarding on lastDot < 0 prevents matching unrelated types in other namespaces
362
+ // (e.g. "NativeScript.Widgets.FlexboxLayout" must not match a different
363
+ // FlexboxLayout that happens to live in another namespace).
364
+ if (lastDot < 0 && string.Equals(ty.Name, shortName, StringComparison.Ordinal))
365
+ return ty;
366
+ }
367
+ }
368
+ catch (ReflectionTypeLoadException) { }
369
+ catch { }
370
+ }
371
+
372
+ // If an assembly name was provided try loading it explicitly and repeat
373
+ // the above search inside that assembly only (less work than scanning all).
374
+ if (!string.IsNullOrEmpty(assemblyName))
375
+ {
376
+ try
377
+ {
378
+ var asm = Assembly.Load(assemblyName);
379
+ if (asm is not null)
380
+ {
381
+ t = asm.GetType(typeName!);
382
+ if (t is not null) return t;
383
+ try
384
+ {
385
+ foreach (var ty in asm.GetTypes())
386
+ {
387
+ if (string.Equals(ty.FullName, typeName, StringComparison.Ordinal))
388
+ return ty;
389
+ if (lastDot < 0 && string.Equals(ty.Name, shortName, StringComparison.Ordinal))
390
+ return ty;
391
+ }
392
+ }
393
+ catch (ReflectionTypeLoadException) { }
394
+ }
395
+ }
396
+ catch { }
397
+ }
398
+
399
+ return null;
400
+ }
401
+
402
+ private static MethodInfo? FindMethodCore(Type type, string name, int argCount, BindingFlags flags)
403
+ {
404
+ var match = Array.Find(type.GetMethods(flags),
405
+ m => m.Name == name && !m.IsGenericMethod && m.GetParameters().Length == argCount);
406
+ return match ?? type.GetMethod(name, flags);
407
+ }
408
+
409
+ internal static Exception Unwrap(Exception ex) =>
410
+ ex is TargetInvocationException { InnerException: { } inner } ? Unwrap(inner) : ex;
411
+
412
+ // Used by benchmarks to give a fair end-to-end comparison of protocols.
413
+
414
+ internal static byte[] PipelineJson(ReadOnlySpan<byte> jsonRequest)
415
+ {
416
+ var req = JsonSerializer.Deserialize(jsonRequest, BridgeJsonContext.Default.InvokeRequest)!;
417
+ var result = Dispatch(req);
418
+ var buf = new ArrayBufferWriter<byte>(128);
419
+ using var w = new Utf8JsonWriter(buf);
420
+ w.WriteStartObject();
421
+ w.WritePropertyName("result"u8);
422
+ result.WriteTo(w, s_coerceOpts);
423
+ w.WriteEndObject();
424
+ w.Flush();
425
+ return buf.WrittenSpan.ToArray();
426
+ }
427
+
428
+ internal static byte[] PipelineBinary(ReadOnlySpan<byte> binRequest)
429
+ {
430
+ var r = new BinReader(binRequest);
431
+ var result = DispatchBin(ref r);
432
+ var buf = new ArrayBufferWriter<byte>(128);
433
+ result.WriteAsBin(buf);
434
+ return buf.WrittenSpan.ToArray();
435
+ }
436
+
437
+ private static unsafe void WriteResult(DispatchResult res, byte** outPtr, int* outLen)
438
+ {
439
+ var buf = new ArrayBufferWriter<byte>(256);
440
+ using var w = new Utf8JsonWriter(buf);
441
+ w.WriteStartObject();
442
+ w.WritePropertyName("result"u8);
443
+ res.WriteTo(w, s_coerceOpts);
444
+ w.WriteEndObject();
445
+ w.Flush();
446
+ WriteUnmanaged(buf.WrittenSpan, outPtr, outLen);
447
+ }
448
+
449
+ private static unsafe void WriteError(string msg, byte** outPtr, int* outLen)
450
+ {
451
+ var buf = new ArrayBufferWriter<byte>(128);
452
+ using var w = new Utf8JsonWriter(buf);
453
+ w.WriteStartObject();
454
+ w.WriteString("error"u8, msg);
455
+ w.WriteEndObject();
456
+ w.Flush();
457
+ WriteUnmanaged(buf.WrittenSpan, outPtr, outLen);
458
+ }
459
+
460
+ internal static unsafe void WriteUnmanaged(ReadOnlySpan<byte> bytes, byte** outPtr, int* outLen)
461
+ {
462
+ var p = (byte*)Marshal.AllocHGlobal(bytes.Length + 1);
463
+ bytes.CopyTo(new Span<byte>(p, bytes.Length));
464
+ p[bytes.Length] = 0;
465
+ *outPtr = p;
466
+ *outLen = bytes.Length;
467
+ }
468
+ }