HOW TO:使用 PInvoke 封送處理陣列
本主題會說明如何使用 .NET Framework Platform Invoke 支援,以 CLR 字串型別 String 呼叫接受 C-Style 字串的原生 (Native) 函式。 我們鼓勵 Visual C++ 程式設計人員盡可能改用 C++ Interop 功能,因為 P/Invoke 提供極少的編譯時期錯誤報告,不具型別安全,而且實作時很瑣碎無聊。 如果 Unmanaged API 封裝為 DLL,而且沒有原始程式碼,則 P/Invoke 是唯一的選擇 (否則請參閱使用 C++ Interop (隱含 PInvoke))。
範例
因為原生和 Managed 陣列在記憶體中排列的方式不同,會需要轉換或封送處理 (Marshaling) 以便在 Managed/Unmanaged 界限之間成功地傳遞這些物件。 本主題會示範如何從 Managed 程式碼將具有簡單 (blitable) 項目的陣列傳遞至原生函式。
DllImportAttribute 屬性 (Attribute) 會用來為每個所用到的原生函式建立 Managed 進入點 (對於 Managed/Unmanaged 資料封送處理而言通常是如此)。 對於取用陣列作為引數的函式,必須使用 MarshalAsAttribute 屬性並且對編譯器 (Compiler) 指定資料封送處理的方式。 在下列範例中,LPArray 列舉型別會用來表示 Managed 陣列將會封送處理成 C-Style 陣列。
下列程式碼由 Unmanaged 和 Managed 的模組所組成。 Unmanaged 模組是 DLL,定義接受整數陣列的函式。 第二個模組是匯入這個函式的 Managed 命令列應用程式,但是使用 Managed 陣列定義此函式,並使用 MarshalAsAttribute 屬性,指定呼叫時要將陣列轉換為原生陣列。
Managed 模組是使用 /clr 編譯的,但是 /clr:pure 也一樣可以執行。
// TraditionalDll4.cpp
// compile with: /LD /EHsc
#include <iostream>
#define TRADITIONALDLL_EXPORTS
#ifdef TRADITIONALDLL_EXPORTS
#define TRADITIONALDLL_API __declspec(dllexport)
#else
#define TRADITIONALDLL_API __declspec(dllimport)
#endif
extern "C" {
TRADITIONALDLL_API void TakesAnArray(int len, int[]);
}
void TakesAnArray(int len, int a[]) {
printf_s("[unmanaged]\n");
for (int i=0; i<len; i++)
printf("%d = %d\n", i, a[i]);
}
// MarshalBlitArray.cpp
// compile with: /clr
using namespace System;
using namespace System::Runtime::InteropServices;
value struct TraditionalDLL {
[DllImport("TraditionalDLL4.dll")]
static public void TakesAnArray(
int len,[MarshalAs(UnmanagedType::LPArray)]array<int>^);
};
int main() {
array<int>^ b = gcnew array<int>(3);
b[0] = 11;
b[1] = 33;
b[2] = 55;
TraditionalDLL::TakesAnArray(3, b);
Console::WriteLine("[managed]");
for (int i=0; i<3; i++)
Console::WriteLine("{0} = {1}", i, b[i]);
}
請注意,傳統 #include 指示詞不會將 DLL 的任何部分公開 (Expose) 給 Managed 程式碼。 事實上,因為只會在執行階段存取 DLL,使用 DllImportAttribute 匯入之函式所產生的問題,就不會在編譯時期偵測出來。