はじめに
Adobe から C/C++ で書いたコードを Flash や AIR で動かす Alchemy というものがリリースされましたね!
Alchemy - Adobe Labs
これはすごい!ということで、少し試してみたいと思います。
その様子をリアルタイムに書いていきます。ちゃんと出来るかな
環境
OS は Mac OS X で gcc 、 java は入っているものとします。
ホームディレクトリ(/Users/amachang)に AlchemyTest というディレクトリを作って作業します。
インストール
まずは、いろいろインストールします。
Flash Player 10 Debugger Version
Adobe Flash Player - Debug Downloads
ここからダウンロードしてきて、普通にインストールします。
Flex SDK
以下から Flex SDK をダウンロードします。
Flex SDK
現段階での最新の Stable のやつをダウンロードしました。具体的には以下のバージョンのものです。
http://opensource.adobe.com/wiki/display/flexsdk/download?build=3.2.0.3794&pkgtype=1
ダウンロードできたら、解凍します。今回は FlexSDK という名前のディレクトリを作ってそこに解凍しました。
$ unzip flex_sdk_3.2.0.3794.zip -d FlexSDK
とりあえず、 bin に PATH を通しておきましょう。
$ export PATH=/Users/amachang/AlchemyTest/FlexSDK/bin:$PATH
試しに、なんか適当な ActionScript のファイルから swf ファイルを作ってみましょう。
以下のように、適当なディレクトリを作って
$ mkdir Src $ cd Src
そこに、以下のようなファイル Hoge.as を置きます。
package { import flash.display.Sprite public class Hoge extends Sprite { public function Hoge() { } } }
で、さっき FlexSDK に解凍したツールを使ってコンパイルします。
$ mxmlc Hoge.as 設定ファイル "/Users/amachang/AlchemyTest/FlexSDK/frameworks/flex-config.xml" をロードしています /Users/amachang/AlchemyTest/Src/Hoge.swf (555 bytes)
おおお。 swf ファイルがちゃんとできましたね。
というわけで、次は Alchemy Toolkit Package を導入します。
ここまでのディレクトリ構成は以下のような感じです。
$ pwd /Users/amachang/AlchemyTest $ tree -L 2 . |-- FlexSDK | |-- AIR SDK Readme.txt | |-- SDK license.pdf | |-- ant | |-- asdoc | |-- bin | |-- flex-sdk-description.xml | |-- frameworks | |-- lib | |-- license-adobesdk.htm | |-- license-adobesdk_ja.htm | |-- license-mpl.htm | |-- readme.htm | |-- readme_ja.htm | |-- runtimes | |-- samples | `-- templates |-- Src | |-- Hoge.as | `-- Hoge.swf `-- flex_sdk_3.2.0.3794.zip 10 directories, 11 files
Alchemy Toolkit Package
次は Alchemy Toolkit Package をダウンロードしてきます。
Alchemy Toolkit Package
ダウンロードしたら、作業ディレクトリに置いて AlchemyToolkit というディレクトリに解凍します。
$ unzip alchemy_sdk_darwin_p1_111708.zip -d AlchemyToolkit
少し、ディレクトリ構成を見てみましょう。
$ tree -L 2 AlchemyToolkit AlchemyToolkit `-- alchemy-darwin-v0.4a |-- CHANGES |-- LICENSE |-- README.cygwin |-- README.darwin |-- README.ubuntu |-- achacks |-- avm2-libc |-- bin |-- config |-- flashlibs `-- samples 7 directories, 5 files
AlchemyToolkit のトップはこんな感じのディレクトリ構成になっています。
まず、 avm2-libc というのがありますね。この中にが libc.so の代わりにリンクされるライブラリが入っているのでしょうか。
また、 bin にツールが入っているのでしょうか。README を読む前に bin の中身を確認してみましょう。
$ ls AlchemyToolkit/alchemy-darwin-v0.4a/bin/ ExplSWF.pl ShrSWF.pl llvm-as llvm-prof GetABC2.pl V10SWF.pl llvm-bcanalyzer llvm-ranlib ImplSWF.pl alc-util llvm-config llvm-stub LICENSE-LLVM.TXT asc.jar llvm-db llvm-upgrade LICENSE-LLVMC.TXT gccas llvm-dis llvm2cpp LICENSE-ZLIB.TXT gccld llvm-extract llvmc PutABC2.pl gluegen llvm-gcc4-darwin-install opt PutBIN.pl llc llvm-ld swfbridge README-LLVM lli llvm-link zpipe README-ZLIB llvm-ar llvm-nm zpipe.pl
おおお LLVM のツールがいっぱい入っています。
というわけで avm2-libc の中をみてみます。
$ ls -la AlchemyToolkit/alchemy-darwin-v0.4a/avm2-libc/ total 0 drwxr-xr-x 5 amachang staff 170 11 14 03:09 . drwxr-xr-x 8 amachang staff 442 11 14 03:09 .. drwxr-xr-x 2 amachang staff 102 11 14 03:09 avm2 drwxr-xr-x 10 amachang staff 2822 11 14 03:09 include drwxr-xr-x 2 amachang staff 136 11 18 20:19 lib
そして、この lib の中に
$ ls -la AlchemyToolkit/alchemy-darwin-v0.4a/avm2-libc/lib/ total 7544 drwxr-xr-x 2 amachang staff 136 11 18 20:19 . drwxr-xr-x 5 amachang staff 170 11 14 03:09 .. -rw-r--r-- 1 amachang staff 1564524 11 14 03:09 avm2-libc.l.bc -rw-r--r-- 1 amachang staff 2297544 11 14 03:09 avm2-libstdc++.l.bc
ちょっと寄り道、します。
avm2-libc.l.bc をディスアセンブルしてみましょう!$ cd AlchemyToolkit/alchemy-darwin-v0.4a/ $ bin/llvm-dis avm2-libc/lib/avm2-libc.l.bcおお。成功したみたいです。 avm2-libc.l.ll というファイルが出来ました。
LLVM というものを全然しらないのですが、そのファイルの中身を見てみると冒頭に ActionScript 3.0 のソースコードみたいなものが見えます。そして、アセンブラのようなものが見えます。
きっと LLVM のアセンブラなのでしょう。; ModuleID = 'avm2-libc/lib/avm2-libc.l.bc' target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-s0:0:64-f80:128:128"target triple = "i686-apple-darwin8" module asm "import flash.utils.*" module asm "import flash.display.*" module asm "import flash.text.*" module asm "import flash.events.*" module asm "import flash.net.*" module asm "import flash.system.*" module asm "" module asm "public var gdomainClass:Class;" module asm "public var gshell:Boolean = false;" module asm "" module asm "public function establishEnv():void" module asm "{" module asm " try" module asm " {" module asm " var ns:Namespace = new Namespace(\22avmplus\22);" module asm " " module asm " gdomainClass = ns::[\22Domain\22];" module asm " gshell = true;" module asm " }" : :初見だと、どこがエントリーポイントかもわかりませんね
寄り道が過ぎたので、インストールに取りかかりましょう。
まず、 AlchemyToolkit/alchemy-darwin-v0.4a に移動します。
$ cd AlchemyToolkit/alchemy-darwin-v0.4a
で、 ./config を実行します。
$ ./config Generating alchemy-setup... Turning execution bit on for Alchemy binaries... Add "source /Users/amachang/AlchemyTest/AlchemyToolkit/alchemy-darwin-v0.4a/alchemy-setup" to your login script. "alc-home" takes you to the Alchemy install folder. "alc-on" puts Alchemy gcc toolchain replacements at the front of your path. "alc-off" restores original path. "alc-util" shows you various Alchemy-related environment vars You need Flash 10 or AIR 1.5 and the Flex 3.2 SDK installed for testing.
alchemy-setup というファイルが作られました。「このファイルを login スクリプトに入れろ」と言われますが、とりあえず使うだけなので手動で実行するだけにします。
$ source alchemy-setup
そうすると、 alc-on alc-off が使えるようになります。これは、実際は環境変数 PATH などを切り替えることをします。
alc-on にした状態で gcc と叩くと achacks/gcc が実行されるといった具合ですね。
というわけで、 alc-on を実行します。
$ alc-on
環境変数を見てみましょう。
$ echo $PATH /Users/amachang/AlchemyTest/AlchemyToolkit/alchemy-darwin-v0.4a/achacks:/Users/amachang/AlchemyTest/AlchemyToolkit/alchemy-darwin-v0.4a/bin:/Users/amachang/AlchemyTest/FlexSDK/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/X11/bin:/opt/local/bin
先頭に、 achacks ディレクトリが追加されていますね。 alc-off を実行すれば環境変数は元に戻ります。
次は、 sample をコンパイルして実行してみたいですね。
サンプルをコンパイルして実行する
サンプルは、 Alchemy Toolkit の直下の samples ディレクトリにありました。
$ cd AlchemyToolkit/alchemy-darwin-v0.4a/samples/ $ ls -la total 0 drwxr-xr-x 6 amachang staff 204 11 14 03:09 . drwxr-xr-x 9 amachang staff 510 11 18 21:39 .. drwxr-xr-x 2 amachang staff 170 11 14 03:09 AS3API drwxr-xr-x 2 amachang staff 204 11 14 03:09 AS3Lib drwxr-xr-x 2 amachang staff 102 11 14 03:09 HelloFlash drwxr-xr-x 3 amachang staff 204 11 14 03:09 stringecho
HelloFlash
まずは、 HelloFlash を実行してみましょう。
HelloFlash ディレクトリの中には HelloFlash.c というファイルが入っていました。
内容はこんな感じ
#include <stdlib.h> #include <stdio.h> int main(int argc, char* argv[]) { printf("Hello Flash!\n"); }
$ cd HelloFlash $ gcc HelloFlash.c WARNING: While resolving call to function 'main' arguments were dropped! 45416.achacks.swf, 354510 bytes written
なんか、 WARNING が出ましたが成功したみたいです。
そして、 a.exe というファイルが出来ました。
$ ls -la total 704 drwxr-xr-x 2 amachang staff 136 11 18 21:51 . drwxr-xr-x 6 amachang staff 204 11 14 03:09 .. -r--r--r-- 1 amachang staff 104 11 14 03:09 HelloFlash.c -rwxr-xr-x 1 amachang staff 354590 11 18 21:51 a.exe
何も考えずに実行してみましょう。ドキドキ
$ ./a.exe /Users/amachang/AlchemyTest/FlexSDK/bin/adl _sb_45518/app.xml 2> /tmp/adl.trace & echo $! Hello Flash!
おおおお。ちゃんと Hello Flash! と表示されました!
ちょっと寄り道
この a.exe っていうのがどういうファイルなのか気になったので調べてみる$ file a.exe a.exe: a /Users/amachang/AlchemyTest/AlchemyToolkit/alchemy-darwin-v0.4a/bin/swfbridge script text executableswfbridge というコマンドで実行されるスクリプト?開いてみる。
バイナリだった。。$ head -3 a.exe #!/Users/amachang/AlchemyTest/AlchemyToolkit/alchemy-darwin-v0.4a/bin/swfbridge FWS (バイナリ)って感じのファイル。
FWS って swf ファイルのマジックだったっけ?
そんな感じのファイルらしい。 swfbridge ってどんなものなんだろう。 adl を呼び出してるみたいだけど。
app.xml っていうファイルを自動で作って adl を呼び出すツールかな
ちょっと strings してみた$ strings /Users/amachang/AlchemyTest/AlchemyToolkit/alchemy-darwin-v0.4a/bin/swfbridge PATH Couldn't write setup _sb_%d Couldn't create temp dir error: %s pid%d %s/app.xml <application xmlns="http://ns.adobe.com/air/application/1.5"> <id>com.adobe.%s</id> <version>1.0</version> <filename>%s</filename> <name>%s</name> <initialWindow> <x>0</x> <y>0</y> <visible>false</visible> <content>app.swf</content> </initialWindow> </application> %s/app.swf SWF: %s :おおお。 XML はけーん。 a.exe から app.xml と app.swf を作って adl に渡すみたいだなー。
またもや脱線しすぎました。
stringecho
次のサンプルは stringecho です。
$ cd ../stringecho
とりあえず、ディレクトリ構成は以下のようは感じになっています。
$ tree . |-- as3 | `-- EchoTest.as |-- readme.txt `-- stringecho.c 1 directory, 3 files
readme.txt がありますね。でも、読んじゃうともったいないので、何も考えずにコンパイルしてみます。
$ gcc stringecho.c WARNING: While resolving call to function 'main' arguments were dropped! 46476.achacks.swf, 355002 bytes written
なんか、成功しました。では、実行します。ドキドキ
$ ./a.exe
おおおお
AIR さんに怒られました!これはこれで嬉しいですね。
というわけで、 readme.txt を読みます。
なるほど、以下のように swc オプションを付けることで、 swf から使える *.swc ファイルになるようになるようです。
$ gcc stringecho.c -swc -o stringecho.swc WARNING: While resolving call to function 'main' arguments were dropped! 47506.achacks.swf, 362570 bytes written frame rate: 60 frame count: 1 69 : 4 72 : 362500 76 : 33 1 : 0 0 : 0 frame rate: 24 frame count: 1 69 : 4 77 : 506 64 : 31 63 : 16 65 : 4 9 : 3 41 : 26 82 : 471 1 : 0 0 : 0 adding: catalog.xml (deflated 75%) adding: library.swf (deflated 61%)
で、 stringecho.swc ファイルが出来るので、 EchoTest.as のコンパイル時に指定してあげます。
$ mxmlc -library-path+=stringecho.swc --target-player=10.0.0 as3/EchoTest.as
そうすると、 as3/EchoTest.swf が出来ます。
ただ、一見これを実行しても何もおきません。
以下のように、 trace 関数に結果を渡しているので、結果を見るには mm.cfg とかめんどくさい設定をしなければならないからです。
package { import flash.display.Sprite; import cmodule.stringecho.CLibInit; public class EchoTest extends Sprite { public function EchoTest() { var loader:CLibInit = new CLibInit; var lib:Object = loader.init(); trace(lib.echo("foo")); } } }
てっとり早く結果を見たいので、わざと Error を発生させましょう。
package { import flash.display.Sprite; import cmodule.stringecho.CLibInit; public class EchoTest extends Sprite { public function EchoTest() { var loader:CLibInit = new CLibInit; var lib:Object = loader.init(); // 結果をエラーに乗せる throw Error(lib.echo("foo")); } } }
というわけで、これで出来た EchoTest.swf をブラウザで開きます。
おおおお。ちゃんとError に foo という文字列が運ばれてきましたね!
この swf は以下のように swfbridge でも実行出来るようです。
$ ../../bin/swfbridge as3/EchoTest.swf
ちなみに、 C 言語側のコードはこんな感じです。 main で初期化して C 言語の関数を Flash から扱えるようにしているみたいですね。
//Simple String Echo example //mike chambers //mchamber@adobe.com #include <stdlib.h> #include <stdio.h> //Header file for AS3 interop APIs //this is linked in by the compiler (when using flaccon) #include "AS3.h" //Method exposed to ActionScript //Takes a String and echos it static AS3_Val echo(void* self, AS3_Val args) { //initialize string to null char* val = NULL; //parse the arguments. Expect 1. //pass in val to hold the first argument, which //should be a string AS3_ArrayValue( args, "StrType", &val ); //if no argument is specified if(val == NULL) { char* nullString = "null"; //return the string "null" return AS3_String(nullString); } //otherwise, return the string that was passed in return AS3_String(val); } //entry point for code int main() { //define the methods exposed to ActionScript //typed as an ActionScript Function instance AS3_Val echoMethod = AS3_Function( NULL, echo ); // construct an object that holds references to the functions AS3_Val result = AS3_Object( "echo: AS3ValType", echoMethod ); // Release AS3_Release( echoMethod ); // notify that we initialized -- THIS DOES NOT RETURN! AS3_LibInit( result ); // should never get here! return 0; }
自分でも何か作ってみる
最初に作った Src ディレクトリに移動して
$ cd /Users/amachang/AlchemyTest/Src
まずは hello
hello.c を書く
#include "AS3.h" static AS3_Val hello(void* self, AS3_Val args) { return AS3_String("Hello, world."); } int main() { AS3_Val method = AS3_Function( NULL, hello ); AS3_Val object = AS3_Object( "hello: AS3ValType", method ); AS3_Release( method ); AS3_LibInit( object ); return 0; }
Hello.as
package { import flash.display.Sprite; import cmodule.hello.CLibInit; public class Hello extends Sprite { public function Hello() { throw Error(new CLibInit().init().hello()); } } }
で、コンパイル
$ gcc hello.c -swc -o hello.swc && mxmlc -library-path+=hello.swc --target-player=10.0.0 Hello.as WARNING: While resolving call to function 'main' arguments were dropped! 49833.achacks.swf, 358742 bytes written frame rate: 60 frame count: 1 69 : 4 72 : 358677 76 : 28 1 : 0 0 : 0 frame rate: 24 frame count: 1 69 : 4 77 : 506 64 : 31 63 : 16 65 : 4 9 : 3 41 : 26 82 : 471 1 : 0 0 : 0 adding: catalog.xml (deflated 75%) adding: library.swf (deflated 60%) 設定ファイル "/Users/amachang/AlchemyTest/FlexSDK/frameworks/flex-config.xml" をロードしています /Users/amachang/AlchemyTest/Src/Hello.swf (142645 bytes)
クロージャを渡す
AS 側からクロージャを渡して
package { import flash.display.Sprite; import cmodule.hello.CLibInit; public class Hello extends Sprite { public function Hello() { var message:String = "Hello, "; new CLibInit().init().hello(function(message2:String):* { message += message2; }); throw Error(message); } } }
C 言語側で実行
#include <stdio.h> #include <stdlib.h> #include "AS3.h" static AS3_Val hello(void* self, AS3_Val args) { AS3_Val callback; AS3_ArrayValue(args, "AS3ValType", &callback); AS3_Call(callback, AS3_Undefined(), AS3_Array("StrType", " world.")); return 0; } int main() { AS3_Val method = AS3_Function( NULL, hello ); AS3_Val object = AS3_Object( "hello: AS3ValType", method ); AS3_Release( method ); AS3_LibInit( object ); return 0; }
Alchemy の gcc は何をしているか
hoge.c が hoge.swc になる過程を順に見て行きましょう。
1. C/C++ を LLVM 用のバイナリにコンパイルする
$ llvm-gcc -v -emit-llvm -nostdinc -I/Users/amachang/AlchemyTest/AlchemyToolkit/alchemy-darwin-v0.4a/avm2-libc/include -I/usr/local/include --include /Users/amachang/AlchemyTest/AlchemyToolkit/alchemy-darwin-v0.4a/avm2-libc/avm2/AVM2Env.h hoge.c -c -o hoge.o
まずは、このように hoge.c が hoge.o にコンパイルされます。
この hoge.o は LLVM のバイトコードです。
2. avm2-libc.l.bc とリンクする
$ llvm-ld -o=hoge -O5 -internalize-public-api-list=_start,malloc,free,__adddi3,__anddi3,__ashldi3,__ashrdi3,__cmpdi2,__divdi3,__fixdfdi,__fixsfdi,__fixunsdfdi,__fixunssfdi,__floatdidf,__floatdisf,__floatunsdidf,__iordi3,__lshldi3,__lshrdi3,__moddi3,__muldi3,__negdi2,__one_cmpldi2,__qdivrem,__adddi3,__anddi3,__ashldi3,__ashrdi3,__cmpdi2,__divdi3,__qdivrem,__fixdfdi,__fixsfdi,__fixunsdfdi,__fixunssfdi,__floatdidf,__floatdisf,__floatunsdidf,__iordi3,__lshldi3,__lshrdi3,__moddi3,__muldi3,__negdi2,__one_cmpldi2,__subdi3,__ucmpdi2,__udivdi3,__umoddi3,__xordi3,__subdi3,__ucmpdi2,__udivdi3,__umoddi3,__xordi3,__error /Users/amachang/AlchemyTest/AlchemyToolkit/alchemy-darwin-v0.4a/avm2-libc/lib/avm2-libc.l.bc hoge.o
次に、 llvm-ld でリンクされて hoge と hoge.bc が出来ます。( hoge はただ単に hoge.bc を実行する sh ファイル)
この avm2-libc.l.bc C 言語側に libc と同じ API を提供しているようですね。
3. llc というツールで ActionScript のコードを作る
$ llc -march=avm2 -avm2-use-memuser -o=hoge.as -avm2-package-name=cmodule.hoge hoge.bc
このツールで LLVM のコードが、なんと ActionScript のコードになります。
package cmodule.hoge { // Start of file scope inline assembly import flash.utils.* import flash.display.* import flash.text.* import flash.events.* import flash.net.* import flash.system.* public var gdomainClass:Class; public var gshell:Boolean = false; public function establishEnv():void { try { var ns:Namespace = new Namespace("avmplus"); gdomainClass = ns::["Domain"]; gshell = true; } catch(e:*) {} if(!gdomainClass) { var ns:Namespace = new Namespace("flash.system"); gdomainClass = ns::["ApplicationDomain"]; } } establishEnv(); public var glogLvl:int = Alchemy::LogLevel; public function log(lvl:int, msg:String):void { if(lvl < glogLvl) : : (27436行)
4. asc.jar で、 as をコンパイルする
$ java -Xms16M -Xmx1024M -jar /Users/amachang/AlchemyTest/AlchemyToolkit/alchemy-darwin-v0.4a/bin/asc.jar -AS3 -strict -import /Users/amachang/AlchemyTest/AlchemyToolkit/alchemy-darwin-v0.4a/flashlibs/global.abc -import /Users/amachang/AlchemyTest/AlchemyToolkit/alchemy-darwin-v0.4a/flashlibs/playerglobal.abc -d -config Alchemy::Shell=false -config Alchemy::NoShell=true -config Alchemy::LogLevel=0 -config Alchemy::Vector=true -config Alchemy::NoVector=false -config Alchemy::SetjmpAbuse=false -swf cmodule.hoge.ConSprite,800,600,60 hoge.as
5. hoge.swf から ActionScript のバイトコードを取り出す
$ GetABC2.pl hoge hoge.swf.abc
6. もっかい swf を作る
$ PutABC2.pl /Users/amachang/AlchemyTest/AlchemyToolkit/alchemy-darwin-v0.4a/achacks/swctmpl.swf temp.swf hoge.swf.abc cmodule/hello/CLibInit $ V10SWF.pl temp.swf library.swf
7. catalog.xml を付けて zip で固めて swc にする
ちなみに、 catalog.xml はこんなの
<?xml version="1.0" encoding ="utf-8"?> <swc xmlns="http://www.adobe.com/flash/swccatalog/9"> <versions> <swc version="1.0" /> <flex version="2.0" build="143452" /> </versions> <features> <feature-script-deps /> <feature-files /> </features> <libraries> <library path="library.swf"> <script name="cmodule/hello/CLibInit" mod="1177909560000" > <def id="cmodule.hello:CLibInit" /> <dep id="Date" type="e" /> <dep id="Date" type="s" /> <dep id="flash.utils:Dictionary" type="e" /> <dep id="flash.utils:Dictionary" type="s" /> : (略) : <dep id="AS3" type="n" /> <dep id="Object" type="i" /> </script> </library> </libraries> <files> </files> </swc>
$ zip hoge.swc catalog.xml library.swf
おおおおおおおお。と言う訳で hoge.swc が出来ました。
まとめ
- Alchemy を使うと結構簡単に C 言語のコードを swc にして使うことができる
- ただ、僕の環境では C++ をコンパイルしようと思っても Alchemy の include ファイル内でエラーが出来なかった。
- C 言語はいったん LLVM のバイトコードになり、最適化されたあと、 ActionScript のコードに変換され、それがコンパイルされる。
- C 言語 (hoge.c)
- LLVM バイトコード (hoge.bc)
- ActionScript のコード (hoge.as)
- ActionScript Byte Code (hoge.abc)
- Swc (hoge.swc)
最後に
Alchemy 楽しいです!
みなさんも遊んでみてはいかがでしょうか。