將一個C語言寫的hello world編譯成web版本是很簡單的,網上有大量這樣的例子。寫這樣的例子是必要的,讓咱們可以快速入門,可是實際項目則要複雜的多,這裏會介紹一些emscripten的基礎知識,同時強調移植AWTK過程當中遇到的問題,但願能讓你們少走彎路。web
-s EXPORTED_FUNCTIONS="['_awtk_web_init']"
函數名前面要加下劃線,好比函數名爲awtk_web_init,導出的名稱則爲_awtk_web_init。json
對於小的項目,導出的函數不多,直接寫在命令行也是能夠的。對於大的項目,導出的函數不少,應該把內容寫到文件中,經過@符合告訴emcc從文件中讀取導出的函數,這樣維護起來會方便不少。如:canvas
-s EXPORTED_FUNCTIONS=@configs/export_app_funcs.json
configs/export_app_funcs.json的內容:瀏覽器
[ "_awtk_web_init", "_awtk_web_deinit", "_awtk_web_main_loop_step", "_awtk_web_on_key_down", "_awtk_web_on_key_up", "_awtk_web_on_wheel", "_awtk_web_on_pointer_down", "_awtk_web_on_pointer_move", "_awtk_web_on_im_commit", "_awtk_web_on_pointer_up" ]
-s EXTRA_EXPORTED_RUNTIME_METHODS ="['cwrap']"
同理,將它的內容放在文件中,也是更可取的方法。如:app
-s EXTRA_EXPORTED_RUNTIME_METHODS=@configs/export_runtime_funcs.json
configs/export_runtime_funcs.json的內容:異步
[ "ccall", "cwrap", "addFunction", "removeFunction", "addOnPostRun", "addOnInit", "addOnExit", "addOnPreMain", "UTF8ToString" ]
對於大的項目,調試版本最好不要加-g標誌,產生的代碼實在太大了,可能讓瀏覽器處於假死狀態,根本無法調試。用缺省生成的代碼調試基本上就OK了。函數
發佈版本建議加-Os,代碼體積會大大減少,並且它會把數據獨立出來,提升加載的速度。oop
emcc生成的常量數據的首地址並不會按32bit/64bit對齊,AWTK就踩到這個坑裏了,SAFE_HEAP宏有助於發現這個問題。建議定義下列這些宏:優化
-DSAFE_HEAP=1 -DASSERTIONS=1 -DSTACK_OVERFLOW_CHECK=1
後來我給AWTK的常量全加上了對齊的屬性:this
#ifdef _MSC_VER #define TK_CONST_DATA_ALIGN(v) __declspec(align(8)) v #else #define TK_CONST_DATA_ALIGN(v) v __attribute__((aligned(8))) #endif /*_MSC_VER*/
TK_CONST_DATA_ALIGN(const unsigned char data_a_b_c_any[]) = { 0x08,0x00,0x00,0x01,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x61,0x2d,0x62,0x2d,0x63,0x2e,0x61,0x6e, 0x79,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x61,0x62,0x63,0x0a,0x00,0x00,0x00,0x00,};/*52*/
對於一個大型項目,在Windows平臺下,命令行參數很容易超長。最簡單的辦法就是將emcc的參數寫入文件中,經過@符合告訴emcc從文件讀取。如:
emcc -v @args.txt
包含emscripten.h頭文件,它提供了一些方法,讓C語言調用JS的函數。
#include <emscripten.h>
如:
emscripten_run_script("alert('hi')");
如:
#include <emscripten.h> EM_JS(void, call_alert, (), { alert('hello world!'); throw 'all done'; }); int main() { call_alert(); return 0; }
#include <emscripten.h> int main() { EM_ASM( alert('hello world!'); throw 'all done'; ); return 0; }
這是最快也是最簡單的調用方式,AWTK裏基本上都是採用這種方式調用的。如:
C端代碼:
static ret_t vgcanvas_web_save(vgcanvas_t *vgcanvas) { int32_t ret = EM_ASM_INT({ return VGCanvas.save(); }, 0); return ret ? RET_OK : RET_FAIL; }
JS端代碼:
VGCanvas.save = function () { VGCanvas.ctx.save(); return true; }
**傳遞數值參數也很是簡單。**如:
C端代碼:
static ret_t vgcanvas_web_move_to(vgcanvas_t *vgcanvas, float_t x, float_t y) { EM_ASM_INT({ return VGCanvas.moveTo($0, $1); }, x, y); return RET_OK; }
JS端代碼:
VGCanvas.moveTo = function (x, y) { VGCanvas.ctx.moveTo(x, y); return true; }
傳遞字符串參數就麻煩一點了。如:
C端代碼:
static ret_t vgcanvas_web_set_text_align(vgcanvas_t *vgcanvas, const char *text_align) { EM_ASM_INT({ return VGCanvas.setTextAlign($0); }, text_align); return RET_OK; }
JS端代碼:
VGCanvas.setTextAlign = function (value) { VGCanvas.ctx.textAlign = pointerToString(value); return true; }
字符串參數傳遞到JS函數裏時,JS函數拿到的內存地址的偏移量,須要把它解碼出來,生成JS的字符串對象。pointerToString函數是這樣實現的:
function pointerToString(pointer) { return pointer && Module.UTF8ToString(pointer, 1024) || null; }
Module.UTF8ToString函數須要在前面介紹的EXTRA_EXPORTED_RUNTIME_METHODS中導出才能使用。
AWTK中還用了二進制數據做爲參數,網上沒有見到相關的例子,只好本身去看代碼研究了。AWTK裏須要把位圖數據(rgba顏色值),傳遞到JS中,再設置到畫布裏。具體作法是這樣的:
VGCanvas.updateMutableImage = function (id) { let mutableImage = ImageCache.get(id); let w = mutableImage.width; let h = mutableImage.height; let size = mutableImage.width * mutableImage.height; let start = mutableImage.addr >> 2; let end = start + size; let array = Module.HEAP32.subarray(start, end); let ctx = mutableImage.getContext('2d'); let imageData = ctx.getImageData(0, 0, w, h); let data = new Int32Array(imageData.data.buffer); for(let i = 0; i < size; i++) { data[i] = array[i]; } ctx.putImageData(imageData, 0, 0, 0, 0, w, h); return true; }
mutableImage.addr是rgba數據的地址,它是用malloc分配出來的,我看了malloc函數的實現,它就相對於Module.HEAP32的字節數偏移量。因爲HEAP32是4字節數據,在做爲偏移量使用時,須要右移2位:
let start = mutableImage.addr >> 2;
再經過subarray從HEAP32中獲取這段數據:
let array = Module.HEAP32.subarray(start, end);
另外這裏值得一提的是,imageData.data是Int8Array,要轉換成Int32Array,能夠用下列方式:
let imageData = ctx.getImageData(0, 0, w, h); let data = new Int32Array(imageData.data.buffer);
按下面這種方式,則是把每個元素從8bit擴展成32bit了。
let imageData = ctx.getImageData(0, 0, w, h); let data = new Int32Array(imageData.data);
要在JS裏調用C的函數,通常用Module.cwrap包裝一下,它須要提供如下參數:
參數和返回值的類型有:
如:
Awtk._onImCommit = Module.cwrap('awtk_web_on_im_commit', 'number', ['string', 'number']); Awtk.onImCommit = function (text, timestamp) { return Awtk._onImCommit(text, timestamp); }
常見的用法的在文檔中都有清楚的說明,這裏再也不贅述。若是參數是一個回調函數,就稍微麻煩一點。
1.要導出addFunction/removeFunction(參考前面)
2.要指定參數RESERVED_FUNCTION_POINTERS。
如:
-s RESERVED_FUNCTION_POINTERS=1000
如:
widget_on(this.nativeObj, type, Module.addFunction(wrap_on_event(on_event)), ctx);
最麻煩的是函數用完以後,要調用removeFunction把函數從表裏移出,對於同步調用的回調函數這沒有什麼問題,可是對異步調用函數,特別是屢次調用的異步函數,何時能夠移出只有C代碼裏才知道,因此須要在C代碼裏添加處理。如:
#ifdef AWTK_WEB_JS #include <emscripten.h> #endif /*AWTK_WEB_JS*/ static ret_t emitter_item_destroy(emitter_item_t* iter) { if (iter->on_destroy) { iter->on_destroy(iter); } #ifdef AWTK_WEB_JS EM_ASM_INT({ return TBrowser.releaseFunction($0); }, iter->handler); #endif /*AWTK_WEB_JS*/ memset(iter, 0x00, sizeof(emitter_item_t)); TKMEM_FREE(iter); return RET_OK; }