本文介绍Emscripten - 用于将C/C++的代码向Javascript转换。可用于如这样一个应用场景:有一份历史代码用C/C++实现,开发者需要用Js调用其中的代码。
介绍Emscripten之前,本文梳理asm.js和WebAssembly的相关知识。
一. asm.js
官方网站:http://asmjs.org/spec/latest/
虽然名字叫“asm.js”,虽然asm.js也可以直接用javascript来编写,但是这样写出来的代码可读性非常差。
而且asm.js的初衷就是将C/C++程序移植到浏览器上来。
所以通常的做法是使用C/C++这样的静态类型和手动回收内存的语言编写程序,然后使用编译器将编写的程序编译为asm.js。
在Wasm出现之前,emscripten将C/C++编译成asm.js,步骤如下:
二. WebAssembly (WASM)
WebAssembly(缩写为Wasm)是用于基于堆栈的虚拟机的二进制指令格式。Wasm被设计为编程语言的可移植编译目标,从而可以在Web上为客户端和服务器应用程序进行部署。
比起asm.js,将源代码编译成wasm调用时效率更高,而且对于源代码的保护更好,也是现在的emscripten默认格式。
注:目前主流的浏览器均已在2018年、2019年左右支持了assembly等。而ASM.js支持所有浏览器运行,因为asm.js本质上还是js,将其他语言编译成为js,我们将这种js成为asm.js,是与ts类似的东西。而assembly是字节码,直接更加底层,已经不是js了,类似于一种flash一样的技术了,但是是浏览器内置的,必须由浏览器实现,如果浏览器不支持,就不能运行,可以预见的是,会有浏览器兼容性问题,另外,底层bug更加难以调试和发现。
google地图早先只能在chrome中使用的原因就是,其本身使用相当部分的naitiv代码,所以google地图才能在web中进行渲染。后面也逐渐切换到webassembly了。
附:一些使用webassembly的项目https://blog.csdn.net/VhWfR2u02Q/article/details/79257270
三. Emscripten
详细教程见官方网站,本文列举一些重点。
3.1 下载和安装
Emscripten需要Git下载并安装,依次用以下命令。
# Get the emsdk repo git clone https://github.com/emscripten-core/emsdk.git # Enter that directory cd emsdk # Fetch the latest version of the emsdk (not needed the first time you clone) git pull # Download and install the latest SDK tools. ./emsdk install latest # Make the "latest" SDK "active" for the current user. (writes .emscripten file) ./emsdk activate latest # Activate PATH and other environment variables in the current terminal # Windows 下运行emsdk_env.bat source ./emsdk_env.sh
3.2 C语言示例
3.2.1 Hello World
将一个带有main函数的C语言代码进行转化:
1 #include <stdio.h> 2 3 int main(){ 4 printf("hello world\n"); 5 return 0; 6 }
通过命令
emcc .\input\hello_world.c -o .\output\hello_world.js 或 emcc .\input\hello_world.c -o .\output\hello_world.html
可以生成js或html文件。
这里html文件双击运行会失效,需要借助httpserver,可以通过python
python -m SimpleHTTPServer 8001
通过http://localhost:8001/hello.html可以看到输出
3.2.2 函数
实现一个简单的C语言代码,调用其中的函数。
1 #include <stdio.h> 2 #include <emscripten.h> 3 4 EMSCRIPTEN_KEEPALIVE 5 void sayHi() { 6 printf("Hi!\n"); 7 } 8 9 EMSCRIPTEN_KEEPALIVE 10 int daysInWeek() { 11 return 7; 12 } 13 14 EMSCRIPTEN_KEEPALIVE 15 int multi2(int input) { 16 return input * 2; 17 }
编译:
emcc function.c -o function.js -s MODULARIZE -s EXPORTED_RUNTIME_METHODS=['ccall'] -s
如果想用asm.js可以加入 -s WASM=0
在nodejs中进行调用:
1 var factory = require("./function"); 2 3 factory().then((instance) => { 4 instance._sayHi(); // direct calling works 5 instance.ccall("sayHi"); // using ccall etc. also work 6 console.log(instance._daysInWeek()); // values can be returned, etc. 7 console.log(instance._multi2(3)); 8 });
在html中使用:
1 <!doctype html> 2 <html> 3 <script src="function.js"></script> 4 <script> 5 Module().then( 6 instance => { 7 instance._sayHi(); // direct calling works 8 instance.ccall("sayHi"); // using ccall etc. also work 9 console.log(instance._daysInWeek()); // values can be returned, etc. 10 console.log(instance._multi2(3)); 11 } 12 ); 13 </script> 14 </html>
3.2.3 C++的函数和类
C++可以通过CWrap将接口转换成C语言并实现,比较复杂。这里介绍另一种方式:embind
Embind用于将C ++函数和类绑定到JavaScript,以便“正常” JavaScript以自然的方式使用编译后的代码。Embind还支持从C ++调用JavaScript类。
Embind支持绑定大多数C ++构造,包括C ++ 11和C ++ 14中引入的构造。它唯一的重要限制是它当前不支持具有复杂生命周期语义的原始指针。
C++代码:
1 #include <emscripten/bind.h> 2 3 using namespace emscripten; 4 5 class MyClass { 6 public: 7 MyClass(int x, std::string y) 8 : x(x) 9 , y(y) 10 {} 11 12 void incrementX() { 13 ++x; 14 } 15 16 int getX() const { return x; } 17 void setX(int x_) { x = x_; } 18 19 static std::string getStringFromInstance(const MyClass& instance) { 20 return instance.y; 21 } 22 23 private: 24 int x; 25 std::string y; 26 }; 27 28 EMSCRIPTEN_BINDINGS(my_class_example) { 29 class_<MyClass>("MyClass") 30 .constructor<int, std::string>() 31 .function("incrementX", &MyClass::incrementX) 32 .property("x", &MyClass::getX, &MyClass::setX) 33 .class_function("getStringFromInstance", &MyClass::getStringFromInstance) 34 ; 35 }
编译:
emcc --bind -o class_cpp.js class.cpp
Js调用:
1 var instance = new Module.MyClass(10, "hello"); 2 instance.incrementX(); 3 instance.x; // 12 4 instance.x = 20; // 20 5 Module.MyClass.getStringFromInstance(instance); // "hello" 6 instance.delete();