3
4

More than 1 year has passed since last update.

Emscriptenを使ってブラウザでWebAssemblyを動かす

Posted at

はじめに

最近WebAssemblyがあついですね。
というわけでC言語からWebAssemblyを作成してHello WebAssemblyしてみようと思います。
コンパイラにはWebAssembly界隈で広く使われているEmscriptenを使用します。

本記事では、以下のステップで進めます。
使用する環境はWindows10となっています。

なお、以下の公式ページでもC言語からWebAssemblyを動かす手順が示されています。

1. 環境構築-Emscriptenのインストール

はじめにEmscriptenをインストールします。
Emscriptenは以下のページからダウンロードします。
パッケージマネージャーを利用したインストール方法が書かれていますが、gitレポジトリからクローンするだけでも使えるようになります。

今回は以下のgitレポジトリをクローンしたものを使用します。

クローンしたのち、こちらに記載の通り、クローンしたフォルダ内で以下のコマンドを実行します。

emsdk install latest
emsdk activate latest
emsdk_env.bat

特にエラーが出なければインストールは完了です。

2. C言語でサンプルプログラムを実装

続いてサンプルプログラムを作成します。
echo.cファイルを作成し、下記のように実装します。
なお、本記事ではブラウザでWebAssemblyを実行するまでの流れに注目していますので、プログラムの詳細は省きます。
処理内容としては、引数で与えられた文字列に対してHello, <文字列> !と表示するだけの簡単なものになっています。

echo.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

char* echo(char* ptr){
    int n;
    char* str;

    n = 10 + strlen(ptr);
    str = (char*)malloc(n*sizeof(char));
    memset(str, '\0', (n*sizeof(char)));

    strcpy(str, "Hello, ");
    strcat(str, ptr);
    strcat(str, " !");

    return str;
}

int main(int argc, char **argv) {
    char* ptr = echo("WebAssembly");
    printf("%s", ptr);
    free(ptr);
    return 0;
}

3. C言語からWebAssemblyへコンパイル

Emscriptenを利用する場合、emccコマンドでコンパイルします。
パッケージマネージャを利用せずにインストールした場合、emccコマンドを実行するプロンプトでemsdk_env.batを実行します。
Linuxの場合はemsdk_env.shとなります。

以下はemsdk_env.bat実行時の出力です。

D:\webassembly>d:\Tools\emsdk\emsdk_env.bat
Setting up EMSDK environment (suppress these messages with EMSDK_QUIET=1)
Adding directories to PATH:
PATH += D:\Tools\emsdk
PATH += D:\Tools\emsdk\upstream\emscripten
PATH += D:\Tools\emsdk\node\14.18.2_64bit\bin

Setting environment variables:
PATH = D:\Tools\emsdk;D:\Tools\emsdk\upstream\emscripten;D:\Tools\emsdk\node\14.18.2_64bit\bin;<以下略>
EMSDK = D:/Tools/emsdk
EM_CONFIG = D:\Tools\emsdk\.emscripten
EMSDK_NODE = D:\Tools\emsdk\node\14.18.2_64bit\bin\node.exe
EMSDK_PYTHON = D:\Tools\emsdk\python\3.9.2-nuget_64bit\python.exe
JAVA_HOME = D:\Tools\emsdk\java\8.152_64bit
Clearing existing environment variable: EMSDK_PY

それではここで、emccコマンドを用いでC言語のプログラムをWebAssemblyにコンパイルしてみます。
コマンドは以下の通りです。

D:\webassembly>emcc echo.c -o echo.html -sWASM=1 -sEXIT_RUNTIME=1

これにより以下のファイルが出力されます。

  • echo.html:emscriptenが出力するサンプルhtml
  • echo.js:html内からWebAssemblyを呼び出すためのjavascript
  • echo.wasm:WebAssemblyの実行ファイル

参考までに、emccコマンドでよく使いそうなオプション引数等を(参考) emccコマンドのオプションについてを示します。
読み込み時にmain関数を実行するかしないか、などもオプション引数でコントロールできます。

4. WebAssemblyをブラウザで実行

作成されたecho.htmlを使ってWebAssemblyを実行してみます。
ただし、単純にecho.htmlを開くだけではwasmファイルは実行されません。これはセキュリティ上の理由からで、ブラウザのCORSポリシーによりjavascriptやwebassemblyのファイルが読み込まれないためです。

ブラウザのCORSを無効にすることもできますが、ここでは真っ当にWebサーバを起動することにします。

Pyhtonを使うのであれば以下のコマンドで簡易的なhttpサーバーは起動できます。

python -m http.server 8000

起動したWebサーバへアクセスすると以下のようにコンパイルされた資材が確認できるかと思います。

list.PNG

ここでecho.htmlを選択すると、プログラムのmain関数が実行され。以下のような表示になります。
これでWebAssemblyが実行されていることが確認できました。

exec.PNG

5. デベロッパーツールでWebAssemblyの中身を見る

ブラウザで実行されているWebAssemblyについては、ブラウザのデベロッパーツールで確認することが出来ます。
Chromeの場合だと以下のように、「ソース」タブを開きecho.wasmを選択するとテキスト形式で実行ファイルの中身が表示されます。

developper.PNG

※ WebAssemblyを使用しているサイトやサービスについても同様に、デベロッパーツールでWebAssemblyのファイルを覗くことが出来ます。処理内容が丸裸になりますのでWebAssemblyを本番運用する際は注意しましょう。

(参考) emccコマンドのオプションについて

emccコマンドでよく使われそうなオプションを以下に示します。

オプション引数 説明
-o 出力ファイル名とファイルタイプ
-O0 最適化を実施しない(デフォルト)
-O1 簡単な最適化を実施。コンパイル時にLLVMの-O1オプションを適用。リンク時にいくつかのランタイムアサーションを含めない。
-O2 -O1よりも多くの最適化を行う。リンク時にjsの最適化も実施。
-O3 -O2よりも多くの最適化を行う。リリース時の推奨。
-Os -O3と同様だが、よりコードサイズを小さくするような最適化を行う。
-Oz -Osと同様だが、さらにコードサイズを小さくするような最適化を行う。
-g0 コードがデバッグできるように何もしない。
-g1 リンク時にjsのスペースを保持する。
-g2, --profiling リンク時に関数名を保持する。
-g3, -g コンパイル時にデバッグ情報やjsのスペース、関数名、LLVMデバッグ情報を保持する。
--emit-symbol-map リンク時に関数名とwasmの関数インデックスの対応を出力する。
-sOPTION[=VALUE] オプション

-sOPTIONで指定できるオプションは以下のようなものがあります。

オプション引数 説明
-sWASM 0:wasmではなくjsを出力する。1:wasmを出力する。2:wasmとjsの両方を出力する。wasmの実行に失敗したときはWsm2JSモードでjsが実行される。
-sEXPORTED_FUNCTIONS deadコードと見做されて削除されないようにexportする関数一覧。"_関数名"の形で記載する。指定しない際は"_main"が暗黙的に適用される。STANDALONE_WASMモードの際は"__start"が暗黙的に適用される。
-sEXPORT_ALL DCE(Dead Code Elimination)を実行せずにすべてのシンボルを保持する。
-sEXPORTED_RUNTIME_METHODS ランタイムからModuleへexportする関数名一覧。
-sINVOKE_RUN wasm読み込み時にmain関数を実行するか否か。falseに設定した場合でもModule.callMain()で実行できる。
-sWARN_ON_UNDEFINED_SYMBOLS jsで定義されていない関数があった際にwarningを出力する。
-sERROR_ON_UNDEFINED_SYMBOLS jsで定義されていない関数があった際にerrorを出力する。
-sEXIT_RUNTIME 0を指定した場合main関数終了後にランタイムを終了させない。
-sSTANDALONE_WASM JavaScript無しで実行できるwasmを出力する。実行にはwasiなどのAPIが必要。

なお、オプションに複数の値を設定する際は以下のような複数の書き方ができます
これらもリファレンスに記載されていますので詳細はこちらをご参照ください。

-sEXPORTED_FUNCTIONS=foo,bar
-sEXPORTED_FUNCTIONS="foo","bar"
-sEXPORTED_FUNCTIONS=["foo","bar"]
-sEXPORTED_FUNCTIONS=[foo,bar]
-sEXPORTED_FUNCTIONS="['liblib.so']"
-s"EXPORTED_FUNCTIONS=['liblib.so']"

emccコマンドのオプション引数の全量はこちらのページをご参照ください。

-sOPTIONで指定できるオプションはこちらに記載されています。

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4