目錄:
- What's C++/CLI 什么是C++/CLI
- Handles and Pointers 句柄和指針
- Hello World
- Classes and UDTs 類和用戶自定義類型
- Arrays 數組
- Parameter Array 可變參數
- Properties 屬性
- Wrapping Around a Native C++ Class 包裝C++類
- Wrapping Around C Callbacks 包裝C回調
- The Other Way Round: From Managed to C Callbacks 另一種方式:從托管調C回調
1、什么是C++ /CLI
眾所周知,C ++是一種高級語言,主要被認為是C語言的超集,它添加了許多功能,例如OOP和模板,但是什么是CLI?
CLI代表公共語言基礎結構。它在整個Web上都有詳盡的解釋,但總而言之:這是一個開放的規范,描述了可執行代碼和運行時環境,該環境允許多種高級語言在不同的計算機平台上使用,而無需為特定的架構進行重寫。[2]
現在,C ++ / CLI是用C ++編寫.NET的方法,類似於使用C#或VB.NET。
2、Handles and Pointers(句柄與指針)
您可能已經在C ++ / CLI代碼中看到標點符號“ ^”並對此感到疑惑。如您所知,在C ++中,*
表示指針,在C ++ / CLI中,^
表示句柄。現在,“ *
”指定駐留在CRT堆上的本機指針,而句柄則指定“安全指針”並駐留在托管堆上。可以將這些句柄視為引用,並且與本機指針不同,如果未正確刪除它們,它們將不會引起內存泄漏,因為GC會處理這些問題,並且它們沒有固定的內存地址,因此將在執行過程中移動。
要創建某個特定類或值類型的新引用,我們必須使用“ gcnew
”關鍵字進行分配;例如:
System::Object ^x = gcnew System::Object();
值得注意的是,nullptr
關鍵字“ ”表示空引用。除了標點符號“ ^
”外,我們還有百分比“ %
”代表跟蹤參考;我想引用ECMA-372:
N* pn = new N; // allocate on native heap N& rn = *pn; // bind ordinary reference to native object R^ hr = gcnew R; // allocate on CLI heap R% rr = *hr; // bind tracking reference to gc-lvalue
在一般情況下,加標點%
是^
因為加標點&
是*
。
2、Classes and UDTs (user defined types)
注意:在public后面跟着ref關鍵字
public ref class MyClass { private: public: MyClass() { } }
3、Hello World
在本節中,您將學習如何創建一個簡單的C ++ / CLI框架程序。首先,您需要知道如何定義正確的“ main
”。您會注意到,兩個原型(C main
和C ++ / CLI main
)都需要將字符串數組傳遞給它們。
#using <mscorlib.dll> using namespace System; int main(array<System::String ^> ^args) { System::Console::WriteLine("Hello world"); return 0; }
4、類和用戶自定義類型
Class (類)
在此示例中,我們將說明如何創建類和用戶定義的類型。要創建托管類,您要做的就是在類定義的前面加上保護修飾符,然后加上“ ref
”,從而:
public ref class MyClass { private: public: MyClass() { } }
要創建本機類,您只需按照自己的方式創建即可。現在,您可能想知道C ++ / CLI中的析構函數,如果它們仍然表現相同,答案是肯定的,則析構函數(確定性)的使用方式仍與在C ++中使用的方式相同。但是,在為您Dispose()
透明實現IDisposable
接口之后,編譯器會將析構函數調用轉換為調用。除此之外,還有由GC調用的所謂終結器(非確定性),它的定義如下:“ !MyClass()
”。在終結器中,您可能要檢查是否調用了析構函數,如果沒有,則可以調用它。
#using <mscorlib.dll> using namespace System; public ref class MyNamesSplitterClass { private: System::String ^_FName, ^_LName; public: MyNamesSplitterClass(System::String ^FullName) { int pos = FullName->IndexOf(" "); if (pos < 0) throw gcnew System::Exception("Invalid full name!"); _FName = FullName->Substring(0, pos); _LName = FullName->Substring(pos+1, FullName->Length - pos -1); } void Print() { Console::WriteLine("First name: {0}\nLastName: {1}", _FName, _LName); } }; int main(array<System::String ^> ^args) { // local copy MyNamesSplitterClass s("John Doe"); s.Print(); // managed heap MyNamesSplitterClass ^ms = gcnew MyNamesSplitterClass("Managed C++"); ms->Print(); return 0; }
Value types (值類型)
值類型是一種允許用戶創建超出原始類型的新類型的方法。所有值類型都源自System::ValueType
。值類型可以存儲在堆棧中,也可以使用equal運算符進行賦值。
public value struct MyPoint { int x, y, z, time; MyPoint(int x, int y, int z, int t) { this->x = x; this->y = y; this->z = z; this->time = t; } };
enum (枚舉)
同樣,您可以使用以下語法創建枚舉:
public enum class SomeColors { Red, Yellow, Blue};
甚至指定元素的類型,例如:
public enum class SomeColors: char { Red, Yellow, Blue};
array (數組)
數組的創建再簡單不過了,這個例子可以幫助您入門:
cli::array<int> ^a = gcnew cli::array<int> {1, 2, 3};
這將創建一個由三個整數組成的數組,而:
array<int> ^a = gcnew array<int>(100) {1, 2, 3};
將創建一個包含100個元素的數組,並初始化前三個元素。要遍歷數組,可以像使用Length
普通數組一樣使用屬性和索引,並使用foreach
:
foreach (int v in a) { Console::WriteLine("value={0}", v); }
為了創建多維數組,在這種情況下,將3D像4x5x2一樣都初始化為零:
array<int, 3> ^threed = gcnew array<int, 3>(4,5,2); Console::WriteLine(threed[0,0,0]);
字符串類的數組可以像這樣完成:
array<String ^> ^strs = gcnew array<String ^> {"Hello", "World"}
在for
循環中初始化的字符串數組:
array<String ^> ^strs = gcnew array<String ^>(5); int cnt = 0; // We use the tracking reference to access the references inside the array // since normally strings are passed by value for each (String ^%s in strs) { s = gcnew String( (cnt++).ToString() ); }
有關的更多參考cli::array
,請檢查System::Array
類,如果要添加/刪除元素,請參考ArrayList
類。
Parameter Array (可變參數)
這等效於C ++中的變量參數。可變參數必須是函數中的最后一個參數。通過放置“ ...”,然后是所需類型的數組來定義它:
using namespace System; void avg(String ^msg, ... array<int> ^values) { int tot = 0; for each (int v in values) tot += v; Console::WriteLine("{0} {1}", msg, tot / values->Length); } int main(array<String ^> ^args) { avg("The avg is:", 1,2,3,4,5); return 0; }
Properties(屬性)
public ref class Xyz { private: int _x, _y; String ^_name; public: property int X { int get() { return _x; } void set(int x) { _x = x; } } property String ^Name { void set(String ^N) { _name = N; } String ^get() { return _name; } } };
Wrapping Around a Native C++ Class(包裝本機C ++類)
在本節中,我們將說明如何為本地C ++類創建C ++ / CLI包裝器。考慮以下本地類:
// native class class Student { private: char *_fullname; double _gpa; public: Student(char *name, double gpa) { _fullname = new char [ strlen(name+1) ]; strcpy(_fullname, name); _gpa = gpa; } ~Student() { delete [] _fullname; } double getGpa() { return _gpa; } char *getName() { return _fullname; } };
現在,要包裝它,我們遵循以下簡單准則:
- 創建托管類,並使其具有指向本機類的成員變量。
- 在構造函數或其他合適的地方,在本機堆上構造本機類(使用“
new
”)。 - 根據需要將參數傳遞給構造函數;從托管變為非托管時,您需要封送某些類型的數據。
- 為要從托管類公開的所有功能創建存根。
- 確保刪除托管類的析構函數中的本機指針。
這是Student
該類的托管包裝器:
// Managed class ref class StudentWrapper { private: Student *_stu; public: StudentWrapper(String ^fullname, double gpa) { _stu = new Student((char *) System::Runtime::InteropServices::Marshal::StringToHGlobalAnsi( fullname).ToPointer(), gpa); } ~StudentWrapper() { delete _stu; _stu = 0; } property String ^Name { String ^get() { return gcnew String(_stu->getName()); } } property double Gpa { double get() { return _stu->getGpa(); } } };
Wrapping Around C Callbacks(包裝C回調)
在本節中,我們將演示如何讓C回調調用.NET。出於說明目的,我們將包裝EnumWindows()
API。這是下面的代碼的概述:
- 創建具有委托或到達本機回調時要調用的函數的托管類。
- 創建一個引用我們托管類的本機類。我們可以通過使用做到這一點
gcroot_auto
從vcclr.h頭。 - 創建本機C回調過程,並將其作為上下文參數(在本例中為
lParam
)傳遞給本機類的指針。 - 現在,在本機回調內部,並具有作為本機類的上下文,我們可以獲取對托管類的引用並調用所需的方法。
以下是一個簡短的示例:
using namespace System; #include <vcclr.h> // Managed class with the desired delegate public ref class MyClass { public: delegate bool delOnEnum(int h); event delOnEnum ^OnEnum; bool handler(int h) { System::Console::WriteLine("Found a new window {0}", h); return true; } MyClass() { OnEnum = gcnew delOnEnum(this, &MyClass::handler); } };
創建本機類是為了保留對托管類的引用並托管本機回調過程:
class EnumWindowsProcThunk { private: // hold reference to the managed class msclr::auto_gcroot<MyClass^> m_clr; public: // the native callback static BOOL CALLBACK fwd( HWND hwnd, LPARAM lParam) { // cast the lParam into the Thunk (native) class, // then get is managed class reference, // finally call the managed delegate return static_cast<EnumWindowsProcThunk *>( (void *)lParam)->m_clr->OnEnum((int)hwnd) ? TRUE : FALSE; } // Constructor of native class that takes a reference to the managed class EnumWindowsProcThunk(MyClass ^clr) { m_clr = clr; } };
放在一起:
int main(array<System::String ^> ^args) { // our native class MyClass ^mc = gcnew MyClass(); // create a thunk and link it to the managed class EnumWindowsProcThunk t(mc); // Call Window's EnumWindows() C API with the pointer // to the callback and our thunk as context parameter ::EnumWindows(&EnumWindowsProcThunk::fwd, (LPARAM)&t); return 0; }
The Other Way Round: From Managed to C Callbacks(相反:從托管回調到C回調)
現在,這個問題甚至更容易了,因為我們可以在托管類中擁有一個指向本機類的指針。解決方案可以描述為:
- 創建具有所需委托的托管類,該委托應觸發本機回調。
- 創建將在本機類(包含回調)和上一個托管類(事件生成器)之間綁定的托管類。
- 創建一個包含給定回調的本機類。
為了演示的目的,我們創建了一個TickGenerator
托管類,該托管類OnTick
每次都會生成一個事件,然后是一個INativeHandler
由托管類調用的類(接口)TickGeneratorThunk
。一MyNativeHandler
類是一個簡單實現的INativeHandler
向您展示如何設置自己的處理程序。
滴答生成器委托:
public delegate void delOnTick(int tickCount);
托管的滴答生成器類:
ref class TickGenerator { private: System::Threading::Thread ^_tickThread; int _tickCounts; int _tickFrequency; bool _bStop; void ThreadProc() { while (!_bStop) { _tickCounts++; OnTick(_tickCounts); System::Threading::Thread::Sleep(_tickFrequency); } } public: event delOnTick ^OnTick; TickGenerator() { _tickThread = nullptr; } void Start(int tickFrequency) { // already started if (_tickThread != nullptr) return; // p.s: no need to check if the event was set, // an unset event does nothing when raised! _tickCounts = 0; _bStop = false; _tickFrequency = tickFrequency; System::Threading::ThreadStart ^ts = gcnew System::Threading::ThreadStart(this, &TickGenerator::ThreadProc); _tickThread = gcnew System::Threading::Thread(ts); _tickThread->Start(); } ~TickGenerator() { Stop(); } void Stop() { // not started? if (_tickThread == nullptr) return; _bStop = true; _tickThread->Join(); _tickThread = nullptr; } };
現在,非托管的滴答處理程序接口:
#pragma unmanaged // Create a simple native interface for handling ticks // Native classes implement this class to add custom OnTick handlers class INativeOnTickHandler { public: virtual void OnTick(int tickCount) = 0; };
一個簡單的實現:
class MyNativeHandler: public INativeOnTickHandler { public: virtual void OnTick(int tickCount) { printf("MyNativeHandler: called with %d\n", tickCount); } };
現在,回到托管創建重排,在托管和非托管之間建立橋梁:
#pragma managed // Create the managed thunk for binding between the native // tick handler and the tick generator managed class ref class TickGeneratorThunk { private: INativeOnTickHandler *_handler; public: TickGeneratorThunk(INativeOnTickHandler *handler) { _handler = handler; } void OnTick(int tickCount) { _handler->OnTick(tickCount); } };
放在一起:
int main(array<System::String ^> ^args) { // Initiate the native handler MyNativeHandler NativeHandler; // Create the tick generator class TickGenerator ^tg = gcnew TickGenerator(); // Create the thunk and bind it with our native handler TickGeneratorThunk ^thunk = gcnew TickGeneratorThunk(&NativeHandler); // Bind the ontick event with the thunk's onclick event tg->OnTick += gcnew delOnTick(thunk, &TickGeneratorThunk::OnTick); // Start the tick generator tg->Start(1000); // Wait for user input Console::ReadLine(); // Stop the generator tg->Stop(); return 0; }
結論
希望您在閱讀本文時學習並喜歡。它應該足以讓您在短時間內入門,其余的取決於您。確保您瀏覽了本文提供的參考文獻列表。
引用:
- Pure C++: Hello, C++/CLI
- Common Language Infrastructure - Wikipedia
- C++/CLI - Wikipedia
- Pro Visual C++/CLI
- Applied Microsoft .NET Framework Programming
- ECMA 372 - C++/CLI Specification
- A first look at C++/CLI
- Managed C++ - Learn by Example - Part 1
- MSDN