![]() |
|
ネイティブ コードを .NET CLR に移行する | |
Don Box | |
管理、株主、または過度に野心的な開発者の希望を示しています。Microsoft .NET 共通言語ランタイム (CLR) で®マネージド コードとして記述されたすべてのソフトウェアを実行することは単に不可能です。 プログラミング能力が不足しているため、多くの組織が、新しいランタイムがどれほど魅力的であっても、既存の Win32® ベース、C、C++、Visual Basic® 6.0 ベース、または COM コードベースの 100% を書き換える可能性は低いです。 つまり、.NET 時代以前に記述されたネイティブ コードの世界は、.NET で記述された新しいマネージド コンポーネントと平和的に共存する必要があります。NET に対応した言語。 幸いなことに、CLR は、過度の痛みや苦しみなしに、2 つの世界を橋渡しすることをサポートしています。 P/InvokeCLR から Win32 ベースのネイティブ DLL を呼び出すことは、P/Invoke を使用して非常に簡単です (P はプラットフォームの略です)。 P/Invoke は、LoadLibrary/GetProcAddress を介して解決できる PE/COFF エントリ ポイントに静的メソッド宣言をマップできるテクノロジです。 前の Java ネイティブ インターフェイス (JNI) や J/Direct® と同様に、P/Invoke はマネージド メソッド宣言を使用してスタック フレームを記述しますが、メソッド本体は外部のネイティブ DLL によって提供されることを前提としています。 ただし、JNI とは異なり、P/Invoke は CLR を念頭に置いて記述されていない "heritage" DLL をインポートする場合に便利です。メソッドが外部のネイティブ DLL で定義されていることを示すには、静的メソッドを extern としてマークし、System.Runtime.InteropServices.DllImport メソッド属性を使用します。 DllImport 属性は、メソッドを呼び出すときに LoadLibrary と GetProcAddress に渡す引数を CLR に指示します。 組み込みの C# DllImport 属性は、System.Runtime.InteropServices.DllImport のエイリアスにすぎません。 DllImport 属性は、さまざまなパラメーターを受け取ります。 図 2 文字列を受け取るメソッドを呼び出す場合は、メソッドまたは周囲の型に Unicode/ANSI ポリシーを設定する必要があります。 これは、アンマネージ コードで使用するために文字列型を変換する方法を制御するために必要です。 DllImport の CharSet パラメーターを使用すると、Unicode (CharSet.Unicode) または ANSI (CharSet.Ansi) を常に使用するか、基になるプラットフォームが Windows NT® または Windows 2000 と Windows 9x または Windows Millennium Edition (Me) (CharSet.Auto) に基づいて自動的に決定するかを指定できます。 CharSet.Auto の使用は、TCHAR データ型を使用して C で Win32 ベースのコードを記述するのと似ていますが、文字型と API はコンパイル時ではなく読み込み時に決定され、1 つのバイナリがすべてのバージョンの Windows で適切かつ効率的に動作することを可能にする点が異なります。 Windows プラットフォームには、呼び出し規則と文字セットを示すさまざまな名前マングリング スキームがあります。 CharSet が CharSet.Auto に設定されている場合、ランタイムで Unicode または ANSI が使用されているかどうかに応じて、シンボリック名に W または A サフィックスが自動的に付けられます。 さらに、プレーン シンボルが見つからない場合、ランタイムは stdcall 規則を使用してシンボルを組み込むようになります (たとえば、Sleep が_Sleep@4場合があります)。 このシンボリック マングリングは、DllImport 属性に対する ExactSpelling パラメーターを使用して抑制できます。 最後に、COM スタイルの HRESULT を使用する Win32 関数を呼び出す場合は、2 つのオプションがあります。 既定では、P/Invoke は HRESULT を関数から返される単なる 32 ビット整数として扱い、プログラマは手動でエラーをテストする必要があります。 このような関数を呼び出すより便利な方法は、TransformSig=true パラメーターを DllImport 属性に渡すことです。 これにより、P/Invoke レイヤーは、その 32 ビット整数を COM HRESULT として扱い、失敗した結果が発生した場合に COMException をスローするように指示します。 (ベータ 1 の TransformSig パラメーターはベータ 2 では PreserveSig に名前が変更され、その意味はベータ 1 から反転されることに注意してください)。図 4 型指定された画面切り替えランタイムから呼び出す (またはランタイムに) 呼び出すとき、パラメーターは、図 5に示すように、必ず呼び出し履歴に渡されます。 これらのパラメーターは、ランタイムと外部の両方の型のインスタンスです。 相互運用のしくみを理解する鍵は、指定された "値" にマネージド型とアンマネージ型の 2 つの型があることを理解することです。 さらに重要なのは、一部のマネージド型はアンマネージ型に等形です。つまり、その型のインスタンスをランタイムの外部に渡す必要がある場合、変換は必要ありません。 しかし、多くの型は同形ではなく、外部に適した表現を思い付くために何らかの変換が必要です。![]() 図 6 ![]() 図 7非同型パラメーター MarshalAs 属性を使用して、指定されたパラメーター (または構造体内のフィールド) をマーシャリングする方法を自由に制御できます。 この属性は、MSCOREE 外の世界に表示するアンマネージ型を示します。 少なくとも、MarshalAs 属性を使用して、特定のパラメーターまたはフィールドに対応するネイティブ型を示すことができます。 多くの型では、CLR は適切な既定値を選択します。 ただし、MarshalAs 属性を使用して、これらの既定値をオーバーライドできます。 たとえば、図 8 MarshalAs 属性を使用して、フィールド単位またはパラメーターごとの型マッピングを制御するだけでなく、構造体とクラスの基になる表現を制御することもできます。 特に、StructLayout 属性と FieldOffset 属性を使用すると、クラスと構造体のメモリ内レイアウトを正確に制御できます。これは、ランタイムの外部で渡される構造体にとって重要です。 注釈付き C# 構造体の例を次に示します。
このコードは、COM IDL の同等の定義です。
RCW と CCWSystem.String または System.Object 以外のオブジェクト参照を渡す場合、既定のマーシャリング動作では、CLR オブジェクト参照と COM オブジェクト参照の間で変換が行われます。 図 9![]() 図 9RCW および CCW アーキテクチャ RCW または CCW を介して MSCOREE 境界をまたぐインターフェイスの場合、CLR はマネージド インターフェイス定義への一連の注釈に依存して、型を変換する方法に関する基になるマーシャリング レイヤー ヒントを提供します。 これらのヒントは、P/Invoke で説明したヒントのスーパーセットです。 定義する必要があるその他の側面としては、UUID、vtable と dispatch、デュアル マッピング、IDispatch の処理方法、配列の変換方法などがあります。 これらの側面は、System.Runtime.InteropServices 名前空間の属性を使用して、マネージド インターフェイス定義に追加されます。 これらの属性がない場合、CLR は、特定のインターフェイスとメソッドの既定の設定を慎重に推測します。 ゼロから定義された新しいマネージド インターフェイスの場合、共通言語ランタイムの外部でインターフェイスを使用する場合は、属性を明示的に使用すると便利です。 TLBIMP と TLBEXPネイティブ COM 型定義 (構造体、インターフェイスなど) を CLR に手動で変換できます。特に正確な TLB が使用できない場合は、これが必要な場合があります。 CLR でのリフレクションのユビキタスを考えると、型定義を逆の方向に変換する方が簡単ですが、やはり、手動翻訳に頼るよりもツールを使用することをお勧めします。 CLR には、COM TLB が十分に正確である限り、この翻訳を行う妥当な作業を行うコードが付属しています。 System.Runtime.InteropServices.TypeLibConverter は、TLB アセンブリと CLR アセンブリ間で変換できます。 ConvertAssemblyToTypeLib メソッドは CLR アセンブリを読み取り、対応する COM 型定義を含む TLB を出力します。 この変換プロセスのヒント (MarshalA など) は、ソース型のインターフェイス、メソッド、フィールド、およびパラメーターにカスタム属性として表示される必要があります。 ConvertTypeLibToAssembly メソッドは COM TLB を読み取り、対応する CLR 型定義を含む CLR アセンブリを出力します。 SDK には、NMAKE での使用に適したコマンド ライン インターフェイスの背後にこれら 2 つの呼び出しをラップする 2 つのツール (TLBIMP.EXEとTLBEXP.EXE) が付属しています。 これらのツール間の関係を図 10![]() 図 10TLBIMP と TLBEXP 一般に、最初に CLR ベースの言語で型を定義してから、TLB を出力する方が簡単です。 たとえば、図 11 REGASM一部の開発者は、COM の CoCreateInstance を使用して CLR クラスにアクセスできるようにする必要があります。 これを行うには、COM CLSID をアセンブリと型にバインドする一部のレジストリ エントリを挿入する必要があります。 REGASM.EXEツールはこれを行います。 REGASM.EXEは、アセンブリを引数として受け取り、各パブリック クラスの適切なレジストリ エントリを書き込みます。 InprocServer32 エントリは、CLR ベースのオブジェクトの COM ファサードとして機能するMSCOREE.DLLを指します。 ネイティブ コードから CLR をかなり簡単にホストし、既定の AppDomain を使用して型を読み込み、任意のクラスの新しいインスタンスをインスタンス化できるため、REGASM.EXEは厳密には必要ではないことに注意してください。 図 13戦略ランタイムにネイティブ コードを移動するための 3 つの基本的な戦略があります。 図 14![]() 図 15 は、ネイティブ サブコンポーネントに依存せずに CLR 対応言語でソース コードを完全に書き換える完全なポート アプローチを示しています。 この方法は、下位のネイティブ ライブラリのマネージド バージョンの存在に依存します。 また、マネージド ライブラリが要求するスタイルの違いにソース コードを適応させる意欲にも依存します。 このアプローチでは最も労力が必要ですが、相互運用の移行が少なくなるだけでなく、(通常は) より豊富で優れた設計のライブラリという利点があります。 ![]() 図 16 ![]() 図 16 バイナリ インポートを使用した .NET の(Punt アプローチ) XML や SOAP などのテクノロジはいつでも使用できますが、CLR では、COM と Win32 の古い世界から C# と Web サービスの新しい世界に移行するためのさまざまな選択肢が開発者に提供されます。 Don to housecom@microsoft.comの質問とコメントを送信します。 |
|
Don Box は、ソフトウェア業界に教育とサポートを提供する DevelopMentor の共同設立者です。 書き込み Essential COM、およびcorote Effective COM と Essential XML (すべて Addison-Wesley)。W3C SOAP 仕様を共同編集しました。Reach Don at http://www.develop.com/dbox.この列は、Don Box と Ted Pattison (2002 年第 1 © 章) が共同執筆した、C# を使用した .NET プログラミングに関する次の書籍に基づいて作成されています。使用はピアソン・エデュケーション社の許可による。すべての権限が予約されています。 |
MSDNマガジン 2001年5月号より