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:
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:
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
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
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:
Congrats; I didn't think that was possible. Perhaps https://github.com/bytecodealliance/wasmtime-dotnet/issues/324 ought to be updated?
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:
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.
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?
https://github.com/bytecodealliance/wasmtime-dotnet/pull/346
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