If anyone is lurking, I now have this slimmed down to the following pattern:
auto call_and = attach<func_t<bool_t(bool_t, bool_t)>>(module_inst, exec_env, liftLowerContext, "example:sample/booleans#and");
std::cout << "call_and(false, false): " << call_and(false, false) << std::endl;
std::cout << "call_and(false, true): " << call_and(false, true) << std::endl;
std::cout << "call_and(true, false): " << call_and(true, false) << std::endl;
std::cout << "call_and(true, true): " << call_and(true, true) << std::endl;
auto call_add = attach<func_t<float64_t(float64_t, float64_t)>>(module_inst, exec_env, liftLowerContext, "example:sample/floats#add");
std::cout << "call_add(3.1, 0.2): " << call_add(3.1, 0.2) << std::endl;
std::cout << "call_add(1.5, 2.5): " << call_add(1.5, 2.5) << std::endl;
auto call_reverse = attach<func_t<string_t(string_t)>>(module_inst, exec_env, liftLowerContext, "example:sample/strings#reverse");
auto call_reverse_result = call_reverse("Hello World!");
std::cout << "call_reverse(\"Hello World!\"): " << call_reverse_result << std::endl;
std::cout << "call_reverse(call_reverse(\"Hello World!\")): " << call_reverse(call_reverse_result) << std::endl;
auto call_lots = attach<func_t<uint32_t(
string_t, string_t, string_t, string_t, string_t, string_t, string_t, string_t,
string_t, string_t, string_t, string_t, string_t, string_t, string_t, string_t, string_t)>>(module_inst, exec_env, liftLowerContext, "example:sample/strings#lots");
// Example call with 33 string_t arguments
auto call_lots_result = call_lots(
"p1", "p2", "p3", "p4", "p5", "p6", "p7", "p8",
"p9", "p10", "p11", "p12", "p13", "p14", "p15", "p16", "p17");
std::cout << "call_lots result: " << call_lots_result << std::endl;
using reverse_tuple_func_t = func_t<tuple_t<string_t, bool_t>(tuple_t<bool_t, string_t>)>;
auto call_reverse_tuple = attach<reverse_tuple_func_t>(module_inst, exec_env, liftLowerContext, "example:sample/tuples#reverse");
auto call_reverse_tuple_result = call_reverse_tuple({false, "Hello World!"});
std::cout << "call_reverse_tuple({false, \"Hello World!\"}): " << std::get<0>(call_reverse_tuple_result) << ", " << std::get<1>(call_reverse_tuple_result) << std::endl;
auto call_list_filter = attach<func_t<list_t<string_t>(list_t<variant_t<bool_t, string_t>>)>>(module_inst, exec_env, liftLowerContext, "example:sample/lists#filter-bool");
auto call_list_filter_result = call_list_filter({{false}, {"Hello World!"}, {"Another String"}, {true}});
std::cout << "call_list_filter result: " << call_list_filter_result.size() << std::endl;
Next step is to remove the need for the func_t
auto call_and = attach<bool_t(bool_t, bool_t)>(module_inst, exec_env, liftLowerContext, "example:sample/booleans#and");
std::cout << "call_and(false, false): " << call_and(false, false) << std::endl;
std::cout << "call_and(false, true): " << call_and(false, true) << std::endl;
std::cout << "call_and(true, false): " << call_and(true, false) << std::endl;
std::cout << "call_and(true, true): " << call_and(true, true) << std::endl;
auto call_add = attach<float64_t(float64_t, float64_t)>(module_inst, exec_env, liftLowerContext, "example:sample/floats#add");
std::cout << "call_add(3.1, 0.2): " << call_add(3.1, 0.2) << std::endl;
std::cout << "call_add(1.5, 2.5): " << call_add(1.5, 2.5) << std::endl;
auto call_reverse = attach<string_t(string_t)>(module_inst, exec_env, liftLowerContext, "example:sample/strings#reverse");
auto call_reverse_result = call_reverse("Hello World!");
std::cout << "call_reverse(\"Hello World!\"): " << call_reverse_result << std::endl;
std::cout << "call_reverse(call_reverse(\"Hello World!\")): " << call_reverse(call_reverse_result) << std::endl;
auto call_lots = attach<uint32_t(
string_t, string_t, string_t, string_t, string_t, string_t, string_t, string_t,
string_t, string_t, string_t, string_t, string_t, string_t, string_t, string_t, string_t)>(module_inst, exec_env, liftLowerContext, "example:sample/strings#lots");
// Example call with 33 string_t arguments
auto call_lots_result = call_lots(
"p1", "p2", "p3", "p4", "p5", "p6", "p7", "p8",
"p9", "p10", "p11", "p12", "p13", "p14", "p15", "p16", "p17");
std::cout << "call_lots result: " << call_lots_result << std::endl;
using reverse_tuple_func_t = tuple_t<string_t, bool_t>(tuple_t<bool_t, string_t>);
auto call_reverse_tuple = attach<reverse_tuple_func_t>(module_inst, exec_env, liftLowerContext, "example:sample/tuples#reverse");
auto call_reverse_tuple_result = call_reverse_tuple({false, "Hello World!"});
std::cout << "call_reverse_tuple({false, \"Hello World!\"}): " << std::get<0>(call_reverse_tuple_result) << ", " << std::get<1>(call_reverse_tuple_result) << std::endl;
auto call_list_filter = attach<list_t<string_t>(list_t<variant_t<bool_t, string_t>>)>(module_inst, exec_env, liftLowerContext, "example:sample/lists#filter-bool");
auto call_list_filter_result = call_list_filter({{false}, {"Hello World!"}, {"Another String"}, {true}});
std::cout << "call_list_filter result: " << call_list_filter_result.size() << std::endl;
...First draft of host functions, now working:
string_t reverse(const string_t &a)
{
std::string result = a;
std::transform(result.begin(), result.end(), result.begin(), ::toupper);
return result;
}
uint32_t lots(const string_t &p1, const string_t &p2, const string_t &p3, const string_t &p4, const string_t &p5, const string_t &p6, const string_t &p7, const string_t &p8, const string_t &p9, const string_t &p10, const string_t &p11, const string_t &p12, const string_t &p13, const string_t &p14, const string_t &p15, const string_t &p16, const string_t &p17)
{
return p1.length() + p2.length() + p3.length() + p4.length() + p5.length() + p6.length() + p7.length() + p8.length() + p9.length() + p10.length() + p11.length() + p12.length() + p13.length() + p14.length() + p15.length() + p16.length() + p17.length();
}
NativeSymbol strings_symbol[] = {
host_function("reverse", reverse),
host_function("lots", lots),
};
void_t log_u32(uint32_t a, string_t b)
{
std::cout << "wasm-log: " << b << a << std::endl;
}
NativeSymbol logging_symbol[] = {
host_function("log-u32", log_u32),
};
...
success = wasm_runtime_register_natives_raw("example:sample/strings", strings_symbol, sizeof(strings_symbol) / sizeof(NativeSymbol));
success = wasm_runtime_register_natives_raw("example:sample/logging", logging_symbol, sizeof(logging_symbol) / sizeof(NativeSymbol));
Getting close to a complete implementation! See https://github.com/GordonSmith/component-model-cpp/tree/WIT-CODEGEN/samples for details, now includes generated stubs from wit definitions...
v0.2.2 successfully generates host stubs + wamr bindings for the entire wit-bindgen test suite.
...and they compile cleanly on windows and nix. (would need host implementations of wasi to actually test them, but they compiled!!!)
$ ./wit-codegen.exe --help
wit-codegen - WebAssembly Interface Types (WIT) Code Generator
USAGE:
C:\Users\gordon\Downloads\cmcpp-0.1.0-win64\cmcpp-0.1.0-win64\bin\wit-codegen.exe <wit-file-or-dir> [output-prefix]
C:\Users\gordon\Downloads\cmcpp-0.1.0-win64\cmcpp-0.1.0-win64\bin\wit-codegen.exe --help
C:\Users\gordon\Downloads\cmcpp-0.1.0-win64\cmcpp-0.1.0-win64\bin\wit-codegen.exe -h
ARGUMENTS:
<wit-file-or-dir> Path to WIT file or directory with WIT package
[output-prefix] Optional output file prefix (default: derived from package name)
OPTIONS:
-h, --help Show this help message and exit
DESCRIPTION:
Generates C++ host function bindings from WebAssembly Interface Types (WIT)
files. The tool parses WIT syntax and generates type-safe C++ code for
interfacing with WebAssembly components.
Supports multi-file WIT packages with deps/ folder dependencies.
GENERATED FILES:
<prefix>.hpp - C++ header with type definitions and declarations
<prefix>_wamr.hpp - WAMR runtime integration header
<prefix>_wamr.cpp - WAMR binding implementation with NativeSymbol arrays
FEATURES:
- Supports all Component Model types (primitives, strings, lists, records,
variants, enums, options, results, flags)
- Generates bidirectional bindings (imports and exports)
- Type-safe C++ wrappers using cmcpp canonical ABI
- WAMR native function registration helpers
- Automatic memory management for complex types
- Multi-file package support with deps/ folder resolution
EXAMPLES:
# Generate bindings from single WIT file
C:\Users\gordon\Downloads\cmcpp-0.1.0-win64\cmcpp-0.1.0-win64\bin\wit-codegen.exe example.wit
# Generate bindings from WIT package directory
C:\Users\gordon\Downloads\cmcpp-0.1.0-win64\cmcpp-0.1.0-win64\bin\wit-codegen.exe wit/
# Generate bindings with custom prefix
C:\Users\gordon\Downloads\cmcpp-0.1.0-win64\cmcpp-0.1.0-win64\bin\wit-codegen.exe example.wit my_bindings
# Show help message
C:\Users\gordon\Downloads\cmcpp-0.1.0-win64\cmcpp-0.1.0-win64\bin\wit-codegen.exe --help
For more information, see: https://github.com/GordonSmith/component-model-cpp
https://github.com/GordonSmith/component-model-cpp/releases
Last updated: Dec 06 2025 at 07:03 UTC