Skip to content

⚠️ DOCUMENTATION UNDER CONSTRUCTION ⚠️

This documentation is being written from scratch. What you see here is essentially placeholder content and should not be trusted as accurate. Expect significant changes and updates.

Generated Files Structure ​

The WIT code generator (wit-codegen) produces three files to support WAMR host applications:

Generated Files ​

1. <prefix>.hpp - Component Model Interface Declarations ​

Purpose: Declares all Component Model interfaces (imports and exports) with C++ type mappings.

Contents:

  • Namespace host - Functions the host must implement (imported by guest)
  • Namespace guest - Type aliases for all guest function signatures (exported by guest)
  • Comments documenting package names and function purposes

Example:

cpp
namespace host {
    // Standalone function: dbglog
    // Package: component-model-cpp:test-wasm
    void dbglog(cmcpp::string_t msg);
}

namespace guest {
    // Standalone function: bool-and
    // Package: component-model-cpp:test-wasm
    // Guest function signature for use with guest_function<bool_and_t>()
    using bool_and_t = cmcpp::bool_t(cmcpp::bool_t, cmcpp::bool_t);
}

Key Changes from Earlier Versions:

  • Guest functions are now type aliases only (not function declarations)
  • Used with guest_function<T>() template from <wamr.hpp>
  • Comprehensive documentation comments included

Usage:

cpp
#include "sample.hpp"

2. <prefix>_wamr.hpp - WAMR Helper Function Declarations ​

Purpose: Declares utility functions for registering WAMR native functions.

Contents:

  • NativeRegistration struct for organizing import interfaces
  • get_import_registrations() - Get array of all import interfaces
  • register_all_imports() - Register all imports at once
  • unregister_all_imports() - Unregister all imports at cleanup
  • wasm_utils namespace with constants:
    • DEFAULT_STACK_SIZE
    • DEFAULT_HEAP_SIZE

Header Guard:

cpp
#ifndef GENERATED_WAMR_BINDINGS_HPP
#define GENERATED_WAMR_BINDINGS_HPP

Includes:

  • <wamr.hpp> - WAMR runtime API
  • <cmcpp.hpp> - Component Model C++ library
  • "<prefix>.hpp" - Your interface declarations
  • Standard library headers (<span>, <stdexcept>, <vector>)

Important Note: Helper functions like create_guest_realloc() and create_lift_lower_context() are no longer generated. They are now available directly from <wamr.hpp> in the cmcpp namespace.

Usage:

cpp
#include "sample_wamr.hpp"  // Automatically includes sample.hpp

3. <prefix>_wamr.cpp - WAMR Symbol Arrays and Implementations ​

Purpose: Implements the WAMR bindings and helper functions.

Contents:

  • NativeSymbol arrays for each import (standalone functions or interfaces)
    • Example: dbglog_symbols[] for standalone imports
    • Example: interface_name_symbols[] for interface imports
  • Implementation of all functions declared in <prefix>_wamr.hpp:
    • get_import_registrations()
    • register_all_imports()
    • unregister_all_imports()
  • Constants in wasm_utils namespace

Example:

cpp
#include "sample_wamr.hpp"

// Import interface: dbglog (standalone function)
// Register with: wasm_runtime_register_natives_raw("$root", dbglog_symbols, 1)
NativeSymbol dbglog_symbols[] = {
    host_function("dbglog", host::dbglog),
};

std::vector<NativeRegistration> get_import_registrations() {
    return {
        {"$root", dbglog_symbols, 1},
    };
}

int register_all_imports() {
    int count = 0;
    for (const auto& reg : get_import_registrations()) {
        if (!wasm_runtime_register_natives_raw(reg.module_name, reg.symbols, reg.count)) {
            return -1;  // Registration failed
        }
        count += reg.count;
    }
    return count;
}

void unregister_all_imports() {
    for (const auto& reg : get_import_registrations()) {
        wasm_runtime_unregister_natives(reg.module_name, reg.symbols);
    }
}

namespace wasm_utils {
    const uint32_t DEFAULT_STACK_SIZE = 8192;
    const uint32_t DEFAULT_HEAP_SIZE = 8192;
}

Build Integration: This file must be compiled and linked with your host application:

cmake
add_executable(my_host
    main.cpp
    host_impl.cpp  # Your implementation of host::*
    ${GENERATED_DIR}/sample_wamr.cpp  # Generated bindings
)

File Dependencies ​

<prefix>_wamr.cpp
    ↓ includes
<prefix>_wamr.hpp
    ↓ includes
<prefix>.hpp

Your host code only needs to include <prefix>_wamr.hpp to get access to everything.


Typical Project Structure ​

my-project/
├── CMakeLists.txt
├── main.cpp                    # Your main application
├── host_impl.cpp               # Your implementations of host::*
└── generated/                  # Generated by WIT codegen
    ├── sample.hpp              # Interface declarations
    ├── sample_wamr.hpp         # Helper function declarations
    └── sample_wamr.cpp         # Symbol arrays & implementations

What You Need to Implement ​

The generator creates the scaffolding, but you must provide:

  1. Host Function Implementations (host_impl.cpp)

    • Implement all functions declared in the host namespace
    • Example: host::dbglog()
  2. Main Application Logic (main.cpp)

    • Initialize WAMR runtime
    • Call register_all_imports() during initialization
    • Load and instantiate your WASM module
    • Use cmcpp::create_lift_lower_context() from <wamr.hpp> to create context
    • Call guest functions using guest_function<T>() template
    • Call unregister_all_imports() during cleanup

Example: Complete Host Application ​

host_impl.cpp - Implement host functions:

cpp
#include "sample.hpp"
#include <iostream>

namespace host {
    void dbglog(cmcpp::string_t msg) {
        std::cout << "[GUEST LOG] " << msg << std::endl;
    }
}

main.cpp - Minimal WAMR integration:

cpp
#include "sample_wamr.hpp"
#include <iostream>
#include <fstream>

int main(int argc, char** argv) {
    // Initialize WAMR
    wasm_runtime_init();
    
    // Load WASM file
    std::ifstream file("sample.wasm", std::ios::binary | std::ios::ate);
    if (!file) {
        std::cerr << "Failed to open WASM file" << std::endl;
        return 1;
    }
    
    size_t size = file.tellg();
    file.seekg(0);
    std::vector<uint8_t> buffer(size);
    file.read(reinterpret_cast<char*>(buffer.data()), size);
    
    // Register all host functions (imports)
    int count = register_all_imports();
    if (count < 0) {
        std::cerr << "Failed to register imports" << std::endl;
        return 1;
    }
    std::cout << "Registered " << count << " host functions" << std::endl;
    
    // Load and instantiate module
    char error_buf[128];
    auto module = wasm_runtime_load(
        buffer.data(), buffer.size(), 
        error_buf, sizeof(error_buf)
    );
    if (!module) {
        std::cerr << "Failed to load module: " << error_buf << std::endl;
        return 1;
    }
    
    auto module_inst = wasm_runtime_instantiate(
        module, 
        wasm_utils::DEFAULT_STACK_SIZE, 
        wasm_utils::DEFAULT_HEAP_SIZE,
        error_buf, sizeof(error_buf)
    );
    if (!module_inst) {
        std::cerr << "Failed to instantiate: " << error_buf << std::endl;
        wasm_runtime_unload(module);
        return 1;
    }
    
    // Create execution environment and context
    auto exec_env = wasm_runtime_create_exec_env(
        module_inst, 
        wasm_utils::DEFAULT_STACK_SIZE
    );
    
    // Create lift/lower context using helper from <wamr.hpp>
    auto ctx = cmcpp::create_lift_lower_context(module_inst, exec_env);
    
    // Call guest functions (exports)
    auto bool_and = guest_function<guest::bool_and_t>(
        module_inst, exec_env, ctx, 
        "bool-and"  // Function name from WIT
    );
    
    std::cout << "Guest bool-and(true, false) = " 
              << std::boolalpha << bool_and(true, false) << std::endl;
    
    // Cleanup
    wasm_runtime_destroy_exec_env(exec_env);
    unregister_all_imports();
    wasm_runtime_deinstantiate(module_inst);
    wasm_runtime_unload(module);
    wasm_runtime_destroy();
    
    return 0;
}

CMakeLists.txt:

cmake
add_executable(my_host
    main.cpp
    host_impl.cpp
    ${CMAKE_CURRENT_BINARY_DIR}/generated/sample_wamr.cpp
)

target_include_directories(my_host PRIVATE
    ${CMAKE_CURRENT_BINARY_DIR}/generated
)

target_link_libraries(my_host PRIVATE
    cmcpp
    vmlib  # WAMR library
)

Benefits of This Structure ​

  1. Clean Separation: Interface declarations separate from implementation
  2. Header-only for Types: Type definitions can be used without linking
  3. Standard C++ Practice: Follows conventional .hpp/.cpp pattern
  4. No ODR Violations: Functions defined in .cpp only, declarations in .hpp
  5. Easy Integration: Single #include "sample_wamr.hpp" gets everything
  6. Documentation: Function declarations in header serve as API documentation
  7. Compile-time Efficiency: No inline bloat in header files

Advanced: Using Only Parts of the Generated Code ​

If you want finer control, you can use the files individually:

Option 1: Just the interfaces (no WAMR)

cpp
#include "sample.hpp"  // Get type definitions only

Option 2: Manual WAMR setup

cpp
#include "sample.hpp"
#include <wamr.hpp>

// Don't include sample_wamr.hpp
// Manually register each interface as needed

Option 3: Full generated helpers (recommended)

cpp
#include "sample_wamr.hpp"  // Everything you need