0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Angularで作ったアプリからWebAssemblyで実装した関数を呼び出す

Posted at

1. 始めに

タイトルの通り、Angularで実装した処理からWebAssemblyで実装した関数を呼び出してみました。
AngularはTypeScriptでの開発が推奨されているため、WebAssemblyのホームページに記載されているサンプルコードをそのままコピペしただけでは動かず、ちょっと細工が必要でした。
今回はその一連の作業をまとめてみました。

2. Angularのサンプルアプリを作成

まずはAngularの環境を構築します。Angular CLIのインストールは既に完了しているものとして、コマンドラインで以下を実行してワークスペースを作成します。

ng new Application

コマンドラインでApplicationフォルダに移動し、以下のコマンドを実行してアプリが起動することを確認します。この「Hello, Application」と書かれている部分を、WebAssemblyで実装した関数から取得した値で書き換えます。

ng serve --open

image.png

ちなみに、Application/src/app/app.component.tsを見てみると以下のようになっています。

app.component.ts
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';

@Component({
  selector: 'app-root',
  imports: [RouterOutlet],
  templateUrl: './app.component.html',
  styleUrl: './app.component.css'
})
export class AppComponent {
  title = 'Application';
}

3. WebAssemblyのバイナリファイル作成

WebAssemblyの技術を利用する際はwasmと呼ばれるバイナリファイルを作成し、それを読み込んでから関数の呼び出しを行います。このwasmの作成方法は色々あるのですが、一番よく知られているであろう、Emscriptenを使ってC++のコードから作成していきます。

3.1 ビルド環境構築

公式サイトを読んで、コマンドラインでセットアップを行います。

3.2 WASM作成の元となるソースコードを準備

C++で書いた、以下のソースファイルを準備します。

test.cpp
#include <emscripten/emscripten.h>

int main() {
	return 0;
}

extern "C" {

EMSCRIPTEN_KEEPALIVE
int add(int x, int y) {
	return x + y;
}

}

ここで、以下の点に注意する必要があります。

  • main関数は必ず作成します
  • 呼び出す関数の前に、EMSCRIPTEN_KEEPALIVEを書いておきます
  • 呼び出す関数にはCリンケージを指定します。こうしないとC++の機能で名前マングリングが行われて、TypeScriptのソースコードでアクセスするときに関数名(今回の場合は「add」という文字列)から関数オブジェクトを取得できません

呼び出す関数のexport方法については、こちらの記事を参考にしました。
Emscriptenを使ったC/C++の関数のエキスポート方法まとめ

3.3 Emscriptenを使ってwasmへコンパイル

コマンドラインから以下を実行します。その結果、test.cppと同じフォルダにtest.wasmが生成されます。このtest.wasmを読み込み、TypeScriptからadd関数を呼び出します。
なお、EmscriptenではラッパーとなるJavaScriptファイルも同時に生成することができますが、言語やコンパイラによってはラッパーを生成しないものがあると思いますので、ラッパーを使わず直接wasmを参照した実装にします。

em++ test.cpp -o test.wasm -sWASM=1

オプションの指定については、こちらの記事を参考にしました。
Emscriptenを使ってブラウザでWebAssemblyを動かす

4. AngularアプリからWebAssemblyの関数を呼び出す

ここからが本番です。

4.1 wasmファイルを配置

生成したwasmファイルをAngularアプリが読み込める場所に配置する必要があるのですが、最初に「ng new Application」を実行してフォルダを作成した時にApplication/publicフォルダが生成されており、angular.jsonではここがアセットとして指定されています。他に適当な場所が思いつかなかったので、ここにtest.wasmを配置します。

4.2 wasm読み込みクラスを作成

まずはWebAssemblyのバイナリ読み込み用にクラスを作成し、このファイルをApplication/src/wasmに配置します(wasmフォルダは新規に作成しました)。

wasm.ts
type ListenerFunction = () => void;

export class WASM {
    // WebAssemblyモジュールの読み込み結果
    private wasm: WebAssembly.WebAssemblyInstantiatedSource | undefined = undefined;
    // モジュール読み込み完了を通知するリスナー
    private listenerList: ListenerFunction[] = [];

    // コンストラクタ
    constructor() {
        const importObject = {
            wasi_snapshot_preview1: {
                proc_exit: () => console.log("proc_exit")
            },
        };
        
        WebAssembly.instantiateStreaming(fetch('test.wasm'), importObject)
            .then(obj => {
                this.wasm = obj;
                this.listenerList.forEach(func => func());
                this.listenerList = [];
            });
    }

    // WebAssemblyのモジュール読み込みが完了しているかどうか
    public isReady(): boolean {
        return this.wasm !== undefined;
    }

    // WebAssemblyのモジュール読み込みが完了時に呼び出すリスナーを登録
    public addListener(func: ListenerFunction): void {
        this.listenerList.push(func);
    }

    // WebAssemblyで実装した関数
    public add(x: number, y: number): number {
        let result: number = 0;
        let func = this.wasm?.instance.exports['add'];
        if (typeof(func) === 'function') {
            result = func(x, y);
        }
        return result;
    }
}

注意点は以下の通りです。

  • instantiateStreaming関数の第2引数は必須です。渡さないとランタイムで読み込み失敗のエラーが出ます。また、importObject.wasi_snapshot_preview1.proc_exitも必須です
  • this.wasm?.instance.exports['add']で関数オブジェクトを取得しています
  • WASMの読み込みは非同期で実行するため、読み込みが完了する前にadd関数が呼ばれると正常に動作しません。そのため読み込みが完了しているか判定する関数と、読み込み完了を通知するリスナーを登録する仕組みを作成しました

4.3 WebAssemblyの関数を呼び出す

上記で作成したwasm.tsを使用し、WebAssemblyの関数を呼び出します。

app.component.ts
import { WASM } from '../wasm/wasm'
// 省略
export class AppComponent {
  title: string;
  wasm: WASM;
  
  constructor() {
    this.title = 'Application';
    this.wasm = new WASM();
    this.wasm.addListener(() => {
      // インスタンス生成直後はまだ読み込みが完了していないので、
      // 完了後に呼び出されるリスナーを登録する
      this.title = '1 + 2 = ' + this.wasm.add(1, 2);
    });
  }
}

4.4 アプリ実行

この状態でアプリを実行すると、以下のように表示されます。
「Hello, Application」が「Hello, 1 + 2 = 3」に変わっていることが分かります。
image.png

最後に

というわけで、AngularからWebAssemblyで実装した関数を呼び出してみました。
どちらもモダンなWeb技術なので、使いこなせるようになっていきたいです。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?