クリックして評価とフィードバックをお寄せください
MSDN
MSDN ライブラリ
Visual Studio 2005
Visual Studio ドキュメント
Visual C++
プログラミング ガイド
このページは次のバージョンについて記述しています。
Microsoft Visual Studio 2005/.NET Framework 2.0

その他のバージョンについては、以下の情報を参照してください。
Visual C++
マネージ コードからのネイティブ関数の呼び出し

共通言語ランタイムにはプラットフォーム呼び出しサービス (PInvoke: Platform Invocation Services) が用意されており、マネージ コードでネイティブなダイナミック リンク ライブラリ (DLL : Dynamic Link Library) の C スタイルの関数を呼び出すことができます。COM とランタイムの相互運用と、"It Just Works (そのままで動く)" つまり IJW 機構のどちらにも、同じデータ マーシャリングが使用されています。

詳細については、次のトピックを参照してください。

このセクションのサンプルで、PInvoke の使い方を示します。PInvoke を使用すると、マーシャリング情報を手続き的なコードで書く代わりに属性として宣言できるため、データ マーシャリングを簡単にカスタマイズできます。

PInvoke と DllImport 属性

次の例は、Visual C++ プログラムで PInvoke を使用する方法を示しています。ネイティブ関数 puts が msvcrt.dll で定義されています。DllImport 属性は puts の宣言に使用されます。

// platform_invocation_services.cpp
// compile with: /clr
using namespace System;
using namespace System::Runtime::InteropServices;

[DllImport("msvcrt", CharSet=CharSet::Ansi)]
extern "C" int puts(String ^);

int main() {
   String ^ pStr = "Hello World!";
   puts(pStr);
}

次の例は上のサンプルと同じものですが、IJW を使用しています。

// platform_invocation_services_2.cpp
// compile with: /clr
using namespace System;
using namespace System::Runtime::InteropServices;

#include <stdio.h>

int main() {
   String ^ pStr = "Hello World!";
   char* pChars = (char*)Marshal::StringToHGlobalAnsi(pStr).ToPointer(); 
   puts(pChars);
   
   Marshal::FreeHGlobal((IntPtr)pChars);
}

IJW の長所

  • プログラムで使用するアンマネージ API のために DLLImport 属性を宣言する必要がありません。ヘッダー ファイルをインクルードし、インポート ライブラリをリンクするだけです。

  • IJW 機構の方が多少高速です。たとえば、データ項目の固定またはコピーは開発者が明示的に行うため、IJW スタブでその必要性をチェックする必要はありません。

  • パフォーマンスの問題が明確になります。この例では、Unicode 文字列から ANSI 文字列に変換しており、それに伴うメモリの割り当てと解放も行っています。IJW でコードを書けば、_putws を呼び出し、PtrToStringChars を使用した方がパフォーマンスが良くなることがわかります。

  • 同じデータを使用して複数のアンマネージ API を呼び出す必要がある場合は、事前にアンマネージ API を 1 回マーシャリングし、マーシャリングしたコピーを渡す方が毎回マーシャリングするよりも効率的です。

IJW の短所

  • 属性でなくコードを使用して、マーシャリングを明示的に指定する必要があります。ただし、この場合と同様、多くのケースには適切な既定値があります。

  • マーシャリング コードがインラインであるため、アプリケーション ロジックのフローが途切れやすくなります。

  • API の明示的なマーシャリングでは、32 ビットから 64 ビットへの変換のため、IntPtr 型が返されます。そのため、さらに ToPointer を呼び出す必要があります。

多くの部分と同様に、C++ によって公開された特定のメソッドの方が、ある程度複雑になるが、わかりやすく効率的です。

アプリケーションで主にアンマネージ データ型を使用する場合、あるいは .NET Framework API よりもアンマネージ API を多く呼び出す場合は、一般に IJW 機能の使用をお勧めします。アプリケーションの大部分でマネージ コードを使用し、ときどきアンマネージ API を呼び出す程度であれば、どちらを選択しても大きな違いはありません。

引数のマーシャリング

PInvoke を使用する場合、マネージ型と C++ のネイティブなプリミティブ型が同じフォームを持っていれば、マーシャリングの必要はありません。たとえば、Int32 と int、および Double と double との間では、マーシャリングは不要です。

フォームが異なる場合はマーシャリングが必要です。フォームが異なる型とは、char、string、struct などの型です。次の表は、マーシャラが各型に使用するマップを示します。

wtypes.h Visual C++ /clr を指定した Visual C++ 共通言語ランタイム

HANDLE

void*

void*

IntPtr、UIntPtr

BYTE

unsigned char

unsigned char

Byte

SHORT

short

short

Int16

WORD

unsigned short

unsigned short

UInt16

INT

int

int

Int32

UINT

unsigned int

unsigned int

UInt32

LONG

long

long

Int32

BOOL

long

bool

Boolean

DWORD

unsigned long

unsigned long

UInt32

ULONG

unsigned long

unsigned long

UInt32

CHAR

char

char

Char

LPCSTR

char*

String ^ [in], StringBuilder ^ [in, out]

String ^ [in], StringBuilder ^ [in, out]

LPCSTR

const char*

String ^

String

LPWSTR

wchar_t*

String ^ [in], StringBuilder ^ [in, out]

String ^ [in], StringBuilder ^ [in, out]

LPCWSTR

const wchar_t *

String ^

String

FLOAT

float

float

Single

DOUBLE

double

double

Double

マーシャラは、ランタイム ヒープに割り当てられたメモリのアドレスをアンマネージ関数に渡す場合、メモリを自動的に固定します。メモリの固定によって、割り当てられたメモリ ブロックをガベージ コレクタが圧縮時に移動することはなくなります。

このトピックの最初に示した例では、DllImport の CharSet パラメータでマネージ型の String をマーシャリングする方法を示しています。この場合、マネージ型の String をネイティブ側で使用できるように ANSI 文字列にマーシャリングしています。

ネイティブ関数で使用する各引数のマーシャリングについては、MarshalAs 属性で指定できます。String * 引数をマーシャリングする場合、BStr、ANSIBStr、TBStr、LPStr、LPWStr、および LPTStr などの選択肢があります。既定値は LPStr です。

// platform_invocation_services_3.cpp
// compile with: /clr
using namespace System;
using namespace System::Runtime::InteropServices;

[DllImport("msvcrt", EntryPoint="puts")]
extern "C" int puts([MarshalAs(UnmanagedType::LPWStr)] String ^);

int main() {
   String ^ pStr = "Hello World!";
   puts(pStr);
}

この例では、文字列を 2 バイトの Unicode 文字列 LPWStr としてマーシャリングしています。出力されるのは、Hello World! の最初の文字だけです。これは、マーシャリング後の文字列の第 2 バイトが null であり、puts は null を文字列の終わりを示すマーカーとして解釈するためです。

MarshalAs 属性は System::Runtime::InteropServices 名前空間にあります。この属性は、配列などのほかのデータ型にも使用できます。

PInvoke と Windows API

PInvoke は、Windows の関数を呼び出すときにも便利です。

次の例では、Visual C++ プログラムと Win32 API に属する MessageBox 関数との相互運用を示します。

// platform_invocation_services_4.cpp
// compile with: /clr /c
using namespace System;
using namespace System::Runtime::InteropServices;
typedef void* HWND;
[DllImport("user32", CharSet=CharSet::Ansi)]
extern "C" int MessageBox(HWND hWnd, String ^ pText, String ^ pCaption, unsigned int uType);

int main() {
   String ^ pText = "Hello World! ";
   String ^ pCaption = "PInvoke Test";
   MessageBox(0, pText, pCaption, 0);
}

出力は PInvoke Test というタイトルのメッセージ ボックスであり、テキスト Hello World! が表示されています。

PInvoke は、DLL から関数を検索するとき、マーシャリング情報も使用します。user32.dll には実際は MessageBox 関数はありませんが、CharSet=CharSet::Ansi により、PInvoke は Unicode バージョンの MessageBoxW ではなく ANSI バージョンの MessageBoxA を使用できます。一般に、Unicode バージョンのアンマネージ API を使用することをお勧めします。これは、.NET Framework の文字列オブジェクトのネイティブな形式である Unicode を ANSI に変換するオーバーヘッドがないためです。

PInvoke の使用が適さないケース

PInvoke は、DLL に属するすべての C スタイルの関数に適しているわけではありません。たとえば、次のように宣言された mylib.dll の MakeSpecial 関数について考えてみます。

char * MakeSpecial(char * pszString);

Visual C++ アプリケーションで PInvoke を使用する場合、次のようなコードになります。

[DllImport("mylib")]

extern "C" String * MakeSpecial([MarshalAs(UnmanagedType::LPStr)] String ^);

ここで問題なのは、MakeSpecial によって返されるアンマネージ文字列のメモリを削除できないことです。PInvoke によって呼び出されたほかの関数は、ユーザーによるメモリ解放の必要のない内部バッファへのポインタを返します。このケースでは、IJW 機能の方が適しているのは明らかです。

PInvoke の制限

パラメータとして使用したものと同じ正確なポインタをネイティブ関数から返すことはできません。PInvoke によってそれにマーシャリングされたポインタをネイティブ関数が返す場合、メモリが破損したり例外が発生したりすることがあります。

__declspec(dllexport)
char* fstringA(char* param) {
   return param;
}

次の例はこの問題を示しています。プログラムが正確な出力を与えるように見える場合でも、その出力は既に解放されたメモリから行われています。

// platform_invocation_services_5.cpp
// compile with: /clr /c
using namespace System;
using namespace System::Runtime::InteropServices;
#include <limits.h>

ref struct MyPInvokeWrap {
public:
   [ DllImport("user32.dll", EntryPoint = "CharLower", CharSet = CharSet::Ansi) ]
   static String^ CharLower([In, Out] String ^);
};

int main() {
   String ^ strout = "AabCc";
   Console::WriteLine(strout);
   strout = MyPInvokeWrap::CharLower(strout);
   Console::WriteLine(strout);
}

パフォーマンスに関する考慮事項

PInvoke は、1 回の呼び出しで 10 ~ 30 個分の x86 命令のオーバーヘッドを要します。この固定コストのほかに、マーシャリングによってオーバーヘッドが発生します。マネージ コードとアンマネージ コードの間で同じ表現である blittable 型については、マーシャリングのコストはかかりません。たとえば、int と Int32 との間の変換にコストは不要です。

パフォーマンスを上げるには、マーシャリングするデータが少ない PInvoke 呼び出しを何度も行うより、少ない PInvoke 呼び出しでできるだけ多くのデータをマーシャリングすることをお勧めします。

参照

コミュニティ コンテンツ   コミュニティ コンテンツとは
新しいコンテンツの追加 RSS  注釈
Processing
© 2010 Microsoft Corporation. All rights reserved. 使用条件 | 商標 | プライバシー
Page view tracker