Codin’ In The Free World

2009-11-26

[][] Adobe alchemy tutorial2

前回(http://d.hatena.ne.jp/lyokato/20091124/1259029512)の続き

非同期処理

オブジェクトに関数を生やす(非同期じゃない場合)

AS3_Val obj = AS3_Object("");
AS3_Val func = AS3_Function(NULL, my_func);
AS3_SetS(obj, "myFunc", func);
AS3_Release(func);

ASからは次のような感じで呼べました。

var result:Object = obj.myFunc(arg1, arg2);

非同期処理にする場合はAS3_FunctionAsyncを使います。

AS3_Val obj = AS3_Object("");
AS3_Val func = AS3_FunctionAsync(NULL, my_func);
AS3_SetS(obj, "myFunc", func);
AS3_Release(func);

そうするとASからの呼び方が少し変わります。

第一引数には、関数の処理が完了したときに呼ばれるコールバックを渡すようにします。

関数自体に渡す引数は、一つずつずらします。


obj.myFunc( function(result:Object):void { ... }, arg1, arg2 );

関数my_funcの戻り値は、myFuncの第一引数として渡したコールバック関数の引数として渡されるようになります。

AS3_Val my_func(void *self, AS3_Val args)
{
  ...
  return result; // myFuncの戻り値ではなく、コールバック関数の引数として渡されるようになる
}

非同期処理を行うときは、IOなどの外部とのやりとりが発生して時間がかかる場合、

もしくは画像処理などの重い処理をするときにブロックしたくないときなどがあると思います。


重い処理を入れるときには適度にflyieldの呼び出しを入れてやるとよいようです。

flyieldは、alchemyによってタイムスライシングを利用して継続を行うようです。

[参考: http://d.hatena.ne.jp/nitoyon/20090115/alchemy_as_stack ]


alchemyに同梱されているlibpngのサンプルや、

こちら(http://segfaultlabs.com/blog/post/asynchronous-jpeg-encoding/)のjpegのサンプルなどでも

画像処理中、あるチャンクが処理されるごとにflyieldを実行してます。


for (i = 0; i < height; i++) {
  for (j = 0; j < width; j++) {
     ...
  }
  // 一行処理するごとに制御を一度戻す
  flyield();
}

ただ気になったのが、非同期設定されたコールバックの中で例外を投げたら、どこかで消されました。

obj.myFunc( function(res:Object):void { throw new Error(String(res)); }, arg1, arg2 );

例外が捕らえられない。

flyieldなどのように、例外を大域脱出に使ってるところで、コールバックの例外などが潰されるんでしょうか。

そもそもC側でActionScriptの例外を飛ばす方法が分からないので何とも言えないですが。

2009-11-24

[][] Adobe Alchemy tutorial

alchemyについて

C/C++で書かれたコードをActionScript Byte Codeに変換することによって、

AS3とC/C++で連携が可能になる。

注意点

  • C/C++で書かれてgccでコンパイル済みのネイティブコードとリンクするわけではなく、C/C++コードをllvmを仲介してAction Script Bytecodeにするだけ。
  • なのでネイティブコード程、実行速度は速くはない
  • でもPure AS3よりは速い。最適化とかの影響。
  • Flash Playerのサンドボックスで動くので、threadやIOに制限がある。
  • なので、そのへんに転がってるC/C++のコードがなんでも動くわけではない。

参考

公式

http://labs.adobe.com/technologies/alchemy/

公式C API

http://labs.adobe.com/wiki/index.php/Alchemy:Documentation:Developing_with_Alchemy:C_API

設定からサンプルの実行まで

http://d.hatena.ne.jp/amachang/20081118/1227005285

使い方の基本

C/C++だけでswfを作るパターンもあるが、今回はswcを作り、actionscript側から呼び出して使うパターン。


AS3.hをinclude

main関数でAS3_LibInit関数にAS3_Val型のオブジェクトを渡す。

とにかくまずはここから。

C側で作ったオブジェクトをAS側に渡すところから始まる。


#include "AS3.h"

...
AS3_Val my_func(void* self, AS3_Val args)
{
...
}

int main(int argc, char **argv)
{
    AS3_Val result = AS3_Object("");

    AS3_Val my_func_val = AS3_Function(NULL, my_func);
    AS3_SetS(result, "myFunc", my_func_val);
    AS3_Release(my_func_val);

    AS3_LibInit(result);

    return 0;
}

AS3_LibInitから制御は戻らない。

上のコードのオブジェクト生成部分をASに置き換えると、下のようになる。

my_funcの実装とか細かいことは後で。

var result:Object = new Object();
result.myFunc = function() {...};

コンパイルしてhoge.swcを生成

alc-on
gcc -swc -o hoge.swc myfunc.c

ちなみにC++だったときは当然g++を使うのだが

macであれば-DOSXオプションが必要。

alc-on
g++ -DOSX -swc -o hoge.swc myfunc.cpp

ASから呼び出すときの定型的な処理。名前のパターンは簡単。

swcの名前がfoo.swcなら,cmodule.foo.CLibInitを呼び出す。

swcの名前がbar.swcならcmodule.bar.CLibInitを呼び出す。

import cmodule.hoge.CLibInit;

var loader:CLibInit = new CLibInit();
var obj:Object = loader.init();
obj.myFunc();

CLibInitのオブジェクトのinitメソッドを呼ぶと、

C側のmainで、AS3_LibInitに渡したオブジェクトが取得できる。

C側で設定した関数、myFuncがAS側から使える。


手順をもう少しきれいにまとめる

AS側に渡すオブジェクトをC側で作成

まずはObjectを作って渡すところから始まるのが典型的なパターン

AS3_Object関数を呼び出す。

AS3_Val my_func_val = AS3_Function(NULL, my_func);
AS3_Val result = AS3_Object("foo:StrType, bar:IntType, buz:AS3ValType", "strForFoo", 10, my_func_val);
AS3_Release(my_func_val);

AS3_Objectの最初の引数は、objectの中に含ませる全てのキーと、その型の指定。

第一引数で指定したキーの順番に対応させて、第二引数以降で値を突っ込んでいく。

ASに直した例を見たほうが早い。上の例は、ASに直すとこんな感じになる。


var result = {
  foo: "strForFoo",
  bar: 10,
  buz: function(){...}
};

この例ではStrType, IntType, AS3_ValTypeを使った。

型をリストアップすると次のようになる。

  • IntType int型 AS3側ではintに変換される。
  • PtrType ポインタを表す型。alchemyのメモリモデルについてはまた後で。
  • DoubleType double型 AS3側ではNumberに変換される。
  • StrType 文字列 char* AS3側ではStringに変換される
  • AS3_ValType その他の型はこれになる。

というわけで、いわゆるプリミティブな型については、IntType, StrTypeなどを指定することで直接ASに渡せる。


直接渡さないパターン。

AS3_Val型にとにかく変換してから渡す場合。

めんどくさいので、プリミティブな型に関しては、上のようにStrTypeやIntTypeを活用したほうがいいだろう。

AS3_Val strVal = AS3_String("strForFoo");
AS3_Val intVal = AS3_Int(10);
AS3_Release(strVal);
AS3_Val my_func_val = AS3_Function(NULL, my_func);
AS3_Val result = AS3_Object("foo:AS3ValType, bar:AS3ValType, buz:AS3ValType", strVal, intVal, my_func_val);
AS3_Release(my_func_val);
AS3_Release(intVal);

Cの型からAS3_Valに変換する関数は次ようなものがある。

  • AS3_String(const char *)
  • AS3_Number(double)
  • AS3_Int(int)
  • AS3_Ptr(void *)
  • AS3_True()
  • AS3_False()
  • AS3_Null()
  • AS3_Unfefined()
  • AS3_Array(...)
  • AS3_Object(...)

AS3_NullとAS3_Undefined以外は、必要なくなったらAS3_Releaseに渡して解放される必要があるので気をつける。


ちなみにundocumentedだが、AS3_ReleaseXという関数もある。

やってることはソース見たほうが早い。nullチェックが追加されるだけ。

#ifdef __cplusplus
inline void AS3_ReleaseX(AS3_Val obj)
{
  if(obj != NULL)
    AS3_Release(obj);
}
#else
#define AS3_ReleaseX(OBJ) do { AS3_Val obj = (OBJ); if(obj != NULL) AS3_Release(obj); } while(0)
#endif

AS3_Objectについては既に説明した。

AS3_Arrayも基本的にはAS3_Objectと同じ。キーの名前の指定がなくなるだけ。

AS3_Val arr = AS3_Array("IntType, StrType, StrType", 10, "foo", "bar");

ASに直すと次のようになる。

var arr:Array = [10, "foo", "bar"];

オブジェクトへのアクセス

AS3_Objectで作成したオブジェクトからデータを取り出したいときは次のようAS3_GetSを使う。

AS3_Val obj = AS3_Object(...);
AS3_Val val = AS3_GetS(obj, "foo");

やってることはAS3にすると次のようなもの。簡単。

var val = obj["foo"]
// var val = obj.foo;

データをセットしたいときは次のようにAS3_SetSを使う

AS3_Val val = AS3_String("newValueForBar");
AS3_SetS(obj, "bar", val);
AS3_Release(val);

ASにすると

obj.bar = "newValueForBar";

ちなみにAS3_GetS/AS3_SetSではなくて、AS3_Get/AS3_Setというパターンもあるが

面倒くさいだけなので、後ろにSがついてるパターンだけ使っとけばいいだろう。

一応捕捉しておくと、Sをつけないパターンでは、下のようになる。

AS3_Val key_name = AS3_String("foo");
AS3_Set(obj, key_name, val);
AS3_Release(key_name);

キー名を指定するときにchar*ではなくてAS3_Val型にしなければならない。面倒。


AS側から呼べる関数

ASに渡すオブジェクトにメソッドを生やす。

関数の型はAS3_ThunkProcでないといけない。

typedef AS3_Val (*AS3_ThunkProc)(void *data, AS3_Val params);

こんな感じの関数を用意しておく。

AS3_Val my_func(void *self, AS3_Val args)
{
...
}

オブジェクトにメソッド生やすコードは次のようになる。

AS3_Val fun = AS3_Function(NULL, my_func);
AS3_SetS(obj, "methodName", fun);
AS3_Release(fun);

AS3_FunctionでAS3_Val型にしておき、オブジェクトに渡す。

AS3_Functionの第二引数に関数ポインタとして渡す。

AS3_Functionの第一引数はオブジェクト指向したいときに使う。

my_funcの第一引数がこれと対応する。

とりあえず今はNULLを渡しておく。

こうしておくとAS側で、

obj.methodName(10, 20);

というように呼びだせるようになる。

では、ここで引数として渡した10, 20を、C側で受け取ってみる。


わたした値は、AS3_Val型に配列として詰め込まれている

AS3_Functionで指定した関数の第二引数として渡る。

下の例のargs。

AS3_Val my_func(void *self, AS3_Val args)
{
  int arg1;
  int arg2;
  int sum;
  AS3_ArrayValue(args, "IntType, IntType", &arg1, &arg2);

  sum = arg1 + arg2;
  return AS3_Int(sum);
}

とりあえず渡された二つの整数を足し算して返すだけの関数にしてみた。


引数の受け取り方はお決まりのパターン。

AS3_ArrayValueで引数を取得する方法はマスターしておくこと。

第一引数で、配列となってるAS3_Val型のオブジェクトを渡す。

第二引数で、配列の中身の型を指定。型の指定の仕方は、AS3_Arrayと同じ。

第三引数以降で、配列の中身を受け取る変数のポインタを渡す。


ちなみに上の例ではintだが、

引数の配列から抽出したいのが文字列だった場合

char *c;
AS3_ArrayValue(args, "StrType", &c);

AS3_Val型だった場合

AS3_Undefined()で初期化するのがよいかも。

AS3_Val data = AS3_Undefined();
AS3_ArrayValue(args, "AS3ValType", &data);


大雑把に関数内の処理手順を書くと次のようになる。


1. 引数として渡されたAS3_Val型の配列を展開してcで扱えるよう準備

2. cでごにょごにょデータを操作

3. 返したいデータをAS3_Val型に変換してから返す。


cのそれぞれの型をAS3_Val型にする方法は上で説明したが、

ここではその逆、AS3_Val型からCで扱える型に変換することが必要になる。

次のような関数が使える。AS3_ArrayValueについては既に例で示した。


  • AS3_StringValue
  • AS3_IntValue
  • AS3_NumberValue
  • AS3_PtrValue
  • AS3_ArrayValue
  • AS3_ObjectValue

今回は足し算した結果のintをAS3_Int関数で、AS3_Val型に変換して返している。

何も返す必要が無くても、AS3_Val型を返すことになっているので、

返すものが無ければ次のようにAS3_Nullを返しておくこと。


return AS3_Null();

まとめ

今回は、基本的なAS3-C間のデータの変換の方法、関数の扱い方をまとめた。

  • C側でさらにASの機能を利用する方法
  • IOやポインタ、メモリモデル
  • オブジェクト指向
  • 非同期処理

についてはまた今度。

今度があれば。