FlashDevelop + GCCでANE入門

Adobe AIR3から ANE (ActionScript Native Extensions) という機能が追加され、これを使うとAIRアプリケーションをネイティブコードで拡張できるらしいです。素晴らしいですね。

今回はFlashDevelopを使ってWindows x86用のANEを作ろうかと思います。小さなdll (Dynamic Link Library)を作るだけなので、Visual Studio(Visual C++)のような大げさなIDEは使わずにGCCで。余計なプロジェクトフォルダやファイル等も作らなくていいのですっきり作れるんじゃないかなと。Windows環境へのGCCのインストール方法はこのエントリーの最後に紹介しています。

Flash Builder + Visual Studio な環境の人は、agendy さんにてANEを利用したAIRアプリケーションの作り方を丁寧に解説されていますので、そちらの良エントリーを参考にしてください。
参考: ActionScript Native Extensionsのサンプルアプリ作成のまとめ

ここではFlashDevelop (Flex/AIR SDK)はインストール、設定完了済みとします。
* 環境
 FlashDevelop 4.0.0 RC2
 gcc version 4.6.1

FlashDevelopでAIRプロジェクトを作る

[Project] > [New Project] > [AIR AS3 Projector] から HelloANE という名前のAIRプロジェクトを作ります。それから bat/CreateCertificate.bat を [右クリック] > Execute して HelloANE.p12 を先に作っておきます。パスワードはデフォルトだと “fd” になるようですが、サンプルなのでここはなんでもいいです。証明書関連についてはこのエントリーの主旨ではないため説明は省きます。

ActionScript3.0でインタフェースを決める

まずはAPIのインタフェースを決めるところから。
[as]
package com.example {
import flash.external.ExtensionContext;

public class HelloWorld {
private var ctx:ExtensionContext;

public function HelloWorld() {
ctx = ExtensionContext.createExtensionContext(“com.example”, null);
}
public function say():String {
return ctx.call(“hello”) as String;
}
public function dispose():void {
return ctx.dispose();
}
}
}
[/as]
コンストラクタで ExtensionContext のインスタンスを作ります。createExtensionContext メソッドの第1引数は拡張IDと呼ばれるものです。後で使うので他と衝突しないユニークなIDを付けておいてください。第2引数はコンテキストタイプと呼ばれるもので、ネイティブ側で複数の関数群を提供している場合にこのコンテキストタイプを使って呼び出せる関数群の判別を行ったりします。今回はネイティブ側で1つしか関数を提供しないのでnullを指定して構いません。

HelloWorld#say ではネイティブ側の hello という名前の関数を呼び出し、その関数の戻り値をStringとして返します。HelloWorld#dispose はコンテキストのリソース解放を行うためのメソッドです。これを明示的に呼び出さない場合はGCがリソースを回収してくれるので今回は特に気にしません。ネイティブ側で巨大な空間をmallocした時などはこれをきちんと呼び出してやる必要がありそうです。

C言語でネイティブ関数を実装する

作業用フォルダとして新規に ext フォルダを作り、{FLEX_SDK_PATH}\include\FlashRuntimeExtensions.h と {FLEX_SDK_PATH}\lib\win\FlashRuntimeExtensions.lib の2つをコピーして置きます。そして hello.c ファイルにC言語でネイティブ関数を実装します。
[cpp]
/* hello.c */
#include
#include “FlashRuntimeExtensions.h”
#define EXPORT __declspec(dllexport)

// ネイティブ関数の本体
// “Hello, World!” という文字列データをFREObject値として返す
FREObject _hello(FREObject ctx, void* funcData,
uint32_t argc, FREObject argv[]) {
FREObject ret;
const char* msg = (const char*)”Hello, World!”;
FRENewObjectFromUTF8(strlen(msg) + 1, (const uint8_t*)msg, &ret);
return ret;
}
/** FREObject型
* typedef void* FREObject
*
** FRENewObjectFromUTF8関数
* FREResult FRENewObjectFromUTF8(uint32_t length,
* const uint8_t* value ,
* FREObject* object);
*/

// 関数テーブル
// “hello” がAIRランタイム側(AS)から呼び出される関数名
// functionData はここでは使わない
// _hello がネイティブ関数へのポインタ
FRENamedFunction _methods[] = {
{ (const uint8_t*)”hello”, NULL, _hello }
};
/** FRENamedFunction構造体
* typedef struct FRENamedFunction_ {
* const uint8_t* name;
* void* functionData;
* FREFunction function;
* } FRENamedFunction;
*/

// 拡張コンテキスト初期化時に呼ばれる
void _ctxInitializer(void* extData, const uint8_t* ctxType,
FREContext ctx, uint32_t* numFunctionsToSet,
const FRENamedFunction** functionsToSet) {
*numFunctionsToSet = sizeof(_methods)/sizeof(FRENamedFunction); // == 1
*functionsToSet = _methods; // 関数テーブルの紐付け
}

// 拡張コンテキスト破棄時に呼ばれる
void _ctxFinalizer(FREContext ctx) { /* なにもしない */}

// アプリケーション初期化時に呼ばれる, DLLからエクスポート
EXPORT void extInitializer(void** extDataToSet,
FREContextInitializer* ctxInitializerToSet,
FREContextFinalizer* ctxFinalizerToSet) {
*extDataToSet = NULL; // 拡張データ, ここでは使わない
*ctxInitializerToSet = _ctxInitializer; // イニシャライザの紐付け
*ctxFinalizerToSet = _ctxFinalizer; // ファイナライザの紐付け
}

// アプリケーション終了時に呼ばれる, DLLからエクスポート
EXPORT void extFinalizer(void* extData) { /* なにもしない */ }

[/cpp]
C++ではないので extern "C" を書く必要はありません。また、最初に #define EXPORT __declspec(dllexport) をしています。これはDLLから関数をエクスポートする為に指定するものです。

FRENamedFunction 構造体のメンバになっている FREFunction というのが FREObject 型の値を返す関数へのポインタになっています。そしてその FREObject というのは単に void* を typedef しているだけ。ここでは関数テーブルでAIRランタイム側から呼び出せるネイティブ拡張関数群を管理しています。今回の例では1つしか関数を提供しないのでメリットがわからないかもしれませんが、提供する関数の数が増えてくると便利になります。その他のAPIや型情報などの細かい部分は公式のドキュメントを読んで理解しておきます。

DevelopingActionScriptExtensionsForAdobeAIR.pdf

ネット上にあるANEのサンプルコードの中には、初期化時関数や終了時関数を紐付ける部分で、関数名の前にアドレス演算子 & を付けている場合もあれば付けていない場合もあって混乱する人もいるかもしれません。が、それらはどれも問題なく動作するはずです。これはC言語の関数ポインタ(と文法)に関する少しややこしい話になるのでここでは割愛しますが、もし興味があれば以下のプログラムの実行結果を予想してから実際に確認してみてください。
[cpp]
#include

int test() {
return 1;
}

int main(void) {
int (*func)() = test;
printf(“%d\n”, test());
printf(“%d\n”, (&test)());
printf(“%d\n”, func());
printf(“%d\n”, (*func)());

printf(“%p\n”, test);
printf(“%p\n”, &test);
printf(“%p\n”, func);
printf(“%p\n”, *func);
printf(“%p\n”, ***********func);
return 0;
}
[/cpp]

* コンパイル
ネイティブ側の実装が済んだのでdllファイルをGCCで作ります。

C:\Users\Ryo\workspace\HelloANE\src\ext>gcc -c hello.c
C:\Users\Ryo\workspace\HelloANE\src\ext>gcc -shared -o hello.dll hello.o FlashRuntimeExtensions.lib

たった2回gccを叩くだけ、簡単ですね。FlashRuntimeExtensions.lib をリンクするのを忘れないように。これで hello.dll というファイルが生成されます。

aneファイルの作成

作成に必要なものは以下の5つ。

* 電子署名ファイル (HelloANE.p12 作成済)
* dllファイル (hello.dll 作成済)
* 拡張記述ファイル
* swcファイル
* library.swf

ということでまだ作っていない拡張記述ファイル(XML形式)から。名前は extension.xml とします。
[xml]

com.example
1
hello.dll
extInitializer
extFinalizer

[/xml]
id 要素の値はAS側で指定した識別子です。今回はWindows用なので platform 要素の name 属性には “Windows-x86” と指定し、後はdllファイルの名前やエクスポートした関数の名前を指定します。

次は library.swf を作ります。swcファイルを展開すると入ってるので各々好きな方法で準備してください。ちょうどコマンドプロンプトが開いていたのでここでは acompc を使ってswcファイルを作ります。

## {FLEX_SDK_PATH}/bin へのPathは通っていることとする
C:\Users\Ryo\Documents\workspace\FlashDevelop\HelloANE>acompc ^
More? -target-player=13 ^
More? -source-path src ^
More? -include-classes com.example.HelloWorld ^
More? -output=hello.swc
設定ファイル "C:\Program Files\FlashDevelop\sdks\flex_sdk_4.5.1\frameworks\air-config.xml" をロードしています
C:\Users\Ryo\Documents\workspace\FlashDevelop\HelloANE\hello.swc (2094 bytes)

このswcファイルをzipとして展開して中にある library.swf を取り出しておきます。

* adtコマンドでaneファイルの作成
作業用フォルダとして新規に ane という名前のフォルダを作って必要な5つのファイルを入れておき、adt を使ってaneファイルを作成します。

C:\Users\Ryo\Documents\workspace\FlashDevelop\HelloANE\ane>adt -package ^
More? -storetype pkcs12 ^
More? -keystore HelloANE.p12 ^
More? -target ane hello.ane extension.xml ^
More? -swc hello.swc ^
More? -platform Windows-x86 library.swf hello.dll
password: パスワードを入力、デフォルトのままなら "fd"

C:\Users\Ryo\Documents\workspace\FlashDevelop\HelloANE\ane>

ここまでの作業でプロジェクトフォルダの中身は以下のようになっているはず。

動作確認

ネイティブ関数の呼び出しテストを行う前に application.xml を少し編集します。以下の情報を好きな所に追記します。extensionID には com.example.HelloWorld.as の中で指定した識別子文字列を入れます。

* application.xmlの編集
[xml]

com.example

[/xml]

* テスト用ASファイル(Main.as)の編集
ネイティブ関数をcallしてその戻り値をTextFieldに書き出します。
[as]
package {
import flash.display.Sprite;
import com.example.HelloWorld;
import flash.text.TextField;

public class Main extends Sprite {
private var ext:HelloWorld;

public function Main():void {
ext = new HelloWorld();
var tf:TextField = new TextField();
tf.text = ext.say(); // HelloWorld#say の中でネイティブ関数をcall
addChild(tf);
}
}
}
[/as]

* extdirフォルダの作成
このままF5を押しても Not supported native extensions profile と言われて実行できないはずです。ここで extdir という名前のフォルダを作り、aneファイルを展開したものを置いておきます。

* Run.batの編集
次に Run.bat ファイルを1行だけ編集します。10行目あたりにあるadlコマンドを叩いている所を以下のように修正します。

adl -runtime "%FLEX_SDK%\runtimes\air\win" -profile extendedDesktop -extdir extdir "%APP_XML%" "%APP_DIR%"

* 実行
今度こそ大丈夫、もう一度F5。

おつかれさまでした。

感想

AIRに触るのも初めてだったのですが特につまづく所はなかったです。ANEの作成手順自体は一見複雑なように見えますが、batファイルに手順をまとめて書いておくとか作業の効率化も難しくはありません。次はもう少し複雑な処理を試してみたいと思います。

enjoy!

——————–

おまけ: コンパイラ (GCC) の準備

C言語で普段プログラムを書いている人にとってGCCはお馴染みですね。Windows環境にGCCを入れるにはMinGWがお手軽なのでこれを入れます。Visual Studioと比べれば大変小さなツールなのですぐにインストールできるはずです。Flasherの方々にとってはもしかしたら聞き慣れないツールかもしれませんが、Cygwinからフォークして作られた有名なものなので神経質になる必要はありません。

公式のインストーラを使って入れます。ここでは最新の mingw-get-inst-20111118 を使います。インストールはマウスでぽちぽちしていくだけなので簡単ですが、以下のサイトで丁寧に説明されているので参考までに。
参考: Windows で MinGW バージョン 20110530 のインストールとテスト実行
ANEを作るだけなら C Compiler と C++ Compiler だけ入れればOK、MSYSはお好みで。インストールできたら環境変数Pathを編集しておきます(C:\Program Files\MinGW にインストールした場合は C:\Program Files\MinGW\bin をPathに追加)。

* 動作確認

C:\Users\Ryo>gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=c:/program files/mingw/bin/../libexec/gcc/mingw32/4.6.1/lto-wrapper.exe
Target: mingw32
Configured with: ../gcc-4.6.1/configure --enable-languages=c,c++,fortran,objc,obj-c++ --disable-sjlj
-exceptions --with-dwarf2 --enable-shared --enable-libgomp --disable-win32-registry --enable-libstdc
xx-debug --enable-version-specific-runtime-libs --build=mingw32 --prefix=/mingw
Thread model: win32
gcc version 4.6.1 (GCC)

あわせて読む:

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です