1
- # Call .NET APIs from JavaScript
1
+ # Node API for .NET: JavaScript + .NET Interop
2
2
3
- Call nearly any .NET APIs in-proc from JavaScript code, with high performance and TypeScript
4
- type-checking. The interop uses [ Node API] ( https://nodejs.org/api/n-api.html ) so it is compatible
5
- with any Node.js version (without rebuilding) or other JavaScript engine that supports Node API.
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 call JS APIs in-proc from a .NET application.
7
+
8
+ Interop is high-performance and supports TypeScript type-definitions generation, async
9
+ (tasks/promises), streams, and more. It uses [ Node API] ( https://nodejs.org/api/n-api.html ) so
10
+ it is compatible with any Node.js version (without recompiling) or other JavaScript runtime that
11
+ supports Node API.
6
12
7
13
:warning : _ ** Status: In Development** - Core functionality works, but many things are incomplete,
8
14
and it isn't yet all packaged up nicely in a way that can be easily consumed._
9
15
10
16
[ Instructions for getting started are below.] ( #getting-started )
11
17
12
- ### Minimal example
18
+ ### Minimal example - JS calling .NET
13
19
``` JavaScript
20
+ // JavaScript
14
21
const Console = require (' node-api-dotnet' ).Console ;
15
22
Console .WriteLine (' Hello from .NET!' );
16
23
```
24
+
25
+ ### Minimal example - .NET calling JS
26
+ ``` C#
27
+ // C#
28
+ [JSImport (" global" , " console" )]
29
+ interface IConsole { void Log (string message ); }
30
+
31
+ var nodejs = new NodejsPlatform (libnodePath ).CreateEnvironment ();
32
+ nodejs .Run (() => {
33
+ var console = nodejs .Import <IConsole >();
34
+ console .Log (" Hello from JS!" );
35
+ });
36
+ ```
37
+
17
38
For more examples, see the [ examples] ( ./examples/ ) directory.
18
39
19
40
## Feature Highlights
20
-
21
- ### Dynamically load .NET assemblies
22
- .NET core library types are available directly on the main module. Additional .NET assemblies can
23
- be loaded by file path:
41
+ - [ Load and call .NET assemblies from JS] ( #load-and-call-net-assemblies-from-js )
42
+ - [ Load and call JavaScript packages from .NET] ( #load-and-call-javascript-packages-from-net )
43
+ - [ Generate TS type definitions for .NET APIs] ( #generate-ts-type-definitions-for-net-apis )
44
+ - [ Full async support] ( #full-async-support )
45
+ - [ Error propagation] ( #error-propagation )
46
+ - [ Develop Node.js addons with C#] ( #develop-nodejs-addons-with-c )
47
+ - [ Optionally work directly with JS types in C#] ( #optionally-work-directly-with-js-types-in-c )
48
+ - [ Automatic efficient marshaling] ( #automatic-efficient-marshaling )
49
+ - [ Stream across .NET and JS] ( #stream-across-net-and-js )
50
+ - [ Optional .NET native AOT compilation] ( #optional-net-native-aot-compilation )
51
+ - [ High performance] ( #high-performance )
52
+
53
+ ### Load and call .NET assemblies from JS
54
+ The ` node-api-dotnet ` package manages hosting the .NET runtime in the JS process
55
+ (if not using AOT - see below). The .NET core library types are available directly on the
56
+ ` node-api-dotnet ` module, and additional .NET assemblies can be loaded by file path:
24
57
``` JavaScript
58
+ // JavaScript
25
59
const dotnet = require (' node-api-dotnet' );
26
60
const ExampleAssembly = dotnet .load (' path/to/ExampleAssembly.dll' );
27
61
const exampleObj = new ExampleAssembly.ExampleClass (... args);
@@ -30,17 +64,56 @@ const exampleObj = new ExampleAssembly.ExampleClass(...args);
30
64
.NET namespaces are stripped for convenience, but in case of ambiguity it's possible to get a type
31
65
by full name:
32
66
``` JavaScript
67
+ // JavaScript
33
68
const MyType = ExampleAssembly[' Namespace.Qualified.MyType' ];
34
69
```
35
70
36
- ### Generate type definitions for .NET APIs
71
+ ### Load and call JavaScript packages from .NET
72
+ Calling JavaScript from .NET requires hosting a JS runtime such as Node.js in the .NET app.
73
+ Then JS packages can be loaded either by directly invoking the JS ` require() ` function and
74
+ working with low-level JS values, or by declaring C# interfaces for the JS types and using
75
+ automatic marshalling.
76
+
77
+ All interaction with a JavaScript environment must be from its thread, via the
78
+ ` Run() ` , ` RunAsync() ` , or ` Post() ` methods on the JS environment object.
79
+ ``` C#
80
+ // C#
81
+ [JSImport (" example-npm-package" , " ExampleClass" )]
82
+ interface IExample
83
+ {
84
+ void ExampleMethod ();
85
+ }
86
+
87
+ var nodejsPlatform = new NodejsPlatform (libnodePath );
88
+ var nodejs = nodejsPlatform .CreateEnvironment ();
89
+
90
+ nodejs .Run (() => {
91
+ // Use require() to load a module, then call a function on it.
92
+ JSValue require = JSValue .Global [" require" ];
93
+ var example1 = require .Call (default , " example-npm-package" ).GetProperty (" ExampleClass" );
94
+ example1 .CallMethod (" exampleMethod" );
95
+
96
+ // Call the same function using the imported interface.
97
+ var example2 = nodejs .Import <IExample >();
98
+ example2 .ExampleMethod ();
99
+ });
100
+ ```
101
+
102
+ > Note: The ` [JSImport] ` attribute is in development. Until it is available, it is possible
103
+ to create an interface adapter for a JS value with a little more code.
104
+
105
+ In the future, it may be possible to automatically generate .NET API definitions from TypeScript
106
+ type definitions.
107
+
108
+ ### Generate TS type definitions for .NET APIs
37
109
If writing TypeScript, or type-checked JavaScript, there is a tool to generate type ` .d.ts ` type
38
110
definitions for .NET APIs. Soon, it should also generate a small ` .js ` file that exports the
39
111
assembly in a more natural way as a JS module.
40
112
``` bash
41
113
$ npm exec node-api-dotnet-generator --assembly ExampleAssembly.dll --typedefs ExampleAssembly.d.ts
42
114
```
43
115
``` TypeScript
116
+ // TypeScript
44
117
import { ExampleClass } from ' ./ExampleAssembly' ;
45
118
ExampleClass .ExampleMethod (... args ); // This call is type-checked!
46
119
```
@@ -52,6 +125,7 @@ JavaScript code can `await` a call to a .NET method that returns a `Task`. The m
52
125
automatically sets up a ` SynchronizationContext ` so that the .NET result is returned back to the
53
126
JS thread.
54
127
``` TypeScript
128
+ // TypeScript
55
129
import { ExampleClass } from ' ./ExampleAssembly' ;
56
130
const asyncResult = await ExampleClass .GetSomethingAsync (... args );
57
131
```
@@ -72,6 +146,7 @@ part of the compilation and generates code to export the tagged APIs and marshal
72
146
JavaScript and C#.
73
147
74
148
``` C#
149
+ // C#
75
150
[JSExport ] // Export class and all public members to JS.
76
151
public class ExampleClass { ... }
77
152
@@ -95,6 +170,7 @@ value of any type, and there are more types like `JSObject`, `JSArray`, `JSMap`,
95
170
C# code can work directly with those types if desired:
96
171
97
172
``` C#
173
+ // C#
98
174
[JSExport ]
99
175
public static JSPromise JSAsyncExample (JSValue input )
100
176
{
@@ -148,10 +224,10 @@ transferred using shared memory (without any additional sockets or pipes), so me
148
224
and copying is minimized.
149
225
150
226
### Optional .NET native AOT compilation
151
- This library supports hosting the .NET Runtime in the same process as the JavaScript engine .
227
+ This library supports hosting the .NET Runtime in the same process as the JavaScript runtime .
152
228
Alternatively, it also supports building [ native ahead-of-time (AOT) compiled C#] (
153
- https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/ ) libraries that are
154
- loadable as a JavaScript module _ without depending on the .NET Runtime_ .
229
+ https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/ ) libraries that are
230
+ loadable as a JavaScript module _ without depending on the .NET Runtime_ .
155
231
156
232
There are advantages and disadvantages to either approach:
157
233
| | .NET Runtime | .NET Native AOT |
@@ -187,25 +263,26 @@ Thanks to these design choices, JS to .NET calls are [more than twice as fast](
187
263
- .NET Framework 4.7.2 or later is supported at runtime,
188
264
but .NET 6 SDK is still required for building.
189
265
- Node.js v16 or later
190
- - Other JS engines may be supported in the future.
266
+ - Other JS runtimes may be supported in the future.
191
267
- OS: Windows, Mac, or Linux
192
268
- It should work on any platform where .NET 6 is supported.
193
269
194
270
#### Instructions
195
- Choose between one of the following scenarios:
196
- - [ Dynamically invoke .NET APIs from JavaScript] ( ./Docs/dynamic-invoke.md )
197
- - [ Develop a Node module in C#] ( ./Docs/node-module.md )
198
-
199
- Dynamic invocation is simpler to set up: all you need is the ` node-api-dotnet ` npm package and
200
- the path to a .NET assembly you want to call. But it has some limitations (not all kinds of APIs
201
- are supported), and is not quite as fast as a C# module, because marshalling code must be generated
202
- at runtime.
203
-
204
- Alternatively, a C# Node module is appropriate for an application that has more advanced interop
205
- needs. It is faster because marshalling code can be generated at compile time, and the shape of
206
- the APIs exposed to JavaScript can be adapted with JS interop in mind.
207
-
208
- TypeScript type definitions can be generated with either aproach.
271
+ For calling .NET from JS, choose between one of the following scenarios:
272
+ - [ Dynamically invoke .NET APIs from JavaScript] ( ./Docs/dynamic-invoke.md ) <br />
273
+ Dynamic invocation is simpler to set up: all you need is the ` node-api-dotnet ` npm package and
274
+ the path to a .NET assembly you want to call. But it has some limitations (not all kinds of APIs
275
+ are supported), and is not quite as fast as a C# module, because marshalling code must be
276
+ generated at runtime.
277
+ - [ Develop a Node module in C#] ( ./Docs/node-module.md ) <br />
278
+ A C# Node module is appropriate for an application that has more advanced interop needs. It can
279
+ be faster because marshalling code can be generated at compile time, and the shape of the APIs
280
+ exposed to JavaScript can be adapted with JS interop in mind.
281
+
282
+ For calling JS from .NET, more documentation will be added soon. For now, see the
283
+ [ ` winui-fluid ` example code] ( ./examples/winui-fluid/ ) .
284
+
285
+ Generated TypeScript type definitions can be utilized with any of these aproaches.
209
286
210
287
## Development
211
288
For information about building, testing, and contributing changes to this project, see
0 commit comments