Node.js   发布时间:2022-04-24  发布网站:大佬教程  code.js-code.com
大佬教程收集整理的这篇文章主要介绍了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++类型提供类型转换

@H_502_129@ @H_502_129@ @H_502_129@ @H_502_129@ @H_502_129@ @H_502_129@ @H_502_129@ @H_502_129@ @H_502_129@ @H_502_129@ @H_502_129@ @H_502_129@ @H_502_129@ @H_502_129@ @H_502_129@ @H_502_129@ @H_502_129@ @H_30_197@
C++类型 JavaScript类型
void undefinedbool true or falsechar numbersigned char numberunsigned char numbershort numberungigned short numberint numberunsigned int numberlog numberunsigned long numberfloat numberdouble numberstd::string ArrayBuffer,Uint8Array,Uint8ClampedArray,Int8Array,or Stringstd::wString String (UTF-16 code units)emscripten::val anything

为了方便,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,请注明来意。