当前位置: 首页 > 后端技术 > Node.js

Emscripten教程之C++与JavaScript绑定(三)

时间:2023-04-03 22:55:58 Node.js

译文:云凰北青Embind是用来将C++的函数和类绑定到JavaScript上的,这样编译后的代码就可以非常自然的在js中使用了。Embind还支持从C++调用JavaScript类。Embind支持绑定到大多数C++结构,包括C++11和C++14中引入的结构。它只有一个明显的限制,即目前不支持具有复杂生命周期语义的原始指针。本文介绍如何使用EMSCRIPTEN_BINDINGS()块为函数、类、值类型、指针(原始指针和智能指针)、枚举和常量创建绑定,以及如何为可以用JavaScript编写的抽象类创建绑定被重写在.它还简要介绍了如何为传递给JavaScript的C++对象句柄管理内存。注意:Embind的灵感来自Boost.Python,它们使用非常相似的方法来定义绑定。一个简单示例以下代码使用EMSCRIPTEN_BINDINGS()将C++lerp()函数公开给JavaScript。//quick_example.cpp#include使用命名空间emscripten;floatlerp(floata,floatb,floatt){return(1-t)*a+t*b;}EMSCRIPTEN_BINDINGS(my_module){函数("lerp",&lerp);}为了使用embind编译上面的例子,请调用emcc的bing选项:emcc--bind-oquick_example.jsquick_example.cpp生成的quick_example.js文件可以作为node模块加载,或者用最初加载quick_example.js文件时,将运行EMSCRIPTEN_BINDINGS()中的代码。通过Embind公开的所有符号都在Module对象中可用。Class将类暴露给JavaScript需要更复杂的绑定语句,例如:classMyClass{public:MyClass(intx,std::stringy):x(x),y(y){}voidincrementX(){++X;}intgetX()const{返回x;}voidsetX(intx_){x=x_;}staticstd::stringgetStringFromInstance(constMyClass&instance){returninstance.y;}私有:intx;标准数据::字符串y;};//绑定代码EMSCRIPTEN_BINDINGS(my_class_example){class_("MyClass").constructor().function("incrementX",&MyClass::incrementX).property("x",&MyClass::getX,&MyClass::setX).class_function("getStringFromInstance",&MyClass::getStringFromInstance);}绑定块定义了对临时类对象的成员函数调用链(Boost.Python也采用相同的样式)。注意:你应该只绑定那些你真正需要的项目(以它为规则或原则),因为每次绑定都会增加代码大小。例如,内部方法和私有变量很少能被绑定。在JavaScript中定义和使用MyClass实例的代码如下:varinstance=newModule.MyClass(10,"hello");instance.incrementX();实例.x;//12实例.x=20;//20模块.MyClass.getStringFromInstance(实例);//“你好”instance.delete();内存管理因为JavaScript,尤其是ECMA-262Edition5.1,不支持带回调的终结器或弱引用,所以Emscripten没有办法调用C++对象的分析构造函数。警告:JavaScript代码必须显式删除C++对象的句柄,否则Emscripten堆将无限增长。varx=newModule.MyClass;x.方法();x.删除();vary=Module.myFunctionThatReturnsClassInstance();y.方法();y.删除();值类型对原始类型做手工内存管理比较麻烦,所以embind提供了对值类型的支持。包括Value数组和value对象,分别对应js数组和对象。示例:structPoint2f{floatx;浮动y;};结构PersonRecord{std::string名称;年龄;};PersonRecordfindPersonAtLocation(Point2f);EMSCRIPTEN_BINDINGS(my_value_example){value_array("Point2f").element(&Point2f::x).element(&Point2f::y);value_object("PersonRecord").field("name",&PersonRecord::name).field("age",&PersonRecord::age);函数(“findPersonAtLocation”,&findPersonAtLocation);以下代码无需担心手动生命周期管理。varperson=Module.findPersonAtLocation([10.2,156.5]);console.log('找到人了!他们的名字是'+person.name+'他们是'+person.age+'岁');高级类概念(todo)重载函数构造函数和函数可以根据参数个数进行重载,但是embind不支持根据参数类型进行重载。指定重载时,使用select_overload()辅助函数来选择适当的签名。结构HasOverloadedMethods{voidfoo();voidfoo(inti);voidfoo(floatf)常量;};EMSCRIPTEN_BINDING(重载){class_("HasOverloadedMethods").function("foo",select_overload(&HasOverloadedMethods::foo)).function("foo_int",select_overload(&HasOverloadedMethods::foo)).function("foo_float",select_overload(&HasOverloadedMethods::foo));}枚举embind支持C++98枚举和C++11枚举类。枚举旧样式{OLD_STYLE_ONE,OLD_STYLE_TWO};枚举类NewStyle{一,二};EMSCRIPTEN_BINDINGS(my_enum_example){enum_("OldStyle").value("ONE",OLD_STYLE_ONE).value("TWO",OLD_STYLE;_enum_("NewStyle").value("ONE",NewStyle::ONE).value("TWO",NewStyle::TWO);}调用JavaScript的方式如下:Module.OldStyle.ONE;Module.NewStyle.TWO;const向JavaScript暴露一个常量:EMSCRIPTEN_BINDINGS(my_constant_example){constant("SOME_CONSTANT",SOME_CONSTANT);}内存视图在某些情况下,将原始二进制数据作为类型值数组直接暴露给JavaScript代码很有用。这对于直接从堆中上传大型WebGL纹理很有用。内存视图应该像指针一样对待;生命周期和有效性不由运行时管理,如果底层对象被修改或重新分配,数据很容易被破坏。#include#include使用命名空间emscripten;unsignedchar*byteBuffer=/*...*/;size_tbufferLength=/*...*/;valgetBytes(){returnval(typed_memory_view(bufferLength,byteBuffer));}EMSCRIPTEN_BINDINGS(memory_view_example){function("getBytes",&getBytes);下面的JavaScript代码接收类型化数组视图varmyUint8Array=Module.getBytes()varxhr=newXMLHttpRequest();xhr.open('POST',/*...*/);xhr.send(myUint8Array);使用val将JavaScript转换为C++Embind提供了一个c++类emscripten::val,您可以使用将JavaScript代码转换为C++。使用val,您可以调用C++中的JavaScript对象,读写它们的属性,或者将它们强制转换为C++值,例如bool、int或std::string。以下代码展示了如何通过val从C++调用JavaScript的WebAudioAPI。先看js代码,展示js是如何使用这个API的://GetwebaudioapicontextvarAudioContext=window.AudioContext||window.webkitAudioContext;//获得一个AudioContext:创建上下文和OscillatorNodevarcontext=newAudioContext();var振荡器=context.createOscillator();//配置振荡器:设置OscillatorNode类型和频率oscillator.type='triangle';oscillator.frequency.value=261.63;//以赫兹为单位的值-中间C//Playingoscillator.connect(context.destination);振荡器.start();//全做完了!然后使用val将代码翻译成c++,如下:#include#include#includeusingnamespaceemscripten;intmain(){valAudioContext=val::global("AudioContext");if(!AudioContext.as()){printf("没有全局AudioContext,正在尝试webkitAudioContext\n");AudioContext=val::global("webkitAudioContext");}printf("得到一个AudioContext\n");valcontext=AudioContext.new_();valoscillator=context.call("createOscillator");printf("配置振荡器\n");oscillator.set("type",val("triangle"));oscillator["frequency"].set("value",val(261.63));//MiddleCprintf("Playing\n");oscillator.call("connect",context["destination"]);oscillator.call("start",0);printf("Alldone!\n");}先用global()获取全局AudioContext对象(如果不存在,获取webkitAudioContext对象),然后使用new_()创建一个实例,从中我们可以创建一个振荡器,set()它的属性,然后播放内建类型转换embind为许多标准C++类型提供类型转换C++类型JavaScript类型voidundefinedbooltrueorfalsecharnumbersignedcharnumberunsignedcharnumbershortnumberungignedshortnumberintnumberunsignedintnumberlognumberunsignedlongnumberfloatnumberdoublenumberstd::stringArrayBuffer,Uint8Array,Uint8ClampedArray,Int8Array,orStringstd::wstringString(UTF-16codeunits)emscripten::valanything为了方便起见,embind还提供了用于注册std::vector(register_vector())和std::map(register_map())类型的工厂函数。EMSCRIPTEN_BINDINGS(stl_wrappers){register_vector("VectorInt");register_map("MapIntInt");}性能在撰写本文时,标准基准测试或相对于WebIDLBinder测试没有全面的嵌入性能。一个简单函数的调用开销在200ns左右。虽然还有进一步优化的空间,但到目前为止,其在实际应用中的性能已被证明是可以接受的。Emscripten代码移植系列文章Emscripten代码移植主题系列是emscripten中文站点的一部分。本文是第三专题的第二篇文章。第一篇介绍代码的可移植性和局限性第二篇介绍Emscripten的运行环境第三篇第一篇介绍连接C++和JavaScript第三篇第二篇介绍embind第四篇介绍文件和文件系统