@nativescript/windows 0.1.0-alpha.1 → 0.1.0-alpha.11
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 +34 -1
- 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__/Package.appxmanifest +2 -5
- package/framework/__PROJECT_NAME__/RuntimeHost.cs +60 -11
- package/framework/__PROJECT_NAME__/__PROJECT_NAME__.csproj +62 -7
- package/framework/dotnet-bridge/Bridge.cs +353 -0
- package/framework/dotnet-bridge/DotNetBridge.csproj +20 -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 +3 -2
- package/framework/__PROJECT_NAME__/App/main.js +0 -12
package/build.ps1
CHANGED
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
param(
|
|
28
28
|
[switch]$SkipArm64,
|
|
29
29
|
[switch]$SkipRelease,
|
|
30
|
-
[switch]$SkipDevtools
|
|
30
|
+
[switch]$SkipDevtools,
|
|
31
|
+
[switch]$SkipDotnet
|
|
31
32
|
)
|
|
32
33
|
|
|
33
34
|
Set-StrictMode -Version Latest
|
|
@@ -69,6 +70,38 @@ function Build-Crate {
|
|
|
69
70
|
}
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
# ── DotNet Bridge ─────────────────────────────────────────────────────────────
|
|
74
|
+
# Copies the dotnet-bridge source (csproj + .cs files) into
|
|
75
|
+
# template/framework/dotnet-bridge/ so it ships inside the npm package.
|
|
76
|
+
# The actual `dotnet publish` is deferred to the app's MSBuild process —
|
|
77
|
+
# see the PublishDotNetBridge target in __PROJECT_NAME__.csproj.
|
|
78
|
+
|
|
79
|
+
if (-not $SkipDotnet) {
|
|
80
|
+
Write-Host "`n=== DotNet Bridge (source copy) ===" -ForegroundColor Cyan
|
|
81
|
+
$BridgeSrc = Join-Path $RepoRoot "dotnet-bridge"
|
|
82
|
+
$BridgeDest = Join-Path $ScriptDir "framework\dotnet-bridge"
|
|
83
|
+
|
|
84
|
+
# Wipe the destination so stale files don't linger between runs.
|
|
85
|
+
if (Test-Path $BridgeDest) { Remove-Item -Recurse -Force $BridgeDest }
|
|
86
|
+
$null = New-Item -ItemType Directory -Force -Path $BridgeDest
|
|
87
|
+
|
|
88
|
+
# Copy only source files; exclude build/publish artefacts.
|
|
89
|
+
Get-ChildItem -Path $BridgeSrc -Recurse |
|
|
90
|
+
Where-Object {
|
|
91
|
+
$rel = $_.FullName.Substring($BridgeSrc.Length + 1)
|
|
92
|
+
$rel -notmatch '^(bin|obj|publish)(\\|$)'
|
|
93
|
+
} |
|
|
94
|
+
ForEach-Object {
|
|
95
|
+
$dest = Join-Path $BridgeDest $_.FullName.Substring($BridgeSrc.Length + 1)
|
|
96
|
+
if ($_.PSIsContainer) {
|
|
97
|
+
$null = New-Item -ItemType Directory -Force -Path $dest
|
|
98
|
+
} else {
|
|
99
|
+
Copy-Item -Force $_.FullName $dest
|
|
100
|
+
Write-Host " copied $($_.FullName.Substring($BridgeSrc.Length + 1))"
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
72
105
|
# ── Targets ───────────────────────────────────────────────────────────────────
|
|
73
106
|
|
|
74
107
|
$Targets = @(
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -2,17 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
<Package
|
|
4
4
|
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
|
5
|
-
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
|
|
6
5
|
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
|
7
|
-
IgnorableNamespaces="uap
|
|
6
|
+
IgnorableNamespaces="uap">
|
|
8
7
|
|
|
9
8
|
<Identity
|
|
10
9
|
Name="__APP_IDENTIFIER__"
|
|
11
10
|
Publisher="CN=__PROJECT_NAME__"
|
|
12
11
|
Version="1.0.0.0" />
|
|
13
12
|
|
|
14
|
-
<mp:PhoneIdentity PhoneProductId="__APP_IDENTIFIER__" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
|
15
|
-
|
|
16
13
|
<Properties>
|
|
17
14
|
<DisplayName>__PROJECT_NAME__</DisplayName>
|
|
18
15
|
<PublisherDisplayName>__PROJECT_NAME__</PublisherDisplayName>
|
|
@@ -24,7 +21,7 @@
|
|
|
24
21
|
</Dependencies>
|
|
25
22
|
|
|
26
23
|
<Resources>
|
|
27
|
-
<Resource Language="
|
|
24
|
+
<Resource Language="en-US"/>
|
|
28
25
|
</Resources>
|
|
29
26
|
|
|
30
27
|
<Applications>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
using System;
|
|
2
2
|
using System.IO;
|
|
3
|
+
using System.Linq;
|
|
3
4
|
using System.Runtime.InteropServices;
|
|
4
5
|
using System.Text.Json;
|
|
5
6
|
|
|
@@ -38,10 +39,17 @@ namespace __PROJECT_NAME__
|
|
|
38
39
|
|
|
39
40
|
public string DevtoolsFrontendUrl { get; private set; }
|
|
40
41
|
|
|
42
|
+
private bool _devtoolsPumpAvailable = true;
|
|
43
|
+
|
|
41
44
|
public void PumpDevtools()
|
|
42
45
|
{
|
|
43
|
-
if (!_initialized) return;
|
|
46
|
+
if (!_initialized || !_devtoolsPumpAvailable) return;
|
|
44
47
|
try { runtime_devtools_pump(_runtime); }
|
|
48
|
+
catch (System.EntryPointNotFoundException)
|
|
49
|
+
{
|
|
50
|
+
// DLL built without devtools feature — stop trying.
|
|
51
|
+
_devtoolsPumpAvailable = false;
|
|
52
|
+
}
|
|
45
53
|
catch (Exception ex)
|
|
46
54
|
{
|
|
47
55
|
System.Diagnostics.Debug.WriteLine($"[NativeScript DevTools] Pump failed: {ex.Message}");
|
|
@@ -139,28 +147,63 @@ namespace __PROJECT_NAME__
|
|
|
139
147
|
private static string ResolveEntryScriptPath()
|
|
140
148
|
{
|
|
141
149
|
var baseDir = AppContext.BaseDirectory;
|
|
142
|
-
|
|
143
|
-
var
|
|
150
|
+
// EXE lives in <project>/bin/; webpack bundle lives in <project>/app/.
|
|
151
|
+
var parentDir = Path.GetDirectoryName(
|
|
152
|
+
baseDir.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar))
|
|
153
|
+
?? baseDir;
|
|
154
|
+
|
|
155
|
+
// Candidate app directories: sibling of bin/ first, then directly under baseDir.
|
|
156
|
+
var appDirCandidates = new[]
|
|
157
|
+
{
|
|
158
|
+
Path.Combine(parentDir, "app"),
|
|
159
|
+
Path.Combine(parentDir, "App"),
|
|
160
|
+
Path.Combine(baseDir, "app"),
|
|
161
|
+
Path.Combine(baseDir, "App"),
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
string packageJsonPath = null;
|
|
165
|
+
string resolvedBaseDir = null;
|
|
166
|
+
foreach (var dir in appDirCandidates)
|
|
167
|
+
{
|
|
168
|
+
var candidate = Path.Combine(dir, "package.json");
|
|
169
|
+
if (File.Exists(candidate))
|
|
170
|
+
{
|
|
171
|
+
packageJsonPath = candidate;
|
|
172
|
+
resolvedBaseDir = dir;
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Also accept package.json at the project root (parent of bin/).
|
|
178
|
+
if (packageJsonPath == null && File.Exists(Path.Combine(parentDir, "package.json")))
|
|
179
|
+
{
|
|
180
|
+
packageJsonPath = Path.Combine(parentDir, "package.json");
|
|
181
|
+
resolvedBaseDir = parentDir;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
string Fallback() =>
|
|
185
|
+
appDirCandidates.Select(d => Path.Combine(d, "bundle.js")).FirstOrDefault(File.Exists);
|
|
144
186
|
|
|
145
|
-
if (
|
|
187
|
+
if (packageJsonPath == null)
|
|
188
|
+
return Fallback();
|
|
146
189
|
|
|
147
190
|
try
|
|
148
191
|
{
|
|
149
192
|
var config = ParsePackageConfig(packageJsonPath);
|
|
150
193
|
if (!string.IsNullOrWhiteSpace(config.WindowsMain))
|
|
151
194
|
{
|
|
152
|
-
var p = ResolveScriptPath(
|
|
195
|
+
var p = ResolveScriptPath(resolvedBaseDir, config.WindowsMain);
|
|
153
196
|
if (p != null) return p;
|
|
154
197
|
}
|
|
155
198
|
if (!string.IsNullOrWhiteSpace(config.Main))
|
|
156
199
|
{
|
|
157
|
-
var p = ResolveScriptPath(
|
|
200
|
+
var p = ResolveScriptPath(resolvedBaseDir, config.Main);
|
|
158
201
|
if (p != null) return p;
|
|
159
202
|
}
|
|
160
203
|
}
|
|
161
204
|
catch { }
|
|
162
205
|
|
|
163
|
-
return
|
|
206
|
+
return Fallback();
|
|
164
207
|
}
|
|
165
208
|
|
|
166
209
|
private static RuntimePackageConfig ParsePackageConfig(string packageJsonPath)
|
|
@@ -179,10 +222,16 @@ namespace __PROJECT_NAME__
|
|
|
179
222
|
{
|
|
180
223
|
if (string.IsNullOrWhiteSpace(scriptPath)) return null;
|
|
181
224
|
var normalized = scriptPath.Replace('/', Path.DirectorySeparatorChar);
|
|
182
|
-
var
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
225
|
+
foreach (var candidate in new[] { normalized, normalized + ".js" })
|
|
226
|
+
{
|
|
227
|
+
var direct = Path.IsPathRooted(candidate) ? candidate : Path.Combine(baseDir, candidate);
|
|
228
|
+
if (File.Exists(direct)) return direct;
|
|
229
|
+
var appLower = Path.Combine(baseDir, "app", candidate);
|
|
230
|
+
if (File.Exists(appLower)) return appLower;
|
|
231
|
+
var appUpper = Path.Combine(baseDir, "App", candidate);
|
|
232
|
+
if (File.Exists(appUpper)) return appUpper;
|
|
233
|
+
}
|
|
234
|
+
return null;
|
|
186
235
|
}
|
|
187
236
|
|
|
188
237
|
public void Dispose()
|
|
@@ -19,12 +19,20 @@
|
|
|
19
19
|
<NSWindowsRoot>$(MSBuildProjectDirectory)\..</NSWindowsRoot>
|
|
20
20
|
</PropertyGroup>
|
|
21
21
|
|
|
22
|
+
<PropertyGroup>
|
|
23
|
+
<!-- Derived library platform for selecting prebuilt native libs.
|
|
24
|
+
Prefer $(Platform); fall back to trimming 'win-' from $(RuntimeIdentifier); default to x64. -->
|
|
25
|
+
<NSLibPlatform Condition="'$(Platform)' != ''">$(Platform)</NSLibPlatform>
|
|
26
|
+
<NSLibPlatform Condition="'$(NSLibPlatform)' == '' and '$(RuntimeIdentifier)' != ''">$([System.Text.RegularExpressions.Regex]::Replace('$(RuntimeIdentifier)','^win-',''))</NSLibPlatform>
|
|
27
|
+
<NSLibPlatform Condition="'$(NSLibPlatform)' == ''">x64</NSLibPlatform>
|
|
28
|
+
</PropertyGroup>
|
|
29
|
+
|
|
22
30
|
<ItemGroup>
|
|
23
31
|
<AppxManifest Include="Package.appxmanifest" />
|
|
24
32
|
<Content Include="Properties\Default.rd.xml" />
|
|
25
33
|
|
|
26
34
|
<!-- App JS/resource files — copied to the output directory on every build -->
|
|
27
|
-
<Content Include="
|
|
35
|
+
<Content Include="app\**\*">
|
|
28
36
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
29
37
|
</Content>
|
|
30
38
|
|
|
@@ -33,18 +41,65 @@
|
|
|
33
41
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
34
42
|
</Content>
|
|
35
43
|
|
|
36
|
-
<!--
|
|
37
|
-
<Content Include="
|
|
38
|
-
|
|
44
|
+
<!-- UWP logo/splash assets required for Add-AppxPackage -Register -->
|
|
45
|
+
<Content Include="Assets\**\*" Condition="Exists('Assets')">
|
|
46
|
+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
47
|
+
</Content>
|
|
48
|
+
|
|
49
|
+
<!-- Debug: devtools-enabled runtime DLL (platform fallback via NSLibPlatform) -->
|
|
50
|
+
<Content Include="$(NSWindowsRoot)\libs\devtools\$(NSLibPlatform)\nativescript.dll"
|
|
51
|
+
Condition="'$(Configuration)' == 'Debug' and Exists('$(NSWindowsRoot)\libs\devtools\$(NSLibPlatform)\nativescript.dll')">
|
|
39
52
|
<Link>nativescript.dll</Link>
|
|
40
53
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
41
54
|
</Content>
|
|
42
55
|
|
|
43
|
-
<!-- Release: production runtime DLL -->
|
|
44
|
-
<Content Include="$(NSWindowsRoot)\libs\$(
|
|
45
|
-
Condition="'$(Configuration)' != 'Debug'">
|
|
56
|
+
<!-- Release: production runtime DLL (platform fallback via NSLibPlatform) -->
|
|
57
|
+
<Content Include="$(NSWindowsRoot)\libs\$(NSLibPlatform)\nativescript.dll"
|
|
58
|
+
Condition="'$(Configuration)' != 'Debug' and Exists('$(NSWindowsRoot)\libs\$(NSLibPlatform)\nativescript.dll')">
|
|
46
59
|
<Link>nativescript.dll</Link>
|
|
47
60
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
48
61
|
</Content>
|
|
62
|
+
|
|
49
63
|
</ItemGroup>
|
|
64
|
+
|
|
65
|
+
<!--
|
|
66
|
+
Publishes DotNetBridge.dll before the app build.
|
|
67
|
+
Inputs/Outputs make this incremental: MSBuild skips the Exec when DotNetBridge.dll
|
|
68
|
+
is already newer than both source files.
|
|
69
|
+
-->
|
|
70
|
+
<Target Name="PublishDotNetBridge"
|
|
71
|
+
BeforeTargets="Build"
|
|
72
|
+
Inputs="$(NSWindowsRoot)\dotnet-bridge\DotNetBridge.csproj;
|
|
73
|
+
$(NSWindowsRoot)\dotnet-bridge\Bridge.cs"
|
|
74
|
+
Outputs="$(NSWindowsRoot)\dotnet-bridge\publish\DotNetBridge.dll"
|
|
75
|
+
Condition="Exists('$(NSWindowsRoot)\dotnet-bridge\DotNetBridge.csproj')">
|
|
76
|
+
<Message Text="[NativeScript] Publishing DotNetBridge..." Importance="high" />
|
|
77
|
+
<Exec Command="dotnet publish "$(NSWindowsRoot)\dotnet-bridge\DotNetBridge.csproj" -c Release -o "$(NSWindowsRoot)\dotnet-bridge\publish" --no-self-contained /nologo" />
|
|
78
|
+
</Target>
|
|
79
|
+
|
|
80
|
+
<!--
|
|
81
|
+
Copies the published bridge files to the output directory AFTER PublishDotNetBridge
|
|
82
|
+
runs, avoiding the MSBuild evaluation-order problem where Condition="Exists(...)"
|
|
83
|
+
on a static Content item is checked before the publish target creates the directory.
|
|
84
|
+
Must land at dotnet-bridge\publish\ relative to the EXE so the Rust runtime can
|
|
85
|
+
locate DotNetBridge.dll via AppContext.BaseDirectory.
|
|
86
|
+
-->
|
|
87
|
+
<Target Name="CopyDotNetBridge"
|
|
88
|
+
AfterTargets="PublishDotNetBridge"
|
|
89
|
+
BeforeTargets="Build"
|
|
90
|
+
Condition="Exists('$(NSWindowsRoot)\dotnet-bridge\publish')">
|
|
91
|
+
<ItemGroup>
|
|
92
|
+
<_DotNetBridgeFiles Include="$(NSWindowsRoot)\dotnet-bridge\publish\**\*" />
|
|
93
|
+
</ItemGroup>
|
|
94
|
+
<Copy SourceFiles="@(_DotNetBridgeFiles)"
|
|
95
|
+
DestinationFiles="@(_DotNetBridgeFiles->'$(OutDir)dotnet-bridge\publish\%(RecursiveDir)%(Filename)%(Extension)')"
|
|
96
|
+
SkipUnchangedFiles="true" />
|
|
97
|
+
</Target>
|
|
98
|
+
|
|
99
|
+
<!-- Allow project-level Windows overrides: place App_Resources\Windows\app.csproj to extend/override generated settings -->
|
|
100
|
+
<Import Project="App_Resources\Windows\app.csproj" Condition="Exists('App_Resources\Windows\app.csproj')" />
|
|
101
|
+
|
|
102
|
+
<!-- CLI-generated plugin imports (managed by the CLI during prepare) -->
|
|
103
|
+
<Import Project="plugins\Plugins.props" Condition="Exists('plugins\Plugins.props')" />
|
|
104
|
+
<Import Project="plugins\Plugins.targets" Condition="Exists('plugins\Plugins.targets')" />
|
|
50
105
|
</Project>
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Collections;
|
|
3
|
+
using System.Collections.Concurrent;
|
|
4
|
+
using System.Collections.Generic;
|
|
5
|
+
using System.Linq;
|
|
6
|
+
using System.Reflection;
|
|
7
|
+
using System.Runtime.InteropServices;
|
|
8
|
+
using System.Text.Json;
|
|
9
|
+
using System.Text.Json.Nodes;
|
|
10
|
+
using System.Threading;
|
|
11
|
+
using System.Threading.Tasks;
|
|
12
|
+
|
|
13
|
+
namespace NativeScriptBridge;
|
|
14
|
+
|
|
15
|
+
/// <summary>
|
|
16
|
+
/// Reflection-based .NET dispatch bridge — UTF-8 ABI edition.
|
|
17
|
+
///
|
|
18
|
+
/// Invoke() receives a UTF-8 byte slice (no string allocation on the Rust side),
|
|
19
|
+
/// dispatches via reflection, and serialises the response directly to a UTF-8
|
|
20
|
+
/// byte array via JsonSerializer.SerializeToUtf8Bytes(). This removes the
|
|
21
|
+
/// UTF-16 encode/decode round-trip of the original char* ABI.
|
|
22
|
+
///
|
|
23
|
+
/// Request JSON schema
|
|
24
|
+
/// -------------------
|
|
25
|
+
/// Static call: { "assembly": "System", "typeName": "System.Diagnostics.Stopwatch", "method": "StartNew", "args": [] }
|
|
26
|
+
/// Constructor: { "assembly": "System", "typeName": "System.Text.StringBuilder", "method": ".ctor", "args": [128] }
|
|
27
|
+
/// Instance call: { "handle": 3, "method": "Stop", "args": [] }
|
|
28
|
+
/// Property get: { "assembly": "...", "typeName": "...", "method": "get_Now", "args": [] }
|
|
29
|
+
/// Property set: { "handle": 3, "method": "set_IsEnabled", "args": [true] }
|
|
30
|
+
/// Release: { "handle": 3, "method": "__release", "args": [] }
|
|
31
|
+
///
|
|
32
|
+
/// Response JSON schema
|
|
33
|
+
/// --------------------
|
|
34
|
+
/// Primitive / string: { "result": 42.5 }
|
|
35
|
+
/// Managed object: { "result": { "__handle": 7, "__type": "System.Diagnostics.Stopwatch" } }
|
|
36
|
+
/// Array / enumerable: { "result": [1, 2, 3] }
|
|
37
|
+
/// Error: { "error": "Method not found" }
|
|
38
|
+
/// </summary>
|
|
39
|
+
public static class Bridge
|
|
40
|
+
{
|
|
41
|
+
private static readonly ConcurrentDictionary<int, object?> s_handles = new();
|
|
42
|
+
private static int s_nextHandle;
|
|
43
|
+
|
|
44
|
+
private static readonly ConcurrentDictionary<string, Type?> s_typeCache = new();
|
|
45
|
+
private static readonly ConcurrentDictionary<string, MethodInfo?> s_methodCache = new();
|
|
46
|
+
private static readonly ConcurrentDictionary<string, PropertyInfo?> s_propertyCache = new();
|
|
47
|
+
|
|
48
|
+
// ── exported entry points ────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
/// Called by the Rust runtime. Request arrives as a raw UTF-8 byte slice;
|
|
51
|
+
/// response is written as a UTF-8 byte buffer allocated with Marshal.AllocHGlobal.
|
|
52
|
+
[UnmanagedCallersOnly(EntryPoint = "Invoke",
|
|
53
|
+
CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
|
|
54
|
+
public static unsafe int Invoke(
|
|
55
|
+
byte* requestPtr, int requestLen,
|
|
56
|
+
byte** responsePtr, int* responseLenPtr)
|
|
57
|
+
{
|
|
58
|
+
byte[] responseBytes;
|
|
59
|
+
try
|
|
60
|
+
{
|
|
61
|
+
var requestSpan = new ReadOnlySpan<byte>(requestPtr, requestLen);
|
|
62
|
+
var req = JsonSerializer.Deserialize<InvokeRequest>(requestSpan, JsonOptions.Default)!;
|
|
63
|
+
var result = Dispatch(req);
|
|
64
|
+
responseBytes = JsonSerializer.SerializeToUtf8Bytes(result, JsonOptions.Default);
|
|
65
|
+
}
|
|
66
|
+
catch (Exception ex)
|
|
67
|
+
{
|
|
68
|
+
var errResult = new InvokeResult(null, Unwrap(ex).Message);
|
|
69
|
+
responseBytes = JsonSerializer.SerializeToUtf8Bytes(errResult, JsonOptions.Default);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
WriteResponse(responseBytes, responsePtr, responseLenPtr);
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/// Frees the response buffer allocated by Invoke.
|
|
77
|
+
[UnmanagedCallersOnly(EntryPoint = "Free",
|
|
78
|
+
CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
|
|
79
|
+
public static unsafe void Free(byte* ptr)
|
|
80
|
+
{
|
|
81
|
+
if (ptr != null)
|
|
82
|
+
Marshal.FreeHGlobal((IntPtr)ptr);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ── dispatch ─────────────────────────────────────────────────────────────
|
|
86
|
+
|
|
87
|
+
private static InvokeResult Dispatch(InvokeRequest req)
|
|
88
|
+
{
|
|
89
|
+
if (req.Method == "__release" && req.Handle.HasValue)
|
|
90
|
+
{
|
|
91
|
+
s_handles.TryRemove(req.Handle.Value, out _);
|
|
92
|
+
return new InvokeResult(null, null);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Return reflection metadata so JS can distinguish methods from properties.
|
|
96
|
+
if (req.Method == "__members__")
|
|
97
|
+
{
|
|
98
|
+
var t = req.Handle.HasValue
|
|
99
|
+
? (s_handles.TryGetValue(req.Handle.Value, out var h) ? h?.GetType() : null)
|
|
100
|
+
: ResolveType(req.Assembly, req.TypeName);
|
|
101
|
+
if (t is null) return new InvokeResult(null, $"Type not found: {req.TypeName}");
|
|
102
|
+
var inst = BindingFlags.Public | BindingFlags.Instance;
|
|
103
|
+
var stat = BindingFlags.Public | BindingFlags.Static;
|
|
104
|
+
var members = new
|
|
105
|
+
{
|
|
106
|
+
methods = t.GetMethods(inst).Where(m => !m.IsSpecialName).Select(m => m.Name).Distinct().ToArray(),
|
|
107
|
+
properties = t.GetProperties(inst).Select(p => p.Name).Distinct().ToArray(),
|
|
108
|
+
staticMethods = t.GetMethods(stat).Where(m => !m.IsSpecialName).Select(m => m.Name).Distinct().ToArray(),
|
|
109
|
+
staticProperties = t.GetProperties(stat).Select(p => p.Name).Distinct().ToArray(),
|
|
110
|
+
};
|
|
111
|
+
return new InvokeResult(JsonSerializer.SerializeToElement(members, JsonOptions.Default), null);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
object? target = null;
|
|
115
|
+
Type? type = null;
|
|
116
|
+
|
|
117
|
+
if (req.Handle.HasValue)
|
|
118
|
+
{
|
|
119
|
+
if (!s_handles.TryGetValue(req.Handle.Value, out target))
|
|
120
|
+
return new InvokeResult(null, $"Invalid handle {req.Handle.Value}");
|
|
121
|
+
type = target?.GetType();
|
|
122
|
+
}
|
|
123
|
+
else
|
|
124
|
+
{
|
|
125
|
+
type = ResolveType(req.Assembly, req.TypeName);
|
|
126
|
+
if (type is null)
|
|
127
|
+
return new InvokeResult(null, $"Type not found: {req.TypeName}");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (type is null)
|
|
131
|
+
return new InvokeResult(null, "Cannot determine target type");
|
|
132
|
+
|
|
133
|
+
var method = req.Method ?? throw new ArgumentException("Method is required");
|
|
134
|
+
|
|
135
|
+
// ── Constructor ──────────────────────────────────────────────────────
|
|
136
|
+
if (method == ".ctor")
|
|
137
|
+
{
|
|
138
|
+
var argElements = req.Args ?? [];
|
|
139
|
+
var ctors = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance);
|
|
140
|
+
var ctor = ctors.FirstOrDefault(c => c.GetParameters().Length == argElements.Count)
|
|
141
|
+
?? ctors.FirstOrDefault();
|
|
142
|
+
if (ctor is null)
|
|
143
|
+
return new InvokeResult(null, $"No public constructor found on {type.FullName}");
|
|
144
|
+
var pars = ctor.GetParameters();
|
|
145
|
+
var ctorArgs = new object?[pars.Length];
|
|
146
|
+
for (int i = 0; i < pars.Length && i < argElements.Count; i++)
|
|
147
|
+
ctorArgs[i] = Coerce(argElements[i], pars[i].ParameterType);
|
|
148
|
+
return Box(ctor.Invoke(ctorArgs));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
var isStatic = target is null;
|
|
152
|
+
var flags = (isStatic ? BindingFlags.Static : BindingFlags.Instance)
|
|
153
|
+
| BindingFlags.Public;
|
|
154
|
+
|
|
155
|
+
// ── Property getter / setter ─────────────────────────────────────────
|
|
156
|
+
if (method.StartsWith("get_", StringComparison.Ordinal))
|
|
157
|
+
{
|
|
158
|
+
var prop = GetCachedProperty(type, method[4..], flags);
|
|
159
|
+
if (prop is not null)
|
|
160
|
+
return Box(prop.GetValue(target));
|
|
161
|
+
}
|
|
162
|
+
if (method.StartsWith("set_", StringComparison.Ordinal) && req.Args?.Count == 1)
|
|
163
|
+
{
|
|
164
|
+
var prop = GetCachedProperty(type, method[4..], flags);
|
|
165
|
+
if (prop is not null)
|
|
166
|
+
{
|
|
167
|
+
prop.SetValue(target, Coerce(req.Args[0], prop.PropertyType));
|
|
168
|
+
return new InvokeResult(null, null);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ── Regular method ───────────────────────────────────────────────────
|
|
173
|
+
var argElems = req.Args ?? [];
|
|
174
|
+
var mi = FindMethod(type, method, argElems.Count, flags);
|
|
175
|
+
if (mi is null)
|
|
176
|
+
return new InvokeResult(null,
|
|
177
|
+
$"Method '{method}' ({argElems.Count} args) not found on {type.FullName}");
|
|
178
|
+
|
|
179
|
+
var parameters = mi.GetParameters();
|
|
180
|
+
var callArgs = new object?[parameters.Length];
|
|
181
|
+
for (int i = 0; i < parameters.Length && i < argElems.Count; i++)
|
|
182
|
+
callArgs[i] = Coerce(argElems[i], parameters[i].ParameterType);
|
|
183
|
+
|
|
184
|
+
return Box(AwaitIfTask(mi.Invoke(target, callArgs)));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ── helpers ───────────────────────────────────────────────────────────────
|
|
188
|
+
|
|
189
|
+
private static object? AwaitIfTask(object? value)
|
|
190
|
+
{
|
|
191
|
+
if (value is null) return null;
|
|
192
|
+
var t = value.GetType();
|
|
193
|
+
|
|
194
|
+
if (value is Task task)
|
|
195
|
+
{
|
|
196
|
+
task.Wait();
|
|
197
|
+
if (task.IsFaulted)
|
|
198
|
+
throw task.Exception!.InnerException ?? task.Exception;
|
|
199
|
+
return t.GetProperty("Result")?.GetValue(value);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(ValueTask<>))
|
|
203
|
+
{
|
|
204
|
+
var innerTask = (Task)t.GetMethod("AsTask")!.Invoke(value, null)!;
|
|
205
|
+
innerTask.Wait();
|
|
206
|
+
if (innerTask.IsFaulted)
|
|
207
|
+
throw innerTask.Exception!.InnerException ?? innerTask.Exception;
|
|
208
|
+
return innerTask.GetType().GetProperty("Result")?.GetValue(innerTask);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (value is System.Threading.Tasks.ValueTask vt)
|
|
212
|
+
{
|
|
213
|
+
vt.AsTask().Wait();
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return value;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
private static Type? ResolveType(string? assemblyName, string? typeName)
|
|
221
|
+
{
|
|
222
|
+
if (string.IsNullOrEmpty(typeName)) return null;
|
|
223
|
+
var key = string.IsNullOrEmpty(assemblyName) ? typeName : $"{assemblyName}|{typeName}";
|
|
224
|
+
return s_typeCache.GetOrAdd(key, _ => ResolveTypeCore(assemblyName, typeName));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private static Type? ResolveTypeCore(string? assemblyName, string? typeName)
|
|
228
|
+
{
|
|
229
|
+
var fqn = string.IsNullOrEmpty(assemblyName) ? typeName! : $"{typeName}, {assemblyName}";
|
|
230
|
+
var t = Type.GetType(fqn);
|
|
231
|
+
if (t is not null) return t;
|
|
232
|
+
|
|
233
|
+
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
|
|
234
|
+
{
|
|
235
|
+
t = asm.GetType(typeName!);
|
|
236
|
+
if (t is not null) return t;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (!string.IsNullOrEmpty(assemblyName))
|
|
240
|
+
{
|
|
241
|
+
try
|
|
242
|
+
{
|
|
243
|
+
var asm = Assembly.Load(assemblyName);
|
|
244
|
+
t = asm.GetType(typeName!);
|
|
245
|
+
if (t is not null) return t;
|
|
246
|
+
}
|
|
247
|
+
catch { }
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
private static MethodInfo? FindMethod(Type type, string name, int argCount, BindingFlags flags)
|
|
254
|
+
{
|
|
255
|
+
var key = $"{type.FullName}|{name}|{argCount}|{(int)flags}";
|
|
256
|
+
return s_methodCache.GetOrAdd(key, _ => FindMethodCore(type, name, argCount, flags));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
private static MethodInfo? FindMethodCore(Type type, string name, int argCount, BindingFlags flags)
|
|
260
|
+
{
|
|
261
|
+
var match = Array.Find(
|
|
262
|
+
type.GetMethods(flags),
|
|
263
|
+
m => m.Name == name && !m.IsGenericMethod && m.GetParameters().Length == argCount);
|
|
264
|
+
if (match is not null) return match;
|
|
265
|
+
return type.GetMethod(name, flags);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private static PropertyInfo? GetCachedProperty(Type type, string name, BindingFlags flags)
|
|
269
|
+
{
|
|
270
|
+
var key = $"{type.FullName}|{name}|{(int)flags}";
|
|
271
|
+
return s_propertyCache.GetOrAdd(key, _ => type.GetProperty(name, flags));
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
private static object? Coerce(JsonElement el, Type targetType)
|
|
275
|
+
{
|
|
276
|
+
if (el.ValueKind == JsonValueKind.Null) return null;
|
|
277
|
+
if (el.ValueKind == JsonValueKind.Object && el.TryGetProperty("__handle", out var h))
|
|
278
|
+
{
|
|
279
|
+
s_handles.TryGetValue(h.GetInt32(), out var obj);
|
|
280
|
+
return obj;
|
|
281
|
+
}
|
|
282
|
+
return JsonSerializer.Deserialize(el.GetRawText(), targetType, JsonOptions.Default);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
private static InvokeResult Box(object? value)
|
|
286
|
+
{
|
|
287
|
+
if (value is null) return new InvokeResult(null, null);
|
|
288
|
+
|
|
289
|
+
var t = value.GetType();
|
|
290
|
+
|
|
291
|
+
if (t.IsPrimitive || t == typeof(string) || t == typeof(decimal)
|
|
292
|
+
|| t == typeof(DateTime) || t == typeof(DateTimeOffset)
|
|
293
|
+
|| t == typeof(TimeSpan) || t == typeof(Guid))
|
|
294
|
+
{
|
|
295
|
+
return new InvokeResult(
|
|
296
|
+
JsonSerializer.SerializeToElement(value, t, JsonOptions.Default), null);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Arrays and IEnumerable — serialise as JSON arrays.
|
|
300
|
+
if (t.IsArray || (t != typeof(string) && value is IEnumerable enumerable))
|
|
301
|
+
{
|
|
302
|
+
try
|
|
303
|
+
{
|
|
304
|
+
var list = new List<object?>();
|
|
305
|
+
foreach (var item in (IEnumerable)value) list.Add(item);
|
|
306
|
+
return new InvokeResult(
|
|
307
|
+
JsonSerializer.SerializeToElement(list, typeof(List<object?>), JsonOptions.Default),
|
|
308
|
+
null);
|
|
309
|
+
}
|
|
310
|
+
catch { }
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
var id = Interlocked.Increment(ref s_nextHandle);
|
|
314
|
+
s_handles[id] = value;
|
|
315
|
+
return new InvokeResult(
|
|
316
|
+
JsonSerializer.SerializeToElement(
|
|
317
|
+
new { __handle = id, __type = t.FullName }, JsonOptions.Default),
|
|
318
|
+
null);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
private static Exception Unwrap(Exception ex) =>
|
|
322
|
+
ex is TargetInvocationException { InnerException: { } inner } ? Unwrap(inner) : ex;
|
|
323
|
+
|
|
324
|
+
private static unsafe void WriteResponse(byte[] json, byte** responsePtr, int* responseLenPtr)
|
|
325
|
+
{
|
|
326
|
+
var ptr = (byte*)Marshal.AllocHGlobal(json.Length + 1);
|
|
327
|
+
Marshal.Copy(json, 0, (IntPtr)ptr, json.Length);
|
|
328
|
+
ptr[json.Length] = 0;
|
|
329
|
+
*responsePtr = ptr;
|
|
330
|
+
*responseLenPtr = json.Length;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// ── JSON types ────────────────────────────────────────────────────────────────
|
|
335
|
+
|
|
336
|
+
internal sealed record InvokeRequest(
|
|
337
|
+
string? Assembly,
|
|
338
|
+
string? TypeName,
|
|
339
|
+
string? Method,
|
|
340
|
+
int? Handle,
|
|
341
|
+
List<JsonElement>? Args
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
internal sealed record InvokeResult(JsonElement? Result, string? Error);
|
|
345
|
+
|
|
346
|
+
internal static class JsonOptions
|
|
347
|
+
{
|
|
348
|
+
internal static readonly JsonSerializerOptions Default = new()
|
|
349
|
+
{
|
|
350
|
+
PropertyNameCaseInsensitive = true,
|
|
351
|
+
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<Project Sdk="Microsoft.NET.Sdk">
|
|
2
|
+
|
|
3
|
+
<PropertyGroup>
|
|
4
|
+
<TargetFramework>net9.0</TargetFramework>
|
|
5
|
+
<AssemblyName>DotNetBridge</AssemblyName>
|
|
6
|
+
<RootNamespace>NativeScriptBridge</RootNamespace>
|
|
7
|
+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
|
8
|
+
<Nullable>enable</Nullable>
|
|
9
|
+
<Optimize>true</Optimize>
|
|
10
|
+
<!-- Regular class library — hostfxr loads it at runtime; no NativeAOT needed. -->
|
|
11
|
+
<OutputType>Library</OutputType>
|
|
12
|
+
<!-- Required: dotnet publish on a class library won't emit runtimeconfig.json by default. -->
|
|
13
|
+
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
|
|
14
|
+
<!-- Allow hostfxr to load this assembly under net10+ without a re-publish.
|
|
15
|
+
Default (Minor) only accepts patch updates within major 9, which fails when the
|
|
16
|
+
host process is already running on net10. LatestMajor accepts any version >= 9. -->
|
|
17
|
+
<RollForward>LatestMajor</RollForward>
|
|
18
|
+
</PropertyGroup>
|
|
19
|
+
|
|
20
|
+
</Project>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nativescript/windows",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.11",
|
|
4
4
|
"description": "NativeScript for using Windows v8",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"build": "pwsh -File build.ps1",
|
|
11
|
-
"build:x64": "pwsh -File build.ps1 -SkipArm64"
|
|
11
|
+
"build:x64": "pwsh -File build.ps1 -SkipArm64",
|
|
12
|
+
"build:nodotnet": "pwsh -File build.ps1 -SkipDotnet"
|
|
12
13
|
},
|
|
13
14
|
"keywords": ["nativescript", "windows", "winrt", "uwp"],
|
|
14
15
|
"license": "Apache-2.0",
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
const page = new Windows.UI.Xaml.Controls.Page();
|
|
2
|
-
const text = new Windows.UI.Xaml.Controls.TextBlock();
|
|
3
|
-
text.Text = "Hello from NativeScript on Windows!";
|
|
4
|
-
text.FontSize = 24;
|
|
5
|
-
text.HorizontalAlignment = Windows.UI.Xaml.HorizontalAlignment.Center;
|
|
6
|
-
text.VerticalAlignment = Windows.UI.Xaml.VerticalAlignment.Center;
|
|
7
|
-
|
|
8
|
-
const grid = new Windows.UI.Xaml.Controls.Grid();
|
|
9
|
-
grid.Children.Append(text);
|
|
10
|
-
page.Content = grid;
|
|
11
|
-
|
|
12
|
-
Windows.UI.Xaml.Window.Current.Content = page;
|