使用Emscripten结合Djinni生成的包装器无法在JS中构建C++类
问题:Djinni生成的Wasm模块无法在JavaScript中调用MathUtils类
环境与代码
目标是从包含加法函数的C++类生成可调用的WebAssembly模块,相关代码如下:
源C++实现(src/MathUtils.cpp)
// ***src/MathUtils.cpp*** #include "MathUtils.hpp" class MathUtils_ : public MathUtils { public: double add_fff64(double a, double b) override { return a + b; } };
Djinni定义文件(djinni/MathUtils.djinni)
// ***djinni/MathUtils.djinni*** MathUtils = interface +c { add_fff64(a: f64, b: f64): f64; }
Djinni生成的C++头文件(src/MathUtils.hpp)
// ***src/MathUtils.hpp*** // AUTOGENERATED FILE - DO NOT MODIFY! // This file was generated by Djinni from MathUtils.djinni #pragma once class MathUtils { public: virtual ~MathUtils() = default; virtual double add_fff64(double a, double b) = 0; };
Djinni生成的Wasm绑定头文件(wasm/MathUtils.hpp)
// ***wasm/MathUtils.hpp*** // AUTOGENERATED FILE - DO NOT MODIFY! // This file was generated by Djinni from MathUtils.djinni #pragma once #include "src/MathUtils.hpp" #include "/Users/sam/snap.djinni/support-lib/wasm/djinni_wasm.hpp" namespace djinni_generated { struct MathUtils : ::djinni::JsInterface<::MathUtils, MathUtils> { using CppType = std::shared_ptr<::MathUtils>; using CppOptType = std::shared_ptr<::MathUtils>; using JsType = em::val; using Boxed = MathUtils; static CppType toCpp(JsType j) { return _fromJs(j); } static JsType fromCppOpt(const CppOptType& c) { return {_toJs(c)}; } static JsType fromCpp(const CppType& c) { ::djinni::checkForNull(c.get(), "MathUtils::fromCpp"); return fromCppOpt(c); } static em::val cppProxyMethods(); static double add_fff64(const CppType& self, double w_a,double w_b); }; } // namespace djinni_generated
Djinni生成的Wasm绑定实现(wasm/MathUtils.cpp)
// ***wasm/MathUtils.cpp*** // AUTOGENERATED FILE - DO NOT MODIFY! // This file was generated by Djinni from MathUtils.djinni #include "wasm/MathUtils.hpp" // my header namespace djinni_generated { em::val MathUtils::cppProxyMethods() { static const em::val methods = em::val::array(std::vector<std::string> { "addFff64", }); return methods; } double MathUtils::add_fff64(const CppType& self, double w_a,double w_b) { try { auto r = self->add_fff64(::djinni::F64::toCpp(w_a), ::djinni::F64::toCpp(w_b)); return ::djinni::F64::fromCpp(r); } catch(const std::exception& e) { return ::djinni::ExceptionHandlingTraits<::djinni::F64>::handleNativeException(e); } } EMSCRIPTEN_BINDINGS(_MathUtils) { em::class_<::MathUtils>("MathUtils") .smart_ptr<std::shared_ptr<::MathUtils>>("MathUtils") .function("nativeDestroy", &MathUtils::nativeDestroy) .function("addFff64", MathUtils::add_fff64) ; } } // namespace djinni_generated
编译命令与问题现象
使用以下Emscripten命令编译:
emcc wasm/MathUtils.cpp src/MathUtils.cpp /Users/sam/snap.djinni/support-lib/wasm/djinni_wasm.cpp -o out/MathUtils.js --bind
编译成功生成MathUtils.js和对应的Wasm文件,但JS文件中完全找不到MathUtils类或addFff64函数的定义,在HTML中调用时会报错。但通过wasm-objdump和wasm2wat分析Wasm文件,能看到相关符号存在:
- 0010490: 7562 6c65 3e00 6164 6446 6666 3634 0048 uble>.addFff64.H
调试建议与解决方案
1. 添加实例工厂函数并导出
MathUtils是抽象基类,JS无法直接实例化,且Emscripten会优化掉未被引用的代码。需要添加一个工厂函数来创建MathUtils_实例,并通过Emscripten绑定导出:
在src/MathUtils.cpp末尾添加:
#include <memory> // 导出工厂函数供JS调用 extern "C" { std::shared_ptr<MathUtils> create_math_utils() { return std::make_shared<MathUtils_>(); } }
然后在单独的绑定文件(比如src/wasm_bindings.cpp)中添加:
#include <emscripten/bind.h> #include "MathUtils.hpp" #include <memory> using namespace emscripten; EMSCRIPTEN_BINDINGS(math_utils_factory) { function("createMathUtils", &create_math_utils); // 确保基类的绑定被正确关联 class_<MathUtils>("MathUtils") .smart_ptr<std::shared_ptr<MathUtils>>("MathUtils") .function("addFff64", &MathUtils::add_fff64); }
编译时将这个新文件加入命令:
emcc wasm/MathUtils.cpp src/MathUtils.cpp src/wasm_bindings.cpp /Users/sam/snap.djinni/support-lib/wasm/djinni_wasm.cpp -o out/MathUtils.js --bind
2. 强制Emscripten保留符号
如果代码未被显式引用,Emscripten的Dead Code Elimination(DCE)会裁剪掉相关内容。可以通过编译参数强制保留需要的符号:
emcc wasm/MathUtils.cpp src/MathUtils.cpp /Users/sam/snap.djinni/support-lib/wasm/djinni_wasm.cpp -o out/MathUtils.js --bind \ -s EXPORTED_FUNCTIONS=['_create_math_utils'] \ -s EXPORTED_RUNTIME_METHODS=['ccall','cwrap'] \ -s NO_ENTRY=1
NO_ENTRY=1:无需main函数作为入口EXPORTED_FUNCTIONS:指定要导出的C函数EXPORTED_RUNTIME_METHODS:导出JS调用Wasm所需的辅助方法
3. 验证Djinni绑定的作用域
Djinni生成的EMSCRIPTEN_BINDINGS位于djinni_generated命名空间内,需确保Emscripten能正确识别这些绑定。可以尝试将绑定代码移到全局命名空间,或者在编译时确保所有绑定代码都被包含。
4. 检查Emscripten版本兼容性
确保使用的Emscripten版本与Djinni的Wasm支持兼容,建议使用最新稳定版Emscripten,避免因版本差异导致绑定失效。
内容的提问来源于stack exchange,提问作者Sam Kouteili
相关产品推荐
相关产品推荐

