次の方法で共有


方法: PInvoke を使用して構造体をマーシャリングする

このドキュメントでは、P/Invoke を使用して、C スタイルの構造体を受け入れるネイティブ関数を、マネージド関数から呼び出す方法について説明します。 P/Invoke ではコンパイル時のエラー報告がほとんどなく、タイプセーフでなく、実装に手間がかかるため、P/Invoke ではなく C++ Interop 機能を使用することをお勧めしますが、アンマネージド API が DLL としてパッケージ化されており、ソース コードを利用できない場合は、P/Invoke が唯一のオプションです。 それ以外の場合は、次のドキュメントを参照してください。

既定では、ネイティブ構造体とマネージド構造体はメモリ内でのレイアウトが異なるため、マネージド/アンマネージド境界を越えて構造体を正常に渡すには、データの整合性を維持するために、追加のステップが必要です。

このドキュメントでは、ネイティブ構造体と同等のマネージド構造体を定義するために必要なステップと、結果の構造体をアンマネージド関数に渡す方法について説明します。 このドキュメントでは、単純な構造体 (文字列やポインターが含まれない構造体) が使用されていることを前提とします。 blittable でない相互運用性の詳細については、「C++ Interop (暗黙の PInvoke) の使用」を参照してください。 P/Invoke の戻り値は、非 blittable 型にはなりません。 blittable 型の表現は、マネージド コードとアンマネージド コードで同じです。 詳細については、「blittable 型と非 blittable 型」を参照してください。

マネージド/アンマネージド境界を越えて単純な blittable 構造体をマーシャリングするには、まず、各ネイティブ構造体のマネージド バージョンを定義する必要があります。 これらの構造体には、任意の正式名称を指定できます。それぞれのデータ レイアウト以外に、2 つの構造体のネイティブ バージョンとマネージド バージョンの間には関係はありません。 したがって、マネージド バージョンには、ネイティブ バージョンのフィールドと同じサイズで同じ順序のフィールドが含まれている必要があります。 (構造体のマネージド バージョンとネイティブ バージョンが同等であることを保証するメカニズムがないため、実行時まで非互換性は明らかになりません。2 つの構造体のデータ レイアウトが同じであることを保証するのはプログラマの責任です)。

マネージド構造体のメンバーはパフォーマンス上の目的で並べ替えられている場合があるため、構造体が順番にレイアウトされていることを示すために、StructLayoutAttribute 属性を使用する必要があります。 また、構造体のパッキング設定をネイティブ構造体で使用されている設定と同じに明示的に設定することもお勧めします。 (ただし、Visual C++ では既定で、両方のマネージド コードに対して、8 バイトの構造体のパッキングを使用します。)

  1. 次に、DllImportAttribute を使用して、構造体を受け入れるアンマネージ関数に対応するエントリ ポイントを宣言しますが、関数シグネチャでは構造体のマネージド バージョンを使用します。これは、構造体の両方のバージョンに同じ名前を使用する場合に問題になります。

  2. これで、マネージド コードで、構造体のマネージド バージョンを、実際にマネージド関数である場合と同様に、アンマネージド関数に渡すことができるようになります。 これらの構造体は、次の例に示す値または参照によって渡すことができます。

アンマネージド モジュールとマネージド モジュール

次のコードは、アンマネージド モジュールとマネージド モジュールで構成されています。 アンマネージ モジュールは、Location という名前の構造体と、その Location 構造体の 2 つのインスタンスを受け入れる GetDistance という関数を定義する DLL です。 2 番目のモジュールは、GetDistance 関数をインポートするマネージド コマンドライン アプリケーションですが、Location 構造体に相当するマネージド構造体である MLocation に換えてそれを定義します。 実際には、おそらく両方のバージョンの構造体に同じ名前が使用されます。ただし、ここではマネージド バージョンに換えて DllImport プロトタイプが定義されていることを示すために、別の名前が使用されています。

従来の #include ディレクティブを使用すると、DLL がマネージド コードに公開されないため注意してください。 実際、DLL へのアクセスが発生するのは実行時のみであるため、DllImport でインポートされた関数に関する問題はコンパイル時に検出されません。

例: アンマネージド DLL モジュール

// TraditionalDll3.cpp
// compile with: /LD /EHsc
#include <iostream>
#include <stdio.h>
#include <math.h>

#define TRADITIONALDLL_EXPORTS
#ifdef TRADITIONALDLL_EXPORTS
   #define TRADITIONALDLL_API __declspec(dllexport)
#else
   #define TRADITIONALDLL_API __declspec(dllimport)
#endif

#pragma pack(push, 8)
struct Location {
   int x;
   int y;
};
#pragma pack(pop)

extern "C" {
   TRADITIONALDLL_API double GetDistance(Location, Location);
   TRADITIONALDLL_API void InitLocation(Location*);
}

double GetDistance(Location loc1, Location loc2) {
   printf_s("[unmanaged] loc1(%d,%d)", loc1.x, loc1.y);
   printf_s(" loc2(%d,%d)\n", loc2.x, loc2.y);

   double h = loc1.x - loc2.x;
   double v = loc1.y = loc2.y;
   double dist = sqrt( pow(h,2) + pow(v,2) );

   return dist;
}

void InitLocation(Location* lp) {
   printf_s("[unmanaged] Initializing location...\n");
   lp->x = 50;
   lp->y = 50;
}

例: マネージド コマンドライン アプリケーション モジュール

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

[StructLayout(LayoutKind::Sequential, Pack=8)]
value struct MLocation {
   int x;
   int y;
};

value struct TraditionalDLL {
   [DllImport("TraditionalDLL3.dll")]
   static public double GetDistance(MLocation, MLocation);
   [DllImport("TraditionalDLL3.dll")]
   static public double InitLocation(MLocation*);
};

int main() {
   MLocation loc1;
   loc1.x = 0;
   loc1.y = 0;

   MLocation loc2;
   loc2.x = 100;
   loc2.y = 100;

   double dist = TraditionalDLL::GetDistance(loc1, loc2);
   Console::WriteLine("[managed] distance = {0}", dist);

   MLocation loc3;
   TraditionalDLL::InitLocation(&loc3);
   Console::WriteLine("[managed] x={0} y={1}", loc3.x, loc3.y);
}
[unmanaged] loc1(0,0) loc2(100,100)
[managed] distance = 141.42135623731
[unmanaged] Initializing location...
[managed] x=50 y=50

関連項目

C++ での明示的な PInvoke (DllImport 属性) の使用方法