WebAssembly编译之(4)-WASM编译进阶(多文件、多接口)

引言
上一节我们讲到如何用Emscripten将一个C编译陈wasm,并导出可供Javascirpt调用的接口,以及C++导出类的函数接口、导出类的封装对象等。然而,编译的方式比较玛法,有没办法能更友好一点实现wasm的编译呢

WASM 相关文档:
WebAssembly编译之(1)-asm.js及WebAssembly原理介绍
WebAssembly编译之(2)-Ubuntu搭建WASM编译环境
WebAssembly编译之(3)-WASM编译实战之C/C++导出asm.js及wasm库

这一节我们继续给大家介绍wasm编译进阶教程,多个文件的编译,甚至多个接口的编译。

多个C/C++接口的导出编译方法

上一节前面我们知道,每次执行emcc指令时,我们都需要在命令行中携带参数EXPORTED_FUNCTIONS=['_funsName1','_funsName2']指定需要导出的函数接口。而然,当需要导出的接口变非常多的时候,这样操作显然不太明智。怎么办呢?

1)使用EMSCRIPTEN_KEEPALIVE宏修饰C的函数

我们可以使用Emscripten给我们提供的宏指令EMSCRIPTEN_KEEPALIVE修饰。使用这个宏之前需要引入#include ,同时为了避免代码提示找不到include头文件的错误信息,我们可以需要在IDE编辑器中,设置好include目录,include的目录位置一般在你的EMSDK git项目目录emsdk/upstream/emscripten/system/include/emscripten

我们还是先上代码

// HelloToolFuns.c
#include 
#include 

EMSCRIPTEN_KEEPALIVE
int myAdd(int a,int b){
    int res = a+b;
    return res;
}

EMSCRIPTEN_KEEPALIVE
int myMutilp(int a, int b){
    int s = a * b;
    return s;
}

EMSCRIPTEN_KEEPALIVE
void sayHello() {
      printf("Hello World!(HelloToolFuns.c)\n");
}

我们可以直接使用一个EMSCRIPTEN_KEEPALIVE修饰在需要导出的函数接口即可。在编译时,不需要指定带导出的接口函数名,只需要直接使用emcc编译即可,如下所示:

注意:cpp的函数导出也同样适用这个方法,只是,任然需要在函数的外层套一个extern "C" { }

emcc HelloToolFuns.c -o ./test-html/HelloToolFuns.js

EMSCRIPTEN_KEEPALIVE本意是保持函数的意思,怎么理解呢?Emscripten在编译时,为了保证wasm足够小,其实还会做一层删除无用函数的操作。如对于内联函数、或是未调用的函数会做DCE清除操作。当我们采用了这个宏,表示这个函数无论如何将做保留操作。

那么在编译后,被保留下来的函数将会挂着到Module.asm下,这样,我们就可以调用了。我们分析一下我们的HelloToolFuns.js代码,确实myAddmyMutilpsayHello这三个函数都不存在任何调用关系,很显然在编译时,必然会被Emscripten优化掉。这也就是我们上一节课开头发现的,为啥直接编译不携带任何参数时,编译出来的wasm在Module.asm下没有任何我们需要的接口函数

2)使用EMSCRIPTEN_BINDINGS实现C++的函数及类的完美导出

前面我们介绍了,使用cpp编译导出wasm可用的函数时,需要添加extern "C",同时,需要将C++的类及方法做一层转化,使C可调用的C++的类。

而在Javascript使用这种导出的wasm类时,用起来总感觉不符合面向对象的编程习惯,看起就是妥妥C的编程习惯。如下代码所示:

var hToolObj = Module.asm.HelloTools_OBJ_New(); // 创建对象
Module.asm.HelloTools_add(hToolObj,10,20) // 访问对象

有没办法能使C++导出的wasm,在javascirpt调用时,可以优雅一点来访问呢?比如这样:

var hToolObj = new Module.asm.HelloTools(); // 创建对象
hToolObj.add(10,20) // 访问对象

这样是不更爽一点呢?这就是我们这小节需要重点介绍的EMSCRIPTEN_BINDINGS宏。

2.1)C++的导出函数

我们还是先看看用怎么通过它实现cpp编写的函数的导出,先上代码:

// HelloToolFuns2.cpp
#include  
#include 

int myAdd(int a,int b){
    int res = a+b;
    return res;
}

int myMutilp(int a, int b){
    int s = a * b;
    return s;
}

void sayHello() {
    printf("Hello World!(HelloToolFuns2.cpp - EMSCRIPTEN_BINDINGS)\n");
}

// 这里是导出列表
EMSCRIPTEN_BINDINGS(my_module) {
    
    emscripten::function("myAdd",&myAdd);

    emscripten::function("myMutilp",&myMutilp);

    emscripten::function("sayHello",&sayHello);
}

然后编译cpp

emcc --bind HelloToolFuns2.cpp -o ./test-html/HelloToolFuns2.js

注意,编译时,我们需要添加 --bind参数,否则会报错

通过使用EMSCRIPTEN_BINDINGS导出列表,我们甚至可以不需要对cpp的函数添加extern "C" {} 来包裹,直接写在EMSCRIPTEN_BINDINGS声明的即可;

而使用这种方式的编译出来的函数接口,是直接挂着到Javascript的Module._<函数名>的,如Module._sayHello,而Module.asm下是找不到的。

2.2)C++的导出类

如何使用EMSCRIPTEN_BINDINGS导出类呢,我们还是直接上代码

// HelloTools.cpp
#include 
#include 

class HelloTools{
    public:
    HelloTools(int n);
    void print(int a, int b);
    int add(int a, int b);
    int num=0;
    int sum=0;
    static void showHello();
};

HelloTools::HelloTools(int n){
    num = n;
}

void HelloTools::print(int a, int b){
    std::cout<<"a+b="<<a<<"+"<<b<<"="<<a+b<<std::endl;
}

int HelloTools::add(int a, int b){
    int c = 0;
    sum+= a+b;
    return sum;
}

void HelloTools::showHello(){
    std::cout<<"show Hello Emscripten!"<<std::endl;
}

// 导出类
EMSCRIPTEN_BINDINGS(my_class_example) {
    emscripten::class_<HelloTools>("HelloTools")
    .constructor<int>()
    .function("print",&HelloTools::print)
    .function("add",&HelloTools::add)
    .property("num",&HelloTools::num)
    .class_function("showHello",&HelloTools::showHello)
    ;
}

执行编译

emcc --bind HelloTools.cpp -o ./test-html/HelloTools.js

编译出来的类将直接挂着到Module.<类名>,即Module.HelloTools

在浏览器中测试一下

var hToolObj = new Module.HelloTools(10) // 实例化HelloTools
hToolObj.add(10,20) // 返回30
hToolObj.num // 返回10
hToolObj.print(100,200) // 输出 a+b=100+200=300
Module.HelloTools.showHello() //输出 show Hello Emscripten!

WebAssembly编译之(4)-WASM编译进阶(多文件、多接口)_第1张图片
至此,我们实现了通过Emscripten提供的接口,使用EMSCRIPTEN_BINDINGS宏,实现C++函数或类的完美导出,且至此Javascript实现友好的调用。

(多文档导出,待续)

你可能感兴趣的:(c++,wasm,WebAssembly)