C/C++ 是電腦世界的共同語言。而在 C# 中,串接 C/C++ 的方式有很多種。前次記錄了一種做法:透過 DllImport 讓 C# 呼叫原生函式,這次要來記錄另一種方式。
先回顧一下,當提到 .NET,通常會先想到 C#,其次可能是 VB.NET 或 C++/CLI ( F#表示:當我塑膠?) ,而 C++/CLI 的特點在於,它能同時撰寫 .NET 的 managed 程式,也能直接整合原生 unmanaged 的 C/C++ 程式碼。
本文將記錄如何將 C/C++ 原始碼放入 C++/CLI 專案中,並將其製作成可供 C# 直接引用的類別庫。
本次的開發環境,如下
- IDE : Visual Studio 2022
- .NET版本 : . NET 8.0
事前準備與方案架構
在開始前,除了需要安裝 Visual Studio、C# 和 .NET 之外,
因為這次會用到 C++/CLI 的部分,所以還需要相關的開發工具。
如果 Visual Studio 安裝時沒有勾選相關項目,
可以開啟 Visual Studio Installer 來進行安裝。
理論上最少要安裝這些,
- MSVC v143 - VS 2022 C++ x64/x86 建置工具
- C++ 核心功能
- C++/CLI support for v143 build tools
因為我測試過一些功能,
安裝的項目也比較混亂,
所以並不是這麼確定 ,
(哪天有機會確定後再更新)
以下附上我的安裝清單與畫面供參考,
如有需要可依照個人需求自行調整環境。

本次的預計的方案架構如下,
1 | Solution: CsUseCppCliSample |
整體分成兩個主要部分,分別是,
- CppCliLibrary:C++/CLI 類別庫
- CSharpTestApp: C# 專案
雖然最後是由 C# 來呈現執行結果,
不過 C++/CLI 是一種可以同時撰寫「原生 C++」與「.NET 託管程式碼」的 C++ 延伸語法,
它可以:
- 呼叫原生 C++ 程式碼
- 同時產生 .NET 類別庫
因此, C++/CLI 類別庫編譯出來的是 ****.NET 類別庫 DLL,
也就是說,只要是 .NET 平台上的語言 ( C#、VB.NET… 等),
理論上都可以直接引用該 DLL,而不限於 C#。
接下來,將從建立專案開始,
並一步步完成整個流程。
建立 C++/CLI 類別庫專案
首先,從建立 C++/CLI 類別庫專案開始。
Visual Studio 建立好方案後,
在方案上按右鍵 → 加入 → 新增專案。

語言篩選部分可以選擇 C++,
接著找到 CLR 類別庫的項目,
次範例將執行於 .NET 架構上,
因此選擇 CLR 類別庫(.NET),
如果目標平台是 .NET Framework 的話,
則要選擇 CLR 類別庫(.NET Framework)。

設定專案名稱後即可建立專案,
建立完成後,畫面會像下圖這樣,
已經有一些必要與預設的檔案。

接下來,建立新的標頭檔(.h),
在「標頭檔」資料夾上按右鍵 → 加入 → 新增項目。

接著選擇「標頭檔 (.h)」,輸入名稱後按下「新增」,
這邊總共新增兩個 .h 檔 NativeDll.h 和 NativeWrapper.h

同樣的步驟,在「來源檔案」資料夾中,
建立 C++ 檔 (.cpp)。

這邊建立與兩個 .h 對應的兩個 cpp 檔,
NativeDll.cpp 和 NativeWrapper.cpp。

上述步驟完成後,
整個專案的架構與狀況如下圖所示,

到這邊為止,專案的初步建立就完成了。
C++ 原生程式碼範例
再來是 C++ 原生程式碼 .h 與 .cpp 的程式內容。
下面提供的程式碼可以直接貼進專案中使用。
這個範例示範了一些基本的 C++ 方法,
各功能方法的細節這裡就不特別說明,
NativeDll.h
1 |
|
NativeDll.cpp
1 |
|
補充說明,在 NativeDll.h 中,
可以看到使用了 extern "C" 與 __declspec(dllexport)。
這是因為本範例的 C++ 程式碼,同時也可以提供給 P/Invoke 機制或不同平台使用的情境。
(有興趣的話,可前往 C# 與原生 C/C++ DLL:從建立到呼叫的實作筆記)
如果實務專案是:
- 純 C++ 使用
- 或僅提供給 C++/CLI 專案呼叫
其實不需要撰寫這些宣告,直接使用一般的 C++ 函式即可。
建立呼叫 C++ 的 C++/CLI 程式碼
接下來實作 C++/CLI 包裝層,
這一層的角色很單純,
將 Native C++ 的函式轉換成 .NET 可直接使用的類別與方法。
- 對外,它是一個標準的 .NET 類別庫。
- 對內,它負責呼叫前面實作的 Native C++ 函式。
NativeWrapper.h
1 |
|
NativeWrapper.cpp
1 |
|
從以上程式碼可以看得出來,
C++/CLI 是一種介於 C++ 與 .NET 之間的混合寫法。
在檔案結構上,仍然保留了 C++ 的風格,
例如 .h 與 .cpp 分離,將宣告與實作拆成兩個檔案。
不過在 .NET 架構下,其實也可以將定義與實作寫在同一個檔案中,
就像 C# 的 .cs 檔一樣。
實務上是否拆分,可以依專案規模與習慣自行決定。
至於程式內容的寫法,
例如 namespace、public、private、class 等語法,
其實和 C# 非常接近。
不過因為本質上還是 C++,
會出現一些 C++/CLI 才有的特殊符號。
例如,:: 、^ 、%
在 C# 中,大多數成員存取只會看到使用 .,
而在 C++/CLI 中,則會混用 C++ 與 .NET 的語法。
雖然這些符號都有規則可循,
但實際撰寫時確實比較容易讓人混淆,
特別是在 Native 與 .NET 型別交錯出現的時候。
剛開始接觸時,可能會有些燒腦。
以前曾因為弄錯符號編譯不過搞半天,
還好現在有 AI 可以問
另外,在 class NativeWrapper 前面可以看到 ref 關鍵字。
這個關鍵字主要用來區分與一般 C++ 類別的差異。
也就是說:
- 僅使用
class→ 代表一般 C++ 類別(Unmanaged) - 使用
ref class→ 代表 .NET 類別(Managed)
所謂 Managed 與 Unmanaged 的差異如下,
- Managed:由 .NET 執行環境(CLR)負責執行與記憶體管理。
- Unmanaged:傳統 C/C++ 程式碼,記憶體需自行管理,
在 .NET 中,像 C#、VB.NET ,撰寫出的程式碼就屬於 Managed,
而 C++/CLI 因為同時支援 Managed 與 Unmanaged 程式碼,
因此在類別宣告時,需要透過 ref 關鍵字來明確區分。
編譯 C++/CLI 類別庫
完成 C++ 與 C++/CLI 程式碼撰寫後,接下來進行專案編譯。
一般情況下,若沒有特別需求,
可以像一般 C# 類別庫一樣直接建置(Build)即可。
若需要指定目標 Framework 或 Windows 版本,
則可以依下列步驟進行設定。
首先,在專案上按右鍵 → 選擇「屬性」。

進入 C++/CLI 專案的屬性設定視窗後,
點選「組態屬性」→「進階」。
在此設定:
- .NET 目標 Framework:選擇 .NET 8.0
- .NET 目標 Windows 版本:選擇 10.0.19041.0
上述設定為本範例使用的環境版本,
實際專案可依開發環境與需求自行調整。

C# 串接 C++/CLI 執行結果
最後進入實際執行的部分。
這裡建立一個 C# 主控台專案。

C# 主控台專案相依性加入前面的 C++/CLI 專案。

完成後,整個方案的架構如下圖所示。

再來,在 Program.cs 中撰寫一些簡單的測試程式,
實際呼叫前面封裝的 NativeWrapper 中的方法。
Program.cs
1 | using NativeBridge; |
編譯前,把編譯目標設定與類別庫相同,

最後執行整個程式,
便可看到執行的結果如下圖,

上述步驟完成後,就能順利看到執行結果,
到這邊為止,應該已能掌握從建立 C++/CLI 到在 C# 中使用的基本流程。
補充:C++/CLI 進入中斷點
程式撰寫過程中,難免需要 debug。
而 C++/CLI 跟使用 C# 一樣,
直接在需要的地方下中斷即可。
例如在 C++/CLI 轉接層程式下中斷,

或專案內 C++ 原生程式中下中斷,

沒有特殊狀況,都可以下中斷點,
並且可以看到變數的內容,
不需要額外開混合模式。
總結
以上就是使用 C++/CLI 串接 C++ 原生函式的整體流程。
整體來說,相較於直接使用原生 C++ DLL 搭配 DllImport 進行呼叫,
透過 C++/CLI 建立一層橋接,
流程步驟個人感覺不算複雜。
在實務上有幾個地方需要特別注意:
- 一開始需要先安裝並啟用 C++/CLI 相關元件。
- C++/CLI 的語法同時包含 C++ 與 .NET 的寫法,但會出現一些特殊符號(例如
^、gcnew),初學時可能會覺得有點不習慣。 - 雖然檔案同樣分為
.h與.cpp,但內容可能是原生 C++,也可能是 C++/CLI 程式碼。
另外,原生 C++ 的寫法,
有時候在 C++/CLI 做橋接時會變得比較麻煩。
有些在 C++ 裡很正常的設計,
在 .NET 環境下不一定適合,
硬是照原本方式實作,
反而會讓程式變得很複雜。
因為 C++/CLI 在 managed 這一側還是走 .NET 的機制,
所以如果發現橋接寫起來太繞,
其實可以換個做法,
用比較符合 .NET 習慣的方式來設計,
不一定要完全照搬原本的 C++ 寫法。
自言自語543
遙想當年稍微碰觸過 MFC 專案後,
對於使用 .h 和 .cpp 檔撰寫視窗程式,
有了初步的認識 。
(是個超麻煩的東西)
而過了幾年後開始接觸 .net framework,
其中就碰到 C++/CLI…
又或是當時稱為 Visual C++ .NET 的專案,
看了一頭霧水後,
決心買一本書來好好的鑽研,
把書中的範例手動 key 到電腦中,
但是卻怎麼樣也編譯不起來…
後面才知道書中的語法都是舊的…
最後搞懂 .h 和 .cpp 在 C++/CLI 的狀況後,
反而是看著網路上 C# 的範例,
自行轉換到 C++/CLI 中,
這樣一步一腳印慢慢地了解並熟悉 .net framework。
記得以前的 Visual Studio 的範本,
有 C#、VB.NET 和 C++.NET 這三本柱,
不過某個版本開始把 C++/CLI 給移除了,
現在不僅要自己去安裝,
並且如果要建立 C++/CLI 的 Winform 專案,
似乎也要完全從零開始建立。
至於在 C# 中要呼叫 C++,
不管是使用 DllImport,還是透過 C++/CLI 做橋接,
其實都算是解法之一。
只是 C++/CLI 微軟已沒有特別的去推進,
或許未來還會有更好的整合方式,
等有機會再來研究看看。