@nativescript/windows 0.1.0-alpha.4 → 0.1.0-alpha.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build.ps1 +4 -2
- package/framework/__PROJECT_NAME__/App.xaml.cs +44 -7
- package/framework/__PROJECT_NAME__/Assets/SplashScreen.png +0 -0
- package/framework/__PROJECT_NAME__/Assets/Square150x150Logo.png +0 -0
- package/framework/__PROJECT_NAME__/Assets/Square44x44Logo.png +0 -0
- package/framework/__PROJECT_NAME__/Assets/StoreLogo.png +0 -0
- package/framework/__PROJECT_NAME__/Assets/Wide310x150Logo.png +0 -0
- package/framework/__PROJECT_NAME__/CrashDiagnostics.cs +249 -66
- package/framework/__PROJECT_NAME__/Package.appxmanifest +2 -5
- package/framework/__PROJECT_NAME__/RuntimeHost.cs +132 -22
- package/framework/__PROJECT_NAME__/__PROJECT_NAME__.csproj +49 -12
- package/framework/dotnet-bridge/BinaryProtocol.cs +124 -0
- package/framework/dotnet-bridge/Bridge.BinaryDispatch.cs +177 -0
- package/framework/dotnet-bridge/Bridge.Dispatch.cs +348 -0
- package/framework/dotnet-bridge/Bridge.JsDelegate.cs +100 -0
- package/framework/dotnet-bridge/Bridge.cs +44 -320
- package/framework/dotnet-bridge/DispatchTypes.cs +281 -0
- package/framework/libs/arm64/nativescript.dll +0 -0
- package/framework/libs/devtools/arm64/nativescript.dll +0 -0
- package/framework/libs/devtools/x64/nativescript.dll +0 -0
- package/framework/libs/x64/nativescript.dll +0 -0
- package/package.json +19 -19
|
@@ -1,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
|
-
|
|
121
|
-
try
|
|
151
|
+
if (entryPath == null)
|
|
122
152
|
{
|
|
123
|
-
|
|
153
|
+
System.Diagnostics.Debug.WriteLine("[NativeScript Runtime] No entry script found — bundle missing from app directory.");
|
|
154
|
+
return;
|
|
124
155
|
}
|
|
125
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
143
|
-
var
|
|
144
|
-
|
|
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
|
-
|
|
147
|
-
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
|
71
|
-
|
|
72
|
-
|
|
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 "$(NSWindowsRoot)\dotnet-bridge\DotNetBridge.csproj" -c Release -o "$(NSWindowsRoot)\dotnet-bridge\publish" --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}");
|
|
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}");
|
|
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
|
+
}
|