如何:使用 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 位元組結構封裝。
接下來,使用 DllImportAttribute 來宣告對應至任何接受 結構的 Unmanaged 函式的進入點,但在函式簽章中使用結構的 Managed 版本,如果您針對這兩個版本的結構使用相同的名稱,這是一個無問題點。
現在 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