Skip to content

Commit 7c4258a

Browse files
Merge pull request #156 from bacongobbler/csharp-language-support
add C# language support documentation
2 parents 120119d + cb136c9 commit 7c4258a

File tree

4 files changed

+303
-5
lines changed

4 files changed

+303
-5
lines changed

component-model/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
- [Language Support for Components](./language-support.md)
1818
- [C/C++](./language-support/c.md)
19+
- [C#](./language-support/csharp.md)
1920
- [Go](./language-support/go.md)
2021
- [JavaScript](./language-support/javascript.md)
2122
- [Python](./language-support/python.md)

component-model/src/introduction.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,20 @@ The WebAssembly Component Model is a broad-reaching architecture for building in
44

55
| Understanding components | Building components | Using components |
66
|--------------------------|----------------------|-------------------|
7-
| [Why Components?] | [C/C++][C] | [Composing] |
8-
| [Components] | [Go] | [Running] |
9-
| [Interfaces] | [JavaScript] | [Distributing] |
10-
| [Worlds] | [Python] | |
7+
| [Why Components?] | [C/C++] | [Composing] |
8+
| [Components] | [C#] | [Running] |
9+
| [Interfaces] | [Go] | [Distributing] |
10+
| [Worlds] | [JavaScript] | |
11+
| | [Python] | |
1112
| | [Rust] | |
1213

1314
[Why Components?]: ./design/why-component-model.md
1415
[Components]: ./design/components.md
1516
[Interfaces]: ./design/interfaces.md
1617
[Worlds]: ./design/worlds.md
1718

18-
[C]: ./language-support/c.md
19+
[C/C++]: ./language-support/c.md
20+
[C#]: ./language-support/csharp.md
1921
[Go]: ./language-support/go.md
2022
[JavaScript]: ./language-support/javascript.md
2123
[Python]: ./language-support/python.md

component-model/src/language-support.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ run components for a given toolchain:
2727
- [C/C++ Tooling](./language-support/c.md)
2828
- [Building a Component with `wit-bindgen` and `wasm-tools`](./language-support/c.md#building-a-component-with-wit-bindgen-and-wasm-tools)
2929
- [Running a Component from C/C++ Applications](./language-support/c.md#running-a-component-from-cc-applications)
30+
- [C# Tooling](./language-support/csharp.md)
3031
- [Go Tooling](./language-support/go.md)
3132
- [JavaScript Tooling](./language-support/javascript.md)
3233
- [Building a Component with `jco`](./language-support/javascript.md#building-a-component-with-jco)
Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
# C# Tooling
2+
3+
## Building a Component with `componentize-dotnet`
4+
5+
[componentize-dotnet](https://github.com/bytecodealliance/componentize-dotnet) makes it easy to
6+
compile your code to WebAssembly components using a single tool. This Bytecode Alliance project is a
7+
NuGet package that can be used to create a fully AOT-compiled component, giving .NET developers a
8+
component experience comparable to those in Rust and TinyGo.
9+
10+
componentize-dotnet serves as a one-stop shop for .NET developers, wrapping several tools into one:
11+
12+
- [NativeAOT-LLVM](https://github.com/dotnet/runtimelab/tree/feature/NativeAOT-LLVM) (compilation)
13+
- [wit-bindgen](https://github.com/bytecodealliance/wit-bindgen) (WIT imports and exports)
14+
- [wasm-tools](https://github.com/bytecodealliance/wasm-tools) (component conversion)
15+
- [WASI SDK](https://github.com/WebAssembly/wasi-sdk) (SDK used by NativeAOT-LLVM)
16+
17+
First, install the .NET SDK. For this walkthrough, we’ll use the [.NET 9 SDK RC
18+
1](https://dotnet.microsoft.com/en-us/download/dotnet/9.0). You should also have
19+
[wasmtime](https://wasmtime.dev/) installed so you can run the binary that you produce.
20+
21+
Once you have the .NET SDK installed, create a new project:
22+
23+
```sh
24+
dotnet new classlib -o adder
25+
cd adder
26+
```
27+
28+
The `componentize-dotnet` package depends on the `NativeAOT-LLVM` package, which resides at the
29+
dotnet-experimental package source, so you will need to make sure that NuGet is configured to refer
30+
to experimental packages. You can create a project-scoped NuGet configuration by running:
31+
32+
```sh
33+
dotnet new nugetconfig
34+
```
35+
36+
Edit your nuget.config file to look like this:
37+
38+
```xml
39+
<?xml version="1.0" encoding="utf-8"?>
40+
<configuration>
41+
<packageSources>
42+
<!--To inherit the global NuGet package sources remove the <clear/> line below -->
43+
<clear />
44+
<add key="dotnet-experimental" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json" />
45+
<add key="nuget" value="https://api.nuget.org/v3/index.json" />
46+
</packageSources>
47+
</configuration>
48+
```
49+
50+
Now back in the console we’ll add the `BytecodeAlliance.Componentize.DotNet.Wasm.SDK` package:
51+
52+
```sh
53+
dotnet add package BytecodeAlliance.Componentize.DotNet.Wasm.SDK --prerelease
54+
```
55+
56+
In the .csproj project file, add the following to the `<PropertyGroup>`:
57+
58+
```xml
59+
<RuntimeIdentifier>wasi-wasm</RuntimeIdentifier>
60+
<UseAppHost>false</UseAppHost>
61+
<PublishTrimmed>true</PublishTrimmed>
62+
<InvariantGlobalization>true</InvariantGlobalization>
63+
<SelfContained>true</SelfContained>
64+
```
65+
66+
Next, create or download the WIT world you would like to target. For this example we will use an
67+
[`example`
68+
world](https://github.com/bytecodealliance/component-docs/tree/main/component-model/examples/example-host/add.wit)
69+
with an `add` function:
70+
71+
```wit
72+
package example:component;
73+
74+
world example {
75+
export add: func(x: s32, y: s32) -> s32;
76+
}
77+
```
78+
79+
In the .csproj project file, add a new `<ItemGroup>`:
80+
81+
```xml
82+
<ItemGroup>
83+
<Wit Update="add.wit" World="example" />
84+
</ItemGroup>
85+
```
86+
87+
If you try to build the project with `dotnet build`, you'll get an error like "The name
88+
'ExampleWorldImpl' does not exist in the current context". This is because you've said you'll
89+
provide an implementation, but haven't yet done so. To fix this, add the following code to your
90+
project:
91+
92+
```csharp
93+
namespace ExampleWorld;
94+
95+
public class ExampleWorldImpl : IOperations
96+
{
97+
public static int Add(int x, int y)
98+
{
99+
return x + y;
100+
}
101+
}
102+
```
103+
104+
If we build it:
105+
106+
```sh
107+
dotnet build
108+
```
109+
110+
The component will be available at `bin/Debug/net9.0/wasi-wasm/native/adder.wasm`.
111+
112+
## Building a component that exports an interface
113+
114+
The previous example uses a WIT file that exports a function. However, to use your component from
115+
another component, it must export an interface. That being said, you rarely find WIT that does not
116+
contain an interface. (Most WITs you'll see in the wild do use interfaces; we've been simplifying by
117+
exporting a function.) Let's expand our `example` world to export an interface rather than directly
118+
export the function. We are also adding the `hostapp` world to our WIT file which we will implement
119+
in [the next section](#building-a-component-that-imports-an-interface) to demonstrate how to build a
120+
component that *imports* an interface.
121+
122+
```wit
123+
// add.wit
124+
package example:component;
125+
126+
interface add {
127+
add: func(x: u32, y: u32) -> u32;
128+
}
129+
130+
world example {
131+
export add;
132+
}
133+
134+
world hostapp {
135+
import add;
136+
}
137+
```
138+
139+
If you peek at the bindings, you'll notice that we now implement a class for the `add` interface
140+
rather than for the `example` world. This is a consistent pattern. As you export more interfaces
141+
from your world, you implement more classes. Our add example gets the slight update of:
142+
143+
```csharp
144+
namespace ExampleWorld.wit.exports.example.component;
145+
146+
public class AddImpl : IAdd
147+
{
148+
public static int Add(int x, int y)
149+
{
150+
return x + y;
151+
}
152+
}
153+
```
154+
155+
Once again, compile an application to a Wasm component using `dotnet build`:
156+
157+
```sh
158+
$ dotnet build
159+
Restore complete (0.4s)
160+
You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy
161+
adder succeeded (1.1s) → bin/Debug/net9.0/wasi-wasm/adder.dll
162+
163+
Build succeeded in 2.5s
164+
```
165+
166+
The component will be available at `bin/Debug/net9.0/wasi-wasm/native/adder.wasm`.
167+
168+
## Building a component that imports an interface
169+
170+
So far, we've been dealing with library components. Now we will be creating a command component that
171+
implements the `hostapp` world. This component will import the `add` interface that is exported from
172+
our `adder` component and call the `add` function. We will later compose this command component with
173+
the `adder` library component we just built.
174+
175+
Now we will be taking the `adder` component and executing it from another WebAssembly component.
176+
`dotnet new console` creates a new project that creates an executable.
177+
178+
```sh
179+
dotnet new console -o host-app
180+
cd host-app
181+
```
182+
183+
The `componentize-dotnet` package depends on the `NativeAOT-LLVM` package, which resides at the
184+
dotnet-experimental package source, so you will need to make sure that NuGet is configured to refer
185+
to experimental packages. You can create a project-scoped NuGet configuration by running:
186+
187+
```sh
188+
dotnet new nugetconfig
189+
```
190+
191+
Edit your nuget.config file to look like this:
192+
193+
```xml
194+
<?xml version="1.0" encoding="utf-8"?>
195+
<configuration>
196+
<packageSources>
197+
<!--To inherit the global NuGet package sources remove the <clear/> line below -->
198+
<clear />
199+
<add key="dotnet-experimental" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json" />
200+
<add key="nuget" value="https://api.nuget.org/v3/index.json" />
201+
</packageSources>
202+
</configuration>
203+
```
204+
205+
Now back in the console we’ll add the `BytecodeAlliance.Componentize.DotNet.Wasm.SDK` package:
206+
207+
```sh
208+
dotnet add package BytecodeAlliance.Componentize.DotNet.Wasm.SDK --prerelease
209+
```
210+
211+
In the .csproj project file, add the following to the `<PropertyGroup>`:
212+
213+
```xml
214+
<RuntimeIdentifier>wasi-wasm</RuntimeIdentifier>
215+
<UseAppHost>false</UseAppHost>
216+
<PublishTrimmed>true</PublishTrimmed>
217+
<InvariantGlobalization>true</InvariantGlobalization>
218+
<SelfContained>true</SelfContained>
219+
```
220+
221+
Copy the same WIT file as before into your project:
222+
223+
```wit
224+
// add.wit
225+
package example:component;
226+
227+
interface add {
228+
add: func(x: u32, y: u32) -> u32;
229+
}
230+
231+
world example {
232+
export add;
233+
}
234+
235+
world hostapp {
236+
import add;
237+
}
238+
```
239+
240+
Add it to your .csproj project file as a new `ItemGroup`:
241+
242+
```xml
243+
<ItemGroup>
244+
<Wit Update="add.wit" World="hostapp" />
245+
</ItemGroup>
246+
```
247+
248+
Notice how the `World` changed from `example` to `hostapp`. The previous examples focused on
249+
implementing the class library for this WIT file - the `export` functions. Now we'll be focusing on
250+
the executable side of the application - the `hostapp` world.
251+
252+
Modify `Program.cs` to look like this:
253+
254+
```csharp
255+
// Pull in all imports of the `hostapp` world, namely the `add` interface.
256+
// example.component refers to the package name defined in the WIT file.
257+
using HostappWorld.wit.imports.example.component;
258+
259+
var left = 1;
260+
var right = 2;
261+
var result = AddInterop.Add(left, right);
262+
Console.WriteLine($"{left} + {right} = {result}");
263+
```
264+
265+
Once again, compile your component with `dotnet build`:
266+
267+
```sh
268+
$ dotnet build
269+
Restore complete (0.4s)
270+
You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy
271+
host-app succeeded (1.1s) → bin/Debug/net9.0/wasi-wasm/host-app.dll
272+
273+
Build succeeded in 2.5s
274+
```
275+
276+
At this point, you'll have two Webassembly components:
277+
278+
1. A component that implements the `example` world.
279+
2. A component that implements the `hostapp` world.
280+
281+
Since the `host-app` component depends on the `add` function which is defined in the `example`
282+
world, it needs to be composed the first component. You can compose your `host-app` component with
283+
your `adder` component by running [`wac plug`](https://github.com/bytecodealliance/wac):
284+
285+
```sh
286+
wac plug bin/Debug/net9.0/wasi-wasm/native/host-app.wasm --plug ../adder/bin/Debug/net9.0/wasi-wasm/native/adder.wasm -o main.wasm
287+
```
288+
289+
Then you can run the composed component:
290+
291+
```sh
292+
wasmtime run main.wasm
293+
1 + 2 = 3
294+
```

0 commit comments

Comments
 (0)