尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Emscripten WebIDL Binder 实战指南:用 WebIDL 把 C++ 类绑定给 JavaScript 调用

Emscripten WebIDL Binder 实战指南:用 WebIDL 把 C++ 类绑定给 JavaScript 调用 Emscripten WebIDL Binder 实战指南用 WebIDL 把 C 类绑定给 JavaScript 调用【免费下载链接】emscriptenEmscripten: An LLVM-to-WebAssembly Compiler项目地址: https://gitcode.com/gh_mirrors/em/emscriptenWebIDL Binder 是 Emscripten 提供的一种轻量级 C 绑定方案先用 WebIDL一种专门为打通 C 与 JavaScript 而设计的接口描述语言描述要暴露的 C 类型再由绑定生成器产出 C 与 JavaScript 两套胶水代码最终让编译产物中的 C 类可以像普通 JavaScript 库一样被直接new、调用方法、读写属性。读完本文你将掌握编写 IDL 文件、生成与编译胶水代码、处理指针/引用/值语义、绑定运算符与枚举、在 JavaScript 中继承 C 基类等一系列完整实战技能。为什么需要 WebIDL Binder把 C 代码编译到 WebAssembly 后JavaScript 无法直接调用其中的类与对象——只能通过导出的函数和原始内存地址交互。WebIDL Binder 的作用就是在两者之间架起一座桥以 WebIDL 文件为契约描述 C 接口然后自动生成双向转换代码使 JavaScript 一侧可以像使用原生 JS 对象一样使用 C 对象。它的核心优势在于接口描述语言天然契合WebIDL 本就是 W3C 为胶合 C 与 JavaScript 而设计的低级接口语言作为绑定描述既自然又便于优化轻量直接绑定的是 C 类型的子集这些子集覆盖了绝大多数使用场景有成熟实战验证Box2D、Bullet 物理引擎ammo.js等知名项目都是通过该 Binder 移植到 Web 的。Emscripten 官方同时提供另一套功能更丰富的绑定方案 Embind当 WebIDL Binder 的表达能力不足以满足需求时可以转向 Embind 对比选用。三步绑定流程概览使用 WebIDL Binder 绑定 C 类是一个固定三步骤过程编写 WebIDL 文件描述要绑定的 C 接口类、函数、属性、枚举等生成胶水代码用绑定生成器tools/webidl_binder.py根据 IDL 文件产出 C 胶水文件如glue.cpp与 JavaScript 胶水文件如glue.js编译工程把 C 胶水文件与业务 C 代码一起交给 emcc 编译并把 JavaScript 胶水文件通过--post-js追加到输出中。下面逐一展开。定义 WebIDL 文件第一步是创建描述待绑定 C 类型的 WebIDL 文件。它会在一定程度上重复 C 头文件中的信息但采用专门为易解析、易表达代码项而设计的格式。以如下两个 C 类为例class Foo { public: int getVal(); void setVal(int v); }; class Bar { public: Bar(long val); void doSomething(); };对应的 IDL 文件如下interface Foo { void Foo(); long getVal(); void setVal(long v); }; interface Bar { void Bar(long val); void doSomething(); };IDL 定义与 C 的对应关系相当直观需要注意两点构造器声明IDL 类定义中必须包含一个返回void、与 interface 同名的构造器方法如上例Foo中的void Foo();即使 C 使用的是默认构造器也必须在 IDL 中显式声明——它负责在 JavaScript 中创建对象的能力类型名映射WebIDL 的类型名与 C 并不一一对应例如上例 C 的int在 IDL 中写作long。完整的类型映射表见下文WebIDL 类型映射小节。另外C 的struct与类一样同样使用interface关键字在 IDL 中声明struct与class在 C 中本就只是默认访问权限不同。在仓库的 test/webidl/test.idl 中可以看到一个覆盖极广的完整 IDL 实例其中用到了interface、attribute、readonly attribute、enum、implements继承、static方法、optional参数、数组类型以及[Prefix]、[Const]、[Ref]、[Value]、[Operator]、[JSImplementation]、[NoDelete]、[BindTo]、[BoundsChecked]等大量扩展属性是学习 IDL 写法的绝佳模板。生成绑定胶水代码绑定生成器tools/webidl_binder.py接收一个 WebIDL 文件名和一个输出文件名产出对应的 C 与 JavaScript 胶水代码文件。例如为 IDL 文件my_classes.idl生成glue.cpp与glue.jstools/webidl_binder my_classes.idl glue生成器的完整调用形式见 tools/webidl_binder.py 顶部的 argparse 定义为tools/webidl_binder [--wasm64] infile outfileinfileWebIDL 输入文件路径outfile输出文件基名生成器会据此创建outfile.cpp与outfile.js--wasm64为 wasm64 目标生成绑定代码。生成器还会读取环境变量IDL_CHECKS来控制胶水方法中的参数类型检查强度见 tools/webidl_binder.py 头部注释IDL_CHECKSFAST跳过大部分参数类型检查比默认模式快约 3 倍IDL_CHECKSALL进行全面的参数类型检查非法数字、非法指针、非法字符串等比默认模式慢约 5 倍其他任何取值包括默认不设置进入 legacy 兼容模式。官方测试 test/test_core.py 的test_webidlL7910-L7954正是通过env_modify({IDL_CHECKS: mode})分别以DEFAULT、ALL、FAST三种模式驱动生成器验证三种检查强度下的行为其中ALL模式还叠加了-sASSERTIONS编译选项。编译工程使用胶水代码要在工程中使用生成的glue.cpp与glue.js追加 JS 胶水在最终emcc命令中加入--post-js glue.js。--post-js选项会把胶水代码追加到编译输出的末尾该选项的完整说明见 emcc 文档。编写 C 包装文件创建类似my_glue_wrapper.cpp的文件在其中#include被绑定类的头文件以及glue.cpp#include ... // 此处为被绑定类的头文件 #include glue.cpp说明生成器产出的 C 胶水代码并不包含被绑定类的头文件——因为这些头文件不在 WebIDL 文件中。上面的包装文件正是把这些头文件提供给胶水代码的途径。另一种做法是把头文件直接#include到glue.cpp顶部但这样每次重新编译 IDL 文件时头文件包含都会被覆盖所以不推荐。仓库测试中的做法可见 test/webidl/test.cpp先#include test.h被绑定类头文件再#include glue.cpp与上述模式完全一致。加入编译命令把my_glue_wrapper.cpp添加到最终 emcc 命令中。最终 emcc 命令同时包含 C 与 JavaScript 两套协同工作的胶水代码emcc my_classes.cpp my_glue_wrapper.cpp --post-js glue.js -o output.js输出产物即包含在 JavaScript 中使用 C 类所需的全部内容。值得一提的是生成器在 C 胶水代码中通过EMSCRIPTEN_KEEPALIVE强制导出了两个自带的分配器webidl_malloc/webidl_free见 tools/webidl_binder.py 中extern C段落用于 JS 侧大字符串/大数组的临时 C 存储ensureCache因此使用者无需额外把malloc/free加入-sEXPORTED_FUNCTIONS。若业务代码仍需直接使用malloc/free可仿照测试在编译命令中加入-sEXPORTED_FUNCTIONS_malloc,_free。模块化输出MODULARIZE 与 EXPORT_NAME用 WebIDL Binder 时通常你正在做的是一个库。此时非常适合启用MODULARIZE选项它会把整个 JavaScript 输出包裹进一个函数并返回一个 Promise该 Promise 解析为初始化完成的 Module 实例var instance; Module().then(module { instance module; });Promise 在可以安全运行编译代码时被解析——即模块下载并实例化完成之后。它被解析的时机与onRuntimeInitialized回调触发时机相同因此使用MODULARIZE时就不需要再使用onRuntimeInitialized。还可以用EXPORT_NAME选项把Module改成其他名字。对于库来说这是良好实践既避免在全局作用域中塞入无关内容也能在需要时创建多个互不冲突的实例。在 JavaScript 中使用 C 类绑定完成后C 对象在 JavaScript 中就可以像普通 JS 对象一样创建和使用。接续上面的例子var f new Module.Foo(); f.setVal(200); alert(f.getVal()); var b new Module.Bar(123); b.doSomething();重要请始终通过Module对象访问这些类如上例所示。虽然默认情况下这些对象也存在于全局命名空间中但在某些场景下它们不会例如用 Closure Compiler 压缩代码或把编译代码包进函数以避免污染全局命名空间时。你也可以把模块赋给任意名字的变量var MyModuleName Module;。重要这些代码只能在可以安全调用编译代码的时机之后使用详见 FAQ 中关于 safe-to-call compiled functions 的说明即模块初始化完成之后。垃圾回收与手动销毁JavaScript 会在没有任何引用时自动垃圾回收被包装的 C 对象。如果 C 对象不需要特殊的清理即没有析构函数则无需任何额外操作。如果 C 对象确实需要清理必须显式调用Module.destroy(obj)来触发其析构函数然后丢弃对该对象的所有引用以便其被垃圾回收。例如Bar若分配了需要清理的内存var b new Module.Bar(123); b.doSomething(); Module.destroy(b); // 如果 C 对象需要清理注意在 JavaScript 中创建 C 对象时其构造器是被透明调用的。但 JavaScript 无法预知对象即将被垃圾回收因此胶水代码无法自动调用析构函数。你通常需要自己销毁所创建的对象具体取决于被移植库的要求。从源码层面看tools/webidl_binder.py 中的destroy实现destroy会调用包装对象的__destroy__方法并将其从缓存中移除以便对象能被 GC 且其上附加的引用被释放。相应地只有没有[NoDelete]标记的接口才会生成__destroy__方法。属性Attributes对象属性在 IDL 中用attribute关键字定义。在 JavaScript 中既可以通过get_foo()/set_foo()访问器方法访问也可以直接作为对象属性读写// C int attr;// WebIDL attribute long attr;// JavaScript var f new Module.Foo(); f.attr 7; // 等价于 f.set_attr(7); console.log(f.attr); console.log(f.get_attr());只读属性const数据成员的写法见下文Const小节。数组属性在 IDL 中写作attribute long[] arr;形式。结合源码tools/webidl_binder.py 属性处理分支可以看到数组属性生成的 getter 签名是(index)setter 签名是(index, value)即在 JavaScript 中以get_int_array(0)、set_int_array(0, 42)的方式按索引访问。测试 test/webidl/post.js 中大量使用了这一形式包括get_struct_array(0).get_attr1()这种返回结构体包装对象再取属性的链式访问。若属性标记了[BoundsChecked]setter 还会执行越界检查array_bounds_check越界访问将抛出异常。指针、引用与值类型Ref 和 ValueC 的参数和返回类型可以是指针、引用或值类型栈上分配。IDL 文件用不同的修饰符区分这三种情况。未修饰 指针IDL 中自定义类型未加修饰的参数与返回值默认视为 C 中的指针// C MyClass* process(MyClass* input);// WebIDL MyClass process(MyClass input);这一默认规则不适用于void、int、bool、DOMString等基础类型。[Ref] 引用引用类型需要用[Ref]修饰// C MyClass process(MyClass input);// WebIDL [Ref] MyClass process([Ref] MyClass input);注意如果引用上漏写[Ref]生成的胶水 C 将无法编译它试图把以为是指针的引用转换成对象时必然失败。源码中生成调用参数时会依据Ref属性决定是否在实参前加解引用符*见 tools/webidl_binder.py 中call_args的拼接逻辑漏写会导致类型不匹配。[Value] 按值返回对象如果 C 返回的是对象而非引用或指针返回类型应使用[Value]修饰。这会分配一个静态单例的该类实例并返回它。你应当立即使用它并在使用后丢弃对它的所有引用。// C MyClass process(MyClass input);// WebIDL [Value] MyClass process([Ref] MyClass input);使用[Value]返回的对象要求对应类有可用的零参构造器测试 test/webidl/test.idl 中[Value] RefUser getCopy();旁注释 must have zero-arg constructor 即此约束。常量ConstC 中使用const的参数或返回类型在 IDL 中用[Const]指定。例如下面的片段展示了一个返回常量指针对象的函数的 C 与 IDL 写法// C const myObject* getAsConst();// WebIDL [Const] myObject getAsConst();而对应 const数据成员的属性必须用readonly关键字而非[Const]指定例如// C const int numericalConstant;// WebIDL readonly attribute long numericalConstant;这会为绑定生成get_numericalConstant()方法但不生成对应的 setter。该属性在 JavaScript 中也被定义为只读尝试设置它不会影响实际值且在严格模式下会抛出错误。测试 test/webidl/post.js 中sme.immutableAttr 1被 try/catch 包裹、随后再次读取值仍不变正是对该行为的验证。技巧返回类型可以同时带多个修饰符。例如返回常量引用的方法在 IDL 中标记为[Ref, Const]仓库测试 test/webidl/test.idl 中的[Operator] void incInPlace([Const, Ref] Inner i);就是[Const, Ref]组合使用的实例。不可删除的类NoDelete如果某个类无法被删除例如析构函数是私有的就在 IDL 文件中指定[NoDelete][NoDelete] interface Foo { ... };标记[NoDelete]后绑定不会为该类生成__destroy__方法调用Module.destroy()会抛出错误源码destroy中if (!obj[__destroy__]) throw ...。仓库测试 test/webidl/test.idl 中的ISmallObject、IObjectProvider即是[NoDelete]接口对应地它们的 JS 实现JSSmallObject、JSObjectProvider也用[JSImplementation]关联构成一套抽象接口 JS 实现的完整用例。内部类与命名空间内的类Prefix声明在命名空间或另一个类内部的 C 类必须在 IDL 文件中使用Prefix关键字指明作用域。此后C 胶水代码中引用该类时都会带上该前缀。例如下面的 IDL 定义确保Inner类在 C 中被引用为MyNameSpace::Inner[PrefixMyNameSpace::] interface Inner { .. };仓库测试 test/webidl/test.idl 中[PrefixSpace::] interface Inner配合 test/webidl/test.h 中namespace Space { struct Inner {...} }使用而 test/webidl/post.js 里new TheModule.Inner()直接通过模块访问——即命名空间内的类在 JavaScript 侧依然以顶层类名出现。运算符绑定Operator可以用[Operator]绑定 C 运算符[Operator] TYPE1 add(TYPE2 x);注意事项运算符的方法名可以是任意名字add只是示例绑定时以[Operator]指定的符号为准目前支持以下二元运算符、-、*、/、%、^、、|、、、、、-、*、/、%、^、、|、、、、、、!、、、、、||以及数组下标运算符[]。仓库测试对运算符绑定有系统覆盖test/webidl/test.idl 的Inner接口[Operator[]] long getAsArray(long x)绑定下标运算、[Operator] void incInPlace(...)绑定复合赋值、[Operator, Value] Inner add(...)绑定加法且按值返回、[Operator*] long mul2(long x)绑定乘法test/webidl/post.js 中相应调用new TheModule.Inner().getAsArray(12)、new TheModule.Inner(1).add(new TheModule.Inner(2)).get_value()等即验证这些绑定正确工作。枚举enums枚举在 C 与 IDL 中的声明非常相似// C enum AnEnum { enum_value1, enum_value2 }; // WebIDL enum AnEnum { enum_value1, enum_value2 };声明在命名空间内的枚举语法稍复杂// C namespace EnumNamespace { enum EnumInNamespace { e_namespace_val 78 }; }; // WebIDL enum EnumNamespace_EnumInNamespace { EnumNamespace::e_namespace_val };当枚举定义在类内部时枚举与类接口的 IDL 定义是分开的// C class EnumClass { public: enum EnumWithinClass { e_val 34 }; EnumWithinClass GetEnum() { return e_val; } EnumNamespace::EnumInNamespace GetEnumFromNameSpace() { return EnumNamespace::e_namespace_val; } };// WebIDL enum EnumClass_EnumWithinClass { EnumClass::e_val }; interface EnumClass { void EnumClass(); EnumClass_EnumWithinClass GetEnum(); EnumNamespace_EnumInNamespace GetEnumFromNameSpace(); };注意 IDL 枚举值字符串如EnumNamespace::e_namespace_val、EnumClass::e_val携带完整的 C 限定名这正是生成器定位真实 C 枚举值的关键。从 test/webidl/post.js 可以看到枚举在 JavaScript 侧的使用方式顶层枚举值直接挂在模块上TheModule.enum_value1类内枚举通过类访问TheModule.EnumClass.e_val同时类方法GetEnum()返回对应值命名空间内枚举也挂在顶层模块上TheModule.e_namespace_val。在 JavaScript 中子类化 C 基类JSImplementationWebIDL Binder 允许在 JavaScript 中实现 C 基类的子类。下面 IDL 片段中JSImplementationBase表示关联的接口ImplJS将是 C 类Base的一个 JavaScript 实现[JSImplementationBase] interface ImplJS { void ImplJS(); void virtualFunc(); void virtualFunc2(); };运行绑定生成器并编译后就可以在 JavaScript 中实现该接口var c new ImplJS(); c.virtualFunc function() { .. };当 C 代码持有指向Base实例的指针并调用virtualFunc()时该调用会到达上面定义的 JavaScript 代码。注意事项必须实现JSImplementation类ImplJS的 IDL 中列出的所有方法否则编译会报错源码 tools/webidl_binder.py 中会生成if (!self.hasOwnProperty(...)) throw a JSImplementation must implement all functions, you forgot ...的运行时检查同时还需要在 IDL 文件中提供Base类自身的接口定义。仓库测试 test/webidl/test.idl 中[JSImplementationChild2] interface Child2JS与[JSImplementationVirtualBase] interface ConcreteJS是完整范例前者在 JS 中替换虚函数实现test/webidl/post.js 中c3.virtualFunc function() {...}后调用runVirtualFunc(c3)验证 C 侧虚函数分发能到达 JS 实现后者验证抽象基类纯虚函数的正确处理——特别是const虚函数constFunc()在 JS 实现中必须以[Const] void constFunc();声明否则编译会失败。函数重载overloads与 BindToC 允许函数重载——多个同名成员函数仅参数不同。默认情况下WebIDL Binder 允许绑定仅在参数个数上不同的重载函数// C class OverloadTest { public: void test(int arg1, int arg2) { ... } void test(int arg) { ... } }; // WebIDL interface OverloadTest { void OverloadTest(); void test(long arg1, long arg2); void test(long arg); };如果重载函数在其他方面不同例如参数类型不同可以使用[BindTo]属性告诉工具要绑定即调用哪个函数名// C class BindToTest { public: void test(const char* arg) { ... } void test(int arg) { ... } }; // WebIDL interface BindToTest { void BindToTest(); [BindTotest] void testString([Const] DOMString arg); [BindTotest] void testInt(long arg); };此例中C 函数test(const char*)在 JavaScript 中命名为testStringtest(int)命名为testInt。注意[BindTo]也可以单纯用于重命名函数例如把MyFunctionName重命名为myFunctionName。从源码看tools/webidl_binder.py 中render_function的调用重载解析的核心约束有两个一是同一函数的所有重载必须具有相同的返回类型二是按参数个数而不是类型组合区分签名optional参数会被折叠进更少参数的签名集合因此无法仅靠类型不同来区分两个长度相同的签名。测试 test/webidl/test.idl 中Child1的getValSqr(optional long more)与getValTimes(optional long times1)含默认参数以及 test/webidl/post.js 中new TheModule.Child1(8)重载构造器按参数个数自动选择与bindTo.testString(hello)/bindTo.testInt(10)都是这些规则的直接验证。指针与比较操作所有绑定函数都期望接收包装对象内部含一个原始指针而非裸指针。正常情况下你不应该直接处理裸指针它本质上只是内存地址/整数。如果确实需要编译产物中提供以下函数wrapPointer(ptr, Class)—— 给定裸指针整数返回包装对象。注意如果不传Class将默认假定为根类——这很可能不是你想要的getPointer(object)—— 返回裸指针。castObject(object, Class)—— 返回同一指针、但按另一个类包装的对象。compare(object1, object2)—— 比较两个对象的指针。注意对于某个指针 某个类的组合始终只有一个包装对象。这允许你在该对象上附加数据并在别处以普通 JavaScript 语法使用object.attribute someData等。compare()应取代直接的指针比较因为当一个类是另一个类的子类时可能存在拥有同一指针的不同包装对象。这些工具函数全部实现于 tools/webidl_binder.py 的 JS 胶水公共段wrapPointer基于每个类的__cache__缓存实现一指针一包装ret cache[ptr]; if (ret) return ret; ... cache[ptr] retcastObject即wrapPointer(obj.ptr, __class__)compare即obj1.ptr obj2.ptr另有文档未单列但同样暴露的getClass(obj)返回对象所属类。此外模块上还暴露了Module.NULL包装了指针 0 的全局单例见下文。NULL 指针的处理所有返回指针、引用或对象的绑定函数都会返回包装后的指针。原因在于始终返回包装对象就可以把输出直接传给另一个绑定函数而无需该函数检查参数类型。一个容易困惑的场景是返回NULL指针时使用绑定时返回的将是NULL一个包装指针为 0 的全局单例而不是 JavaScript 内置的null或数字0。这一点在源码中对应Module[NULL] wrapPointer(0);的定义。void* 类型void*类型通过VoidPtr类型在 IDL 文件中使用也可以使用any类型。两者的区别在于VoidPtr表现得像指针类型——你得到的是一个包装对象而any表现得像 32 位整数这正是 Emscripten 编译代码中裸指针的形态。源码中interface VoidPtr {};被预置进解析器输入见 tools/webidl_binder.py 中p.parse(interface VoidPtr {...})使 IDL 可以直接引用该类型。仓库测试 test/webidl/test.idl 中VoidPtr voidStar(VoidPtr something);与any GetVoidPointer();/void SetVoidPointer(any ptr);两种风格并存test/webidl/post.js 中sme.voidStar(sme)传包装对象与voidPointerUser.SetVoidPointer(3)传整数分别验证了两者的行为。WebIDL 类型映射WebIDL 中的类型名与 C 并不相同。下表是常见类型的映射CIDLboolbooleanfloatfloatdoubledoublecharbytechar*DOMString表示 JavaScript 字符串unsigned charoctetintlonglonglongunsigned shortunsigned shortunsigned longunsigned longlong longlong longvoidvoidvoid*any或VoidPtr见上文void* 类型说明WebIDL 类型的完整规范见 W3C 的 WebIDL 规范文档。上述byte/octet/unsigned short/unsigned long等映射在仓库测试 test/webidl/test.idl 的TypeTestClass接口中均有逐一验证ReturnCharMethod/AcceptCharMethod、ReturnUnsignedCharMethod等对应 test/webidl/test.h 中接受/返回char、unsigned char、unsigned short、unsigned long的 C 方法。另外值得补充的是WebIDL Binder 还支持DOMString[]、byte[]、octet[]等数组形态的参数DOMString[]会从 JS 字符串数组转换为 C 字符串数组byte[]/octet[]则接受包含字节数据的 TypedArray 或二进制缓冲区test/webidl/post.js 中byteArrayTest(bufferAddr)展示了把_malloc出的地址直接传给byte[]参数、domStringTest(...)展示字符串直传两种用法。测试与示例代码完整的可运行示例位于仓库测试套件中测试驱动test/test_core.py 中的test_webidlL7910-L7954——先调用WEBIDL_BINDER生成胶水再以--post-jsglue.js编译并支持DEFAULT/ALL/FAST三种IDL_CHECKS模式与内存增长ALLOW_MEMORY_GROWTH组合。它同时验证了闭包压缩下必须配合MODULARIZEEXPORT_NAMEcreateModule使用、wasm64 需传--wasm64等边界情况测试素材IDL 定义见 test/webidl/test.idlC 实现见 test/webidl/test.cpp含#include glue.cpp的标准用法被绑定类头文件见 test/webidl/test.hJavaScript 侧完整调用脚本见 test/webidl/post.js覆盖构造器、继承、虚函数替换、属性、数组、枚举、运算符、类型检查失败路径、百万次创建/销毁压力循环、堆重分配后数组复制正确性等远超本文列举的场景。测试套件中的代码保证可用且覆盖面比本文更广。另一个可参考的实战案例是 ammo.js——它正是使用 WebIDL Binder 把 Bullet 物理引擎移植到 Web 的其使用模式作为独立库、MODULARIZE模块化输出、通过Module.Ammo命名空间访问与本指南描述的工作流一致。常见问题与使用要点小结构造器必须声明IDL 中每个可创建的 interface 都要有同名void方法即使 C 侧用默认构造器类型名用 IDL 词汇int写long、char*写DOMString、bool写boolean数组用[]后缀语义修饰符默认指针、[Ref]引用、[Value]按值返回、[Const]常量、[NoDelete]禁删、[Prefix]命名空间限定、[BindTo]重命名/重载区分、[JSImplementation]JS 继承、[Operator]运算符、[BoundsChecked]数组越界检查编译组合--post-js glue.js 包装文件#include头文件与glue.cpp做库时启用MODULARIZE并用EXPORT_NAME重命名导出对象生命周期需要析构的对象必须显式Module.destroy(obj)[NoDelete]类不可销毁检查模式通过环境变量IDL_CHECKSDEFAULT/ALL/FAST在性能与参数校验强度之间取舍。【免费下载链接】emscriptenEmscripten: An LLVM-to-WebAssembly Compiler项目地址: https://gitcode.com/gh_mirrors/em/emscripten创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表