11 | {
12 | }
13 |
14 | [JSImport]
15 | public struct SharedMapValueChangedEvent
16 | {
17 | public string Key { get; set; }
18 |
19 | public JSValue PreviousValue { get; set; }
20 | }
21 |
--------------------------------------------------------------------------------
/examples/electron/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Hello World!
6 |
7 |
8 |
9 | Hello World!
10 |
11 | We are using Node.js ,
12 | Chromium ,
13 | Electron ,
14 | and .NET .
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/examples/electron/preload.js:
--------------------------------------------------------------------------------
1 | const { ipcRenderer } = require('electron/renderer')
2 |
3 | window.addEventListener('DOMContentLoaded', () => {
4 | const replaceText = (selector, text) => {
5 | const element = document.getElementById(selector)
6 | if (element) element.innerText = text
7 | }
8 |
9 | for (const type of ['chrome', 'node', 'electron']) {
10 | replaceText(`${type}-version`, process.versions[type])
11 | }
12 |
13 | ipcRenderer.on('dotnet-version', (_event, value) => {
14 | const element = document.getElementById('dotnet-version')
15 | if (element) element.innerText = value
16 | })
17 | })
18 |
--------------------------------------------------------------------------------
/test/TestCases/napi-dotnet/Counter.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Threading;
6 |
7 | namespace Microsoft.JavaScript.NodeApi.TestCases;
8 |
9 | ///
10 | /// Enables testing static state.
11 | ///
12 | [JSExport]
13 | public static class Counter
14 | {
15 | private static int s_count;
16 |
17 | public static int Count()
18 | {
19 | int result = Interlocked.Increment(ref s_count);
20 |
21 | Console.WriteLine($"Counter.Count() => {result}");
22 |
23 | return result;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/examples/aot-module/aot-module.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | true
6 | true
7 | true
8 | bin
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/test/TestCases/napi-dotnet/Hello.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 |
6 | namespace Microsoft.JavaScript.NodeApi.TestCases;
7 |
8 | public static class Hello
9 | {
10 | ///
11 | /// Gets a greeting string for testing.
12 | ///
13 | /// Name of the greeter.
14 | /// A greeting with the name.
15 | [JSExport("hello")]
16 | public static string Test(string greeter)
17 | {
18 | Console.WriteLine($"Hello(\"{greeter}\")");
19 | return $"Hello {greeter}!";
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/test/TestCases/napi-dotnet/ModuleExports.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | namespace Microsoft.JavaScript.NodeApi.TestCases;
5 |
6 | public static class ModuleExports
7 | {
8 | private static string s_value = "test";
9 |
10 | [JSExport]
11 | public static JSValue MergedProperty
12 | {
13 | get => s_value;
14 | set => s_value = (string)value;
15 | }
16 |
17 | [JSExport]
18 | public static JSValue MergedMethod(JSCallbackArgs args)
19 | {
20 | string stringValue = (string)args[0];
21 | return $"Hello {stringValue}!";
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 |
8 |
9 |
10 |
11 | $(MSBuildThisFileDirectory)..\out\pkg
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/node-api-dotnet/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "node-api-dotnet",
3 | "version": "0.1.0",
4 | "description": "Node-API bindings for .Net",
5 | "license": "MIT",
6 | "author": "Microsoft",
7 | "scripts": {
8 | },
9 | "type": "commonjs",
10 | "exports": {
11 | ".": "./index.js"
12 | },
13 | "types": "./index.d.ts",
14 | "keywords": [
15 | "Node-API",
16 | "NAPI",
17 | ".Net",
18 | "dotnet"
19 | ],
20 | "repository": "github:microsoft/node-api-dotnet",
21 | "homepage": "https://github.com/microsoft/node-api-dotnet#readme",
22 | "bugs": {
23 | "url": "https://github.com/microsoft/node-api-dotnet/issues"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/examples/winui-fluid/Fluid/ISharedString.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | namespace Microsoft.JavaScript.NodeApi.Examples.Fluid;
5 |
6 | [JSImport]
7 | public interface ISharedString
8 | {
9 | public int GetLength();
10 |
11 | public string GetText(int? start = null, int? end = null);
12 |
13 | public void InsertText(int pos, string text, JSValue props = default);
14 |
15 | public void ReplaceText(int start, int end, string text, JSValue props = default);
16 |
17 | public void RemoveText(int start, int end);
18 |
19 | // SharedString has more methods, but they aren't currently used.
20 | }
21 |
--------------------------------------------------------------------------------
/src/node-api-dotnet/generator/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "node-api-dotnet-generator",
3 | "version": "0.1.0",
4 | "description": "Node-API for .Net code generator",
5 | "main": "index.js",
6 | "bin": "index.js",
7 | "license": "MIT",
8 | "author": "Microsoft",
9 | "dependencies": {
10 | "node-api-dotnet": "0.1.0"
11 | },
12 | "keywords": [
13 | "Node-API",
14 | "NAPI",
15 | "generator",
16 | ".Net",
17 | "dotnet"
18 | ],
19 | "repository": "github:microsoft/node-api-dotnet",
20 | "homepage": "https://github.com/microsoft/node-api-dotnet#readme",
21 | "bugs": {
22 | "url": "https://github.com/microsoft/node-api-dotnet/issues"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/test/TestCases/node-addon-api/basic_types/boolean.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using Microsoft.JavaScript.NodeApi;
5 |
6 | namespace Microsoft.JavaScript.NodeApiTest;
7 |
8 | public class TestBasicTypesBoolean : TestHelper, ITestObject
9 | {
10 | private static JSValue CreateBoolean(JSCallbackArgs args)
11 | => JSValue.GetBoolean((bool)args[0]);
12 |
13 | private static JSValue CreateBooleanFromPrimitive(JSCallbackArgs args)
14 | => (bool)args[0];
15 |
16 | public JSObject Init() => new()
17 | {
18 | Method(CreateBoolean),
19 | Method(CreateBooleanFromPrimitive),
20 | };
21 | }
22 |
--------------------------------------------------------------------------------
/src/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | DoNotCopy
10 | false
11 | false
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/test/TestCases/node-addon-api/object/finalizer.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | 'use strict';
5 |
6 | const assert = require('assert');
7 | const testUtil = require('../testUtil');
8 |
9 | module.exports = require('../../common').runTest(test);
10 |
11 | function createWeakRef (binding, bindingToTest) {
12 | return binding.object[bindingToTest]({});
13 | }
14 |
15 | function test (binding) {
16 | let obj1;
17 | return testUtil.runGCTests([
18 | 'addFinalizer',
19 | () => {
20 | obj1 = createWeakRef(binding, 'addFinalizer');
21 | },
22 | () => assert.deepStrictEqual(obj1, { finalizerCalled: true })
23 | ]);
24 | }
25 |
--------------------------------------------------------------------------------
/examples/aot-module/Example.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | namespace Microsoft.JavaScript.NodeApi.Examples;
5 |
6 | ///
7 | /// Example Node API module that exports a simple "hello" method.
8 | ///
9 | [JSExport]
10 | public static class Example
11 | {
12 | ///
13 | /// Gets a greeting string.
14 | ///
15 | /// Name of the greeter.
16 | /// A greeting with the name.
17 | public static string Hello(string greeter)
18 | {
19 | System.Console.WriteLine($"Hello {greeter}!");
20 | return $"Hello {greeter}!";
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/semantic-kernel/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | /* https://aka.ms/tsconfig */
3 | "compilerOptions": {
4 | "target": "es2020",
5 | "module": "node16",
6 | "types": ["node"],
7 | "allowJs": true,
8 | "checkJs": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "strict": true,
13 |
14 | // Diable full type-checking of type libraries, including those generated by node-api-dotnet.
15 | // Currently there are a few bugs in generated type definitions for .NET APIs, which do not
16 | // affect most JS applications as long as they don't reference the problematic APIs.
17 | "skipLibCheck": true
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/examples/dotnet-module/Example.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | namespace Microsoft.JavaScript.NodeApi.Examples;
5 |
6 | ///
7 | /// Example Node API module that exports a simple "hello" method.
8 | ///
9 | [JSExport]
10 | public static class Example
11 | {
12 | ///
13 | /// Gets a greeting string.
14 | ///
15 | /// Name of the greeter.
16 | /// A greeting with the name.
17 | public static string Hello(string greeter)
18 | {
19 | System.Console.WriteLine($"Hello {greeter}!");
20 | return $"Hello {greeter}!";
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/aot-npm-package/lib/Example.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | namespace Microsoft.JavaScript.NodeApi.Examples;
5 |
6 | ///
7 | /// Example Node API module that exports a simple "hello" method.
8 | ///
9 | [JSExport]
10 | public static class Example
11 | {
12 | ///
13 | /// Gets a greeting string.
14 | ///
15 | /// Name of the greeter.
16 | /// A greeting with the name.
17 | public static string Hello(string greeter)
18 | {
19 | System.Console.WriteLine($"Hello {greeter}!");
20 | return $"Hello {greeter}!";
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/dotnet-dynamic-classlib/README.md:
--------------------------------------------------------------------------------
1 |
2 | ## Example of dynamically invoking .NET APIs from a referenced project
3 | The `example.js` script loads .NET, loads the `ClassLib` assembly, and calls `new Class1().Hello()`.
4 |
5 | | Command | Explanation
6 | |----------------------------------|--------------------------------------------------
7 | | `dotnet pack ../..` | Build Node API .NET packages.
8 | | `dotnet build` | Build the `ClassLib` project and generate type definitions.
9 | | `npm install` | Install `node-api-dotnet` npm package into the example project.
10 | | `node example.js` | Run example JS code that dynamically invokes the class library API.
11 |
--------------------------------------------------------------------------------
/src/NodeApi.Generator/StringExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | #if NETFRAMEWORK || NETSTANDARD
5 |
6 | using System;
7 |
8 | ///
9 | /// Fills in extension methods for the class that are not present
10 | /// in .NET Framework.
11 | ///
12 | internal static class StringExtensions
13 | {
14 | public static bool Contains(this string s, char c) => s.Contains(c.ToString());
15 |
16 | public static bool StartsWith(this string s, char c) => s.StartsWith(c.ToString(), StringComparison.Ordinal);
17 |
18 | public static bool EndsWith(this string s, char c) => s.EndsWith(c.ToString(), StringComparison.Ordinal);
19 | }
20 |
21 | #endif
22 |
--------------------------------------------------------------------------------
/examples/hermes-engine/HermesConfig.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using static Hermes.Example.HermesApi.Interop;
5 |
6 | namespace Hermes.Example;
7 |
8 | public sealed class HermesConfig : IDisposable
9 | {
10 | private hermes_config _config;
11 | private bool _isDisposed;
12 |
13 | public HermesConfig()
14 | {
15 | hermes_create_config(out _config).ThrowIfFailed();
16 | }
17 |
18 | public void Dispose()
19 | {
20 | if (_isDisposed) return;
21 | _isDisposed = true;
22 | hermes_delete_config(_config).ThrowIfFailed();
23 | }
24 |
25 | public static explicit operator hermes_config(HermesConfig value) => value._config;
26 | }
27 |
--------------------------------------------------------------------------------
/examples/wpf/Window1.xaml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/test/TestCases/node-addon-api/object/finalizer.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using Microsoft.JavaScript.NodeApi;
5 |
6 | namespace Microsoft.JavaScript.NodeApiTest;
7 |
8 | public partial class TestObject
9 | {
10 | private static JSValue AddFinalizer(JSCallbackArgs args)
11 | {
12 | JSValue result = JSValue.CreateObject();
13 | JSReference objRef = new(result);
14 | args[0].AddFinalizer(() =>
15 | {
16 | if (objRef.GetValue() is JSValue value)
17 | {
18 | value.SetProperty("finalizerCalled", true);
19 | }
20 | objRef.Dispose();
21 | });
22 | return result;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/examples/dotnet-dynamic-classlib/ClassLib/Class1.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | namespace Microsoft.JavaScript.NodeApi.Examples;
5 |
6 | ///
7 | /// Example class that will be dynamically instantiated in JavaScript.
8 | ///
9 | public class Class1
10 | {
11 | ///
12 | /// Creates a new instance of the class.
13 | ///
14 | public Class1()
15 | {
16 | }
17 |
18 | ///
19 | /// Gets a greeting message.
20 | ///
21 | public string Hello(string greeter)
22 | {
23 | System.Console.WriteLine($"Hello {greeter}!");
24 | return $"Hello {greeter}!";
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/examples/dotnet-dynamic-classlib/dotnet-dynamic-classlib.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | $(MSBuildThisFileDirectory)/pkg
6 | bin
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/test/TestCases/node-addon-api/basic_types/boolean.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | 'use strict';
5 |
6 | const assert = require('assert');
7 |
8 | module.exports = require('../../common').runTest(test);
9 |
10 | function test(binding) {
11 | const bool1 = binding.basicTypesBoolean.createBoolean(true);
12 | assert.strictEqual(bool1, true);
13 |
14 | const bool2 = binding.basicTypesBoolean.createBoolean(false);
15 | assert.strictEqual(bool2, false);
16 |
17 | const bool3 = binding.basicTypesBoolean.createBooleanFromPrimitive(true);
18 | assert.strictEqual(bool3, true);
19 |
20 | const bool4 = binding.basicTypesBoolean.createBooleanFromPrimitive(false);
21 | assert.strictEqual(bool4, false);
22 | }
23 |
--------------------------------------------------------------------------------
/examples/jsdom/README.md:
--------------------------------------------------------------------------------
1 | ## C# JSDom Example
2 | This project is a C# executable application that uses the JSDOM library
3 | (https://github.com/jsdom/jsdom) to parse HTML.
4 |
5 | Before building and running this project, download or build `libnode.dll` that
6 | _includes Node API embedding support_.
7 | See https://microsoft.github.io/node-api-dotnet/scenarios/dotnet-js.html
8 |
9 | | Command | Explanation
10 | |-------------------------|--------------------------------------------------
11 | | `dotnet pack ../..` | Build Node API .NET packages.
12 | | `npm install` | Install JavaScript packages.
13 | | `dotnet build` | Install .NET nuget packages; build example project.
14 | | `dotnet run --no-build` | Run the example project.
15 |
16 |
--------------------------------------------------------------------------------
/src/node-api-dotnet/generator/index.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | // Copyright (c) Microsoft Corporation.
4 | // Licensed under the MIT License.
5 |
6 | const path = require('path');
7 | const assemblyDir = path.join(__dirname, 'net8.0');
8 |
9 | const dotnet = require('node-api-dotnet');
10 |
11 | // The generator depends on these assemblies; for now they have to be loaded explicitly.
12 | dotnet.load(path.join(assemblyDir, 'System.Reflection.MetadataLoadContext.dll'));
13 | dotnet.load(path.join(assemblyDir, 'Microsoft.CodeAnalysis.dll'));
14 | dotnet.load(path.join(assemblyDir, 'Microsoft.JavaScript.NodeApi.Generator.dll'));
15 | const Generator = dotnet.Microsoft.JavaScript.NodeApi.Generator;
16 |
17 | const args = process.argv.slice(2);
18 | Generator.Program.Main(args);
19 |
--------------------------------------------------------------------------------
/examples/jsdom/jsdom.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | Exe
6 | enable
7 | Microsoft.JavaScript.NodeApi.Examples
8 | false
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/test/TestCases/node-addon-api/object/object_freeze_seal.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using Microsoft.JavaScript.NodeApi;
5 |
6 | namespace Microsoft.JavaScript.NodeApiTest;
7 |
8 | public partial class TestObjectFreezeSeal : TestHelper, ITestObject
9 | {
10 | private static JSValue Freeze(JSCallbackArgs args)
11 | {
12 | JSValue obj = args[0];
13 | obj.Freeze();
14 | return true;
15 | }
16 |
17 | private static JSValue Seal(JSCallbackArgs args)
18 | {
19 | JSValue obj = args[0];
20 | obj.Seal();
21 | return true;
22 | }
23 |
24 | public JSObject Init() => new()
25 | {
26 | Method(Freeze),
27 | Method(Seal),
28 | };
29 | }
30 |
--------------------------------------------------------------------------------
/test/TestCases/napi-dotnet/module_class.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | const assert = require('assert');
5 |
6 | /** @type {import('./napi-dotnet')} */
7 | const binding = require('../common').binding;
8 |
9 | assert.strictEqual(typeof binding, 'object');
10 |
11 | assert.strictEqual(binding.moduleProperty, 'test');
12 | assert.strictEqual(binding.moduleMethod('test'), 'Hello test!');
13 | assert.strictEqual(binding.mergedProperty, 'test');
14 | assert.strictEqual(binding.mergedMethod('test'), 'Hello test!');
15 |
16 | /*
17 | // Delete the cached binding. This should invoke the module Dispose() method.
18 | // TODO: With CLR hosting, there should be a way to delete one .NET module.
19 | delete require.cache[dotnetHost];
20 | delete require.cache[dotnetModule];
21 | */
22 |
--------------------------------------------------------------------------------
/test/TestCases/napi-dotnet/Errors.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Threading.Tasks;
6 |
7 | namespace Microsoft.JavaScript.NodeApi.TestCases;
8 |
9 | [JSExport]
10 | public static class Errors
11 | {
12 | public static void ThrowDotnetError(string message)
13 | {
14 | throw new Exception(message);
15 | }
16 |
17 | public static void ThrowJSError(string message, IJSErrors jsErrors)
18 | {
19 | jsErrors.ThrowJSError(message);
20 | }
21 |
22 | public static async Task ThrowAsyncDotnetError(string message)
23 | {
24 | await Task.Yield();
25 | throw new Exception(message);
26 | }
27 | }
28 |
29 | [JSExport]
30 | public interface IJSErrors
31 | {
32 | void ThrowJSError(string message);
33 | }
34 |
--------------------------------------------------------------------------------
/test/TestCases/projects/ts-cjs-module/test.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | import * as assert from 'assert';
5 | import * as testModule from './bin/ts-cjs-module';
6 |
7 | assert.strictEqual(testModule.readOnlyProperty, 'ROProperty');
8 | assert.strictEqual(typeof testModule.method, 'function');
9 | assert.strictEqual(testModule.method('test'), 'test');
10 |
11 | const { ModuleClass } = testModule;
12 | assert.strictEqual(typeof new ModuleClass('test'), 'object');
13 | assert.strictEqual(new ModuleClass('test').property, 'test');
14 | assert.strictEqual(new ModuleClass('test').method('test2'), 'test2');
15 |
16 | const { ModuleEnum } = testModule;
17 | assert.strictEqual(typeof ModuleEnum, 'object');
18 | assert.strictEqual(ModuleEnum.None, 0);
19 | assert.strictEqual(ModuleEnum.One, 1);
20 |
--------------------------------------------------------------------------------
/src/NodeApi.Generator/TypeExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 |
6 | #if NETFRAMEWORK || NETSTANDARD
7 |
8 | namespace Microsoft.JavaScript.NodeApi.Generator;
9 |
10 | internal static class TypeExtensions
11 | {
12 | //https://github.com/dotnet/runtime/issues/23493
13 | public static bool IsGenericTypeParameter(this Type target)
14 | {
15 | return target.IsGenericParameter &&
16 | target.DeclaringType != null &&
17 | target.DeclaringMethod == null;
18 | }
19 |
20 | //https://github.com/dotnet/runtime/issues/23493
21 | public static bool IsGenericMethodParameter(this Type target)
22 | {
23 | return target.IsGenericParameter &&
24 | target.DeclaringMethod != null;
25 | }
26 | }
27 |
28 | #endif
29 |
--------------------------------------------------------------------------------
/test/TestCases/napi-dotnet/dynamic_no_namespace_type.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | const assert = require('assert');
5 |
6 | const dotnet = require('../common').dotnet;
7 |
8 | // Load the test module using dynamic binding `load()` instead of static binding `require()`.
9 | const assemblyPath = process.env.NODE_API_TEST_MODULE_PATH;
10 | dotnet.load(assemblyPath);
11 |
12 | // The unnamespaced type should be skipped.
13 | assert.strictEqual(dotnet.NoNamespaceType, undefined);
14 |
15 | assert.notStrictEqual(dotnet.Microsoft.JavaScript.NodeApi.TestCases.NoNamespaceInterfaceImpl, undefined)
16 |
17 | assert.notStrictEqual(dotnet.Microsoft.JavaScript.NodeApi.TestCases.NoNamespaceTypeImpl, undefined)
18 |
19 | assert.notStrictEqual(dotnet.Microsoft.JavaScript.NodeApi.TestCases.NoNamespaceContainer, undefined)
20 |
--------------------------------------------------------------------------------
/examples/hermes-engine/hermes-engine.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 | enable
8 | Hermes.Example
9 | true
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/examples/aot-module/README.md:
--------------------------------------------------------------------------------
1 |
2 | ## Minimal Example .NET AOT Node Module
3 | The `Example.cs` class defines a Node.js add-on module that is AOT-compiled, so that it does not
4 | depend on the .NET runtime. The `example.js` script loads that _native_ module as a Node.js add-on
5 | and calls a method on it. The script has access to type definitions and doc-comments for the
6 | module's APIs via the auto-generated `.d.ts` file.
7 |
8 | | Command | Explanation
9 | |----------------------------------|--------------------------------------------------
10 | | `dotnet pack ../..` | Build Node API .NET packages.
11 | | `dotnet publish` | Install Node API .NET packages into example project; build example project and compile to native binary.
12 | | `node example.js` | Run example JS code that calls the example module.
13 |
--------------------------------------------------------------------------------
/examples/wpf/README.md:
--------------------------------------------------------------------------------
1 |
2 | ## Example: Calling WFP APIs from JS
3 | The `example.js` script loads WPF .NET assemblies and shows a WPF window with a WebView2
4 | control with a JS script that renders a mermaid diagram.
5 |
6 | _**.NET events** are not yet projected to JS
7 | ([#59](https://github.com/microsoft/node-api-dotnet/issues/59)).
8 | WPF capabilities will be limited until that issue is resolved._
9 |
10 | | Command | Explanation
11 | |----------------------------------|--------------------------------------------------
12 | | `dotnet pack ../..` | Build Node API .NET packages.
13 | | `dotnet build` | Generate type definitions for WPF assemblies.
14 | | `npm install` | Install `node-api-dotnet` npm package into the project.
15 | | `node example.js` | Run example JS code that calls WPF.
16 |
--------------------------------------------------------------------------------
/bench/NodeApi.Bench.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | exe
5 | Microsoft.JavaScript.NodeApi.Bench
6 | false
7 | false
8 | MSB3270
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/examples/electron/main.js:
--------------------------------------------------------------------------------
1 | const { app, BrowserWindow } = require('electron/main')
2 | const path = require('node:path')
3 |
4 | function createWindow () {
5 | const win = new BrowserWindow({
6 | width: 800,
7 | height: 600,
8 | webPreferences: {
9 | preload: path.join(__dirname, 'preload.js')
10 | }
11 | })
12 |
13 | win.loadFile('index.html')
14 |
15 | const dotnet = require('node-api-dotnet')
16 | const dotnetVersion = dotnet.System.Environment.Version.toString()
17 | win.webContents.send('dotnet-version', dotnetVersion)
18 | }
19 |
20 | app.whenReady().then(() => {
21 | createWindow()
22 |
23 | app.on('activate', () => {
24 | if (BrowserWindow.getAllWindows().length === 0) {
25 | createWindow()
26 | }
27 | })
28 | })
29 |
30 | app.on('window-all-closed', () => {
31 | if (process.platform !== 'darwin') {
32 | app.quit()
33 | }
34 | })
35 |
--------------------------------------------------------------------------------
/src/NodeApi/JSCallback.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | namespace Microsoft.JavaScript.NodeApi;
5 |
6 | ///
7 | /// Represents a low-level function or method call or callback from JavaScript into .NET.
8 | ///
9 | /// Provides access to the arguments for the call, along with the `this`
10 | /// argument and an optional context object.
11 | /// The return value as a JS value.
12 | public delegate JSValue JSCallback(JSCallbackArgs args);
13 |
14 | ///
15 | /// Represents a low-level void function or method call or callback from JavaScript into .NET.
16 | ///
17 | /// Provides access to the arguments for the call, along with the `this`
18 | /// argument and an optional context object.
19 | public delegate void JSActionCallback(JSCallbackArgs args);
20 |
--------------------------------------------------------------------------------
/test/TestCases/napi-dotnet/OptionalParameters.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | namespace Microsoft.JavaScript.NodeApi.TestCases;
5 |
6 | [JSExport]
7 | public static class OptionalParameters
8 | {
9 | public static string DefaultNull(string a, string? b = null)
10 | {
11 | b ??= "(null)";
12 | return $"{a},{b}";
13 | }
14 |
15 | public static string DefaultFalse(bool a, bool b = false)
16 | {
17 | return $"{a},{b}";
18 | }
19 |
20 | public static string DefaultZero(int a, int b = 0)
21 | {
22 | return $"{a},{b}";
23 | }
24 |
25 | public static string DefaultEmptyString(string a, string b = "")
26 | {
27 | return $"{a},{b}";
28 | }
29 |
30 | public static string Multiple(string a, string? b = null, int c = 0)
31 | {
32 | return $"{a},{b},{c}";
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/docs/reference/delegates.md:
--------------------------------------------------------------------------------
1 | # Delegates
2 |
3 | An exported .NET delegate type is converted to a TypeScript function type definition:
4 | ```C#
5 | [JSExport]
6 | public delegate string ExampleCallback(int arg1, bool arg2);
7 | ```
8 | ```TS
9 | export function ExampleCallback(arg1: number, arg2: boolean): string;
10 | ```
11 |
12 | Then a JavaScript function can be passed to a .NET API that expects a delegate of that type, and
13 | the parameters and return value will be marshalled accordingly.
14 |
15 | ```C#
16 | [JSExport]
17 | public static class Example
18 | {
19 | public static void RegisterCallback(ExampleCallback cb) { … }
20 | }
21 | ```
22 | ```JS
23 | Example.registerCallback((arg1, arg2) => 'ok');
24 | ```
25 |
26 | This is one way for .NET to call back into JavaScript. Another way is to
27 | [implement a .NET interface with a JavaScript class](./classes-interfaces#implement-a-net-interface-with-a-js-class).
28 |
--------------------------------------------------------------------------------
/examples/winui-fluid/Fluid/ISequenceDeltaEvent.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | namespace Microsoft.JavaScript.NodeApi.Examples.Fluid;
5 |
6 | [JSImport]
7 | public struct SequenceDeltaEvent
8 | {
9 | public bool IsLocal { get; set; }
10 |
11 | public string ClientId { get; set; }
12 |
13 | public MergeTreeDeltaOpArgs OpArgs { get; set; }
14 | }
15 |
16 | [JSImport]
17 | public struct MergeTreeDeltaOpArgs
18 | {
19 | public MergeTreeOp Op { get; set; }
20 | }
21 |
22 | [JSImport]
23 | public struct MergeTreeOp
24 | {
25 | public MergeTreeDeltaType Type { get; set; }
26 |
27 | public int? Pos1 { get; set; }
28 |
29 | public int? Pos2 { get; set; }
30 |
31 | public string? Seg { get; set; }
32 | }
33 |
34 | public enum MergeTreeDeltaType
35 | {
36 | Insert = 0,
37 | Remove = 1,
38 | Annotate = 2,
39 | Group = 3,
40 | }
41 |
--------------------------------------------------------------------------------
/test/TestCases/napi-dotnet/dynamic_optional_params.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | const assert = require('assert');
5 |
6 | // This only tests dynamic invocation because optional parameters
7 | // are not yet implemented for static binding.
8 |
9 | const dotnet = require('../common').dotnet;
10 |
11 | const assemblyPath = process.env.NODE_API_TEST_MODULE_PATH;
12 | const assembly = dotnet.load(assemblyPath);
13 | const OptionalParameters = dotnet.Microsoft.JavaScript.NodeApi.TestCases.OptionalParameters;
14 |
15 | assert.strictEqual('a,(null)', OptionalParameters.DefaultNull('a'));
16 | assert.strictEqual('True,False', OptionalParameters.DefaultFalse(true));
17 | assert.strictEqual('1,0', OptionalParameters.DefaultZero(1));
18 | assert.strictEqual('a,', OptionalParameters.DefaultEmptyString('a'));
19 | assert.strictEqual('a,b,0', OptionalParameters.Multiple('a', 'b'));
20 |
--------------------------------------------------------------------------------
/test/TestCases/napi-dotnet/object_map.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | // Test the JSRuntimeContext "object map" which keeps track of JS wrapper objects
5 | // for corresponding .NET objects.
6 |
7 | const assert = require('assert');
8 |
9 | /** @type {import('./napi-dotnet')} */
10 | const binding = require('../common').binding;
11 |
12 | const ComplexTypes = binding.ComplexTypes;
13 | assert.strictEqual(typeof ComplexTypes, 'object');
14 |
15 | let obj1 = ComplexTypes.classObject;
16 | assert(obj1);
17 |
18 | // The same JS wrapper instance should be returned every time.
19 | let obj2 = ComplexTypes.classObject;
20 | assert.strictEqual(obj1, obj2);
21 |
22 | // Force the JS wrapper object to be collected.
23 | obj1 = obj2 = undefined;
24 | global.gc();
25 |
26 | // A new JS wrapper object should be created.
27 | let obj3 = ComplexTypes.classObject;
28 | assert(obj3);
29 |
--------------------------------------------------------------------------------
/test/TestCases/projects/js-esm-module/test.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | import assert from 'assert';
5 | import DefaultClass, {
6 | method,
7 | readOnlyProperty,
8 | ModuleClass,
9 | ModuleEnum,
10 | } from './bin/js-esm-module.js';
11 |
12 | assert.strictEqual(typeof DefaultClass, 'object');
13 | assert.strictEqual(DefaultClass.method('test'), 'test');
14 |
15 | assert.strictEqual(readOnlyProperty, 'ROProperty');
16 | assert.strictEqual(typeof method, 'function');
17 | assert.strictEqual(method('test'), 'test');
18 |
19 | assert.strictEqual(typeof new ModuleClass('test'), 'object');
20 | assert.strictEqual(new ModuleClass('test').property, 'test');
21 | assert.strictEqual(new ModuleClass('test').method('test2'), 'test2');
22 |
23 | assert.strictEqual(typeof ModuleEnum, 'object');
24 | assert.strictEqual(ModuleEnum.None, 0);
25 | assert.strictEqual(ModuleEnum.One, 1);
26 |
--------------------------------------------------------------------------------
/test/TestCases/projects/ts-esm-module/test.ts:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | import assert from 'assert';
5 | import DefaultClass, {
6 | method,
7 | readOnlyProperty,
8 | ModuleClass,
9 | ModuleEnum,
10 | } from './bin/ts-esm-module.js';
11 |
12 | assert.strictEqual(typeof DefaultClass, 'object');
13 | assert.strictEqual(DefaultClass.method('test'), 'test');
14 |
15 | assert.strictEqual(readOnlyProperty, 'ROProperty');
16 | assert.strictEqual(typeof method, 'function');
17 | assert.strictEqual(method('test'), 'test');
18 |
19 | assert.strictEqual(typeof new ModuleClass('test'), 'object');
20 | assert.strictEqual(new ModuleClass('test').property, 'test');
21 | assert.strictEqual(new ModuleClass('test').method('test2'), 'test2');
22 |
23 | assert.strictEqual(typeof ModuleEnum, 'object');
24 | assert.strictEqual(ModuleEnum.None, 0);
25 | assert.strictEqual(ModuleEnum.One, 1);
26 |
--------------------------------------------------------------------------------
/examples/winui-fluid/Properties/PublishProfiles/win10-x64.pubxml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | FileSystem
8 | x64
9 | win-x64
10 | bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\
11 | true
12 | False
13 | False
14 | True
15 |
19 |
20 |
--------------------------------------------------------------------------------
/examples/winui-fluid/Properties/PublishProfiles/win10-x86.pubxml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | FileSystem
8 | x86
9 | win-x86
10 | bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\
11 | true
12 | False
13 | False
14 | True
15 |
19 |
20 |
--------------------------------------------------------------------------------
/examples/winui-fluid/Properties/PublishProfiles/win10-arm64.pubxml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | FileSystem
8 | arm64
9 | win-arm64
10 | bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\
11 | true
12 | False
13 | False
14 | True
15 |
19 |
20 |
--------------------------------------------------------------------------------
/src/NodeApi/Runtime/NodeEmbeddingPlatformSettings.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | namespace Microsoft.JavaScript.NodeApi.Runtime;
5 |
6 | using static NodeEmbedding;
7 | using static NodejsRuntime;
8 |
9 | public class NodeEmbeddingPlatformSettings
10 | {
11 | public string? LibNodePath { get; set; }
12 | public NodeEmbeddingPlatformFlags? PlatformFlags { get; set; }
13 | public string[]? Args { get; set; }
14 | public ConfigurePlatformCallback? ConfigurePlatform { get; set; }
15 |
16 | public unsafe ConfigurePlatformCallback CreateConfigurePlatformCallback()
17 | => new((config) =>
18 | {
19 | if (PlatformFlags != null)
20 | {
21 | NodeEmbedding.JSRuntime.EmbeddingPlatformConfigSetFlags(config, PlatformFlags.Value)
22 | .ThrowIfFailed();
23 | }
24 | ConfigurePlatform?.Invoke(config);
25 | });
26 | }
27 |
--------------------------------------------------------------------------------
/docs/requirements.md:
--------------------------------------------------------------------------------
1 | # Requirements
2 |
3 | ## Runtime requirements
4 | #### OS
5 | - Windows: x64, arm64
6 | - Mac: x64, arm64
7 | - Linux: x64 ([arm64 coming soon](https://github.com/microsoft/node-api-dotnet/issues/80))
8 |
9 | #### .NET
10 | - For .NET runtime-dependent applications, .NET 4.7.2 or later, .NET 6, or .NET 8 runtime
11 | must be installed.
12 | - For .NET Native AOT applications, .NET is not required on the target system.
13 | - On Linux, AOT binaries may depend on optional system packages. See
14 | [Install .NET on Linux](https://learn.microsoft.com/en-us/dotnet/core/install/linux)
15 | and browse to the distro specific dependencies.
16 |
17 | #### Node.js
18 | - Node.js v18 or later
19 | - Other JS runtimes may be supported in the future.
20 |
21 | ## Build requirements
22 |
23 | - .NET 8 SDK
24 | - Optional: .NET 6 SDK, if targeting .NET 6 runtime
25 | - Optional: .NET Framework 4.x developer pack, if targeting .NET Framework
26 | - Node.js v18 or later
27 |
--------------------------------------------------------------------------------
/bench/README.md:
--------------------------------------------------------------------------------
1 | # Micro-benchmarks for node-api-dotnet APIs
2 |
3 | This project contains a set of micro-benchmarks for .NET + JS interop operations, driven by
4 | [BenchmarkDotNet](https://benchmarkdotnet.org/). Most benchmarks run in both CLR and AOT modes,
5 | though the "Dynamic" benchmarks are CLR-only.
6 |
7 | > :warning: The benchmarks currently depend on a special branch build of `libnode` being present at
8 | `../bin/`. This should be resolved with [#107](https://github.com/microsoft/node-api-dotnet/issues/107).
9 |
10 | ### Run all benchmarks
11 | ```
12 | dotnet run -c Release -f net9.0 --filter *
13 | ```
14 |
15 | ### Run only CLR or only AOT benchmarks
16 | ```
17 | dotnet run -c Release -f net9.0 --filter *clr.*
18 | dotnet run -c Release -f net9.0 --filter *aot.*
19 | ```
20 |
21 | ### Run a specific benchmark
22 | ```
23 | dotnet run -c Release -f net9.0 --filter *clr.CallDotnetFunction
24 | ```
25 |
26 | ### List benchmarks
27 | ```
28 | dotnet run -c Release -f net9.0 --list flat
29 | ```
30 |
--------------------------------------------------------------------------------
/bench/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 | true
6 | net9.0;net8.0;netstandard2.0
7 | net10.0;$(TargetFrameworks)
8 | $(TargetFrameworks);net472
9 |
10 |
11 | 12
12 | enable
13 | false
14 | true
15 |
16 |
17 |
--------------------------------------------------------------------------------
/test/TestCases/projects/js-cjs-module/test.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | const assert = require('assert');
5 | const testModule = require('./bin/js-cjs-module');
6 |
7 | assert.strictEqual(testModule.readOnlyProperty, 'ROProperty');
8 | assert.strictEqual(testModule.readWriteProperty, 'RWProperty');
9 | testModule.readWriteProperty = 'test';
10 | assert.strictEqual(testModule.readWriteProperty, 'test');
11 | assert.strictEqual(typeof testModule.method, 'function');
12 | assert.strictEqual(testModule.method('test'), 'test');
13 |
14 | const { ModuleClass } = testModule;
15 | assert.strictEqual(typeof new ModuleClass('test'), 'object');
16 | assert.strictEqual(new ModuleClass('test').property, 'test');
17 | assert.strictEqual(new ModuleClass('test').method('test2'), 'test2');
18 |
19 | const { ModuleEnum } = testModule;
20 | assert.strictEqual(typeof ModuleEnum, 'object');
21 | assert.strictEqual(ModuleEnum.None, 0);
22 | assert.strictEqual(ModuleEnum.One, 1);
23 |
--------------------------------------------------------------------------------
/docs/reference/other-types.md:
--------------------------------------------------------------------------------
1 | # Other Special Types
2 |
3 | ## BigInteger
4 |
5 | | C# Type | JS Type |
6 | |--------------|----------|
7 | | `BigInteger` | `BigInt` |
8 |
9 | .NET [`BigInteger`](https://learn.microsoft.com/en-us/dotnet/api/system.numerics.biginteger)
10 | converts to and from JavaScript
11 | [`BigInt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt)
12 | with no loss in precision. (Conversion internally allocates and copies memory for the number data.)
13 | The [`JSBigInt`](./dotnet/Microsoft.JavaScript.NodeApi/JSBigInt) class supports working directly
14 | with JS `BigInt` values, and converting to/from .NET `BigInteger`.
15 |
16 | ## Guid
17 |
18 | | C# Type | JS Type |
19 | |---------|----------|
20 | | `Guid` | `string` |
21 |
22 | A .NET `Guid` is marshalled to JS as a `string` in the default `"D"` format. Marshalling in the
23 | other direction supports any format accepted by
24 | [`Guid.Parse()`](https://learn.microsoft.com/en-us/dotnet/api/system.guid.parse).
25 |
--------------------------------------------------------------------------------
/test/TestCases/node-addon-api/object/subscript_operator.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | 'use strict';
5 |
6 | const assert = require('assert');
7 |
8 | module.exports = require('../../common').runTest(test);
9 |
10 | function test(binding) {
11 | function testProperty(obj, key, value, nativeGetProperty, nativeSetProperty) {
12 | nativeSetProperty(obj, key, value);
13 | assert.strictEqual(nativeGetProperty(obj, key), value);
14 | }
15 |
16 | testProperty({}, 'key', 'value', binding.object.subscriptGetWithUtf8StyleString, binding.object.subscriptSetWithUtf8StyleString);
17 | testProperty({ key: 'override me' }, 'key', 'value', binding.object.subscriptGetWithCSharpStyleString, binding.object.subscriptSetWithCSharpStyleString);
18 | testProperty({}, 0, 'value', binding.object.subscriptGetAtIndex, binding.object.subscriptSetAtIndex);
19 | testProperty({ key: 'override me' }, 0, 'value', binding.object.subscriptGetAtIndex, binding.object.subscriptSetAtIndex);
20 | }
21 |
--------------------------------------------------------------------------------
/test/TestCases/node-addon-api/basic_types/array.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using Microsoft.JavaScript.NodeApi;
5 |
6 | namespace Microsoft.JavaScript.NodeApiTest;
7 |
8 | public class TestBasicTypesArray : TestHelper, ITestObject
9 | {
10 | private static JSValue CreateArray(JSCallbackArgs args)
11 | => (args.Length > 0) ? new JSArray((int)args[0]) : new JSArray();
12 |
13 | private static JSValue GetLength(JSCallbackArgs args)
14 | => ((JSArray)args[0]).Length;
15 |
16 | private static JSValue Get(JSCallbackArgs args)
17 | => ((JSArray)args[0])[(int)args[1]];
18 |
19 | private static JSValue Set(JSCallbackArgs args)
20 | {
21 | var array = (JSArray)args[0];
22 | array[(int)args[1]] = args[2];
23 | return JSValue.Undefined;
24 | }
25 |
26 | public JSObject Init() => new()
27 | {
28 | Method(CreateArray),
29 | Method(GetLength),
30 | Method(Get),
31 | Method(Set),
32 | };
33 | }
34 |
--------------------------------------------------------------------------------
/src/NodeApi.DotNetHost/JSMarshallerException.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Reflection;
6 |
7 | namespace Microsoft.JavaScript.NodeApi.DotNetHost;
8 |
9 | ///
10 | /// Exception thrown by about a failure while dynamically generating
11 | /// marshaling expressions.
12 | ///
13 | public class JSMarshallerException : JSException
14 | {
15 | public JSMarshallerException(string message, Type type, Exception? innerException = null)
16 | : base(message + $" Type: {type}", innerException)
17 | {
18 | Type = type;
19 | }
20 |
21 | public JSMarshallerException(string message, MemberInfo member, Exception? innerException = null)
22 | : base(message + $" Type: {member.DeclaringType}, Member: {member}", innerException)
23 | {
24 | Type = member.DeclaringType!;
25 | Member = member;
26 | }
27 |
28 | public Type Type { get; }
29 |
30 | public MemberInfo? Member { get; }
31 | }
32 |
--------------------------------------------------------------------------------
/docs/features/performance.md:
--------------------------------------------------------------------------------
1 | # Performance
2 |
3 | .NET / JS interop is fast because:
4 | - Marshaling does not use JSON serialization.
5 | - Compile-time or runtime [code generation](./js-dotnet-marshalling#marshalling-code-generation)
6 | avoids reflection.
7 | - Use of shared memory and proxies minimizes data transfer.
8 | - Use of modern C# like `struct`, `Span`, and `stackalloc` minimizes heap allocation & copying.
9 |
10 | ## Performance comparison vs Edge.js
11 | Warm JS to .NET calls are nearly twice as fast when compared to
12 | [`edge-js`](https://github.com/agracio/edge-js) using
13 | [that project's benchmark](https://github.com/tjanczuk/edge/wiki/Performance).
14 |
15 | | | HTTP | Edge.js | Node API .NET | AOT | JS (baseline) |
16 | |-----:|------:|--------:|--------------:|----:|--------------:|
17 | | Cold | 32039 | 38761 | 9355 | 362 | 1680 |
18 | | Warm | 2003 | 87 | 54 | 47 | 23 |
19 |
20 | Numbers are _microseconds_. "Warm" is an average of 10000 .NET -> JS calls (passing a medium-size object).
21 |
--------------------------------------------------------------------------------
/examples/dotnet-module/README.md:
--------------------------------------------------------------------------------
1 |
2 | ## Minimal Example .NET Node Module
3 | The `Example.cs` class defines a Node.js add-on module that runs on .NET. The `example.js` script
4 | loads that .NET module and calls a method on it. The script has access to type definitions and
5 | doc-comments for the module's APIs via the auto-generated `.d.ts` file.
6 |
7 | | Command | Explanation
8 | |----------------------------------|--------------------------------------------------
9 | | `dotnet pack ../..` | Build Node API .NET packages.
10 | | `npm install` | Install Node API .NET npm package into example project.
11 | | `dotnet build` | Install Node API .NET nuget packages into example project; build example project.
12 | | `node example.js` | Run example JS code that calls the example module.
13 |
14 | ### .NET Framework
15 | To use .NET Framework, apply the follwing change to `example.js`:
16 | ```diff
17 | -const dotnet = require('node-api-dotnet');
18 | +const dotnet = require('node-api-dotnet/net472');
19 | ```
20 |
--------------------------------------------------------------------------------
/test/TestCases/napi-dotnet-init/ModuleInitializer.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using Microsoft.JavaScript.NodeApi.Interop;
6 |
7 | namespace Microsoft.JavaScript.NodeApi.TestCases;
8 |
9 | public class ModuleInitializer
10 | {
11 | [JSModule]
12 | public static JSValue Initialize(JSRuntimeContext context, JSObject exports)
13 | {
14 | Console.WriteLine("Module.Initialize()");
15 |
16 | string? exportValue = Environment.GetEnvironmentVariable("TEST_DOTNET_MODULE_INIT_EXPORT");
17 | if (!string.IsNullOrEmpty(exportValue))
18 | {
19 | // Export a single string value instead of the `exports` object.
20 | return exportValue;
21 | }
22 |
23 | // Export a module with a JS property that doesn't map to any C# property.
24 | JSModuleBuilder moduleBuilder = new();
25 | moduleBuilder.AddProperty("test", JSValue.GetBoolean(true));
26 | return moduleBuilder.ExportModule(context, exports);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/docs/reference/js-apis.md:
--------------------------------------------------------------------------------
1 | ---
2 | outline: deep
3 | ---
4 |
5 | # Runtime API Examples
6 |
7 | This page demonstrates usage of some of the runtime APIs provided by VitePress.
8 |
9 | The main `useData()` API can be used to access site, theme, and page data for the current page. It works in both `.md` and `.vue` files:
10 |
11 | ```md
12 |
17 |
18 | ## Results
19 |
20 | ### Theme Data
21 | {{ theme }}
22 |
23 | ### Page Data
24 | {{ page }}
25 |
26 | ### Page Frontmatter
27 | {{ frontmatter }}
28 | ```
29 |
30 |
35 |
36 | ## Results
37 |
38 | ### Theme Data
39 | {{ theme }}
40 |
41 | ### Page Data
42 | {{ page }}
43 |
44 | ### Page Frontmatter
45 | {{ frontmatter }}
46 |
47 | ## More
48 |
49 | Check out the documentation for the [full list of runtime APIs](https://vitepress.dev/reference/runtime-api#usedata).
50 |
--------------------------------------------------------------------------------
/examples/winappsdk/example.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | const dotnet = require('node-api-dotnet');
4 |
5 | require('./bin/Microsoft.Windows.SDK.NET.js');
6 | require('./bin/Microsoft.WindowsAppRuntime.Bootstrap.Net.js');
7 | require('./bin/Microsoft.InteractiveExperiences.Projection.js');
8 | require('./bin/Microsoft.Windows.ApplicationModel.WindowsAppRuntime.Projection.js');
9 | require('./bin/Microsoft.Windows.AI.Text.Projection.js');
10 | require('./bin/Microsoft.Windows.AI.ContentSafety.Projection.js');
11 | require('./bin/Microsoft.Windows.AppNotifications.Projection.js');
12 | require('./bin/Microsoft.Windows.AppNotifications.Builder.Projection.js');
13 |
14 | const majorVersion = 1;
15 | const minorVersion = 8;
16 |
17 | console.log("Attempt to initialize the WindowsAppRuntime Bootstrapper. (This requires the WindowsAppRuntime " + majorVersion + "." + minorVersion + " to be installed on the system.)");
18 | const fullVersion = (majorVersion << 16) | minorVersion;
19 | dotnet.Microsoft.Windows.ApplicationModel.DynamicDependency.Bootstrap.Initialize(fullVersion);
20 | console.log("Initialized Bootstraper. WindowsAppRuntime is now available.");
21 |
--------------------------------------------------------------------------------
/test/TestCases/napi-dotnet/ModuleClass.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using Microsoft.JavaScript.NodeApi.Interop;
6 |
7 | namespace Microsoft.JavaScript.NodeApi.TestCases;
8 |
9 | #pragma warning disable CA1822 // Mark members as static
10 |
11 | ///
12 | /// An instance of this class is constructed when the module is loaded and disposed when the
13 | /// module is unloaded. Public instance properties and methods on the module class are
14 | /// automatically exported.
15 | ///
16 | [JSModule]
17 | public sealed class ModuleClass : IDisposable
18 | {
19 | ///
20 | /// The module class must have a public constructor that takes either no parameters
21 | /// or a single JSRuntimeContext parameter.
22 | ///
23 | public ModuleClass()
24 | {
25 | }
26 |
27 | public void Dispose()
28 | {
29 | }
30 |
31 | public string ModuleProperty { get; set; } = "test";
32 |
33 | public string ModuleMethod(string greeter)
34 | {
35 | string stringValue = greeter;
36 | return $"Hello {stringValue}!";
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Microsoft Corporation.
4 | Copyright (c) 2022 Jason Ginchereau
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/test/TestCases/napi-dotnet/Another.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 |
6 | namespace Microsoft.JavaScript.NodeApi.TestCases;
7 |
8 | #pragma warning disable CA1822 // Mark members as static
9 |
10 | [JSExport]
11 | public class Another
12 | {
13 | public Another()
14 | {
15 | Console.WriteLine("Another({init})");
16 | }
17 |
18 | public static string StaticValue
19 | {
20 | get
21 | {
22 | Console.WriteLine("Another.StaticValue.get()");
23 | return "static";
24 | }
25 | }
26 |
27 | public string InstanceValue
28 | {
29 | get
30 | {
31 | Console.WriteLine("Another.InstanceValue.get()");
32 | return "instance";
33 | }
34 | }
35 |
36 | public static bool StaticMethod(bool arg1, int arg2)
37 | {
38 | Console.WriteLine($"Another.StaticMethod({arg1}, {arg2})");
39 | return true;
40 | }
41 |
42 | public bool InstanceMethod(string arg)
43 | {
44 | Console.WriteLine($"Another.InstanceMethod({arg})");
45 | return false;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/examples/hermes-engine/hermes-engine.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.5.33516.290
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "hermes-engine", "hermes-engine.csproj", "{EF350F77-7B60-4B5C-8634-8EB3153FB130}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {EF350F77-7B60-4B5C-8634-8EB3153FB130}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {EF350F77-7B60-4B5C-8634-8EB3153FB130}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {EF350F77-7B60-4B5C-8634-8EB3153FB130}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {EF350F77-7B60-4B5C-8634-8EB3153FB130}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {C85AD6C1-99F4-4858-BE2C-3065E5DD7CF1}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/test/TestCases/napi-dotnet/multi_instance.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | const assert = require('assert');
5 | const { Worker, isMainThread, parentPort } = require('worker_threads');
6 |
7 | /** @typedef {import('./napi-dotnet')} Binding */
8 | /** @type Binding */
9 | const binding = require('../common').binding;
10 |
11 | const testModulePath = require('../common').testModulePath;
12 | const isAot = /\.node$/.test(testModulePath);
13 |
14 | if (isMainThread) {
15 | // Increment the static counter to 2.
16 | const count1 = binding.Counter.count();
17 | assert.strictEqual(count1, 1);
18 | assert.strictEqual(binding.Counter.count(), 2);
19 |
20 | // AOT modules do not get reloaded when the node module is rebound, so their static data is
21 | // not isolated across threads. But .NET hosted modules do get reloaded with isolated static data,
22 | // so in that case the worker-thread counter should be independent.
23 | const expectedCount = isAot ? 3 : 1;
24 | const worker = new Worker(__filename);
25 | worker.on('message', (count3) => assert.strictEqual(count3, expectedCount));
26 | } else {
27 | const count3 = binding.Counter.count();
28 | parentPort.postMessage(count3)
29 | }
30 |
--------------------------------------------------------------------------------
/examples/semantic-kernel/README.md:
--------------------------------------------------------------------------------
1 |
2 | ## Example: Using .NET Semantic Kernel to call Azure OpenAI
3 | The `example.js` script dynamically loads the `Microsoft.SemanticKernel` .NET assembly and uses it
4 | to call Azure OpenAI to summarize some text. It is a direct JavaScript translation of the C# example
5 | code in the [Semantic Kernel](https://github.com/microsoft/semantic-kernel) project readme.
6 |
7 | To run this example, first set the following environment variables referencing your
8 | [Azure OpenAI deployment](https://learn.microsoft.com/en-us/azure/cognitive-services/openai/quickstart):
9 | - `OPENAI_ENDPOINT`
10 | - `OPENAI_DEPLOYMENT`
11 | - `OPENAI_KEY`
12 |
13 | Then run the following commands in sequence:
14 |
15 | | Command | Explanation
16 | |----------------------------------|--------------------------------------------------
17 | | `dotnet pack ../..` | Build Node API .NET packages.
18 | | `dotnet build` | Install `SemanticKernel` nuget packages into the project and generate type definitions.
19 | | `npm install` | Install `node-api-dotnet` npm package into the project.
20 | | `node example.js` | Run example JS code that uses the above packages to call the Azure OpenAI service.
21 |
--------------------------------------------------------------------------------
/test/TestCases/node-addon-api/object/has_own_property.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using Microsoft.JavaScript.NodeApi;
5 | using Microsoft.JavaScript.NodeApi.Runtime;
6 |
7 | namespace Microsoft.JavaScript.NodeApiTest;
8 |
9 | public partial class TestObject
10 | {
11 | private static JSValue HasOwnPropertyWithNapiValue(JSCallbackArgs args)
12 | {
13 | JSValue obj = args[0];
14 | JSValue key = args[1];
15 | return obj.HasOwnProperty((JSRuntime.napi_value)key);
16 | }
17 |
18 | private static JSValue HasOwnPropertyWithNapiWrapperValue(JSCallbackArgs args)
19 | {
20 | JSValue obj = args[0];
21 | JSValue key = args[1];
22 | return obj.HasOwnProperty(key);
23 | }
24 |
25 | private static JSValue HasOwnPropertyWithUtf8StyleString(JSCallbackArgs args)
26 | {
27 | JSValue obj = args[0];
28 | JSValue key = args[1];
29 | return obj.HasOwnProperty(JSValue.CreateStringUtf8(key.GetValueStringUtf8()));
30 | }
31 |
32 | private static JSValue HasOwnPropertyWithCSharpStyleString(JSCallbackArgs args)
33 | {
34 | JSValue obj = args[0];
35 | JSValue key = args[1];
36 | return obj.HasOwnProperty((string)key);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/docs/contributing.md:
--------------------------------------------------------------------------------
1 | ---
2 | prev: false
3 | next: false
4 | ---
5 |
6 | # Contributing
7 |
8 | For information about building, testing, and contributing changes to this project, see
9 | [README-DEV.md](https://github.com/microsoft/node-api-dotnet/blob/main/README-DEV.md).
10 |
11 | ## Contributor License Agreement
12 |
13 | This project welcomes contributions and suggestions. Most contributions require you to agree to a
14 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
15 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
16 |
17 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide
18 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the
19 | instructions provided by the bot. You will only need to do this once across all repos using our CLA.
20 |
21 | ## Code of Conduct
22 |
23 | This project has adopted the
24 | [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
25 | For more information see the
26 | [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact
27 | [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
28 |
--------------------------------------------------------------------------------
/test/TestCases/napi-dotnet/Generics.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | namespace Microsoft.JavaScript.NodeApi.TestCases;
5 |
6 | #pragma warning disable CA1822 // Mark members as static
7 |
8 | // Note these types do not have [JSExport] attributes: static binding does not support generics.
9 |
10 | public interface IGenericInterface
11 | {
12 | T Value { get; set; }
13 | }
14 |
15 | public class GenericClass : IGenericInterface
16 | {
17 | public GenericClass(T value) { Value = value; }
18 | public T Value { get; set; }
19 | public T GetValue(T value) => value;
20 | }
21 |
22 | public class GenericClassWithConstraint where T : struct
23 | {
24 | public GenericClassWithConstraint(T value) { Value = value; }
25 | public T Value { get; set; }
26 | public T GetValue(T value) => value;
27 | }
28 |
29 | public struct GenericStruct
30 | {
31 | public GenericStruct(T value) { Value = value; }
32 | public T Value { get; set; }
33 | public readonly T GetValue(T value) => value;
34 | }
35 |
36 | public static class StaticClassWithGenericMethods
37 | {
38 | public static T GetValue(T value) => value;
39 | }
40 |
41 | public class NonstaticClassWithGenericMethods
42 | {
43 | public T GetValue(T value) => value;
44 | }
45 |
--------------------------------------------------------------------------------
/src/NodeApi/Interop/JSModuleContext.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 |
6 | namespace Microsoft.JavaScript.NodeApi.Interop;
7 |
8 | ///
9 | /// Manages JavaScript interop context for the lifetime of a .NET module.
10 | ///
11 | ///
12 | /// A instance is constructed when the module is loaded and disposed
13 | /// when the module is unloaded.
14 | ///
15 | public sealed class JSModuleContext : IDisposable
16 | {
17 | ///
18 | /// Gets the current module context.
19 | ///
20 | public static JSModuleContext Current => JSValueScope.Current.ModuleContext
21 | ?? throw new InvalidCastException("No current module context.");
22 |
23 | ///
24 | /// Gets an instance of the class that represents the module, or null if there is no module
25 | /// class.
26 | ///
27 | public object? Module { get; internal set; }
28 |
29 | public bool IsDisposed { get; private set; }
30 |
31 | public void Dispose()
32 | {
33 | if (IsDisposed) return;
34 |
35 | IsDisposed = true;
36 |
37 | if (Module is IDisposable module)
38 | {
39 | module.Dispose();
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/docs/reference/streams.md:
--------------------------------------------------------------------------------
1 | # Streams
2 |
3 | | C# Type | JS Type |
4 | |-----------------------|------------|
5 | | `Stream` (read/write) | `Duplex` |
6 | | `Stream` (read-only) | `Readable` |
7 | | `Stream` (write-only) | `Writable` |
8 |
9 | A .NET `Stream` instance is marshalled to and from Node.js
10 | [`Duplex`](https://nodejs.org/api/stream.html#duplex-and-transform-streams),
11 | [`Readable`](https://nodejs.org/api/stream.html#readable-streams), or
12 | [`Writable`](https://nodejs.org/api/stream.html#writable-streams),
13 | depending whether the stream supports reading and/or writing. JS code can seamlessly read from
14 | or write to streams created by .NET, or .NET code can read from or write to streams created by JS.
15 | Streamed data is transferred using shared memory (without any additional sockets or pipes), so
16 | memory allocation and copying is minimized.
17 |
18 | ```C#
19 | [JSExport]
20 | public static class Example
21 | {
22 | public Stream GetContent() { … }
23 | }
24 | ```
25 |
26 | ```JS
27 | const stream = Example.getContent();
28 | stream.on('data', (chunk) => { console.log(chunk.toString()); });
29 | ```
30 |
31 | The [`NodeStream`](./dotnet/Microsoft.JavaScript.NodeApi.Interop/NodeStream) class provides the
32 | .NET `Stream` adapter over a Node.js `Duplex`, `Readable`, or `Writable` stream.
33 |
--------------------------------------------------------------------------------
/examples/hermes-engine/Program.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using Hermes.Example;
5 | using Microsoft.JavaScript.NodeApi;
6 |
7 | JSDispatcherQueueController controller = JSDispatcherQueueController.CreateOnDedicatedThread();
8 |
9 | HermesRuntime runtime = await HermesRuntime.Create(controller.DispatcherQueue);
10 |
11 | await runtime.RunAsync(() =>
12 | {
13 | JSNativeApi.RunScript("x = 2");
14 | Console.WriteLine($"Result: {(int)JSValue.Global["x"]}");
15 | Console.Out.Flush();
16 |
17 | JSNativeApi.RunScript("""
18 | setTimeout(function() {
19 | console.log('This is printed after 1 second delay');
20 | }, 1000);
21 | """);
22 |
23 | JSNativeApi.RunScript("""
24 | function createPromise(delay, value) {
25 | return new Promise(function(resolve) {
26 | setTimeout(function() {
27 | resolve(value);
28 | }, delay);
29 | });
30 | }
31 |
32 | var myPromise = createPromise(100, "myPromise");
33 | myPromise.then(msg => { console.log(msg + " completed"); })
34 | .catch(() => { console.log("myPromise canceled"); });
35 | """);
36 | });
37 |
38 | await runtime.CloseAsync();
39 | await controller.ShutdownQueueAsync();
40 |
--------------------------------------------------------------------------------
/test/TestCases/Directory.Build.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
13 |
14 |
15 |
16 |
19 |
20 |
21 |
22 |
25 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/test/TestCases/projects/Module.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | #pragma warning disable CA1050 // Declare types in namespaces
5 | #pragma warning disable CA1822 // Mark members as static
6 |
7 | using Microsoft.JavaScript.NodeApi;
8 |
9 | [assembly: JSExport]
10 |
11 | // Tests exporting top-level properties on the JS module.
12 | [JSExport(false)]
13 | public static class ModuleProperties
14 | {
15 | [JSExport]
16 | public static string ReadOnlyProperty { get; } = "ROProperty";
17 |
18 | [JSExport]
19 | public static string ReadWriteProperty { get; set; } = "RWProperty";
20 |
21 | [JSExport]
22 | public static string Method(string arg) => arg;
23 | }
24 |
25 | public class ModuleClass
26 | {
27 | public ModuleClass(string value)
28 | {
29 | Property = value;
30 | }
31 |
32 | public string Property { get; }
33 |
34 | public string Method(string arg) => arg;
35 | }
36 |
37 | namespace TestNamespace
38 | {
39 | [JSExport("default")]
40 | public static class DefaultClass
41 | {
42 | public static string Method(string arg) => arg;
43 |
44 | public static TestEnum EnumProperty { get; set; }
45 | }
46 |
47 | [JSExport("ModuleEnum")]
48 | public enum TestEnum
49 | {
50 | None = 0,
51 | One = 1,
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Directory.Packages.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | true
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/examples/winui-fluid/app.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
21 | true/PM
22 | PerMonitorV2, PerMonitor
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/test/TestCases/node-addon-api/object/set_property.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | 'use strict';
5 |
6 | const assert = require('assert');
7 |
8 | module.exports = require('../../common').runTest(test);
9 |
10 | function test(binding) {
11 | function testSetProperty(nativeSetProperty, key = 'test') {
12 | const obj = {};
13 | nativeSetProperty(obj, key, 1);
14 | assert.strictEqual(obj[key], 1);
15 | }
16 |
17 | function testShouldThrowErrorIfKeyIsInvalid(nativeSetProperty) {
18 | assert.throws(() => {
19 | nativeSetProperty(undefined, 'test', 1);
20 | }, /Cannot convert undefined or null to object/);
21 | }
22 |
23 | testSetProperty(binding.object.setPropertyWithNapiValue);
24 | testSetProperty(binding.object.setPropertyWithNapiWrapperValue);
25 | testSetProperty(binding.object.setPropertyWithUtf8StyleString);
26 | testSetProperty(binding.object.setPropertyWithCSharpStyleString);
27 | testSetProperty(binding.object.setPropertyWithUInt32, 12);
28 |
29 | testShouldThrowErrorIfKeyIsInvalid(binding.object.setPropertyWithNapiValue);
30 | testShouldThrowErrorIfKeyIsInvalid(binding.object.setPropertyWithNapiWrapperValue);
31 | testShouldThrowErrorIfKeyIsInvalid(binding.object.setPropertyWithUtf8StyleString);
32 | testShouldThrowErrorIfKeyIsInvalid(binding.object.setPropertyWithCSharpStyleString);
33 | }
34 |
--------------------------------------------------------------------------------
/docs/reference/async-promises.md:
--------------------------------------------------------------------------------
1 | # Async, Tasks, and Promises
2 |
3 | | C# Type | JS Type |
4 | |-----------|-----------------|
5 | | `Task` | `Promise` |
6 | | `Task` | `Promise` |
7 |
8 | ## Async methods
9 |
10 | A .NET method that returns a `Task` or `Task` can be awaited in JavaScript because the
11 | `Task` is automatically marshalled as a JS `Promise`:
12 |
13 | ```C#
14 | [JSExport]
15 | public static class AsyncExample
16 | {
17 | public async Task GetResultAsync();
18 | }
19 | ```
20 |
21 | ```JS
22 | const result = await AsyncExample.getResultAsync();
23 | ```
24 |
25 | A JavaScript method that returns a `Promise` can be awaited in C# by converting the `Promise` to a
26 | `Task`:
27 |
28 | ```C#
29 | JSFunction exampleAsyncJSFunc = …
30 | string result = await exampleAsyncJSFunc.CallAsStatic(arg).AsTask();
31 | ```
32 |
33 | ## `JSPromise` type
34 |
35 | The [`JSPromise`](./dotnet/Microsoft.JavaScript.NodeApi/JSPromise) type supports working directly
36 | with JavaScript `Promise` values, while the extension methods in the
37 | [`JSPromiseExtensions`](./dotnet/Microsoft.JavaScript.NodeApi/TaskExtensions)
38 | class enable converting between `JSPromise` and `Task` values.
39 |
40 | ## Async execution
41 | See [JS Threading and Async Continuations](../features/js-threading-async) for more about
42 | coordinating asynchronous .NET and JavaScript execution.
43 |
--------------------------------------------------------------------------------
/test/TestCases/node-addon-api/basic_types/array.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | 'use strict';
5 | const assert = require('assert');
6 |
7 | module.exports = require('../../common').runTest(test);
8 |
9 | function test(binding) {
10 | // create empty array
11 | const array = binding.basicTypesArray.createArray();
12 | assert.strictEqual(binding.basicTypesArray.getLength(array), 0);
13 |
14 | // create array with length
15 | const arrayWithLength = binding.basicTypesArray.createArray(10);
16 | assert.strictEqual(binding.basicTypesArray.getLength(arrayWithLength), 10);
17 |
18 | // set function test
19 | binding.basicTypesArray.set(array, 0, 10);
20 | binding.basicTypesArray.set(array, 1, 'test');
21 | binding.basicTypesArray.set(array, 2, 3.0);
22 |
23 | // check length after set data
24 | assert.strictEqual(binding.basicTypesArray.getLength(array), 3);
25 |
26 | // get function test
27 | assert.strictEqual(binding.basicTypesArray.get(array, 0), 10);
28 | assert.strictEqual(binding.basicTypesArray.get(array, 1), 'test');
29 | assert.strictEqual(binding.basicTypesArray.get(array, 2), 3.0);
30 |
31 | // overwrite test
32 | binding.basicTypesArray.set(array, 0, 5);
33 | assert.strictEqual(binding.basicTypesArray.get(array, 0), 5);
34 |
35 | // out of index test
36 | assert.strictEqual(binding.basicTypesArray.get(array, 5), undefined);
37 | }
38 |
--------------------------------------------------------------------------------
/examples/wpf/example.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | // @ts-check
5 |
6 | import * as path from 'node:path';
7 | import { fileURLToPath } from 'node:url';
8 | const __filename = fileURLToPath(import.meta.url);
9 | const __dirname = path.dirname(__filename);
10 |
11 | import dotnet from 'node-api-dotnet';
12 | import './bin/PresentationFramework.js';
13 | import './bin/WpfExample.js';
14 |
15 | // Explicitly load some assembly dependencies that are not automatically loaded
16 | // by the NodeApi assembly resolver. (This may be improved in the future.)
17 | dotnet.load('System.Configuration.ConfigurationManager');
18 | dotnet.load('System.Windows.Extensions');
19 | dotnet.load(__dirname + '/pkg/microsoft.web.webview2/1.0.2088.41/lib/netcoreapp3.0/Microsoft.Web.WebView2.Wpf.dll');
20 | dotnet.load('PresentationFramework.Aero2');
21 |
22 | // Explicitly load some native library dependencies.
23 | dotnet.load('wpfgfx_cor3');
24 | dotnet.load(__dirname + '/bin/runtimes/win-x64/native/WebView2Loader.dll');
25 |
26 | // Show a simple message box. (This doesn't need most of the dependencies.)
27 | ////dotnet.System.Windows.MessageBox.Show('Hello from JS!', "Example");
28 |
29 | // Show a WPF window with a WebView2 control that renders a mermaid diagram.
30 | const diagram = 'graph TD\n A[Hello from JS!]';
31 | dotnet.Microsoft.JavaScript.NodeApi.Examples.Window1.CreateWebView2Window(diagram);
32 |
--------------------------------------------------------------------------------
/src/NodeApi/JSValueScopeClosedException.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 |
6 | namespace Microsoft.JavaScript.NodeApi;
7 |
8 | ///
9 | /// An exception that was caused by an attempt to access a (or a more
10 | /// specific JS value type, such as or )
11 | /// after its was closed.
12 | ///
13 | public class JSValueScopeClosedException : ObjectDisposedException
14 | {
15 | ///
16 | /// Creates a new instance of with an optional
17 | /// object name and message.
18 | ///
19 | public JSValueScopeClosedException(JSValueScope scope, string? message = null)
20 | : base(scope.ScopeType.ToString(), message ?? GetMessage(scope))
21 | {
22 | Scope = scope;
23 | }
24 |
25 | public JSValueScope Scope { get; }
26 |
27 | private static string GetMessage(JSValueScope scope)
28 | {
29 | return $"The JS value scope of type {scope.ScopeType} was closed.\n" +
30 | "Values created within a scope are no longer available after their scope is " +
31 | "closed. Consider using an escapable scope to promote a value to the parent scope, " +
32 | "or a reference to make a value available to a future callback scope.";
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/test/TestCases/napi-dotnet/async_methods.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | const assert = require('assert');
5 | const common = require('../common');
6 |
7 | /** @type {import('./napi-dotnet')} */
8 | const binding = common.binding;
9 |
10 | common.runTest(async () => {
11 | const result = await binding.async_method('buddy');
12 | assert.strictEqual(result, 'Hey buddy!');
13 |
14 | const result2 = await binding.async_method_cs('buddy');
15 | assert.strictEqual(result2, 'Hey buddy!');
16 |
17 | const result3 = await binding.async_interface.testAsync('buddy');
18 | assert.strictEqual(result3, 'Hey buddy!');
19 |
20 | // Invoke a C# method that calls back to a JS object that implements an interface.
21 | const asyncInterfaceImpl = {
22 | async testAsync(greeting) { return `Hello, ${greeting}!`; }
23 | };
24 | const result4 = await binding.async_interface_reverse(asyncInterfaceImpl, 'buddy');
25 | assert.strictEqual(result4, 'Hello, buddy!');
26 |
27 | // A JS object that implements an interface can be returned from C#.
28 | binding.async_interface = asyncInterfaceImpl;
29 | assert.strictEqual(binding.async_interface, asyncInterfaceImpl);
30 |
31 | // Invoke C# methods that return ValueTask.
32 | await binding.async_method_valuetask();
33 | const result5 = await binding.async_method_valuetask_of_string('buddy');
34 | assert.strictEqual(result5, 'Hey buddy!');
35 | });
36 |
37 |
--------------------------------------------------------------------------------
/test/TestCases/node-addon-api/object/get_property.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using Microsoft.JavaScript.NodeApi;
5 | using Microsoft.JavaScript.NodeApi.Runtime;
6 |
7 | namespace Microsoft.JavaScript.NodeApiTest;
8 |
9 | public partial class TestObject
10 | {
11 | private static JSValue GetPropertyWithNapiValue(JSCallbackArgs args)
12 | {
13 | JSValue obj = args[0];
14 | JSValue key = args[1];
15 | return obj.GetProperty((JSRuntime.napi_value)key);
16 | }
17 |
18 | private static JSValue GetPropertyWithNapiWrapperValue(JSCallbackArgs args)
19 | {
20 | JSValue obj = args[0];
21 | JSValue key = args[1];
22 | return obj.GetProperty(key);
23 | }
24 |
25 | private static JSValue GetPropertyWithUtf8StyleString(JSCallbackArgs args)
26 | {
27 | JSValue obj = args[0];
28 | JSValue key = args[1];
29 | return obj.GetProperty(JSValue.CreateStringUtf8(key.GetValueStringUtf8()));
30 | }
31 |
32 | private static JSValue GetPropertyWithCSharpStyleString(JSCallbackArgs args)
33 | {
34 | JSValue obj = args[0];
35 | JSValue key = args[1];
36 | return obj.GetProperty((string)key);
37 | }
38 |
39 | private static JSValue GetPropertyWithUInt32(JSCallbackArgs args)
40 | {
41 | JSValue obj = args[0];
42 | JSValue key = args[1];
43 | return obj.GetProperty((uint)key);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/test/TestCases/node-addon-api/object/has_property.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using Microsoft.JavaScript.NodeApi;
5 | using Microsoft.JavaScript.NodeApi.Runtime;
6 |
7 | namespace Microsoft.JavaScript.NodeApiTest;
8 |
9 | public partial class TestObject
10 | {
11 | private static JSValue HasPropertyWithNapiValue(JSCallbackArgs args)
12 | {
13 | JSValue obj = args[0];
14 | JSValue key = args[1];
15 | return obj.HasProperty((JSRuntime.napi_value)key);
16 | }
17 |
18 | private static JSValue HasPropertyWithNapiWrapperValue(JSCallbackArgs args)
19 | {
20 | JSValue obj = args[0];
21 | JSValue key = args[1];
22 | return obj.HasProperty(key);
23 | }
24 |
25 | private static JSValue HasPropertyWithUtf8StyleString(JSCallbackArgs args)
26 | {
27 | JSValue obj = args[0];
28 | JSValue key = args[1];
29 | return obj.HasProperty(JSValue.CreateStringUtf8(key.GetValueStringUtf8()));
30 | }
31 |
32 | private static JSValue HasPropertyWithCSharpStyleString(JSCallbackArgs args)
33 | {
34 | JSValue obj = args[0];
35 | JSValue key = args[1];
36 | return obj.HasProperty((string)key);
37 | }
38 |
39 | private static JSValue HasPropertyWithUInt32(JSCallbackArgs args)
40 | {
41 | JSValue obj = args[0];
42 | JSValue key = args[1];
43 | return obj.HasProperty((uint)key);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/examples/electron/README.md:
--------------------------------------------------------------------------------
1 |
2 | ## Invoking .NET APIs from an Electron app
3 |
4 | | Command | Explanation
5 | |----------------------------------|--------------------------------------------------
6 | | `dotnet pack ../..` | Build Node API .NET packages.
7 | | `npm install` | Install `node-api-dotnet` and `electron` npm packages into the example project.
8 | | `npm run start` | Run the example Electron app.
9 |
10 | The example is from the [Electron "Quick Start" app](
11 | https://www.electronjs.org/docs/latest/tutorial/quick-start),
12 | with some small modifications to additionally load .NET and display the .NET version obtained from
13 | `System.Environment.Version`.
14 |
15 | The `node-api-dotnet` package is loaded in the _main_ Electron process immediately after creating
16 | the window. Then the .NET version value is sent via IPC to the _renderer_ process where it is
17 | displayed on the HTML page.
18 |
19 | ---
20 |
21 | This is not intended to be an attempt to replace or compete with the great work at
22 | [Electron.NET](https://github.com/ElectronNET/Electron.NET). That project runs a full ASP.NET +
23 | Blazor stack inside an Electron app, enabling use of Blazor components to build a cross-platform
24 | client application UI.
25 |
26 | However if you're building a more traditional Electron JS app and you just need to call a few
27 | .NET APIs from JS code, then the example here offers a more lightweight approach.
28 |
--------------------------------------------------------------------------------
/test/TestCases/napi-dotnet/ExtensionMethods.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 |
6 | namespace Microsoft.JavaScript.NodeApi.TestCases;
7 |
8 | public class SubclassOfGenericClass : GenericClass
9 | {
10 | public SubclassOfGenericClass(string value) : base(value) { }
11 |
12 | public static IGenericInterface Create(string value)
13 | => new SubclassOfGenericClass(value);
14 | }
15 |
16 | public static class TestClassExtensions
17 | {
18 | public static string GetValueOrDefault(this ClassObject obj, string defaultValue)
19 | => obj.Value ?? defaultValue;
20 |
21 | public static T GetGenericValueOrDefault(this GenericClass obj, T defaultValue)
22 | => obj.Value ?? defaultValue;
23 |
24 | public static string GetGenericStringValueOrDefault(this GenericClass obj, string defaultValue)
25 | => obj.Value ?? defaultValue;
26 | }
27 |
28 | public static class TestInterfaceExtensions
29 | {
30 | public static int? ToInteger(this ITestInterface obj)
31 | => int.TryParse(obj.Value, out int value) ? (int?)value : null;
32 |
33 | public static int? GenericToInteger(this IGenericInterface obj)
34 | => int.TryParse(obj.Value?.ToString(), out int value) ? (int?)value : null;
35 |
36 | public static int? GenericStringToInteger(this IGenericInterface obj)
37 | => int.TryParse(obj.Value, out int value) ? (int?)value : null;
38 | }
39 |
--------------------------------------------------------------------------------
/test/TestCases/node-addon-api/testUtil.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | // Run each test function in sequence,
5 | // with an async delay and GC call between each.
6 |
7 | function tick(x) {
8 | return new Promise((resolve) => {
9 | setImmediate(function ontick() {
10 | if (--x === 0) {
11 | resolve();
12 | } else {
13 | setImmediate(ontick);
14 | }
15 | });
16 | });
17 | }
18 |
19 | async function runGCTests(tests) {
20 | // Break up test list into a list of lists of the form
21 | // [ [ 'test name', function() {}, ... ], ..., ].
22 | const testList = [];
23 | let currentTest;
24 | for (const item of tests) {
25 | if (typeof item === 'string') {
26 | currentTest = [];
27 | testList.push(currentTest);
28 | }
29 | currentTest.push(item);
30 | }
31 |
32 | for (const test of testList) {
33 | await (async function (test) {
34 | let title;
35 | for (let i = 0; i < test.length; i++) {
36 | if (i === 0) {
37 | title = test[i];
38 | } else {
39 | try {
40 | test[i]();
41 | } catch (e) {
42 | console.error('Test failed: ' + title);
43 | throw e;
44 | }
45 | if (i < tests.length - 1) {
46 | global.gc();
47 | await tick(10);
48 | }
49 | }
50 | }
51 | })(test);
52 | }
53 | }
54 |
55 | module.exports = {
56 | runGCTests
57 | };
58 |
--------------------------------------------------------------------------------
/test/TestCases/node-addon-api/object/get_property.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | 'use strict';
5 |
6 | const assert = require('assert');
7 |
8 | module.exports = require('../../common').runTest(test);
9 |
10 | function test(binding) {
11 | function testGetProperty(nativeGetProperty) {
12 | const obj = { test: 1 };
13 | assert.strictEqual(nativeGetProperty(obj, 'test'), 1);
14 | }
15 |
16 | function testShouldReturnUndefinedIfKeyIsNotPresent(nativeGetProperty) {
17 | const obj = {};
18 | assert.strictEqual(nativeGetProperty(obj, 'test'), undefined);
19 | }
20 |
21 | function testShouldThrowErrorIfKeyIsInvalid(nativeGetProperty) {
22 | assert.throws(() => {
23 | nativeGetProperty(undefined, 'test');
24 | }, /Cannot convert undefined or null to object/);
25 | }
26 |
27 | const testObject = { 42: 100 };
28 | const property = binding.object.getPropertyWithUInt32(testObject, 42);
29 | assert.strictEqual(property, 100);
30 |
31 | const nativeFunctions = [
32 | binding.object.getPropertyWithNapiValue,
33 | binding.object.getPropertyWithNapiWrapperValue,
34 | binding.object.getPropertyWithUtf8StyleString,
35 | binding.object.getPropertyWithCSharpStyleString
36 | ];
37 |
38 | nativeFunctions.forEach((nativeFunction) => {
39 | testGetProperty(nativeFunction);
40 | testShouldReturnUndefinedIfKeyIsNotPresent(nativeFunction);
41 | testShouldThrowErrorIfKeyIsInvalid(nativeFunction);
42 | });
43 | }
44 |
--------------------------------------------------------------------------------
/test/TestCases/node-addon-api/object/delete_property.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using Microsoft.JavaScript.NodeApi;
5 | using Microsoft.JavaScript.NodeApi.Runtime;
6 |
7 | namespace Microsoft.JavaScript.NodeApiTest;
8 |
9 | public partial class TestObject
10 | {
11 | private static JSValue DeletePropertyWithNapiValue(JSCallbackArgs args)
12 | {
13 | JSValue obj = args[0];
14 | JSValue key = args[1];
15 | return obj.DeleteProperty((JSRuntime.napi_value)key);
16 | }
17 |
18 | private static JSValue DeletePropertyWithNapiWrapperValue(JSCallbackArgs args)
19 | {
20 | JSValue obj = args[0];
21 | JSValue key = args[1];
22 | return obj.DeleteProperty(key);
23 | }
24 |
25 | private static JSValue DeletePropertyWithUtf8StyleString(JSCallbackArgs args)
26 | {
27 | JSValue obj = args[0];
28 | JSValue key = args[1];
29 | return obj.DeleteProperty(JSValue.CreateStringUtf8(key.GetValueStringUtf8()));
30 | }
31 |
32 | private static JSValue DeletePropertyWithCSharpStyleString(JSCallbackArgs args)
33 | {
34 | JSValue obj = args[0];
35 | JSValue key = args[1];
36 | return obj.DeleteProperty((string)key);
37 | }
38 |
39 | private static JSValue DeletePropertyWithUInt32(JSCallbackArgs args)
40 | {
41 | JSValue obj = args[0];
42 | JSValue key = args[1];
43 | return obj.DeleteProperty((uint)key);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/examples/aot-npm-package/lib/aot-npm-package.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 |
6 |
7 | true
8 |
9 |
10 | true
11 | true
12 | bin
13 |
14 |
17 | true
18 |
19 |
20 |
21 | true
22 | pkg
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/test/TestCases/napi-dotnet/thread_safety.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | const assert = require('assert');
5 |
6 | /** @type {import('./napi-dotnet')} */
7 | const binding = require('../common').binding;
8 |
9 | const ThreadSafety = binding.ThreadSafety;
10 |
11 | async function test() {
12 | let delegateCalled = false;
13 | await ThreadSafety.callDelegateFromOtherThread(() => delegateCalled = true);
14 | assert(delegateCalled);
15 |
16 | let interfaceMethodArgument = false;
17 | await ThreadSafety.callInterfaceMethodFromOtherThread(
18 | {
19 | echo: (value) => {
20 | interfaceMethodArgument = value;
21 | return value;
22 | }
23 | },
24 | 'test');
25 | assert.strictEqual(interfaceMethodArgument, 'test');
26 |
27 | const count = await ThreadSafety.enumerateCollectionFromOtherThread([1, 2, 3]);
28 | assert.strictEqual(count, 3);
29 |
30 | const map = new Map();
31 | map.set('a', '1');
32 | map.set('b', '2');
33 | map.set('c', '3');
34 | const mapSize = await ThreadSafety.enumerateDictionaryFromOtherThread(map);
35 | assert.strictEqual(mapSize, 3);
36 |
37 | const modifyResult = await ThreadSafety.modifyDictionaryFromOtherThread(map, 'a');
38 | assert(modifyResult);
39 | assert(!map.has('a'));
40 | assert.strictEqual(map.size, 2);
41 | }
42 | test().catch((err) => {
43 | console.error(err);
44 | process.exit(1);
45 | });
46 |
--------------------------------------------------------------------------------
/test/TestCases/node-addon-api/bigint.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | 'use strict';
5 | const assert = require('assert');
6 |
7 | module.exports = require('../common').runTest(test);
8 |
9 | function test (binding) {
10 | const {
11 | isLossless,
12 | isBigInt,
13 | testInt64,
14 | testUInt64,
15 | testWords,
16 | testWordSpan,
17 | testBigInteger
18 | } = binding.bigInt;
19 |
20 | [
21 | 0n,
22 | -0n,
23 | 1n,
24 | -1n,
25 | 100n,
26 | 2121n,
27 | -1233n,
28 | 986583n,
29 | -976675n,
30 | 98765432213456789876546896323445679887645323232436587988766545658n,
31 | -4350987086545760976737453646576078997096876957864353245245769809n
32 | ].forEach((num) => {
33 | if (num > -(2n ** 63n) && num < 2n ** 63n) {
34 | assert.strictEqual(testInt64(num), num);
35 | assert.strictEqual(isLossless(num, true), true);
36 | } else {
37 | assert.strictEqual(isLossless(num, true), false);
38 | }
39 |
40 | if (num >= 0 && num < 2n ** 64n) {
41 | assert.strictEqual(testUInt64(num), num);
42 | assert.strictEqual(isLossless(num, false), true);
43 | } else {
44 | assert.strictEqual(isLossless(num, false), false);
45 | }
46 |
47 | assert.strictEqual(isBigInt(num), true);
48 |
49 | assert.strictEqual(num, testWords(num));
50 | assert.strictEqual(num, testWordSpan(num));
51 | assert.strictEqual(num, testBigInteger(num, num.toString()));
52 | });
53 | }
54 |
--------------------------------------------------------------------------------
/examples/wpf/WpfExample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0-windows
5 | $(MSBuildThisFileDirectory)/pkg
6 | bin
7 | true
8 | true
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/NodeApi/Runtime/NodeEmbeddingNodeApiScope.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | namespace Microsoft.JavaScript.NodeApi.Runtime;
5 |
6 | using System;
7 | using static JSRuntime;
8 | using static NodejsRuntime;
9 |
10 | public sealed class NodeEmbeddingNodeApiScope : IDisposable
11 | {
12 | readonly NodeEmbeddingRuntime _runtime;
13 | private node_embedding_node_api_scope _nodeApiScope;
14 | private readonly JSValueScope _valueScope;
15 |
16 | public NodeEmbeddingNodeApiScope(NodeEmbeddingRuntime runtime)
17 | {
18 | _runtime = runtime;
19 | NodeEmbedding.JSRuntime.EmbeddingRuntimeOpenNodeApiScope(
20 | runtime.Handle, out _nodeApiScope, out napi_env env)
21 | .ThrowIfFailed();
22 | _valueScope = new JSValueScope(
23 | JSValueScopeType.Root, env, NodeEmbedding.JSRuntime);
24 | }
25 |
26 | ///
27 | /// Gets a value indicating whether the Node.js embedding Node-API scope is disposed.
28 | ///
29 | public bool IsDisposed { get; private set; }
30 |
31 | ///
32 | /// Disposes the Node.js embedding Node-API scope.
33 | ///
34 | public void Dispose()
35 | {
36 | if (IsDisposed) return;
37 | IsDisposed = true;
38 |
39 | _valueScope.Dispose();
40 | NodeEmbedding.JSRuntime.EmbeddingRuntimeCloseNodeApiScope(
41 | _runtime.Handle, _nodeApiScope)
42 | .ThrowIfFailed();
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Directory.Build.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
15 |
18 |
19 | %(PackageVersion.Version)
20 |
21 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/examples/winui-fluid/index.js:
--------------------------------------------------------------------------------
1 | const { TinyliciousClient } = require("@fluidframework/tinylicious-client");
2 | const { ConnectionState, SharedString } = require("fluid-framework");
3 |
4 | /** @type import('@fluidframework/common-definitions').ITelemetryBaseLogger */
5 | const logger = {
6 | send: (event) => {
7 | console.log(`[fluid:${event.category}] ${event.eventName}`);
8 | }
9 | };
10 |
11 | /** @returns {import('fluid-framework').SharedString} */
12 | const getFluidData = async () => {
13 | const client = new TinyliciousClient({ logger });
14 |
15 | /** @type import('fluid-framework').ContainerSchema */
16 | const containerSchema = {
17 | initialObjects: { sharedString: SharedString }
18 | };
19 | /** @type import('fluid-framework').IFluidContainer */
20 | let container;
21 | const containerId = ''; // TODO: Get from UI? Or generate and show in UI?
22 | if (!containerId) {
23 | ({ container } = await client.createContainer(containerSchema));
24 | const id = await container.attach();
25 | console.log('container id: ' + id);
26 | //window.location.hash = id;
27 | } else {
28 | ({ container } = await client.getContainer(containerId, containerSchema));
29 | if (container.connectionState !== ConnectionState.Connected) {
30 | await new Promise((resolve) => {
31 | container.once("connected", resolve);
32 | });
33 | console.log('connected to ' + containerId);
34 | }
35 | }
36 | return container.initialObjects.sharedString;
37 | };
38 | getFluidData();
39 |
--------------------------------------------------------------------------------
/src/NodeApi/Interop/JSModuleBuilderOfT.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Linq;
6 |
7 | namespace Microsoft.JavaScript.NodeApi.Interop;
8 |
9 | ///
10 | /// Builds JS module exports.
11 | ///
12 | /// Either or a custom module class that
13 | /// wraps a instance.
14 | public class JSModuleBuilder : JSPropertyDescriptorList, T> where T : class
15 | {
16 | public JSModuleBuilder() : base(Unwrap)
17 | {
18 | }
19 |
20 | private static new T? Unwrap(JSCallbackArgs _)
21 | {
22 | return (T?)JSModuleContext.Current.Module;
23 | }
24 |
25 | ///
26 | /// Exports the built properties to the module exports object.
27 | ///
28 | /// An object that represents the module instance and is
29 | /// used as the 'this' argument for any non-static methods on the module. If the object
30 | /// implements then it is also registered for disposal when
31 | /// the module is unloaded.
32 | /// Object to be returned from the module initializer.
33 | /// The module exports.
34 | public JSValue ExportModule(T module, JSObject exports)
35 | {
36 | JSModuleContext.Current.Module = module;
37 | exports.DefineProperties(Properties.ToArray());
38 | return exports;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/NodeApi/JSAsyncIterable.Enumerator.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Threading.Tasks;
7 |
8 | namespace Microsoft.JavaScript.NodeApi;
9 |
10 | public partial struct JSAsyncIterable
11 | {
12 | public struct Enumerator : IAsyncEnumerator
13 | {
14 | private readonly JSValue _iterable;
15 | private readonly JSValue _iterator;
16 | private JSValue? _current;
17 |
18 | internal Enumerator(JSValue iterable)
19 | {
20 | _iterable = iterable;
21 | _iterator = _iterable.CallMethod(JSSymbol.AsyncIterator);
22 | _current = default;
23 | }
24 |
25 | public async ValueTask MoveNextAsync()
26 | {
27 | var nextPromise = (JSPromise)_iterator.CallMethod("next");
28 | JSValue nextResult = await nextPromise.AsTask();
29 | JSValue done = nextResult["done"];
30 | if (done.IsBoolean() && (bool)done)
31 | {
32 | _current = default;
33 | return false;
34 | }
35 | else
36 | {
37 | _current = nextResult["value"];
38 | return true;
39 | }
40 | }
41 |
42 | public readonly JSValue Current
43 | => _current ?? throw new InvalidOperationException("Unexpected enumerator state");
44 |
45 | readonly ValueTask IAsyncDisposable.DisposeAsync() => default;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/test/TestCases/node-addon-api/object/has_own_property.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | 'use strict';
5 |
6 | const assert = require('assert');
7 |
8 | module.exports = require('../../common').runTest(test);
9 |
10 | function test (binding) {
11 | function testHasOwnProperty (nativeHasOwnProperty) {
12 | const obj = { one: 1 };
13 |
14 | Object.defineProperty(obj, 'two', { value: 2 });
15 |
16 | assert.strictEqual(nativeHasOwnProperty(obj, 'one'), true);
17 | assert.strictEqual(nativeHasOwnProperty(obj, 'two'), true);
18 | assert.strictEqual('toString' in obj, true);
19 | assert.strictEqual(nativeHasOwnProperty(obj, 'toString'), false);
20 | }
21 |
22 | function testShouldThrowErrorIfKeyIsInvalid (nativeHasOwnProperty) {
23 | assert.throws(() => {
24 | nativeHasOwnProperty(undefined, 'test');
25 | }, /Cannot convert undefined or null to object/);
26 | }
27 |
28 | testHasOwnProperty(binding.object.hasOwnPropertyWithNapiValue);
29 | testHasOwnProperty(binding.object.hasOwnPropertyWithNapiWrapperValue);
30 | testHasOwnProperty(binding.object.hasOwnPropertyWithUtf8StyleString);
31 | testHasOwnProperty(binding.object.hasOwnPropertyWithCSharpStyleString);
32 |
33 | testShouldThrowErrorIfKeyIsInvalid(binding.object.hasOwnPropertyWithNapiValue);
34 | testShouldThrowErrorIfKeyIsInvalid(binding.object.hasOwnPropertyWithNapiWrapperValue);
35 | testShouldThrowErrorIfKeyIsInvalid(binding.object.hasOwnPropertyWithUtf8StyleString);
36 | testShouldThrowErrorIfKeyIsInvalid(binding.object.hasOwnPropertyWithCSharpStyleString);
37 | }
38 |
--------------------------------------------------------------------------------
/examples/semantic-kernel/example.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | import dotnet from 'node-api-dotnet';
5 | import './bin/Microsoft.SemanticKernel.Abstractions.js';
6 | import './bin/Microsoft.SemanticKernel.Core.js';
7 | import './bin/Microsoft.SemanticKernel.Connectors.OpenAI.js';
8 |
9 | const SK = dotnet.Microsoft.SemanticKernel;
10 |
11 | const kernelBuilder = SK.Kernel.CreateBuilder();
12 |
13 | kernelBuilder.AddAzureOpenAIChatCompletion(
14 | process.env['OPENAI_DEPLOYMENT'] || '',
15 | process.env['OPENAI_ENDPOINT'] || '',
16 | process.env['OPENAI_KEY'] || '',
17 | );
18 |
19 | const kernel = kernelBuilder.Build();
20 |
21 | const prompt = `{{$input}}
22 |
23 | Give me the TLDR in 10 words.
24 | `;
25 |
26 | const textToSummarize = `
27 | 1) A robot may not injure a human being or, through inaction,
28 | allow a human being to come to harm.
29 |
30 | 2) A robot must obey orders given it by human beings except where
31 | such orders would conflict with the First Law.
32 |
33 | 3) A robot must protect its own existence as long as such protection
34 | does not conflict with the First or Second Law.
35 | `;
36 |
37 | const executionSettings = new SK.Connectors.OpenAI.OpenAIPromptExecutionSettings();
38 | executionSettings.MaxTokens = 100;
39 |
40 | const summaryFunction = kernel.CreateFunctionFromPrompt(prompt, executionSettings);
41 |
42 | const summarizeArguments = new Map([
43 | ['input', textToSummarize],
44 | ]);
45 |
46 | const summary = await kernel.InvokeAsync(
47 | summaryFunction, new SK.KernelArguments(summarizeArguments, undefined));
48 |
49 | console.log();
50 | console.log(summary.toString());
51 |
--------------------------------------------------------------------------------
/docs/reference/extension-methods.md:
--------------------------------------------------------------------------------
1 | # .NET Extension Methods in JavaScript
2 |
3 | Extension methods are important to the usability and discoverability of many .NET libraries, yet
4 | JavaScript has no built-in support for extension methods. Since the JavaScript type system is
5 | very dynamic, the JS marshaller can simulate extension methods by dynamically attaching the methods
6 | to the types they apply to.
7 |
8 | Extension methods are supported only in the
9 | [dynamic invocation scenario](../scenarios/js-dotnet-dynamic)
10 | since pre-built .NET APIs were most likely not designed with JavaScript in mind. But when developing
11 | a [Node.js addon module in C#](../scenarios/js-dotnet-module) the expectation is that the APIs
12 | specifically exported to JavaScript with `[JSExport]` should be designed without any need for
13 | extension methods.
14 |
15 | Extension methods may be provided by the same assembly as the target type, or a different assembly.
16 | When provided by a different assembly, it may be necessary to explicitly import the other assembly,
17 | otherwise the extension method will not be registered on the target type.
18 |
19 | Here is a snippet from the Semantic Kernel example. The Semantic Kernel library makes heavy use of
20 | extension methods.
21 |
22 | ```JS
23 | import dotnet from 'node-api-dotnet';
24 | import './bin/Microsoft.SemanticKernel.Core.js';
25 | import './bin/Microsoft.SemanticKernel.Connectors.OpenAI.js';
26 |
27 | const kernelBuilder = dotnet.Microsoft.SemanticKernel.Kernel.CreateBuilder();
28 |
29 | // Call an extension method provided by the MS.SK.Connectors.OpenAI assembly.
30 | kernelBuilder.AddAzureOpenAIChatCompletion(deployment, endpoint, key);
31 | ```
32 |
--------------------------------------------------------------------------------
/.github/workflows/test-report.yml:
--------------------------------------------------------------------------------
1 | # https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
2 | # https://github.com/dorny/test-reporter#recommended-setup-for-public-repositories
3 |
4 | name: Test Report
5 | on:
6 | workflow_run:
7 | workflows: ['PR Verification'] # runs after 'PR Verification' workflow
8 | types:
9 | - completed
10 |
11 | permissions:
12 | checks: write
13 | pull-requests: write
14 | statuses: write
15 |
16 | jobs:
17 | report:
18 | strategy:
19 | matrix: # This must be kept in sync with the PR build matrix.
20 | os: [ windows-latest, macos-latest, ubuntu-latest ]
21 | dotnet-version: [ net472, net8.0, net9.0]
22 | node-version: [ 18.x, 22.x ]
23 | configuration: [ Release ]
24 | exclude:
25 | # Exclude Node 18.x on .NET < 9, to thin the matrix.
26 | - dotnet-version: net8.0
27 | node-version: 18.x
28 | - dotnet-version: net472
29 | node-version: 18.x
30 | # Exclude .NET 4.x on non-Windows OS.
31 | - os: macos-latest
32 | dotnet-version: net472
33 | - os: ubuntu-latest
34 | dotnet-version: net472
35 |
36 | runs-on: ubuntu-latest
37 |
38 | steps:
39 | - name: Publish test results
40 | uses: dorny/test-reporter@v1
41 | with:
42 | artifact: test-logs-${{ matrix.os }}-${{matrix.dotnet-version}}-node${{matrix.node-version}}-${{matrix.configuration}}
43 | name: test results (${{ matrix.os }}, ${{matrix.dotnet-version}}, node${{ matrix.node-version }}, ${{ matrix.configuration }})
44 | path: test/**/*.trx
45 | reporter: dotnet-trx
46 |
--------------------------------------------------------------------------------
/rid.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | win-x64
5 | win-x86
6 | win-arm64
7 |
8 |
9 | osx-x64
10 | osx-arm64
11 |
12 |
13 | linux-x64
14 | linux-x86
15 | linux-arm64
16 |
17 |
18 |
--------------------------------------------------------------------------------
/test/TestCases/node-addon-api/object/has_property.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | 'use strict';
5 |
6 | const assert = require('assert');
7 |
8 | module.exports = require('../../common').runTest(test);
9 |
10 | function test (binding) {
11 | function testHasProperty (nativeHasProperty) {
12 | const obj = { one: 1 };
13 |
14 | Object.defineProperty(obj, 'two', { value: 2 });
15 |
16 | assert.strictEqual(nativeHasProperty(obj, 'one'), true);
17 | assert.strictEqual(nativeHasProperty(obj, 'two'), true);
18 | assert.strictEqual('toString' in obj, true);
19 | assert.strictEqual(nativeHasProperty(obj, 'toString'), true);
20 | }
21 |
22 | function testShouldThrowErrorIfKeyIsInvalid (nativeHasProperty) {
23 | assert.throws(() => {
24 | nativeHasProperty(undefined, 'test');
25 | }, /Cannot convert undefined or null to object/);
26 | }
27 |
28 | const objectWithInt32Key = { 12: 101 };
29 | assert.strictEqual(binding.object.hasPropertyWithUInt32(objectWithInt32Key, 12), true);
30 |
31 | testHasProperty(binding.object.hasPropertyWithNapiValue);
32 | testHasProperty(binding.object.hasPropertyWithNapiWrapperValue);
33 | testHasProperty(binding.object.hasPropertyWithUtf8StyleString);
34 | testHasProperty(binding.object.hasPropertyWithCSharpStyleString);
35 |
36 | testShouldThrowErrorIfKeyIsInvalid(binding.object.hasPropertyWithNapiValue);
37 | testShouldThrowErrorIfKeyIsInvalid(binding.object.hasPropertyWithNapiWrapperValue);
38 | testShouldThrowErrorIfKeyIsInvalid(binding.object.hasPropertyWithUtf8StyleString);
39 | testShouldThrowErrorIfKeyIsInvalid(binding.object.hasPropertyWithCSharpStyleString);
40 | }
41 |
--------------------------------------------------------------------------------
/examples/winui-fluid/Fluid/IFluidContainer.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Threading.Tasks;
6 |
7 | namespace Microsoft.JavaScript.NodeApi.Examples.Fluid;
8 |
9 | [JSImport]
10 | public interface IFluidContainer : IDisposable
11 | {
12 | public ConnectionState ConnectionState { get; }
13 |
14 | public bool IsDirty { get; }
15 |
16 | // TODO: Marshal as "disposed" property.
17 | public bool IsDisposed { get; }
18 |
19 | public JSValue InitialObjects { get; set; }
20 |
21 | // TODO: Marshal attribute to indicate the dictionary should be marshalled as object (not Map).
22 | ////[JSMarshalAs(JSMarshal.Object)]
23 | ////public IDictionary InitialObjects { get; set; }
24 |
25 | // TODO: Automatic marshalling of stringified enums.
26 | public string AttachState { get; }
27 |
28 | public Task Attach();
29 |
30 | public void Connect();
31 |
32 | public void Disconnect();
33 |
34 | ////public Task Create();
35 |
36 | // TODO: Events
37 | /*
38 | public event EventHandler Connected;
39 | public event EventHandler Disconnected;
40 | public event EventHandler Saved;
41 | public event EventHandler Dirty;
42 | public event EventHandler Disposed;
43 | */
44 | }
45 |
46 | public enum ConnectionState
47 | {
48 | Disconnected = 0,
49 | EstablishingConnection = 3,
50 | CatchingUp = 1,
51 | Connected = 2,
52 | }
53 |
54 | [JSImport]
55 | public struct Connection
56 | {
57 | public string Id { get; set; }
58 |
59 | public string Mode { get; set; }
60 | }
61 |
--------------------------------------------------------------------------------
/src/NodeApi/JSIterable.Enumerator.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Collections;
6 | using System.Collections.Generic;
7 |
8 | namespace Microsoft.JavaScript.NodeApi;
9 |
10 | public partial struct JSIterable
11 | {
12 | public struct Enumerator : IEnumerator, IEnumerator
13 | {
14 | private readonly JSValue _iterable;
15 | private JSValue _iterator;
16 | private JSValue? _current;
17 |
18 | internal Enumerator(JSValue iterable)
19 | {
20 | _iterable = iterable;
21 | _iterator = _iterable.CallMethod(JSSymbol.Iterator);
22 | _current = default;
23 | }
24 |
25 | public bool MoveNext()
26 | {
27 | JSValue nextResult = _iterator.CallMethod("next");
28 | JSValue done = nextResult["done"];
29 | if (done.IsBoolean() && (bool)done)
30 | {
31 | _current = default;
32 | return false;
33 | }
34 | else
35 | {
36 | _current = nextResult["value"];
37 | return true;
38 | }
39 | }
40 |
41 | public readonly JSValue Current
42 | => _current ?? throw new InvalidOperationException("Unexpected enumerator state");
43 |
44 | readonly object? IEnumerator.Current => Current;
45 |
46 | void IEnumerator.Reset()
47 | {
48 | _iterator = _iterable.CallMethod(JSSymbol.Iterator);
49 | _current = default;
50 | }
51 |
52 | readonly void IDisposable.Dispose()
53 | {
54 | }
55 | }
56 | }
57 |
58 |
--------------------------------------------------------------------------------
/examples/jsdom/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.IO;
4 | using Microsoft.JavaScript.NodeApi.Runtime;
5 |
6 | namespace Microsoft.JavaScript.NodeApi.Examples;
7 |
8 | public static class Program
9 | {
10 | public static void Main()
11 | {
12 | string appDir = Path.GetDirectoryName(typeof(Program).Assembly.Location)!;
13 | string libnodePath = Path.Combine(appDir, "libnode.dll");
14 | using NodeEmbeddingPlatform nodejsPlatform = new(libnodePath, null);
15 | using NodeEmbeddingThreadRuntime nodejs = nodejsPlatform.CreateThreadRuntime(appDir,
16 | new NodeEmbeddingRuntimeSettings
17 | {
18 | MainScript =
19 | "globalThis.require = require('module').createRequire(process.execPath);\n"
20 | });
21 | if (Debugger.IsAttached)
22 | {
23 | int pid = Process.GetCurrentProcess().Id;
24 | Uri inspectionUri = nodejs.StartInspector();
25 | Debug.WriteLine($"Node.js ({pid}) inspector listening at {inspectionUri.AbsoluteUri}");
26 | }
27 |
28 | string html = "Hello world!
";
29 | string content = nodejs.Run(() => GetContent(nodejs, html));
30 | Console.WriteLine(content);
31 | }
32 |
33 | private static string GetContent(NodeEmbeddingThreadRuntime nodejs, string html)
34 | {
35 | JSValue jsdomClass = nodejs.Import(module: "jsdom", property: "JSDOM");
36 | JSValue dom = jsdomClass.CallAsConstructor(html);
37 | JSValue document = dom["window"]["document"];
38 | string content = (string)document.CallMethod("querySelector", "p")["textContent"];
39 | return content;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/test/TestCases/napi-dotnet/Delegates.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 | using Microsoft.JavaScript.NodeApi.Interop;
8 |
9 | namespace Microsoft.JavaScript.NodeApi.TestCases;
10 |
11 | [JSExport]
12 | public delegate string TestDelegate(string value);
13 |
14 |
15 | [JSExport]
16 | public static class Delegates
17 | {
18 | public delegate string NestedDelegate(string value);
19 |
20 | public static void CallAction(Action actionDelegate, int value) => actionDelegate(value);
21 |
22 | public static int CallFunc(Func funcDelegate, int value) => funcDelegate(value);
23 |
24 | public static string CallDelegate(TestDelegate testDelegate, string value) => testDelegate(value);
25 |
26 | public static string CallDotnetDelegate(Func callDelegate)
27 | => callDelegate((string value) => "#" + value);
28 |
29 | public static async Task WaitUntilCancelled(CancellationToken cancellation)
30 | {
31 | JSSynchronizationContext syncContext = JSSynchronizationContext.Current!;
32 | TaskCompletionSource completion = new();
33 | cancellation.Register(() => syncContext.Post(() => completion.SetResult(true)));
34 | await completion.Task;
35 | }
36 |
37 | public static async Task CallDelegateAndCancel(
38 | Func cancellableDelegate)
39 | {
40 | CancellationTokenSource cancellationSource = new();
41 | Task delegateTask = cancellableDelegate(cancellationSource.Token);
42 | await Task.Delay(100);
43 | cancellationSource.Cancel();
44 | await delegateTask;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/examples/winappsdk/WinAppSdkExample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0-windows10.0.19041.0
5 | 10.0.17763.0
6 | bin
7 | x86;x64;ARM64
8 | win-x86;win-x64;win-arm64
9 | CommonJs
10 |
11 |
12 | ABI.*;WinRT.IInspectable+Vftbl*;WinRT.Interop.IUnknownVftbl*
13 |
14 |
15 |
16 |
17 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/examples/aot-npm-package/README.md:
--------------------------------------------------------------------------------
1 |
2 | ## Minimal Example .NET AOT NPM Package
3 | The `lib/Example.cs` class defines a Node.js add-on module that is AOT-compiled, so that it does not
4 | depend on the .NET runtime. The AOT module is then packaged as an npm package. The `app/example.js`
5 | script loads that _native_ module via its npm package and calls a method on it. The script has
6 | access to type definitions and doc-comments for the module's APIs via the auto-generated `.d.ts`
7 | file that was included in the npm package.
8 |
9 | | Command | Explanation
10 | |-------------------------------|--------------------------------------------------
11 | | `dotnet pack ../..` | Build Node API .NET packages.
12 | | `cd lib`
`dotnet publish` | Install Node API .NET packages into lib project; build lib project and compile to native binary; pack npm package.
13 | | `cd app`
`npm install` | Install lib project npm package into app project.
14 | | `node example.js` | Run example JS code that calls the library API.
15 |
16 | ### Building multi-platform npm packages with platform-specific AOT binaries
17 | Native AOT binaries are platform-specific. The `dotnet publish` command above creates a package
18 | only for the current OS / CPU platform (aka .NET runtime-identifier). To create a multi-platform
19 | npm package with Native AOT binaries, run `dotnet publish` separately for each runtime-identifier,
20 | and only create the package on the last one:
21 | ```
22 | dotnet publish -r:win-x64 -p:PackNpmPackage=false
23 | dotnet publish -r:win-arm64 -p:PackNpmPackage=true
24 | ```
25 |
26 | To create a fully cross-platform packatge, it will be necessary to compile on each targeted OS
27 | (Windows, Mac, Linux), then copy the outputs into a shared directory before creating the final
28 | npm package.
29 |
--------------------------------------------------------------------------------
/test/TestCases/node-addon-api/object/subscript_operator.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using Microsoft.JavaScript.NodeApi;
5 |
6 | namespace Microsoft.JavaScript.NodeApiTest;
7 |
8 | public partial class TestObject
9 | {
10 | private static JSValue SubscriptGetWithUtf8StyleString(JSCallbackArgs args)
11 | {
12 | JSValue obj = args[0];
13 | byte[] key = args[1].GetValueStringUtf8();
14 | return obj[JSValue.CreateStringUtf8(key)];
15 | }
16 |
17 | private static JSValue SubscriptGetWithCSharpStyleString(JSCallbackArgs args)
18 | {
19 | JSValue obj = args[0];
20 | string key = (string)args[1];
21 | return obj[key];
22 | }
23 |
24 | private static JSValue SubscriptGetAtIndex(JSCallbackArgs args)
25 | {
26 | JSValue obj = args[0];
27 | uint index = (uint)args[1];
28 | return obj[index];
29 | }
30 |
31 | private static JSValue SubscriptSetWithUtf8StyleString(JSCallbackArgs args)
32 | {
33 | JSValue obj = args[0];
34 | byte[] key = args[1].GetValueStringUtf8();
35 | JSValue value = args[2];
36 | obj[JSValue.CreateStringUtf8(key)] = value;
37 | return JSValue.Undefined;
38 | }
39 |
40 | private static JSValue SubscriptSetWithCSharpStyleString(JSCallbackArgs args)
41 | {
42 | JSValue obj = args[0];
43 | string key = (string)args[1];
44 | JSValue value = args[2];
45 | obj[key] = value;
46 | return JSValue.Undefined;
47 | }
48 |
49 | private static JSValue SubscriptSetAtIndex(JSCallbackArgs args)
50 | {
51 | JSValue obj = args[0];
52 | uint index = (uint)args[1];
53 | JSValue value = args[2];
54 | obj[index] = value;
55 | return JSValue.Undefined;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/docs/overview.md:
--------------------------------------------------------------------------------
1 | # Project Overview
2 |
3 | This project enables advanced interoperability between .NET and JavaScript in the same process.
4 |
5 | - Load .NET assemblies and call .NET APIs in-proc from a JavaScript application.
6 | - Load JavaScript packages and call JS APIs in-proc from a .NET application.
7 |
8 | Interop is [high-performance](./features/performance) and supports [TypeScript type-definitions
9 | generation](./features/type-definitions), [async (tasks/promises)](./features/js-threading-async),
10 | [streams](./reference/streams), [exception propagation](./reference/exceptions), and more. It is
11 | built on [Node API](https://nodejs.org/api/n-api.html) so it is compatible with any Node.js version
12 | (without recompiling) or other JavaScript runtime that supports Node API, such as Deno.
13 |
14 | :warning: _**Status: Public Preview** - Most functionality works well, though there are some known
15 | limitations around the edges, and there may still be minor breaking API changes._
16 |
17 | ### Minimal example - JS calling .NET
18 | ```JavaScript
19 | const Console = require('node-api-dotnet').System.Console;
20 | Console.WriteLine('Hello from .NET!'); // JS writes to the .NET console API
21 | ```
22 |
23 | ### Minimal example - .NET calling JS
24 | ```C#
25 | interface IConsole { void Log(string message); }
26 |
27 | var nodejs = new NodejsPlatform(libnodePath).CreateEnvironment();
28 | nodejs.Run(() =>
29 | {
30 | var console = nodejs.Import("global", "console");
31 | console.Log("Hello from JS!"); // C# writes to the JS console API
32 | });
33 | ```
34 |
35 | For more complete example projects, see the
36 | [examples directory in the repo](https://github.com/microsoft/node-api-dotnet/tree/main/examples).
37 | Or proceed to the next page to learn about the different supported scenarios and how to get started
38 | with your own project.
39 |
--------------------------------------------------------------------------------
/test/TestCases/node-addon-api/object/set_property.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using Microsoft.JavaScript.NodeApi;
5 | using Microsoft.JavaScript.NodeApi.Runtime;
6 |
7 | namespace Microsoft.JavaScript.NodeApiTest;
8 |
9 | public partial class TestObject
10 | {
11 | private static JSValue SetPropertyWithNapiValue(JSCallbackArgs args)
12 | {
13 | JSValue obj = args[0];
14 | JSValue key = args[1];
15 | JSValue value = args[2];
16 | obj.SetProperty((JSRuntime.napi_value)key, value);
17 | return JSValue.Undefined;
18 | }
19 |
20 | private static JSValue SetPropertyWithNapiWrapperValue(JSCallbackArgs args)
21 | {
22 | JSValue obj = args[0];
23 | JSValue key = args[1];
24 | JSValue value = args[2];
25 | obj.SetProperty(key, value);
26 | return JSValue.Undefined;
27 | }
28 |
29 | private static JSValue SetPropertyWithUtf8StyleString(JSCallbackArgs args)
30 | {
31 | JSValue obj = args[0];
32 | JSValue key = args[1];
33 | JSValue value = args[2];
34 | obj.SetProperty(JSValue.CreateStringUtf8(key.GetValueStringUtf8()), value);
35 | return JSValue.Undefined;
36 | }
37 |
38 | private static JSValue SetPropertyWithCSharpStyleString(JSCallbackArgs args)
39 | {
40 | JSValue obj = args[0];
41 | JSValue key = args[1];
42 | JSValue value = args[2];
43 | obj.SetProperty((string)key, value);
44 | return JSValue.Undefined;
45 | }
46 |
47 | private static JSValue SetPropertyWithUInt32(JSCallbackArgs args)
48 | {
49 | JSValue obj = args[0];
50 | JSValue key = args[1];
51 | JSValue value = args[2];
52 | obj.SetProperty((uint)key, value);
53 | return JSValue.Undefined;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/examples/winui-fluid/Package.appxmanifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
13 |
14 |
15 | winui-fluid
16 | Microsoft
17 | Assets\StoreLogo.png
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
33 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/docs/images/dark/ts.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
--------------------------------------------------------------------------------
/docs/images/light/ts.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
--------------------------------------------------------------------------------
/docs/reference/packages-releases.md:
--------------------------------------------------------------------------------
1 | # Packages & Releases
2 |
3 | ## NuGet packages
4 |
5 | [`Microsoft.JavaScript.NodeApi`](https://www.nuget.org/packages/Microsoft.JavaScript.NodeApi/) -
6 | Contains the core functionality for interop between .NET and JavaScript, including runtime
7 | code-generation for dynamic marshalling. See the [.NET API reference](../reference/dotnet/).
8 |
9 | [`Microsoft.JavaScript.NodeApi.Generator`](https://www.nuget.org/packages/Microsoft.JavaScript.NodeApi.Generator/) -
10 | Contains the MSBuild tasks and supporting assemblies for generating marshalling code at compile
11 | time, and for generating TypeScript type defintions for .NET APIs.
12 |
13 | ## NPM packages
14 |
15 | [`node-api-dotnet`](https://www.npmjs.com/package/node-api-dotnet) - Supports loading .NET
16 | assemblies into a Node.js process. Contains the native .NET hosting modules
17 | (`Microsoft.JavaScript.NodeApi.node`, built with .NET Native AOT) for all supported platforms,
18 | the runtime assemblies (`Microsoft.JavaScript.NodeApi.dll`) for all supported target frameworks,
19 | and loader scripts. See the [JavaScript API reference](../reference/js/).
20 |
21 | [`node-api-dotnet-generator`](https://www.npmjs.com/package/node-api-dotnet-generator) - A Node.js
22 | command-line tool that is a wrapper around the .NET generator assembly. It enables generating
23 | TypeScript type definitions without using MSBuild. See the CLI reference at
24 | [TypeScript Type Definitions](../features/type-definitions).
25 |
26 | ## Releases
27 |
28 | Packages are published to nuget and npm automatically, usually around an hour after any PR
29 | is merged.
30 |
31 | While the project is in pre-release phase, there may be occasional breaking API changes between
32 | versions less than 1.0. Starting with v1.0 (expected late 2024), it will follow
33 | [semantic versioning practices](https://semver.org/).
34 |
--------------------------------------------------------------------------------
/examples/winui-fluid/README.md:
--------------------------------------------------------------------------------
1 | ## C# WinUI + JS Fluid Framework
2 | This project is a C# WinUI application that demonstrates collaborative text editing
3 | using the [Fluid Framework](https://fluidframework.com/), which has a JS-only API.
4 |
5 | Before building and running this project:
6 | - If necessary, [install tools for WinUI C# development](
7 | https://learn.microsoft.com/en-us/windows/apps/windows-app-sdk/set-up-your-development-environment
8 | ).
9 | - Download a build of `libnode.dll` that _includes Node API embedding support_. (Soon this will be
10 | available as a nuget package, but for now you can contact us to get a copy.) Place it at
11 | `bin\win-x64\libnode.dll` relative to the repo root (not this subdirectory).
12 |
13 | | Command | Explanation
14 | |-------------------------|--------------------------------------------------
15 | | `dotnet pack ../..` | Build Node API .NET packages.
16 | | `npm install` | Install JavaScript packages.
17 | | `dotnet build` | Install .NET nuget packages; build example project.
18 | | `npx tinylicious` | Start the local fluid development server on port 7070.
19 | | `dotnet run --no-build` | Run the example project.
20 |
21 | Launch two or more instances of the app to set up a collaborative editing session. Each instance of
22 | the application can either host or join a session.
23 | - To host a new session, click the **Start** button. Then **Copy** the session ID and send it
24 | to a guest.
25 | - To join an existing session, paste the session ID (obtained from a host) and click **Join**.
26 |
27 | All participants in the session can see each others' cursors and simultanesouly edit the document.
28 |
29 | To join remotely, forward port 7070 using `ngrok` or a similar tool. See
30 | [Testing with Tinylicious and multiple clients](
31 | https://fluidframework.com/docs/testing/tinylicious/#testing-with-tinylicious-and-multiple-clients)
32 |
--------------------------------------------------------------------------------
/src/NodeApi/Interop/TypeExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Linq;
6 |
7 | namespace Microsoft.JavaScript.NodeApi.Interop;
8 |
9 | public static class TypeExtensions
10 | {
11 | public static string FormatName(this Type type)
12 | {
13 | if (type.IsArray)
14 | {
15 | return FormatName(type.GetElementType()!) + "[]";
16 | }
17 |
18 | static string FormatNameWithoutNamespace(Type type)
19 | {
20 | string typeName = type.Name;
21 | if (type.IsGenericType)
22 | {
23 | int nameEnd = typeName.IndexOf('`');
24 | if (nameEnd >= 0)
25 | {
26 | typeName = typeName.Substring(0, nameEnd);
27 | }
28 |
29 | Type[] typeArgs = type.GetGenericArguments();
30 | if (type.IsGenericTypeDefinition)
31 | {
32 | typeName += '<' + string.Join(",", typeArgs.Select((t) => t.Name)) + '>';
33 | }
34 | else
35 | {
36 | typeName += '<' + string.Join(",", typeArgs.Select(FormatName)) + '>';
37 | }
38 | }
39 | return typeName;
40 | }
41 |
42 | // Include the declaring type(s) of nested types.
43 | string typeName = FormatNameWithoutNamespace(type);
44 | Type? declaringType = type.DeclaringType;
45 | while (declaringType != null)
46 | {
47 | typeName = FormatNameWithoutNamespace(declaringType) + '.' + typeName;
48 | declaringType = declaringType.DeclaringType;
49 | }
50 |
51 | if (type.Namespace != null)
52 | {
53 | typeName = type.Namespace + '.' + typeName;
54 | }
55 |
56 | return typeName;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/NodeApi/JSMap.Enumerator.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | using System;
5 | using System.Collections.Generic;
6 |
7 | namespace Microsoft.JavaScript.NodeApi;
8 |
9 | public partial struct JSMap
10 | {
11 | public struct Enumerator :
12 | IEnumerator>,
13 | System.Collections.IEnumerator
14 | {
15 | private readonly JSValue _iterable;
16 | private JSValue _iterator;
17 | private KeyValuePair? _current;
18 |
19 | internal Enumerator(JSValue iterable)
20 | {
21 | _iterable = iterable;
22 | _iterator = _iterable.CallMethod(JSSymbol.Iterator);
23 | _current = default;
24 | }
25 |
26 | public bool MoveNext()
27 | {
28 | JSValue nextResult = _iterator.CallMethod("next");
29 | JSValue done = nextResult["done"];
30 | if (done.IsBoolean() && (bool)done)
31 | {
32 | _current = default;
33 | return false;
34 | }
35 | else
36 | {
37 | JSArray currentEntry = (JSArray)nextResult["value"];
38 | _current = new KeyValuePair(currentEntry[0], currentEntry[1]);
39 | return true;
40 | }
41 | }
42 |
43 | public readonly KeyValuePair Current
44 | => _current ?? throw new InvalidOperationException("Unexpected enumerator state");
45 |
46 | readonly object? System.Collections.IEnumerator.Current => Current;
47 |
48 | void System.Collections.IEnumerator.Reset()
49 | {
50 | _iterator = _iterable.CallMethod(JSSymbol.Iterator);
51 | _current = default;
52 | }
53 |
54 | readonly void IDisposable.Dispose()
55 | {
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/examples/jsdom/jsdom.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.5.33103.201
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "jsdom", "jsdom.csproj", "{76D22090-236C-4F28-8863-ACF5C66E9559}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|arm64 = Debug|arm64
11 | Debug|x64 = Debug|x64
12 | Debug|x86 = Debug|x86
13 | Release|arm64 = Release|arm64
14 | Release|x64 = Release|x64
15 | Release|x86 = Release|x86
16 | EndGlobalSection
17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
18 | {76D22090-236C-4F28-8863-ACF5C66E9559}.Debug|arm64.ActiveCfg = Debug|Any CPU
19 | {76D22090-236C-4F28-8863-ACF5C66E9559}.Debug|arm64.Build.0 = Debug|Any CPU
20 | {76D22090-236C-4F28-8863-ACF5C66E9559}.Debug|x64.ActiveCfg = Debug|Any CPU
21 | {76D22090-236C-4F28-8863-ACF5C66E9559}.Debug|x64.Build.0 = Debug|Any CPU
22 | {76D22090-236C-4F28-8863-ACF5C66E9559}.Debug|x86.ActiveCfg = Debug|Any CPU
23 | {76D22090-236C-4F28-8863-ACF5C66E9559}.Debug|x86.Build.0 = Debug|Any CPU
24 | {76D22090-236C-4F28-8863-ACF5C66E9559}.Release|arm64.ActiveCfg = Release|Any CPU
25 | {76D22090-236C-4F28-8863-ACF5C66E9559}.Release|arm64.Build.0 = Release|Any CPU
26 | {76D22090-236C-4F28-8863-ACF5C66E9559}.Release|x64.ActiveCfg = Release|Any CPU
27 | {76D22090-236C-4F28-8863-ACF5C66E9559}.Release|x64.Build.0 = Release|Any CPU
28 | {76D22090-236C-4F28-8863-ACF5C66E9559}.Release|x86.ActiveCfg = Release|Any CPU
29 | {76D22090-236C-4F28-8863-ACF5C66E9559}.Release|x86.Build.0 = Release|Any CPU
30 | EndGlobalSection
31 | GlobalSection(SolutionProperties) = preSolution
32 | HideSolutionNode = FALSE
33 | EndGlobalSection
34 | GlobalSection(ExtensibilityGlobals) = postSolution
35 | SolutionGuid = {E61C40BC-6DC2-4619-8B32-75E681FAAEA1}
36 | EndGlobalSection
37 | EndGlobal
38 |
--------------------------------------------------------------------------------
/test/TestCases/node-addon-api/object/object_freeze_seal.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | 'use strict';
5 |
6 | const assert = require('assert');
7 |
8 | module.exports = require('../../common').runTest(test);
9 |
10 | function test(binding) {
11 | {
12 | const obj = { x: 'a', y: 'b', z: 'c' };
13 | assert.strictEqual(binding.objectFreezeSeal.freeze(obj), true);
14 | assert.strictEqual(Object.isFrozen(obj), true);
15 | assert.throws(() => {
16 | obj.x = 10;
17 | }, /Cannot assign to read only property 'x' of object '#