リフレクションを用いてC#とActionScriptで定義されたメソッドを相互に呼び出す

環境はC#(VC# 2008 Express) .NET Framework 3.5 & Flash 8(ActionScript2)
(環境が最新ではないので事情が変わってるかもしれませんが)

ActionScriptで定義されたメソッドの呼び出し

呼び出されるメソッドはあらかじめActionScriptで以下のように記述しておく必要があります。

import flash.external.ExternalInterface;
ExternalInterface.addCallback("hoge", null, hoge);

通常swfに定義されたActionScriptのメソッドは、XMLを作成しFlashのコンテナに渡すことで呼び出しを行います。

C#ではAxShockwaveFlashObjectsのCallFunctionにXMLを渡します。

参考
External API の XML フォーマット

今回は、XMLを暗黙的に作成しメソッドの呼び出しを行えるようにします。
呼び出しのイメージとしては、メソッド呼び出しを仲介するオブジェクトを介し、

FlashPlayer.hoge();

のように呼び出せるようにします。
この場合、FlashPlayer.hoge();の呼び出し時にを作成し、CallFunctionに渡します。

C#で定義されたのメソッド呼び出し

ActionScriptC#に定義されたメソッドを呼び出す場合以下のようにします。

ExternalInterface.call("piyo");

こうするとFlashコンテナのFlashCallイベントがコールされるので、XML(_IShockwaveFlashEvents_FlashCallEventのrequestフィールド)を解析しメソッドを呼び出します。

この場合はリフレクションを用いてメソッドを呼び出すだけです。


上記のそれぞれの機能をまとめたクラス群を以下に示します。

ソース

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using AxShockwaveFlashObjects;
using System.Xml;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Proxies;

namespace FlashObject
{
    /// <summary>
    /// Flashオブジェクトにロードされたswfに定義されたメソッド呼び出しを簡易化するクラス
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public abstract class CallFunc<T> : System.MarshalByRefObject
        where T : new()
    {
        /// <summary>
        /// Ploxyを介したインスタンスを作成します
        /// </summary>
        /// <param name="flashobject"></param>
        /// <returns></returns>
        public static T CreateInstance(AxShockwaveFlash flashobject)
        {
            return (T)(new Ploxy(new T(), flashobject).GetTransparentProxy());
        }

        class Ploxy : RealProxy
        {
            T _this;
            AxShockwaveFlash flashobject;
            public Ploxy(T _this, AxShockwaveFlash flashobject) :
                base(typeof(T))
            {
                this._this = _this;
                this.flashobject = flashobject;
            }
            public override IMessage
                Invoke(IMessage msg)
            {
                var mm = msg as IMethodMessage;
                var ret = invoke(mm.MethodName, mm.Args);
                return new ReturnMessage(ret, null,
                    0, mm.LogicalCallContext, (IMethodCallMessage)msg);
            }

            /// <summary>
            /// Flashオブジェクトにロードされたswfに定義されたメソッドを呼び出す
            /// </summary>
            /// <param name="methodname"></param>
            /// <param name="_params"></param>
            /// <returns></returns>
            private string invoke(string methodname, object[] _params)
            {
                var doc = new XmlDocument();
                var invoke = (XmlElement)doc.AppendChild(doc.CreateElement("invoke"));
                invoke.SetAttribute("name", methodname);
                invoke.SetAttribute("returntype", "xml");
                invoke = (XmlElement)invoke.AppendChild(doc.CreateElement("arguments"));

                foreach (var param in _params)
                {
                    invoke.AppendChild(create_args(doc, param));
                }

                string response = String.Empty;
                try
                {
                    response = flashobject.CallFunction(doc.InnerXml);
                }
                catch
                {
                }

                return response;
            }

            /// <summary>
            /// 引数処理を行う
            /// </summary>
            /// <param name="doc"></param>
            /// <param name="param"></param>
            /// <returns></returns>
            private XmlElement create_args(XmlDocument doc, object param)
            {
                XmlElement element;
                if (param is int
                    || param is uint
                    || param is long
                    || param is ulong
                    || param is short
                    || param is ushort
                    || param is byte
                    )
                {
                    element = doc.CreateElement("number");
                    element.InnerText = param.ToString();
                }
                else if (param is bool)
                {
                    element = doc.CreateElement(param.ToString().ToLower());
                    element.InnerText = "";
                }
                else
                {
                    element = doc.CreateElement("string");
                    element.InnerText = param.ToString();
                }
                return element;
            }
        }
    }

    /// <summary>
    /// Flashオブジェクトからのコールバックを処理するクラス
    /// </summary>
    /// <remarks>
    /// <seealso cref="CallBackProxy&lt;T&gt;">CallBackProxy&lt;T&gt;</seealso>のインスタンス作成を容易にするための
    /// ジェネリックメソッドを提供します
    /// </remarks>
    public class CallBackProxy
    {
        /// <summary>
        /// <seealso cref="CallBackProxy&lt;T&gt;">CallBackProxy&lt;T&gt;</seealso>のインスタンスを作成します
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="owner"></param>
        /// <param name="flashobject"></param>
        /// <returns></returns>
        public static CallBackProxy<T> CreateInstance<T>(T owner, AxShockwaveFlash flashobject)
        {
            return new CallBackProxy<T>(owner, flashobject);
        }
    }

    /// <summary>
    /// Flashオブジェクトからのコールバックを処理するクラス
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class CallBackProxy<T>
    {
        T owner;
        AxShockwaveFlash flashobject;
        /// <summary>
        /// Flashオブジェクトを初期化します
        /// </summary>
        /// <param name="owner"></param>
        /// <param name="flashobject"></param>
        public CallBackProxy(T owner, AxShockwaveFlash flashobject)
        {
            this.owner = owner;
            this.flashobject = flashobject;
            flashobject.FlashCall += FlashCall;
        }

        /// <summary>
        /// Flashオブジェクトからのコールバックイベント
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void FlashCall(object sender, _IShockwaveFlashEvents_FlashCallEvent e)
        {
            var document = new XmlDocument();
            document.LoadXml(e.request);
            callfunc(document);
        }

        /// <summary>
        /// Flashオブジェクトからのコールバック処理
        /// </summary>
        /// <param name="document"></param>
        private void callfunc(XmlDocument document)
        {
            var invoke = document.GetElementsByTagName("invoke");
            string name = invoke[0].Attributes["name"].Value;
            XmlNode _param = document.GetElementsByTagName("arguments")[0];
            var t = owner.GetType();
            var m = t.GetMethod(name);
            m.Invoke(owner, parseparam(_param.ChildNodes));
        }

        /// <summary>
        /// Flashオブジェクトからのコールバック引数処理
        /// </summary>
        /// <param name="_params"></param>
        /// <returns></returns>
        private object[] parseparam(XmlNodeList _params)
        {
            List<object> retparam = new List<object>(_params.Count);
            foreach (XmlNode param in _params)
            {
                if (param.Name == "number")
                {
                    retparam.Add(int.Parse(param.InnerText));
                }
                else if (param.Name == "true"
                    || param.Name == "false")
                {
                    retparam.Add(bool.Parse(param.Name));
                }
                else
                {
                    retparam.Add(param.InnerText);
                }
            }
            return retparam.ToArray();
        }
    }
}

C#からメソッドの呼び出しを行う

メソッドの名前が必要になるので、それらと同じメソッドを定義したクラスを用意します。
ActionScript

function hoge():Void{ }
function hogehoge(arg:Number):Void{ }
function hogehoge2():Number{ }

と定義されていれば、

public class FlashObjectCallFunc : FlashObject.CallFunc<FlashObjectCallFunc>
{
    public void hoge(){ }
    public void hogehoge(int arg){ }
    public int hogehoge2(){ }
}

このようにC#で定義します。

メソッドの呼び出しは以下のようにします。

var axfunc = new FlashObjectCallFunc.CreateInstance(axShockwaveFlash);
axfunc.hoge();

axShockwaveFlashはFlashのコンテナのインスタンスです。

ActionScriptからメソッドの呼び出しを行う

FlashObject.CallBackProxy<App> axcb = FlashObject.CallBackProxy.CreateInstance(owner, owner.axShockwaveFlash);

Appは呼び出されるメソッドのクラスです。
ownerはAppのインスタンスです。

ActionScriptから呼び出す場合以下のようにすればAppクラスのpiyoメソッドを呼び出すことができます。

ExternalInterface.call("piyo");

参考
[サンプル] 透過プロキシ - C# によるプログラミング入門 | ++C++; // 未確認飛行 C