You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 01:12:35