一、簡單用法
C#和C++是非常相似的兩種語言,然而我們卻常常將其用於兩種不同的地方,C#得益於其簡潔的語法和豐富的類庫,常用來構建業務系統。C++則具有底層API的訪問能力和拔尖的執行效率,往往用於訪問底層模塊和構建有性能要求的算法。
這兩種場景看起來有較大的差異,大多數的時候可以各行其道。但還是有很多時候會出現融合的情況。當我們構建分布式系統的時候,由於RPC機制一般都是語言無關的,我們大可以將其各盡所長,按需划分在最能發揮其長處的位置。然而,一旦我們需要構建融合兩者需求的集中式系統的時候,就會頭痛無比。
此時,我們可以使用C++/CLI搭建C++和.Net之間的橋梁,C++/CLI是一個比較有意思的兩棲模塊,它具有如下特點
-
既可以訪問.Net類庫,也可以訪問C++原生類庫
-
既可以被.Net程序引用,也可以被C++原生程序引用
使用C++/CLI,我們可以使用C++編寫算法,用C#編寫界面,也可以使用.Net Framework類庫增強C++程序功能,各取所長。關於的優點,園子里有篇文章介紹的比較詳細,值得一讀:從C++到C++/CLI。
下面我們就以一個簡單的例子來演示一下它的用法:
Calculator.h:
#pragma once namespace CppCliTest { public ref class Calculator { public: int Add(int a, int b); }; }
Calculator.cpp
#include "stdafx.h" #include "Calculator.h" namespace CppCliTest { int Calculator::Add(int a, int b) { return a + b; } }
main.cpp
#include "stdafx.h" #include "Calculator.h" using namespace System; using namespace CppCliTest; int main(array<System::String ^> ^args) { Calculator^ calculator = gcnew Calculator(); int result = calculator->Add(3, 2); Console::WriteLine(L"Result is {0}", result); return 0; }
從這個例子中,我們可以簡單的管中窺豹的看看C++/CLI是在C++的基礎上擴充了一套語法,使其具有訪問.Net原始的功能,這里用到的有:
-
使用ref class聲明CLI引用類型(C#中的class)
-
使用^(例如如這里的String ^)來定義CLI引用類型
-
使用gcnew創建CLI的引用類型
具體的功能我將在后面的文章中再做介紹,MSDN中也有文檔詳細的介紹了這些語法:https://msdn.microsoft.com/zh-cn/library/ms235289.aspx
雖然C++/CLI同時具有兩者的功能,但它使得本就比較復雜的C++語法變得更加復雜了(特別是初期的版本,非常復雜,現在已經簡化了不少了),並且長期沒有得到VisualStudio這宇宙第一IDE的較好支持(在VS2010的時候還不支持智能提示),是無法與擁有大量語法糖的C#比開發效率的。加上大多數需求場景可以通過分布式系統解決,這些都導致了它一直沒有得到太多的關注。但是,微軟還是在積極的改進它的,加上C++11的支持,現在已經比之前好用多了,如果用在合適的位置,是絕對能讓你的開發如魚得水的。
二、復雜用法
對於如下C#代碼:
System.Object x = new System.Object();
其在C++/CLI中的等價代碼如下:
System::Object^ x = gcnew System::Object();
和傳統的C++創建的語法比較下,
P* x = new P();
我們不難發現,對於托管對象,主要引入了如下兩個語法:
-
用gcnew代替new實現托管對象的創建
-
用^代替*實現托管對象的指針
這種方式創建的對象是可以直接被CLR支持的,可以在C#中使用。托管對象指針使用的方式和傳統的對象指針還是比較類似的,直接使用->即可:
System::Object^ x = gcnew System::Object();
auto str = x->ToString();
另外,C++/CLI也有一種類似於C++的對托管對象的引用的語法:
System::Object^ x = gcnew System::Object(); System::Object% y = *x; auto str = y.ToString();
由於這種方式在C#里沒有對應的語法,用起來感覺怪怪的,也不方便於其它.net語言集成。
托管類型的定義
我們也可以自定義托管類型,在CLR中,托管類型是分為引用類型(class)和值類型(struct)的,在C++/CLI中的分別定義方式如下:
引用類型:
public ref class MyClass { };
值類型:
public value class MyClass { };
在ISO C++中類定義中加上了ref或value標記為托管類型,還算比較容易使用。
枚舉
枚舉的定義和C++11的enum class一樣,它像數字那樣可以同時應用於托管類型和非托管類型。
public enum class SomeColors { Red, Yellow, Blue };
或者更精確的表示:
public enum class SomeColors : char { Red, Yellow, Blue };
數組
C++/CLI中新增了array<T> ^的方式定義數組。
array<int> ^a = gcnew array<int>(100) { 1, 2, 3 };
或者使用它的完整版:
cli::array<int> ^a = gcnew cli::array<int> {1, 2, 3};
不定參數
對於C#中的不定參數的語法:
void foo(params string[] args)
在C++/CLI中對應的版本為:
void foo(... array<String^>^ args)
三、基本類型
數值類型
對於基本的數值類型,在C++/CLI中是可以直接映射為托管類型的數值的,可以同時應用於托管類型和非托管類型,編譯器會將其自動轉換。
| 基本類型 |
System命名空間中對應的類 |
注釋/用法 |
| bool |
System::Boolean |
bool dirty = false; |
| char |
System::SByte |
char sp = ' '; |
| signed char |
System::SByte |
signed char ch = -1; |
| unsigned char |
System::Byte |
unsigned char ch = '\0'; |
| wchar_t |
System::Char |
wchar_t wch = ch; |
| short |
System::Int16 |
short s = ch; |
| unsigned short |
System::UInt16 |
unsigned short s = 0xffff; |
| int |
System::Int32 |
int ival = s; |
| unsigned int |
System::UInt32 |
unsigned int ui = 0xffffffff; |
| long |
System::Int32 |
long lval = ival; |
| unsigned long |
System::UInt32 |
unsigned long ul = ui; |
| long long |
System::Int64 |
long long etime = ui; |
| unsigned long long |
System::UInt64 |
unsigned long long mtime = etime; |
| float |
System::Single |
float f = 3.14f; |
| double |
System::Double |
double d = 3.14159; |
| long double |
System::Double |
long double d = 3.14159L; |
字符串
字符串CLI已經內置了:System::String,但C++的常用字符串有char*、wchar_t*、std::string等好多種,編譯器提供了char*、wchar_t*到System::String的自動轉換:
System::String^ s = "hello worold"; System::String^ s2 = L"hello worold";
另外,也可以使用gcnew創建托管字符串:
System::String^ s = gcnew String("hello worold");
但是,對於System::String轉char*,系統沒有直接的語法支持。方法有很多種,我通常使用如下方式來轉換:
IntPtr ip = Marshal::StringToHGlobalAnsi(str); const char* ch = static_cast<const char*>(ip.ToPointer()); //do something with ch Marshal::FreeHGlobal(ip);
這里有個需要注意的地方是在使用完轉換出來的const char*后需要釋放掉轉換過程中的Intptr,如果沒有太多需要考慮性能的地方,大可以使用一個std::string將其拷貝走,寫成如下函數形式:
#include <string> using namespace std; using namespace System; using namespace System::Runtime::InteropServices; string cast_to_string(String^ str) { IntPtr ip = Marshal::StringToHGlobalAnsi(str); const char* ch = static_cast<const char*>(ip.ToPointer()); string stdStr = ch; Marshal::FreeHGlobal(ip); return stdStr; }
四、網絡資源
關於C++/CLI的基礎,我前面已經寫過了幾篇文章介紹過一些了,不過這些基本上都是管中窺豹,如果要詳細了解C++/CLI,MSDN無疑是最好的教程。
如果需要在MFC中使用.net控件的話,可以參考如下三篇文章:
此外,MSDN文章如何實現 C++ 互操作 上有更加詳細的文章索引。后面有空的話,我會繼續繼寫一些相關的介紹文章的。
