COM相互運用機能の利用はじめにMicrosoft社は.NETの最初のバージョンを開発する際に、それまで8年以上もの間アプリケーション開発に使われてきた既存のWindowsテクノロジ――つまりCOM――との連携機能を組み込む必要があると考えました。このような考えからMicrosoft社が.NETランタイムに追加したのが「COM相互運用機能」です。この機能は両方向に対応しており、.NETコードからCOMコンポーネントを呼び出すことも、COMコードから.NETコンポーネントを呼び出すこともできます。 この記事は、COMコンポーネント開発やインターフェイスの概念に精通しているVB6プログラマを対象としています。ここではCOMの背景を復習してVB6でのCOM処理を解説したうえで、COMとスムーズにやり取りできる.NETコンポーネントの設計方法を示します。別の記事で、.NETアプリケーション内で既存のCOMコンポーネントを使う方法も説明する予定です。 COMに精通しているエキスパートから見れば単純化しすぎの部分もあると思いますが、ここではCOMコードの不足部分を.NETコンポーネントで補おうとする開発者にとって重要なポイントを中心に説明します。 COMとVB6の背景 COMで重要な点の1つは、すべてがインターフェイスから呼び出されるということです。 ここでこう思うかもしれません。「ちょっと待ってくれ、VB6で何百ものクラスをコーディングしてきたけど、インターフェイスなんて一度も必要なかったぞ」。そのとおりです。VB6ではバックグラウンドでインターフェイスが提供されていたので、開発者が直接操作する必要はありませんでした。開発者がクラスのパブリックメソッドを定義するだけで、VB6がそのメソッドをCOMインターフェイスに含め、そのインターフェイスをクラスに実装してくれました。 例えば、ActiveX DLLプロジェクトに次のような Option Strict Public Sub MoveForward() ... End Sub Public Sub FindCar() ... End Sub このクラスをDLLにコンパイルすると、VB6によって OLE View(OLE/COMオブジェクトビューア)というMicrosoftユーティリティは、COMコンポーネントで定義されている型を調べるツールです。OLE Viewは、[スタート]-[すべてのプログラム]-[Microsoft Visual Studio 6.0]-[Microsoft Visual Studio 6.0 Tools]-[OLE View]にインストールされています。OLE Viewを起動し、[File]メニューの[View TypeLib...]を選択して、上記の例でコンパイルしたActiveX DLLを選択します。この場合の出力のうち、重要な部分の抜粋を以下に示します。 interface _Robot : IDispatch { ... HRESULT MoveForward(); ... HRESULT FindCar(); }; coclass Robot { [default] interface _Robot; }; まず見てほしいのは、 COMとデフォルトインターフェイスについての詳しい説明は省きますが、ここでVB6コンポーネントの観点から見て重要な点は、クラスに実装可能な全インターフェイスのうち、遅延バインディングをサポートしているのはデフォルトインターフェイスのみであるということです。従って、遅延バインディングのみに対応しているVBScriptなどのスクリプティングクライアントからは、デフォルトインターフェイスのメソッドしか見えません。 複数のインターフェイス COMでは、複数のインターフェイスを実装できます。上記のActiveX DLLの例に、 Option Explicit Public Sub CleanKitchen() ... End Sub Public Sub WashCar() ... End Sub 次に、 Option Explicit Implements IMaid Public Sub MoveForward() End Sub Public Sub FindCar() End Sub Private Sub IMaid_CleanKitchen() End Sub Private Sub IMaid_WashCar() End Sub このDLLをコンパイルしてからOLE Viewで見てみると、次のように出力されます(ここでも重要な部分以外は省略します)。 interface _Robot : IDispatch { ... HRESULT MoveForward(); ... HRESULT FindCar(); }; coclass Robot { [default] interface _Robot; interface _IMaid; }; interface _IMaid : IDispatch { .. HRESULT CleanKitchen(); .. HRESULT WashCar(); }; coclass IMaid { [default] interface _IMaid; }; 前にも述べたとおり、VB6では開発者がCOMインターフェイスを直接作成することはできません。ただし、VB6によってすべてのクラスに(先頭にアンダースコアの付いた)COMインターフェイスが作成されるので、VB6でクラスに VB6で自動的に作成された .NETまでもう少しここまでくれば、.NETコードに近づいてきました。COMに公開可能な.NETコンポーネントの作成に進むには、まず、こうした概念をよく理解しておくことが重要です。家を建てる前に必要な設計図のようなものと考えてください。設計図なしでも進めることは可能ですが、正しく始めた方が良い家になります。 VB6でCOMオブジェクトを作成する際に内部でどのような処理が行われているか、簡単に復習します。
メソッドはどこに消えた!?多くの人は、初めて.NETオブジェクトをCOMに公開してそのオブジェクトをVB6から使おうとしたときに、メソッドが1つもないと驚きます。COMに公開する簡単な.NETクラスライブラリを作成して、なぜそうなるのか見ていきましょう。 VB.NETの場合
Option Strict On Option Explicit On Namespace QuickNET Public Class Bee Public Sub FindFlowers() End Sub Public Sub MakeHoney() End Sub End Class End Namespace C#の場合
using System; namespace QuickNET { public class Bee { public void FindFlowers() { } public void MakeHoney() { } } } この.NETコンポーネントからCOMタイプライブラリを作成し、どのようになるか見てみます(この段階では、COMタイプライブラリを作成するだけでCOM用の登録は行いません)。Visual Studio .NETコマンドプロンプト([スタート]→[すべてのプログラム]→[Microsoft Visual Studio .NET]→[Visual Studio .NET Tools]→[Microsoft Visual Studio.NET Command Prompt])を開き、上記をコンパイルした.NETコンポーネントのディレクトリに移動し、以下のコマンドを入力します。 TLBEXP.EXE QuickNET.dll /out:Com.QuickNET.tlb 「TLBEXP.EXE」ユーティリティは、.NETアセンブリからCOMタイプライブラリを生成します。生成されるタイプライブラリには任意の名前を付けることができますが、通常は慣例的ルールに従って.tlb拡張子を付けます。また、私はCOMに公開するタイプライブラリの名前の先頭に「Com.」と付けることにしています。次に、OLE Viewで「Com.QuickNET.tlb」というタイプライブラリを開きます。以下に重要な部分を抜粋します。
coclass Bee {
[default] interface _Bee;
interface _Object;
};
interface _Bee : IDispatch {
};
これは、VB6のCOMタイプライブラリとよく似ています。 注
この記事の説明においては、
_Objectインターフェイスは重要ではありません。あらゆるクラスの継承元となる.NETのObjectクラスは_Objectインターフェイスを公開しているので、エクスポートされた型すべてに_Objectインターフェイスが追加されます。 TLBEXP.EXEユーティリティで 各種の制御それでは、VB6で.NETコンポーネントのこれらのメソッドを表示するにはどうすればよいのでしょうか。VB6とCOMについて言えることを、.NETにも適用できます。基本的に、必要な操作は以下の2つです。
1番目は簡単です。COMに公開したいメソッドを含む.NETインターフェイスを追加します。 VB.NETの場合
Public Interface IBee Sub FindFlower() Sub MakeHoney() End Interface C#の場合
public interface IBee { void FindFlower(); void MakeHoney(); } 次にこのインターフェイスを VB.NETの場合
Public Class Bee Implements IBee Public Sub FindFlower() Implements IBee.FindFlower End Sub Public Sub MakeHoney() Implements IBee.MakeHoney End Sub End Class C#の場合
public class Bee : IBee { public void FindFlower() { } public void MakeHoney() { } } このコンポーネントをコンパイルし、TLBEXP.EXEユーティリティを実行し、OLE Viewで確認します。結果は意外かもしれません。
interface IBee : IDispatch {
...
HRESULT FindFlower();
...
HRESULT MakeHoney();
};
coclass Bee {
[default] interface _Bee;
interface _Object;
interface IBee;
};
interface _Bee : IDispatch {
};
標準COMインターフェイスとして、 それでも、 Dim bee As IBee Set bee = New Bee bee.FindFlower bee.MakeHoney しかしながら、もっときちんと統合するには、 属性属性になじみがない人のために、簡単に説明しましょう。属性とは、「記述的な宣言」です。型、フィールド、メソッド、クラスなどのプログラミング要素に「注釈」を付ける役割を果たします。属性には関連する値を指定でき、その値と属性情報は、他のすべての.NETメタデータと一緒に保存されます。属性を使うと、CLR(共通言語ランタイム)に対してコードを説明したり、実行時のアプリケーション動作を変更できます。 属性を使用して、TLBEXP.EXEによるタイプライブラリの作成方法を制御し、こうしたデフォルトインターフェイスが作成されないように指定できます。これを行うには、 VB.NETの場合
Option Strict On Option Explicit On Imports System.Runtime.InteropServices Namespace QuickNET <ClassInterface(ClassInterfaceType.None)> _ Public Class Bee Implements IBee Public Sub FindFlower() Implements IBee.FindFlower End Sub Public Sub MakeHoney() Implements IBee.MakeHoney End Sub End Class End Namespace C#の場合
using System; using System.Runtime.InteropServices; namespace QuickNET { [ClassInterface(ClassInterfaceType.None)] public class Bee : IBee { public void FindFlower() { } public void MakeHoney() { } } } これをコンパイルしてからタイプライブラリを作成し直すと、OLE Viewには次のように整った出力が表示されます。 interface IBee : IDispatch { ... HRESULT FindFlower(); ... HRESULT FindHoney(); }; coclass Bee { interface _Object; [default] interface IBee; }; GUIDの制御最後に制御するのはGUID(グローバル一意識別子)です。これまでもGUIDは見たことがあるでしょう。例えば、次のようなGUIDがあります。 82CC3E6A-148E-4b77-866E-598DBEDC5C74 COM内のすべてのインターフェイスとすべてのコクラス(作成可能クラスオブジェクト)は、それぞれ固有のGUIDで識別されます。VB6では、開発者の代わりにVB6がGUIDの作成を制御します。VB6で常に同じGUIDがコクラスやインターフェイスに使われるようにするには、VB6プロジェクトをコンパイルするときに「バイナリ互換性」モードを使います。.NETのTLBEXP.EXEユーティリティでも、COMにエクスポートする各インターフェイスとクラスのGUIDが自動生成されますが、属性を使うと開発者がGUIDを定義することができます。 なぜそのような制御が必要なのでしょうか。.NETオブジェクトをCOMコンポーネントとして登録する場合、レジストリエントリが作成されます。これらのレジストリエントリには、COMでクラスとインターフェイスを識別するためのGUIDもあります。自分で定義したGUIDを指定しなかった場合、TLBEXP.EXEはCOMタイプライブラリを作成し直すたびに新しいGUIDを生成します。作成済みのCOMクライアントで古いGUIDへの参照を使用している場合には、GUIDが変更されると動作しなくなります。クラスとインターフェイスのGUIDを制御して特定のGUIDを定義すれば、GUIDは変わらず、COMクライアントはコンパイルし直さなくても新しい.NETコンポーネントを使用できます。 作成したクラスとインターフェイスのGUIDは、属性を使って定義できます。独自のGUIDを「考え出す」代わりに、VS .NET(および以前のバージョンのVisual Studio)には、GUIDを生成する「guidgen.exe」というツールが付属しています。このツールは「Program FilesMicrosoft Visual Studio .NET 2003Common7Tools」ディレクトリにあります。このファイルをダブルクリックすると、次のような画面が表示されます。 GUIDはさまざまな場所で使われるので、guidgenでは4種類の形式のGUIDを作成できるようになっています。ここでは4番目の、レジストリ形式のGUIDを作成します。GUIDの前後の中かっこ({})は不要なので削除する必要があります。[New GUID]をクリックすると新しいGUIDが生成され、[Copy]をクリックすると生成されたGUIDがクリップボードにコピーされます。 では、 VB.NETの場合
<Guid("0490E147-F2D2-4909-A4B8-3533D2F264D0")> _ Public Interface IBee Sub FindFlower() Sub MakeHoney() End Interface C#の場合
[Guid("0490E147-F2D2-4909-A4B8-3533D2F264D0")] public interface IBee { void FindFlower(); void MakeHoney(); } 次に、クラスにもGUIDが必要です。guidgenに戻り、[New GUID]をクリックし、[Copy]をクリックします。クラスの VB.NETの場合
<ClassInterface(ClassInterfaceType.None), _ Guid("03AD5D2D-2AFD-439f-8713-A4EC0705B4D9")> _ Public Class Bee Implements IBee Public Sub FindFlower() Implements IBee.FindFlower End Sub Public Sub MakeHoney() Implements IBee.MakeHoney End Sub End Class C#の場合
[ClassInterface(ClassInterfaceType.None)] [Guid("03AD5D2D-2AFD-439f-8713-A4EC0705B4D9")] public class Bee : IBee { public void FindFlower() { } public void MakeHoney() { } } これで、tblexpが常に同じGUIDを使用するようになります。 配置これまでこの記事では、COMタイプライブラリの生成にTLBEXP.EXEユーティリティを使ってきました。これは、.NETコンポーネントをCOMに公開する仕組みだけに注目するためでした。COMコンポーネントと同じように使える.NETコンポーネントを実際に作成する場合は、他のCOMコンポーネントと同じように登録を行う必要があります。 「regsvr32.exe」はご存知の通り、COMコンポーネントの登録と登録解除を行うユーティリティです。これは.NETコンポーネントには使用できません。代わりに、.NETの「regasm.exe」というユーティリティを使用します(regasmはRegister Assembly(アセンブリ登録)の略)。regasm.exeは、.NETコンポーネントを通常のCOMコンポーネントのように見せるレジストリエントリを追加します。 regasm.exeユーティリティは、VB6で使えるCOMタイプライブラリを生成する機能もあります。regasm.exeの/tlbオプションを指定すると、TLBEXP.EXEと同じ処理を実行します。従って、COM用の REGASM myassem.dll /tlb:com.myassem.tlb 作成したオブジェクトは、これでCOMコンポーネントとして登録され、VB6からこのコンポーネントの事前バインディング用に参照設定できるCOMタイプライブラリが生成されます。後は、COMから.NETアセンブリを見つけられるようにするだけです。 ローカル配置かGACか作成した.NETアセンブリをCOMコンポーネントとして登録すると、COMからその.NETコンポーネントのインスタンスを作成しようとしたときに、.NETランタイムのコピーがロードされます。この.NETランタイムがアセンブリを発見できる必要があります。COM環境で実行している場合も、アセンブリを探す際の.NETの以下のルールは適用されます。
2番目は簡単ですが、あまり柔軟性がありません。例えば、.NETアセンブリをCOM exeクライアントと同じディレクトリにコピーすれば、ランタイムはこれを見つけることができます。ただし、VB6 IDEでビルドとデバッグを行うときは、VB6.EXEの場所がローカルディレクトリになります。従って、VB6 IDE内で実行しているかどうかによって、.NETアセンブリのコピー場所を変えなければなりません。VB6.EXEと同じディレクトリにファイルをコピーするのもよくないことです。.NETアセンブリを変更したら、両方の場所にコピーしなければならなくなります。このような理由から、COM相互運用機能では通常、アセンブリをグローバルアセンブリキャッシュ(GAC)に配置することが最善の方法となります。 アセンブリをGACに配置する前に、固定バージョン番号スキームと厳密名キーペアを指定する必要があります。固定バージョン番号スキームの指定は簡単です。AssemblyInfoファイルで、 次に、厳密名キーペアを生成します。これにはsn.exeツールを使用します。Visual Studio .NET 2003コマンドプロンプトから.NETプロジェクトのディレクトリに移動し、以下のコマンドを入力します。 sn -k mykey.snk 次に、AssemblyInfoで、 gacutil -I myassembly.dll これによってアセンブリはGACにインストールされます。これで、どのCOMクライアントがこの.NETコンポーネントのインスタンスを作成する場合も、ランタイムはこのアセンブリを見つけることができます。 推奨手順のまとめこの記事では、多くの内容を説明しました。COM相互運用機能を簡単に利用する推奨手順をもう一度復習しましょう。
生成したタイプライブラリへの参照をVB6プロジェクトに追加すると、事前バインディングを利用できます。VBScriptなどのスクリプティングクライアントからもオブジェクトにアクセスできますが、その場合に使用できるのはデフォルトインターフェイスのメソッドのみです。 .NETコンポーネントを拡張する場合は、既存のインターフェイスは変更しないでください。新しいメソッドを追加する必要がある場合、新しいインターフェイスを作成し(GUIDも指定します)、そのインターフェイスを実装します。既存のインターフェイスを変更すると、そのインターフェイスを使用するように既にコンパイルされているクライアントが動作しなくなることがあります。 著者紹介Patrick Steele(Patrick Steele)
ミシガン南東部で独立コンサルタントとして活動。ASP.NET、WinForms、COM+、COM相互運用機能など.NETの広範な経験を持つ。ミシガン五大湖地域の.NETユーザーグループ(GANG - http://www.migang.org)の主事でもあり、過去5年にわたりMicrosoft社の.NET MVPを受賞。連絡したい場合は、ブログhttp://weblogs.asp.net/psteeleから、またはpatrick@mvps.orgまで。
関連記事 最新トップニュース
|
|