次の方法で共有


.NET の相互運用性に関するトラブルシューティング

Bob Burns
Visual Studio Team
Microsoft Corporation

May 2002
日本語版最終更新日 2002 年 7 月 15 日

要約 : この文書では、マネージ コードとアンマネージ コードの両方を使用するアプリケーションの開発におけるいくつかの問題点について説明します。既存の COM オブジェクトをマネージ コードで使用する方法と、マネージ コードから WindowsR API を利用する方法について説明します。

要件

  • COM の使用経験があり、MicrosoftR Visual BasicR .NET で COM を使用している
  • Visual StudioR .NET および Visual Basic 6.0 がインストール済み

目次

はじめに
Visual Basic .NET で COM オブジェクトを使用する
相互運用機能アセンブリの一般的な問題に対処する
API の宣言
モジュール レベルの COM メソッド
イベント ハンドラで処理されないエラー
ActiveX コントロールの問題
まとめ

はじめに

Visual Studio .NET では、共通言語ランタイム (Common Language Runtime) を対象とする "マネージ コード" の概念を導入することにより、アプリケーションの作成方法および実行方法が大きく変更されました。マネージ コードには、自動メモリ管理、属性付きプログラミング、Common Type System (CTS) など、多くの長所があります。ところが、マネージ コードのパワフルな機能は Windows API や COM オブジェクトなどのアンマネージ コードとは根本的に異なります。Visual Studio .NET ではアンマネージ オブジェクトの使用および作成方法が比較的簡単になっていますが、複雑な処理が必要になる場合もあります。マネージ オブジェクトとアンマネージ オブジェクトの両者を連携させるプロセスは相互運用性と呼ばれ、一般的には相互運用機能とも呼ばれます。

相互運用機能という用語は、Visual Studio .NET に関連する次の 3 つの問題を包含します。

  • マネージ コードで COM オブジェクトを使用する。

    Visual C#? または Visual Basic .NET などのマネージ コードで既存の COM オブジェクトを使用する場合の問題です。

  • Visual Studio .NET で COM オブジェクトを作成する。

    Visual Studio .NET により開発を簡便化する場合の問題です。.NET Framework がインストールされていないクライアントでは従来の COM オブジェクトを使用する必要があります。

  • マネージ コードで Windows API を使用する。

    .NET Framework には多くの Windows API に代わるマネージ コードが用意されていますが、ダイナミック リンク ライブラリ (dll) の関数を呼び出す必要がある場合もあります。

各種の相互運用機能には固有の課題があります。たとえば、マネージ コードから COM オブジェクトを使用する場合の難点の多くは、相互運用機能アセンブリの使用に関連します。一方、Windows API の問題は、通常は MarshalAs 属性の適切な使用に関連します。そこで、この文書では、COM オブジェクトおよび Windows API の使用方法について説明します。

Visual Basic .NET で COM オブジェクトを使用する

最近のアプリケーションでは COM オブジェクトが一般的に使用されています。.NET アセンブリ形式のマネージ コードが最終的に大部分の COM オブジェクトに置き換えられるようになりますが、置き換えが完了するまでは COM オブジェクトを使用および作成する場合があります。

一見、マネージ コードでの COM オブジェクトの使用方法は、従来バージョンの Visual Basic の場合とまったく変わらないように見えます。[プロジェクト] メニューの [参照の追加] をクリックしてプロジェクトに COM オブジェクトを追加し、[COM] タブで COM コンポーネント名を選択すればよいのです。違いは見えないところにあります。最新の Visual Basic では、相互運用機能アセンブリを使用して .NET プラットフォームと COM との間におけるデータ表現の変換を行います。

相互運用機能アセンブリは、マネージ コードとアンマネージ コードとの橋渡し役を果たす .NET アセンブリで、COM オブジェクト メンバを対応する .NET マネージ メンバにマップします。COM オブジェクトへの参照を追加すると、グローバル アセンブリ キャッシュがチェックされ、適切なプライマリ相互運用機能アセンブリが使用可能であるかどうかが判別されます。プライマリ相互運用機能アセンブリは、特定の COM オブジェクトとの連携処理を行う相互運用機能アセンブリです。プライマリ相互運用機能アセンブリは高度な署名によりファイル ID を確認し、マネージ コードとアンマネージ コードとをシームレスに最適化します。マネージ コードで COM オブジェクトを使用する場合はなるべくプライマリ相互運用機能アセンブリを使うことをお勧めします。ただし、現在使用可能なプライマリ相互運用機能アセンブリは多くありません。COM オブジェクトに適したプライマリ相互運用機能アセンブリが見つからない場合、統合開発環境 (IDE) を使用していれば、Visual Studio .NET で相互運用機能アセンブリを作成することが可能です。また、コマンド プロンプトで Tlbimp ユーティリティを実行して相互運用機能アセンブリを作成することも可能です。

通常は Visual Studio .NET ですぐに相互運用機能アセンブリが作成されますが、生成されたコードを最適化する必要がある場合もあります。相互運用機能アセンブリの問題が発生するのは、データ マーシャリングの処理に必要なすべての情報がタイプ ライブラリに存在するとは限らないためです。相互運用機能アセンブリが正常に動作しない場合、または相互運用機能アセンブリを使用して COM オブジェクトを処理する際にパフォーマンスが低下する場合は、相互運用機能アセンブリを手動で編集し、データ マーシャリングを指定する必要があります。相互運用機能アセンブリを変更する機能により、OLE オートメーションに対応していないメソッドの呼び出しが可能になり、Visual Basic 6.0 からの呼び出しに失敗する可能性もあります。

相互運用機能アセンブリを変更する

相互運用機能アセンブリはすべての .NET アセンブリと同様のバイナリ ファイルです。ただし、中間言語逆アセンブラ ildasm を使用して、Microsoft Intermediate Language (MSIL) と呼ばれる判読可能な形式に逆アセンブルすることが可能です。相互運用機能アセンブリをカスタマイズするには、ildasm で逆アセンブルしてから中間言語コードを編集し、ilasm アセンブリ ユーティリティを使用して再アセンブルします。

相互運用機能アセンブリの名前と場所を検索するには

  • ソリューション エクスプローラの参照リストの COM ライブラリ名を右クリックし、[プロパティ] をクリックします。

    相互運用機能アセンブリの名前と場所は、COM オブジェクトのパス プロパティに保存されています。

相互運用機能アセンブリを変更するには

  1. Windows の [スタート] メニューで [Microsoft Visual Studio .NET] をポイントし、[Visual Studio .NET ツール] をポイントして [Visual Studio .NET コマンド プロンプト] をクリックします。

  2. 変更する相互運用機能アセンブリの保存場所にディレクトリを移動します。

  3. 相互運用機能アセンブリの dll を ildasm ユーティリティを使用して逆アセンブルします。

    次の例は、相互運用機能アセンブリ interop.comclass.dllinterop.ComClass.il というファイル名の中間コードに逆アセンブルする方法を示しています。

    ildasm interop.comclass.dll /out=interop.ComClass.il
    
  4. メモ帳や Visual Studio などのテキスト エディタで MSIL を変更し、変更内容を保存します。

相互運用機能アセンブリを再アセンブルするには

  • Visual Studio .NET コマンド プロンプトで ilasm ユーティリティのコマンドを入力し、MSIL ファイルをアセンブリに再アセンブルします。

    次の例は、相互運用機能アセンブリ interop.comclass.il interop.NewIA.dll というファイル名の中間コードに逆アセンブルする方法を示しています。

    ilasm interop.comclass.il /dll /output=interop.NewIA.dll
    

新規の相互運用機能アセンブリへの参照を追加するには

  1. 古い相互運用機能アセンブリへの参照を削除します。
  2. [プロジェクト] メニューの [参照の追加] をクリックし、[.NET] タブをクリックしてから [参照] をクリックし、新規の相互運用機能アセンブリ名を選択します。

相互運用機能アセンブリの一般的な問題に対処する

以下のセクションでは、相互運用機能アセンブリに関する一般的な問題について説明し、正常に動作させるための対処方法を示します。

  • C スタイルの整合配列
  • C スタイルの In/Out 配列
  • C スタイルの多次元配列
  • 下限がゼロ以外の境界を持つ SAFEARRAY
  • 署名の保持
  • 値の型への参照ではなく Null を渡す場合

COM オブジェクトを作成する方法はいくつかありますが、ここではインターフェイス定義言語 (IDL) で COM オブジェクトを記述しています。IDL と MSIL は似ていますが構文とデータ型が異なります。この文書の付録の表に、Visual Basic 6.0、Visual Basic .NET、IDL、および MSIL で使用されるデータ型の間の変換を示します。

C スタイルの整合配列

インターフェイス定義言語 (IDL) による次の宣言は、C スタイルの配列を表します。

HRESULT ConformantArray([in] int cElems, [in, size_is(cElems)] int 
aConf[ ]);

Tlbimp.exe では第 2 パラメータがマネージ配列ではなく整数への参照としてインポートされます。パラメータを調整するには、MSIL を次のように編集します。

MSIL で以下のコードを探します。

method public hidebysig newslot virtual 
instance void  ConformantArray([in] int32 cElems,
[in] int32& aConf) runtime managed internalcall

これを以下のコードで置き換えます。

method public hidebysig newslot virtual 
instance void  ConformantArray([in] int32 cElems,
[in] int32[ ] marshal([+0]) aConf) runtime managed internalcall

C スタイルの In/Out 配列

次の IDL 宣言は、C スタイルの In/Out 配列です。

HRESULT InOutArray([in, out] int* pcElems, [in, out, size_is(*pcElems)] 
int** ppInOut);

この場合は、配列のサイズを変更し、新しいサイズを返すことが可能です。Tlbimp.exe では第 2 パラメータが IntPtr 型としてインポートされます。マネージ コードからこのメソッドを呼び出すことは可能ですが、配列のサイズを変更するには MSIL を編集し、Marshal クラスからのメソッドを使用してメモリの割り当てと解放を手動で処理する必要があります。

MSIL で以下のコードを探します。

instance void  TestPassingNull([in] native int) runtime managed internalcall

これを以下のコードで置き換えます。

instance void  TestPassingNull([in] native int) runtime managed internalcall

C スタイルの多次元配列

次の IDL 宣言は、C スタイルの 2 次元配列です。

HRESULT TwoDimArray([in] int cDim, [in, size_is(cDim)] int aMatrix[][3]);

Tlbimp.exe では第 2 パラメータがマネージ多次元配列ではなく IntPtr 型としてインポートされます。パラメータを調整するには、MSIL を編集します。

MSIL で以下のコードを探します。

.method public hidebysig newslot virtual 
instance void  TwoDimArray([in] int32 cDim,
[in] native int aMatrix) runtime managed internalcall

これを以下のコードで置き換えます。

.method public hidebysig newslot virtual 
instance void  TwoDimArray([in] int32 cDim,
[in] int32[,] marshal([+0]) aMatrix) runtime managed internalcall

下限がゼロ以外の境界を持つ SAFEARRAY

次の IDL 宣言は、SAFEARRAY パラメータを表します。

HRESULT InSArray([in] SAFEARRAY(int) *ppsa);

この SAFEARRAY は下限が 0 以外の配列を表します。マネージ コードではこのような配列は System.Array 型で記述されます。しかし、既定ではインポータによりすべての SAFEARRAY パラメータがマネージ配列への参照に変換されます。既定の動作を変更する方法は 2 つあります。

  • /sysarray スイッチを指定して Tlbimp.exe を実行し、タイプ ライブラリのすべての配列を System.Array 型としてインポートします。

    -または-

  • 手動で MSIL を編集し、特定のパラメータを System.Array 型としてインポートします。次に例を示します。

MSIL で以下のコードを探します。

.method public hidebysig newslot virtual 
instance void  InSArray([in] int32[]&  marshal( safearray int) ppsa) runtime managed internalcall

これを以下のコードで置き換えます。

.method public hidebysig newslot virtual 
instance void  InSArray(class [mscorlib]System.Array& marshal( safearray) 
ppsa) runtime managed internalcall

署名の保持

次の IDL 宣言は、COM メソッドの署名を表します。

HRESULT TestPreserveSig2([in] int inParam, [out,retval] int* outParam);

Tlbimp.exe では COM メソッドの署名が変更されます。IDL で [out, retval] と記述されたパラメータはマネージ メソッドの戻り値になります。エラーを示す HRESULT 値はすべてマネージ例外に変換されます。メソッドから正常な HRESULT 以外の値が返される場合などは、元の COM メソッドの署名を保持しておく必要があります。次のマネージ コードは、変更が可能な署名の例です。

MSIL で以下のコードを探します。

.method public hidebysig newslot virtual 
instance int32 TestPreserveSig2([in] int32 inParam) runtime managed internalcall

これを以下のコードで置き換えます。

.method public hidebysig newslot virtual 
instance string TestPreserveSig2([in] int32 inParam, [out] string& outParam) runtime managed internalcall preservesig

値の型への参照ではなく Null を渡す場合

次の IDL 宣言は、構造体へのポインタである IDL パラメータ宣言を表します。

HRESULT TestPassingNull([in, unique] Point* refParam);

Tlbimp.exe ではパラメータが値の型 Point への参照としてインポートされます。C# および Visual Basic .NET では、値の型への参照が予期される場所ではパラメータとして Null 値を渡すことはできません。COM 関数で Null パラメータが必要な場合は MSIL を編集して署名を変更できます。

MSIL で以下のコードを探します。

.method public hidebysig newslot virtual 
instance void  TestPassingNull(
[in] valuetype MiscSrv.tagPoint& refParam) 
runtime managed internalcall

これを以下のコードで置き換えます。

.method public hidebysig newslot virtual 
instance void  TestPassingNull([in] native int) runtime managed internalcall

Windows API

Windows API 呼び出しは、プラットフォーム呼び出し (PInvoke) という相互運用機能のカテゴリに分類されます。Windows API は COM オブジェクトではなく、いずれのバージョンの Visual Basic からも使用が困難な面があります。

Windows API を使用する場合の主な問題点は、アンマネージ コードとの間のデータの受け渡し、つまり相互運用データ マーシャリングと呼ばれるプロセスにあります。Windows API では、配列や文字列などの要素に対していくつかの異なる表現を使用しますが、これらの表現の一部は COM または .NET Framework のいずれにおいても相当するものがありません。

マネージ コードから Windows API を正常に呼び出すには、次の知識が必要です。

  • API が使用するデータの種類
  • API 呼び出しのアンマネージ コードでデータのフォーマットが API によってどのように指定されるか
  • アプリケーションのマネージ コードでデータのフォーマットを指定する方法

コードを作成する前に、呼び出す関数の名前、引数、引数の型、戻り値、および関数が含まれる DLL の名前と場所を決定する必要があります。Platform SDK Windows API には、Windows API に関する詳細なドキュメントがあります。Windows API で使用される定数の詳細については、Platform SDK に含まれる Windows.h などのヘッダー ファイルを参照してください。この文書の執筆時点では Platform SDK のドキュメントが Visual Studio .NET リリース以前の C++ 開発者を対象としていたため、必要に応じてデータ型を変更する必要があります。

従来の Visual Basic プログラマは DLL の静的エントリ ポイントへのアクセスに Declare ステートメントを使用していました。Visual Basic .NET および C# では、DLLImport 属性を使用して API を利用するという別の方法があります。この文書の例では Declare ステートメントを使用していますが、どちらかの形式にメリットがあるわけではないため、必要に応じて DLLImport 属性を使用するように再度フォーマットすることは簡単です。詳細については、「Walkthrough Calling Windows APIs」 を参照してください。

API の宣言

次の例は、ディスカッション グループで実際に提起された問題に基づいて作成されています。GetVersionEx API の実行後、引数の構造体にデータが取得されていないという報告がありました。この問題を解決するには、Marshal.SizeOf メソッドと MarshalAs 属性を使用して、構造体メンバのマーシャリング方法を明示的に指定します。

Imports System.Runtime.InteropServices
Module Module1
   Public Declare Auto Function GetVersionEx Lib "kernel32.dll" 
(<MarshalAs(UnmanagedType.Struct)> ByRef osinfo As OSVERSIONINFOEX)
As Int32

   ' 構造体の定義
   <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _
   Public Structure OSVERSIONINFOEX
      Public dwOSVersionInfoSize As Int32
      Public dwMajorVersion As Int32
      Public dwMinorVersion As Int32
      Public dwBuildNumber As Int32
      Public dwPlatformId As Int32
      <MarshalAs(UnmanagedType.ByValTStr, _
         SizeConst:=128)> Public szCSDVersion As String
      Public wServicePackMajor As Int16
      Public wServicePackMinor As Int16
      Public wSuiteMask As Int16
      Public wProductType As Byte
      Public wReserved As Byte
   End Structure

   Sub testAPI()
      Dim osverinfo As OSVERSIONINFOEX
      Dim result As Integer
      osverinfo.dwOSVersionInfoSize = Marshal.SizeOf(osverinfo)
      result = GetVersionEx(osverinfo)
      MsgBox(osverinfo.dwBuildNumber)
   End Sub

End Module

上のコードは目的の機能を果たしますが、いくつか説明する点があります。GetVersionEx を含む一部の Win32R API 呼び出しでは、使用する構造体のアンマネージでのサイズおよびフィールドの場所を指定するようになっています。Visual Studio .NET ではデータの格納方法が最適化されるため、構造体のアンマネージとマネージの表現が常に一致するとは限りません。StructLayout 属性を設定すると、構造体のフィールドのメモリでの配置方法が変更され、Visual Studio .NET の最適化機能が無効になります。このような最適化はマネージ コードには適していますが、構造体のフィールドが API 呼び出しで予期されるオフセットにない場合に問題が発生する可能性があります。Marshal.SizeOf メソッドは、アンマネージ コードで記述された正しい構造体サイズを返します。多くの場合、この値は LEN などの関数で取得される最適化後のサイズとは異なります。MarshalAs 属性はアンマネージ側で szCSDVersion フィールドのサイズを指定する場合に必要です。多くの PInvoke 宣言では、マネージ コードおよびアンマネージ コードの両方のデータ表現を管理する必要があります。

アップグレード ウィザードを使用して Declare ステートメントを修正する

Visual Basic 6.0 で使用する Declare ステートメントの変更方法に自信がない場合、アップグレード ウィザードの指示に従って変更する方法があります。アップグレード ウィザードを使用すると作業が簡単になりますが、MarshalAs 属性の追加が必要な場合もあります。

アップグレード ウィザードを使用して Declare ステートメントを更新するには

  1. Visual Studio .NET で使用する Declare ステートメントを含む Visual Basic 6.0 プロジェクトを作成します。

  2. Visual Basic .NET の構文にアップグレードできるようにコードを変更します。

    たとえば、Dim Buffer As String * 25
    というコードを Visual Basic .NET 互換にするには、コードを次のように変更する必要があります。

    
    
    
    Dim Buffer As String
    Buffer = String$(25, " ")
    
    
  3. Visual Basic 6.0 のコードをテストして、動作を確認します。

  4. プロジェクトを保存し、Visual Basic 6.0 を終了します。

  5. Visual Studio .NET を起動し、Visual Basic 6.0 の既存のプロジェクトを開きます。

    Visual Studio .NET がアップグレード ウィザードを起動します。

  6. ウィザードが完了したら、アップグレードしたコードをテストします。

多くの場合、アップグレード ウィザードは Declare ステートメントに必要なすべての処理を実行します。たとえば、次の Visual Basic 6.0 のコードを実行するとします。アップグレード ウィザードが作成するコードをその次のセクションに示します。

Private Declare Function GetUserName Lib "advapi32.dll" _
                Alias "GetUserNameA" (ByVal lpBuffer As String, _
                                      ByRef nSize As Long) As Long

Function UserName() As String
    Dim Ret As Long
    Dim Buffer As String
    Buffer = String$(25, " ")
    Ret = GetUserName(Buffer, 25)
    UserName = Left$(Buffer, InStr(Buffer, Chr(0)) - 1)
End Function
'----------------  VB 6 コード終了 -------------------

次のセクションに、アップグレード ウィザードが生成するコードを示します。

Option Strict Off
Option Explicit On
Module GetName
    Private Declare Function GetUserName Lib "advapi32.dll" _
       Alias "GetUserNameA" (ByVal lpBuffer As String, _
       ByRef nSize As Integer) As Integer

   Function UserName() As String
      Dim Ret As Integer
      Dim Buffer As String
      Buffer = New String(" ", 25)
      Ret = GetUserName(Buffer, 25)
      UserName = Left(Buffer, InStr(Buffer, Chr(0)) - 1)
   End Function
End Module

アップグレード ウィザードがいくつかの変更を加えたため、このコードは正常に動作します。たとえば、Visual Basic 6.0 では 16 ビット整数を使用しますが Visual Basic .NET では 32 ビット整数を使用するため、戻り値の変数 Ret および nSize パラメータは長整数型 (Long) から整数型 (Integer) に変更されています。

モジュール レベルの 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
' データベースを開きます。
Set db = DBEngine.OpenDatabase("C:\nwind.mdb")
' データベース オブジェクトを使用します。

Visual Basic .NET では常に COM オブジェクトのインスタンスを作成した後でそれらのメソッドを使用する必要があります。これらのメソッドを Visual Basic .NET で使用するには、必要なクラスの変数を宣言し、new キーワードを使用してオブジェクトをオブジェクト変数に割り当てます。Shared キーワードを使用すると、クラスのインスタンスが必ず 1 つだけ作成されるようにすることができます。

' 宣言レベルの変数です。
Shared DBEngine As New DAO.DBEngine()

   Sub DAOOpenRecordset()
      Dim db As DAO.Database
      Dim rst As DAO.Recordset
      Dim fld As DAO.Field
      ' データベースを開きます。
      db = DBEngine.OpenDatabase("C:\nwind.mdb")

      ' レコードセットを開きます。
      rst = db.OpenRecordset _
        ("SELECT * FROM Customers WHERE Region = 'WA'", _
         DAO.RecordsetTypeEnum.dbOpenForwardOnly, _
         DAO.RecordsetOptionEnum.dbReadOnly)
      ' フィールドの値をデバッグ ウィンドウに出力します。
      For Each fld In rst.Fields
         Debug.WriteLine(fld.Value & ";")
      Next
      Debug.WriteLine("")
      ' レコードセットを閉じます。
      rst.Close()
   End Sub

イベント ハンドラで処理されないエラー

相互運用機能に関する一般的な問題として、COM オブジェクトで発生したイベントを処理するイベント ハンドラのエラーがあります。On Error ステートメントまたは Try...Catch...Finally ステートメントを使用して明示的にエラー チェックを行わない限り、このようなエラーは無視されます。たとえば、ADODB COM オブジェクトへの参照を持つ Visual Basic .NET プロジェクトの例を次に示します。

' このサンプルを使用するには、プロジェクトの参照設定ページの 
' [COM] タブで ADODB への参照を追加します。
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
'  データベースが開かれたときに ADODB.Connection オブジェクトで発生する 
'  cn_ConnectComplete イベントのイベント ハンドラです。
   Dim x As Integer = 6
   Dim y As Integer = 0
   Try      x = x / y ' 0 による除算を試行します。
     ' 例外処理を行わなかった場合はこのプロシージャが暗黙のうちに失敗します。
   Catch ex As Exception
      MsgBox("エラーが発生しました:" & ex.Message)
   End Try
End Sub

この例では、予想される場所でエラーが発生します。しかし、Try...Catch...Finally ブロックを使用せずに同じ例を実行した場合、OnError Resume Next ステートメントの使用時と同様にエラーが無視されます。エラー処理を行わなかった場合は 0 による除算が暗黙のうちに失敗します。このようなエラーからは未処理の例外エラーが発生しないため、COM オブジェクトのイベントを処理するイベント ハンドラにおいて何らかの例外処理を行うことが重要です。

COM 相互運用機能エラーについて理解する

エラー処理を行わなかった場合は、相互運用機能呼び出しで生成されるエラーに関してわずかな情報しか提示されないことがほとんどです。構造化エラー処理を可能な限り行い、問題が発生した場所でより多くの情報を取得するようにします。エラー処理を作成しておくと、アプリケーションのデバッグ時に非常に有効です。次に例を示します。

Try
' ここで COM オブジェクトを呼び出します。
Catch ex As Exception
' 失敗した呼び出しに関する情報を表示します。
End Try

例外オブジェクトの内容を調べることで、エラーの説明、HRESULT、COM エラーのソースなどの情報を取得することが可能です。

ActiveX コントロールの問題

Visual Basic 6.0 のほとんどの ActiveXR コントロールは、Visual Basic .NET でも問題なく動作します。主な例外は、他のコントロールを視覚的に包含するコンテナ コントロールです。Visual Studio .NET で正常に動作しない古いコントロールの一部を次に示します。

  • Microsoft Forms 2.0 のフレーム コントロール
  • アップダウン コントロール (またはスピン コントロール)
  • Sheridan タブ コントロール

サポートされていない ActiveX コントロールの問題を回避する方法は限られています。元のソース コードがある場合は、既存のコントロールを Visual Studio .NET に移行することが可能です。元のソース コードがない場合は、ソフトウェアの開発元に問い合わせて .NET 互換バージョンのコントロールにアップデート可能かどうかを確認します。

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

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

呼び出されるプロシージャを入手できる場合は、ByVal キーワードを使用して ReadOnly プロパティを受け取るパラメータを宣言すると、エラーが発生しません。次に例を示します。

Sub ProcessParams(ByVal c As Object)
   ' ここで引数を使用します。
End Sub

呼び出されるプロシージャのソース コードを入手できない場合は、呼び出しプロシージャでさらにかっこを追加してプロパティを強制的に値渡しにすることが可能です。次に例を示します。

Sub PassByVal ()
' 引数をさらにかっこで囲み、
' 強制的に値渡しにします。
   ProcessParams((Me.AxListView1.ListItems))
End Sub

まとめ

  • COM 相互運用機能は、アンマネージ コードの資産を保護しながらマネージ コードへの移行を進める優れた手段です。
  • 全体的に、Visual Studio .NET ではアンマネージ コードの操作が簡単です。
  • 問題が発生しても、多くの場合は解決可能です。
  • 相互運用機能アセンブリを編集して、相互運用機能の問題を修正したり、パフォーマンスを改善することが可能です。
  • マネージ コードから Windows API を正常に呼び出す場合は、API が使用するデータの種類を把握しておくことが重要です。
  • Windows API などの DLL の静的エントリ ポイントにアクセスする場合に、MarshalAs 属性を使用して関数にデータを渡す方法を指定することが可能です。
  • アップグレード ウィザードの指示に従って、Visual Basic 6.0 で正常に動作する Declare ステートメントを変更することが可能です。
  • COM オブジェクトのイベント プロシージャでは必ず何らかの形式でエラー処理を行うようにします。
  • ReadOnly プロパティを他のプロシージャに参照渡しする場合は注意が必要です。

付録

COM 相互運用機能を使用するにはデータ型およびそれらに相当する型を把握しておくことが重要です。次の表に、COM インターフェイスを定義するために一般的に使用されるインターフェイス定義言語 (IDL) で記述されたデータ型と、それらの型に相当する Visual Basic 6.0、Visual Basic .NET、.NET Framework および Microsoft Intermediate Language (MSIL) のデータ型を示します。

IDL Visual Basic 6.0 Visual Basic .NET .NET Framework MSIL
char 該当なし 該当なし* System.SByte int8
short Integer Short System.Int16 int16
long、int、HRESULT および SCODE Long Integer System.Int32 int32
int64 該当なし Long System.Int64 int64
unsigned char Byte Byte System.Byte unsigned int8
unsigned short 該当なし 該当なし* System.Uint16 unsigned int16
unsigned int 該当なし 該当なし* System.UInt32 unsigned int32
uint64 該当なし 該当なし* System.UInt32 unsigned int64
float Single Single System.Single float32
double Double Double System.Double float64
BSTR、LPSTR および LPWSTR String String System.String String
VARIANT_BOOL Boolean Boolean System.Boolean bool
DATE* Date Date System.DateTime Valuetype [mscorlib]System.DateTime
GUID 該当なし 該当なし* System.GUID Valuetype [mscorlib]System.GUID
DECIMAL 該当なし Decimal System.Decimal Valuetype [mscorlib]System.Decimal
CURRENCY Currency Decimal System.Decimal Valuetype [mscorlib]System.Decimal
VARIANT Variant Object System.Object object
IUnknown* 該当なし Object System.Object object
IDispatch* Object Object System.Object object
void* Any 該当なし System.IntPtr native int
IEnumVARIANT 該当なし 該当なし* System.Collections.IEnumerator 該当なし

* これらの型は Visual Basic .NET には組み込まれていませんが、System 名前空間の System.SByte などの相当する型を使用することが可能です。

その他のリソース