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.js
or 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.cpp
a HelloTools class file that we wrote when we introduced c++ earlier. There is mainly one add
method; 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 emcc
compile 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.js
The 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.js
script
// 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-installed
nodejs
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.cpp
any 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 F12
open debug panel, browse http://localhost:8080
, load normally, and print out the following content
Again, we still haven't found any identities, properties, or methods related to HelloTools.cpp; we expand Module.asm
the properties as follows:
Correct C export and Javascript calling posture
Emscripten
Let'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.html
to 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.c
introduce the calling method as an example:
1) By Module.cwrap
calling the C interface
// Module.cwrap
var myMutilp = Module.cwrap('myMutilp', 'number', ['number','number'])
var s = myMutilp(10,20)
console.log(s);
Module.cwrap
The 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.ccall
calling the C interface
// Module.ccall
var s = Module.ccall('myMutilp', 'number', ['number','number'],[10,20])
console.log(s);
Module.ccall
The usage of Module.cwrap
is slightly different from the previous one. Module.ccall
It has four parameters, the first three Module.cwrap
are the same as the meaning and type, and the fourth is the actual incoming parameter. And Module.ccall
it 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')
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_METHODS
parameters should be used
But when we tested again in the browser, there were new errors, as follows:
const sayHello = Module.cwrap('sayHello')
sayHello()
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_FUNCTIONS
the 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_FUNCTIONS
that when exporting, add an underscore "_" before the function name. The
sayHello
export format of the above command parameter: needs to be written as_sayHello
Let's test again:
const sayHello = Module.cwrap('sayHello')
sayHello()
HelloToolFuns.c
The 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.ccall
or Module.cwrap
calling.
Module._sayHello()
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.js
this ;胶水代码
// 部分代码
/** @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.js
the 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.asm
we exported directly throughsayHello
Module.asm.sayHello()
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.js
the 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 loadingHelloToolFuns.wasm
and 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.c
directly modify the previous file name toHelloToolFuns.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.cpp
modify 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
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
sum
each 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
So far, we have successfully used Emscripten to export C++ classes