PInvoke の使用に役立つツール

再び Jared です。

私が作業に携わってきたツールが、最近 MSDN でリリースされました。マネージ コードでの PInvoke の使用に役立つ PInvoke Interop Assistant という名前のツールです。PInvoke と Reverse PInvoke のシナリオでのデータのマーシャリングに関する MSDN の記事をご覧ください。

この記事へのリンクとツールの入手元は次のとおりです。

このツールを開発した理由は、PInvoke の記述が困難で、うんざりする作業になることがよくあるためです。従わなくてはならない規則が多く、考慮すべき例外も多数あります。単純なデータ構造が延々と続き、C の微妙でわかりにくい構文が必要なシグネチャを大幅に変えてしまうことがあります。変換が正しく行われないために、不明瞭な例外やクラッシュが発生することもよくあります。

要するに、楽しい作業ではないのです。

このツールは、さまざまな方法で PInvoke の生成を容易にします。私たちの目標は、構造体、共用体、列挙体、定数、関数、typedef などのマネージ コード生成をできるだけ容易にすることでした。コードは、VB と C# のどちらでも生成できます。

このツールの GUI バージョンは 3 つのモードで動作します。

  1. SigImp Search : 一般的に使われる関数を検索し、それをマネージ コードに変換します。
  2. SigImp Translate Snippet : C コードをマネージ PInvoke シグネチャに直接変換します。
  3. SigExp : マネージ バイナリを C++ Reverse PInvoke シナリオに変換します。

私が作業した最初の 2 つは、PInvoke シナリオを表します。3 番目は Ladi Prosek が記述したもので、別の記事で説明する予定です。SigImp と SigExp は、同様の機能を持つ tblimp/tlbexp ツールにならって名付けました。

C コードから PInvoke シグネチャへの直接変換

PInvoke での大部分の作業は、開発者がマネージ バイナリから使用したいと思った C コードの小さなセットから始まります。典型的なのは、1 つまたは 2 つの関数といくつかの C 構造体を持つコードです。以前はすべてを手作業で最初からマネージ コードに変換していました。このツールを使用すれば、コードをツールに貼り付けるだけで、相互運用シグネチャが生成されます。

たとえば、次の C コードを VB に変換するとします。

 struct S1
 {
   int a;
   char[10] b;
 };
  
 float CalculateData(S1* p);

ツールを起動して [SigImp Translate Snippet] タブに切り替え、このコードを貼り付けて [Generate] をクリックします。

PInvoke1 

[Auto Generate] ボックスをクリックすると、入力中にコードが自動的に更新されるのを確認できます。

この変換は組み込みの C 型に限定されるものではありません。HANDLE や DWORD などの最も一般的に使用される型を、WIN32_FIND_DATA などの複雑な構造体にまで解決します。

一般的に使用される関数の検索

開発者はしばしば、使い慣れた C 関数をマネージ コードで使用します。シグネチャが既に使用可能な状態でなければ、最初からコーディングをしなければならないので、この作業も面倒です。確認するヘッダー ファイルがわからない場合は、定数を追加するだけでも注意が必要です。

このツールには、よく使用される関数、構造体、定数などのデータベースも備わっています。基本的に、windows.h に含まれているすべての要素があります。[SigImp Search] タブに切り替え、検索する項目の名前を入力し、[Generate] をクリックします。たとえば、WM_PAINT の値を検索する場合は、それを入力するだけで済みます。

Pinvoke2

このツールでは、依存関係の計算も処理します。たとえば、C 構造体のパラメータを持つメソッドを選択すると、その関数と構造体が自動的に生成されます。たとえば、関数 FindFirstFile を選択した場合、ツールではその関数が WIN32_FIND_DATA 構造体に依存すると判断されます。さらに、WIN32_FIND_DATA が FILETIME に依存することも認識され、メソッドに加えて、それらの両方が生成されます。

 <System.Runtime.InteropServices.StructLayoutAttribute( _
     System.Runtime.InteropServices.LayoutKind.Sequential, _
     CharSet:=System.Runtime.InteropServices.CharSet.[Unicode])> _
 Public Structure WIN32_FIND_DATAW
     '''DWORD->unsigned int
     Public dwFileAttributes As UInteger
     '''FILETIME->_FILETIME
     Public ftCreationTime As FILETIME
     '''FILETIME->_FILETIME
     Public ftLastAccessTime As FILETIME
     '''FILETIME->_FILETIME
     Public ftLastWriteTime As FILETIME
     '''DWORD->unsigned int
     Public nFileSizeHigh As UInteger
     '''DWORD->unsigned int
     Public nFileSizeLow As UInteger
     '''DWORD->unsigned int
     Public dwReserved0 As UInteger
     '''DWORD->unsigned int
     Public dwReserved1 As UInteger
     '''WCHAR[260]
     <System.Runtime.InteropServices.MarshalAsAttribute( _
         System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst:=260)> _
     Public cFileName As String
     '''WCHAR[14]
     <System.Runtime.InteropServices.MarshalAsAttribute( _
         System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst:=14)> _
     Public cAlternateFileName As String
 End Structure
  
 <System.Runtime.InteropServices.StructLayoutAttribute( _
     System.Runtime.InteropServices.LayoutKind.Sequential)> _
 Public Structure FILETIME
     '''DWORD->unsigned int
     Public dwLowDateTime As UInteger
     '''DWORD->unsigned int
     Public dwHighDateTime As UInteger
 End Structure
  
 Partial Public Class NativeMethods
     '''Return Type: HANDLE->void*
     '''lpFileName: LPCWSTR->WCHAR*
     '''lpFindFileData: LPWIN32_FIND_DATAW->_WIN32_FIND_DATAW*
     <System.Runtime.InteropServices.DllImportAttribute("kernel32.dll", EntryPoint:="FindFirstFileW")> _
     Public Shared Function FindFirstFileW( _
         <System.Runtime.InteropServices.InAttribute(), _
             System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.LPWStr)> _
             ByVal lpFileName As String, _
         <System.Runtime.InteropServices.OutAttribute()> _
         ByRef lpFindFileData As WIN32_FIND_DATAW) As System.IntPtr
     End Function
 End Class

大きなコードベースの変換

小さなコード スニペットの場合はスニペット トランスレータで十分処理できますが、相互依存関係にあるいくつかのヘッダー ファイルなど、大きなコードベースを変換する場合は、小さなスニペット ダイアログ ボックスではうまく処理できません。大きなコードベースを処理するには、ツールのコマンド ライン バージョンである sigimp.exe を使用する必要があります。これは、複数のヘッダー ファイルを処理して大容量の出力を出すように設計されています。

最後に

このツールは、私が長年温めてきたペット プロジェクトとしてスタートしました。今、ユーザーの皆さんにこのツールを活用していただけるようになって、大変嬉しく思っています。皆さんからのフィードバックをお待ちしています。今後も、いくつかの投稿を追加して、このツールの機能について詳しくご説明する予定です。

Jared Parsons

https://blogs.msdn.com/jaredpar

VB チーム

投稿 : 2008 年 3 月 14 日 9:56 AM

分類 : VB6_Migration/InteropJared ParsonsDid you know?PInvoke

VB チームの Web ログ - https://blogs.msdn.com/vbteam/archive/2008/03/14/making-pinvoke-easy.aspx (英語) より