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