Wednesday, June 1, 2022

Running a C# WebAssembly module outside of the browser

Based on "Future Possibilities for .NET Core and WASI (WebAssembly on the Server) | OD108", published May 27, 2022 by Steve Sanderson (SteveSandersonMS on Github, @stevensanderson on Twitter).  Video at https://youtu.be/A0vz_BWxIMc.

Prerequisites

.NET Version

.NET 7 or later is required.  I used ".NET 7 Preview 4", downloaded from: https://dotnet.microsoft.com/en-us/download/dotnet/7.0.

On Windows
I did not use the Installer.  Rather, I downloaded the "Binaries" and unzipped them to a local folder.  Then I added the root of that local folder to the front of the PATH environment variable, in a command prompt.
I also used the "Developer Command Prompt for VS 2022" command prompt.

On Mac
I installed .NET 7 Preview 4 using the installer.

WASI Runtime

To run the final .wasm file, I used wasmtime which I installed from here: https://wasmtime.dev/
I also ran it under wasmer, Installed from here: https://wasmer.io/

Build and Run

From a terminal or command prompt, create a new dotnet console project and "cd" into it:
dotnet new console -o HelloWasi 
cd HelloWasi

Then run it to make sure it works:
dotnet run 

It will just say "Hello world" or something like that.   Update Program.cs to output something more interesting.  Replace Program.cs with this code:

using System.Runtime.InteropServices;

System.Console.WriteLine($"Hello world! The time is {DateTime.UtcNow.ToLongTimeString()}");
System.Console.WriteLine($"OSArchitecture: {RuntimeInformation.OSArchitecture}");
System.Console.WriteLine($"OSDescription: {RuntimeInformation.OSDescription}");
System.Console.WriteLine($"FrameworkDescription {RuntimeInformation.FrameworkDescription}");
System.Console.WriteLine($"ProcessArchitecture {RuntimeInformation.ProcessArchitecture}");
System.Console.WriteLine($"RuntimeIdentifier {RuntimeInformation.RuntimeIdentifier}");

Build and run again:
dotnet build
dotnet run 

You should see output something like this.  Note what it says for "OSArchitecture"
Hello world! The time is 1:34:03 AM
OSArchitecture: X64
OSDescription: Microsoft Windows 10.0.19044
FrameworkDescription .NET 7.0.0-preview.4.22229.4
ProcessArchitecture X64
RuntimeIdentifier win10-x64

On macOS you'd get something like this:
Hello world! The time is 2:51:07 PM
OSArchitecture: X64
OSDescription: Darwin 19.6.0 Darwin Kernel Version 19.6.0: Tue Feb 15 21:39:11 PST 2022; root:xnu-6153.141.59~1/RELEASE_X86_64
FrameworkDescription .NET 7.0.0-preview.4.22229.4
ProcessArchitecture X64
RuntimeIdentifier osx.10.15-x64

Now let's update the project to target WebAssembly instead.
Add the Wasi.Sdk package:
dotnet add package Wasi.Sdk --prerelease

Or update the .csproj file to include this:
  <ItemGroup>
    <PackageReference Include="Wasi.Sdk" Version="0.1.1" />
  </ItemGroup>

Now when you do:
dotnet build

it will create HelloWasi.wasm next to the HelloWasi..exe file: 
bin\Debug\net7.0\HelloWasi.wasm
I'll be a single 16 MB file, because it contains all of the required .NET code.

Using Wasmtime, we can run this .wasm file:
wasmtime bin\Debug\net7.0\HelloWasi.wasm

Note the "OSArchitecture" field in the output:

Hello world! The time is 01:33:31
OSArchitecture: Wasm
OSDescription: Browser
FrameworkDescription .NET 7.0.0-dev
ProcessArchitecture Wasm
RuntimeIdentifier browser-wasm

Same exact .wasm file can run on any platform that supports WASI.  I tried it under wsl2 using wasmtime.  Also works under MacOS

Also tried the wasmer runtime.