本文介紹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中使用的原因就是,其本身使用相當部分的native代碼,所以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調用,還不知道如何在nodejs后端調用:
1 <!doctype html> 2 <html> 3 <script> 4 var Module = { 5 onRuntimeInitialized: function() { 6 var instance = new Module.MyClass(10, "hello"); 7 instance.incrementX(); 8 console.log(instance.x); // 12 9 instance.x = 20; 10 console.log(instance.x); // 20 11 console.log(Module.MyClass.getStringFromInstance(instance)); // "hello" 12 instance.delete(); 13 } 14 }; 15 </script> 16 <script src="class_cpp.js"></script> 17 </html>