12
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

WebAssemblyでJavaVMを動かす

Last updated at Posted at 2019-06-02

最近環境が整いつつあるWebAssemblyを触ってみます。
題材としてJavaを実行できる環境を作ってみたいと思います。そうです、Javaアプレットのような、ブラウザで動くJavaインタプリタ(JavaVM)です。

最初に、お断りしておきます。
Javaのバイトコードを解釈するJavaインタプリタではありますが、最低限のバイトコードしか実装していません。また、java/lang配下のほんの一部のクラスしか実装していないので、実用的には使い物にならないです。個人的に、Javaインタプリタの勉強のために作ってみたものです。
期待してこられた方にはすみません。

以下の通り進めていきます。

  • WebAssemblyコードを生成するコンパイラをインストールする。
  • プロジェクトフォルダを作成する
  • WebAssemblyに埋め込むJavaソースコードをコンパイルする。
  • JavaインタプリタのC言語ソースコードをコンパイルする。
  • Webに配置してWebAssemblyに埋め込んだJavaバイトコードの動作確認する。
  • WebAssemblyに埋め込まなかったJavaソースコードをコンパイルする。
  • WebAssemblyに埋め込まなかったJavaバイトコードの動作確認する。

JavaインタプリタのC言語ソースコードとして、「waba」を参考にさせていただきました。非常に勉強になりました。ありがとうございました。

wabasoft
 http://wabasoft.com/

WebAssemblyコンパイラには、「emscripten」を利用しました。
 https://emscripten.org/index.html

以下、続編です。
 WebAssemblyでJavaVMを動かす:補足

emscriptenのインストール

Windows10のWSL環境にインストールしてみました。
ですので、Python2.7を使うのですが、インストール済みでした。

> python --version
Python 2.7.15rc1
> git clone https://github.com/juj/emsdk.git
> cd emsdk
> ./emsdk install latest
> ./emsdk activate latest

これで、WebAssemblyコンパイラである「emcc」がインストールされました。
使うときには、パスを通す必要があるため、以下を実行します。

> source ./emsdk_env.sh --build=Release

#プロジェクトフォルダの作成

適当なフォルダを作って作業します。

C言語のソースファイル一式を同フォルダに配置します。

以下のGitHubに上げておきました。

 https://github.com/poruruba/javaemu

> git clone https://github.com/poruruba/javaemu.git
> cd javaemu

#WebAssemblyに埋め込むJavaソースコードをコンパイルする

Javaソースコードをコンパイルします。
Javaソースコードは2種類に分けて、配置の方法を変えます。
一つ目は、WebAssemblyに埋め込むJavaソースで、もう一つは、WebAssemblyに埋め込まないJavaソースです。
後者は、通常のユーザ作成のJavaソースコードです。一方、java/langなどは通常のユーザは利用するだけで、変更することのないシステムのJavaソースが前者です。また、インタプリタとの連携やC言語でしか処理できないネイティブ関数があるJavaクラスも前者になります。

ソースコードは、java_srcのフォルダ配下に作っていくことを前提としています。
コンパイルしたクラスファイルを置くフォルダを作ります。pre_classesとします。

> mkdir pre_classes

コンパイルは以下の通りです。

>javac -encoding UTF8 -d pre_classes -sourcepath java_src java_src/base/framework/*.java java_src/java/lang/*.java java_src/test/*.java

base/frameworkとjava/lang配下のJavaソースファイルをシステムのJavaソースとしています。java_src/test/*.java は、後でテスト用に使うものですので、テストが終わったら抜いてください。
これで、pre_classesフォルダ配下に、Javaクラスファイルができあがりました。

Windows上でOracle JDKを使いました。OpenJDKの場合、実行環境上のクラスファイルとバッティングしてしまって、排他する方法がわかりませんでした。。。

#C言語ソースコードをコンパイルする

さあ、コンパイルしましょう

> emcc *.c -s WASM=1 -o javaemu.js -s "EXTRA_EXPORTED_RUNTIME_METHODS=['ccall', 'UTF8ArrayToString', 'stringToUTF8Array']" --preload-file pre_classes

(参考)コンパイル時オプション
 https://emscripten.org/docs/tools_reference/emcc.html#emccdoc

-s WASM=1 はWASMファイルを生成することを示すおまじないです。

-o javaemu.js は、WASMファイル(javaemu.wasm)とユーティリティのJavascript(javaemu.js)とWebAssemblyに埋め込むJavaクラスファイルをまとめたファイル(javaemu.data)を生成するためのものです。
javaemuという名前は適当です。好きな名前にしてください。

-s EXTRA_EXPORTED_RUNTIME_METHODS は、javaemu.jsが提供する関数のうち、Webに配置するHTMLページのJavascriptから呼び出すことを許可する関数名です。ccallは必須ですが、それ以外の2つは有用なので設定しました。

--preload-file は、WebAssemblyに埋め込むファイルが置かれたフォルダ名を指定します。指定したフォルダ名配下のファイルが、javaemu.dataというファイルにまとめられます。

出来上がったファイルのうちWebの配置するのは、「javaemu.wasm」と「javaemu.js」と「javaemu.data」です。
javaemu.wasmが、まさしくC言語をコンパイルしたWebAssemblyファイルです。

WebページのJavascriptから呼び出される関数は、main.cに定義しています。

main.c
int EMSCRIPTEN_KEEPALIVE setInoutBuffer(const unsigned char *buffer, long bufferSize );
int EMSCRIPTEN_KEEPALIVE setRomImage(unsigned char *romImage, long romSize );
int EMSCRIPTEN_KEEPALIVE callStaticMain(char *className, char *param );

Webに配置してWebAssemblyに埋め込んだJavaバイトコードの動作確認する。

GitHubから落としてきたファイルのうちのhtmlをWebサーバにアップします。
動作確認のためのページも用意しておきました。
さきほど生成した3つのファイルも、htmlフォルダに配置します。

> cp javaemu.wasm javaemu.js javaemu.data html/

または

> cd html
> ln -s ../javaemu.wasm ../javaemu.js ../javaemu.data .
> cd ..

(HTMLのルートフォルダにおいてください。そうしないと、javaemu.jsがうまくwasmやdataファイルを読んでくれないです)

ありがたいことに、emscriptenには簡易Webサーバ機能があるので、それを使います。ポート番号は8080にしました。

emrun --serve_root html --no_browser --port 8080 .

それでは、ブラウザからアクセスしてみます。
ブラウザとして、Windows上のChromeで動かしてみました。

 http://localhost:8080

image.png

わかりやすいように、F12を押して開発者コンソールを表示しています。

Class Nameのこところに、テスト用に埋め込んだJavaクラス名を入力します。「test/TestFunc」です。

もととなったソースコードは以下の感じです。

TestFunc.java
package test;

import base.framework.System;
import base.framework.Convert;
import base.framework.Util;

public class TestFunc{
  public static void main( String[] args ){
  	try
  	{
  		System.println("Hello test/TestFunc");

  		if( args.length >= 1 )
  			System.println("args[0]=" + args[0]);
  		
  		String[] input = System.getInput(-1);
  		for( int i = 0 ; i < input.length ; i++ )
	  		System.println("params[" + i + "]=" + input[i]);

  		System.setOutput(new String[]{ "World", "こんばんは" });
  	}catch( Exception ex )
  	{
		System.print( ex.toString() );
  		ex.printStackTrace();
  	}
  }
}

Input ArgやInput Paramsにも適当な文字列をいれます。
さっそく「start wasm」ボタンを押下します。

以下の感じで、C言語で実装した関数を呼び出します。
WebAssemblyファイルをロードして実行するには、手間がかかるのですが、そこはjavaemu.jsがうまくやってくれています。

start.js
            this.output_return = Module.ccall('callStaticMain', // name of C function 
                                    "number", // return type
                                    ["string", "string"], // argument types
                                    [this.class_name, this.input_arg]); // arguments

image.png

すると、ブラウザのConsoleにごちゃごちゃデバッグ文字が表示された中に、以下のような文字列が表示されたかと思います。JavaインタプリタによるJavaクラスファイルの実行完了です。

Hello test/TestFunc
args[0]=ABCD
params[]=あいうえお
params[1]=かきくけこ

また、ページにも以下が表示されたかと思います。

Output Return 0
Output Params [ "World", "こんばんは" ]

WebAssemblyに埋め込まなかったJavaソースコードをコンパイルする。

今度は、いろいろなJavaソースを作成して動かしてみましょう。

java_src 配下に作っていきます。
今回は、test2フォルダ以下に作っていきます。

コンパイルは以下の通りです。

> mkdir classes
> javac -encoding UTF8 -d classes -sourcepath java_src -classpath pre_classes java_src/test2/*.java

クラスファイル格納用に、フォルダclassesを作っています。
pre_classesはWebAssemblyファイルに埋め込まれている前提なので、すでにあるクラスファイルとみなしてコンパイルします。

次に、作ったクラスファイルをJarファイルにします。

> jar cvf classes.jar -C classes .

これで、classesフォルダ配下のファイルが、classes.jarファイルにまとめられました。test2のクラスファイル以外のファイルも作られてましたが、実害はないので無視してもいいですし、Jarファイル対象から除いてもよいです。

「ファイルを選択」ボタンを押下し、先ほど作成した「classes.jar」を指定します。
そうするとその下に、Jarファイルに含まれるクラスファイル一覧が表示されます。

Jarファイルの展開、すなわち、ZIPファイルの展開には、zlib.jsのunzip.min.jsを使わせていただきました。

zlib.js
 https://github.com/imaya/zlib.js

それではもう一度、ブラウザから実行します。

image.png

Class Nameに、今度はJarファイルに含まれるtest2/TestFuncを指定して、「start wasm」ボタンを押下して実行します。

コンソールに以下のように表示されましたでしょうか。test2/TestFuncとなって、該当ソースが実行されているのがわかります。

Hello test2/TestFunc
args[0]=ABCD
[]=あいうえお
[1]=かきくけこ

補足

Javaインタプリタは、wabaからかなりカスタマイズしていまして、バグだらけですので、それだけお断りしておきます。。。

解説は次回の記事で。まずはここまで。

以下、続編です。
 WebAssemblyでJavaVMを動かす:補足

以上。

12
8
1

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
12
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?