大佬教程收集整理的这篇文章主要介绍了Emscripten代码移植之embind(三),大佬教程大佬觉得挺不错的,现在分享给大家,也给大家做个参考。
翻译:云荒杯倾
Embind用于绑定C++函数和类到JavaScript,这样编译代码就能在js中以一种很自然的方式来使用。Embind也支持从C++调JavaScript的class。
Embind支持绑定大多数C++的结构,包括C++11和C++14中引入的。它只有一个明显的限制就是目前还不支持raw pointers with complicated lifetime semantics。
本文展示了如何使用EMSCRIPTEN_BINDINGS()块来创建函数、类、值类型、指针(包括原始和智能指针)、枚举和常量的绑定,以及如何为抽象类创建绑定,这些抽象类可以在JavaScript中被重写。它还简要介绍了如何管理传递给JavaScript的c++对象句柄的内存。
note: Embind的灵感来自 Boost.Python,他们使用非常相似的方法定义绑定。@H_944_15@一个简单例子
下面的代码使用EMSCRIPTEN_BINDINGS()暴露了C++ lerp()函数给JavaScript。
// quick_example.cpp #include <emscripten/bind.h> using namespace emscripten; float lerp(float a,float b,float t) { return (1 - t) * a + t * b; } EMSCRIPTEN_BINDINGS(my_modulE) { function("lerp",&lerp); }@H_944_15@为了使用embind编译上例,请调用emcc的bing选项:
emcc --bind -o quick_example.js quick_example.cpp@H_944_15@生成的quick_example.js文件可以作为node模块加载,也可以使用<script>加载:
<!doctype html> <html> <script src="quick_example.js"></script> <script> console.log('lerp result: ' + Module.lerp(1,2,0.5)); </script> </html>@H_944_15@当quick_example.js文件初始化加载后, EMSCRIPTEN_BINDINGS()中代码会运行。
所有通过Embind暴露的symblols都可以在Module对象获取。
类
暴露一个类给JavaScript需要比较复杂的绑定语句,比如:
class MyClass { public: MyClass(int x,std::string y) : x(X),y(y) {} void incrementX() { ++x; } int getX() const { return x; } void setX(int x_) { x = x_; } static std::string getStringFromInstance(const MyClass& instancE) { return instance.y; } private: int x; std::string y; }; // Binding code EMSCRIPTEN_BINDINGS(my_class_examplE) { class_<MyClass>("MyClass") .constructor<int,std::string>() .function("incrementX",&MyClass::incrementX) .property("x",&MyClass::getX,&MyClass::setX) .class_function("getStringFromInstance",&MyClass::getStringFromInstancE) ; }@H_944_15@绑定块在一个临时class_对象上定义了成员函数调用链(Boost.Python也是同样风格)。
note: 你应该只绑定那些你实际需要的项(将它作为一个规则或原则),因为每个绑定会增加代码大小。比如,内部方法和私有变量可以很少绑定。@H_944_15@在JavaScript中定义和使用MyClass实例的代码如下:
var instance = new Module.MyClass(10,"@R_696_11423@lo"); instance.incrementX(); instance.x; // 12 instance.x = 20; // 20 Module.MyClass.getStringFromInstance(instancE); // "@R_696_11423@lo" instance.delete();@H_944_15@内存管理
因为JavaScript,尤其是ECMA-262 Edition 5.1,不支持 finalizers or weak references with callBACks,因此Emscripten没有办法调用C++对象的析构函数。
警告: JavaScript代码必须明确删除C++对象的句柄,否则Emscripten堆会无限增长。@H_944_15@var x = new Module.MyClass; x.method(); x.delete(); var y = Module.MyFunctionThatReturnsClassInstance(); y.method(); y.delete();@H_944_15@值类型
对基本类型进行手动内存管理是麻烦的,所以embind对值类型提供了支持。包括Value arrays和 value objects,分别对应js的array和object。
示例:
struct Point2f { float x; float y; }; struct PersonRecord { std::string name; int age; }; PersonRecord findPersonAtLOCATIOn(Point2f); EMSCRIPTEN_BINDINGS(my_value_examplE) { value_array<Point2f>("Point2f") .element(&Point2f::X) .element(&Point2f::y) ; value_object<PersonRecord>("PersonRecord") .field("name",&PersonRecord::Name) .field("age",&PersonRecord::agE) ; function("findPersonAtLOCATIOn",&findPersonAtLOCATIOn); }@H_944_15@以下代码就不需要担心手动生命周期管理。
var person = Module.findPersonAtLOCATIOn([10.2,156.5]); console.log('Found someone! their name is ' + person.name + ' and they are ' + person.age + ' years old');@H_944_15@高级类概念(todo)
重载函数
构造函数和函数可以根据参数数量重载,但embind不支持根据参数类型重载。当你指定一个重载,请使用SELEct_overload()帮助函数选中合适的签名。
struct HasOverloadedMethods { void foo(); void foo(int i); void foo(float f) const; }; EMSCRIPTEN_BINDING(overloads) { class_<HasOverloadedMethods>("HasOverloadedMethods") .function("foo",SELEct_overload<void()>(&HasOverloadedMethods::foo)) .function("foo_int",SELEct_overload<void(int)>(&HasOverloadedMethods::foo)) .function("foo_float",SELEct_overload<void(float)const>(&HasOverloadedMethods::foo)) ; }@H_944_15@枚举
embind支持C++98枚举和C++11枚举类。
enum OldStyle { OLD_STYLE_ONE,OLD_STYLE_TWO }; enum class NewStyle { ONE,TWO }; EMSCRIPTEN_BINDINGS(my_enum_examplE) { enum_<OldStyle>("OldStyle") .value("ONE",OLD_STYLE_ONE) .value("TWO",OLD_STYLE_TWO) ; enum_<NewStyle>("NewStyle") .value("ONE",NewStyle::ONE) .value("TWO",NewStyle::TWO) ; }@H_944_15@JavaScript调用方式如下:
@H_274_14@module.oldStyle.oNE; Module.NewStyle.TWO;@H_944_15@常量
向JavaScript暴露一个常量:
EMSCRIPTEN_BINDINGS(my_constant_examplE) { constant("SOME_CONSTANT",SOME_CONSTANT); }@H_944_15@内存视图
在某些情况下,将原始二进制数据以一个类型化数组的形式直接暴露给JavaScript代码是有价值的。这对于直接从堆上上传大型WebGL纹理非常有用。
内存视图应该像指针一样对待;生命周期和有效性不由运行时管理的,如果底层对象被修改或重新分配,则很容易损坏数据。
#include <emscripten/bind.h> #include <emscripten/val.h> using namespace emscripten; unsigned char *byteBuffer = /* ... */; size_t bufferLength = /* ... */; val getBytes() { return val(typed_memory_view(bufferLength,byteBuffer)); } EMSCRIPTEN_BINDINGS(memory_view_examplE) { function("getBytes",&getBytes); }@H_944_15@下面JavaScript代码接收类型数组视图
var myUint8Array = Module.getBytes() var xhr = new XMLhttprequest(); xhr.open('POST',/* ... */); xhr.send(myUint8Array);@H_944_15@使用val将JavaScript翻译为C++
Embind提供了一个c++类,emscripten::val,您可以使用它将JavaScript代码转换为c++。使用val,可以在c++中调用JavaScript对象,读取和写入它们的属性,或者强制它们成为c++值,比如bool、int或std::string。
下面代码展示了你可以通过val在C++中调用JavaScript的 Web Audio API。
首先看一下js的代码,展示js怎么用这个API:// Get web audio api context var AudioContext = window.AudioContext || window.webkitAudioContext; // Got an AudioContext: Create context and OscillatorNode var context = new AudioContext(); var oscillator = context.createOscillator(); // Configuring oscillator: set OscillatorNode type and frequency oscillator.type = 'triangle'; oscillator.frequency.value = 261.63; // value in hertz - middle C // Playing oscillator.connect(context.desTination); oscillator.start(); // All done!@H_944_15@然后使用val将代码翻译成c++,如下:
#include <emscripten/val.h> #include <stdio.h> #include <math.h> using namespace emscripten; int main() { val AudioContext = val::global("AudioContext"); if (!AudioContext.as<bool>()) { printf("No global AudioContext,trying webkitAudioContext\n"); AudioContext = val::global("webkitAudioContext"); } printf("Got an AudioContext\n"); val context = AudioContext.new_(); val oscillator = context.call<val>("createOscillator"); printf("Configuring oscillator\n"); oscillator.set("type",val("triangle")); oscillator["frequency"].set("value",val(261.63)); // Middle C printf("Playing\n"); oscillator.call<void>("connect",context["desTination"]); oscillator.call<void>("start",0); printf("All done!\n"); }@H_944_15@首先使用global()取全局AudioContext对象(如果不存在就取webkitAudioContext对象),然后使用New_()创建实例,从实例我们可以创建oscillator,设置set()它的属性,然后播放。
内建类型转换
embind为许多标准C++类型提供类型转换
C++类型 | JavaScript类型 | @H_502_129@void | undefined | @H_502_129@bool | true or false | @H_502_129@char | number | @H_502_129@signed char | number | @H_502_129@unsigned char | number | @H_502_129@short | number | @H_502_129@ungigned short | number | @H_502_129@int | number | @H_502_129@unsigned int | number | @H_502_129@log | number | @H_502_129@unsigned long | number | @H_502_129@float | number | @H_502_129@double | number | @H_502_129@std::string | ArrayBuffer,Uint8Array,Uint8ClampedArray,Int8Array,or String | @H_502_129@std::wString | String (UTF-16 code units) | @H_502_129@emscripten::val | anything | @H_30_197@
---|
为了方便,embind还提供了工厂函数用来注册std::vector<T>(register_vector())和std::map<K,V>(register_map())类型。
EMSCRIPTEN_BINDINGS(stl_wrappers) { register_vector<int>("VectorInt"); register_map<int,int>("MapInTint"); }@H_944_15@性能
在撰写本文时,还没有对标准基准测试或相对于WebIDL Binder的全面的embind性能测试。
简单函数的调用开销在200ns左右。虽然还有进一步优化的空间,但到目前为止,它在实际应用程序中的性能已经被证明是完全可以接受的。
Emscripten代码移植系列文章
Emscripten代码移植主题系列文章是emscripten中文站点的一部分内容。
本文是第三个主题第二篇文章。
第一个主题介绍代码可移植性与限制
第二个主题介绍Emscripten的运行时环境
第三个主题第一篇文章介绍连接C++和JavaScript
第三个主题第二篇文章介绍embind
第四个主题介绍文件和文件系统
以上是大佬教程为你收集整理的Emscripten代码移植之embind(三)全部内容,希望文章能够帮你解决Emscripten代码移植之embind(三)所遇到的程序开发问题。
如果觉得大佬教程网站内容还不错,欢迎将大佬教程推荐给程序员好友。
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。