WebAssembly Compilation (3)-WASM Compilation Actual C/C++ Export asm.js and Wasm Library

introduction

In the previous section , we introduced the quick construction of the WASM compilation environment under Ubuntu. In this section, we continue the introduction of WASM compilation - how to export the function library written in C/C++

WASM related documents:
WebAssembly compilation (1)-asm.js and WebAssembly principle introductionWebAssembly
compilation (2)-Ubuntu build WASM compilation environment

Export of a single C++ file (*.cpp)

We first introduce how to export a file (*.cpp) for Emscripten compilation asm.jsor wasm. Let's go directly to the c++ code first

// HelloTools.cpp
#include <iostream>

class HelloTools{
    
    
    public:
    void print(int a, int b);
    int add(int a, int b);
};

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;
    c = a+b;
    print(a , b);
    return c;
}

This is HelloTools.cppa HelloTools class file that we wrote when we introduced c++ earlier. There is mainly one addmethod; how to export this library and how to compile it into a js library that can be called by the web front end?

Let's first emcccompile with the command to see what the result is

# 注意,首次执行我们需要激活一下环境变量,找到emsdk的源码路径,进入emsdk目录,激活环境变量
source ./emsdk_env.sh
# 进入项目
cd 1.singleCPP
# 开始编译
emcc HelloTools.cpp -o HelloTools.js

HelloTools.jsThe compilation is successful, and it is generated HelloTools.wasm. How to check the result of the compilation? Let’s continue

Test wasm in node environment

We first simply write a test test.jsscript

// test.js
var em_module = require('./HelloTools.js');
console.log(em_module);
var toolObj = new em_module();
var sum = toolObj.add(10,20);
console.log(sum);

Use the node command to execute the js

node test.js

Note that it needs to be pre-installednodejs

The result is as follows:

{
    
    
  inspect: [Function (anonymous)],
  FS_createDataFile: [Function: createDataFile],
  FS_createPreloadedFile: [Function: createPreloadedFile],
  ___wasm_call_ctors: [Function (anonymous)],
  ___errno_location: [Function (anonymous)],
  _fflush: [Function (anonymous)],
  _emscripten_stack_init: [Function (anonymous)],
  _emscripten_stack_get_free: [Function (anonymous)],
  _emscripten_stack_get_base: [Function (anonymous)],
  _emscripten_stack_get_end: [Function (anonymous)],
  stackSave: [Function (anonymous)],
  stackRestore: [Function (anonymous)],
  stackAlloc: [Function (anonymous)],
  dynCall_jiji: [Function (anonymous)]
}
/home/1.singleCPP/test-node/HelloTools.js:147
      throw ex;
      ^

TypeError: em_module is not a constructor
    at Object.<anonymous> (/home/1.singleCPP/test-node/test.js:3:15)
    at Module._compile (internal/modules/cjs/loader.js:1085:14)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10)
    at Module.load (internal/modules/cjs/loader.js:950:32)
    at Function.Module._load (internal/modules/cjs/loader.js:790:12)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:76:12)
    at internal/main/run_main_module.js:17:47

It loads normally, but it fails later 创建实例, and a bunch of errors are reported, and the usage is wrong, which is obviously not what we expected; and from the Module module information printed earlier, it seems that we have not found HelloTools.cppany identification related to our class.

HTML test wasm in browser

In order to facilitate debugging and testing, we use the chrome browser for testing (we will test and explain in this environment later). Let's create an html test file first, the html is as follows

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Emscripten:HelloTools Class Test</title>
  </head>
  <body>
    <script>
    Module = {
      
      };
    Module.onRuntimeInitialized = function() {
      
       //此时才能获得完整Module对象
      console.log(Module)
    }
    </script>
    <script src="./HelloTools.js"></script>
  </body>
</html>

Start a web service, here emsdk is more thoughtful, and has provided a quick start web service commandemrun

First, let's look at the file deconstruction of the entire project

- 1.singleCPP
  - test-html // html测试文件夹
    - HelloTools.js
  	- HelloTools.wasm
  	- index.html // html测试文件
  - test-node // node测试文件夹
    - HelloTools.js
    - HelloTools.wasm
    - test.js // node测试文件
	- HelloTools.cpp // c++ 源码

start web service

# 进入文件夹
cd test-html
# 启动web服务
emrun --no_browser --port 8080 .

Open the chrome browser, press the F12open debug panel, browse http://localhost:8080, load normally, and print out the following content

insert image description here
Again, we still haven't found any identities, properties, or methods related to HelloTools.cpp; we expand Module.asmthe properties as follows:
insert image description here

Correct C export and Javascript calling posture

EmscriptenLet's start with the simple one and see how the compiler under the C language exports the interface, and how Javascirpt calls the exported C. First a piece of C code

// HelloToolFuns.c
#include  <stdio.h>

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!(HelloToolFuns.c)\n");
}

We use emcc to compile

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

Note, remember index.htmlto modify the js reference in<script src="./HelloToolFuns.js"></script>

Reload the browser, and then directly test how javascirpt calls the wasm interface exported by c in the Console panel

Javascirpt calls the C interface

cwrap and ccall are two methods provided by wasm to call the c interface under javascript. Let us HelloToolFuns.cintroduce the calling method as an example:

1) By Module.cwrapcalling the C interface
// Module.cwrap
var myMutilp = Module.cwrap('myMutilp', 'number', ['number','number'])
var s = myMutilp(10,20)
console.log(s);

Module.cwrapThe first parameter is the function name, the second parameter is the return type of the function, and the third is the parameter type; there are three types that can be used in the return type and parameter type, namely: number, string and array.

number - (number in js, corresponding to integer, floating point, general pointer in C)
string - (string in JavaScript, corresponding to char in C, char in C represents a string)
array - (It is an array or type array in js, corresponding to an array in C; if it is a type array, it must be Uint8Array or Int8Array).

2) By Module.ccallcalling the C interface
// Module.ccall
var s = Module.ccall('myMutilp', 'number', ['number','number'],[10,20])
console.log(s);

Module.ccallThe usage of Module.cwrapis slightly different from the previous one. Module.ccallIt has four parameters, the first three Module.cwrapare the same as the meaning and type, and the fourth is the actual incoming parameter. And Module.ccallit directly calls the function needed for execution, and `Module.cwarp`` just returns a specific function instance.

We choose the first method and test it in the console panel

const sayHello = Module.cwrap('sayHello')

insert image description here
Unfortunately, I found an error. It turns out that the default EMCC compiler will not export these two functions, so we need to specify this to export these two functions at compile time.

emcc -s EXPORTED_RUNTIME_METHODS=['cwrap','ccall'] HelloToolFuns.c -o ./test-html/HelloToolFuns.js

Note: EXTRA_EXPORTED_RUNTIME_METHODS has been deprecated, EXPORTED_RUNTIME_METHODSparameters should be used

But when we tested again in the browser, there were new errors, as follows:

const sayHello = Module.cwrap('sayHello')
sayHello()

insert image description here
The sayHello method cannot be called, and it prompts that this method needs to be exported. How to export it?

When compiling, we need to use EXPORTED_FUNCTIONSthe specified export method

emcc -s -EXPORTED_RUNTIME_METHODS=['cwrap','ccall'] -s EXPORTED_FUNCTIONS=['_sayHello'] HelloToolFuns.c -o ./test-html/HelloToolFuns.js

It should be noted here EXPORTED_FUNCTIONSthat when exporting, add an underscore "_" before the function name. The
sayHelloexport format of the above command parameter: needs to be written as_sayHello

Let's test again:

const sayHello = Module.cwrap('sayHello')
sayHello()

insert image description here
HelloToolFuns.cThe method in was successfully called sayHello!

3) By Module._<FunctionName>calling directly (recommended)

In fact, when we compile, after setting -s EXPORTED_FUNCTIONS=['_sayHello'], we can call directly through Module._sayHello(), without using Module.ccallor Module.cwrapcalling.

Module._sayHello()

insert image description here

Obviously because the EMCC compiler automatically injected sayHello()this method in HelloToolFuns.c for us in the Module. We can search for sayHello in HelloToolFuns.jsthis ;胶水代码

// 部分代码
/** @type {function(...*):?} */
var _sayHello = Module["_sayHello"] = createExportWrapper("sayHello"); // createExportWrapper这个方法即用来创建C代码中导出的接口

Usually, we recommend users to use this direct Module._sayHello()method to call

4) By Module.asm.<FunctionName>calling directly

In fact, we checked HelloToolFuns.jsthe source code and found that after the wasm was loaded, the entire asm instance was hung in the middle Module.asm, and Module['asm']the exported object of the WebAssembly instance was saved in the middle—and the exported function is the main entrance of the WebAssembly instance for external calls.
Therefore, we can also find the method Module.asmwe exported directly throughsayHello

Module.asm.sayHello()

insert image description here
Finally, we can summarize the above four methods. The most convenient ones are actually the latter two, which seem to be more in line with Javascript development methods; and through the analysis of the glue code, the difference between the third and fourth methods is not big. The third type is only hanging under the Module after the fourth is packaged.

Regarding HelloToolFuns.jsthe analysis of this glue code, we will spend another time analyzing and introducing the source code. Here we only need to know that its core work is to help us do asynchronous loading HelloToolFuns.wasmand instantiation, and expose some interfaces for us to use; if you want, you can write this js completely by yourself or even don’t need this js. `WebAssembly.instantiate(binary, info) in js, and then directly call the interface exported by C;

Correct C++ export and Javascirpt calling posture ( 重点)

First of all, we need to know that C++ supports function overloading as an example, and will perform Mangle processing on the function name, that is, modify the function name. That is to say, if we directly write a C++ library class, the exported wasm cannot be called to the relevant interface normally. ;

1) Export functions written in C++

So when we write a *.cpp(note, no *.c), even if there is only a function, Emscripten will compile the file as c++ code by default. At this time, even if no function overloading occurs, the function cannot be exported normally according to the previous c method.

We HelloToolFuns.cdirectly modify the previous file name to HelloToolFuns.cpp, when executing EMCC compilation and specifying the export function name, an error will be reported:emcc: error: undefined exported symbol: "_sayHello" [-Wundefined] [-Werror]

This is what we need to use to extern "C" { } include the function, which can prevent C++'s default behavior of modifying the function name;

The use extern "C"is actually the main implementation path for C to call the C++ library; you can refer to relevant documents.

Therefore, we found a way to realize C++ exporting the wasm library, that is, by extern "C"exposing the interface that C can call, so the problem of C++ exporting the wasm library is consistent with the previous problem of C exporting the wasm library;

We HelloToolFuns.cppmodify it as follows

//HelloToolFuns.cpp (注意,此时是cpp文件)
#include  <stdio.h>

extern "C"{
    
    
    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!(HelloToolFuns.c)\n");
    }
}

Execute the compilation again. At this time, the normal compilation can be successful, and no error will be reported. Symbol cannot be found.

2) Realize the function interface exposure of the C++ class

As we know earlier, since Emscripten will have a Mangle mechanism when compiling C++, it is obvious that classes cannot be exported directly as before; we need to save the country by writing an intermediate package program to realize the C++ to C interface, thereby exposing the C interface;

The long-awaited content is finally out

Let's first come to a long-awaited cpp code, the source code is as follows:

// HelloTools.cpp
#include <iostream>
class HelloTools{
    
    
    public:
    void print(int a, int b);
    int add(int a, int b);
};

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;
    c = a+b;
    print(a , b);
    return c;
}

According to the previous analysis, we need to add the relevant interface of the exposed class, and extern "C" {}wrap the interface function that needs to be exposed. The new code is as follows:

// HelloTools.cpp
#include <iostream>
class HelloTools{
    
    
    public:
    void print(int a, int b);
    int add(int a, int b);
};

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;
    c = a+b;
    print(a , b);
    return c;
}

extern "C"{
    
    
    void HelloTools_print(int a, int b){
    
    
        HelloTools hTools;
        hTools.print(a,b);
    }

    int HelloTools_add(int a, int b){
    
    
        HelloTools hTools;
        return hTools.add(a,b);
    }
}

In this way, we expose a library function in a C++ class through C, and we compile it

mcc -s EXPORTED_FUNCTIONS=['_HelloTools_print','_HelloTools_add'] HelloTools.cpp -o ./test-html/HelloTools.js

Compiled successfully! Go ahead and test it in your browser

Module.asm.HelloTools.print(10,20)
// >> a+b=10+20=30

The execution is successful, and the method we need is successfully called
insert image description here

2) Realize the object and function interface of exporting C++

Sometimes we want to get a class, and can instantiate this class as an object, and then call the interface of this object; how to achieve it?

In order to facilitate the verification of the export function of the object, we slightly modify the code, as follows:

#include <iostream>
class HelloTools{
    
    
    public:
    void print(int a, int b);
    int add(int a, int b);
    int sum=0;
};

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;
}

Note that the above class has a member variable , which will be added to this member variable after sumeach method used ;add()sum

In order to realize the export of objects, we have added an interface for exporting object creation and deletion. The final code is as follows:

#include <iostream>
class HelloTools{
    
    
    public:
    void print(int a, int b);
    int add(int a, int b);
    int sum=0;
};

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;
}

struct C_HelloTools;

extern "C"{
    
    
    // 创建对象
    struct C_HelloTools* HelloTools_OBJ_New(){
    
    
        HelloTools *obj = new HelloTools();
	    return (struct C_HelloTools*)obj;
    }

    // 删除对象
    void HelloTools_OBJ_Delete(struct C_HelloTools* c_htools) {
    
    
	    HelloTools *obj = (HelloTools*)c_htools;
	    delete obj;
    }

    // print
    void HelloTools_print(struct C_HelloTools* c_htools, int a, int b){
    
    
        HelloTools *obj = (HelloTools*)c_htools;
        obj->print(a,b);
    }

    // add
    int HelloTools_add(struct C_HelloTools* c_htools,int a, int b){
    
    
        HelloTools *obj = (HelloTools*)c_htools;
        return obj->add(a,b);
    }
}

Compile with Emscripten

emcc -s EXPORTED_FUNCTIONS=['_HelloTools_OBJ_New','_HelloTools_OBJ_Delete','_HelloTools_print','_HelloTools_add'] HelloTools.cpp -o ./test-html/HelloTools.js

compile successfully

Let's verify it in the browser:

var hToolObj = Module.asm.HelloTools_OBJ_New()
Module.asm.HelloTools_add(hToolObj,10,20) // >> 30
Module.asm.HelloTools_add(hToolObj,1,2) // >>33

insert image description here
So far, we have successfully used Emscripten to export C++ classes

Guess you like

Origin blog.csdn.net/youlinhuanyan/article/details/128768615