Using WebAssembly from .NET with Wasmtime

Wasmtime, the WebAssembly runtime from the Bytecode Alliance, recently added an early preview of an API for .NET Core, Microsoft’s free, open-source, and cross-platform application runtime. This API enables developers to programmatically load and execute WebAssembly code directly from their .NET programs.

.NET Core is already a cross-platform runtime, so why should .NET developers pay any attention to WebAssembly?

There are several reasons to be excited about WebAssembly if you’re a .NET developer, such as sharing the same executable code across platforms, being able to securely isolate untrusted code, and having a seamless interop experience with the upcoming WebAssembly interface types proposal.

Share more code across platforms

.NET assemblies can already be built for cross-platform use, but using a native library (for example, a library written in C or Rust) can be difficult because it requires native interop and distributing a platform-specific build of the library for each supported platform.

However, if the native library were compiled to WebAssembly, the same WebAssembly module could be used across many different platforms and programming environments, including .NET; this would simplify the distribution of the library and the applications that depend on it.

Securely isolate untrusted code

The .NET Framework attempted to sandbox untrusted code with technologies such as Code Access Security and Application Domains, but ultimately these failed to properly isolate untrusted code. As a result, Microsoft deprecated their use for sandboxing and ultimately removed them from .NET Core.

Have you ever wanted to load untrusted plugins in your application but couldn’t figure out a way to prevent the plugin from invoking arbitrary system calls or from directly reading your process’ memory? You can do this with WebAssembly because it was designed for the web, an environment where untrusted code executes every time you visit a website.

A WebAssembly module can only call the external functions it explicitly imports from a host environment, and may only access a region of memory given to it by the host. We can leverage this design to sandbox code in a .NET program too!

Improved interoperability with interface types

The WebAssembly interface types proposal introduces a way for WebAssembly to better integrate with programming languages by reducing the amount of glue code that is necessary to pass more complex types back and forth between the hosting application and a WebAssembly module.

When support for interface types is eventually implemented by the Wasmtime for .NET API, it will enable a seamless experience for exchanging complex types between WebAssembly and .NET.

Diving into using WebAssembly from .NET

In this article we’ll dive into using a Rust library compiled to WebAssembly from .NET with the Wasmtime for .NET API, so it will help to be a little familiar with the C# programming language to follow along.

The API described here is fairly low-level. That means that there is quite a bit of glue code required for conceptually simple operations, such as passing or receiving a string value.

In the future we’ll also provide a higher-level API based on WebAssembly interface types which will significantly reduce the code required for the same operations. Using that API will enable interacting with a WebAssembly module from .NET as easily as you would a .NET assembly.

Note also that the API is still under active development and will change in backwards-incompatible ways. We’re aiming to stabilize it as we stabilize Wasmtime itself.

If you’re reading this and you aren’t a .NET developer, that’s okay! Check out the Wasmtime Demos repository for corresponding implementations for Python, Node.js, and Rust too!

Creating the WebAssembly module

We’ll start by building a Rust library that can be used to render Markdown to HTML. However, instead of compiling the Rust library for your processor architecture, we’ll be compiling it to WebAssembly so we can use it from .NET.

You don’t need to be familiar with the Rust programming language to follow along, but it will help to have a Rust toolchain installed if you want to build the WebAssembly module. See the homepage for Rustup for an easy way to install a Rust toolchain.

Additionally, we’re going to use cargo-wasi, a command that bootstraps everything we need for Rust to target WebAssembly:

cargo install cargo-wasi

Next, clone the Wasmtime Demos repository:

git clone
cd wasmtime-demos

This repository includes the markdown directory that contains a Rust library. The library wraps a well-known Rust crate that can render Markdown as HTML. (Note for .NET developers: a crate is like a NuGet package, in a way).

Let’s build the markdown WebAssembly module using cargo-wasi:

cd markdown
cargo wasi build --release

There should now be a markdown.wasm file in the target/wasm32-wasi/release directory.

If you’re curious about the Rust implementation, open src/; it contains the following:

use pulldown_cmark::{html, Parser};
use wasm_bindgen::prelude::*;

pub fn render(input: &str) -> String {
    let parser = Parser::new(input);
    let mut html_output = String::new();
    html::push_html(&mut html_output, parser);
    return html_output;

The Rust library is exporting only a single function, render, that takes a string (the Markdown) as input and returns a string (the rendered HTML). All of the code required to parse Markdown and translate it to HTML is provided by the pulldown-cmark crate.

Let’s step back and simply appreciate what is about to happen here. We’re taking a popular Rust crate, wrapping it with a few lines of code that exposes the functionality as a WebAssembly function, and then compiling it to a WebAssembly module that we can load from .NET regardless of the platform we’re running on. How cool is that!?

Peeking under the hood of a WebAssembly module

Now that we have the WebAssembly module we’re going to use, what does it need from a host to function and what functionality does it offer the host?

To figure that out, let’s disassemble the module to a textual representation using the wasm2wat tool from the WebAssembly Binary Toolkit to a file called markdown.wat:

wasm2wat markdown.wasm --enable-multi-value > markdown.wat

Note: the --enable-multi-value option enables support for functions that return multiple values and is required to disassemble the markdown module.

What the module needs from a host

The module’s imports define what the host should provide for the module to work.

Here are the imports for the markdown module:

(import "wasi_unstable" "fd_write" (func $fd_write (param i32 i32 i32 i32) (result i32)))
(import "wasi_unstable" "random_get" (func $random_get (param i32 i32) (result i32)))

This tells us that the module will need two functions from the host: fd_write and random_get. These are actually WebAssembly System Interface (WASI) functions that have well-defined behavior: fd_write is used to write data to a file descriptor and random_get will fill a buffer with random data.

Shortly we’ll implement these functions for a .NET host, but it is important to understand that this module can only call these functions from the host; the host gets to decide how, and even if, the functions are implemented.

What the module offers a host

The module’s exports define what functionality it offers the host.

Here are the exports for the markdown module:

(export "memory" (memory 0))
(export "render" (func $render_multivalue_shim))
(export "__wbindgen_malloc" (func $__wbindgen_malloc))
(export "__wbindgen_realloc" (func $__wbindgen_realloc))
(export "__wbindgen_free" (func $__wbindgen_free))


(func $render_multivalue_shim (param i32 i32) (result i32 i32) ...)
(func $__wbindgen_malloc (param i32) (result i32) ...)
(func $__wbindgen_realloc (param i32 i32 i32) (result i32) ...)
(func $__wbindgen_free (param i32 i32) ...)

First, the module is exporting a memory. A WebAssembly memory is the linear address space accessible to the module; it will be the only region of memory the module can read from or write to. As the module cannot access any other region of the host’s address space directly, the exported memory is where the host will exchange data with the WebAssembly module.

Second, the module exports the render function we implemented in Rust. But wait a second, why does it have two parameters and return two values when the Rust implementation only has one parameter and one return value?

In Rust, both a string slice (&str) and an owned string (String) are represented as an address and length (in bytes) pair when compiled to WebAssembly. Thus, the WebAssembly version of the Rust function takes an address-length pair for the markdown input string and returns an address-length pair for the rendered HTML string. Here, addresses are represented as integer offsets into the exported memory.

Note that since the Rust code returns a String, which is an owned type, the caller of render will be responsible for freeing the returned memory containing the rendered string.

During the implementation of the .NET host we’ll discuss the rest of the exports.

Creating the .NET project

We will need a .NET Core SDK to create a .NET Core project, so make sure you have a 3.0 or later SDK installed.

Start by creating a directory for the project:

mkdir WasmtimeDemo
cd WasmtimeDemo

Next, create a new .NET Core console project:

dotnet new console

Finally, add a reference to the Wasmtime NuGet package:

dotnet add package wasmtime --version 0.8.0-preview2

That’s it! Now we’re ready to use the Wasmtime for .NET API to load and execute the markdown WebAssembly module.

Importing .NET code from WebAssembly

Importing .NET functions from WebAssembly is as simple as implementing the IHost interface in .NET. This only requires a public Instance property that will represent the WebAssembly module instance the host is bound to.

The Import attribute is then used to mark functions and fields as imports to a WebAssembly module.

As we discussed earlier, the module requires two imports from the host: fd_write and random_get, so let’s create implementations for those functions.

Create a file named Host.cs in the project directory and add the following content:

using System.Security.Cryptography;
using Wasmtime;

namespace WasmtimeDemo
    class Host : IHost
        // These are from the current WASI proposal.
        const int WASI_ERRNO_NOTSUP = 58;
        const int WASI_ERRNO_SUCCESS = 0;

        public Instance Instance { get; set; }

        [Import("fd_write", Module = "wasi_unstable")]
        public int WriteFile(int fd, int iovs, int iovs_len, int nwritten)
            return WASI_ERRNO_NOTSUP;

        [Import("random_get", Module = "wasi_unstable")]
        public int GetRandomBytes(int buf, int buf_len)
            _random.GetBytes(Instance.Externs.Memories[0].Span.Slice(buf, buf_len));
            return WASI_ERRNO_SUCCESS;

        private RNGCryptoServiceProvider _random = new RNGCryptoServiceProvider();

The fd_write implementation simply returns an error indicating the operation isn’t supported. It is used by the module for writing errors to stderr, which will not happen for this example.

The random_get implementation fills the requested buffer with random bytes. It slices the Span representing the entire exported memory of the module so that the .NET implementation can write directly to the requested buffer without having to perform any intermediate copies. The random_get function is being called by the implementation of HashMap from Rust’s standard library.

That’s all it takes to expose .NET functions to the WebAssembly module with the Wasmtime for .NET API.

However, before we can load the WebAssembly module and use it from .NET, we need to discuss how a string gets passed from the .NET host as a parameter to the render function.

Being a good host

Based on the exports of the module, we know it exports a memory. From the host’s perspective, think of a WebAssembly module’s exported memory as being granted access to the address space of a foreign process, even though the module shares the same process of the host itself.

If you randomly write data to a foreign address space, Bad Things Happen™ because it’s quite easy to corrupt the state of the other program and cause undefined behavior, such as a crash or the total protonic reversal of the universe. So how can a host pass data to the WebAssembly module in a safe manner?

Internally the Rust program uses a memory allocator to manage its memory. So, for .NET to be a good host to the WebAssembly module, it must also use the same memory allocator when allocating and freeing memory accessible to the WebAssembly module.

Thankfully, wasm-bindgen, used by the Rust program to export itself as WebAssembly, also exported two functions for that purpose: __wbindgen_malloc and __wbindgen_free. These two functions are essentially malloc and free from C, except __wbindgen_free needs the size of the previous allocation in addition to the memory address.

With this in mind, let us write a simple wrapper for these exported functions in C# so we can easily allocate and free memory accessible to the WebAssembly module.

Create a file named Allocator.cs in the project directory and add the following content:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Wasmtime.Externs;

namespace WasmtimeDemo
    class Allocator
        public Allocator(ExternMemory memory, IReadOnlyList<ExternFunction> functions)
            _memory = memory ??
                throw new ArgumentNullException(nameof(memory));

            _malloc = functions
                .Where(f => f.Name == "__wbindgen_malloc")
                .SingleOrDefault() ??
                    throw new ArgumentException("Unable to resolve malloc function.");

            _free = functions
                .Where(f => f.Name == "__wbindgen_free")
                .SingleOrDefault() ??
                    throw new ArgumentException("Unable to resolve free function.");

        public int Allocate(int length)
            return (int)_malloc.Invoke(length);

        public (int Address, int Length) AllocateString(string str)
            var length = Encoding.UTF8.GetByteCount(str);

            int addr = Allocate(length);

            _memory.WriteString(addr, str);

            return (addr, length);

        public void Free(int address, int length)
            _free.Invoke(address, length);

        private ExternMemory _memory;
        private ExternFunction _malloc;
        private ExternFunction _free;

This code looks complicated, but all it is doing is finding the needed exported functions by name from the module and wrapping them with an easier to use interface.

We’ll use this helper Allocator class to allocate the input string to the exported render function.

Now we’re ready to render some Markdown.

Rendering the Markdown

Open Program.cs in the project directory and replace it with the following content:

using System;
using System.Linq;
using Wasmtime;

namespace WasmtimeDemo
    class Program
        const string MarkdownSource = 
            "# Hello, `.NET`! Welcome to **WebAssembly** with [Wasmtime](!";

        static void Main()
            using var engine = new Engine();

            using var store = engine.CreateStore();

            using var module = store.CreateModule("markdown.wasm");

            using var instance = module.Instantiate(new Host());

            var memory = instance.Externs.Memories.SingleOrDefault() ??
                throw new InvalidOperationException("Module must export a memory.");

            var allocator = new Allocator(memory, instance.Externs.Functions);

            (var inputAddress, var inputLength) = allocator.AllocateString(MarkdownSource);

                object[] results = (instance as dynamic).render(inputAddress, inputLength);

                var outputAddress = (int)results[0];
                var outputLength = (int)results[1];

                    Console.WriteLine(memory.ReadString(outputAddress, outputLength));
                    allocator.Free(outputAddress, outputLength);
                allocator.Free(inputAddress, inputLength);

Let’s walk through what the code doing. Step-by-step, it:

  1. Creates an Engine. The engine represents the Wasmtime runtime itself. The runtime is what enables loading and executing WebAssembly modules from .NET.
  2. Then it creates a Store. A store is where all WebAssembly objects, such as modules and their instantiations, are kept. There can be multiple stores in an engine, but their associated objects cannot interact with one another.
  3. Next it creates a Module from the markdown.wasm file on disk. A Module represents the data of the WebAssembly module itself, such as what it imports and exports. A module can have one or more instantiations. An instantiation is the runtime representation of a WebAssembly module. It compiles the module’s WebAssembly instructions to instructions of the current CPU architecture, allocates the memory accessible to the module, and binds imports from the host.
  4. It instantiates the module using an instance of the Host class we implemented earlier, binding the .NET functions as imports.
  5. Finds the memory exported by the module.
  6. Creates an allocator and then allocates a string for the Markdown source we want to render.
  7. Invokes the render function with the input string by casting the instance to dynamic. This is a C# feature that enables dynamic binding of functions at runtime; think of it simply as a shortcut to searching for the exported render function and invoking it.
  8. Outputs the rendered HTML by reading the returned string from the exported memory of the WebAssembly module.
  9. Finally, it frees both the input string it allocated and the returned string that the Rust program gave us to own.

That’s it for the implementation; onwards to actually running the code!

Running the .NET program

Before we can run the program, we need to copy markdown.wasm to the project directory, as this is where we’ll run the program from. You can find the markdown.wasm file in the target/wasm32-wasi/release directory from where you built it.

From the Program.cs source above, we see that the program hard-coded some Markdown to render:

# Hello, `.NET`! Welcome to **WebAssembly** with [Wasmtime](!

Run the program to render it as HTML:

dotnet run

If everything went according to plan, this should be the result:

<h1>Hello, <code>.NET</code>! Welcome to <strong>WebAssembly</strong> with <a href="">Wasmtime</a>!</h1>

What’s next for Wasmtime for .NET?

That was a surprisingly large amount of C# code that was necessary to implement this demo, wasn’t it?

There are two major features we have planned that will help simplify this:

  • Exposing Wasmtime’s WASI implementation to .NET (and other languages)

    In our implementation of Host above, we had to manually implement fd_write and random_get, which are WASI functions.

    Wasmtime itself has a WASI implementation, but currently it isn’t accessible to the .NET API.

    Once the .NET API can access and configure the WASI implementation of Wasmtime, there will no longer be a need for .NET hosts to provide their own implementation of WASI functions.

  • Implementing interface types for .NET

    As discussed earlier, WebAssembly interface types enable a more idiomatic integration of WebAssembly with a hosting programming language.

    Once the .NET API implements the interface types proposal, there shouldn’t be a need to create an Allocator class like the one we implemented.

    Instead, functions that use types like string should simply work without having to write any glue code in .NET.

The hope, then, is that this is what it might look like in the future to implement this demo from .NET:

using System;
using Wasmtime;

namespace WasmtimeDemo
    interface Markdown
        string Render(string input);

    class Program
        const string MarkdownSource =
            "# Hello, `.NET`! Welcome to **WebAssembly** with [Wasmtime](!";

        static void Main()
            using var markdown = Module.Load<Markdown>("markdown.wasm");


I think we can all agree that looks so much better!

That’s a wrap!

This is the exciting beginning of using WebAssembly outside of the web browser from many different programming environments, including Microsoft’s .NET platform.

If you’re a .NET developer, we hope you’ll join us on this journey!

The .NET demo code from this article can be found in the Wasmtime Demos repository.

About Peter Huene

Peter is a software developer working on Wasmtime, Cranelift, and WASI at Mozilla.

More articles by Peter Huene…


  1. Reza

    This was amazing. Can’t wait to try it myself. Thanks for sharing!

    December 4th, 2019 at 13:32

  2. Tom Van Acker

    So cool! The future looks bright, I love being a .NET developer!

    December 4th, 2019 at 21:18

  3. Austin Wise

    I don’t know if I’m doing something wrong, but I can only get this to work if I use a debug build of wastime from the master branch. If I use the release build or the one that comes with the Nuget package, I get the following exception:

    Unhandled exception. System.NotSupportedException: Unsupported value kind.
    at Wasmtime.Interop.ToString(ValueKind kind)
    at Wasmtime.Bindings.FunctionBinding.ValidateParameters()
    at Wasmtime.Bindings.FunctionBinding.Validate()
    at Wasmtime.Bindings.FunctionBinding..ctor(FunctionImport import, MethodInfo method)
    at Wasmtime.Bindings.Binding.BindFunction(FunctionImport import, IEnumerable`1 methods)
    at Wasmtime.Bindings.Binding.GetImportBindings(IHost host, Module module)
    at Wasmtime.IHost.GetImportBindings(Module module)
    at Wasmtime.Instance..ctor(Module module, IHost host)
    at Wasmtime.Module.Instantiate(IHost host)
    at WasmtimeDemo.Program.Main() in /home/austin/wasmtime-demos/dotnet/Program.cs:line 20

    The value kind is 0x03020100, which looks suspicious.

    I tried the sample under both Windows and WSL (fake linux). Version numbers were:
    rustc 1.39.0 (4560ea788 2019-11-04)
    cargo-wasi 0.1.18 (0fc4ec83f3 2019-11-19)
    cargo 1.39.0 (1c6ec66d5 2019-09-30)

    December 5th, 2019 at 00:47

    1. Peter Huene

      Hi Austin,

      Thanks for reporting this to me. I’ll look into it immediately and let you know what’s going on.

      December 5th, 2019 at 09:06

    2. Peter Huene

      I was able to reproduce this on Windows only and I’m now investigating the cause.

      If it’s an issue with the .NET API, I’ll release a new NuGet package with the fix and update the article.

      Thanks very much for the detailed information!

      December 5th, 2019 at 09:54

    3. Peter Huene

      I’ve determined the cause of the issue and I should have a fixed NuGet package (0.8.0-preview2) up shortly and then I’ll update the article with that version.

      Sorry that you ran into this!

      December 5th, 2019 at 11:25

    4. Peter Huene

      Hi Austin,

      I’ve pushed up 0.8.0-preview2 of the Wasmtime package that contains this fix.

      Please update your PackageReference in your project file (you may need to clean your obj/bin directories too).

      Please let me know if this doesn’t fix the issue for you. Thanks!

      December 5th, 2019 at 13:44

      1. Austin Wise

        Hi Peter,

        I tried the new Nuget package and it works great. And the startup time and runtime is much faster than a debug build of wastime.


        December 5th, 2019 at 16:10

  4. Francis West

    This is actually something to get excited about – please keep going :) :)

    That ‘future implementation’ obviously looks appealing.

    December 5th, 2019 at 06:03

  5. Jeff Jones

    How do you write an article about wasm and .NET, and totally ignore Blazor Client? That *IS* .NET development for wasm, and it is already in preview (Blazor Server is already in production, but is not wasm).

    December 5th, 2019 at 06:54

    1. Peter Huene

      Hi Jeff,

      Blazor is a super exciting technology to be sure, but I view it as trying to solve a different problem.

      My understanding of client-side Blazor is that it offers a .NET runtime implemented in WebAssembly so that it can load and execute .NET assemblies when running in the context of a web browser, primarily to render and interact with web pages.

      It’s definitely related to what’s described here in that, under the covers at least, WebAssembly is being used to solve the problem.

      However, the goal of this effort is to enable .NET programs to interact with WebAssembly modules outside the context a web browser. The .NET code isn’t compiled to WebAssembly, instead it’s running normally inside of a native .NET runtime and it uses the Wasmtime runtime to execute the WebAssembly code, with the hope that in the near future it will be a seamless interop experience.

      Does that distinction make sense?

      Using WebAssembly outside of a web browser opens up interesting scenarios of securely running code from many different programming languages that can target WebAssembly from a .NET program.

      December 5th, 2019 at 09:41

  6. Chris

    This is great. Thank you!

    December 5th, 2019 at 09:35

  7. Tony Henrique

    It is awesome to see this work between WebAssembly and .NET

    December 5th, 2019 at 13:36

  8. TamusJRoyce

    How does this compare to Blazer? Rust also compiles to web assembly. LLVM is a powerful compiler.

    December 5th, 2019 at 17:12

    1. Peter Huene

      Hi TamusJRoyce,

      Thanks for the questions!

      As I mentioned in a previous comment, the current goal of client-side Blazor is to enable running .NET code inside of a web browser to render and interact with web pages. There’s a lot of exciting things happening with Blazor, with future plans having cross-platform UI rendering *outside* of a web browser to complete with frameworks like Electron and native GUI applications.

      However, the goal of Wasmtime is to be a runtime for WebAssembly outside of a web browser. As you mentioned, many languages compile to WebAssembly thanks to LLVM (Rust being one of those, which we use in this demo). With Wasmtime you can load and execute the WebAssembly directly in .NET, with the future goal of making it as easy to use as if you were referencing a .NET assembly.

      I’m happy to answer more questions here or you can always find me on Twitter as @peterhuene.

      Thanks for reading!

      December 5th, 2019 at 18:36

  9. Johan Mulder

    Thnks Peter, awesome article. To me this reinforces the idea that all library creators should target web assembly for all their libraries then it can be consumed anywhere from the web to cross platform desktop languages. WASM is definitely the future and you are helping making that a reality.

    December 6th, 2019 at 11:45

  10. Md khayrul hasan

    A lot of thanks for good information

    December 10th, 2019 at 10:42

  11. Fallon

    Why Wasmtime and not Wasmer? They appear to have the same goals, correct me if I’m wrong.

    December 12th, 2019 at 16:35

    1. Peter Huene

      Hi Fallon,

      It’s true that Wasmtime and Wasmer have some overlapping goals, but they are competing projects with different approaches and priorities regarding running WebAssembly outside of a web browser.

      Miguel de Icaza (a fantastic human being that I had the privilege to work with when I was at Microsoft) has created a .NET binding for Wasmer which you can find on GitHub. Check it out if you’re curious!

      We do plan on making .NET a first-class binding for Wasmtime and we’re hoping that the interface types support will be coming soon!

      December 12th, 2019 at 17:10

  12. Julien Bataillé

    Thanks Peter for the great article, it’s really interesting and helpful.

    I’m interested in making a Java binding for Wasmtime.
    Are you aware of anyone else working on it at this time?

    December 19th, 2019 at 05:42

    1. Peter Huene

      Hi Julien,

      I’m not aware of one at this time, but we are always open to code contributions!

      You can find the C API bindings the .NET uses here: I would imagine a Java implementation would also use the same libwasmtime exported functions.

      Feel free to email me at if you have any questions or mention me as @peterhuene on GitHub if you would like any reviews.


      December 29th, 2019 at 11:00

  13. Oleksiy Gapotchenko

    This is seriously awesome. Thank you for putting this together. I am sure the technology has the bright future.

    I would like to suggest to use the names like IWasmHost instead of IHost, WasmInstance instead of just Instance, WasmBinding instead of plain Binding etc.

    While .NET platform provides an awesome notion of namespaces, the life is SO much easier when component suppliers take a bit of consideration on how said components would interfere in the code editor of their consumers.

    Prefixing public identifiers with something that denotes a component is the way to go in .NET ecosystem! So something like WasmXYZ is very on point.

    December 28th, 2019 at 03:05

    1. Peter Huene

      Hi Oleksiy,

      That’s great feedback, thank you!

      If you would like to refactor the types to appropriate names and submit a PR to the Wasmtime repo, I would be happy to review it.

      December 29th, 2019 at 11:01

  14. zuraff

    I’am trying to build a small proof-of-concept for my sandboxing scenario.

    Unfortunately I am running into an issue with the wastime-demos:
    Any help will be appreciated.

    January 3rd, 2020 at 02:31

Comments are closed for this article.