如何使用embind适配非std::string的自定义String类实现JS与C++类型互转?
如何使用embind适配非std::string的自定义String类实现JS与C++类型互转?
嘿,我完全懂你碰到的这个问题——embind对std::string的自动转换确实很省心,但碰到自定义字符串类就直接罢工了对吧?你的思路其实没毛病,就是通过特化BindingType模板来实现双向转换,但代码里有几个关键的小疏漏导致了那个错误,我来帮你把问题解决掉。
首先,先说说你代码里的核心问题:
- 没有处理JS值到自定义String类的直接转换逻辑,embind不知道怎么把JS字符串转成你的
ContrivedExampleString - 缺少wire类型内存释放的逻辑,会导致内存泄漏
- 没有告诉embind这个类型的基本特征(比如是值类型、支持JS自动转换)
接下来是修正后的完整代码,我会标注关键改动点:
#include <cstdlib> #include <string> #include <cstring> #include <emscripten/bind.h> #include <emscripten/wire.h> namespace SomeLibrary { /** Some library's own String class, oversimplified here for illustration purposes only */ class ContrivedExampleString { public: ContrivedExampleString() : _str(NULL) {/* empty */} ContrivedExampleString(const char * s) : _str(strdup(s)) {/* empty */} ContrivedExampleString(const ContrivedExampleString & rhs) : _str(NULL) {*this = rhs;} ~ContrivedExampleString() {if (_str) free(_str);} ContrivedExampleString & operator = (const ContrivedExampleString & rhs) { if (&rhs != this) { if (_str) free(_str); _str = rhs._str ? strdup(rhs._str) : NULL; } return *this; } const char * CStr() const {return _str ? _str : "";} size_t Length() const {return _str ? strlen(_str) : 0;} private: char * _str; }; /** Contrived example class */ class ContrivedExampleClass { public: ContrivedExampleClass() {/* empty */} ContrivedExampleString ContrivedExampleFunction(const ContrivedExampleString & s) const { return s; } }; }; // end SomeLibrary namespace using namespace emscripten; using namespace SomeLibrary; // 修正后的BindingType特化 template <> struct emscripten::internal::BindingType<ContrivedExampleString> { // 复用embind内置的字符串wire类型,避免自己定义格式出错 typedef emscripten::internal::StringWireType WireType; // 自定义String -> WireType(C++到JS的中间格式) static WireType toWireType(const ContrivedExampleString & v, rvp::default_tag) { return emscripten::internal::allocateStringWire(v.CStr(), v.Length()); } // WireType -> 自定义String(JS到C++的中间格式转换) static ContrivedExampleString fromWireType(WireType v) { ContrivedExampleString result(v->data); // 必须释放wire内存,避免泄漏 emscripten::internal::freeStringWire(v); return result; } // 释放WireType的内存,embind会在合适时机调用 static void freeWireType(WireType v) { emscripten::internal::freeStringWire(v); } // 自定义String -> JS值(直接返回JS字符串,而非自定义对象) static JSValue toJS(ContrivedExampleString v) { return val(std::string(v.CStr())); } // JS值 -> 自定义String(处理JS字符串传入C++的情况) static ContrivedExampleString fromJSVal(JSValue val) { std::string jsStr = val.as<std::string>(); return ContrivedExampleString(jsStr.c_str()); } // 告诉embind这是值类型,不是引用/指针 typedef std::true_type IsValueType; // 允许JS值自动转换为该类型 typedef std::true_type IsConvertibleFromJS; }; EMSCRIPTEN_BINDINGS(ContrivedExample) { // 注册自定义String类为值对象(如果不需要在JS调用它的方法,可以省略,但建议加上) value_object<ContrivedExampleString>("ContrivedExampleString") .function("cstr", &ContrivedExampleString::CStr) .function("length", &ContrivedExampleString::Length); class_<ContrivedExampleClass>("ContrivedExampleClass") .constructor<>() .function("ContrivedExampleFunction", &ContrivedExampleClass::ContrivedExampleFunction); }
关键改动解释
- 复用内置StringWireType:不用自己定义wire格式,和embind处理
std::string的格式保持一致,减少兼容性问题 - 添加
fromJSVal和toJS:这是解决你那个错误的核心——embind终于知道怎么把JS字符串转换成你的自定义String类,以及如何把自定义String转成JS能识别的字符串 - 添加
freeWireType:手动管理wire内存,避免内存泄漏 - 类型特征声明:
IsValueType告诉embind这个类型是值语义,IsConvertibleFromJS允许JS值自动转换为该类型 - 注册
value_object:让embind识别你的自定义String类,如果你需要在JS里调用它的方法(比如cstr()),这一步是必须的
你的编译命令可以保持不变,测试的index.html代码也不用修改,现在运行的话,控制台应该会打印出XXX ABC,完美符合你的预期。
备注:内容来源于stack exchange,提问作者Jeremy Friesner
相关产品推荐
相关产品推荐

