What is WASI-SDK?
Unlike Rust with cargo-component, C/C++ lacks an integrated toolchain for building WebAssembly components. The WASI SDK provides the essential tooling needed to compile C code to WebAssembly.
The WASI SDK includes:
- 
clangcompiler configured with a WASI sysroot (complete set of target platform headers and libraries) for thewasm32-wasitarget
- 
WASI-enabled C standard library (libc) that implements WASI interfaces
- Cross-platform support for different operating systems and architectures
- Preview 2 compatibility for building modern WebAssembly components
This allows you to write C plugins that can access filesystem, networking, and other system resources through WASI interfaces, just like Rust plugins.
WASI-SDK Setup
The project uses a custom script just dl-wasi-sdk that acts like a package manager, automatically downloading and extracting the correct version of the WASI SDK for your OS/architecture into c_deps/ (which acts like a node_modules for C dependencies).
How to write a C plugin
Build Process
C plugins are built using a two-step process:
- 
Generate bindings: wit-bindgen c ./crates/pluginlab/wit --world plugin-api --out-dir ./c_modules/plugin-namecreates the C bindings from your WIT interface
- 
Compile and convert: Use the WASI SDK's clangto compile C code to a WebAssembly module (P1), then convert it to a P2 component
The build process:
- Compiles component.c,plugin_api.c, andplugin_api_component_type.oto a WebAssembly module with-mexec-model=reactor:
  ./c_deps/wasi-sdk/bin/clang component.c plugin_api.c plugin_api_component_type.o \
    -o plugin-name-c.module.p1.wasm -mexec-model=reactor
- Converts the P1 module to a P2 component using wasm-tools component new:
  wasm-tools component new plugin-name-c.module.p1.wasm -o plugin-name-c.wasm
File Structure
The C plugins follow this structure in the repo:
c_deps/                           # WASI SDK installation
c_modules/
  plugin-echo/                    # Plugin directory
    component.c                   # Your plugin implementation
    plugin_api.c                  # Generated bindings (from wit-bindgen)
    plugin_api.h                  # Generated header (from wit-bindgen)
    plugin_api_component_type.o   # Generated object file (from wit-bindgen)
    plugin-echo-c.module.p1.wasm  # Compiled WebAssembly module (P1)
    plugin-echo-c.wasm            # Final WebAssembly component (P2)
Plugin Implementation
The C plugin implements the same interface as the Rust version, with function signatures generated from the WIT interface by wit-bindgen:
- 
exports_repl_api_plugin_name()corresponds tofn name() -> String
- 
exports_repl_api_plugin_man()corresponds tofn man() -> String
- 
exports_repl_api_plugin_run()corresponds tofn run(payload: String) -> Result<PluginResponse, ()>
Here's the key implementation details - plugin-echo/component.c:
#include "plugin_api.h"
#include <string.h>
#include <stdlib.h>
void exports_repl_api_plugin_name(plugin_api_string_t *ret)
{
    // Populate ret with "echoc" as the plugin name
    // plugin_api_string_dup() allocates new memory and copies the string
    plugin_api_string_dup(ret, "echoc");
}
void exports_repl_api_plugin_man(plugin_api_string_t *ret)
{
    // Populate ret with the manual text for the echo command
    // plugin_api_string_dup() allocates new memory and copies the string
    const char *man_text = "some man text ...\n";
    plugin_api_string_dup(ret, man_text);
}
bool exports_repl_api_plugin_run(plugin_api_string_t *payload, exports_repl_api_plugin_plugin_response_t *ret)
{
    // Set status to success (0 = success, 1 = error)
    ret->status = REPL_API_TRANSPORT_REPL_STATUS_SUCCESS;
    // Set stdout to contain the payload
    // is_some = true means the optional string has a value
    ret->stdout.is_some = true;
    // Create a properly null-terminated string from the payload
    // The payload has ptr and len, we need to ensure it's null-terminated
    char *temp_str = malloc(payload->len + 1);
    if (temp_str == NULL)
    {
        // Handle allocation failure
        ret->stdout.is_some = false;
        ret->stderr.is_some = false;
        return false;
    }
    // Copy the payload data and null-terminate it
    memcpy(temp_str, payload->ptr, payload->len);
    temp_str[payload->len] = '\0';
    // Use plugin_api_string_dup to create the output string
    plugin_api_string_dup(&ret->stdout.val, temp_str);
    // Free our temporary string
    free(temp_str);
    // Set stderr to none (no error output)
    ret->stderr.is_some = false;
    // Return true for success (false would indicate an error)
    // This corresponds to Ok(response) in the Rust Result<T, ()> pattern
    return true;
}
Memory Management Notes:
- Input parameters (like payload) are owned by the runtime - they MUST NOT be freed by the plugin
- Output parameters (like ret) are populated by the plugin, freed by the runtime
- 
plugin_api_string_dup()allocates new memory for string copies
- The generated _freefunctions handle cleanup automatically
Key Differences from Rust:
- Manual memory management for temporary strings
- Explicit handling of string length vs null termination
- Boolean return values instead of Rust's Result<T, ()>pattern
- Direct manipulation of the generated C structs
📎 Here are links to:
- the plugin-echo C implementation and the plugin_api.h header file
- PR#6 - Add C Language Plugin Support
 
 
              
 
    
Top comments (0)