Tamarin の avmplus.exe に独自関数を実装
「Tamarin と遊んでみた」の続きです。
今回は、avmplus.exe にビルトイン関数を追加してみます。
// hoge.as import avmplus.*; System.alert("hoge");
avmplus.System パッケージには exit などの関数がありますが、alert 関数は実装ありません。そのため、この hoge.as を実行すると
$ avmplus.exe hoge.abc ReferenceError: Error #1065
参照エラーになってしまいます。
これを動かすのが今回の目標です。
toplevel.as の実装
toplevel.as を覗いてみましょう。
// toplevel.as package avmplus { public class System { public native static function exit(status:int):void public native static function exec(command:String):int public native static function getAvmplusVersion():String public native static function trace(a:Array):void public native static function write(s:String):void public native static function debugger():void public native static function isDebugger():Boolean public native static function getTimer():uint private native static function getArgv():Array public static const argv:Array = getArgv(); public native static function readLine():String } // ...
avmplus.System パッケージの関数が宣言されていますね。
native キーワードがついていますが、これらの関数が C++ で関数が実装されていることをあらわします。
alert 関数も native で実装してみたいところですが、まずは ActionScript で実装してみましょう。
ということで、toplevel.as を書き換えます。System.write 関数をそのまんま使っちゃいます。
// toplevel.as package avmplus { public class System { public static function alert(text:String):void { System.write(text); } public native static function exec(command:String):int public native static function getAvmplusVersion():String // ...
toplevel.as のコンパイル
さて、avmplus 側は、どう修正すればよいのでしょうか。
答えは、簡単。shell/toplevel.cpp の中にあります。
/* shell/toplevel.cpp */ const int toplevel_abc_length = 4403; const int toplevel_abc_method_count = 137; const int toplevel_abc_class_count = 15; const int toplevel_abc_script_count = 13; static unsigned char toplevel_abc_data[4403] = { 0x10, 0x00, 0x2e, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0xe0, 0xff, 0xff, 0xff, 0xef, 0x41, 0x86, 0x01, 0x08, 0x72, 0x65, 0x61, 0x64, 0x4c, 0x69, 0x6e, 0x65, 0x0d, 0x74, 0x6f, 0x70, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x2e, 0x61, 0x73, 0x24, 0x31, 0x00, 0x07, 0x61, 0x76, 0x6d, 0x70, 0x6c, // ...
この16進数の羅列が toplevel.as のバイトコードなんです。toplevel.as をバイトコードに変換して、toplevel.cpp を作れば動きそうです。
ということで、toplevel.as をコンパイルしてみます。-in を指定して、他の AS もまとめてコンパイルするのを忘れずに。
java -jar asc_authoring.jar ^ -import global.abc ^ -in shell\ByteArray.as ^ -in shell\Domain.as ^ -in shell\DoubleArray.as ^ -in shell\Endian.as ^ -in shell\FloatArray.as ^ -in shell\IntArray.as ^ -in shell\Java.as ^ -in shell\ShortArray.as ^ -in shell\StringBuilder.as ^ -in shell\UIntArray.as ^ -in shell\UShortArray.as ^ -in extensions\Dictionary.as ^ shell\toplevel.as toplevel.abc, 3850 bytes written
toplevel.abc が生成されました。
さらに、なんと toplevel.cpp と toplevel.h も自動的に更新されました。
const int toplevel_abc_length = 3908; const int toplevel_abc_method_count = 122; const int toplevel_abc_class_count = 15; const int toplevel_abc_script_count = 1; static unsigned char toplevel_abc_data[3908] = { 0x10, 0x00, 0x2e, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0xe0, 0xff, 0xff, 0xff, 0xef, 0x41, 0x79, 0x08, 0x72, 0x65, 0x61, 0x64, 0x4c, 0x69, 0x6e, 0x65, 0x0d, 0x74, 0x6f, 0x70, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x2e, 0x61, 0x73, 0x24, 0x31, 0x00, 0x07, 0x61, 0x76, 0x6d, 0x70, 0x6c, 0x75, // ...
どうも、ActionScript 内に native 宣言された関数があると、cpp と h が書き出されるようです。
toplevel_abc_class_count と toplevel_abc_script_count の値が減ってしまっていますが、ひとまずは気にせずに先に進むことにします。
いざ、実行
toplevel.cpp と toplevel.h が更新されたところで、avmplus.exe をビルドします。
いざ、実行!
$ avmplus.exe hoge.abc hoge
やったー!
地味ですが、System.alert() が実行できたことが分かります。
alert 関数を native 実装
さて、ActionScript で実装していた alert 関数を C++ での実装に切り替えてみましょう。
まずは、toplevel.as の編集です。alert 関数を native 宣言にします。
// toplevel.as package avmplus { public class System { public native static function alert(text:String):void public native static function exec(command:String):int public native static function getAvmplusVersion():String // ...
toplevel.as をバイトコードに変換すると、toplevel.cpp と toplevel.h が更新されます。
native 部分の実装は System.exit 関数を参考に、shell/SystemClass.cpp と shell/SystemClass.h を修正してみます。
/* shell/SystemClass.h */ // ... namespace avmshell { class SystemClass : public ClassClosure { //#define PERFORMANCE_GETTIMER #ifdef PERFORMANCE_GETTIMER uint64 initialTime; #else double initialTime; #endif public: SystemClass(VTable* cvtable); ~SystemClass(); // set by shell static int user_argc; static char **user_argv; void alert(Stringp text); /** * Implementation of System.exit * AS usage: System.exit(status); * Exits the VM with OS exit code specified by status. */ void exit(int status); // ...
/* shell/SystemClass.cpp */ #include "avmshell.h" #include <stdlib.h> namespace avmshell { BEGIN_NATIVE_MAP(SystemClass) sNATIVE_METHOD(avmplus_System_alert, SystemClass::alert) NATIVE_METHOD(avmplus_System_exit, SystemClass::exit) // ... END_NATIVE_MAP() void SystemClass::alert(Stringp text) { MessageBoxW(NULL, text->c_str(), L"avmplus", MB_ICONEXCLAMATION); } void SystemClass::exit(int status) { ::exit(status); } // ...
avmplus_System_alert は、先ほどの toplevel.as のコンパイル時に toplevel.h に生成されています。バイトコード中の native 宣言と C++ の中継ぎをするのが、この BEGIN_NATIVE_MAP であり、avmplus_System_alert であるわけです。
SystemClass::alert では、C++ らしく MessageBoxW 関数を呼び出しています。Win32 プログラミングではおなじみのメッセージ表示用の関数です。W は Unicode 版をあらわします。
いざ、実行 (2)
avmplus.exe をビルドして、hoge.abc を実行してみます。
$ avmplus.exe hoge.abc
メッセージが表示されました。
hoge.abc の再コンパイルしなくても、avmplus.exe の実装を変えるだけで挙動が変わるのがおもしろいですね!