相互運用性のトラブルシューティング
更新 : 2007 年 11 月
COM オブジェクトと .NET Framework のマネージ コードの間で相互運用するときには、以下のような一般的な問題が発生することがあります。
相互運用マーシャリング
.NET Framework に含まれないデータ型を使用しなければならないケースも考えられます。相互運用アセンブリは COM オブジェクトの大部分の操作を処理しますが、マネージ オブジェクトを COM に公開するときに、使用するデータ型を制御する必要が生じることもあります。たとえば、クラス ライブラリ内の構造体では、Visual Basic 6.0 で作成された COM オブジェクトに送信される文字列に対して、BStr アンマネージ型を指定する必要があります。そのような場合は、MarshalAsAttribute 属性を使用して、マネージ型がアンマネージ型として公開されるように指定できます。
固定長文字列のアンマネージ コードへのエクスポート
6.0 以前の Visual Basic では、文字列は null 終了文字のないバイト シーケンスとして COM オブジェクトにエクスポートされます。Visual Basic 2005 では、その他の言語との互換性を考慮して、文字列をエクスポートするときに終了文字が追加されます。この非互換性の問題に対処する最善の方法は、終了文字のない文字列をバイト型 (Byte) または char 型 (Char) の配列としてエクスポートする方法です。
継承階層のエクスポート
マネージ クラスの階層は、COM オブジェクトとして公開されるときに平坦化されます。たとえば、メンバを含む基本クラスを定義し、COM オブジェクトとして公開される派生クラス内でその基本クラスを継承すると、COM オブジェクト内の派生クラスを使用するクライアントは、継承されるメンバを使用できなくなります。基本クラスのメンバに COM オブジェクトからアクセスできるのは、基本クラスのインスタンスとしてだけであり、その基本クラスも COM オブジェクトとして作成されている場合に限られます。
オーバーロードされたメソッド
Visual Basic ではオーバーロードされたメソッドを作成できますが、COM ではオーバーロードされたメソッドはサポートされていません。オーバーロードされたメソッドを含むクラスを COM オブジェクトとして公開した場合、オーバーロードされたメソッドに対して新しいメソッド名が生成されます。
たとえば、Synch メソッドについて、2 つのオーバーロードを持つクラスがあるとします。このクラスを COM オブジェクトとして公開した場合、Synch と Synch_2 というメソッド名が新たに生成されます。
COM オブジェクトのコンシューマから見ると、この名前変更には 2 つの問題点があります。
クライアントが想定していないメソッド名が生成される可能性があります。
COM オブジェクトとして公開されたクラスまたはその基本クラスに新しいオーバーロードが追加された場合、いったん生成されたメソッド名が変わることがあります。これにより、バージョン管理の問題が生じる可能性があります。
この 2 つの問題を解決するには、COM オブジェクトとして公開されるオブジェクトを開発するときに、オーバーロードの使用を避け、各メソッドに一意の名前を指定するようにします。
相互運用アセンブリを通じた COM オブジェクトの使用
相互運用アセンブリは、それらが表す COM オブジェクトに置き換わるマネージ コードとして使用します。ただし、相互運用アセンブリはラッパーであり、実際の COM オブジェクトではないため、相互運用アセンブリと標準アセンブリの使用には多少の違いがあります。相違点としては、クラスの公開、パラメータおよび戻り値のデータ型などがあります。
インターフェイスおよびクラスとして公開されるクラス
COM クラスは、標準アセンブリのクラスとは異なり、COM クラスを表すインターフェイスおよびクラスの両方として相互運用アセンブリで公開されます。インターフェイスの名前は COM クラスの名前と同じです。相互運用クラスの名前は元の COM クラスと同じですが、"Class" という語が末尾に追加されます。たとえば、COM オブジェクトの相互運用アセンブリへの参照を持つプロジェクトがあるとします。COM クラスの名前が MyComClass である場合、IntelliSense およびオブジェクト ブラウザには、MyComClass という名前のインターフェイスと MyComClassClass という名前のクラスが表示されます。
.NET Framework クラスのインスタンスの作成
通常、.NET Framework クラスのインスタンスを作成するときは、New ステートメントとクラス名を使用します。相互運用アセンブリによって表される COM クラスを持つということは、New ステートメントとインターフェイスを使用できる 1 つのケースです。COM クラスと Inherits ステートメントを使用する場合を除いて、インターフェイスはクラスと同様に使用できます。次のコードは、Command オブジェクトを、Microsoft ActiveX データ オブジェクト 2.8 ライブラリの COM オブジェクトへの参照があるプロジェクト内で作成する方法を示します。
Dim cmd As New ADODB.Command
ただし、COM クラスを派生クラスの基本クラスとして使用する場合は、COM クラスを表す相互運用クラスを使用する必要があります。次に例を示します。
Class DerivedCommand
Inherits ADODB.CommandClass
End Class
メモ : |
---|
相互運用機能アセンブリは、COM クラスを表すインターフェイスを暗黙的に実装します。Implements ステートメントを使用してこれらのインターフェイスを実装しないでください。エラーが発生します。 |
パラメータおよび戻り値のデータ型
標準アセンブリのメンバと異なり、相互運用機能アセンブリのメンバは、オブジェクトの元の宣言で使用されているデータ型とは異なるデータ型を持つ場合があります。相互運用アセンブリは COM 型を互換性のある共通言語ランタイム型に暗黙的に変換しますが、実行時エラーが発生しないように、両側で使用されるデータ型に注意する必要があります。たとえば、Visual Basic 6.0 以前で作成された COM オブジェクトでは、整数型 (Integer) の値に、対応する .NET Framework の短整数型 (Short) が使用されます。インポートしたメンバを使用する場合は、オブジェクト ブラウザでその特性を調べてから使用することをお勧めします。
モジュール レベルの COM メソッド
ほとんどの COM オブジェクトは、New キーワードを使用して COM クラスのインスタンスを作成し、そのオブジェクトのメソッドを呼び出すことによって使用されます。この規則の例外としては、AppObj または GlobalMultiUse COM クラスを含む COM オブジェクトがあります。このようなクラスは、Visual Basic 2005 のクラスのモジュール レベルのメソッドと似ています。Visual Basic 6.0 以前のバージョンでは、このようなオブジェクトのメソッドのいずれかを最初に呼び出したときに、インスタンスが暗黙的に作成されます。たとえば、Visual Basic 6.0 では、最初にインスタンスを作成しなくても、Microsoft DAO 3.6 オブジェクト ライブラリへの参照を追加し、DBEngine メソッドを呼び出すことができます。
Dim db As DAO.Database
' Open the database.
Set db = DBEngine.OpenDatabase("C:\nwind.mdb")
' Use the database object.
Visual Basic 2005 では、必ず COM オブジェクトのインスタンスを作成してから、これらのメソッドを使用する必要があります。Visual Basic 2005 でこれらのメソッドを使用するには、目的のクラスの変数を宣言し、New キーワードを使用して、オブジェクト変数にオブジェクトを割り当てます。クラスのインスタンスが 1 つだけ作成されていることを確認するときには、Shared キーワードを使用できます。
' Class level variable.
Shared DBEngine As New DAO.DBEngine
Sub DAOOpenRecordset()
Dim db As DAO.Database
Dim rst As DAO.Recordset
Dim fld As DAO.Field
' Open the database.
db = DBEngine.OpenDatabase("C:\nwind.mdb")
' Open the Recordset.
rst = db.OpenRecordset( _
"SELECT * FROM Customers WHERE Region = 'WA'", _
DAO.RecordsetTypeEnum.dbOpenForwardOnly, _
DAO.RecordsetOptionEnum.dbReadOnly)
' Print the values for the fields in the debug window.
For Each fld In rst.Fields
Debug.WriteLine(fld.Value.ToString & ";")
Next
Debug.WriteLine("")
' Close the Recordset.
rst.Close()
End Sub
イベント ハンドラで処理されないエラー
相互運用の一般的な問題の 1 つに、COM オブジェクトが発生したイベントを処理するイベント ハンドラのエラーがあります。On Error ステートメントまたは Try...Catch...Finally ステートメントを使用してエラーを明確にチェックしない限り、このようなエラーは無視されます。次の例は Microsoft ActiveX データ オブジェクト 2.8 ライブラリの COM オブジェクトへの参照を含む Visual Basic 2005 プロジェクトの一部です。
' To use this example, add a reference to the
' Microsoft ActiveX Data Objects 2.8 Library
' from the COM tab of the project references page.
Dim WithEvents cn As New ADODB.Connection
Sub ADODBConnect()
cn.ConnectionString = _
"Provider=Microsoft.Jet.OLEDB.4.0;" & _
"Data Source=C:\NWIND.MDB"
cn.Open()
MsgBox(cn.ConnectionString)
End Sub
Private Sub Form1_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
ADODBConnect()
End Sub
Private Sub cn_ConnectComplete( _
ByVal pError As ADODB.Error, _
ByRef adStatus As ADODB.EventStatusEnum, _
ByVal pConnection As ADODB.Connection) _
Handles cn.ConnectComplete
' This is the event handler for the cn_ConnectComplete event raised
' by the ADODB.Connection object when a database is opened.
Dim x As Integer = 6
Dim y As Integer = 0
Try
x = CInt(x / y) ' Attempt to divide by zero.
' This procedure would fail silently without exception handling.
Catch ex As Exception
MsgBox("There was an error: " & ex.Message)
End Try
End Sub
この例のコードは、エラーが通知されるように記述されています。しかし、Try...Catch...Finally ブロックを使用せずにこのコードを実行すると、OnError Resume Next ステートメントを使用した場合と同じようにエラーが無視されます。エラー処理がなければ、ゼロによる除算は通知なしで失敗します。この種のエラーが未処理の例外エラーとして通知されることはないため、COM オブジェクトからのイベントを処理するイベント ハンドラで例外処理の手段を講じることが重要です。
COM 相互運用エラーについて
相互運用の呼び出しでは、エラー処理を組み込んでいないと、エラーが発生してもそれに関する情報がほとんど得られません。可能な場合は、必ず構造化されたエラー処理を使用して、エラー発生時の問題に関する情報が通知されるようにしてください。これは、特にアプリケーションをデバッグするときに役立ちます。次に例を示します。
Try
' Place call to COM object here.
Catch ex As Exception
' Display information about the failed call.
End Try
例外オブジェクトの内容を調べることにより、エラーの説明 (HRESULT) や、COM エラーのソースなどの情報が得られます。
ActiveX コントロールの問題
Visual Basic 6.0 で動作するほとんどの ActiveX コントロールは、Visual Basic 2005 でも問題なく動作します。主な例外としては、コンテナ コントロール、つまり別のコントロールを視覚的に含むようなコントロールがあります。Visual Studio で正常に動作しない古いコントロールの例としては、次のようなものが挙げられます。
Microsoft Forms 2.0 フレーム コントロール
スピン コントロールとも呼ばれるアップダウン コントロール
Sheridan タブ コントロール
サポートされていない ActiveX コントロールに関する問題の回避方法はいくつかしかありません。元のソース コードがある場合は、既存のコントロールを Visual Studio に移行できます。元のソース コードがない場合は、ソフトウェアの販売元に問い合わせて、サポートされていない ActiveX コントロールを、更新された .NET 互換バージョンのコントロールに置き換えてください。
ByRef を使用した、コントロールの ReadOnly プロパティの受け渡し
Visual Basic 2005 では、古い ActiveX コントロールの ReadOnly プロパティを ByRef パラメータとして別のプロシージャに渡すときに、"Error 0x800A017F CTL_E_SETNOTSUPPORTED" などの COM エラーが発生することがあります。Visual Basic 6.0 では、同様にプロシージャを呼び出してもエラーは発生せず、パラメータは値で渡す場合と同様に扱われます。Visual Basic 2005 では、Property Set プロシージャを持たないプロパティを変更しようとしているという内容のエラー メッセージが COM オブジェクトとして表示されます。
呼び出し先のプロシージャにアクセスできる場合は、ByVal キーワードを使用して ReadOnly プロパティを受け入れるパラメータを宣言することにより、このエラーを阻止できます。以下に例を示します。
Sub ProcessParams(ByVal c As Object)
'Use the arguments here.
End Sub
呼び出し先のプロシージャのソース コードにアクセスできない場合は、呼び出し元プロシージャをさらにかっこで囲むことにより、プロパティを強制的に値で渡すことができます。たとえば、Microsoft ActiveX データ オブジェクト 2.8 ライブラリの COM オブジェクトへの参照を含むプロジェクトでは、次のように指定できます。
Sub PassByVal(ByVal pError As ADODB.Error)
' The extra set of parentheses around the arguments
' forces them to be passed by value.
ProcessParams((pError.Description))
End Sub
相互運用機能を公開するアセンブリの配置
COM インターフェイスを公開するアセンブリの配置については、いくつかの考慮事項があります。たとえば、別のアプリケーションが同じ COM アセンブリを参照する場合、問題が発生する可能性があります。このような状況として一般的なのは、新しいバージョンのアセンブリをインストールした後、別のアプリケーションが依然として古いバージョンのアセンブリを使用している場合です。同じ DLL を共有するアセンブリのいずれかをアンインストールすると、他のアセンブリでその DLL が使用できなくなる可能性があります。
この問題を回避するには、各共有アセンブリをグローバル アセンブリ キャッシュ (GAC: Global Assembly Cache) にインストールし、そのコンポーネントに対して MergeModule を使用します。アプリケーションを GAC にインストールできない場合は、バージョン固有のサブディレクトリにある CommonFilesFolder にインストールします。
共有されないアセンブリは、呼び出し元のアプリケーションと同じディレクトリに格納してください。
参照
処理手順
概念
データ型の変更点 (Visual Basic 6.0 ユーザー向け)