简介:
我们在js中调用c/c++函数主要是用ccall
和cwrap
那么在C/C++代码中调用JavaScript函数
主要有4种方法:
- 通过
emscripten_run_script()
函数 - 通过宏函数
EM_JS()
EM_ASM()
EM_ASM_()
EM_ASM_INT()
EM_ASM_DOUBLE()
- 通过插入到“胶水”内部依赖库
- 通过指针在C/C++代码中调用js函数
1.通过 emscripten_run_script()
函数
没优化效率低不安全
#include <emscripten.h>
int main(int argc,char **argv){
emscripten_run_script("console.log('hello world')");
return 0;
}
2.通过宏函数EM_JS()
EM_ASM()
EM_ASM_()
EM_ASM_INT()
EM_ASM_DOUBLE()
EM_JS()
c/c++源码
#include <emscripten.h>
#include <iostream>
using namespace std;
//定义一个add函数,函数体由js代码实现,声明部分由c/c++实现
EM_JS(int,add,(int x,int y),{
console.log(x,y);
document.getElementById("my_result").innerText = x+y;
return x+y;
});
int main(int argc,char **argv){
cout<<add(1,2)<<endl;
return 0;
}
命令
emcc dependent.cc -s WASM=1 -o dependent.js
js代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>我的wasm学习</title>
</head>
<body>
<h1 id="my_result"></h1>
<script>
var Module = {};
fetch("/cctest/dependent.wasm").then(
response => response.arrayBuffer()
).then((bytes) => {
Module.wasmBinary = bytes;
var script = document.createElement('script');
script.src = "/cctest/dependent.js";
document.body.appendChild(script);
})
</script>
</body>
</html>
-
EM_ASM()
它和EM_JS
类似,但是并不需要通过函数定义来“包装”上层环境中的js代码;而是直接在里面编写。
#include <emscripten.h>
int main(int argc,char **argv){
EM_ASM(
console.log("hello world")
);
EM_ASM(
document.getElementById("my_result").innerText = "我 爱 星星";
);
return 0;
}
-
EM_ASM_()
和上个唯一不同在于:可以直接使用从c/c++环境中传入的基本类型变量。
#include <emscripten.h>
int main(int argc,char **argv){
int x = 8;
EM_ASM_({
document.getElementById("my_result").innerText = $0;
},
x
);
return 0;
}
只支持基本类型
-
EM_ASM_INT()
不仅可以传递基本变量值,而且可以返回js环境执行后的整型值
#include <emscripten.h>
#include <iostream>
using namespace std;
int main(int argc,char **argv){
int x = 8;
int r = EM_ASM_INT({
document.getElementById("my_result").innerText = $0;
return $0+1;
},
x
);
cout<<r<<endl;
return 0;
}
-
EM_ASM_DOUBLE()
与EM_ASM_INT()
区别在于返回浮点数
3.通过插入到“胶水”内部依赖库
c/c++源码
#include <emscripten.h>
#include <iostream>
using namespace std;
extern "C"{
//声明在外部模块中定义的custom_add函数
extern int custom_add(int x,int y);
}
int main(int argc,char **argv){
int x = 8,y=10;
cout<<custom_add(x,y)<<endl;
return 0;
}
附加的js代码
mergeInto(LibraryManager.library,{
custom_add: function(x,y){
return x+y;
}
});
命令
emcc dependent.cc -s WASM=1 -o dependent.js --js-library js_library_command.js
调用
mergeInto
函数:将其第二个参数所对应对象结构内的所有成员函数全部拷贝到第一个参数LibraryManager.library
所对应的对象结构中。这里LibraryManager.library
便是Emscripten在其内部维护的一个依赖库对象。在该对象中,存放着Wasm应用可以使用到的所有JavaScript库函数,这些库函数将会在编译过程中有选择性地被输出到用于连接浏览器与Wasm模块的“胶水”脚本文件中。
4.通过指针在C/C++代码中调用js函数
c/c++源码
#include <emscripten.h>
#include <iostream>
using namespace std;
//该函数接受一个从javascript环境传递过来的函数指针
extern "C" {
EMSCRIPTEN_KEEPALIVE void wrapper (int fp){
//定义目标函数类型
using fpt = void (*)(int);
cout<<"the function pointer is :"<<fp<<endl;
//对传递过来的int类型的指针进行类型转换
fpt f = reinterpret_cast<fpt>(fp);
//通过指针调用对应的js函数
f(7);
//清理函数索引表
EM_ASM_({
Module.removeFunction($0);
},f);
}
}
在这段代码中,我们定义了一个名为wrapper
的函数。该函数的主要功能是负责接收从JavaScript环境传递过来的一个整型的函数指针,并根据JavaScript代码中原函数的类型将该函数指针转换成特定的指针类型。接下来,我们便可以通过该指针间接地调用定义在JavaScript环境中的函数。在代码段的最后,我们通过EM_ASM_宏函数调用了Emscripten “胶水”脚本中的removeFunction
方法,该方法会将函数索引表中对应位置处的函数引用清空。下面我们继续编写JavaScript部分的代码。
附加js代码
__ATPOSTRUN__.push(() =>{
//通过Emscripten内部的addFunction向函数索引表中注册一个函数,并返回该函数的函数指针
var newFuncPtr = Module.addFunction(function(num){
console.log(num);
console.log(`hello ${num} from JS!`);//注意这里是`````````
},"vi");
//调用在C/C++代码中定义的wrappper函数
Module.ccall('wrapper',null,['number'],[newFuncPtr]);
});
上面代码一共完成两个任务。首先,通过Module.addFunction
方法将一个JavaScript 匿名函数注册到全局的函数索引表中,该方法在执行完成后会返回该匿名函数在索引表中的函数指针。然后,调用在C/C++代码中编写的wrapper
函数,并将之前得到的函数指针作为参数传递进去。
命令
emcc dependent.cc
--std=c++11
-s WASM=1
-s RESERVED_FUNCTION_POINTERS=20
-s EXTRA_EXPORTED_RUNTIME_METHODS="['addFunction','removeFunction','ccall']"
-o dependent.js
--post-js post-script.js
RESERVED_FUNCTION_POINTERS
:初始化函数索引表的可用大小的
最后要注意的是:相对于c++代码来说,从JavaScript环境传递过来的函数指针是不透明的,我们在编写代码时需要通过reinterpret _cast
严格地将这些指针转换为对应JavaScript函数的签名类型。因此,专门的wrapper函数将用于对接专门的某一类JavaScript函数指针,而这样可能会损失应用开发的灵活性。并且如果函数指针在转换过程中没有保持严格的函数签名一致性, 则可能会引发未定义的错误,让应用调试变得复杂。