COM のレイトバインディング
COM を参照設定して利用するのは静的参照(アーリーバインディング)で、参照無しに動的に利用できるようにすることを動的参照(レイトバインディング)と言います。
記事にするきっかけ
C# 環境で COM クライアント側のレイトバインディングのサンプルを探しましたが、わかりやすいものが見当たりませんでした。
そのため、自分のサンプル実装をふまえてメモとして残しておくことにしました。
Windows Script で ActiveX を制御するようなコードを書いたことがあれば、それを C# で記述したものと考えてください。
環境
- Windows 10 Professional
- Visual Studio 2019 Professional
- COM Automation サーバー(C++, MFC で作成したダイアログベースアプリケーション)
- COM クライアントアプリケーション(ここで実装した C# WPF アプリケーション)
C# でのサンプルコード
COM インスタンスの生成
COM インスタンスを dynamic 型で定義し、ProgID を指定して生成するだけです。
usingSystem;usingSystem.Collections.Generic;usingSystem.Diagnostics;usingSystem.Linq;usingSystem.Reflection;usingSystem.Text;usingSystem.Threading.Tasks;usingSystem.Windows;usingSystem.Windows.Controls;usingSystem.Windows.Data;usingSystem.Windows.Documents;usingSystem.Windows.Input;usingSystem.Windows.Media;usingSystem.Windows.Media.Imaging;usingSystem.Windows.Navigation;usingSystem.Windows.Shapes;usingSystem.Runtime.InteropServices;usingSystem.Threading;usingSystem.Runtime.CompilerServices;// for MethodImplusingSystem.Runtime.InteropServices.ComTypes;// for IConnectionPointContainer, IConnectionPointusingSystem.Windows.Threading;namespaceWpfApp1{/// <summary>/// MainWindow.xaml の相互作用ロジック/// </summary>publicpartialclassMainWindow:Window{publicMainWindow(){InitializeComponent();}protecteddynamicm_comInstance=null;// COM インスタンスprivatevoidCreateButton_Click(objectsender,RoutedEventArgse){if(null==m_comInstance){///////////////////////////////////// COM のインスタンスを作成するconststringprogID="OutOfProcessSample.Application"; // ProgID を指定するTypecomType=Type.GetTypeFromProgID(progID);m_comInstance=Activator.CreateInstance(comType);Debug.WriteLine("Instance created.");COM インスタンスの開放
privatevoidQuitButton_Click(objectsender,RoutedEventArgse){if(null!=m_comInstance){// Quit メソッドでアプリケーションを閉じる(COM サーバーに Quit というメソッドがある前提)m_comInstance.Quit();// COMオブジェクトの開放Marshal.ReleaseComObject(m_comInstance);m_comInstance=null;}return;}属性の取得、設定、メソッドの呼び出し
普通に呼び出すだけです。
属性やメソッドにどのようなものがあるかは、Windows Kit に付属の 「OLE/COM Object Viewer」アプリで確認するとよいでしょう。
///////////////////////////////////// 属性の取得objectname=m_comInstance.Name;Debug.WriteLine("Name = {0}",name);objectid=m_comInstance.Id;Debug.WriteLine("Id = {0}",id);objectstatus=m_comInstance.Status;Debug.WriteLine("Status = {0}",status);///////////////////////////////////// 属性の設定m_comInstance.Visible=-1;Thread.Sleep(1000);///////////////////////////////////// メソッドの呼び出しm_comInstance.SetRect(0,0,200,200);Thread.Sleep(1000);m_comInstance.SetRect(100,100,400,400);イベントの受信
COM サーバーのイベントインターフェースに対応する場合のみ実装が必要です。
イベントインターフェースを受信する手順とコードは以下のようになります。
メンバ の用意
protectedintm_sinkCookie=-1;// イベントインターフェース CookieprotectedIConnectionPointm_connectionPoint=null;// イベント接続ポイントprotectedOutOfProcessEvents_SinkHelperm_sink=null;// イベントシンクイベント受信設定:イベント接続部
///////////////////////////////////// COM からのイベント受信// COM インスタンスから、IConnectionPointContainer を取得するIConnectionPointContainerconnectionPointContainer=m_comInstanceasIConnectionPointContainer;// 指定 GUID から IConnectionPoint を取得するGuidguid=newGuid("00000000-0000-0000-0000-000000000000");// ← イベントインターフェースIDを指定connectionPointContainer.FindConnectionPoint(refguid,outm_connectionPoint);m_sink=newOutOfProcessEvents_SinkHelper();// イベント受信クラスの生成m_sink.win=this;// イベント受信の結果を画面に反映するためにセットする。(独自実装:無くてもよい)m_connectionPoint.Advise(m_sink,outm_sinkCookie);// イベント受信クラスを接続Debug.WriteLine("IConnectionPoint::Advise ok.");イベント受信設定:イベント受信クラス
// COM のイベントに関連する enum も記載しておく。(後述の「イベント受信インターフェース記述の手順」を参照)publicenumLibStatus{LibStatus_Fatal=-1,// 0xFFFFFFFFLibStatus_Unknown=0,LibStatus_Ready=1,LibStatus_Busy=2,LibStatus_Done=3,LibStatus_Error=1000}// イベント受信インターフェース(後述の「イベント受信インターフェース記述の手順」を参照)[TypeLibType(4096)][Guid("00000000-0000-0000-0000-000000000000")]// ← 具体的なイベントIF の ID を記載します[InterfaceType(2)][ComImport]publicinterface_IOutOfProcessSampleEvents{[DispId(1)][MethodImpl(MethodImplOptions.PreserveSig|MethodImplOptions.InternalCall,MethodCodeType=MethodCodeType.Runtime)]voidClickIn([ComAliasName("stdole.OLE_XPOS_PIXELS")]intxCoord,[ComAliasName("stdole.OLE_YPOS_PIXELS")]intyCoord);[DispId(2)][MethodImpl(MethodImplOptions.PreserveSig|MethodImplOptions.InternalCall,MethodCodeType=MethodCodeType.Runtime)]voidStatusChange(LibStatusStatus);} // イベント受信クラス(イベント受信インターフェースのラッパークラスを自前で実装します)// 基本的にイベントインターフェース名やメソッドはインターフェースからそのまま持ってくればよいです。[ClassInterface(ClassInterfaceType.None)]publicsealedclassOutOfProcessEvents_SinkHelper:_IOutOfProcessSampleEvents{publicMainWindowwin=null;// GUI にイベントを表示するための独自実装(無くてもよい)publicvoidClickIn(intxCoord,intyCoord){Debug.WriteLine("ClickIn {0}, {1}.",xCoord,yCoord);// GUI にイベントを表示するための独自実装(無くてもよい) ===>>>Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background,newAction(()=>{win.ClickLabel.Content=String.Format("{0}, {1}",xCoord,yCoord);}));// GUI にイベントを表示するための独自実装(無くてもよい) ===<<<<}publicvoidStatusChange(LibStatusStatus){Debug.WriteLine("StatusChange : {0}",Status);// GUI にイベントを表示するための独自実装(無くてもよい) ===>>>Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background,newAction(()=>{win.StatusLabel.Content=Status;}));// GUI にイベントを表示するための独自実装(無くてもよい) ===<<<<}}イベント受信の切断
privatevoidQuitButton_Click(objectsender,RoutedEventArgse){if(null!=m_connectionPoint){// イベントシンクとの切断m_connectionPoint.Unadvise(m_sinkCookie);m_sink=null;// COMオブジェクトの開放Marshal.ReleaseComObject(m_connectionPoint);m_connectionPoint=null;}return;}イベント受信インターフェース記述の手順
Visual Studio で COM のオブジェクト参照を設定して、いったんビルドします。
すると相互運用性のために DLL が生成されるので DLL パスを確認します。(下図の赤枠参照)
※一連の作業後に COM のオブジェクト参照は削除してください。
dotPeekをインストールして、起動します。
※アセンブリをデコンパイルできるツールであれば、他のツールでも構いません。
dotPeekを起動したら、相互運用性のために生成された DLL のパスを読み込ませます。
そして、イベントI/F (赤枠)を選択します。
右側に表示されているイベントインターフェースを namespace の中身だけ、C# のソース(.cs)に貼り付けます。
利用している列挙型などあれば、あわせて namespace の中身だけを C# にコピーします。
前述の記述例を参考にイベントインターフェース を継承するラッパークラスを定義します。
気づき
- イベントを除けば、ProgID さえわかってしまえば、レイトバインディングで実装することができます。
- 利用する COM は automation (IDispatch 継承)である必要があります。
- レイトバインディングとはいっても、イベントインターフェースを利用する場合は、イベントインターフェースの ID がソースに埋め込まれる形になる(この例では「_IOutOfProcessSampleEvents」の定義部分)ため、その制約が残ります。
