2024年12月11日 星期三

C# P/Invoke 呼叫 C++ DLL 範例與說明

 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 或其他方式。

沒有留言:

張貼留言