ActiveXを様々なアプリケーションから動作できるようにする。

ActiveXを.NETで使用するためにaximp等のツールで生成されるAxHostのラッパー。
これ、メインスレッドがSTAで動作してるなら問題なく使うことができるんですけど、
MTAだと普通に使うことができないんですよね。
コンソールアプリとかASP.NETとかで、new すると例外吐いて
オブジェクトのインスタンスすら生成できない。
そんなわけで、STAスレッドを動かしてオブジェクトのインスタンスを生成すれば、
解決できるんですけど、
次に問題になるのがInvokeReuiredプロパティに状態に応じて
メソッドやプロパティの呼び出しをマーシャリング(同期はInvokeメソッド、非同期はBeginInvokeメソッド、EndInvokeメソッド)しないといけないという
問題があったりと、いろいろと考慮する必要があって面倒。
そんなわけで、これらの問題を解決できるクラス作ってみました。

ポイントは、

  • STAのワーカースレッドでWindwosメッセージポンプを動作させる。
  • プロパティとメソッドはRealProxyクラスでAOPライクにマーシャリング処理させる。
  • イベントハンドラはSynchronizationContextクラスのPostメソッドで実行させる。

の部分ですかね。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Contexts;
using System.Runtime.Remoting.Proxies;
using System.Runtime.Remoting.Messaging;
using System.Reflection;
using System.Threading;
using System.Windows.Forms;

namespace Kazupon.Windows.Forms
{
  delegate object AxHostOperationDelegate();


  public sealed class AxMultiAppHost : RealProxy, IDisposable
  {
    private static readonly object syncObject;
    private AxHost axHostObject;
    private Type axHostObjectType;
    private Dictionary<int, Delegate> events;
    private static SynchronizationContext context;
    private static Thread worker;
    private static ManualResetEvent windowsMessageTerminated;


    static AxMultiAppHost()
    {
      syncObject = new object();
    }


    private AxMultiAppHost(AxHost axHost, Type axHostType)
      : base(axHostType)
    {
      axHostObject = axHost;
      axHostObjectType = axHostType;
      events = new Dictionary<int, Delegate>();
    }


    // AxHost派生クラスのインスタンスを作成するファクトリメソッド
    public static AxHostObject CreateAxHostObject<AxHostObject>() where AxHostObject : AxHost, new()
    {
      AxHostObject axHost = Initialize<AxHostObject>();
      AxMultiAppHost proxy = new AxMultiAppHost(axHost, axHost.GetType());
      return (AxHostObject)proxy.GetTransparentProxy();
    }


    // AxHost派生クラスのインスタンスの初期化処理
    private static AxHostObject Initialize<AxHostObject>() where AxHostObject : AxHost, new()
    {
      AxHostObject axHost = null;
      context = AsyncOperationManager.SynchronizationContext;

      // STA属性スレッドでAxHost派生クラスのインスタンスを生成する
      using (ManualResetEvent windowsMessageReady = new ManualResetEvent(false))
      {
        ThreadStart action = delegate
        {
          axHost = new AxHostObject();
          axHost.CreateControl();

          windowsMessageReady.Set();

          Application.Run();

          windowsMessageTerminated.Set();
        };
        worker = new Thread(action);
        worker.Name = "ActiveX Hosting Thread";
        worker.IsBackground = true;
        worker.SetApartmentState(ApartmentState.STA);
        worker.Start();

        windowsMessageReady.WaitOne();
      }

      if (axHost == null) throw new ApplicationException("Cannot Create ActiveX !!");

      return axHost;
    }


    ~AxMultiAppHost()
    {
      this.Dispose(false);
    }


    public void Dispose()
    {
      this.Dispose(true);
      GC.SuppressFinalize(this);
    }


    private void Dispose(bool disposing)
    {
      if (disposing)
      {
        // TODO: Release Managed Resource Code !!
      }

      if (worker != null)
      {
        // Windowsメッセージポンプの終了処理
        windowsMessageTerminated = new ManualResetEvent(false);
        Application.Exit();
        windowsMessageTerminated.WaitOne();
        worker.Join();
        axHostObject.Dispose();
      }
    }

    // プロキシ実行処理
    public override IMessage Invoke(IMessage msg)
    {
      IMethodCallMessage methodCallMessage = msg as IMethodCallMessage;
      MethodInfo method = methodCallMessage.MethodBase as MethodInfo;

      object returnValue = null;
      string methodName = method.Name;
      Type returnType = method.ReturnType;
      BindingFlags flags = BindingFlags.InvokeMethod;
      int argCount = methodCallMessage.ArgCount;
      object invokeObject = this.axHostObject;
      Type invokeType = this.axHostObjectType;
      object[] args = null;

      if (argCount == 1 && returnType == typeof(void)
        && (methodName.StartsWith("add_") || methodName.StartsWith("remove_")))
      {
        // イベント

        bool removeHandler = methodName.StartsWith("remove_");
        methodName = methodName.Substring(removeHandler ? 7 : 4);

        Delegate handler = methodCallMessage.InArgs[0] as Delegate;
        if (handler == null) return new ReturnMessage(new ArgumentNullException("handler"), methodCallMessage);

        try
        {
          // イベントハンドラの追加/削除処理
          if (removeHandler) RemoveEventHandler(methodName, handler);
          else AddEventHandler(methodName, handler);
        }
        catch (Exception exception)
        {
          return new ReturnMessage(exception, methodCallMessage);
        }
      }
      else
      {
        if (methodName.StartsWith("get_"))
        {
          // 取得プロパティ

          methodName = methodName.Substring(4);
          flags = BindingFlags.GetProperty;
          args = methodCallMessage.InArgs;
        }
        else if (methodName.StartsWith("set_"))
        {
          // 設定プロパティ

          methodName = methodName.Substring(4);
          flags = BindingFlags.SetProperty;
          args = methodCallMessage.InArgs;
        }
        else
        {
          // メソッド

          args = methodCallMessage.Args;
        }

        // メソッド/プロパティの実行処理
        try
        {
          if (methodName == "InvokeRequired" || methodName == "Invoke" || methodName == "BeginInvoke" || methodName == "EndInvoke")
          {
            // InvokeRequiredプロパティ、Invokeメソッド、BeginInvokeメソッド、EndInvokeメソッドはマーシャリングなしで実行
            returnValue = invokeType.InvokeMember(methodName, flags, null, invokeObject, args);
          }
          else
          {
            // それ以外はマーシャリングが必要な場合はInvokeメソッドで実行し、必要ない場合は直接実行する
            AxHostOperationDelegate action = delegate
            {
              return invokeType.InvokeMember(methodName, flags, null, invokeObject, args);
            };

            if (axHostObject.InvokeRequired) returnValue = axHostObject.Invoke(action);
            else returnValue = action.DynamicInvoke(methodName, flags, null, invokeObject, args);
          }
        }
        catch (Exception exception)
        {
          return new ReturnMessage(exception, methodCallMessage);
        }

        // 戻り値がenum型の場合はそれに変換する
        if (method.ReturnType.IsEnum && returnValue != null)
        {
          returnValue = Enum.Parse(method.ReturnType, returnValue.ToString());
        }
      }

      ReturnMessage returnMessage = new ReturnMessage(returnValue, null, 0, methodCallMessage.LogicalCallContext, methodCallMessage);
      return returnMessage;
    }


    // イベントハンドラデリゲートで定義されている戻り値の型を取得する
    private Type GetEventHandlerReturnType(Type delegateType)
    {
      return delegateType.GetMethod("Invoke").ReflectedType;
    }


    // イベントハンドラデリゲートで定義されているパラメータの型を取得する
    private Type[] GetEventHandlerParameterTypes(Type delegateType)
    {
      MethodInfo method = delegateType.GetMethod("Invoke");
      ParameterInfo[] parameters = method.GetParameters();
      Type[] parameterTypes = new Type[parameters.Length];
      for (int i = 0; i < parameterTypes.Length; i++)
      {
        parameterTypes[i] = parameters[i].ParameterType;
      }
      return parameterTypes;
    }


    // 割り込みイベントハンドラを作成する
    private Delegate CreateInterceptHandler(Type eventHandlerType, Delegate interceptedHandler)
    {
      // AxHostオブジェクトイベントデリゲートで定義されている戻り値の型を取得
      Type returnValueType = GetEventHandlerReturnType(eventHandlerType);

      // AxHostオブジェクトイベントデリゲートで定義されている引数の型を取得
      Type[] parameterTypes = GetEventHandlerParameterTypes(eventHandlerType);

      // 割り込みイベントハンドラをホストするオブジェクトを生成する
      Type intrcptrType = typeof(EventHandlerInterceptor<>);
      Type intrcptrArg = parameterTypes[1];
      intrcptrType = intrcptrType.MakeGenericType(intrcptrArg);
      object intrcptrInstance = intrcptrType.GetConstructor(new Type[] { interceptedHandler.GetType(), }).Invoke(new object[] { interceptedHandler, });

      // AxHostオブジェクトに登録するイベントハンドラのデリゲートを動的に生成
      MethodInfo intrcptrEventHandlerMethod = intrcptrType.GetMethod("InterceptHandler");
      return Delegate.CreateDelegate(eventHandlerType, intrcptrInstance, intrcptrEventHandlerMethod);
    }


    // イベントハンドラの追加
    private void AddEventHandler(string eventName, Delegate handler)
    {
      lock (syncObject)
      {
        if (!events.ContainsKey(handler.GetHashCode()))
        {
          EventInfo eventInfo = axHostObject.GetType().GetEvent(eventName);

          // ユーザが実装したイベントハンドラを元に割り込みイベントハンドラを動的に生成
          Delegate intrcptrEventHandler = CreateInterceptHandler(eventInfo.EventHandlerType, handler);

          // 作成した割り込みイベントハンドラをAxHostオブジェクトに登録する
          eventInfo.AddEventHandler(axHostObject, intrcptrEventHandler);

          events[handler.GetHashCode()] = intrcptrEventHandler;
        }
      }
    }


    // イベントハンドラの削除
    private void RemoveEventHandler(string eventName, Delegate handler)
    {
      lock (syncObject)
      {
        if (events.ContainsKey(handler.GetHashCode()))
        {
          Delegate intrcptrEventHandler = events[handler.GetHashCode()] as Delegate;

          EventInfo eventInfo = axHostObject.GetType().GetEvent(eventName);
          eventInfo.RemoveEventHandler(axHostObject, intrcptrEventHandler);

          events.Remove(handler.GetHashCode());
        }
      }
    }



    // ユーザが実装したイベントハンドラに割り込みを入れるための内部クラス
    class EventHandlerInterceptor<EventArgsType>
    {
      // ユーザが実装したイベントハンドラ
      Delegate handler;

      public EventHandlerInterceptor(Delegate handler) { this.handler = handler; }

      // 実際にAxHostオブジェクトのイベントハンドラに登録されるメソッド
      public void InterceptHandler(object sender, EventArgsType e)
      {
        SendOrPostCallback action = delegate(object state)
        {
          // ユーザが実装したイベントハンドラを実行
          handler.DynamicInvoke(sender, e);
        };

        /*
         * どのアプリケーションモデルにおいても
         * ユーザが実装したイベントハンドラが実行されるよう
         * SynchronizationContextオブジェクトによって別コンテキストで実施してもらう
         * ・コンソール、Web、Windowsサービス
         *  → スレッドプールのワーカースレッド
         * ・Windows Forms、WPF
         *  → アプリケーションメインスレッド
         */
        context.Post(action, null);
      }
    }
  }
}