MDN’s new design is in Beta! A sneak peek: https://blog.mozilla.org/opendesign/mdns-new-design-beta/

C/C++からWebAssemblyにコンパイルする

C / C ++のような言語でコードを書いたら、Emscripten のようなツールを使って WebAssembly にコンパイルすることができます。 どのように動作するかを見てみましょう。

Emscripten の環境設定

まず、必要な開発環境をセットアップしましょう。

準備

事前にコンピュータにインストールする必要があるものがいくつかあります。はじめに確認してください:

  • Git — Linux と OS X では最初から存在します。Windows では Git for Windows installer からダウンロードしてください。
  • CMake — Linux と OS X では apt-get や brew などのパッケージマネーを使用してインストールして、依存関係とパスを正しく設定します。Windows では CMake installer を使用してください。
  • Host system compiler — Linux では GCC、OS X では Xcode 、Windows では Visual Studio Community 2015 with Update 3 以上 をインストールしてください。 最近リリースされた VS 2017 では動作しません。Emscriptenではまだサポートされていません。
  • Python 2.7.x — Linux と OS X では、殆どの場合、最初から提供されています。手順については、ビギナーズガイド を参照してください。Windowsでは Python homepage からインストーラをダウンロードしてください。

注: Windows では pywin32 が必要になるかもしれません。インストール中のエラーを減らすために管理者権限でコマンドプロンプトからインストーラを実行してください。

Emscripten のコンパイル

次に、Emscriptenをソースからコンパイルするための準備をします。Emscripten SDKを使用してコンパイルの自動化をするために、次のコマンドを実行してください (Emscriptenを保存する親ディレクトリ内で):

git clone https://github.com/juj/emsdk.git
cd emsdk

# on Linux or Mac OS X
./emsdk install --build=Release sdk-incoming-64bit binaryen-master-64bit
./emsdk activate --global --build=Release sdk-incoming-64bit binaryen-master-64bit

# on Windows
emsdk install --build=Release sdk-incoming-64bit binaryen-master-64bit
emsdk activate --global --build=Release sdk-incoming-64bit binaryen-master-64bit

インストールにはしばらく時間がかかるので、お茶でも飲んでください。インストールステップでは環境変数など、Emscripten を実行するのに必要なものの全てを設定します。

: --global フラグを指定すると必要な PATH 変数をグローバルに設定されますので、現在開いている、後で開く全てのターミナルやコマンドプロンプトで有効になります。現在開いているウィンドウだけで Emscripten をアクティブ化したい場合、このフラグを省略してください。

: 最新の Emscripten コードを pull してきて (emsdk ディレクトリ内で git pull を実行する) 、使用する度に install と activate コマンドを再実行して、最新の機能がインストールされていることを確認すると良いでしょう。

C の例を asm.js/wasm にコンパイルするために、 emsdk ディレクトリ内で次のコマンドを入力し、 Emscripten コンパイラ環境を導入しましょう:

# on Linux or Mac OS X
source ./emsdk_env.sh

# on Windows
emsdk_env.bat

例をコンパイルする

環境を設定した後は、C の例を Emscripten にコンパイルする方法を見てみましょう。 Emscripten でコンパイルするときにはいくつかのオプションがありますが、この記事でカバーする主な2つのシナリオは次のとおりです:

  • wasm にコンパイルし、コードを実行するための HTML とウェブ環境上で wasm を実行するための全ての JavaScript グルーコードを生成する。
  • wasm にコンパイルと JavaScript の生成だけ行う。

2つについて見てみましょう。

HTML と JavaScript を生成する

最も簡単なケースを見てみましょう。コードを WebAssembly としてブラウザで実行するための全てを Emscripten で生成するようにします。

  1. まずはコンパイルするための例を用意します。以下のCの例をコピーして hello.c としてローカルドライブの新しいディレクトリに保存してください:
    #include <stdio.h>
    
    int main(int argc, char ** argv) {
      printf("Hello World\n");
    }
  2. Emscripten コンパイラ環境を導入したターミナルウィンドウを使用して、hello.c ファイルと同じディレクトリに移動して、次のコマンドを実行します:
    emcc hello.c -s WASM=1 -o hello.html

このコマンドで渡されたオプションは次のとおりです:

  • -s WASM=1 — 出力を wasm に指定します。指定しない場合、Emscripten はデフォルトでは asm.js として出力します。
  • -o hello.html — コードを実行するための HTML ページを指定します。wasm モジュールとそれをウェブ環境で使用できるようにコンパイル、インスタンス化するための JavaScript グルーコードも出力に含まれます。

この時点でソースディレクトリに以下のファイルが出力されているはずです:

  • バイナリの wasm モジュールコード (hello.wasm)
  • ネイティブの C の関数と JavaScript/wasm の間で変換を行う JavaScript ファイル (hello.js)
  • wasm コードをロード、コンパイル、インスタンス化し、ブラウザに出力するための HTML ファイル (hello.html)

例を実行する

WebAssembly をサポートしているブラウザで hello.html をロードするだけです。WebAssembly は Firefox 52+ と Chrome 57+/最新の Opera でデフォルトで有効になっています (Firefox 47+では about:config で javascript.options.wasm flag を有効にすることで、Chrome (51+) と Opera (38+) では chrome://flags に飛んで Experimental WebAssembly フラグを有効にすることで wasm コードを実行することができます) 。

全てが計画通りに機能していれば、ウェブページ上の Emscripten コンソールに "Hello world" の出力が表示されるはずです。おめでとうございます、ようやくCを WebAssembly にコンパイルしてブラウザで実行することができました!

カスタム HTML テンプレートを使う

場合によっては、カスタム HTML テンプレートを使用することもできます。 どうやってできるかを見てみましょう。

  1. まず、次の C のコードを hello2.c として新しいディレクトリに保存します:

    #include <stdio.h>
    
    int main(int argc, char ** argv) {
        printf("Hello World\n");
    
    }
  2. shell_minimal.html をあなたの emsdk レポジトリから探します。先程作成した新しいディレクトリに html_template というサブディレクトリを作って、そこにコピーします。

  3. 新しいディレクトリに移動して (Emscripten コンパイラ環境があるターミナルウィンドウで) 、次のコマンドを実行します:

    emcc -o hello2.html hello2.c -O3 -s WASM=1 --shell-file html_template/shell_minimal.html

    今回渡したオプションは少しだけ異なります:

    • -o hello2.html と指定したことで、今回コンパイラは JavaScript グルーコードと .html を出力します。
    • さらに --shell-file html_template/shell_minimal.html と指定しました — これは例を実行する HTML を生成するための、HTML テンプレートパスです。
  4. この例を実行してみましょう。上記のコマンドで hello2.html が生成されます。これは生成された wasm コードに対してロード、実行などを行うグルーコードを含むテンプレートと同じ内容を持ちます。ブラウザを開いて最後の例と同じ出力であることを確認してください。

: 例えば、 emcc -o hello2.js hello2.c -O3 -s WASM=1 のように -o フラグにHTMLファイルの代わりに .js ファイルを渡すことで JavaScript で出力することを指定できます。そのあとに、スクラッチで独自のカスタム HTML を作ることができます。しかし、これはおすすめできません — Emscripten はメモリ割り当て、メモリリークやその他の問題を扱うための JavaScript グルーコードが多数必要になります。これは提供されたテンプレートにすでに含まれています。全てをあなた自身で書くよりも簡単に使用できます。していること全てに習熟してきたら、必要に応じて独自のカスタマイズバージョンを作りましょう。

C で定義されたカスタム関数を呼び出す

C で定義された関数があって、それを JavaScript から呼び出したい場合、Emscripten の ccall() 関数と EMSCRIPTEN_KEEPALIVE 宣言 (対象の関数をエクスポートする関数リストに加えるものです (Why do functions in my C/C++ source code vanish when I compile to JavaScript, and/or I get No functions to process? を参照してください)) を使用します。これがどのように働くか見てみましょう。

  1. はじめに、次のコードを hello3.c として新しいディレクトリに保存します:

    #include <stdio.h>
    #include <emscripten/emscripten.h>
    
    int main(int argc, char ** argv) {
        printf("Hello World\n");
    }
    
    #ifdef __cplusplus
    extern "C" {
    #endif
    
    void EMSCRIPTEN_KEEPALIVE myFunction(int argc, char ** argv) {
      printf("MyFunction Called\n");
    }
    
    #ifdef __cplusplus
    }
    #endif

    デフォルトでは、Emscripten が生成したコードは常に main() を呼び出し、他のデッドコードは削除されます。関数名の前に EMSCRIPTEN_KEEPALIVE を置くことによって、これが起こらなくなります。また、EMSCRIPTEN_KEEPALIVE を使用するために emscripten.h をインポートする必要があります。

    : #ifdef ブロックを加えたことによって、C++ のコードからこの例をインクルードしようとしても動作するでしょう。 C と C++ の間でのマングリング規則によって、他の場合では壊れることもありますが、ここでは C++ を使用している場合に、外部の C の関数として扱うように設定しています。

  2. 便宜上、この新しいディレクトリに html_template/shell_minimal.html (もちろん、このファイルはあなたの実際の開発環境に置きます)を加えます。

  3. さて、再びコンパイル手順を実行しましょう。あなたの最新のディレクトリの中 (そして、Emscripten コンパイラ環境の入っているターミナルウィンドウ) で、このように C のコードをコンパイルします:

    emcc -o hello3.html hello3.c -O3 -s WASM=1 --shell-file html_template/shell_minimal.html
  4. 例をブラウザでロードしたら、前と同じものが見れるでしょう。

  5. JavaScript から新しい myFunction() 関数を呼び出す必要があります。まずは、 以下のような<button> を最初の <script type='text/javascript'> タグの上に加えましょう。

    <button class="mybutton">Run myFunction</button>
  6. そして、<script> 要素内の最後に次のコードを追加します ( </script> タグの直前):

    document.querySelector('.mybutton').addEventListener('click', function(){
      alert('check console');
      var result = Module.ccall('myFunction', // name of C function 
                                 null, // return type
                                 null, // argument types
                                 null); // arguments
    });

これはエクスポートされた関数をどのようにして ccall() を使用して呼び出すかを示しています。

関連情報

ドキュメントのタグと貢献者

 このページの貢献者: syu_kato
 最終更新者: syu_kato,