@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.
@@ -1,5 +1,7 @@
1
1
  using System;
2
+ using System.Collections.Generic;
2
3
  using System.IO;
4
+ using System.Linq;
3
5
  using System.Runtime.InteropServices;
4
6
  using System.Text.Json;
5
7
 
@@ -26,6 +28,28 @@ namespace __PROJECT_NAME__
26
28
  [DllImport(NativeScriptLibrary, EntryPoint = nameof(runtime_install_ctrlc_handler))]
27
29
  private static extern void runtime_install_ctrlc_handler(int exitCode);
28
30
 
31
+ [DllImport(NativeScriptLibrary, EntryPoint = nameof(runtime_set_local_folder))]
32
+ private static extern void runtime_set_local_folder([MarshalAs(UnmanagedType.LPUTF8Str)] string localFolder);
33
+
34
+ [DllImport(NativeScriptLibrary, EntryPoint = nameof(runtime_get_last_js_error))]
35
+ private static extern IntPtr runtime_get_last_js_error();
36
+
37
+ [DllImport(NativeScriptLibrary, EntryPoint = nameof(runtime_free_js_error))]
38
+ private static extern void runtime_free_js_error(IntPtr ptr);
39
+
40
+ [DllImport(NativeScriptLibrary, EntryPoint = nameof(runtime_has_devtools))]
41
+ private static extern bool runtime_has_devtools();
42
+
43
+ [DllImport(NativeScriptLibrary, EntryPoint = nameof(runtime_pump_timers))]
44
+ private static extern void runtime_pump_timers();
45
+
46
+ public void PumpTimers()
47
+ {
48
+ if (!_initialized) return;
49
+ try { runtime_pump_timers(); }
50
+ catch { }
51
+ }
52
+
29
53
  #if DEBUG
30
54
  [DllImport(NativeScriptLibrary, EntryPoint = nameof(runtime_devtools_start))]
31
55
  private static extern IntPtr runtime_devtools_start(long runtime, ushort port);
@@ -38,9 +62,11 @@ namespace __PROJECT_NAME__
38
62
 
39
63
  public string DevtoolsFrontendUrl { get; private set; }
40
64
 
65
+ private bool _devtoolsAvailable;
66
+
41
67
  public void PumpDevtools()
42
68
  {
43
- if (!_initialized) return;
69
+ if (!_initialized || !_devtoolsAvailable) return;
44
70
  try { runtime_devtools_pump(_runtime); }
45
71
  catch (Exception ex)
46
72
  {
@@ -50,6 +76,7 @@ namespace __PROJECT_NAME__
50
76
 
51
77
  private void StartDevtoolsSafely()
52
78
  {
79
+ if (!runtime_has_devtools()) return;
53
80
  IntPtr urlPtr = IntPtr.Zero;
54
81
  try
55
82
  {
@@ -60,7 +87,10 @@ namespace __PROJECT_NAME__
60
87
  ? $"devtools://devtools/bundled/inspector.html?ws={wsUrl.Replace("ws://", "")}"
61
88
  : null;
62
89
  if (DevtoolsFrontendUrl != null)
90
+ {
91
+ _devtoolsAvailable = true;
63
92
  System.Diagnostics.Debug.WriteLine($"[NativeScript DevTools] {DevtoolsFrontendUrl}");
93
+ }
64
94
  }
65
95
  catch (Exception ex)
66
96
  {
@@ -82,6 +112,7 @@ namespace __PROJECT_NAME__
82
112
  if (_initialized) return;
83
113
  AttachConsole(ATTACH_PARENT_PROCESS);
84
114
  runtime_install_ctrlc_handler(0);
115
+ runtime_set_local_folder(Windows.Storage.ApplicationData.Current.LocalFolder.Path);
85
116
  _runtime = runtime_init(AppContext.BaseDirectory);
86
117
  #if DEBUG
87
118
  if (ConsumeDebugBreakMarker())
@@ -117,16 +148,38 @@ namespace __PROJECT_NAME__
117
148
  throw new InvalidOperationException("Runtime must be initialized before running scripts.");
118
149
 
119
150
  var entryPath = ResolveEntryScriptPath();
120
- var script = File.ReadAllText(Path.GetFullPath(entryPath));
121
- try
151
+ if (entryPath == null)
122
152
  {
123
- runtime_runscript(_runtime, script, Path.GetFileName(entryPath));
153
+ System.Diagnostics.Debug.WriteLine("[NativeScript Runtime] No entry script found — bundle missing from app directory.");
154
+ return;
124
155
  }
125
- catch (Exception ex)
156
+
157
+ var dir = Path.GetDirectoryName(Path.GetFullPath(entryPath));
158
+ var chunks = new List<string>();
159
+ foreach (var chunkName in new[] { "runtime.js", "vendor.js" })
160
+ {
161
+ var chunkPath = Path.Combine(dir, chunkName);
162
+ if (File.Exists(chunkPath) &&
163
+ !string.Equals(chunkPath, Path.GetFullPath(entryPath), StringComparison.OrdinalIgnoreCase))
164
+ {
165
+ chunks.Add(chunkPath);
166
+ }
167
+ }
168
+ chunks.Add(entryPath);
169
+
170
+ foreach (var scriptPath in chunks)
126
171
  {
127
- CrashDiagnostics.WriteExceptionReport("RuntimeHost.RunMainScript", ex, "EntryScript=" + entryPath);
128
- System.Diagnostics.Debug.WriteLine($"[NativeScript Runtime] Script execution failed ({entryPath}): {ex}");
129
- throw;
172
+ var script = File.ReadAllText(Path.GetFullPath(scriptPath));
173
+ try
174
+ {
175
+ runtime_runscript(_runtime, script, Path.GetFileName(scriptPath));
176
+ }
177
+ catch (Exception ex)
178
+ {
179
+ CrashDiagnostics.WriteExceptionReport("RuntimeHost.RunMainScript", ex, "Script=" + scriptPath);
180
+ System.Diagnostics.Debug.WriteLine($"[NativeScript Runtime] Script execution failed ({scriptPath}): {ex}");
181
+ throw;
182
+ }
130
183
  }
131
184
  }
132
185
 
@@ -139,30 +192,65 @@ namespace __PROJECT_NAME__
139
192
  private static string ResolveEntryScriptPath()
140
193
  {
141
194
  var baseDir = AppContext.BaseDirectory;
142
- var defaultLower = Path.Combine(baseDir, "app", "main.js");
143
- var defaultUpper = Path.Combine(baseDir, "App", "main.js");
144
- var packageJsonPath = Path.Combine(baseDir, "package.json");
195
+ // EXE lives in <project>/bin/; webpack bundle lives in <project>/app/.
196
+ var parentDir = Path.GetDirectoryName(
197
+ baseDir.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar))
198
+ ?? baseDir;
199
+
200
+ // Candidate app directories: sibling of bin/ first, then directly under baseDir.
201
+ var appDirCandidates = new[]
202
+ {
203
+ Path.Combine(parentDir, "app"),
204
+ Path.Combine(parentDir, "App"),
205
+ Path.Combine(baseDir, "app"),
206
+ Path.Combine(baseDir, "App"),
207
+ };
208
+
209
+ string packageJsonPath = null;
210
+ string resolvedBaseDir = null;
211
+ foreach (var dir in appDirCandidates)
212
+ {
213
+ var candidate = Path.Combine(dir, "package.json");
214
+ if (File.Exists(candidate))
215
+ {
216
+ packageJsonPath = candidate;
217
+ resolvedBaseDir = dir;
218
+ break;
219
+ }
220
+ }
221
+
222
+ // Also accept package.json at the project root (parent of bin/).
223
+ if (packageJsonPath == null && File.Exists(Path.Combine(parentDir, "package.json")))
224
+ {
225
+ packageJsonPath = Path.Combine(parentDir, "package.json");
226
+ resolvedBaseDir = parentDir;
227
+ }
145
228
 
146
- if (!File.Exists(packageJsonPath))
147
- return File.Exists(defaultLower) ? defaultLower : defaultUpper;
229
+ string Fallback() =>
230
+ appDirCandidates
231
+ .SelectMany(d => new[] { Path.Combine(d, "bundle.js"), Path.Combine(d, "bundle.mjs") })
232
+ .FirstOrDefault(File.Exists);
233
+
234
+ if (packageJsonPath == null)
235
+ return Fallback();
148
236
 
149
237
  try
150
238
  {
151
239
  var config = ParsePackageConfig(packageJsonPath);
152
240
  if (!string.IsNullOrWhiteSpace(config.WindowsMain))
153
241
  {
154
- var p = ResolveScriptPath(baseDir, config.WindowsMain);
242
+ var p = ResolveScriptPath(resolvedBaseDir, config.WindowsMain);
155
243
  if (p != null) return p;
156
244
  }
157
245
  if (!string.IsNullOrWhiteSpace(config.Main))
158
246
  {
159
- var p = ResolveScriptPath(baseDir, config.Main);
247
+ var p = ResolveScriptPath(resolvedBaseDir, config.Main);
160
248
  if (p != null) return p;
161
249
  }
162
250
  }
163
251
  catch { }
164
252
 
165
- return File.Exists(defaultLower) ? defaultLower : defaultUpper;
253
+ return Fallback();
166
254
  }
167
255
 
168
256
  private static RuntimePackageConfig ParsePackageConfig(string packageJsonPath)
@@ -181,12 +269,34 @@ namespace __PROJECT_NAME__
181
269
  {
182
270
  if (string.IsNullOrWhiteSpace(scriptPath)) return null;
183
271
  var normalized = scriptPath.Replace('/', Path.DirectorySeparatorChar);
184
- var direct = Path.IsPathRooted(normalized) ? normalized : Path.Combine(baseDir, normalized);
185
- if (File.Exists(direct)) return direct;
186
- var appLower = Path.Combine(baseDir, "app", normalized);
187
- if (File.Exists(appLower)) return appLower;
188
- var appUpper = Path.Combine(baseDir, "App", normalized);
189
- return File.Exists(appUpper) ? appUpper : null;
272
+ foreach (var candidate in new[] { normalized, normalized + ".js", normalized + ".mjs" })
273
+ {
274
+ var direct = Path.IsPathRooted(candidate) ? candidate : Path.Combine(baseDir, candidate);
275
+ if (File.Exists(direct)) return direct;
276
+ var appLower = Path.Combine(baseDir, "app", candidate);
277
+ if (File.Exists(appLower)) return appLower;
278
+ var appUpper = Path.Combine(baseDir, "App", candidate);
279
+ if (File.Exists(appUpper)) return appUpper;
280
+ }
281
+ return null;
282
+ }
283
+
284
+ /// Returns the last JavaScript error (message + stack trace) captured during
285
+ /// script execution, or null if no error was recorded since the last call.
286
+ public string GetLastJsError()
287
+ {
288
+ if (!_initialized) return null;
289
+ IntPtr ptr = IntPtr.Zero;
290
+ try
291
+ {
292
+ ptr = runtime_get_last_js_error();
293
+ return ptr == IntPtr.Zero ? null : Marshal.PtrToStringUTF8(ptr);
294
+ }
295
+ catch { return null; }
296
+ finally
297
+ {
298
+ if (ptr != IntPtr.Zero) runtime_free_js_error(ptr);
299
+ }
190
300
  }
191
301
 
192
302
  public void Dispose()
@@ -25,6 +25,10 @@
25
25
  <NSLibPlatform Condition="'$(Platform)' != ''">$(Platform)</NSLibPlatform>
26
26
  <NSLibPlatform Condition="'$(NSLibPlatform)' == '' and '$(RuntimeIdentifier)' != ''">$([System.Text.RegularExpressions.Regex]::Replace('$(RuntimeIdentifier)','^win-',''))</NSLibPlatform>
27
27
  <NSLibPlatform Condition="'$(NSLibPlatform)' == ''">x64</NSLibPlatform>
28
+ <!-- Windows SDK .NET runtime pack location (resolved from NuGet cache) -->
29
+ <_WinSdkNetRefPkg>$(NuGetPackageRoot)microsoft.windows.sdk.net.ref</_WinSdkNetRefPkg>
30
+ <_WinSdkNetRefVer Condition="Exists('$(_WinSdkNetRefPkg)\10.0.26100.57')">10.0.26100.57</_WinSdkNetRefVer>
31
+ <_WinSdkNetRefLib Condition="'$(_WinSdkNetRefVer)' != ''">$(_WinSdkNetRefPkg)\$(_WinSdkNetRefVer)\lib\net8.0</_WinSdkNetRefLib>
28
32
  </PropertyGroup>
29
33
 
30
34
  <ItemGroup>
@@ -41,6 +45,29 @@
41
45
  <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
42
46
  </Content>
43
47
 
48
+ <!-- UWP logo/splash assets required for Add-AppxPackage -Register -->
49
+ <Content Include="Assets\**\*" Condition="Exists('Assets')">
50
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
51
+ </Content>
52
+
53
+ <!-- Windows SDK .NET runtime pack assemblies — required when the dotnet workload
54
+ is not installed; copied from the NuGet package cache. -->
55
+ <Content Include="$(_WinSdkNetRefLib)\Microsoft.Windows.UI.Xaml.dll"
56
+ Condition="'$(_WinSdkNetRefLib)' != '' and Exists('$(_WinSdkNetRefLib)\Microsoft.Windows.UI.Xaml.dll')">
57
+ <Link>Microsoft.Windows.UI.Xaml.dll</Link>
58
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
59
+ </Content>
60
+ <Content Include="$(_WinSdkNetRefLib)\WinRT.Runtime.dll"
61
+ Condition="'$(_WinSdkNetRefLib)' != '' and Exists('$(_WinSdkNetRefLib)\WinRT.Runtime.dll')">
62
+ <Link>WinRT.Runtime.dll</Link>
63
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
64
+ </Content>
65
+ <Content Include="$(_WinSdkNetRefLib)\Microsoft.Windows.SDK.NET.dll"
66
+ Condition="'$(_WinSdkNetRefLib)' != '' and Exists('$(_WinSdkNetRefLib)\Microsoft.Windows.SDK.NET.dll')">
67
+ <Link>Microsoft.Windows.SDK.NET.dll</Link>
68
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
69
+ </Content>
70
+
44
71
  <!-- Debug: devtools-enabled runtime DLL (platform fallback via NSLibPlatform) -->
45
72
  <Content Include="$(NSWindowsRoot)\libs\devtools\$(NSLibPlatform)\nativescript.dll"
46
73
  Condition="'$(Configuration)' == 'Debug' and Exists('$(NSWindowsRoot)\libs\devtools\$(NSLibPlatform)\nativescript.dll')">
@@ -55,21 +82,12 @@
55
82
  <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
56
83
  </Content>
57
84
 
58
- <!-- .NET bridge — published on first build by the target below; enables ns.dotnet.*
59
- APIs from JavaScript. Must land at dotnet-bridge\publish\ relative to the
60
- executable so the Rust runtime can locate DotNetBridge.dll via
61
- AppContext.BaseDirectory. Condition guards against a missing source tree. -->
62
- <Content Include="$(NSWindowsRoot)\dotnet-bridge\publish\**\*"
63
- Condition="Exists('$(NSWindowsRoot)\dotnet-bridge\publish')">
64
- <Link>dotnet-bridge\publish\%(RecursiveDir)%(Filename)%(Extension)</Link>
65
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
66
- </Content>
67
85
  </ItemGroup>
68
86
 
69
87
  <!--
70
- Publishes DotNetBridge.dll before the app build so the Content glob above has
71
- something to pick up. Inputs/Outputs make this incremental: MSBuild skips the
72
- Exec entirely when DotNetBridge.dll is newer than both source files.
88
+ Publishes DotNetBridge.dll before the app build.
89
+ Inputs/Outputs make this incremental: MSBuild skips the Exec when DotNetBridge.dll
90
+ is already newer than both source files.
73
91
  -->
74
92
  <Target Name="PublishDotNetBridge"
75
93
  BeforeTargets="Build"
@@ -81,6 +99,25 @@
81
99
  <Exec Command="dotnet publish &quot;$(NSWindowsRoot)\dotnet-bridge\DotNetBridge.csproj&quot; -c Release -o &quot;$(NSWindowsRoot)\dotnet-bridge\publish&quot; --no-self-contained /nologo" />
82
100
  </Target>
83
101
 
102
+ <!--
103
+ Copies the published bridge files to the output directory AFTER PublishDotNetBridge
104
+ runs, avoiding the MSBuild evaluation-order problem where Condition="Exists(...)"
105
+ on a static Content item is checked before the publish target creates the directory.
106
+ Must land at dotnet-bridge\publish\ relative to the EXE so the Rust runtime can
107
+ locate DotNetBridge.dll via AppContext.BaseDirectory.
108
+ -->
109
+ <Target Name="CopyDotNetBridge"
110
+ AfterTargets="PublishDotNetBridge"
111
+ BeforeTargets="Build"
112
+ Condition="Exists('$(NSWindowsRoot)\dotnet-bridge\publish')">
113
+ <ItemGroup>
114
+ <_DotNetBridgeFiles Include="$(NSWindowsRoot)\dotnet-bridge\publish\**\*" />
115
+ </ItemGroup>
116
+ <Copy SourceFiles="@(_DotNetBridgeFiles)"
117
+ DestinationFiles="@(_DotNetBridgeFiles->'$(OutDir)dotnet-bridge\publish\%(RecursiveDir)%(Filename)%(Extension)')"
118
+ SkipUnchangedFiles="true" />
119
+ </Target>
120
+
84
121
  <!-- Allow project-level Windows overrides: place App_Resources\Windows\app.csproj to extend/override generated settings -->
85
122
  <Import Project="App_Resources\Windows\app.csproj" Condition="Exists('App_Resources\Windows\app.csproj')" />
86
123
 
@@ -0,0 +1,124 @@
1
+ using System;
2
+ using System.Buffers;
3
+ using System.Buffers.Binary;
4
+ using System.Text;
5
+
6
+ namespace NativeScriptBridge;
7
+
8
+ internal readonly struct HandleRef(int id)
9
+ {
10
+ public readonly int Id = id;
11
+ }
12
+
13
+ internal ref struct BinReader(ReadOnlySpan<byte> buf)
14
+ {
15
+ private readonly ReadOnlySpan<byte> _buf = buf;
16
+ private int _pos = 0;
17
+
18
+ public byte ReadByte() => _buf[_pos++];
19
+
20
+ public int ReadI32()
21
+ {
22
+ var v = BinaryPrimitives.ReadInt32LittleEndian(_buf[_pos..]);
23
+ _pos += 4;
24
+ return v;
25
+ }
26
+
27
+ public ushort ReadU16()
28
+ {
29
+ var v = BinaryPrimitives.ReadUInt16LittleEndian(_buf[_pos..]);
30
+ _pos += 2;
31
+ return v;
32
+ }
33
+
34
+ public double ReadF64()
35
+ {
36
+ var v = BinaryPrimitives.ReadDoubleLittleEndian(_buf[_pos..]);
37
+ _pos += 8;
38
+ return v;
39
+ }
40
+
41
+ public string ReadString16()
42
+ {
43
+ var len = ReadU16();
44
+ var s = Encoding.UTF8.GetString(_buf.Slice(_pos, len));
45
+ _pos += len;
46
+ // Intern so repeated method/type names reuse the same heap string.
47
+ // Eliminates the allocation on every subsequent warm-path call.
48
+ return string.Intern(s);
49
+ }
50
+
51
+ public object?[] ReadArgs()
52
+ {
53
+ var count = ReadByte();
54
+ if (count == 0) return [];
55
+ var args = new object?[count];
56
+ for (int i = 0; i < count; i++)
57
+ {
58
+ var tag = ReadByte();
59
+ args[i] = tag switch
60
+ {
61
+ 0x00 => null,
62
+ 0x01 => (object)false,
63
+ 0x02 => (object)true,
64
+ 0x03 => (object)ReadI32(),
65
+ 0x04 => (object)ReadF64(),
66
+ 0x05 => (object)ReadString16(),
67
+ 0x06 => (object)new HandleRef(ReadI32()),
68
+ _ => null,
69
+ };
70
+ }
71
+ return args;
72
+ }
73
+ }
74
+
75
+ internal ref struct BinWriter(ArrayBufferWriter<byte> buf)
76
+ {
77
+ private readonly ArrayBufferWriter<byte> _buf = buf;
78
+
79
+ public void WriteByte(byte b)
80
+ {
81
+ _buf.GetSpan(1)[0] = b;
82
+ _buf.Advance(1);
83
+ }
84
+
85
+ public void WriteI32(int v)
86
+ {
87
+ BinaryPrimitives.WriteInt32LittleEndian(_buf.GetSpan(4), v);
88
+ _buf.Advance(4);
89
+ }
90
+
91
+ public void WriteU16(ushort v)
92
+ {
93
+ BinaryPrimitives.WriteUInt16LittleEndian(_buf.GetSpan(2), v);
94
+ _buf.Advance(2);
95
+ }
96
+
97
+ public void WriteU32(uint v)
98
+ {
99
+ BinaryPrimitives.WriteUInt32LittleEndian(_buf.GetSpan(4), v);
100
+ _buf.Advance(4);
101
+ }
102
+
103
+ public void WriteF64(double v)
104
+ {
105
+ BinaryPrimitives.WriteDoubleLittleEndian(_buf.GetSpan(8), v);
106
+ _buf.Advance(8);
107
+ }
108
+
109
+ public void WriteString32(ReadOnlySpan<char> s)
110
+ {
111
+ var byteCount = Encoding.UTF8.GetByteCount(s);
112
+ WriteU32((uint)byteCount);
113
+ Encoding.UTF8.GetBytes(s, _buf.GetSpan(byteCount));
114
+ _buf.Advance(byteCount);
115
+ }
116
+
117
+ public void WriteString16(ReadOnlySpan<char> s)
118
+ {
119
+ var byteCount = Encoding.UTF8.GetByteCount(s);
120
+ WriteU16((ushort)byteCount);
121
+ Encoding.UTF8.GetBytes(s, _buf.GetSpan(byteCount));
122
+ _buf.Advance(byteCount);
123
+ }
124
+ }
@@ -0,0 +1,177 @@
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
+ s_handles.TryRemove(r.ReadI32(), out _);
39
+ return DispatchResult.Void;
40
+ }
41
+
42
+ if (op == 0x05) // members by handle
43
+ {
44
+ var h = r.ReadI32();
45
+ if (!s_handles.TryGetValue(h, out var obj))
46
+ throw new KeyNotFoundException($"Invalid handle {h}");
47
+ return BuildMembersResult(
48
+ obj?.GetType() ?? throw new InvalidOperationException("Handle is null"));
49
+ }
50
+
51
+ if (op == 0x06) // members by type
52
+ {
53
+ var typeName = r.ReadString16();
54
+ var assembly = r.ReadString16();
55
+ var type = ResolveType(NullIfEmpty(assembly), typeName)
56
+ ?? throw new TypeLoadException($"Type not found: {typeName} (assembly: {assembly})");
57
+ return BuildMembersResult(type);
58
+ }
59
+
60
+ if (op == 0x01) // instance call
61
+ {
62
+ var handle = r.ReadI32();
63
+ if (!s_handles.TryGetValue(handle, out var target))
64
+ throw new KeyNotFoundException($"Invalid handle {handle}");
65
+ var type = target?.GetType() ?? throw new InvalidOperationException("Handle is null");
66
+ var method = r.ReadString16();
67
+ var args = r.ReadArgs();
68
+ return DispatchCallBin(target, type, method, args, isStatic: false);
69
+ }
70
+
71
+ if (op == 0x09) // create JS delegate
72
+ {
73
+ var delTypeName = r.ReadString16(); // "" → System.Action
74
+ var callbackId = r.ReadI32();
75
+ return CreateJsDelegate(delTypeName, callbackId);
76
+ }
77
+
78
+ // Static ops: 0x02 = call, 0x03 = constructor
79
+ var typeNameS = r.ReadString16();
80
+ var assemblyS = r.ReadString16();
81
+ var typeS = ResolveType(NullIfEmpty(assemblyS), typeNameS)
82
+ ?? throw new TypeLoadException($"Type not found: {typeNameS} (assembly: {assemblyS})");
83
+
84
+ if (op == 0x03) // constructor
85
+ {
86
+ var args = r.ReadArgs();
87
+ var entry = GetCachedCtor(typeS, args.Length);
88
+ if (entry.Ctor is null)
89
+ throw new MissingMethodException(
90
+ $"No public ctor on {typeS.FullName} for {args.Length} args");
91
+ // ConstructorInfo.Invoke requires exact arg count — build a precise array.
92
+ return Box(entry.Ctor.Invoke(BuildArgsBinExact(args, entry.Parameters)));
93
+ }
94
+
95
+ // op == 0x02: static call
96
+ var methodS = r.ReadString16();
97
+ var argsS = r.ReadArgs();
98
+ return DispatchCallBin(null, typeS, methodS, argsS, isStatic: true);
99
+ }
100
+
101
+ private static DispatchResult DispatchCallBin(
102
+ object? target, Type type, string method, object?[] args, bool isStatic)
103
+ {
104
+ var flags = (isStatic ? BindingFlags.Static : BindingFlags.Instance) | BindingFlags.Public;
105
+
106
+ if (method.Length > 4
107
+ && method[0] == 'g' && method[1] == 'e' && method[2] == 't' && method[3] == '_')
108
+ {
109
+ var prop = GetCachedProp(type, method, 4, flags);
110
+ if (prop is not null) return Box(prop.GetValue(target));
111
+ }
112
+
113
+ if (method.Length > 4
114
+ && method[0] == 's' && method[1] == 'e' && method[2] == 't' && method[3] == '_'
115
+ && args.Length == 1)
116
+ {
117
+ var prop = GetCachedProp(type, method, 4, flags);
118
+ if (prop is not null)
119
+ {
120
+ prop.SetValue(target, CoerceBin(args[0], prop.PropertyType));
121
+ return DispatchResult.Void;
122
+ }
123
+ }
124
+
125
+ var entry = GetCachedMethod(type, method, args.Length, flags);
126
+ if (entry.Invoke is null)
127
+ throw new MissingMethodException(
128
+ $"Method '{method}' ({args.Length} args) not found on {type.FullName}");
129
+
130
+ var builtArgs = BuildArgsBin(args, entry.Parameters);
131
+ try { return Box(AwaitIfTask(entry.Invoke(target, builtArgs))); }
132
+ finally { if (builtArgs.Length > 0) ReturnArgs(builtArgs); }
133
+ }
134
+
135
+ private static object?[] BuildArgsBin(object?[] binArgs, ParameterInfo[] parameters)
136
+ {
137
+ if (parameters.Length == 0) return [];
138
+ var result = ArrayPool<object?>.Shared.Rent(parameters.Length);
139
+ for (int i = 0; i < parameters.Length && i < binArgs.Length; i++)
140
+ result[i] = CoerceBin(binArgs[i], parameters[i].ParameterType);
141
+ return result;
142
+ }
143
+
144
+ private static object?[] BuildArgsBinExact(object?[] binArgs, ParameterInfo[] parameters)
145
+ {
146
+ if (parameters.Length == 0) return [];
147
+ var result = new object?[parameters.Length];
148
+ for (int i = 0; i < parameters.Length && i < binArgs.Length; i++)
149
+ result[i] = CoerceBin(binArgs[i], parameters[i].ParameterType);
150
+ return result;
151
+ }
152
+
153
+ private static object? CoerceBin(object? value, Type targetType)
154
+ {
155
+ if (value is null) return null;
156
+ if (value is HandleRef hr)
157
+ {
158
+ s_handles.TryGetValue(hr.Id, out var obj);
159
+ return obj;
160
+ }
161
+ if (value.GetType() == targetType) return value;
162
+ try { return Convert.ChangeType(value, targetType); }
163
+ catch { return value; }
164
+ }
165
+
166
+ private static string? NullIfEmpty(string s) => s.Length == 0 ? null : s;
167
+
168
+ private static unsafe void WriteBinError(string msg, byte** outPtr, int* outLen)
169
+ {
170
+ var msgBytes = System.Text.Encoding.UTF8.GetByteCount(msg);
171
+ var buf = new ArrayBufferWriter<byte>(5 + msgBytes);
172
+ var w = new BinWriter(buf);
173
+ w.WriteByte(0xFF);
174
+ w.WriteString32(msg);
175
+ WriteUnmanaged(buf.WrittenSpan, outPtr, outLen);
176
+ }
177
+ }