コードの操作

Emscripten は、JavaScript とコンパイルされた C または C++ の間で接続および相互作用するための多数の方法を提供します。

この記事では、上記の方法のそれぞれについて説明し、詳細情報へのリンクを提供します。

注意

コンパイルされたコードがブラウザ環境とどのように相互作用するかについては、Emscripten ランタイム環境 を参照してください。ファイルシステム関連の操作については、ファイルシステムの概要 を参照してください。

注意

コードを呼び出す前に、最適化とビルド設定によっては、ランタイム環境がメモリ初期化ファイルのロード、ファイルのプリロード、またはその他の非同期操作を行う必要がある場合があります。FAQ の ページが完全にロードされ、コンパイルされた関数を呼び出すのが安全なタイミングを判断するにはどうすればよいですか? を参照してください。

ccall/cwrap を使用して JavaScript からコンパイルされた C 関数を呼び出す

JavaScript からコンパイルされた C 関数を呼び出す最も簡単な方法は、ccall() または cwrap() を使用することです。

ccall() は、指定されたパラメータでコンパイルされた C 関数を呼び出し、結果を返しますが、cwrap() はコンパイルされた C 関数を「ラップ」し、通常どおり呼び出すことができる JavaScript 関数を返します。したがって、コンパイルされた関数を何度も呼び出す予定がある場合は、cwrap() の方が便利です。

以下に示す test/hello_function.cpp ファイルを検討してください。コンパイルされる int_sqrt() 関数は、C++ の名前マングリングを防止するために extern "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 にコンパイルする場合は、2 番目のコマンドでそのオプションが必要になります。この例のようにすべてを一緒に実行する場合(ソースから直接 JavaScript)、これはもちろん機能します。

コンパイル後、次の JavaScript を使用して cwrap() でこの関数を呼び出すことができます。

int_sqrt = Module.cwrap('int_sqrt', 'number', ['number'])
int_sqrt(12)
int_sqrt(28)

最初のパラメータはラップされる関数の名前、2 番目は関数の戻り値の型(存在しない場合は JavaScript の null 値)、3 番目はパラメータの型の配列(パラメータがない場合は省略できます)です。型は、「number」(C の整数、浮動小数点数、または一般的なポインタに対応する JavaScript 数値の場合)、「string」(文字列を表す C の char* に対応する JavaScript 文字列の場合)、または「array」(C 配列に対応する JavaScript 配列または型付き配列の場合)です。型付き配列の場合、Uint8Array または Int8Array である必要があります。

生成されたページ function.html を Web ブラウザで開いて、自分でこれを実行できます(main() がないため、ページロード時には何も起こりません)。JavaScript 環境を開き(Firefox では Control-Shift-K、Chrome では Control-Shift-J)、上記のコマンドを 3 つの別々のコマンドとして入力し、それぞれを入力後に Enter キーを押します。結果は 35 になるはずです。これは、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 にも追加する必要があります。後者はメソッドを実際にビルドに含めることを強制するためです。

  • コンパイラは、コードサイズを改善するために、使用されていないと判断したコードを削除します。--pre-js--post-js のようなコンパイラが見る場所で ccall を使用する場合、それは問題なく動作します。このチュートリアルで行ったように、HTMLの別のスクリプトタグやJSコンソールのように、コンパイラが見ていない場所で使用する場合、最適化と縮小のため、-sEXPORTED_RUNTIME_METHODS=ccall,cwrap のように EXPORTED_RUNTIME_METHODS を使用してランタイムから ccall をエクスポートし、Module (すべてエクスポートされたものが含まれており、縮小や最適化の影響を受けない安全な方法で)で呼び出す必要があります。

NodeJSからC/C++で記述されたAPIとのやり取り

いくつかのプロシージャを公開する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

ライブラリを require し、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 を使用するため、前述のようにエクスポートされたランタイムメソッドに追加する必要があることに注意してください。)

コンパイルされたC/C++コードをJavaScriptから「直接」呼び出す

元のソースの関数はJavaScript関数になるため、自分で型変換を行うと、それらを直接呼び出すことができます。これは、ccall() または cwrap() を使用するよりも高速ですが、少し複雑になります。

メソッドを直接呼び出すには、生成されたコードに表示される完全な名前を使用する必要があります。これは元のC関数と同じになりますが、先頭に _ が付いています。

注意

ccall() または cwrap() を使用する場合、関数呼び出しに _ をプレフィックスとして付ける必要はありません。Cの名前を使用するだけです。

関数に渡すパラメータと関数から受け取るパラメータは、プリミティブ値である必要があります。

  • 整数と浮動小数点数はそのまま渡すことができます。

  • ポインタも、生成されたコードでは単なる整数であるため、そのまま渡すことができます。

  • JavaScript文字列 someString は、ptr = stringToNewUTF8(someString) を使用して char * に変換できます。

    注意

    ポインタへの変換はメモリを割り当てるため、後で free(ptr) (JavaScript側では _free)を呼び出して解放する必要があります。

  • C/C++から受け取った char * は、UTF8ToString() を使用してJavaScript文字列に変換できます。

    preamble.js には、文字列とエンコーディングを変換するための他の便利な関数があります。

  • その他の値は、emscripten::val を使用して渡すことができます。as_handle および take_ownership メソッドの例を確認してください。

C/C++からJavaScriptを呼び出す

Emscriptenには、C/C++からJavaScriptを呼び出すための2つの主なアプローチがあります。emscripten_run_script() を使用してスクリプトを実行するか、「インラインJavaScript」を記述する方法です。

最も直接的ですが、わずかに遅い方法は、emscripten_run_script() を使用することです。これは事実上、eval() を使用してC/C++から指定されたJavaScriptコードを実行します。例えば、ブラウザの alert() 関数をテキスト「hi」で呼び出すには、次のJavaScriptを呼び出します。

emscripten_run_script("alert('hi')");

注意

alert 関数はブラウザには存在しますが、node や他のJavaScriptシェルには存在しません。より一般的な代替手段は、console.log を呼び出すことです。

CからJavaScriptを呼び出すより高速な方法は、EM_JS() または EM_ASM() (および関連するマクロ)を使用して、「インラインJavaScript」を記述することです。

EM_JSは、Cファイル内からJavaScript関数を宣言するために使用されます。「alert」の例は、EM_JSを使用して次のように記述できます。

#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は、インラインアセンブリコードと同様の方法で使用されます。「alert」の例は、インラインJavaScriptを使用して次のように記述できます。

#include <emscripten.h>

int main() {
  EM_ASM(
    alert('hello world!');
    throw 'all done';
  );
  return 0;
}

コンパイルして実行すると、Emscriptenは、生成されたコードに直接表示されたかのように、JavaScriptの2行を実行します。その結果、アラートの後に例外が発生します。(ただし、内部ではEmscriptenは、この場合でも関数呼び出しを実行することに注意してください。これにはある程度のオーバーヘッドがあります。)

例えば、EM_ASM 内でCから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_INTEM_ASM_DOUBLE または EM_ASM_PTR を使用して、戻り値が intdouble、またはポインタ型かどうかを指定する必要があります。(EM_ASM_PTR は、MEMORY64 が使用されていない限り 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']。または、clang-format をオフにするには、EM_ASM セクションの前に // clang-format off を、後に // clang-format on を記述してください。

JavaScript で C API を実装する

JavaScript で C API を実装することは可能です! これは、SDL1 や OpenGL など、Emscripten の多くのライブラリで使用されているアプローチです。

これを使用して、C/C++ から呼び出す独自の API を記述できます。これを行うには、インターフェースを定義し、API 内のメソッドを外部シンボルとしてマークするために extern で装飾します。次に、library.js (デフォルト) に定義を追加することで、JavaScript でシンボルを実装します。C コードをコンパイルするとき、コンパイラは関連する外部シンボルについて JavaScript ライブラリを検索します。

デフォルトでは、実装は **library.js** に追加されます(ここには Emscripten の *libc* の一部があります)。JavaScript の実装を独自のライブラリファイルに配置し、emcc オプション --js-library を使用して追加できます。JavaScript ライブラリファイル内で使用する必要がある構文を含め、完全な動作例については、 **test/test_other.py** の test_js_libraries を参照してください。

簡単な例として、次のような C コードがある場合を考えてみましょう

extern void my_js(void);

int main() {
  my_js();
  return 1;
}

注意

C++ を使用する場合、C++ の名前マングリングを防ぐために、extern void my_js();extern "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 の制限

JavaScript に慣れていない場合、たとえばあなたが C/C++ プログラマーで emscripten を使用しているだけの場合、以下の問題は発生しないでしょう。しかし、経験豊富な JavaScript プログラマーである場合は、emscripten ライブラリファイルでは、いくつかの一般的な JavaScript のプラクティスを特定の方法で使用できないことを認識する必要があります。

スペースを節約するために、デフォルトでは、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 ライブラリ専用です。**library_*.js** という名前形式の /src の例を参照してください。

  • autoAddDeps(myLibrary, name) を使用して、すべてのメソッドの依存関係を追加できます。ここで、myLibrary はすべてのメソッドを含むオブジェクトであり、name はそれらすべてが依存するものです。これは、実装されたすべてのメソッドがヘルパーメソッドを含む JavaScript シングルトンを使用する場合に役立ちます。例については、library_webgl.js を参照してください。

  • addToLibrary に渡されるキーは、_ がプレフィックスとして付加された関数を生成します。言い換えれば、my_func: function() {},function _my_func() {} になります。これは、emscripten のすべての C メソッドに _ プレフィックスが付加されているためです。$ で始まるキーは、$ が削除され、アンダースコアは追加されません。

C から関数ポインタとして JavaScript 関数を呼び出す

addFunction を使用して、関数ポインタを表す整数値を返すことができます。その整数を C コードに渡すと、C コードはその値を関数ポインタとして呼び出すことができ、addFunction に送信した JavaScript 関数が呼び出されます。

例については、test_add_function in test/test_core.py を参照してください。

テーブルに新しい関数を追加できるようにするには、-sALLOW_TABLE_GROWTH を使用してビルドする必要があります。そうしないと、デフォルトでテーブルは固定サイズになります。

JavaScript 関数で addFunction を使用する場合は、追加の 2 番目の引数、つまり後述する Wasm 関数シグネチャ文字列を提供する必要があります。例については、test/interop/test_add_function_post.js を参照してください。

関数シグネチャ

LLVM Wasm バックエンドでは、addFunction および JavaScript ライブラリを使用するときに Wasm 関数シグネチャ文字列が必要です。シグネチャ文字列内の各文字は、型を表します。最初の文字は関数の戻り値の型を表し、残りの文字はパラメーターの型を表します。

  • 'v': void 型

  • 'i': 32 ビット整数型

  • 'j': 64 ビット整数型 (下記の注を参照)

  • 'f': 32 ビット浮動小数点型

  • 'd': 64 ビット浮動小数点型

  • 'p': 32 ビットまたは 64 ビット ポインタ (MEMORY64)

たとえば、整数を受け取り、何も返さない関数を追加する場合、シグネチャは 'vi' です。

'j' を使用する場合、パラメーター値が JavaScript に渡される方法がいくつかあります。デフォルトでは、WASM_BIGINT 設定が有効になっているかどうかに応じて、値は単一の BigInt または JavaScript 数値 (double) のペアとして渡されます。さらに、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()` を使用して、使用可能な値に変換する必要がある場合があります。詳細については、設定リファレンスを参照してください。

JavaScript からメモリにアクセスする

getValue(ptr, type) および setValue(ptr, value, type) を使用してメモリにアクセスできます。最初の引数は、ポインタ (メモリアドレスを表す数値) です。type は、i8i16i32i64floatdouble、または i8* (または単に *) のようなポインタ型のいずれかである LLVM IR 型である必要があります。

これらの関数の使用例はテストにあります。 test/core/test_utf.intest/test_core.py を参照してください。

注意

これは、ccall()cwrap() よりも低レベルの操作です。特定の型(整数など)が使用されていることを意識する必要があります。

メモリを表す配列を操作することで、メモリに「直接」アクセスすることもできます。これは、自分が何をしているかを確実に理解しており、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 は、単一の整数パラメータ(またはポインタ。どちらも32ビット整数です)を受け取り、整数を返す C 関数です。これは、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];
}

このような問題を回避するもう1つのオプションは、データが意味をなす場合にデータをコピーすることです。

実行動作に影響を与える

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 のバインディング — WebIDL バインダーと Embind

コンパイルされた C 関数を呼び出すための JavaScript メソッドは効率的ですが、名前がマングルされた C++ 関数では使用できません。

WebIDL バインダーEmbind は C++ と JavaScript の間のバインディングを作成し、C++ コードエンティティを JavaScript から自然な方法で使用できるようにします。Embind は、C++ から JavaScript コードを呼び出すこともサポートしています。

Embind は、洗練された C++ 構成要素(例:shared_ptr および unique_ptr)を含む、ほぼすべての C++ コードをバインドできます。WebIDL バインダー は、WebIDL で表現できる C++ 型をサポートしています。このサブセットは Embind でサポートされているよりも小さいですが、ほとんどのユースケースには十分です。バインダーを使用して移植されたプロジェクトの例には、Box2DBullet 物理エンジンが含まれます。

どちらのツールも、マッピングされたアイテムを JavaScript から同様の方法で使用できるようにします。ただし、それらは異なるレベルで動作し、バインディングを定義するために非常に異なるアプローチを使用します。

  • Embind は、C/C++ ファイル内でバインディングを宣言します。

  • WebIDL-Binder は、別のファイルでバインディングを宣言します。これは、バインダーツールを通して実行され、プロジェクトでコンパイルされる「グルー」コードを作成します。

注意

パフォーマンスの観点から、一方のツールが他方よりも「優れている」という強力な証拠はありません(比較ベンチマークは存在しません)。どちらも多くのプロジェクトで正常に使用されています。どちらのツールを選択するかは通常、プロジェクトとそのビルドシステムに最も適しているものに基づいて決定されます。

C/C++ と JavaScript のバインディング - Node-API

Emnapi は、Emscripten で使用できる非公式の Node-API 実装です。既存の Node-API アドオンを WebAssembly に移植したり、同じバインディングコードを Node.js ネイティブアドオンと WebAssembly の両方にコンパイルしたい場合は、試してみてください。詳細については、Emnapi ドキュメント を参照してください。