次の方法で共有


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

COM と .NET Framework のマネージド コードの間で相互運用を行うと、次の一般的な問題が 1 つ以上発生する可能性があります。

相互運用マーシャリング

場合によっては、.NET Framework に含まれていないデータ型を使用する必要があります。 相互運用機能アセンブリは COM オブジェクトのほとんどの作業を処理しますが、マネージド オブジェクトが COM に公開されるときに使用されるデータ型を制御する必要がある場合があります。 たとえば、クラス ライブラリの構造体では、Visual Basic 6.0 以前のバージョンによって作成された COM オブジェクトに送信される文字列に対して、BStr アンマネージ型を指定する必要があります。 このような場合は、MarshalAsAttribute 属性を使用して、マネージド型をアンマネージ型として公開することができます。

アンマネージ コードへの Fixed-Length 文字列のエクスポート

Visual Basic 6.0 以前のバージョンでは、文字列は、NULL 終端文字のないバイト シーケンスとして COM オブジェクトにエクスポートされます。 他の言語との互換性のために、Visual Basic .NET には文字列をエクスポートするときに終了文字が含まれます。 この非互換性に対処する最善の方法は、終了文字がない文字列を Byte または Charの配列としてエクスポートすることです。

継承階層のエクスポート

マネージド クラス階層は、COM オブジェクトとして公開されるとフラット化されます。 たとえば、メンバーを持つ基底クラスを定義し、COM オブジェクトとして公開されている派生クラスの基底クラスを継承する場合、COM オブジェクトで派生クラスを使用するクライアントは、継承されたメンバーを使用できません。 基底クラスのメンバーは、COM オブジェクトから基底クラスのインスタンスとしてのみアクセスでき、その後、基底クラスが COM オブジェクトとしても作成される場合にのみアクセスできます。

オーバーロードされたメソッド

Visual Basic ではオーバーロードされたメソッドを作成できますが、COM ではサポートされていません。 オーバーロードされたメソッドを含むクラスが COM オブジェクトとして公開されると、オーバーロードされたメソッドに対して新しいメソッド名が生成されます。

たとえば、Synch メソッドの 2 つのオーバーロードを持つクラスについて考えてみましょう。 クラスが COM オブジェクトとして公開されると、新しく生成されるメソッド名は SynchSynch_2になる可能性があります。

名前を変更すると、COM オブジェクトのコンシューマーに 2 つの問題が発生する可能性があります。

  1. クライアントでは、生成されたメソッド名が予期されない場合があります。

  2. COM オブジェクトとして公開されるクラスで生成されたメソッド名は、新しいオーバーロードがクラスまたはその基底クラスに追加されたときに変更される可能性があります。 これにより、バージョン管理の問題が発生する可能性があります。

両方の問題を解決するには、COM オブジェクトとして公開されるオブジェクトを開発するときに、オーバーロードを使用するのではなく、各メソッドに一意の名前を付けます。

相互運用機能アセンブリを介した COM オブジェクトの使用

相互運用機能アセンブリは、それらが表す COM オブジェクトのマネージド コードの置き換えであるかのように使用します。 ただし、これらはラッパーであり、実際の COM オブジェクトではないため、相互運用機能アセンブリと標準アセンブリの使用にはいくつかの違いがあります。 これらの違いの領域には、クラスの公開、およびパラメーターと戻り値のデータ型が含まれます。

インターフェイスとクラスの両方として公開されるクラス

標準アセンブリのクラスとは異なり、COM クラスは相互運用機能アセンブリで、COM クラスを表すインターフェイスとクラスの両方として公開されます。 インターフェイスの名前は COM クラスの名前と同じです。 相互運用クラスの名前は元の COM クラスと同じですが、"Class" という単語が追加されています。 たとえば、COM オブジェクトの相互運用機能アセンブリへの参照を持つプロジェクトがあるとします。 COM クラスの名前が MyComClassの場合、IntelliSense とオブジェクト ブラウザーには MyComClass という名前のインターフェイスと MyComClassClassという名前のクラスが表示されます。

.NET Framework クラスのインスタンスの作成

一般に、クラス名を持つ New ステートメントを使用して、.NET Framework クラスのインスタンスを作成します。 相互運用機能アセンブリによって表される COM クラスを持つことは、インターフェイスで New ステートメントを使用できる 1 つのケースです。 Inherits ステートメントで COM クラスを使用しない限り、クラスと同じようにインターフェイスを使用できます。 次のコードは、Microsoft ActiveX データ オブジェクト 2.8 ライブラリ COM オブジェクトへの参照を持つプロジェクトで Command オブジェクトを作成する方法を示しています。

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 クラスのインスタンスを作成し、オブジェクトのメソッドを呼び出すことによって使用されます。 この規則の例外の 1 つは、AppObj または GlobalMultiUse COM クラスを含む COM オブジェクトです。 このようなクラスは、Visual Basic .NET クラスのモジュール レベルのメソッドに似ています。 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 .NET では、メソッドを使用する前に、常に COM オブジェクトのインスタンスを作成する必要があります。 Visual Basic でこれらのメソッドを使用するには、目的のクラスの変数を宣言し、new キーワードを使用してオブジェクト変数にオブジェクトを割り当てます。 Shared キーワードは、クラスのインスタンスが 1 つだけ作成されるようにする場合に使用できます。

' 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 .NET プロジェクトの例です。

' 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 = "..."
    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 .NET で問題なく動作します。 主な例外は、コンテナー コントロール、または他のコントロールを視覚的に含むコントロールです。 Visual Studio で正しく動作しない古いコントロールの例を次に示します。

  • Microsoft Forms 2.0 Frame コントロール

  • Up-Down コントロール (スピン コントロールとも呼ばれます)

  • Sheridan タブ コントロール

サポートされていない ActiveX コントロールの問題には、いくつかの回避策しかありません。 元のソース コードを所有している場合は、既存のコントロールを Visual Studio に移行できます。 それ以外の場合は、ソフトウェア ベンダーに確認して、サポートされていない ActiveX コントロールを置き換えるための更新された .NET 互換バージョンのコントロールを入手できるか確認できます。

コントロール ByRef の ReadOnly プロパティを渡す

Visual Basic では、一部の古い ActiveX コントロールの ReadOnly プロパティを他のプロシージャに ByRef パラメーターとして渡すと、"エラー 0x800A017F CTL_E_SETNOTSUPPORTED" などの COM エラーが発生することがあります。 Visual Basic 6.0 からの同様のプロシージャ呼び出しではエラーは発生せず、パラメーターは値渡しされたかのように扱われます。 Visual Basic .NET エラーメッセージは、Set プロシージャを持たないプロパティを変更しようとしていることを示しています。

呼び出されるプロシージャにアクセスできる場合は、ByVal キーワードを使用して、ReadOnly プロパティを受け入れるパラメーターを宣言することで、このエラーを回避できます。 例えば:

Sub ProcessParams(ByVal c As Object)
    'Use the arguments here.
End Sub

呼び出されるプロシージャのソース コードにアクセスできない場合は、呼び出し元のプロシージャの周囲に追加の角かっこを追加することで、プロパティを値渡しするように強制できます。 たとえば、Microsoft ActiveX Data Objects 2.8 Library 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 を共有するアセンブリをアンインストールすると、誤って他のアセンブリで使用できなくなる可能性があります。

この問題を回避するには、共有アセンブリをグローバル アセンブリ キャッシュ (GAC) にインストールし、コンポーネントに MergeModule を使用する必要があります。 GAC にアプリケーションをインストールできない場合は、バージョン固有のサブディレクトリの CommonFilesFolder にインストールする必要があります。

共有されていないアセンブリは、呼び出し元アプリケーションと共にディレクトリ内に並べて配置する必要があります。

関連項目