P/Invoke (Platform Invoke) 是 .NET Framework 提供的一種機制,允許受控程式碼 (如 C#) 呼叫非受控程式碼 (如 C++ DLL) 中的函式。
這對於需要利用既有 C++ 程式庫或直接與系統 API 互動的 .NET 應用程式來說非常有用。
[基本步驟]
1. 準備 C++ DLL:
○ 使用 C++ 編寫 DLL,並將函式宣告為 extern "C" __declspec(dllexport),以便 C# 能正確找到並呼叫這些函式。
2. 在 C# 中宣告 P/Invoke 函式:
○ 使用 DllImportAttribute 標記,指定 DLL 的名稱和函式的名稱。
○ 匹配函式的簽名,包括傳入參數和回傳值。
3. 呼叫 P/Invoke 函式:
○ 在 C# 程式碼中,直接呼叫 P/Invoke 函式,就像呼叫任何其他 .NET 方法一樣。
[範例]
C++ DLL (mydll.cpp):
C++
#include <windows.h>
extern "C" __declspec(dllexport) int Add(int a, int b)
{
return a + b;
}
[C# 程式碼]:
C#
using System;
using System.Runtime.InteropServices;
namespace PInvokeExample
{
class Program
{
[DllImport("mydll.dll")]
static extern int Add(int a, int b);
static void Main(string[] args)
{
int result = Add(5, 3);
Console.WriteLine("Result: " + result);
}
}
}
[說明]
• DllImportAttribute:
○ DllImport("mydll.dll"): 指定要載入的 DLL 名稱。
○ static extern int Add(int a, int b): 宣告一個外部函式 Add,其簽名與 C++ 中的 Add 函式完全匹配。
• 封送處理 (Marshalling):
○ P/Invoke 會自動處理 C# 類型和 C++ 類型之間的轉換,這稱為封送處理。
○ 基本的數值類型 (int, double 等) 通常可以直接對應。
○ 對於結構、字串等複雜類型,需要使用 Marshal 類來進行手動封送。
• 注意事項:
○ DLL 的位數 (32 位或 64 位) 必須與你的 .NET 應用程式一致。
○ DLL 的路徑要確保能被找到。
○ 函式簽名必須完全匹配,包括參數類型、順序和返回值類型。
○ 對於 C++ 中的指標,在 C# 中可以使用 IntPtr 來表示。
[更複雜的例子]:傳遞結構
C++ DLL (mydll.cpp):
C++
struct Point
{
int x;
int y;
};
extern "C" __declspec(dllexport) int GetDistance(Point* p1, Point* p2)
{
// ... 计算距离
}
C# 程式碼:
C#
[StructLayout(LayoutKind.Sequential)]
struct Point
{
public int x;
public int y;
}
[DllImport("mydll.dll")]
static extern int GetDistance(ref Point p1, ref Point p2);
[更多注意事項]
• 錯誤處理: 可以使用 Marshal.GetLastWin32Error() 獲取最後一次 Win32 錯誤碼。
• 回呼函式: 可以使用 Callback 屬性來定義回呼函式。
• 字串封送: 使用 Marshal.StringToHGlobalAnsi 和 Marshal.PtrToStringAnsi 來轉換字串。
[總結]
P/Invoke 提供了一種方便的方式,讓 C# 程式可以利用 C++ DLL 中的函式。但需要注意的是,P/Invoke 的使用需要對 C++ 和 C# 的記憶體管理、資料類型以及平台調用機制有一定的了解。
建議:
• 保持函式簽名一致: 確保 C# 中的函式簽名與 C++ 中的函式簽名完全匹配。
• 處理封送: 對於複雜的資料類型,需要仔細處理封送。
• 錯誤處理: 始終檢查 P/Invoke 呼叫是否成功。
• 性能考慮: P/Invoke 會引入一定的性能開銷,對於性能要求極高的應用程式,可以考慮使用 C++/CLI 或其他方式。