目录:
- 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