welcom ! Handel home

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

探討:Visual C++ 中的 P/Invoke 替代方案與 C++ Interop

 C++ Interop。 這項技術提供了一種更直觀、更符合 C++ 語言特性的方式來與非託管程式碼進行互動,

相較於傳統的 P/Invoke,它能帶來更高的效率和更少的程式碼。

C++ Interop 的優勢與特性

• 無需手動封送: C++ Interop 能自動處理託管與非託管類型之間的轉換,大大簡化了程式碼。

• 原生 C++ 語法: 使用 C++ 語法來宣告和呼叫非託管函式,讓開發者能夠更自然地寫出程式碼。

• 模板支援: C++ 模板可以提供更強大的類型安全和泛型程式設計能力。

• CLR 和 WinRT 元件互動: 不僅限於傳統的 DLL,C++ Interop 還能與 CLR 和 WinRT 元件進行互動。

C++ Interop 的運作原理

C++ Interop 主要透過以下機制實現:

• __declspec(dllimport):用於宣告導入的函式或變數。

• extern "C":用於指定函式採用 C 語言連結方式,以避免名稱修飾。

• CLR 和 WinRT 投影: C++/CLI 提供了 CLR 和 WinRT 的投影,讓 C++ 程式碼能夠直接使用這些平臺的類型和成員。

何時使用 C++ Interop?

• 需要與現有的 C++ DLL 互動: 如果您已經有現成的 C++ DLL,使用 C++ Interop 是最直接的方式。

• 希望利用 C++ 的性能優勢: 對於性能要求較高的部分,可以將部分程式碼用 C++ 實現,然後透過 C++ Interop 呼叫。

• 需要與 CLR 或 WinRT 元件互動: 如果您的應用程式需要同時使用 C++ 和 .NET Framework 或 Windows Runtime,C++ Interop 能提供無縫的整合。

示例

C++

// C++ header file (mylib.h)

extern "C" __declspec(dllimport) int add(int a, int b);


// C++ source file (mycpp.cpp)

#include "mylib.h"


int main() {

    int result = add(3, 4);

    std::cout << result << std::endl;

    return 0;

}


結語

C++ Interop 是 Visual C++ 開發者的一項強大工具,它能幫助開發者更有效地利用 C++ 的優勢,同時又能與其他程式語言和平臺進行互操作。

如果您需要在 C++ 程式中呼叫非託管程式碼,C++ Interop 絕對是值得深入了解的技術。

C++ Interop 與 P/Invoke 的差異比較

 C++ Interop 和 P/Invoke 都是用於在 C++ 程式中呼叫非託管程式碼的技術,但兩者在使用方式、便利性以及功能上有一些差異。

C++ Interop

• 概念: C++ Interop 是一種更現代、更直觀的方式,允許 C++ 程式碼直接與其他 .NET 語言(如 C#、VB.NET)編寫的程式碼進行互動。它利用 C++/CLI 提供的語言擴展,使 C++ 程式碼能更自然地使用 .NET Framework 的類別和功能。

• 特性: 

○ 隱式 P/Invoke: C++ Interop 常常被稱為「隱式 P/Invoke」,因為它在幕後自動處理許多 P/Invoke 所需的手動配置。

○ 類型安全: C++ Interop 提供了更強的類型安全,減少了因類型不匹配導致的錯誤。

○ 自然語法: 可以使用 C++ 語法直接操作 .NET 類別和物件。

○ 自動封送處理: 大部分的資料封送處理都是自動完成的。

• 優點: 

○ 更易於使用: 比 P/Invoke 更直觀,減少了程式設計師的工作量。

○ 更好的整合性: 與 .NET Framework 的整合更緊密。

○ 更高的效率: 在許多情況下,C++ Interop 的性能比 P/Invoke 更高。

• 缺點: 

○ 學習曲線: 需要學習 C++/CLI 的語法和概念。

○ 較為複雜: 當需要處理複雜的資料結構或 COM 元件時,可能需要更深入的了解。

P/Invoke (平台調用)

• 概念: P/Invoke 是一種更傳統的方式,允許 .NET 程式碼呼叫 Windows API 或其他非託管 DLL 中的函式。它需要程式設計師手動指定函式簽名、參數類型以及封送處理方式。

• 特性: 

○ 手動配置: 需要手動指定函式簽名、參數類型和封送處理方式。

○ 封送處理: 必須處理託管類型和非託管類型之間的資料轉換。

○ 較低層級: 提供了更低層級的控制,但也需要更多的程式碼。

• 優點: 

○ 靈活性高: 可以呼叫任何 Windows API 函式。

○ 適用於各種場景: 適用於各種互操作場景,包括 COM 元件、C++ DLL 等。

• 缺點: 

○ 易錯: 手動配置容易出錯,尤其是在處理複雜的資料結構時。

○ 程式碼複雜: 需要寫更多的程式碼。

○ 性能較低: 相對於 C++ Interop,P/Invoke 的性能可能稍低。

何時使用哪一種?

• C++ Interop: 

○ 希望與 .NET Framework 深度整合。

○ 需要使用 C++/CLI 的特性。

○ 追求更高的開發效率。

• P/Invoke: 

○ 需要呼叫特定的 Windows API 函式。

○ 需要對互操作過程有精確的控制。

○ 性能要求極高,且對程式碼大小敏感。

總結

C++ Interop 和 P/Invoke 都是有用的工具,選擇哪一種取決於具體的應用場景。如果您的目標是與 .NET Framework 深度整合,並且希望提高開發效率,那麼 C++ Interop 是更好的選擇。如果需要對互操作過程有精確的控制,或者需要呼叫特定的 Windows API 函式,那麼 P/Invoke 可能是更合適的選擇。

建議:

• 一般情況下,建議優先考慮 C++ Interop,因為它更易於使用且效率更高。

• 對於複雜的場景,可能需要結合 C++ Interop 和 P/Invoke 來實現。

• 如果需要呼叫非常底層的 API,或者對性能要求極高,那麼 P/Invoke 可能是唯一的選擇。

舉例來說:

• 如果您想在 C++ 程式中使用 .NET 的集合類,那麼 C++ Interop 是更好的選擇。

• 如果您想在 C# 程式中呼叫一個 C++ DLL 中的特定函式,那麼 P/Invoke 可能更適合。


C++ 程式中的非託管程式碼與託管程式碼