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:
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:
#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 interfacesget_import_registrations()
- Get array of all import interfacesregister_all_imports()
- Register all imports at onceunregister_all_imports()
- Unregister all imports at cleanupwasm_utils
namespace with constants:DEFAULT_STACK_SIZE
DEFAULT_HEAP_SIZE
Header Guard:
#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:
#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
- Example:
- Implementation of all functions declared in
<prefix>_wamr.hpp
:get_import_registrations()
register_all_imports()
unregister_all_imports()
- Constants in
wasm_utils
namespace
Example:
#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:
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:
Host Function Implementations (
host_impl.cpp
)- Implement all functions declared in the
host
namespace - Example:
host::dbglog()
- Implement all functions declared in the
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:
#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:
#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:
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
- Clean Separation: Interface declarations separate from implementation
- Header-only for Types: Type definitions can be used without linking
- Standard C++ Practice: Follows conventional
.hpp
/.cpp
pattern - No ODR Violations: Functions defined in
.cpp
only, declarations in.hpp
- Easy Integration: Single
#include "sample_wamr.hpp"
gets everything - Documentation: Function declarations in header serve as API documentation
- 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)
#include "sample.hpp" // Get type definitions only
Option 2: Manual WAMR setup
#include "sample.hpp"
#include <wamr.hpp>
// Don't include sample_wamr.hpp
// Manually register each interface as needed
Option 3: Full generated helpers (recommended)
#include "sample_wamr.hpp" // Everything you need