BeInteractive!

最近、告知と勉強会エントリばっかりでしたが、タイトル通り、ActionScript でクラス置換風なことが出来ることに気付いたので、まとめてみます。この手法を使うと、任意の SWF 内で使用されているクラスを、自分で用意した別のクラスで置き換えて実行することが出来ます。ポイントは、その任意の SWF をパブリッシュしなおしたりだとか、バイトコードでどうこうだとかそういったハックの類を使わなくてよく、一切弄る必要がないところです。

サンプルを Spark project にコミットしたので、これを見ながら読み進めて下さい。

まず、以下のような Context クラスと DocumentRoot クラスで構成された、contents.swf があります。

src/org/libspark/sample/Context.as

package org.libspark.sample
{
	/**
	 * アプリケーションに必要な情報を保持します
	 *
	 * @author yossy:beinteractive
	 */
	public class Context
	{
		public static function get message():String
		{
			return "test";
		}
	}
}

src/org/libspark/sample/DocumentRoot.as

package org.libspark.sample
{
	import flash.display.Sprite;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	
	/**
	 * 実行時クラス置換のサンプル
	 * 読み込まれるコンテンツのドキュメントルートです
	 *
	 * @author yossy:beinteractive
	 */
	public class DocumentRoot extends Sprite
	{
		public function DocumentRoot()
		{
			var field:TextField = new TextField();
			field.autoSize = TextFieldAutoSize.LEFT;
			field.text = Context.message;
			addChild(field);
		}
	}
}

見ての通り、Context.message プロパティの内容をテキストフィールドに表示しているだけです。contents.swf を見てみる と、予想通り、「test」が表示されます。ここまでは普通ですね。

続いて、この contents.swf を Loader で読み込んで表示するだけの、en.swf と、jp.swf を見てみて下さい。見れば分かると思いますが、contents.swf を読み込んで表示しているのであれば test と表示されるかと思いきや、それぞれ「hello」と「こんにちは」と表示されていますね。一体どうやっているのでしょう?

と、もったいつけてみますが、ここでクラス置換が登場します。実は、en.swf と jp.swf には、以下のような Context クラスが埋め込まれています。

src-en/org/libspark/sample/Context.as

package org.libspark.sample
{
	/**
	 * 英語版アプリケーションに必要な情報を保持します
	 *
	 * @author yossy:beinteractive
	 */
	public class Context
	{
		public static function get message():String
		{
			return "hello";
		}
	}
}

src-jp/org/libspark/sample/Context.as

package org.libspark.sample
{
	/**
	 * 日本語版アプリケーションに必要な情報を保持します
	 *
	 * @author yossy:beinteractive
	 */
	public class Context
	{
		public static function get message():String
		{
			return "こんにちは";
		}
	}
}

それぞれのクラスで、message プロパティの値が変わっていますね。そして、この英語版 Context クラスと、日本語版 Context クラスを、contents.swf の実行時に、元々存在してた Context クラス (message プロパティの値が test な Context クラス) と入れ替えることで、表示されるメッセージの内容を切り替えていたのです。これが、クラス置換です。

さて、やってみれば分かると思いますが、en.swf と jp.swf に置き換えたい Context クラスを埋め込んだからといって、普通に contents.swf を読み込んだだけでは、クラス置換は実現出来ません。ここで、ApplicationDomain というヤツが登場します。

通常、親の SWF が読み込んだ子 SWF が、親 SWF で定義されているクラスにアクセス出来てしまうと、色々と悪さをされる可能性があるので、親 SWF のスクリプトと、子 SWF のスクリプトは、別の空間で実行され、互いに参照出来ないようになっています。この「空間」に相当するのが ApplicationDomain です。逆に、同じ空間 (=同じ ApplicationDomain) で親 SWF と子 SWF を実行すると、お互い定義されているスクリプトやクラスを参照出来るようになります。ちなみに、このとき、親 SWF と子 SWF で同じクラスが使われていて、コンフリクトする、という事態が想定出来るわけですが、FlashPlayer の仕様では、このようなとき、エラーになるのではなく、親 SWF で使われているクラスが優先され、子 SWF のクラスは無視されるという動作をします。

ここまでくれば気付いた人も多いと思いますが、この、子 SWF を同じ ApplicationDomain に読み込むとき、親 SWF で定義されているクラスが優先される、という動作を利用して、今回のクラス置換を実現しています。en.swf と jp.swf の ApplicationDomain には、既に英語版もしくは日本語版の Context クラスが存在するため、contents.swf を同じ ApplicationDomain に読み込んだとき、それらのクラスが使われる、というわけです。

実際の、en.swf と jp.swf のドキュメントルートのコードは以下のようになります。

src-en/org/libspark/sample/ParentDocumentRoot.as

src-jp/org/libspark/sample/ParentDocumentRoot.as

package org.libspark.sample
{
	import flash.display.Sprite;
	import flash.display.Loader;
	import flash.net.URLRequest;
	import flash.system.LoaderContext;
	import flash.system.ApplicationDomain;
	
	/**
	 * 実行時クラス置換のサンプル
	 * コンテンツを読み込むドキュメントルートです
	 *
	 * @author yossy:beinteractive
	 */
	public class ParentDocumentRoot extends Sprite
	{
		public function ParentDocumentRoot()
		{
			// 置換したいクラスを参照することで必ずそのクラスが SWF に埋め込まれるようにします
			Context;
			
			// コンテンツをカレントドメインに読み込みます
			var loader:Loader = new Loader();
			addChild(loader);
			loader.load(new URLRequest('contents.swf'), new LoaderContext(false, ApplicationDomain.currentDomain));
		}
	}
}

Loader クラスを使って load するときに、LoaderContext を使って、ApplicationDomain.currentDomain (=現在のドメイン = 同じ ApplicationDomain) に読み込まれるようにするのがポイントです。

というわけで、今回のクラス置換の方法をまとめると、

  1. 置換したいクラスと同じパッケージ、クラス名で、置換先のクラスを用意する
  2. ドキュメントルートなどでそのクラスを参照し、必ず SWF に埋め込まれるようにする
  3. Loader::load(new URLRequest('/path/to/swf.swf'), new LoaderContext(flase, ApplicationDomain.currentDomain)) を使用して、SWF を同じ ApplicationDomain に読み込むことにより、置換する

今回のサンプルのように、ローカライズに使用したり、バリエーションを作らなければならない場合に設定をひとつのクラスにまとめて置換したり、ハックに使用したり、色々と応用が効くと思うので、ぜひ活用してみて下さい〜。

この記事へのトラックバック

トラックバックはありません。

TrackBack URL:

http://www.be-interactive.org/trackback.php?id=428

この記事へのコメント

コメントはありません。

コメント書き込み:

カテゴリ

タグ

アーカイブ