這個文章主要描述在cheerp環境下, js和c++側數據類型的轉換和包裝內容。
1 基礎數據類型
首先我們知道javascript是弱類型的腳本語言,開發者在開發的時候不必關注數據的類型和邊界,而c++是靜態編程語言, 在編譯階段就需要確定類型,在編譯器處理的時候可以獲得更好的優化。
我們都知道js所擁有的數值類型,(int,uint,float,double)默認不區分都是double類型存儲,這點和lua很相似。
在一般堆棧機下,如果使用函數調用會有大量的push,pop指令來獲取傳遞的參數,還要在內部進行根據類型包裝成可識別的類型。
cheerp的內存模型是平坦的,目標如果不是wasm的話, 是和js一致的,可以直接調用(翻譯成javascript,不需要push,pop這種指令)。
在cheerp環境下如果交織javascript和c++代碼, cheerp會通過位移移除來替我們處理類型, 比如int類型,用js的Number左移兩位來標識。我們可以不關心這些基礎數據的轉換。
2複合數據類型
如果是js模式,cheerp會將js對象轉換成struct,或者是class類的映射,默認不推薦使用動態結構。 首先要在cheerp側擁有對應的結構定義,才能獲取和寫入屬性。
從js側生成庫給c++使用:
比如公開一個函數到c++
function window_base64_encode(s) {
return window.btoa(s);
}
我們在c++裏定義
String * window_base64_encode(String *);
cheerp 會自動給我們生成函數調用參數的原型,並切映射到js側.
如果是傳輸的對象我們可以使用Object* 來標識js側的對象。
Object * window_getData(String * );
如果是wasm模式,cheerp會嚴格遵守c++標準,對數據進行轉換。
如果要傳輸複合類型的數據,比如說對象那麼我們需要用struct對基礎數據進行包裝.而不是直接使基礎類型.
struct Sub {
int age ;
};
struct Data {
char hellow [20];
int age ;
struct Sub c;
};
等同於{age:20, c:{age:21},hellow:"cheerp"}
從c++調用js:
//JSExportAPI::test();
uint8 Indata[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
for(char i = 0; i < 16; i++) {
Indata[i] = i; ///用例
}
JSExportAPI::myJsSayHello(Indata); ///wasm調用js
for(char i = 0; i < 16; i++) {
std::cout << (*Indata+i)<< endl; //結果
}
從js調用c++
int myCppSayHello(Data a) {
cout << "hellow i am from cpp " <<endl;
cout <<"hellow="<< a.hellow <<endl;
cout <<"age="<< a.age <<endl;
cout <<"sub.age="<< a.c.age <<endl;
return 0;
}
class [[cheerp::genericjs]] JSExportAPI {
public:
JSExportAPI() {
}
static void myJsSayHello (uint8 * buffer) {
Uint8Array * t = (Uint8Array *)cheerp::MakeArrayBufferView(buffer);
auto array = cheerp::makeArrayRef(t);
for( int i = 0 ; i < array->get_length(); i++ ) {
array[i] = array[i] + 100 ;
*(buffer+i) = array[i];
}
struct Data d ;
d.age = 20;
d.c = {21};
memcpy(d.hellow ,"cheerp", 7);
myCppSayHello(d);///js調用wasm
}
};
[[cheerp::genericjs]] 字段是生成js模式的代碼。
無論是js到c++還是c++到js, cheerp轉換的開銷比一般的(ffi)的技術開銷要小很多。類型包裝也是他性能優化的基礎方式之一。