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 の実装を変えるだけで挙動が変わるのがおもしろいですね!