Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
4c30fd3
Add create-cs-addon command that creates a C# add-on
JesseCol Oct 23, 2025
00dfae2
Electron sample app now uses WinAppSDK
JesseCol Oct 23, 2025
e4974b4
Merge branch 'main' into jessecol/dotnet-addon-2
nmetulev Oct 26, 2025
0fd3191
Make the test proj the way we want it
JesseCol Oct 23, 2025
b5bc81c
Square 1
JesseCol Oct 23, 2025
ca67a45
Porject is basically working
JesseCol Oct 23, 2025
10d2eb3
Improve readmes
JesseCol Oct 27, 2025
32a3226
Add logic to update Directory.packags.props automatically on winsdk r…
JesseCol Oct 27, 2025
c7e6052
Remove extra readme
JesseCol Oct 27, 2025
64e6309
Don't use = for --template param
JesseCol Oct 27, 2025
5bedc1e
Merge branch 'main' into jessecol/dotnet-addon-2
nmetulev Oct 28, 2025
dd04e51
Merge branch 'main' into jessecol/dotnet-addon-2
nmetulev Oct 28, 2025
1d81694
Fix winsdk init by adding new files to winsdk package.json
JesseCol Oct 29, 2025
c87c8ff
Put the .node file in a simpler place, and update readme.md to show c…
JesseCol Oct 30, 2025
77b61be
Reduce the size of the Electron sample's .asar file from 195 MB to 6 MB
JesseCol Oct 30, 2025
0836802
Merge branch 'main' into jessecol/dotnet-addon-2
nmetulev Oct 30, 2025
0b993aa
Merge remote-tracking branch 'origin/main' into jessecol/dotnet-addon-2
JesseCol Oct 31, 2025
9750b10
Merge fixes
JesseCol Oct 31, 2025
32d237b
Merge remote-tracking branch 'origin/main' into jessecol/dotnet-addon-2
JesseCol Oct 31, 2025
ee843dc
Oops, cs-addon defaults to x64. Make it default to the current archi…
JesseCol Oct 31, 2025
9d58b4a
minor docs cleanup
nmetulev Oct 31, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,16 @@ We are actively working on improving Node and Python support. These features are

This repository also contains an **experimental** app (GUI) that wraps the CLI and provides an intuitive, drag-and-drop experience. [See the docs](/docs/gui-usage.md) for more details.

You can also use C#:

```bash
# Create a C# node add-on
npx winapp node create-addon --template cs --name myCsAddon
```

This will create a .csproj file with a .cs file you can call from your Javascript using the node-api-dotnet
package. (This package will also be installed when you run the create-addon command)

## Contributing

This project welcomes contributions and suggestions. Most contributions require you to agree to a
Expand All @@ -128,6 +138,17 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
contact [[email protected]](mailto:[email protected]) with any additional questions or comments.

To build the CLI:
```
# Build the CLI and package for npm from repo root
.\scripts\build-cli.ps1
```

To consume the CLI you built from a local test project:
```
npm install --save-dev <winapp-repo-root>\artifacts\microsoft-winapp-<version-string>.tgz
```

## Trademarks

This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft
Expand Down
6 changes: 6 additions & 0 deletions samples/electron/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,9 @@ out/

# Development certificate
devcert.pfx

# C# build artifacts
bin/
obj/
*.user
*.suo
Binary file modified samples/electron/Assets/LockScreenLogo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified samples/electron/Assets/LockScreenLogo.scale-200.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified samples/electron/Assets/SplashScreen.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified samples/electron/Assets/SplashScreen.scale-200.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified samples/electron/Assets/Square150x150Logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified samples/electron/Assets/Square150x150Logo.scale-200.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified samples/electron/Assets/Square44x44Logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified samples/electron/Assets/Square44x44Logo.scale-200.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified samples/electron/Assets/StoreLogo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified samples/electron/Assets/Wide310x150Logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified samples/electron/Assets/Wide310x150Logo.scale-200.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions samples/electron/Directory.packages.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project>
<PropertyGroup>
<!-- Enable central package versioning -->
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<!-- These versions are updated automatically during winapp restore to match winapp.yaml -->
<PackageVersion Include="Microsoft.JavaScript.NodeApi" Version="0.9.17" />
<PackageVersion Include="Microsoft.JavaScript.NodeApi.Generator" Version="0.9.17" />

<PackageVersion Include="Microsoft.WindowsAppSDK" Version="2.0.250930001-experimental1" />
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.6584" />
</ItemGroup>
</Project>
42 changes: 41 additions & 1 deletion samples/electron/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,39 @@

This sample demonstrates usage of the @microsoft/winappcli npm package with an Electron app.

## 🚀 Quick Start: How to build and run the sample
Here's how you can build, package, deploy, and run the sample (more detail in below sections):

```pwsh
# Enter pwsh and cd to this sample
pwsh
cd <reporoot>\samples\electron

# Build the winapp CLI
..\..\build-cli.ps1

# Install/restore the project dependencies
npm install

# FOR ARM64 ONLY: Build and create the MSIX package
npm run package-msix

# FOR x64 ONLY: Build and create the MSIX package
npm run package-msix:x64

# Install your dev cert if you haven't already (If Sudo isn't enabled, use an admin prompt)
sudo pwsh -c "npx winapp cert install .\devcert.pfx ; pause"

# Deploy the MSIX to local machine
add-appxpackage out\electronwinappSample.msix

# Find "Electron winapp sample" in your start menu and run!
# (note that first launch will be slow)
```


## Overview

The sample is a default Electron Forge generated application with the following modifications:

1. **Initialized a winapp project** by running `npx winapp init`. This generates:
Expand All @@ -15,10 +48,17 @@ The sample is a default Electron Forge generated application with the following

2. **Generated a native addon** using `npx winapp node generate-addon` to call APIs from the Windows SDK and Windows App SDK. The addon folder contains the generated addon alongside the `build-addon` script added to `package.json`. The addon contains a function to raise a Windows notification, and the JavaScript code has been modified to call this function.

3. **Modified `forge.config.js`** to ignore the `.winapp`, `devcert.pfx`, and `winapp.yaml` files from the final package, and to copy the `appxmanifest.xml` and `Assets` folder to the final package.
3. **Generated a C# addon** using `npx winapp node create-addon --template cs`. This generates a simple c# addon using the node-api-dotnet project. When you build the C# addon, this will use NAOT to produce
a .node file that is trimmed and doesn't require the .net runtime to be installed on the target machine.

4. **Modified `forge.config.js`** to ignore the `.winapp`, `devcert.pfx`, and `winapp.yaml` files from the final package, and to copy the `appxmanifest.xml` and `Assets` folder to the final package.

## Prerequisites

* Node.js
* Visual Studio 2022 for building the native component
* .Net 8 SDK for building the C# component

Before running the sample, ensure the npm package has been built:

1. Navigate to the root of this repo
Expand Down
83 changes: 83 additions & 0 deletions samples/electron/csAddon/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# csAddon - C# Native Addon

This is a C# native addon for Node.js/Electron, created using node-api-dotnet.

## Building the Addon

To build the C# addon, run:

```bash
npm run build-csAddon
```

This will compile the C# code and output the assembly to `build/Release/csAddon.dll`.

## Using the Addon in JavaScript

After building, you can use the addon in your JavaScript code:

```javascript
// Load the C# module
const csAddon = require('./csAddon/build/Release/csAddon');

// Call exported methods (note: C# method names are converted to camelCase in JavaScript)
console.log(csAddon.Addon.hello('World'));
// Output: Hello from C#, World!

console.log(csAddon.Addon.add(5, 3));
// Output: 8

console.log(csAddon.Addon.getCurrentTime());
// Output: (current time)
```

**Note:**
- The module is loaded via `dotnet.require()` from the `node-api-dotnet` package.
- C# method names (PascalCase) are automatically converted to camelCase in JavaScript.
- Make sure `node-api-dotnet` is installed (it should be added automatically when you created this addon).

## Development

The addon source code is in `csAddon/addon.cs`. You can modify this file to add your own C# functionality.

### Adding New Methods

To add new methods that are callable from JavaScript:

1. Add the `[JSExport]` attribute to the method
2. Make sure the method is `public static`
3. Rebuild the addon with `npm run build-csAddon`

Example:

```csharp
[JSExport]
public static string MyNewMethod(string input)
{
return $"Processed: {input}";
}
```

## Debugging

To debug the C# addon in Visual Studio or VS Code:

1. Open the `.csproj` file in Visual Studio
2. Set breakpoints in your C# code
3. Attach the debugger to the Node.js/Electron process

## Type Definitions

The build process automatically generates TypeScript type definitions (`.d.ts` file) in the output directory. You can reference these types in your TypeScript code for full IntelliSense support.

## Dependencies

This addon uses:
- [node-api-dotnet](https://github.com/microsoft/node-api-dotnet) - .NET interop for Node.js
- .NET 8.0 SDK (required for building)

## Learn More

- [node-api-dotnet documentation](https://microsoft.github.io/node-api-dotnet/)
- [C# Node.js addon module guide](https://microsoft.github.io/node-api-dotnet/scenarios/js-dotnet-module.html)
- [Node-API (N-API) documentation](https://nodejs.org/api/n-api.html)
89 changes: 89 additions & 0 deletions samples/electron/csAddon/addon.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using System;
using Microsoft.JavaScript.NodeApi;

namespace csAddon
{
/// <summary>
/// Sample C# addon for Node.js using node-api-dotnet.
/// This class demonstrates how to export C# methods to JavaScript.
/// </summary>
[JSExport]
public class Addon
{
/// <summary>
/// A simple hello function that returns a greeting message.
/// </summary>
/// <param name="name">The name to greet</param>
/// <returns>A greeting message</returns>
[JSExport]
public static string Hello(string name)
{
return $"Hello from C#, {name}!";
}

/// <summary>
/// A sample function that adds two numbers.
/// </summary>
/// <param name="a">First number</param>
/// <param name="b">Second number</param>
/// <returns>The sum of a and b</returns>
[JSExport]
public static int Add(int a, int b)
{
return a + b;
}

/// <summary>
/// A sample function that returns the current date and time.
/// </summary>
/// <returns>Current date and time as a string</returns>
[JSExport]
public static string GetCurrentTime()
{
return DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
}

[JSExport]
public static void ShowAppWindow()
{
var controller = Microsoft.UI.Dispatching.DispatcherQueueController.CreateOnDedicatedThread();
var dispatcher = controller.DispatcherQueue;
dispatcher.TryEnqueue(() =>
{
var w = Microsoft.UI.Windowing.AppWindow.Create();
w.Title = "Hello, World!";
w.Show();
});
dispatcher.RunEventLoop();
}

/// <summary>
/// Gets the version of the Windows App Runtime currently in use.
/// </summary>
/// <returns>The Windows App Runtime version as a string</returns>
[JSExport]
public static string GetWindowsAppRuntimeVersion()
{
return Microsoft.Windows.ApplicationModel.WindowsAppRuntime.RuntimeInfo.AsString;
}

/// <summary>
/// Initializes the Windows App Runtime in an unpackaged app. This is required for
/// using the Windows App Runtime APIs in an unpackaged app.
/// Requires this DLL: Microsoft.WindowsAppRuntime.Bootstrap.dll
/// Example JavaScript usage:
/// const csAddon = require('../csAddon/build/Release/csAddon.node');
/// csAddon.Addon.initializeWindowsAppRuntimeInUnpackagedApp(2, 0, 'experimental1');
/// </summary>
[JSExport]
public static void InitializeWindowsAppRuntimeInUnpackagedApp(
int majorVersion,
int minorVersion,
string versionTag)
{
Microsoft.Windows.ApplicationModel.DynamicDependency.Bootstrap.Initialize(
((uint)majorVersion) << 16 | (uint)minorVersion,
versionTag);
}
}
}
30 changes: 30 additions & 0 deletions samples/electron/csAddon/csAddon.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows10.0.19041.0</TargetFramework>
<OutputType>Library</OutputType>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AssemblyName>csAddon</AssemblyName>
<EnableDynamicLoading>true</EnableDynamicLoading>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<!-- RuntimeIdentifier defaults to current architecture, can be overridden via -r parameter.
By default, we build for the current architecture. -->
<RuntimeIdentifier Condition="'$(RuntimeIdentifier)' == '' and '$(PROCESSOR_ARCHITECTURE)' == 'ARM64'">win-arm64</RuntimeIdentifier>
<RuntimeIdentifier Condition="'$(RuntimeIdentifier)' == '' and '$(PROCESSOR_ARCHITECTURE)' == 'AMD64'">win-x64</RuntimeIdentifier>
<RuntimeIdentifier Condition="'$(RuntimeIdentifier)' == '' and '$(PROCESSOR_ARCHITECTURE)' == 'x86'">win-x86</RuntimeIdentifier>
<NodeApiJSModuleType>CommonJs</NodeApiJSModuleType>

<!-- Allows us to run dotnet publish to generate an AOT .node file that doesn't depend on the .net runtime -->
<PublishAot>true</PublishAot>
<PublishNodeModule>true</PublishNodeModule>
<PublishDir>dist</PublishDir>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.JavaScript.NodeApi" />
<PackageReference Include="Microsoft.JavaScript.NodeApi.Generator" />

<PackageReference Include="Microsoft.Windows.SDK.BuildTools" />
<PackageReference Include="Microsoft.WindowsAppSDK" />
</ItemGroup>
</Project>
5 changes: 4 additions & 1 deletion samples/electron/forge.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ module.exports = {
asar: true,
ignore: [
/^\/\.winapp($|\/)/,
/^\/winapp\.yaml$/,
/\.pfx$/,
/^\/winapp\.yaml$/
/\.pdb$/,
/\/obj($|\/)/,
/\/bin($|\/)/
]
},
rebuildConfig: {},
Expand Down
9 changes: 8 additions & 1 deletion samples/electron/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 8 additions & 3 deletions samples/electron/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,21 @@
"package": "electron-forge package",
"make": "electron-forge make",
"publish": "electron-forge publish",
"build-all": "npm run build-csAddon && npm run build-addon",
"build-addon": "node-gyp clean configure build --directory=addon",
"build-csAddon": "dotnet publish ./csAddon/csAddon.csproj -c Release",
"clean-csAddon": "dotnet clean ./csAddon/csAddon.csproj",
"setup-debug": "winapp node add-electron-debug-identity",
"package-msix": "npm run build-addon && npm run package & winapp package ./out/sample-electron-app-win32-arm64/ --output ./out --cert ./devcert.pfx --manifest appxmanifest.xml",
"package-msix": "npm run build-all && npm run package & winapp package ./out/sample-electron-app-win32-arm64/ --output ./out --cert ./devcert.pfx --manifest appxmanifest.xml",
"package-msix:x64": "npm run build-all && npm run package & winapp package ./out/sample-electron-app-win32-x64/ --output ./out --cert ./devcert.pfx --manifest appxmanifest.xml",
"postinstall": "winapp restore && winapp cert generate --if-exists skip && npm run setup-debug"
},
"keywords": [],
"author": "Sample Author",
"license": "MIT",
"dependencies": {
"electron-squirrel-startup": "^1.0.1"
"electron-squirrel-startup": "^1.0.1",
"node-api-dotnet": "^0.9.17"
},
"devDependencies": {
"@electron-forge/cli": "^7.10.2",
Expand All @@ -35,4 +40,4 @@
"node-addon-api": "^8.5.0",
"node-gyp": "^11.4.2"
}
}
}
Loading