@nativescript/windows-quickjs 0.1.0-alpha.0
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/README.md +27 -0
- package/framework/__PROJECT_NAME__/App.xaml +13 -0
- package/framework/__PROJECT_NAME__/App.xaml.cs +240 -0
- 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 +281 -0
- package/framework/__PROJECT_NAME__/Directory.Build.props +15 -0
- package/framework/__PROJECT_NAME__/Directory.Build.targets +106 -0
- package/framework/__PROJECT_NAME__/Package.appxmanifest +35 -0
- package/framework/__PROJECT_NAME__/Program.cs +34 -0
- package/framework/__PROJECT_NAME__/Properties/Default.rd.xml +9 -0
- package/framework/__PROJECT_NAME__/RuntimeHost.cs +359 -0
- package/framework/__PROJECT_NAME__/__PROJECT_NAME__.csproj +263 -0
- package/framework/dotnet-bridge/BinaryProtocol.cs +161 -0
- package/framework/dotnet-bridge/Bridge.BinaryDispatch.cs +373 -0
- package/framework/dotnet-bridge/Bridge.Dispatch.cs +597 -0
- package/framework/dotnet-bridge/Bridge.JsDelegate.cs +244 -0
- package/framework/dotnet-bridge/Bridge.Proxy.cs +573 -0
- package/framework/dotnet-bridge/Bridge.TaskHelper.cs +207 -0
- package/framework/dotnet-bridge/Bridge.cs +724 -0
- package/framework/dotnet-bridge/DispatchTypes.cs +333 -0
- package/framework/dotnet-bridge/DotNetBridge.csproj +30 -0
- package/framework/dotnet-bridge/publish_build/DotNetBridge.deps.json +23 -0
- package/framework/dotnet-bridge/publish_build/DotNetBridge.dll +0 -0
- package/framework/dotnet-bridge/publish_build/DotNetBridge.pdb +0 -0
- package/framework/dotnet-bridge/publish_build/DotNetBridge.runtimeconfig.json +14 -0
- package/framework/libs/x64/nativescript.dll +0 -0
- package/framework/tools/ManifestMerger/ManifestMerger.deps.json +24 -0
- package/framework/tools/ManifestMerger/ManifestMerger.dll +0 -0
- package/framework/tools/ManifestMerger/ManifestMerger.pdb +0 -0
- package/framework/tools/dotnet-tool-arm64.exe +0 -0
- package/framework/tools/dotnet-tool-x64.exe +0 -0
- package/framework/tools/dotnet-tool.exe +0 -0
- package/framework/tools/dotnet-typings-gen/dotnet-typings-gen.deps.json +23 -0
- package/framework/tools/dotnet-typings-gen/dotnet-typings-gen.dll +0 -0
- package/framework/tools/dotnet-typings-gen/dotnet-typings-gen.exe +0 -0
- package/framework/tools/dotnet-typings-gen/dotnet-typings-gen.pdb +0 -0
- package/framework/tools/dotnet-typings-gen/dotnet-typings-gen.runtimeconfig.json +13 -0
- package/framework/tools/sbg-arm64.exe +0 -0
- package/framework/tools/sbg-x64.exe +0 -0
- package/framework/tools/sbg.exe +0 -0
- package/framework/tools/typings-generator-arm64.exe +0 -0
- package/framework/tools/typings-generator-x64.exe +0 -0
- package/framework/tools/typings-generator.exe +0 -0
- package/package.json +43 -0
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Buffers;
|
|
3
|
+
using System.Collections.Concurrent;
|
|
4
|
+
using System.Collections.Generic;
|
|
5
|
+
using System.Linq;
|
|
6
|
+
using System.Reflection;
|
|
7
|
+
using System.Reflection.Emit;
|
|
8
|
+
using System.Runtime.CompilerServices;
|
|
9
|
+
using System.Runtime.InteropServices;
|
|
10
|
+
using System.Threading;
|
|
11
|
+
using System.Globalization;
|
|
12
|
+
|
|
13
|
+
namespace NativeScriptBridge;
|
|
14
|
+
|
|
15
|
+
public static partial class Bridge
|
|
16
|
+
{
|
|
17
|
+
// Cache for dynamically generated proxy types (dev-only fallback). Keyed by base type PLUS
|
|
18
|
+
// the exact set of interfaces/members a given JS `overrides` object asked for, so different
|
|
19
|
+
// JS subclasses of the same base type (different override sets) get distinct emitted types.
|
|
20
|
+
private readonly record struct ProxyTypeKey(Type BaseType, string InterfaceKey, string MemberKey);
|
|
21
|
+
private static readonly ConcurrentDictionary<ProxyTypeKey, Type> s_dynamicProxyCache = new();
|
|
22
|
+
private static int s_dynamicProxyTypeCounter = 0;
|
|
23
|
+
private static readonly AssemblyBuilder? s_dynamicAssembly = CreateDynamicAssembly();
|
|
24
|
+
private static readonly ModuleBuilder? s_dynamicModule = CreateDynamicModule(s_dynamicAssembly);
|
|
25
|
+
|
|
26
|
+
private static AssemblyBuilder? CreateDynamicAssembly()
|
|
27
|
+
{
|
|
28
|
+
try
|
|
29
|
+
{
|
|
30
|
+
var an = new AssemblyName("NSWinRTDynamicProxies");
|
|
31
|
+
var asm = AssemblyBuilder.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
|
|
32
|
+
|
|
33
|
+
return asm;
|
|
34
|
+
}
|
|
35
|
+
catch { return null; }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private static ModuleBuilder? CreateDynamicModule(AssemblyBuilder? asm)
|
|
39
|
+
{
|
|
40
|
+
try {
|
|
41
|
+
var m = asm?.DefineDynamicModule("NSWinRTDynamicProxiesModule");
|
|
42
|
+
return m;
|
|
43
|
+
} catch { return null; }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Public helpers callable from runtime-generated types. Nested type so it can
|
|
47
|
+
// access Bridge's private proxy helpers (ProxyInvokeMethod, etc.) while
|
|
48
|
+
// exposing a stable public API for emitted proxies.
|
|
49
|
+
public static class ProxyRuntime
|
|
50
|
+
{
|
|
51
|
+
public static T InvokeMethodTyped<T>(object instance, string methodName, object[] args)
|
|
52
|
+
{
|
|
53
|
+
var result = ProxyInvokeMethod(instance, methodName, args);
|
|
54
|
+
if (result is null) return default!;
|
|
55
|
+
if (result is T t) return t;
|
|
56
|
+
return (T)Convert.ChangeType(result, typeof(T), CultureInfo.InvariantCulture);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public static object? InvokeMethod(object instance, string methodName, object[] args)
|
|
60
|
+
{
|
|
61
|
+
return ProxyInvokeMethod(instance, methodName, args);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public static void InvokeVoid(object instance, string methodName, object[] args)
|
|
65
|
+
{
|
|
66
|
+
ProxyInvokeVoid(instance, methodName, args);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public static T GetProperty<T>(object instance, string propertyName)
|
|
70
|
+
{
|
|
71
|
+
var result = ProxyGetProperty(instance, propertyName);
|
|
72
|
+
if (result is null) return default!;
|
|
73
|
+
if (result is T t) return t;
|
|
74
|
+
return (T)Convert.ChangeType(result, typeof(T), CultureInfo.InvariantCulture);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public static void SetProperty(object instance, string propertyName, object? value)
|
|
78
|
+
{
|
|
79
|
+
ProxySetProperty(instance, propertyName, value);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
private sealed class ProxyCallbackHolder { public int CallbackId; public int HandleId; public ProxyCallbackHolder(int cb) { CallbackId = cb; HandleId = 0; } }
|
|
83
|
+
|
|
84
|
+
private static readonly ConditionalWeakTable<object, ProxyCallbackHolder> s_proxyCallbacks = new();
|
|
85
|
+
private static readonly ThreadLocal<int?> s_pendingProxyCallbackId = new(() => null);
|
|
86
|
+
private static readonly object s_proxyRegisterLock = new();
|
|
87
|
+
private static bool s_proxyDispatcherRegistered = false;
|
|
88
|
+
|
|
89
|
+
private static void EnsureProxyDispatcherCallbacksRegistered()
|
|
90
|
+
{
|
|
91
|
+
if (s_proxyDispatcherRegistered) return;
|
|
92
|
+
lock (s_proxyRegisterLock)
|
|
93
|
+
{
|
|
94
|
+
if (s_proxyDispatcherRegistered) return;
|
|
95
|
+
|
|
96
|
+
Type? pdType = null;
|
|
97
|
+
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
|
|
98
|
+
{
|
|
99
|
+
try
|
|
100
|
+
{
|
|
101
|
+
pdType = asm.GetType("NativeScriptGeneratedProxies.ProxyDispatcher")
|
|
102
|
+
?? asm.GetType("NSWinRTProxies.ProxyDispatcher")
|
|
103
|
+
?? asm.GetType("NativeScriptGeneratedProxies.ProxyDispatcher");
|
|
104
|
+
if (pdType != null) break;
|
|
105
|
+
}
|
|
106
|
+
catch { }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (pdType != null)
|
|
110
|
+
{
|
|
111
|
+
// Properties generated by SBG
|
|
112
|
+
var p1 = pdType.GetProperty("JsInvokeMethod", BindingFlags.Public | BindingFlags.Static);
|
|
113
|
+
p1?.SetValue(null, new Func<object, string, object[], object?>(ProxyInvokeMethod));
|
|
114
|
+
|
|
115
|
+
var p2 = pdType.GetProperty("JsInvokeVoid", BindingFlags.Public | BindingFlags.Static);
|
|
116
|
+
p2?.SetValue(null, new Action<object, string, object[]>(ProxyInvokeVoid));
|
|
117
|
+
|
|
118
|
+
var p3 = pdType.GetProperty("JsGetProperty", BindingFlags.Public | BindingFlags.Static);
|
|
119
|
+
p3?.SetValue(null, new Func<object, string, object?>(ProxyGetProperty));
|
|
120
|
+
|
|
121
|
+
var p4 = pdType.GetProperty("JsSetProperty", BindingFlags.Public | BindingFlags.Static);
|
|
122
|
+
p4?.SetValue(null, new Action<object, string, object?>(ProxySetProperty));
|
|
123
|
+
|
|
124
|
+
var p5 = pdType.GetProperty("JsInitializeInstance", BindingFlags.Public | BindingFlags.Static);
|
|
125
|
+
p5?.SetValue(null, new Action<object, string>(ProxyInitializeInstance));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
s_proxyDispatcherRegistered = true;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private static bool DynamicProxiesEnabled()
|
|
133
|
+
{
|
|
134
|
+
// Always allow dynamic proxies in dev when Reflection.Emit is available.
|
|
135
|
+
return s_dynamicModule != null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private static Type? FindGeneratedProxyType(string typeName)
|
|
139
|
+
{
|
|
140
|
+
if (string.IsNullOrEmpty(typeName)) return null;
|
|
141
|
+
var lastDot = typeName.LastIndexOf('.');
|
|
142
|
+
var shortName = lastDot >= 0 ? typeName[(lastDot + 1)..] : typeName;
|
|
143
|
+
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
|
|
144
|
+
{
|
|
145
|
+
try
|
|
146
|
+
{
|
|
147
|
+
// Only consider assemblies that look like generated proxy assemblies
|
|
148
|
+
var asmName = asm.GetName().Name ?? "";
|
|
149
|
+
if (!string.Equals(asmName, "NativeScriptGeneratedProxies", StringComparison.Ordinal)
|
|
150
|
+
&& !string.Equals(asmName, "NSWinRTProxies", StringComparison.Ordinal))
|
|
151
|
+
{
|
|
152
|
+
// Also check for candidate full-type names in any assembly
|
|
153
|
+
var candidates = new[] { $"NativeScriptGeneratedProxies.{shortName}", $"NSWinRTProxies.{shortName}" };
|
|
154
|
+
foreach (var cn in candidates)
|
|
155
|
+
{
|
|
156
|
+
var t = asm.GetType(cn);
|
|
157
|
+
if (t is not null) return t;
|
|
158
|
+
}
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// If this is a generated proxies assembly, look up the proxy type name.
|
|
163
|
+
var genCandidates = new[] { $"NativeScriptGeneratedProxies.{shortName}", $"NSWinRTProxies.{shortName}" };
|
|
164
|
+
foreach (var cn in genCandidates)
|
|
165
|
+
{
|
|
166
|
+
var t = asm.GetType(cn);
|
|
167
|
+
if (t is not null) return t;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
catch { }
|
|
171
|
+
}
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private static Type GetOrCreateDynamicProxyType(Type baseType, string[] interfaceNames, string[] memberNames)
|
|
176
|
+
{
|
|
177
|
+
var interfaceKey = string.Join(",", interfaceNames.OrderBy(n => n, StringComparer.Ordinal));
|
|
178
|
+
var memberKey = string.Join(",", memberNames.OrderBy(n => n, StringComparer.Ordinal));
|
|
179
|
+
var key = new ProxyTypeKey(baseType, interfaceKey, memberKey);
|
|
180
|
+
return s_dynamicProxyCache.GetOrAdd(key, _ => CreateDynamicProxyType(baseType, interfaceNames, memberNames));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private static Type CreateDynamicProxyType(Type baseType, string[] interfaceNames, string[] memberNames)
|
|
184
|
+
{
|
|
185
|
+
|
|
186
|
+
if (s_dynamicModule == null)
|
|
187
|
+
throw new InvalidOperationException("Dynamic proxy generation not available in this environment");
|
|
188
|
+
|
|
189
|
+
if (baseType.IsSealed)
|
|
190
|
+
throw new InvalidOperationException($"Cannot derive from sealed type {baseType.FullName}");
|
|
191
|
+
|
|
192
|
+
var interfaceTypes = interfaceNames
|
|
193
|
+
.Select(n => ResolveType(null, n) ?? throw new TypeLoadException($"Interface type not found: {n}"))
|
|
194
|
+
.ToArray();
|
|
195
|
+
|
|
196
|
+
// Members JS actually overrides. Only base virtuals in this set get an IL override
|
|
197
|
+
// emitted — anything else is left alone so the real base implementation stays live in
|
|
198
|
+
// the vtable (no "call base" IL needed, the base method was simply never touched).
|
|
199
|
+
var memberSet = new HashSet<string>(memberNames, StringComparer.Ordinal);
|
|
200
|
+
|
|
201
|
+
var safeName = (baseType.FullName ?? Guid.NewGuid().ToString()).Replace('.', '_');
|
|
202
|
+
var uniqueSuffix = Interlocked.Increment(ref s_dynamicProxyTypeCounter);
|
|
203
|
+
var typeName = $"NSWinRTDynamicProxies.{safeName}_JsProxy_{uniqueSuffix}";
|
|
204
|
+
|
|
205
|
+
var tb = s_dynamicModule.DefineType(typeName, TypeAttributes.Public | TypeAttributes.Class, baseType);
|
|
206
|
+
|
|
207
|
+
// Mirror public base constructors and ensure at least a parameterless
|
|
208
|
+
// ctor exists on the derived type. If the base type has no parameterless
|
|
209
|
+
// ctor, emit a parameterless ctor that forwards default(T) values to the
|
|
210
|
+
// first available base ctor (dev-only convenience).
|
|
211
|
+
var baseCtors = baseType.GetConstructors(BindingFlags.Public | BindingFlags.Instance)
|
|
212
|
+
.Where(c => !c.IsGenericMethod)
|
|
213
|
+
.ToArray();
|
|
214
|
+
|
|
215
|
+
ConstructorInfo? chosenBaseForDefault = null;
|
|
216
|
+
foreach (var bc in baseCtors)
|
|
217
|
+
{
|
|
218
|
+
var parameters = bc.GetParameters();
|
|
219
|
+
if (parameters.Any(p => p.ParameterType.IsByRef)) continue;
|
|
220
|
+
chosenBaseForDefault ??= bc;
|
|
221
|
+
|
|
222
|
+
// Define a matching ctor on the derived type that forwards args to base.
|
|
223
|
+
var paramTypes = parameters.Select(p => p.ParameterType).ToArray();
|
|
224
|
+
var derivedCtor = tb.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, paramTypes);
|
|
225
|
+
var ilg = derivedCtor.GetILGenerator();
|
|
226
|
+
// Load 'this'
|
|
227
|
+
ilg.Emit(OpCodes.Ldarg_0);
|
|
228
|
+
// Load each argument and forward
|
|
229
|
+
for (int i = 0; i < paramTypes.Length; i++)
|
|
230
|
+
{
|
|
231
|
+
// ldarg_1..n
|
|
232
|
+
switch (i + 1)
|
|
233
|
+
{
|
|
234
|
+
case 1: ilg.Emit(OpCodes.Ldarg_1); break;
|
|
235
|
+
case 2: ilg.Emit(OpCodes.Ldarg_2); break;
|
|
236
|
+
case 3: ilg.Emit(OpCodes.Ldarg_3); break;
|
|
237
|
+
default: ilg.Emit(OpCodes.Ldarg, i + 1); break;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
ilg.Emit(OpCodes.Call, bc);
|
|
241
|
+
ilg.Emit(OpCodes.Ret);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// If we didn't define a parameterless ctor, emit one that forwards
|
|
245
|
+
// default(T) values to the chosen base ctor (if any), otherwise call
|
|
246
|
+
// System.Object..ctor.
|
|
247
|
+
if (!baseCtors.Any(c => c.GetParameters().Length == 0))
|
|
248
|
+
{
|
|
249
|
+
var paramless = tb.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, Type.EmptyTypes);
|
|
250
|
+
var ilg = paramless.GetILGenerator();
|
|
251
|
+
if (chosenBaseForDefault is not null)
|
|
252
|
+
{
|
|
253
|
+
ilg.Emit(OpCodes.Ldarg_0);
|
|
254
|
+
var parameters = chosenBaseForDefault.GetParameters();
|
|
255
|
+
// For each param, emit default value (null for ref types, initobj local for value types)
|
|
256
|
+
for (int i = 0; i < parameters.Length; i++)
|
|
257
|
+
{
|
|
258
|
+
var ptype = parameters[i].ParameterType;
|
|
259
|
+
if (ptype.IsByRef) throw new InvalidOperationException("Cannot forward byref parameters in dynamic ctor");
|
|
260
|
+
if (ptype.IsValueType)
|
|
261
|
+
{
|
|
262
|
+
var temp = ilg.DeclareLocal(ptype);
|
|
263
|
+
ilg.Emit(OpCodes.Ldloca_S, temp);
|
|
264
|
+
ilg.Emit(OpCodes.Initobj, ptype);
|
|
265
|
+
ilg.Emit(OpCodes.Ldloc, temp);
|
|
266
|
+
}
|
|
267
|
+
else
|
|
268
|
+
{
|
|
269
|
+
ilg.Emit(OpCodes.Ldnull);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
ilg.Emit(OpCodes.Call, chosenBaseForDefault);
|
|
273
|
+
ilg.Emit(OpCodes.Ret);
|
|
274
|
+
}
|
|
275
|
+
else
|
|
276
|
+
{
|
|
277
|
+
// No public base ctors (rare); call Object..ctor
|
|
278
|
+
var objCtor = typeof(object).GetConstructor(Type.EmptyTypes)!;
|
|
279
|
+
ilg.Emit(OpCodes.Ldarg_0);
|
|
280
|
+
ilg.Emit(OpCodes.Call, objCtor);
|
|
281
|
+
ilg.Emit(OpCodes.Ret);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Shared by both loops below, keyed by "Name(Param1FullName,Param2FullName,...)" so a
|
|
286
|
+
// member that's both an overridden base virtual and a requested interface member (e.g.
|
|
287
|
+
// the base class already implements that interface) reuses one emitted body instead of
|
|
288
|
+
// tripping a "duplicate member" TypeBuilder error.
|
|
289
|
+
var definedSignatures = new Dictionary<string, MethodBuilder>(StringComparer.Ordinal);
|
|
290
|
+
static string SignatureKey(string name, Type[] paramTypes) =>
|
|
291
|
+
name + "(" + string.Join(",", paramTypes.Select(p => p.FullName)) + ")";
|
|
292
|
+
|
|
293
|
+
// Override only the base virtuals JS actually asked for (memberSet). Anything not in
|
|
294
|
+
// that set is simply never touched, so the base implementation stays live in the vtable —
|
|
295
|
+
// no "call base" IL needed. (Skip ref/out and generic methods — unsupported.)
|
|
296
|
+
var virtualMethods = baseType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
|
|
297
|
+
.Where(m => m.IsVirtual && !m.IsFinal && !m.IsGenericMethod && !m.IsConstructor && memberSet.Contains(m.Name))
|
|
298
|
+
.ToArray();
|
|
299
|
+
|
|
300
|
+
foreach (var mi in virtualMethods)
|
|
301
|
+
{
|
|
302
|
+
var parameters = mi.GetParameters();
|
|
303
|
+
if (parameters.Any(p => p.ParameterType.IsByRef)) continue;
|
|
304
|
+
|
|
305
|
+
var paramTypes = parameters.Select(p => p.ParameterType).ToArray();
|
|
306
|
+
var sigKey = SignatureKey(mi.Name, paramTypes);
|
|
307
|
+
if (!definedSignatures.TryGetValue(sigKey, out var mb))
|
|
308
|
+
{
|
|
309
|
+
mb = EmitProxyMethodBody(tb, mi.Name,
|
|
310
|
+
MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig,
|
|
311
|
+
mi.CallingConvention, mi.ReturnType, paramTypes, mi.Name);
|
|
312
|
+
definedSignatures[sigKey] = mb;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
tb.DefineMethodOverride(mb, mi);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Interfaces: every requested interface (plus any interfaces *those* extend, since
|
|
319
|
+
// AddInterfaceImplementation only registers the interface named, not its ancestors) must
|
|
320
|
+
// have every member implemented — the CLR requires it, unlike base-class virtuals where
|
|
321
|
+
// skipping is fine. Members JS doesn't override fall back to default/no-op via the same
|
|
322
|
+
// generic dispatcher (ProxyRuntime.InvokeMethodTyped/InvokeVoid already do this).
|
|
323
|
+
if (interfaceTypes.Length > 0)
|
|
324
|
+
{
|
|
325
|
+
var allInterfaceTypes = new List<Type>();
|
|
326
|
+
void CollectInterfaces(Type t)
|
|
327
|
+
{
|
|
328
|
+
if (allInterfaceTypes.Contains(t)) return;
|
|
329
|
+
allInterfaceTypes.Add(t);
|
|
330
|
+
foreach (var parent in t.GetInterfaces()) CollectInterfaces(parent);
|
|
331
|
+
}
|
|
332
|
+
foreach (var ifaceType in interfaceTypes) CollectInterfaces(ifaceType);
|
|
333
|
+
|
|
334
|
+
foreach (var ifaceType in allInterfaceTypes) tb.AddInterfaceImplementation(ifaceType);
|
|
335
|
+
|
|
336
|
+
foreach (var ifaceType in allInterfaceTypes)
|
|
337
|
+
{
|
|
338
|
+
foreach (var mi in ifaceType.GetMethods())
|
|
339
|
+
{
|
|
340
|
+
var parameters = mi.GetParameters();
|
|
341
|
+
if (parameters.Any(p => p.ParameterType.IsByRef)) continue;
|
|
342
|
+
|
|
343
|
+
var paramTypes = parameters.Select(p => p.ParameterType).ToArray();
|
|
344
|
+
var sigKey = SignatureKey(mi.Name, paramTypes);
|
|
345
|
+
if (!definedSignatures.TryGetValue(sigKey, out var mb))
|
|
346
|
+
{
|
|
347
|
+
mb = EmitProxyMethodBody(tb, mi.Name,
|
|
348
|
+
MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final
|
|
349
|
+
| MethodAttributes.HideBySig | MethodAttributes.NewSlot,
|
|
350
|
+
mi.CallingConvention, mi.ReturnType, paramTypes, mi.Name);
|
|
351
|
+
definedSignatures[sigKey] = mb;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
tb.DefineMethodOverride(mb, mi);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return tb.CreateTypeInfo()!.AsType();
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Shared IL body for both base-virtual overrides and interface-member implementations: box
|
|
363
|
+
// args into an object[], forward to ProxyRuntime.InvokeVoid/InvokeMethodTyped<T> by name.
|
|
364
|
+
private static MethodBuilder EmitProxyMethodBody(
|
|
365
|
+
TypeBuilder tb, string emittedName, MethodAttributes attrs, CallingConventions callingConvention,
|
|
366
|
+
Type returnType, Type[] paramTypes, string dispatchName)
|
|
367
|
+
{
|
|
368
|
+
var mb = tb.DefineMethod(emittedName, attrs, callingConvention, returnType, paramTypes);
|
|
369
|
+
var ilg = mb.GetILGenerator();
|
|
370
|
+
var argsLocal = ilg.DeclareLocal(typeof(object[]));
|
|
371
|
+
ilg.Emit(OpCodes.Ldc_I4, paramTypes.Length);
|
|
372
|
+
ilg.Emit(OpCodes.Newarr, typeof(object));
|
|
373
|
+
ilg.Emit(OpCodes.Stloc, argsLocal);
|
|
374
|
+
|
|
375
|
+
for (int i = 0; i < paramTypes.Length; i++)
|
|
376
|
+
{
|
|
377
|
+
ilg.Emit(OpCodes.Ldloc, argsLocal);
|
|
378
|
+
ilg.Emit(OpCodes.Ldc_I4, i);
|
|
379
|
+
ilg.Emit(OpCodes.Ldarg, i + 1);
|
|
380
|
+
if (paramTypes[i].IsValueType) ilg.Emit(OpCodes.Box, paramTypes[i]);
|
|
381
|
+
ilg.Emit(OpCodes.Stelem_Ref);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (returnType == typeof(void))
|
|
385
|
+
{
|
|
386
|
+
var invokeVoid = typeof(Bridge).GetNestedType("ProxyRuntime", BindingFlags.Public | BindingFlags.Static)!.GetMethod("InvokeVoid", BindingFlags.Public | BindingFlags.Static)!;
|
|
387
|
+
ilg.Emit(OpCodes.Ldarg_0);
|
|
388
|
+
ilg.Emit(OpCodes.Ldstr, dispatchName);
|
|
389
|
+
ilg.Emit(OpCodes.Ldloc, argsLocal);
|
|
390
|
+
ilg.Emit(OpCodes.Call, invokeVoid);
|
|
391
|
+
ilg.Emit(OpCodes.Ret);
|
|
392
|
+
}
|
|
393
|
+
else
|
|
394
|
+
{
|
|
395
|
+
var proxyRuntimeType = typeof(Bridge).GetNestedType("ProxyRuntime", BindingFlags.Public | BindingFlags.Static)!;
|
|
396
|
+
var gm = proxyRuntimeType.GetMethod("InvokeMethodTyped", BindingFlags.Public | BindingFlags.Static)!.MakeGenericMethod(returnType);
|
|
397
|
+
ilg.Emit(OpCodes.Ldarg_0);
|
|
398
|
+
ilg.Emit(OpCodes.Ldstr, dispatchName);
|
|
399
|
+
ilg.Emit(OpCodes.Ldloc, argsLocal);
|
|
400
|
+
ilg.Emit(OpCodes.Call, gm);
|
|
401
|
+
ilg.Emit(OpCodes.Ret);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return mb;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
private static DispatchResult CreateJsSubclass(
|
|
408
|
+
string? assemblyName, string typeName, int callbackId, string[] interfaceNames, string[] memberNames)
|
|
409
|
+
{
|
|
410
|
+
EnsureProxyDispatcherCallbacksRegistered();
|
|
411
|
+
|
|
412
|
+
s_pendingProxyCallbackId.Value = callbackId;
|
|
413
|
+
|
|
414
|
+
// Try to find a statically-generated proxy type first (Mechanism A: sbg-compiled into
|
|
415
|
+
// the app itself — already has any requested interfaces baked into real C# source).
|
|
416
|
+
Type? t = FindGeneratedProxyType(typeName);
|
|
417
|
+
if (t is null)
|
|
418
|
+
{
|
|
419
|
+
// When typeName is an auto-generated proxy name (com.tns.gen.winrt.*),
|
|
420
|
+
// the caller puts the real base type name in assemblyName so we can
|
|
421
|
+
// look up or dynamically emit a proxy for the correct base class.
|
|
422
|
+
string? resolveFrom = typeName;
|
|
423
|
+
if (!string.IsNullOrEmpty(assemblyName)
|
|
424
|
+
&& typeName != null
|
|
425
|
+
&& typeName.StartsWith("com.tns.gen.winrt.", StringComparison.Ordinal))
|
|
426
|
+
{
|
|
427
|
+
resolveFrom = assemblyName;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Resolve the WinRT/base type and optionally emit a dynamic proxy (Mechanism B fallback).
|
|
431
|
+
var baseType = ResolveType(null, resolveFrom)
|
|
432
|
+
?? ResolveType(assemblyName, typeName)
|
|
433
|
+
?? throw new TypeLoadException($"Type not found: {resolveFrom} (proxy: {typeName}, assembly: {assemblyName})");
|
|
434
|
+
|
|
435
|
+
if (baseType.IsClass && !baseType.IsSealed)
|
|
436
|
+
{
|
|
437
|
+
try { t = GetOrCreateDynamicProxyType(baseType, interfaceNames, memberNames); }
|
|
438
|
+
catch (Exception) { t = baseType; }
|
|
439
|
+
}
|
|
440
|
+
else
|
|
441
|
+
{
|
|
442
|
+
t = baseType;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Create the managed instance. Generated classes should call
|
|
447
|
+
// ProxyDispatcher.InitializeInstance(this, typeName) in their constructor,
|
|
448
|
+
// which will invoke our ProxyInitializeInstance and capture the pending callback id.
|
|
449
|
+
object? instance = Activator.CreateInstance(t);
|
|
450
|
+
if (instance is null)
|
|
451
|
+
throw new InvalidOperationException($"Failed to instantiate proxy type {t.FullName}.");
|
|
452
|
+
|
|
453
|
+
// Ensure there's a mapping for this instance immediately to avoid any
|
|
454
|
+
// early virtual calls from constructors reaching the JS side before
|
|
455
|
+
// the handle is registered. Call our initializer directly to register
|
|
456
|
+
// the pending callback id when dynamic proxies don't call it.
|
|
457
|
+
try { ProxyInitializeInstance(instance, typeName); } catch { }
|
|
458
|
+
|
|
459
|
+
// Force COM CCW vtable creation for any implemented WinRT interfaces now, rather than
|
|
460
|
+
// relying on it happening implicitly the first time the instance crosses the ABI boundary.
|
|
461
|
+
// Best-effort/reflection-only — no-ops cleanly when CsWinRT isn't loaded (e.g. xunit host).
|
|
462
|
+
if (interfaceNames.Length > 0)
|
|
463
|
+
{
|
|
464
|
+
TryActivateWinRTInterfaces(instance);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Box the instance into a handle to return to the runtime.
|
|
468
|
+
var result = Box(instance);
|
|
469
|
+
|
|
470
|
+
// Populate holder with handle id now that a handle exists.
|
|
471
|
+
if (s_proxyCallbacks.TryGetValue(instance, out var holder))
|
|
472
|
+
{
|
|
473
|
+
try { holder.HandleId = result.HandleId(); } catch { holder.HandleId = 0; }
|
|
474
|
+
}
|
|
475
|
+
else
|
|
476
|
+
{
|
|
477
|
+
// If the generated proxy did not call InitializeInstance, create mapping now.
|
|
478
|
+
int hid = 0;
|
|
479
|
+
try { hid = result.HandleId(); } catch { hid = 0; }
|
|
480
|
+
var newHolder = new ProxyCallbackHolder(callbackId) { HandleId = hid };
|
|
481
|
+
s_proxyCallbacks.Add(instance, newHolder);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
s_pendingProxyCallbackId.Value = null;
|
|
485
|
+
return result;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Locates CsWinRT's ComWrappersSupport via reflection (same style as the WinRT.IWinRTObject
|
|
489
|
+
// lookup in ObtainNativePtr — no compile-time reference needed, since DotNetBridge.csproj is a
|
|
490
|
+
// dependency-free net9.0 library and CsWinRT is only ever loaded into the process by the WinUI
|
|
491
|
+
// app itself) and asks it to build a real COM CCW for the instance's implemented WinRT
|
|
492
|
+
// interfaces now, via CsWinRT's JIT reflection-fallback vtable path (works for types it never
|
|
493
|
+
// saw at compile time — see plan doc for the CsWinRT version/AOT caveats). Best-effort: no-ops
|
|
494
|
+
// when CsWinRT isn't loaded, e.g. the xunit test host has no WinUI/CsWinRT at all.
|
|
495
|
+
private static void TryActivateWinRTInterfaces(object instance)
|
|
496
|
+
{
|
|
497
|
+
try
|
|
498
|
+
{
|
|
499
|
+
Type? supportType = null;
|
|
500
|
+
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
|
|
501
|
+
{
|
|
502
|
+
try
|
|
503
|
+
{
|
|
504
|
+
supportType = asm.GetType("WinRT.ComWrappersSupport");
|
|
505
|
+
if (supportType != null) break;
|
|
506
|
+
}
|
|
507
|
+
catch { }
|
|
508
|
+
}
|
|
509
|
+
if (supportType == null) return;
|
|
510
|
+
|
|
511
|
+
foreach (var name in new[] { "CreateCCWForObject", "GetOrCreateComInterfaceForObject" })
|
|
512
|
+
{
|
|
513
|
+
foreach (var candidate in supportType.GetMethods(BindingFlags.Public | BindingFlags.Static)
|
|
514
|
+
.Where(m => m.Name == name))
|
|
515
|
+
{
|
|
516
|
+
try
|
|
517
|
+
{
|
|
518
|
+
var target = candidate.IsGenericMethodDefinition
|
|
519
|
+
? candidate.MakeGenericMethod(instance.GetType())
|
|
520
|
+
: candidate;
|
|
521
|
+
var ps = target.GetParameters();
|
|
522
|
+
if (ps.Length == 1) { target.Invoke(null, [instance]); return; }
|
|
523
|
+
if (ps.Length == 2 && ps[1].ParameterType == typeof(Guid)) { target.Invoke(null, [instance, Guid.Empty]); return; }
|
|
524
|
+
}
|
|
525
|
+
catch { /* try the next overload/name */ }
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
catch { /* best-effort only */ }
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
private static void ProxyInitializeInstance(object instance, string typeName)
|
|
533
|
+
{
|
|
534
|
+
var cb = s_pendingProxyCallbackId.Value;
|
|
535
|
+
if (cb.HasValue)
|
|
536
|
+
{
|
|
537
|
+
var holder = new ProxyCallbackHolder(cb.Value);
|
|
538
|
+
s_proxyCallbacks.Add(instance, holder);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
private static object? ProxyInvokeMethod(object instance, string methodName, object[] args)
|
|
543
|
+
{
|
|
544
|
+
if (!s_proxyCallbacks.TryGetValue(instance, out var holder)) throw new InvalidOperationException("No JS callback registered for proxy instance.");
|
|
545
|
+
var payload = new object?[] { new HandleRef(holder.HandleId), methodName, args };
|
|
546
|
+
return CallJsCallback(holder.CallbackId, payload);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
private static void ProxyInvokeVoid(object instance, string methodName, object[] args)
|
|
550
|
+
{
|
|
551
|
+
if (!s_proxyCallbacks.TryGetValue(instance, out var holder)) throw new InvalidOperationException("No JS callback registered for proxy instance.");
|
|
552
|
+
|
|
553
|
+
var payload = new object?[] { new HandleRef(holder.HandleId), methodName, args };
|
|
554
|
+
CallJsCallback(holder.CallbackId, payload);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
private static object? ProxyGetProperty(object instance, string propertyName)
|
|
558
|
+
{
|
|
559
|
+
if (!s_proxyCallbacks.TryGetValue(instance, out var holder)) throw new InvalidOperationException("No JS callback registered for proxy instance.");
|
|
560
|
+
// Tag "get:" prefix so the JS dispatcher can distinguish getter from method call.
|
|
561
|
+
var payload = new object?[] { new HandleRef(holder.HandleId), "get:" + propertyName };
|
|
562
|
+
return CallJsCallback(holder.CallbackId, payload);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
private static void ProxySetProperty(object instance, string propertyName, object? value)
|
|
566
|
+
{
|
|
567
|
+
if (!s_proxyCallbacks.TryGetValue(instance, out var holder)) throw new InvalidOperationException("No JS callback registered for proxy instance.");
|
|
568
|
+
// Tag "set:" prefix so the JS dispatcher can distinguish setter from method call.
|
|
569
|
+
var payload = new object?[] { new HandleRef(holder.HandleId), "set:" + propertyName, value };
|
|
570
|
+
CallJsCallback(holder.CallbackId, payload);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|