Emscripten 提供了許多方法來連結和互動 JavaScript 與編譯過的 C 或 C++
從一般 JavaScript 呼叫編譯過的 C 函式
使用直接函式呼叫 (更快但更複雜)。
使用以建立的繫結,從 JavaScript 呼叫編譯過的 C++ 類別
從 C/C++ 呼叫 JavaScript 函式
本文說明上述列出的每個方法,並提供更多詳細資訊的連結。
注意
如需編譯程式碼如何與瀏覽器環境互動的資訊,請參閱Emscripten 執行階段環境。如需檔案系統相關事項,請參閱檔案系統概觀。
注意
在您可以呼叫程式碼之前,執行階段環境可能需要載入記憶體初始化檔案、預先載入檔案,或執行其他非同步作業,具體取決於最佳化和建置設定。請參閱 FAQ 中的我如何知道頁面已完全載入,並且可以安全地呼叫編譯的函式?。
從 JavaScript 呼叫編譯的 C 函式最簡單的方法是使用ccall()
或 cwrap()
。
ccall()
會以指定的參數呼叫已編譯的 C 函式並傳回結果,而 cwrap()
則會「包裝」已編譯的 C 函式並傳回您可以正常呼叫的 JavaScript 函式。因此,如果您計畫多次呼叫已編譯的函式,則 cwrap()
更有用。
請考慮下面顯示的 test/hello_function.cpp 檔案。要編譯的 int_sqrt()
函式會包裝在 extern "C"
中,以防止 C++ 名稱修飾。
// Copyright 2012 The Emscripten Authors. All rights reserved.
// Emscripten is available under two separate licenses, the MIT license and the
// University of Illinois/NCSA Open Source License. Both these licenses can be
// found in the LICENSE file.
#include <math.h>
extern "C" {
int int_sqrt(int x) {
return sqrt(x);
}
}
若要編譯此程式碼,請在 Emscripten 主目錄中執行下列命令
emcc test/hello_function.cpp -o function.html -sEXPORTED_FUNCTIONS=_int_sqrt -sEXPORTED_RUNTIME_METHODS=ccall,cwrap
EXPORTED_FUNCTIONS
會告訴編譯器我們希望從編譯程式碼中存取哪些內容 (如果未使用,則可能會移除所有其他內容),而 EXPORTED_RUNTIME_METHODS
會告訴編譯器我們想要使用執行階段函式 ccall
和 cwrap
(否則,它不會包含它們)。
注意
EXPORTED_FUNCTIONS 會影響 JavaScript 編譯。如果您先編譯為物件檔,然後將物件編譯為 JavaScript,則您需要在第二個命令上使用該選項。如果您像這裡的範例一樣 (原始碼直接到 JavaScript),則一切都會正常運作。
編譯後,您可以使用下列 JavaScript,使用 cwrap()
呼叫此函式
int_sqrt = Module.cwrap('int_sqrt', 'number', ['number'])
int_sqrt(12)
int_sqrt(28)
第一個參數是要包裝的函式名稱,第二個參數是函式的傳回類型 (如果沒有,則為 JavaScript null 值),第三個參數是參數類型陣列 (如果沒有參數,則可以省略)。這些類型是「number」(對應於 C 整數、浮點數或一般指標的 JavaScript 數字)、「string」(對應於 C char*
表示字串的 JavaScript 字串) 或「array」(對應於 C 陣列的 JavaScript 陣列或類型陣列;對於類型陣列,它必須是 Uint8Array 或 Int8Array)。
您可以先在網頁瀏覽器中開啟產生的頁面 function.html (由於沒有 main()
,因此頁面載入時不會發生任何事情) 來自行執行。開啟 JavaScript 環境 (在 Firefox 上按 Control-Shift-K,在 Chrome 上按 Control-Shift-J),並輸入上述命令作為三個單獨的命令,在每個命令後按 Enter。您應該會得到結果 3
和 5
,這是使用 C++ 整數數學的這些輸入的預期輸出。
ccall()
類似,但會接收另一個參數,其中包含要傳遞給函式的參數
// Call C from JavaScript
var result = Module.ccall('int_sqrt', // name of C function
'number', // return type
['number'], // argument types
[28]); // arguments
// result is 5
注意
此範例說明了其他幾點,您在使用 ccall()
或 cwrap()
時應該記住這些點
這些方法可用於已編譯的 C 函式,名稱修飾的 C++ 函式無法運作。
我們強烈建議您匯出要從 JavaScript 呼叫的函式
匯出是在編譯時完成的。例如:-sEXPORTED_FUNCTIONS=_main,_other_function
會匯出 main()
和 other_function()
。
請注意,您需要在 EXPORTED_FUNCTIONS
清單中的函式名稱開頭加上 _
。
請注意,該清單中提到了 _main
。如果您沒有將其放在那裡,編譯器會將其作為無效程式碼刪除。匯出的函式清單是將保持運作的完整清單 (除非其他程式碼以其他方式保持運作)。
Emscripten 會執行無效程式碼刪除,以盡可能減少程式碼大小,匯出可確保不會移除您需要的函式。
在較高的最佳化層級 (-O2
及更高版本) 中,程式碼會縮小,包括函式名稱。匯出函式可讓您繼續使用全域 Module
物件使用原始名稱來存取它們。
如果您想要匯出 JS 程式庫函式 (例如,來自 src/library*.js
檔案的內容),則除了 EXPORTED_FUNCTIONS
之外,您還需要將其新增至 DEFAULT_LIBRARY_FUNCS_TO_INCLUDE
,因為後者會強制將該方法實際包含在建置中。
編譯器會移除它認為未使用的程式碼,以改善程式碼大小。如果在編譯器可見之處使用 ccall
,例如在 --pre-js
或 --post-js
中的程式碼,它會正常運作。如果在編譯器看不到的地方使用它,例如在 HTML 中的另一個 script 標籤或像本教學一樣在 JS 主控台中使用,那麼由於最佳化和縮小化,您應該使用 EXPORTED_RUNTIME_METHODS
從執行時環境匯出 ccall,例如使用 -sEXPORTED_RUNTIME_METHODS=ccall,cwrap
,並在 Module
上呼叫它 (其中包含所有匯出的內容,以一種安全的方式,不受縮小化或最佳化的影響)。
假設您有一個 C 函式庫公開了一些程序
//api_example.c
#include <stdio.h>
#include <emscripten.h>
EMSCRIPTEN_KEEPALIVE
void sayHi() {
printf("Hi!\n");
}
EMSCRIPTEN_KEEPALIVE
int daysInWeek() {
return 7;
}
使用 emcc 編譯函式庫
emcc api_example.c -o api_example.js -sMODULARIZE -sEXPORTED_RUNTIME_METHODS=ccall
要求函式庫並從 node 呼叫其程序
var factory = require('./api_example.js');
factory().then((instance) => {
instance._sayHi(); // direct calling works
instance.ccall("sayHi"); // using ccall etc. also work
console.log(instance._daysInWeek()); // values can be returned, etc.
});
MODULARIZE
選項使 emcc
以模組化格式發出程式碼,該格式易於使用 require()
匯入和使用:模組的 require()
會傳回一個工廠函數,該函數可以實例化編譯後的程式碼,傳回一個 Promise
來告知我們何時準備就緒,並以參數形式提供模組的實例給我們。
(請注意,這裡我們使用 ccall
,因此我們需要像之前一樣將其新增到匯出的執行時方法中。)
原始碼中的函數會變成 JavaScript 函數,因此如果您自己進行類型轉換,則可以直接呼叫它們 — 這會比使用 ccall()
或 cwrap()
快,但會稍微複雜一些。
若要直接呼叫方法,您需要使用產生程式碼中顯示的完整名稱。這將與原始 C 函數相同,但帶有前導 _
。
您傳遞給函數和從函數接收的參數需要是原始值
整數和浮點數可以按原樣傳遞。
指標也可以按原樣傳遞,因為它們在產生的程式碼中只是整數。
可以使用
ptr = stringToNewUTF8(someString)
將 JavaScript 字串someString
轉換為char *
。注意
轉換為指標會配置記憶體,之後需要透過呼叫
free(ptr)
(_free
在 JavaScript 端) 來釋放記憶體 -可以使用
UTF8ToString()
將從 C/C++ 接收的char *
轉換為 JavaScript 字串。在 preamble.js 中,還有其他用於轉換字串和編碼的便利函數。
其他值可以透過
emscripten::val
傳遞。請查看關於 as_handle 和 take_ownership 方法 的範例。
Emscripten 提供了兩種主要方法,可從 C/C++ 呼叫 JavaScript:使用 emscripten_run_script()
執行腳本,或編寫「內嵌 JavaScript」。
最直接但稍慢的方式是使用 emscripten_run_script()
。這實際上是使用 eval()
從 C/C++ 執行指定的 JavaScript 程式碼。例如,若要使用文字「hi」呼叫瀏覽器的 alert()
函數,您會呼叫下列 JavaScript
emscripten_run_script("alert('hi')");
注意
函數 alert
出現在瀏覽器中,但不會出現在 *node* 或其他 JavaScript shell 中。更通用的替代方法是呼叫 console.log。
從 C 呼叫 JavaScript 的更快方法是使用 EM_JS()
或 EM_ASM()
(和相關巨集) 編寫「內嵌 JavaScript」。
EM_JS 用於從 C 檔案內宣告 JavaScript 函數。可以使用 EM_JS 撰寫「alert」範例,如下所示
#include <emscripten.h>
EM_JS(void, call_alert, (), {
alert('hello world!');
throw 'all done';
});
int main() {
call_alert();
return 0;
}
EM_JS 的實作本質上是 實作 JavaScript 函式庫 的簡寫。
EM_ASM 的使用方式與內嵌組合語言程式碼類似。可以使用內嵌 JavaScript 撰寫「alert」範例,如下所示
#include <emscripten.h>
int main() {
EM_ASM(
alert('hello world!');
throw 'all done';
);
return 0;
}
在編譯和執行時,Emscripten 會執行兩行 JavaScript,就像它們直接出現在產生的程式碼中一樣。結果將是一個警示,然後是一個例外。(但是請注意,在幕後,即使在這種情況下,Emscripten 仍然會進行函數呼叫,這會有一些額外的負荷。)
您也可以將值從 C 傳送到 EM_ASM
中的 JavaScript,例如
EM_ASM({
console.log('I received: ' + $0);
}, 100);
這會顯示 I received: 100
。
您也可以接收傳回值,例如,以下程式碼會先印出 I received: 100
,然後印出 101
int x = EM_ASM_INT({
console.log('I received: ' + $0);
return $0 + 1;
}, 100);
printf("%d\n", x);
請參閱 emscripten.h 文件
以了解更多詳細資訊。
注意
您需要使用適當的巨集 EM_ASM_INT
、EM_ASM_DOUBLE
或 EM_ASM_PTR
指定傳回值是 int
、double
或指標類型。(除非使用 MEMORY64
,否則 EM_ASM_PTR
與 EM_ASM_INT
相同,因此主要用於想要與 MEMORY64
相容的程式碼中。)
輸入值顯示為 $0
、$1
等等。
return
用於提供從 JavaScript 傳回 C 的值。
請參閱此處如何使用 {
和 }
來括住程式碼。這是必要的,以區分程式碼和稍後傳遞的引數 (輸入值)(這是 C 巨集的工作方式)。
當使用 EM_ASM
巨集時,請確保僅使用單引號 (`)。雙引號 (") 會導致編譯器無法偵測到的語法錯誤,並且僅在執行錯誤的程式碼時查看 JavaScript 主控台時才會顯示。
clang-format 可能會損壞 Javascript 結構,例如將 =>
變成 = >
。若要避免這種情況,請將下列程式碼新增至您的 .clang-format
:WhitespaceSensitiveMacros: ['EM_ASM', 'EM_JS', 'EM_ASM_INT', 'EM_ASM_DOUBLE', 'EM_ASM_PTR', 'MAIN_THREAD_EM_ASM', 'MAIN_THREAD_EM_ASM_INT', 'MAIN_THREAD_EM_ASM_DOUBLE', 'MAIN_THREAD_EM_ASM_DOUBLE', 'MAIN_THREAD_ASYNC_EM_ASM']
。或者,在 EM_ASM
區段之前撰寫 // clang-format off
,並在其之後撰寫 // clang-format on
,以關閉 clang-format。
可以在 JavaScript 中實作 C API!這是 Emscripten 的許多函式庫 (如 SDL1 和 OpenGL) 中使用的方法。
您可以使用它來撰寫自己的 API 以從 C/C++ 呼叫。若要執行此操作,您可以定義介面,使用 extern
裝飾,以將 API 中的方法標記為外部符號。然後,您只需將其定義新增至 library.js (預設) 即可在 JavaScript 中實作這些符號。在編譯 C 程式碼時,編譯器會在 JavaScript 函式庫中尋找相關的外部符號。
預設情況下,實作會被加入到 library.js 中(你可以在這裡找到 Emscripten 的 libc 部分)。你可以將 JavaScript 實作放在你自己的函式庫檔案中,並使用 emcc 選項 --js-library
來加入。請參閱 test/test_other.py 中的 test_js_libraries,其中包含完整可運作的範例,包括你應該在 JavaScript 函式庫檔案中使用的語法。
舉一個簡單的例子,考慮以下 C 程式碼的情況:
extern void my_js(void);
int main() {
my_js();
return 1;
}
注意
當使用 C++ 時,你應該將 extern void my_js();
包在 extern "C" {}
區塊中,以防止 C++ 名稱修飾。
extern "C" {
extern void my_js();
}
然後,你可以透過簡單地將實作加入到 library.js(或你自己的檔案)中,來在 JavaScript 中實作 my_js
。就像我們其他從 C 呼叫 JavaScript 的例子一樣,下面的範例僅使用簡單的 alert()
函數建立一個對話框。
my_js: function() {
alert('hi');
},
如果你將其加入到你自己的檔案中,你應該寫入如下內容:
addToLibrary({
my_js: function() {
alert('hi');
},
});
addToLibrary
會將輸入物件的屬性複製到 LibraryManager.library
(所有 JavaScript 函式庫程式碼所在的全域物件)。在這個例子中,它會在這個物件上加入一個名為 my_js
的函式。
如果你不熟悉 JavaScript,例如你是一位 C/C++ 程式設計師且只是使用 emscripten,那麼以下問題可能不會出現。但如果你是一位有經驗的 JavaScript 程式設計師,你需要注意某些常見的 JavaScript 實務無法在 emscripten 函式庫檔案中以某些方式使用。
為了節省空間,預設情況下,emscripten 只會包含 C/C++ 參考的函式庫屬性。它透過對連結的 JavaScript 函式庫中每個使用的屬性呼叫 toString
來完成此操作。這表示你不能直接使用閉包,例如,因為 toString
與之不相容 - 就像使用字串建立 Web Worker 時,你也無法傳遞閉包一樣。(請注意,此限制僅適用於傳遞給 JS 函式庫中 addToLibrary
的物件的鍵的值,也就是說,頂層的鍵值對是特殊的。函數內部的程式碼當然可以有任意的 JS)。
為了避免 JS 函式庫的此限制,你可以使用 --pre-js
或 --post-js
選項將程式碼放在另一個檔案中,這允許使用任意的標準 JS,並且它會與輸出的其餘部分一起包含和優化。這是大多數情況下建議的方法。另一個選項是另一個 <script>
標籤。
或者,如果你喜歡使用 JS 函式庫檔案,你可以讓一個函數取代自身,並在初始化期間呼叫它。
addToLibrary({
// Solution for bind or referencing other functions directly
good_02__postset: '_good_02();',
good_02: function() {
_good_02 = document.querySelector.bind(document);
},
// Solution for closures
good_03__postset: '_good_03();',
good_03: function() {
var callCount = 0;
_good_03 = function() {
console.log("times called: ", ++callCount);
};
},
// Solution for curry/transform
good_05__postset: '_good_05();',
good_05: function() {
_good_05 = curry(scrollTo, 0);
},
});
__postset 是一個編譯器將直接發送到輸出檔案的字串。對於上面的範例,將會發出此程式碼。
function _good_02() {
_good_o2 = document.querySelector.bind(document);
}
function _good_03() {
var callCount = 0;
_good_03 = function() {
console.log("times called: ", ++callCount);
};
}
function _good_05() {
_good_05 = curry(scrollTo, 0);
};
// Call each function once so it will replace itself
_good_02();
_good_03();
_good_05();
你也可以將大部分程式碼放在 xxx__postset
字串中。下面的範例中,每個方法都宣告對 $method_support
的依賴,否則都是虛設函數。$method_support
本身具有一個對應的 __postset
屬性,其中包含將各種方法設定為我們實際想要的函數的所有程式碼。
addToLibrary({
$method_support: {},
$method_support__postset: [
'(function() { ',
' var SomeLib = function() { ',
' this.callCount = 0; ',
' }; ',
' ',
' SomeLib.prototype.getCallCount = function() {',
' return this.callCount; ',
' }; ',
' ',
' SomeLib.prototype.process = function() { ',
' ++this.callCount; ',
' }; ',
' ',
' SomeLib.prototype.reset = function() { ',
' this.callCount = 0; ',
' }; ',
' ',
' var inst = new SomeLib(); ',
' _method_01 = inst.getCallCount.bind(inst); ',
' _method_02 = inst.process.bind(inst); ',
' _method_03 = inst.reset.bind(inst); ',
'}()); ',
].join('\n'),
method_01: function() {},
method_01__deps: ['$method_support'],
method_02: function() {},
method_01__deps: ['$method_support'],
method_03: function() {},
method_01__deps: ['$method_support'],
});
注意:如果你使用 node 4.1 或更新版本,你可以使用多行字串。它們僅在編譯時使用,而非執行時,因此輸出仍然會在基於 ES5 的環境中執行。
另一個選項是將大部分程式碼放在一個物件中,而不是一個函數。
addToLibrary({
$method_support__postset: 'method_support();',
$method_support: function() {
var SomeLib = function() {
this.callCount = 0;
};
SomeLib.prototype.getCallCount = function() {
return this.callCount;
};
SomeLib.prototype.process = function() {
++this.callCount;
};
SomeLib.prototype.reset = function() {
this.callCount = 0;
};
var inst = new SomeLib();
_method_01 = inst.getCallCount.bind(inst);
_method_02 = inst.process.bind(inst);
_method_03 = inst.reset.bind(inst);
},
method_01: function() {},
method_01__deps: ['$method_support'],
method_02: function() {},
method_01__deps: ['$method_support'],
method_03: function() {},
method_01__deps: ['$method_support'],
});
請參閱 library_*.js 檔案以獲取其他範例。
注意
JavaScript 函式庫可以宣告相依性(__deps
),但是這些相依性僅適用於其他 JavaScript 函式庫。請參閱 /src 中名稱格式為 library_*.js 的範例
你可以使用 autoAddDeps(myLibrary, name)
為所有方法新增相依性,其中 myLibrary 是包含所有方法的物件,而 name
是它們都依賴的項目。當所有已實作的方法都使用包含 Helper 方法的 JavaScript 單例時,這非常有用。請參閱 library_webgl.js
以獲取範例。
傳遞給 addToLibrary 的鍵會產生以 _
開頭的函數。換句話說,my_func: function() {},
會變成 function _my_func() {}
,因為 emscripten 中的所有 C 方法都有 _
前綴。以 $
開頭的鍵會移除 $
,並且不會新增底線。
你可以使用 addFunction
來傳回代表函數指標的整數值。然後將該整數傳遞給 C 程式碼,可以將該值作為函數指標呼叫,並且將呼叫你傳遞給 addFunction
的 JavaScript 函數。
請參閱 test_add_function in test/test_core.py 以獲取範例。
你應該使用 -sALLOW_TABLE_GROWTH
進行建置,以允許將新函數加入到表格中。否則,預設情況下,表格具有固定大小。
當使用帶有 JavaScript 函數的 addFunction
時,你需要提供一個額外的第二個引數,即 Wasm 函數簽名字串,如下所述。請參閱 test/interop/test_add_function_post.js 以獲取範例。
當使用 addFunction
和在 JavaScript 函式庫中使用時,LLVM Wasm 後端需要 Wasm 函數簽名字串。簽名字串中的每個字元都代表一個類型。第一個字元代表函數的傳回類型,其餘字元則代表參數類型。
'v'
:void 類型
'i'
:32 位元整數類型
'j'
:64 位元整數類型(請參閱下方註解)
'f'
:32 位元浮點數類型
'd'
:64 位元浮點數類型
'p'
:32 位元或 64 位元指標 (MEMORY64)
例如,如果你加入一個採用整數且不傳回任何內容的函數,則簽名為 'vi'
。
當使用 'j'
時,將會有多種方式將參數值傳遞給 JavaScript。預設情況下,該值將會被當作單一 BigInt 或一對 JavaScript 數字 (double) 傳遞,具體取決於是否啟用 WASM_BIGINT
設定。此外,如果你只需要 53 位元的精確度,你可以新增 __i53abi
修飾符,這將會忽略高位元,並且該值將會以單一 JavaScript 數字 (double) 接收。它不能與 addFunction
一起使用。以下是一個函式庫函數的範例,該函數使用以 53 位元 (double) 傳遞的 64 位元值設定檔案大小,並傳回整數錯誤碼
extern "C" int _set_file_size(int handle, uint64_t size);
_set_file_size__i53abi: true, // Handle 64-bit
_set_file_size__sig: 'iij', // Function signature
_set_file_size: function(handle, size) { ... return error; }
在連結時使用 -sWASM_BIGINT
是在函式庫中處理 64 位元類型的另一種方法。可能需要在 JavaScript 端使用 `Number()`
來將其轉換為可用的值。請參閱 設定參考。
你可以使用 getValue(ptr, type)
和 setValue(ptr, value, type)
來存取記憶體。第一個引數是指標(表示記憶體位址的數字)。type
必須是 LLVM IR 類型,即 i8
、i16
、i32
、i64
、float
、double
或指標類型,例如 i8*
(或只是 *
)。
在測試中使用了這些函數的範例 — 請參閱 test/core/test_utf.in 和 test/test_core.py。
您也可以透過操作代表記憶體的陣列「直接」存取記憶體。除非您確定知道自己在做什麼,並且需要比 getValue()
和 setValue()
更快的速度,否則不建議這樣做。
一個可能需要這樣做的情況是,如果您想從 JavaScript 導入大量資料,以供編譯後的程式碼處理。例如,以下程式碼會配置一個緩衝區、複製一些資料進去、呼叫一個 C 函數來處理資料,最後釋放該緩衝區。
var buf = Module._malloc(myTypedArray.length*myTypedArray.BYTES_PER_ELEMENT);
Module.HEAPU8.set(myTypedArray, buf);
Module.ccall('my_function', 'number', ['number'], [buf]);
Module._free(buf);
這裡的 my_function
是一個 C 函數,它接收一個整數參數(或者一個指標,對我們來說它們都只是 32 位元的整數)並回傳一個整數。這可能是像 int my_function(char *buf)
這樣的函數。
當基於 Wasm 的記憶體允許透過使用 -sALLOW_MEMORY_GROWTH
編譯來 **擴增** 時,將配置的記憶體匯出到 JavaScript 的相反情況可能會很棘手。增加記憶體大小會變更到新的緩衝區,而現有的陣列視圖基本上會失效,因此您不能簡單地這樣做
function func() {
let someView = HEAPU8.subarray(x, y);
compute(someView);
// This may grow memory, which would invalidate all views.
maybeGrow();
// If we grew, this use of an invalidated view will fail. Failure in this
// case will return undefined, the same as reading out of bounds from a
// typed array. If the operation were someView.subarray(), however, then it
// would throw an error.
return someView[z];
}
Emscripten 會為您刷新像 HEAPU8
這樣的標準視圖,您可以使用這些視圖來刷新您自己的視圖
function func() {
let someView = HEAPU8.subarray(x, y);
compute(someView);
// This may grow memory, which would invalidate all views.
maybeGrow();
// Create a new, fresh view after the possible growth.
someView = HEAPU8.subarray(x, y);
return someView[z];
}
另一個避免此類問題的方法是在適當的情況下複製資料。
Module
是一個全域 JavaScript 物件,其屬性會在 Emscripten 產生的程式碼執行時的各個點被呼叫。
開發人員提供 Module
的實作,以控制如何顯示來自 Emscripten 的通知、在主迴圈執行前載入哪些檔案,以及其他一些行為。有關更多資訊,請參閱 Module 物件。
有時,編譯後的程式碼需要存取環境變數(例如,在 C 語言中,透過呼叫 getenv()
函數)。Emscripten 產生的 JavaScript 無法直接存取電腦的環境變數,因此提供了一個「虛擬化」的環境。
JavaScript 物件 ENV
包含虛擬化的環境變數,您可以透過修改它來將變數傳遞給編譯後的程式碼。必須注意確保 ENV
變數已由 Emscripten 初始化後再進行修改 — 使用 Module.preRun
是一種方便的方式。
例如,若要將環境變數 MY_FILE_ROOT
設定為 "/usr/lib/test/"
,您可以將以下 JavaScript 新增到您的 Module
設定程式碼
Module.preRun = () => {ENV.MY_FILE_ROOT = "/usr/lib/test"};
請注意,如果您沒有設定自己的值,Emscripten 會在您設定 ENV
後,為某些環境變數(例如 LANG)設定預設值。如果您希望這些變數保持未設定狀態,您可以將它們的值明確設定為 undefined。例如
Module.preRun = () => {ENV.LANG = undefined};
用於呼叫編譯後的 C 函數的 JavaScript 方法效率很高,但不能與名稱修飾的 C++ 函數一起使用。
WebIDL Binder 和 Embind 會在 C++ 和 JavaScript 之間建立綁定,允許從 JavaScript 以自然的方式使用 C++ 程式碼實體。Embind 還支援從 C++ 程式碼呼叫 JavaScript 程式碼。
Embind 可以綁定幾乎任何 C++ 程式碼,包括複雜的 C++ 結構(例如 shared_ptr
和 unique_ptr
)。WebIDL Binder 支援可以在 WebIDL 中表達的 C++ 型別。雖然此子集比 Embind 支援的範圍小,但對於大多數用例來說已綽綽有餘 — 使用 binder 移植的專案範例包括 Box2D 和 Bullet 物理引擎。
這兩種工具都允許以類似的方式從 JavaScript 使用對應的項目。但是,它們在不同的層級運作,並使用非常不同的方法來定義綁定
Embind 在 C/C++ 檔案中宣告綁定。
WebIDL-Binder 在單獨的檔案中宣告綁定。此檔案會透過 binder 工具執行,以建立「膠合」程式碼,然後與專案一起編譯。
注意
沒有強有力的證據表明一種工具在效能方面比另一種「更好」(不存在比較基準),並且這兩種工具都已在許多專案中成功使用。選擇其中一種工具通常基於哪一種最適合專案及其建置系統。
Emnapi 是一個非官方的 Node-API 實作,可用於 Emscripten。如果您想將現有的 Node-API 附加元件移植到 WebAssembly,或將相同的綁定程式碼編譯為 Node.js 原生附加元件和 WebAssembly,您可以試試看。有關更多詳細資訊,請參閱 Emnapi 文件。