Skip to content

Commit 99bd8dc

Browse files
authored
WindowsDesktop targeting pack, WPF example project (#162)
1 parent 80f9cff commit 99bd8dc

10 files changed

Lines changed: 182 additions & 45 deletions

File tree

Docs/dynamic-invoke.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ For examples of this scenario, see
103103
.NET system assemblies can also be loaded and used. For example, import `System.Runtime.js` to
104104
get the core types, `System.Console.js` to get console APIs, etc. Type definitions for those
105105
two assembiles are generated by default; to generate typedefs for additional system assemblies,
106-
add items to the `NodeApiSystemReferenceAssemblies` MSBuild item-list in the project.
106+
add items to the `NodeApiSystemReferenceAssembly` MSBuild item-list in the project.
107107

108108
> :warning: Generic types and methods are not yet supported very well -- with the exception of
109109
generic collections which work great.

examples/wpf/.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/node_modules
2+
/pkg
3+
/obj
4+
/bin
5+
6+
*.d.ts
7+
package-lock.json

examples/wpf/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
2+
## Example: Calling WFP APIs from JS
3+
The `example.js` script dynamically loads the WPF .NET assemblies and shows a simple message box.
4+
5+
_**.NET events** are not yet projected to JS ([#59](https://github.com/microsoft/node-api-dotnet/issues/59)). WPF capabilities will be limited until that issue is resolved._
6+
7+
| Command | Explanation
8+
|----------------------------------|--------------------------------------------------
9+
| `dotnet pack ../..` | Build Node API .NET packages.
10+
| `dotnet build` | Generate type definitions for WPF assemblies.
11+
| `npm install` | Install `node-api-dotnet` npm package into the project.
12+
| `node example.js` | Run example JS code that calls WPF.

examples/wpf/example.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
// @ts-check
5+
6+
import dotnet from 'node-api-dotnet';
7+
import './bin/PresentationFramework.js';
8+
9+
dotnet.System.Windows.MessageBox.Show('Hello from JS!', "Example");

examples/wpf/package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"name": "node-api-dotnet-examples-semantic-kernel",
3+
"type": "module",
4+
"dependencies": {
5+
"node-api-dotnet": "file:../../out/pkg/node-api-dotnet"
6+
}
7+
}

examples/wpf/wpf.csproj

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net6.0-windows</TargetFramework>
5+
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
6+
<RestorePackagesPath>$(MSBuildThisFileDirectory)/pkg</RestorePackagesPath>
7+
<OutDir>bin</OutDir>
8+
<NodeApiAssemblyJSModuleType>esm</NodeApiAssemblyJSModuleType>
9+
<UseWPF>true</UseWPF>
10+
</PropertyGroup>
11+
12+
<ItemGroup>
13+
<NodeApiSystemReferenceAssembly Include="WindowsBase" />
14+
<NodeApiSystemReferenceAssembly Include="PresentationCore" />
15+
<NodeApiSystemReferenceAssembly Include="PresentationFramework" />
16+
<NodeApiSystemReferenceAssembly Include="UIAutomationTypes" />
17+
<NodeApiSystemReferenceAssembly Include="System.IO.Packaging" />
18+
<NodeApiSystemReferenceAssembly Include="System.Security.Cryptography.Xml" />
19+
<NodeApiSystemReferenceAssembly Include="System.Windows" />
20+
<NodeApiSystemReferenceAssembly Include="System.Windows.Extensions" />
21+
<NodeApiSystemReferenceAssembly Include="System.Windows.Input.Manipulations" />
22+
<NodeApiSystemReferenceAssembly Include="System.Xaml" />
23+
<PackageReference Include="Microsoft.JavaScript.NodeApi.Generator" Version="0.4.*-*" />
24+
</ItemGroup>
25+
26+
</Project>

src/NodeApi.DotNetHost/ManagedHost.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,17 @@ private Assembly LoadAssembly(string assemblyNameOrFilePath)
447447
assemblyFilePath = Path.Combine(
448448
Path.GetDirectoryName(typeof(object).Assembly.Location)!,
449449
assemblyFilePath + ".dll");
450+
451+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
452+
{
453+
// Also support loading Windows-specific system assemblies.
454+
string assemblyFilePath2 = assemblyFilePath.Replace(
455+
"Microsoft.NETCore.App", "Microsoft.WindowsDesktop.App");
456+
if (File.Exists(assemblyFilePath2))
457+
{
458+
assemblyFilePath = assemblyFilePath2;
459+
}
460+
}
450461
}
451462
else if (!Path.IsPathRooted(assemblyFilePath))
452463
{

src/NodeApi.Generator/NodeApi.Generator.targets

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
Outputs="$(TargetDir)$(NodeApiTypeDefinitionsFileName)"
3232
Condition=" '$(GenerateNodeApiTypeDefinitions)' == 'true' AND Exists('$(TargetPath)') "
3333
>
34-
<Exec Command="dotnet &quot;$(NodeApiGeneratorAssemblyPath)&quot; --assembly &quot;$(TargetPath)&quot; --reference &quot;@(ReferencePathWithRefAssemblies)&quot; --typedefs &quot;$(TargetDir)$(NodeApiTypeDefinitionsFileName)&quot; $(NodeApiTypeDefinitionsGeneratorOptions)"
34+
<Exec Command="dotnet &quot;$(NodeApiGeneratorAssemblyPath)&quot; --assembly &quot;$(TargetPath)&quot; --packs &quot;@(TargetingPack)&quot; --reference &quot;@(ReferencePathWithRefAssemblies)&quot; --typedefs &quot;$(TargetDir)$(NodeApiTypeDefinitionsFileName)&quot; $(NodeApiTypeDefinitionsGeneratorOptions)"
3535
ConsoleToMSBuild="true" />
3636
</Target>
3737

@@ -85,23 +85,23 @@
8585
</PropertyGroup>
8686

8787
<ItemGroup Condition="$(TargetFramework.StartsWith('net4'))">
88-
<NodeApiSystemReferenceAssemblies Include="mscorlib" />
89-
<NodeApiSystemReferenceAssemblies Include="System" />
88+
<NodeApiSystemReferenceAssembly Include="mscorlib" />
89+
<NodeApiSystemReferenceAssembly Include="System" />
9090
</ItemGroup>
9191
<ItemGroup Condition="! $(TargetFramework.StartsWith('net4'))">
92-
<NodeApiSystemReferenceAssemblies Include="System.Runtime" />
93-
<NodeApiSystemReferenceAssemblies Include="System.Console" />
92+
<NodeApiSystemReferenceAssembly Include="System.Runtime" />
93+
<NodeApiSystemReferenceAssembly Include="System.Console" />
9494
</ItemGroup>
9595
<ItemGroup>
9696
<!-- This does not use @(NodeApiReferenceAssemblies), like the target Inputs, to avoid excluding items that are up-to-date. -->
9797
<_NodeApiAllReferenceAssemblies Include="@(RuntimeCopyLocalItems)" />
98-
<_NodeApiAllReferenceAssemblies Include="@(NodeApiSystemReferenceAssemblies)" />
98+
<_NodeApiAllReferenceAssemblies Include="@(NodeApiSystemReferenceAssembly)" />
9999

100100
<_NodeApiAllTypeDefs Include="@(RuntimeCopyLocalItems->'$(TargetDir)%(Filename).d.ts')" />
101-
<_NodeApiAllTypeDefs Include="@(NodeApiSystemReferenceAssemblies->'$(TargetDir)%(Identity).d.ts')" />
101+
<_NodeApiAllTypeDefs Include="@(NodeApiSystemReferenceAssembly->'$(TargetDir)%(Identity).d.ts')" />
102102
</ItemGroup>
103103

104-
<Exec Command="dotnet &quot;$(NodeApiGeneratorAssemblyPath)&quot; $(_SuppressTSGenerationWarnings) --assemblies &quot;@(_NodeApiAllReferenceAssemblies)&quot; --typedefs &quot;@(_NodeApiAllTypeDefs)&quot; $(NodeApiTypeDefinitionsGeneratorOptions)"
104+
<Exec Command="dotnet &quot;$(NodeApiGeneratorAssemblyPath)&quot; $(_SuppressTSGenerationWarnings) --assemblies &quot;@(_NodeApiAllReferenceAssemblies)&quot; --packs &quot;@(TargetingPack)&quot; --typedefs &quot;@(_NodeApiAllTypeDefs)&quot; $(NodeApiTypeDefinitionsGeneratorOptions)"
105105
ConsoleToMSBuild="true" />
106106
</Target>
107107

src/NodeApi.Generator/Program.cs

Lines changed: 92 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ public static class Program
2525
private const char PathSeparator = ';';
2626

2727
private static readonly List<string> s_assemblyPaths = new();
28+
private static readonly List<string> s_referenceAssemblyDirectories = new();
2829
private static readonly List<string> s_referenceAssemblyPaths = new();
2930
private static readonly List<string> s_typeDefinitionsPaths = new();
3031
private static readonly HashSet<int> s_systemAssemblyIndexes = new();
31-
private static string? s_systemAssemblyDirectory;
3232
private static TypeDefinitionsGenerator.ModuleType s_moduleType;
3333
private static bool s_suppressWarnings;
3434

@@ -49,6 +49,7 @@ public static int Main(string[] args)
4949
Console.WriteLine(" -a --asssembly Path to input assembly (required)");
5050
Console.WriteLine(" -f --framework Target framework of system assemblies " +
5151
"(optional)");
52+
Console.WriteLine(" -p --pack Targeting pack (optional, multiple)");
5253
Console.WriteLine(" -r --reference Path to reference assembly " +
5354
"(optional, multiple)");
5455
Console.WriteLine(" -t --typedefs Path to output type definitions file (required)");
@@ -70,10 +71,10 @@ public static int Main(string[] args)
7071
TypeDefinitionsGenerator.GenerateTypeDefinitions(
7172
s_assemblyPaths[i],
7273
allReferencePaths,
74+
s_referenceAssemblyDirectories,
7375
s_typeDefinitionsPaths[i],
7476
s_moduleType,
7577
isSystemAssembly: s_systemAssemblyIndexes.Contains(i),
76-
s_systemAssemblyDirectory,
7778
s_suppressWarnings);
7879

7980
if (s_moduleType != TypeDefinitionsGenerator.ModuleType.None)
@@ -91,6 +92,7 @@ public static int Main(string[] args)
9192
private static bool ParseArgs(string[] args)
9293
{
9394
string? targetFramework = null;
95+
List<string> targetingPacks = new();
9496

9597
for (int i = 0; i < args.Length; i++)
9698
{
@@ -122,6 +124,12 @@ void AddItems(List<string> list, string items)
122124
targetFramework = args[++i];
123125
break;
124126

127+
case "-p":
128+
case "--pack":
129+
case "--packs":
130+
AddItems(targetingPacks, args[++i]);
131+
break;
132+
125133
case "-r":
126134
case "--reference":
127135
case "--references":
@@ -163,21 +171,31 @@ void AddItems(List<string> list, string items)
163171
}
164172
}
165173

166-
ResolveSystemAssemblies(targetFramework);
174+
ResolveSystemAssemblies(targetFramework, targetingPacks);
167175

168176
bool HasAssemblyExtension(string fileName) =>
169177
fileName.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) ||
170178
fileName.EndsWith(".exe", StringComparison.OrdinalIgnoreCase);
171179

172-
if (s_assemblyPaths.Any((a) => !HasAssemblyExtension(a)) ||
173-
s_referenceAssemblyPaths.Any((r) => !HasAssemblyExtension(r)) ||
174-
s_typeDefinitionsPaths.Any(
175-
(t) => !t.EndsWith(".d.ts", StringComparison.OrdinalIgnoreCase)))
180+
string? invalidAssemblyPath = s_assemblyPaths.Concat(s_referenceAssemblyPaths)
181+
.FirstOrDefault((a) => !HasAssemblyExtension(a));
182+
if (invalidAssemblyPath != null)
176183
{
177-
Console.WriteLine("Incorrect file path or extension.");
184+
Console.WriteLine(
185+
"Incorrect assembly file extension: " + Path.GetFileName(invalidAssemblyPath));
178186
return false;
179187
}
180-
else if (s_assemblyPaths.Count == 0)
188+
189+
string? invalidTypedefPath = s_typeDefinitionsPaths.FirstOrDefault(
190+
(t) => !t.EndsWith(".d.ts", StringComparison.OrdinalIgnoreCase));
191+
if (invalidTypedefPath != null)
192+
{
193+
Console.WriteLine(
194+
"Incorrect typedef file extension: " + Path.GetFileName(invalidTypedefPath));
195+
return false;
196+
}
197+
198+
if (s_assemblyPaths.Count == 0)
181199
{
182200
Console.WriteLine("Specify an assembly file path.");
183201
return false;
@@ -196,12 +214,27 @@ bool HasAssemblyExtension(string fileName) =>
196214
return true;
197215
}
198216

199-
private static void ResolveSystemAssemblies(string? targetFramework)
217+
private static void ResolveSystemAssemblies(
218+
string? targetFramework,
219+
List<string> targetingPacks)
200220
{
201-
targetFramework ??= GetCurrentFrameworkTarget();
221+
if (targetFramework == null)
222+
{
223+
targetFramework = GetCurrentFrameworkTarget();
224+
}
225+
else if (targetFramework.Contains('-'))
226+
{
227+
// Strip off a platform suffix from a target framework like "net6.0-windows".
228+
targetFramework = targetFramework.Substring(0, targetFramework.IndexOf('-'));
229+
}
202230

203231
if (targetFramework.StartsWith("net4"))
204232
{
233+
if (targetingPacks.Count > 0)
234+
{
235+
Console.WriteLine("Ignoring target packs for .NET Framework target");
236+
}
237+
205238
string refAssemblyDirectory = Path.Combine(
206239
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86),
207240
"Reference Assemblies",
@@ -211,11 +244,17 @@ private static void ResolveSystemAssemblies(string? targetFramework)
211244
"v" + string.Join(".", targetFramework.Substring(3).ToArray())); // v4.7.2
212245
if (Directory.Exists(refAssemblyDirectory))
213246
{
214-
s_systemAssemblyDirectory = refAssemblyDirectory;
247+
s_referenceAssemblyDirectories.Add(refAssemblyDirectory);
215248
}
216249
}
217250
else
218251
{
252+
if (targetingPacks.Count == 0)
253+
{
254+
// If no targeting packs were specified, use the deafult targeting pack for .NET.
255+
targetingPacks.Add("Microsoft.NETCore.App");
256+
}
257+
219258
string runtimeDirectory = RuntimeEnvironment.GetRuntimeDirectory();
220259
if (runtimeDirectory[runtimeDirectory.Length - 1] == Path.DirectorySeparatorChar)
221260
{
@@ -225,36 +264,60 @@ private static void ResolveSystemAssemblies(string? targetFramework)
225264
string dotnetRootDirectory = Path.GetDirectoryName(Path.GetDirectoryName(
226265
Path.GetDirectoryName(runtimeDirectory)!)!)!;
227266

228-
string refAssemblyDirectory = Path.Combine(
229-
dotnetRootDirectory,
230-
"packs",
231-
"Microsoft.NETCore.App.Ref");
232-
s_systemAssemblyDirectory = Directory.GetDirectories(refAssemblyDirectory)
233-
.OrderByDescending((d) => Path.GetFileName(d))
234-
.Select((d) => Path.Combine(d, "ref", targetFramework))
235-
.FirstOrDefault(Directory.Exists);
236-
}
267+
foreach (string targetPack in targetingPacks)
268+
{
269+
string targetPackDirectory = Path.Combine(
270+
dotnetRootDirectory,
271+
"packs",
272+
targetPack + ".Ref");
273+
if (Directory.Exists(targetPackDirectory))
274+
{
275+
string? refAssemblyDirectory = Directory.GetDirectories(targetPackDirectory)
276+
.OrderByDescending((d) => Path.GetFileName(d))
277+
.Select((d) => Path.Combine(d, "ref", targetFramework))
278+
.FirstOrDefault(Directory.Exists);
279+
if (refAssemblyDirectory != null)
280+
{
281+
s_referenceAssemblyDirectories.Add(refAssemblyDirectory);
282+
}
283+
}
284+
}
237285

238-
if (s_systemAssemblyDirectory == null)
239-
{
240-
// TODO: Check .NET Framework.
241-
return;
286+
// Reverse the order of reference assembly directories so that reference assemblies
287+
// specified later in the list are searched first (they override earlier directories).
288+
s_referenceAssemblyDirectories.Reverse();
242289
}
243290

244291
for (int i = 0; i < s_assemblyPaths.Count; i++)
245292
{
246293
if (!s_assemblyPaths[i].EndsWith(".dll", StringComparison.OrdinalIgnoreCase))
247294
{
248-
string systemAssemblyPath = Path.Combine(
249-
s_systemAssemblyDirectory, s_assemblyPaths[i] + ".dll");
250-
if (File.Exists(systemAssemblyPath))
295+
string? systemAssemblyPath = null;
296+
foreach (string referenceAssemblyDirectory in s_referenceAssemblyDirectories)
297+
{
298+
string potentialSystemAssemblyPath = Path.Combine(
299+
referenceAssemblyDirectory, s_assemblyPaths[i] + ".dll");
300+
if (File.Exists(potentialSystemAssemblyPath))
301+
{
302+
systemAssemblyPath = potentialSystemAssemblyPath;
303+
break;
304+
}
305+
}
306+
307+
if (systemAssemblyPath != null)
251308
{
252309
s_assemblyPaths[i] = systemAssemblyPath;
253310
s_systemAssemblyIndexes.Add(i);
254311
}
255312
else
256313
{
257-
Console.WriteLine("System assembly not found at " + systemAssemblyPath);
314+
Console.WriteLine(
315+
$"Assembly '{s_assemblyPaths[i]}' was not found in " +
316+
"reference assembly directories:");
317+
foreach (string referenceAssemblyDirectory in s_referenceAssemblyDirectories)
318+
{
319+
Console.WriteLine(" " + referenceAssemblyDirectory);
320+
}
258321
}
259322
}
260323
}

src/NodeApi.Generator/TypeDefinitionsGenerator.cs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -128,24 +128,26 @@ public enum ModuleType
128128
public static void GenerateTypeDefinitions(
129129
string assemblyPath,
130130
IEnumerable<string> referenceAssemblyPaths,
131+
IEnumerable<string> systemReferenceAssemblyDirectories,
131132
string typeDefinitionsPath,
132133
ModuleType loaderModuleType,
133134
bool isSystemAssembly = false,
134-
string? systemReferenceAssemblyDirectory = null,
135135
bool suppressWarnings = false)
136136
{
137137
// Create a metadata load context that includes a resolver for .NET system assemblies
138138
// along with the target assembly.
139139

140-
systemReferenceAssemblyDirectory ??= RuntimeEnvironment.GetRuntimeDirectory();
141-
string[] systemAssemblies = Directory.GetFiles(
142-
systemReferenceAssemblyDirectory, "*.dll");
140+
// Resolve all assemblies in all the system reference assembly directories.
141+
string[] systemAssemblies = systemReferenceAssemblyDirectories
142+
.SelectMany((d) => Directory.GetFiles(d, "*.dll"))
143+
.ToArray();
143144

144-
// Drop reference assemblies that are already in the system directory.
145+
// Drop reference assemblies that are already in any system ref assembly directories.
145146
// (They would only support older framework versions.)
146147
referenceAssemblyPaths = referenceAssemblyPaths.Where(
147-
(r) => !systemAssemblies.Any((a) =>
148-
Path.GetFileName(a).Equals(Path.GetFileName(r), StringComparison.OrdinalIgnoreCase)));
148+
(r) => Path.GetFileNameWithoutExtension(r).Equals("WindowsBase") ||
149+
!systemAssemblies.Any((a) => Path.GetFileName(a).Equals(
150+
Path.GetFileName(r), StringComparison.OrdinalIgnoreCase)));
149151

150152
PathAssemblyResolver assemblyResolver = new(
151153
new[] { typeof(object).Assembly.Location }

0 commit comments

Comments
 (0)