Stream: C#/.net-collaboration

Topic: Components


view this post on Zulip Gerard Smit (Aug 26 2025 at 20:15):

Hey,

I'm trying to make a component in C#. I've followed the "Adder" docs and created a component.

I've compiled this component, and I see in the exports that the function docs:adder/add@0.1.0#add is exposed.
However, there are no components (that I can see in https://wa2.dev/exports).

When I try to call "docs:adder/add@0.1.0#add", I'm getting the error that the function cannot be found.
I've also tried multiple names (add etc.) without success:

using var engine = new Engine();
using var linker = new Linker(engine);
linker.AddWasiP2();

using var store = new Store(engine);

var bytes = File.ReadAllBytes("Wasm/Adder.wasm");
using var component = Component.Compile(engine, bytes);

var instance = component.CreateInstance(linker, store);

using var a = ComponentValue.CreateInt32(40);
using var b = ComponentValue.CreateInt32(2);

for (var i = 0; i < 10; i++)
{
  using var result = instance.Call("docs:adder/add@0.1.0#add", [a, b]);

  Assert.Equal(42, result.GetInt32(0));
}

With a component in WAT, the following works:

const string addIntModule =
    """
    (component
      (core module $AddModule
        (func (export "return") (param i32) (param i32) (result i32)
          local.get 0
          local.get 1
          i32.add
        )
      )

      (core instance $add_instance (instantiate $AddModule))

      (func (export "return") (param "a" s32) (param "b" s32) (result s32)
        (canon lift
          (core func $add_instance "return")
        )
      )
    )
    """;

using var engine = new Engine();
using var linker = new Linker(engine);
using var store = new Store(engine);

using var component = Component.Compile(engine, addIntModule);

var instance = component.CreateInstance(linker, store);

using var a = ComponentValue.CreateInt32(40);
using var b = ComponentValue.CreateInt32(2);

for (var i = 0; i < 10; i++)
{
  using var result = instance.Call("return", [a, b]);

  Assert.Equal(42, result.GetInt32(0));
}

So I'm not sure what I'm doing wrong :sweat_smile:

The Compile-method calls wasmtime_component_new which is OK I think.

Note: I'm implementing the component c-api in .NET, so I'm testing stuff out :slight_smile:

view this post on Zulip Gerard Smit (Aug 26 2025 at 20:29):

It seems I can get docs:adder/add@0.1.0, and then I get a different error:

Export 'docs:adder/add@0.1.0' is not a function

So it looks like I need to get this export first, and then the function. I'm not sure what kind of export this is (e.g. a interface). And I'm also not sure if this is already implemented in the c-api. :thinking:

view this post on Zulip Gerard Smit (Aug 26 2025 at 20:46):

Got it. I have to export the function directly:

package docs:adder@0.1.0;

world example {
    export add: func(x: u32, y: u32) -> u32;
}

Then I can call the function directly with "add".

However now I'm getting a panic error :hmm:

Exit code is -1073740791 (thread '<unnamed>' panicked at crates\c-api\src\store.rs:110:39:
called `Option::unwrap()` on a `None` value

view this post on Zulip Gerard Smit (Aug 26 2025 at 20:52):

Got that as well. The store was not initialized with the WasiP2 context.
Now I'm getting C# exceptions. Progress :tada:

error while executing at wasm backtrace:
    0:  0x8b366 - .tmp1wig0Y!S_P_CoreLib_System_Runtime_EH__DispatchException
    1:  0x53c6c - .tmp1wig0Y!S_P_CoreLib_System_Runtime_EH__RhpThrowEx
    2:  0x6dbd1 - .tmp1wig0Y!S_P_CoreLib_Internal_Runtime_CompilerHelpers_ThrowHelpers__ThrowNullReferenceException
    3:  0x63e18 - .tmp1wig0Y!S_P_CoreLib_Internal_Runtime_CompilerHelpers_StartupCodeHelpers__InitializeGlobalTablesForModule
    4:  0x98ead - .tmp1wig0Y!S_P_CoreLib_Internal_Runtime_CompilerHelpers_StartupCodeHelpers__InitializeModules
    5:   0x24ba - .tmp1wig0Y!InitializeRuntime()
    6:   0x5e3e - .tmp1wig0Y!Thread::ReversePInvokeAttachOrTrapThread(ReversePInvokeFrame*)
    7:   0x5f4a - .tmp1wig0Y!RhpReversePInvokeAttachOrTrapThread2
    8:  0x39a90 - .tmp1wig0Y!RhpReversePInvoke
    9:  0x69eca - .tmp1wig0Y!Adder_ExampleWorld_exports_ExampleWorld__wasmExportAdd
note: using the `WASMTIME_BACKTRACE_DETAILS=1` environment variable may show more debugging information

Caused by:
    wasm trap: uninitialized element

view this post on Zulip Gerard Smit (Aug 26 2025 at 21:02):

Nvm, that's a bug in the latest version in NativeAOT-LLVM. 10.0.0-alpha.1.25162.1 works without issues :tada:

Now I can run components in .NET Wasmtime :smile:

view this post on Zulip Joel Dice (Aug 26 2025 at 21:06):

Congrats; I didn't think that was possible. Perhaps https://github.com/bytecodealliance/wasmtime-dotnet/issues/324 ought to be updated?

Context https://github.com/bytecodealliance/wasmtime/tree/main/examples/component Add support for making the same example from C#. https://github.com/WebAssembly/component-model/tree/main https://g...

view this post on Zulip Gerard Smit (Aug 26 2025 at 21:10):

I made a new library with ClangSharp (that generated C# bindings for headers) and then implemented a minimal wrapper around this. So it's not directly wasmtime-dotnet :slight_smile:

view this post on Zulip Gerard Smit (Aug 26 2025 at 21:12):

There was an open issue about using ClangSharp: https://github.com/bytecodealliance/wasmtime-dotnet/issues/329
but with this, the structure is completely different. It was easier to start from scratch for me.

Right now the api is generated by hand by referencing the wasmtime c library headers. We could automate this process which would make it easier to catch issues and make updates. See: Doing a little...

view this post on Zulip James Sturtevant (Aug 26 2025 at 22:05):

awsome work! Someone started scratching out what it might look like in wasmtime-dotnet, but I was thinking that using clangsharp might be better approach here particuallary for the component model stuff. Do you think we could add a folder to wasmtime-dotnet and have low level API's exposed then add nice api on top?

view this post on Zulip James Sturtevant (Aug 26 2025 at 22:05):

https://github.com/bytecodealliance/wasmtime-dotnet/pull/346

This is a rough first pass looking at what's required for wasm component support. I've pulled in everything from the component folder of the C-API (see here) and have wrapped some of the ea...

view this post on Zulip Gerard Smit (Aug 28 2025 at 13:48):

If I want to add the component-model back to the main repo with a PR, it's gonna be a large one. :sweat_smile: Maybe I need to chop it down first.

But I'm still experimenting around.

I just made a source generator that converts the WIT-files:

package docs:adder@0.1.0;

world example {
    export add: func(x: u32, y: u32) -> u32;
}

To callable methods:

// Create component instance (component = the compiled WASM file)
var instance = component.CreateInstance(linker, store);

// Wrap the instance around world 'example'
// The type 'Wit.docs.adder.example' is source generated.
var example = new Wit.docs.adder.example(instance);

// Call the method 'add'
var result = example.add(40, 2);

Last updated: Dec 06 2025 at 07:03 UTC