C/C++ Tooling
Building a Component with wit-bindgen
and wasm-tools
wit-bindgen
is a tool to generate guest language bindings from a given .wit
file. Although it is less integrated into language toolchains than other tools such as cargo-component
, it can currently generate source-level bindings for Rust
, C
, Java (TeaVM)
, and TinyGo
, with the ability for more language generators to be added in the future.
wit-bindgen
can be used to generate C applications that can be compiled directly to Wasm modules using clang
with a wasm32-wasi
target.
First, install the CLI for wit-bindgen
, wasm-tools
, and the WASI SDK
.
The WASI SDK will install a local version of clang
configured with a wasi-sysroot. Follow these instructions to configure it for use. Note that you can also use your installed system or emscripten clang
by building with --target=wasm32-wasi
but you will need some artifacts from WASI SDK to enable and link that build target (more information is available in WASI SDK's docs).
Start by generating a C skeleton from wit-bindgen
using the sample add.wit
file:
>wit-bindgen c add.wit
Generating "example.c"
Generating "example.h"
Generating "example_component_type.o"
This has generated several files - an example.h
(based on the name of your world
) with the prototype of the add
function - int32_t example_add(int32_t x, int32_t y);
, as well as some generated code in example.c
that interfaces with the component model ABI to call your function. Additionally, example_component_type.o
contains object code referenced in example.c
from an extern
that must be linked via clang.
Next, create an add.c
that implements your function defined in example.h
:
#include "example.h"
int32_t example_add(int32_t x, int32_t y)
{
return x + y;
}
Now, you can compile the function into a Wasm module via clang:
clang add.c example.c example_component_type.o -o add-core.wasm -mexec-model=reactor
Use the
clang
included in the WASI SDK installation, for example at<WASI_SDK_PATH>/bin/clang
.
Next, you need to transform the module into a component. For this example, you can use wasm-tools component new
:
wasm-tools component new ./add-core.wasm -o add-component.wasm
Do note this will fail if your code references any WASI APIs that must be imported. This requires an additional step as the WASI SDK still references wasi_snapshot_preview1
APIs that are not compatible directly with components.
For example, modifying the above to reference printf()
would compile:
#include "example.h"
#include <stdio.h>
int32_t example_add(int32_t x, int32_t y)
{
int32_t result = x + y;
printf("%d", result);
return result;
}
However, the module would fail to transform to a component:
>wasm-tools component new ./add-core.wasm -o add-component.wasm
error: failed to encode a component from module
Caused by:
0: failed to decode world from module
1: module was not valid
2: module requires an import interface named `wasi_snapshot_preview1`
Install the appropriate reactor adapter module as documented here - you can either get the linked release of wasi_snapshot_preview1.reactor.wasm
and rename it to wasi_snapshot_preview1.wasm
, or build it directly from source in wasmtime
following the instructions here (make sure you git submodule update --init
first).
Now, you can adapt preview1 to preview2 to build a component:
wasm-tools component new add-core.wasm --adapt wasi_snapshot_preview1.wasm -o add-component.wasm
Finally, you can inspect the embedded wit to see your component (including any WASI imports if necessary):
>wasm-tools component wit add-component.wasm
package root:component;
world root {
import wasi:io/error@0.2.0;
import wasi:io/streams@0.2.0;
import wasi:cli/stdin@0.2.0;
import wasi:cli/stdout@0.2.0;
import wasi:cli/stderr@0.2.0;
import wasi:cli/terminal-input@0.2.0;
import wasi:cli/terminal-output@0.2.0;
import wasi:cli/terminal-stdin@0.2.0;
import wasi:cli/terminal-stdout@0.2.0;
import wasi:cli/terminal-stderr@0.2.0;
import wasi:clocks/wall-clock@0.2.0;
import wasi:filesystem/types@0.2.0;
import wasi:filesystem/preopens@0.2.0;
export add: func(x: s32, y: s32) -> s32;
}
Running a Component from C/C++ Applications
It is not yet possible to run a Component using the wasmtime
c-api
- see this issue. The c-api is preferred to trying to directly use the Rust crate in C++.
However, C/C++ language guest components can be composed with components written in any other language and run by their toolchains, or even composed with a C language command component and run via the wasmtime
CLI or any other host.
See the Rust Tooling guide for instructions on how to run this component from the Rust example-host
.