I have two fairly simple C++ class definitions and their interfaces, uuid.{hpp,cpp}
and uuid_util.{hpp,cpp}
, and I have one more file uuid_bind.cpp
with #include <emscripten/bind.h>
to bind the C++ classes, function and static function definitions to JavaScript.
The two classes are first built as a static library uuid_lib.a
, which is then linked against the latter C++ source file and built with em++ --bind -o uuid_module.js uuid_bind.cpp uuid_lib.a
(which CMake generates) to generate uuid_module.js
and uuid_module.wasm
. Now, what do I do with these?
The documentation on Embind is somewhat sparse, and only says
The resulting quick_example.js file can be loaded as a node module or via a
<script>
tag: ...
I found this Google tutorial on combining Emscripten/Embind and node.js, and I have replicated it as much as possible (excluding the bit on Docker, as my Linux distribution serves Emscripten directly). I have both an index.html
and a package.json
file, and npm test
launches http-server
, which I run from Chrome.
I was under the impression that Emscripten/Embind would simply serve as a translation layer for any bound classes, functions (static or otherwise), variables, and would be able to be called straightforwardly from JavaScript, but it turns out this is not the case. Am I missing out on something here? I'm not very familiar with JS. All I want to do in index.js
is something like:
index.js
import uuid_module from './uuid_lib.js';
console.log(uuid_module.uuid_util.generate_type1_uuid("BLA"));
// ... other calls
and run this with node index.js
which would print a UUID string to the console.
For background, I've provided my CMakeLists.txt
and uuid_bind.cpp
below.
CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
# Set project file
project(
uuid_generator
VERSION 1.0
DESCRIPTION "A UUID generator"
LANGUAGES CXX)
# Export compile commands for clangd
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Add Emscripten CMake toolchain file
if(EMSCRIPTEN)
message("Using Emscripten.")
set(CMAKE_TOOLCHAIN_FILE
${PROJECT_SOURCE_DIR}/deps/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake)
else()
message("Using system compilers.")
endif(EMSCRIPTEN)
# C++20 guaranteed, no extensions eg. GNU/MSVC
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# Strict compilation for MSVC or GCC/Clang
if(MSVC)
add_compile_options(/W4 /WX)
else()
add_compile_options(-Wall -Wextra -pedantic)
endif()
# Add uuid_generator as a shared library
add_library(uuid_lib STATIC src/uuid.cpp src/uuid_util.cpp)
# Set C++ library properties: output name, location, etc
set_target_properties(
uuid_lib
PROPERTIES OUTPUT_NAME uuid_lib
PREFIX ""
ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/dist)
# Set JS binding application properties: set bind, output type, etc. Runs only if Emscripten is enabled.
if(DEFINED ENV{EMSDK})
add_executable(uuid_module src/uuid_bind.cpp)
set_target_properties(
uuid_module
PROPERTIES OUTPUT_NAME uuid_module
SUFFIX ".js"
RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/dist)
target_include_directories(uuid_module
PRIVATE ${PROJECT_SOURCE_DIR}/deps/emsdk/upstream/emscripten/system/include)
target_link_libraries(uuid_module uuid_lib)
target_link_options(
uuid_module
PUBLIC
$<$<CONFIG:DEBUG>:
-v
--bind
-sEXCEPTION_DEBUG=1
-sDEMANGLE_SUPPORT=1
-sDISABLE_EXCEPTION_CATCHING=1
-sWASM_BIGINT=1
-sMODULARIZE=1
-sEXPORT_ES6=1
-sEXPORT_NAME=uuid_module
>)
endif(DEFINED ENV{EMSDK})
uuid_bind.cpp
#include "uuid.hpp"
#include "uuid_util.hpp"
#include <cstdint>
#include <emscripten.h>
#include <emscripten/bind.h>
#include <string>
#include <iostream>
EMSCRIPTEN_BINDINGS(uuid_generator)
{
emscripten::constant("UINT29_MAX", uuid_gen::uuid::UINT29_MAX);
emscripten::constant("ICAO_ADDRESS_MAX", uuid_gen::uuid_util::ICAO_ADDRESS_MAX);
emscripten::constant("MAC_ADDRESS_MAX", uuid_gen::uuid_util::MAC_ADDRESS_MAX);
emscripten::constant("RANDOM_NAMESPACE_MAX", uuid_gen::uuid_util::RANDOM_NAMESPACE_MAX);
emscripten::class_<uuid_gen::uuid>("uuid")
.property("namespace_type", &uuid_gen::uuid::get_namespace_type)
.property("namespace_value", &uuid_gen::uuid::get_namespace_value)
.property("namespace_string", &uuid_gen::uuid::get_namespace_string)
.property("to_string", &uuid_gen::uuid::to_string)
.class_function("uniform_int_distr", &uuid_gen::uuid::uniform_int_dist);
emscripten::class_<uuid_gen::uuid_util>("uuid_util")
.class_function("generate_type1_uuid", &uuid_gen::uuid_util::generate_type1_uuid)
.class_function("generate_type2_uuid", &uuid_gen::uuid_util::generate_type2_uuid)
.class_function("generate_type3_uuid", &uuid_gen::uuid_util::generate_type3_uuid)
.class_function("generate_type4_uuid", &uuid_gen::uuid_util::generate_type4_uuid)
.class_function("generate_type5_uuid_str", emscripten::select_overload<uuid_gen::uuid(const std::string &)>(
&uuid_gen::uuid_util::generate_type5_uuid))
.class_function("generate_type5_uuid_int", emscripten::select_overload<uuid_gen::uuid(std::uint32_t)>(
&uuid_gen::uuid_util::generate_type5_uuid))
.class_function("generate_type6_uuid_str", emscripten::select_overload<uuid_gen::uuid(const std::string &)>(
&uuid_gen::uuid_util::generate_type6_uuid))
.class_function("generate_type6_uuid_int", emscripten::select_overload<uuid_gen::uuid(std::uint64_t)>(
&uuid_gen::uuid_util::generate_type6_uuid))
.class_function("generate_type7_uuid", &uuid_gen::uuid_util::generate_type7_uuid)
.class_function("from_string", &uuid_gen::uuid_util::from_string)
.class_function("is_valid_oper_agency", &uuid_gen::uuid_util::is_valid_oper_agency)
.class_function("is_valid_loc_atm_id", &uuid_gen::uuid_util::is_valid_loc_atm_id)
.class_function("is_valid_mac_address_string", &uuid_gen::uuid_util::is_valid_mac_address_string)
.class_function("namespace_char_encode", &uuid_gen::uuid_util::namespace_char_encode)
.class_function("namespace_char_decode", &uuid_gen::uuid_util::namespace_char_decode)
.class_function("namespace_encode", &uuid_gen::uuid_util::namespace_encode)
.class_function("namespace_decode", &uuid_gen::uuid_util::namespace_decode)
.class_function("mac_address_encode", &uuid_gen::uuid_util::mac_address_encode)
.class_function("mac_address_decode", &uuid_gen::uuid_util::mac_address_decode);
};
Am I missing out on something here?
What you're missing is that in -s MODULARIZE=1
mode, the default export is a factory, not the module object itself.
You'll need to create a module first and then you should be able to access the exposed properties:
import uuidModuleFactory from './uuid_lib.js';
const uuid_module = uuidModuleFactory(/* optional Emscripten config goes here */);
console.log(uuid_module.uuid_util.generate_type1_uuid("BLA"));
// ... other calls