共用方式為


如何:使用 PInvoke 封送處理結構

本檔說明如何使用 P/Invoke,從 Managed 函式呼叫接受 C 樣式結構的原生函式。 雖然我們建議您使用 C++ Interop 功能,而不是 P/Invoke,因為 P/Invoke 提供很少的編譯時期錯誤報表、不是型別安全,而且如果 Unmanaged API 封裝為 DLL 且原始程式碼無法使用,P/Invoke 是唯一的選項。 否則,請參閱下列檔:

根據預設,原生和 Managed 結構會在記憶體中以不同的方式配置,因此成功跨 Managed/Unmanaged 界限傳遞結構需要額外的步驟來保留資料完整性。

本檔說明定義原生結構的 Managed 對等專案,以及如何將產生的結構傳遞至 Unmanaged 函式所需的步驟。 本檔假設會使用簡單的結構,也就是不包含字串或指標的結構。 如需非 blittable 互通性的相關資訊,請參閱 使用 C++ Interop (隱含 PInvoke) 。 P/Invoke 不能有非 blittable 型別做為傳回值。 Blittable 類型在 Managed 和 Unmanaged 程式碼中具有相同的標記法。 如需詳細資訊,請參閱 Blittable 和非 Blittable 類型

首先,跨 Managed/Unmanaged 界限封送處理簡單且 blittable 的結構,必須先定義每個原生結構的 Managed 版本。 這些結構可以具有任何法定名稱;除了資料配置以外,兩個結構的原生和 Managed 版本之間沒有關聯性。 因此,受控版本必須包含大小相同且與原生版本相同順序的欄位。 (沒有機制可確保結構的 Managed 和原生版本相等,因此在執行時間之前,不相容不會變得明顯。程式設計人員有責任確保兩個結構具有相同的資料配置。

因為 Managed 結構的成員有時會因為效能目的而重新排列,所以必須使用 StructLayoutAttribute 屬性來指示結構會循序配置。 也建議您明確設定結構封裝設定,使其與原生結構所使用的相同。 (雖然根據預設,Visual C++ 會針對兩個 Managed 程式碼使用 8 位元組結構封裝。

  1. 接下來,使用 DllImportAttribute 來宣告對應至任何接受 結構的 Unmanaged 函式的進入點,但在函式簽章中使用結構的 Managed 版本,如果您針對這兩個版本的結構使用相同的名稱,這是一個無問題點。

  2. 現在 Managed 程式碼可以將結構的 Managed 版本傳遞至 Unmanaged 函式,就像它們實際上是受控函式一樣。 這些結構可以依值或傳址方式傳遞,如下列範例所示。

Unmanaged 和 Managed 模組

下列程式碼包含 Unmanaged 和 Managed 模組。 Unmanaged 模組是 DLL,定義名為 Location 的結構,以及稱為 GetDistance 的函式,可接受兩個 Location 結構的實例。 第二個模組是匯入 GetDistance 函式的 Managed 命令列應用程式,但以位置結構 MLocation 的 Managed 對等專案來定義它。 在實務上,同一個名稱可能會用於結構的這兩個版本:不過,這裡使用不同的名稱來示範 DllImport 原型是以 Managed 版本來定義。

請注意,DLL 中沒有任何部分會使用傳統的 #include 指示詞向 Managed 程式碼公開。 事實上,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;
}

範例:Managed 命令列應用程式模組

// 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 屬性)