言語ゲーム

とあるエンジニアが嘘ばかり書く日記

Twitter: @propella

ocaml の swflib を使って ActionScript Virtual Machine 2 コードを生成する。その2

退屈な話が続きますが、完全を期すために続けます。前回、http://d.hatena.ne.jp/propella/20090803/p1 ocaml を使って abc ファイルを生成する所までやりました。しかしこれで出来たファイルは Tamarin が無いと動かないので、全く何の役にも立ちませんでした。そこで、今回はそこから swf ファイルを作って Flash コンテンツとしてウェブブラウザで表示する所までやります。

まとめ

AVM2 が発表されて随分たちます。しかし私には一体これが何なのか、どんな利用価値があるのかを理解するのに長い間かかりました。特にどの機能が Tamarin に含まれて、Flash Player との境界はどうなっているのか謎でした。

色々実験してみて、Flash Player などのホストの機能を利用するには、単に名前を参照すれば良く、特に名前空間を使えば全ての API にアクセス出来る事が分かりました。また、AdobeコンパイラAPI を使うプログラムをコンパイルするためにライブラリ自体を必要としますが、実際はクラスの名前さえ分かってればライブラリのファイル無しでコンパイルしても問題ない事が分かりました。Flash Player のライブラリは abc ファイルとして公開されていないのでこれが分かっただけでも収穫です。

TamarinFirefox に組み込まれるかも知れないと言われていた数年前と比べて、高速な他の Javascript エンジンが複数ある現在 AVM2 は話題性に欠けますが、それでも最も普及している VM なので、VM で遊ぶには最適だと思います。

ABC ファイルから SWF ファイルを生成する。

ABC ファイルというのはバイトコードが納められたファイルです。最近の Flash の中枢とも言える部品です。奇妙な事に、このファイルから直接 swf を生成する方法は無いみたいです。普通の人は Adobe のツールを使うし、マニアックな人はプログラムの中で処理してしまうので、わざわざそんなツールは必要無いのかも知れません。このファイルをビルドするには、前回挙げた方法で ocaml/swflib 等を準備する必要があります。仕方が無いので swflib を使って自分で書きました。実際に swf を構築する部分だけを取り上げます。

let make_swf (abc:tag) (width:int) (height:int) (classname:string) : swf =
  let header : header = {
    h_version = 4;
    h_size = ({rect_nbits = 16; left = 0; right = width * 20; top = 0; bottom = height * 20});
    h_fps = 1;
    h_frame_count = 1;
    h_compressed = false;
  } in
  let body : tag list = [
    { tid = 0; textended = false; tdata = TSandbox (SBUnknown 0b00001000) };
    abc;
    { tid = 0; textended = false; tdata = TF9Classes [{f9_cid = Some 0; f9_classname = classname}]};
    { tid = 0; textended = false; tdata = TShowFrame };
    { tid = 0; textended = false; tdata = TEnd };
  ] in
  (header, body)

プログラム全体は http://gist.github.com/163142 にあります。

./abc2swf ファイル名 横 縦 のようにすると swf が出来上がります。ActionScript を実行するために最低限必要なタグは、82 番の DoABC (この関数では引き数 abc としてやってくる) の他に、69 番の FileAttributes (TSandbox) と 76 番の SymbolClass (TF9Classes)、その他 ShowFrame と End です。FileAttributes で AS3 を使うフラグを立てて、SymbolClass で ActionScript のクラスを Flash名前空間と関連づけます。典型的には、SymbolClass 内でゼロ番の ID を持つクラスが起動クラスになります。

起動クラスが無くても、ABC 内で最初に定義された静的関数は必ず実行される事になっていて、起動クラスの初期化もその中で行われています。

Flash Player で表示出来る ABC バイトコードを作る。

次に、Flash に埋込む abc ファイルを作ります。abc から Flash API にアクセスするには名前空間を使います。例えば起動クラスを作るためには flash.display.Sprite のサブクラスが必要ですが、flash.display という名前空間にある Sprite にアクセスすれば良いです。

ここで肝なのが、ActionScript の package の実体は名前空間だという事です。試しに次のように ActionScript で "flash.text" という名前空間を作ると、import 宣言無しで flash.text.TextField にアクセス出来ます。これはマニュアルにもほんのさらりとしか触れられていないので最重要情報です。

namespace ns = "flash.text";
var field = new ns::TextField();

という事で、前回作った Hello World にクラス宣言と名前空間へのアクセスを付け加えると Flash Player でも表示出来るバイトコードが出来上がります。クラス定義の部分はまだ完全に理解していないので、名前空間の作り方の所を取り上げます。

let idents:as3_ident array =
  [|"abcsprite"; ""; "TextField"; "flash.display:Sprite"; "Object";
    "flash.text"; "Hello, World!"; "text"; "addChild"; "flash.display";
    "Sprite"|]

let namespaces:as3_namespace array = 
  [|A3NPublic None;
    A3NPublic (Some (id 6));   (* "flash.text":6 *)
    A3NPublic (Some (id 10))|] (* "flash.display":10 *)

let nsets:as3_ns_set array = [||]

let names:as3_multi_name array =
  [|(A3MName (id 3, id 2));  (* flash.text::TextField *)
    (A3MName (id 8, id 1));  (* public::text *)
    (A3MName (id 9, id 1));  (* public::addChild *)
    (A3MName (id 1, id 1));  (* public::abcsprite *)
    (A3MName (id 11, id 3)); (* flash.display::Sprite *)
    (A3MName (id 5, id 1));  (* public::Object *)
  |]

ファイル全体は http://gist.github.com/163147 にあります。

このように最初に文字列定数を全部定義して、それを組み合わせて名前空間とシンボルを作ります。このプログラムを作るために ActionScript プログラムから逆コンパイルした物を元にしたのですが、Flex の作るバイトコードはかなり無駄が多くて、定数テーブルは実際この倍以上の大きさに膨れ上がります。機械は人間より馬鹿だなーという事が分かって嬉しいです。

実行結果はこんな感じです。

$ ocamlc -g -I ocaml -I ocaml/swflib ocaml/extLib.cma ocaml/swflib/swflib.cma -o abc2swf abc2swf.ml
$ ocamlc -g -I ocaml -I ocaml/swflib ocaml/extLib.cma ocaml/swflib/swflib.cma -o abcsprite abcsprite.ml
$ ./abcsprite 
$ ./abc2swf abcsprite.abc 
$ ./abc2swf abcsprite.abc 100 100
$ open abcsprite.swf

abcsprite