相互運用性のトラブルシューティング (Visual Basic)

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 つの問題点があります。

  1. クライアントが想定していないメソッド名が生成される可能性があります。

  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 にインストールします。

共有されないアセンブリは、呼び出し元のアプリケーションと同じディレクトリに格納してください。

参照

処理手順

チュートリアル: COM オブジェクトによる継承の実装 (Visual Basic)

方法 : 配置プロジェクトにマージ モジュールを登録する

参照

MarshalAsAttribute

Tlbimp.exe (タイプ ライブラリ インポーター)

Tlbexp.exe (タイプ ライブラリ エクスポーター)

Inherits ステートメント

概念

グローバル アセンブリ キャッシュ

その他の技術情報

COM 相互運用 (Visual Basic)