C++
C言語
ActionScript3
Crossbridge

Adobe Crossbridgeで、C/C++の画像処理コードをswcにクロスコンパイルする。

More than 1 year has passed since last update.

1.Crossbridgeって?

Adobeがオープンソース化したクロスコンパイルツールです。
以前はAlchemyとかFlashCCと呼ばれていたようです。
公式サイト http://adobe-flash.github.io/crossbridge/

なお、この文書はCrossbridge1.0.1と、64bit版Windows7 Professionalを対象にしています。

2.何ができるの?

これを用いることで、過去に作成したC/C++のライブラリをswcにコンパイルすることができます。
ノリとしてはJavaのJNEに近い感覚だと考えてください
ActionScript(以下AS)から呼び出すためのラッパーを記述して、それと一緒にC/C++のコードをビルドすることで、swcファイルを生成します。
通常のASからは、そのラッパーを通じてアクセスすることになります。
制約としては、以下の点が上げられます。

  • 外部ライブラリがCrossbridgeには限られたものしか用意されていないので、普段-lオプションで指定するだけのライブラリも、ソースを集めてきてまとめてビルドしなければならない。
  • gccの制約でビルドできないコードが出るかもしれない。

3.環境構築手順

3-1.Crossbridge本体をダウンロード

公式サイト上部のDOWNLOADからsourceforge に飛んでzipをDLするか、github から取得します。
Windowsで扱う場合は、zip版の方が面倒がないと思います。
ここではzip版で話を進めます。任意のフォルダに展開しておいてください。

3-2.FlexSDKの準備。

Flex SDK 4.6以上のライブラリが必要になります。
http://www.adobe.com/devnet/flex/flex-sdk-download.html などからDLして、任意のフォルダに展開してください。

3-3.Flash Playerのインストール

Flash Player11以上が必要です。適宜インストールしておいてください。
デバッグ版の方が後々都合がよいと思います。

3-4.JREのインストール

64bit版のJVMが必要です。http://www.oracle.com/technetwork/jp/java/javase/downloads/jre7-downloads-1880261.html あたりからインストールしてください。
また、java.exeにパスを通しておいてください。

4.サンプルコードのビルド

3-1でDLしたzipを展開してできるCrossbridge_1.0.1/doc/sample.htmlに、パターン別サンプルのリンクと説明がありますので、参考にしてください。
最初はやりたいことに近いサンプルを書き換えていくのがよいと思います。

  1. Crossbridge_1.0.1フォルダ内のrun.batを実行して、環境変数を設定したcygwinのbashを起動します。
  2. sampleフォルダに移動して、tun.bat実行時のメッセージどおりにmakeを実行します

bash起動時に

make FLASCC="/cygdrive/c/Crossbridge_1.0.1/sdk" FLEX="/path/to/flexsdk"

というメッセージが出るので、これに従ってオプションを設定して下さい。

FLASCC
クロスコンパイラ環境のパス指定です。表示例は、Cドライブの直下にCrossbridge_1.0.1を展開した時のフルパスです。
FLEX
3-2でダウンロードして展開したFLEX SDKのパスです。/path/to/の部分を展開先にあわせて変更してください。

Crossbridgeは、カスタマイズされたgccでC/C++のコードをswcにビルドします。引数FLASCCで指定されたパスには、そのgccをはじめ、ビルドに必要なファイルが格納されています。

6.自作画像処理ライブラリをswcにビルドする例

ASからビットマップ画像データ(unsigned char配列と幅と高さの数値)を渡して反転処理処理を行うサンプルプログラムを作りました。
バイト配列のようなデータをどうやってC/C++に渡すかは、ドキュメントにもサンプルがなかったので、その一例です。

6-1.ファイルの説明

libHoge.h,libHoge.cpp
反転処理を書いたC言語のライブラリです。
as3api.cpp
ラッパー関数を記述したソースです。
main.cpp
おまじないを記述するファイルです。
swcdemo.as
クロスコンパイルしたswcをリンクして実行するASのソースです。これがswfファイルになります。

6-2.C言語ソース

サンプルなので簡単なことしかしていません。

libHoge.h

#ifndef _LIBHOGE_H_
#define _LIBHOGE_H_
#if defined(__cplusplus) || defined(c_plusplus)
extern "C" {
#endif
// RGB画像の各画素の輝度を反転させた画像を作成する。
// あらかじめ必要なメモリをpDstに確保しておくこと。
void reverse(unsigned char* pSrc, int width, int height, unsigned char* pDst);
#if defined(__cplusplus) || defined(c_plusplus)
}    // extern "C"
#endif
#endif 

libHoge.cpp

#include "libHoge.h"
void reverse(unsigned char* pSrc, int width, int height, unsigned char* pDst)
{
    for (int y = 0; y < height; y++)
    {
        for (int x = 0; x < width; x++)
        {
            int offset = (y * width + x) * 4;
            pDst[offset] = pSrc[offset];                                // alpha
            pDst[offset + 1] = (unsigned char)(255 - pSrc[offset + 1]); // R
            pDst[offset + 2] = (unsigned char)(255 - pSrc[offset + 2]); // G
            pDst[offset + 3] = (unsigned char)(255 - pSrc[offset + 3]); // B
        }
    }
}

6-3.ラッパー関数

随所にコメントを入れたので参照してください。

as3api.cpp

#include "AS3/AS3.h"
#include "libHoge.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

// ラッパー関数の宣言
void reverseAS() __attribute__((used,
    // ASから呼び出す時に参照される関数定義
    annotate("as3sig:public function reverseAS(data:ByteArray, width:int, height:int):ByteArray"),
    // この関数の所属するパッケージ指定。swfのソースで指定する必要があります。
    annotate("as3package:libHoge.Package"),
    // プリミティブ型以外に引数で使用している変数の型宣言
    annotate("as3import:flash.utils.ByteArray") 
));


// ラッパー関数の実装
void reverseAS()
{
    // AS側でCModule.malloc()を呼び出して、C/C++世界で使えるメモリを確保し、
    // ByteArrayの中身をコピーする。
    // inline_as3マクロは、その内側でASのコードを記述できます。
    // マクロ内で宣言された変数は、以後のCコードでもスコープとしては参照できますが、
    // 値にアクセスする場合は、AS3_GetScalarFromVar()関数などでCコードの変数に
    // 変換してやる必要があります。
    inline_as3(
        "var dataPtr:int = CModule.malloc(data.length);\n"
        "CModule.writeBytes(dataPtr, data.length, data);\n"
        "var dataSize:int = data.length;\n"
    );

    // AS3_GetScalarFromVar(=CrossbridgeのAPI)を使ってASの変数(ポインタ値とサイズ値)
    // をC/C++の変数にコピー
    // AS3_GetScalarFromVarは、上でincludeしているAS3/AS3.hに定義されています。
    // 詳しくはドキュメントを参照
    unsigned char* pSrc = 0;
    unsigned long srcSize = 0;
    AS3_GetScalarFromVar(pSrc, dataPtr);
    AS3_GetScalarFromVar(srcSize, dataSize);

    // width,heightを、Cで参照できる変数に変換
    int widthC, heightC;
    AS3_GetScalarFromVar(widthC, width);
    AS3_GetScalarFromVar(heightC, height);

    // C/C++の処理の呼び出し
    unsigned char* pDst = (unsigned char*)malloc(heightC * widthC * 4);
    reverse(pSrc, widthC, heightC, pDst);

    // もういらないので開放
    inline_as3("CModule.free(dataPtr);\n");     

    // ASの変数をC/C++で使えるようにで宣言。ただし、プリミティブでない型は宣言しただけ
    // (=これだけでnewされるわけではない)。
    AS3_DeclareVar(ba, ByteArray);         // 戻り値の宣言
    AS3_DeclareVar(pDstPointer, int);      // pDstのアドレスを渡すためのASの変数
    AS3_CopyScalarToVar(pDstPointer, pDst);// pDstのポインタ値をASにコピー

    // CのポインタをAS世界のByteArrayにコピー
    inline_as3(
        "ba = new ByteArray();\n"
        "CModule.readBytes(pDstPointer, width * height * 4, ba);\n"
        "ba.position = 0;\n"    // ASのコードに戻った時にEOFエラーになることがあるので0に戻す
        "trace(\"readbyte ok\" + (width * height * 4));"
    );

    // 開放
    free(pDst);
    AS3_ReturnAS3Var(ba);
}

6-4.main.cpp

このファイルは自作したものではなく、Crossbridgeのサンプルに付属しているものをそのまま使います。
クロスコンパイルされた処理の実行時にmain関数として扱われます。
とりあえずおまじないだと思えばよいです。

main.cpp

#include "AS3/AS3.h"
int main()
{
    // We still need a main function for the SWC. this function must be called
    // so that all the static init code is executed before any library functions
    // are used.
    //
    // The main function for a library must throw an exception so that it does
    // not return normally. Returning normally would cause the static
    // destructors to be executed leaving the library in an unuseable state.

    AS3_GoAsync();
}


6-5.呼び出し側のASコード

以下の2つをimportし、ライブラリを追加して、通常のモジュールのように使用するだけです。

  • import libHoge.Package.CModule;
  • import libHoge.Package.reverseAS;

libHoge.PackageはMakefileやラッパー関数で指定した、自作ライブラリのパッケージ名です。
reverseASは、ラッパー関数で定義した、ASとのインターフェース関数名です。

swcdemo.as

package
{
    import flash.display.*;
    import flash.events.Event;
    import flash.utils.ByteArray;
    import flash.display.BitmapData;
    import flash.display.Bitmap;
    import flash.geom.Rectangle;
    import libHoge.Package.reverseAS;
    import libHoge.Package.CModule;

    public class swcdemo extends Sprite
    {
        [Embed(source="sample.jpg", mimeType="image/jpeg")] 
        [Bindable]
        private var ImageClass:Class;
        private var img01:Bitmap;

        public function swcdemo()
        {
            img01 = new ImageClass();
            addEventListener(Event.ADDED_TO_STAGE, initCode);
        }

        public function initCode(e:Event):void
        {
        try
            {
                var data:ByteArray = img01.bitmapData.getPixels(new Rectangle(0, 0, 480, 360));
                data.position = 0;
                CModule.startAsync(this); // 最初にこれを呼ばないといけない
                var reversed:ByteArray = reverseAS(data, 480, 360);
                var bmData:BitmapData = new BitmapData(480, 360);
                bmData.setPixels(new Rectangle(0, 0, 480, 360), reversed);

                addChild(new Bitmap(bmData));
           }
           catch(err:Error)
           {
               trace(e);
           }
        }
    }
}

6-5.Makefile

自分で作成したprojectのビルドについては、Crossbridgeに同梱されている、Makefile.commonをincludeして使うことで、面倒な記述を省略することが出来ます。

# SRC
WRAPPER_SRC = main.cpp libHoge.cpp as3api.cpp

# Compile option
CPPFLAGS =-O3 -DNDEBUG -DWEBP_HAVE_PNG -DWEBP_HAVE_JPEG -DWEBP_HAVE_TIFF -DWEBP_USE_THREAD -Wextra -Wshadow -Wall
# -Wold-style-definition -Wmissing-declarations -Wdeclaration-after-statement -Wmissing-prototypes

T05: check
    @echo "->Now compile a SWC"
    "$(FLASCC)/usr/bin/gcc" $(BASE_CFLAGS) $(CFLAGS) -O4 $(UTIL_SRC) $(DSP_SRC) $(DEC_SRC) $(WRAPPER_SRC) -emit-swc=libHoge.Package  -lAS3++ -o libHoge2.swc

    @echo "->Now compile demo SWF"
    "$(FLEX)/bin/mxmlc" -static-link-runtime-shared-libraries -compiler.omit-trace-statements=false -library-path+=libHoge2.swc swcdemo.as -debug=true -o swcdemo.swf

# 共通の設定は、Crossbridgeに付属のMakefileをincludeすることでそちらに任せる
include ../Makefile.common

clean:
    rm -f *_wrapper.c *.swf *.swc *.as3 *.abc

swcのビルドをしているのは以下の行です。

\$(FLASCC)/usr/bin/gcc \$(BASE_CFLAGS) \$(CFLAGS) -O4 \$(UTIL_SRC) \$(DSP_SRC) \$(DEC_SRC) \$(WRAPPER_SRC) -emit-swc=libHoge.Package -lAS3++\ -o libHoge.swc

-emit-swcオ\プションに、ラッパーファイルのannotate("as3package:")で指定したパッケージ名を指定します。
-lAS3++オプションは、ラッパーで使っているAS3.hの関数のリンクに必要です。

swfをビルドしているのは以下の行です。

$(FLEX)/bin/mxmlc -static-link-runtime-shared-libraries -compiler.omit-trace-statements=false -library-path+=libHoge.swc swcdemo.as -debug=true -o swcdemo.swf

-debugオプションは必要に応じて付けてください。

7.まとめ

ラッパー関数を記述するコツが分かれば、そんなに難しいものはありません。
また、今回触りませんでしたが、C++のクラスを扱う方法などもありますが、そちらはマニュアルを参照してください。