托管堆上创建引用类型 ref ^ gcnew nullptr

ref class MyClass {
public:
   MyClass() : i(){}
   int i;
   void Test() {
      i++;
      System::Console::WriteLine(i);
   }
};

int main() {
   MyClass ^ p_MyClass = gcnew MyClass;
   p_MyClass->Test();

   MyClass ^ p_MyClass2;
   p_MyClass2 = p_MyClass;

   p_MyClass = nullptr;
   p_MyClass2->Test();
}

^

C++/CLI中使用gcnew关键字表示在托管堆上分配内存,并且为了与以前的指针区分,用^来替换* ,就语义上来说他们的区别大致如下:

  1. gcnew返回的是一个句柄(Handle),而new返回的是实际的内存地址;
  2. gcnew创建的对象由虚拟机托管,而new创建的对象必须自己来管理和释放。

跟踪句柄、跟踪引用及内部指针

与本地C++自己维护堆不同,C++/CLI中动态分配的内存是由CLR来维护的。当不需要堆时,CLR自动将其删除回收,同时CLR还能自动地压缩内存堆以避免产生不必要的内存碎片。这种机制能够避免内存泄露和内存碎片,被称为垃圾回收,而由CLR管理的这种堆被称为CLR堆。它由操作符gcnew创建。由于垃圾回收机制会改变堆中对象的地址,因此不能在CLR堆中使用普通C++指针,因为如果指针指向的对象地址发生了变化,则指针将不再有效。为了能够安全地访问堆对象,CLR提供了跟踪句柄(类似于C++指针)和跟踪引用(类似于C++)引用。

跟踪句柄

跟踪句柄类似于本地C++指针,但能够被CLR垃圾回收器自动更新以反映被跟踪对象的新地址。同时不允许对跟踪句柄进行地址的算术运算,也不能够进行强制类型转换。注意:所有分配在堆上的对象都不能在全局范围内被创建。凡是在CLR堆上创建的对象必须被跟踪句柄引用,这些对象包括:

  1. 用gcnew操作符显示创建在堆上的对象;
  2. 所有的引用数据类型(数值类型默认分配在堆栈上)。

跟踪引用

跟踪引用类似于本地C++引用,表示某对象的别名。可以给堆栈上的值对象、CLR堆上的跟踪句柄创建跟踪引用。跟踪引用本身总是在堆栈上创建的。如果垃圾回收移动了被引用的对象,则跟踪引用将被自动更新。跟踪引用用%来定义。 int value = 10; int% trackValue = value;

内部指针

C++/CLI还提供一种用关键字interior_ptr定义的内部指针,它允许进行地址的算术操作。必要时,该指针内存储的地址会由CLR垃圾回收自动更新。注意,内部指针总是函数的局部自动变量。必须给interio_ptr指定内部指针指向的对象类型。此外还应该给指针进行初始化,如果不提供初始值,系统将其默认初始化为nullptr。内部指针在指定类型时应注意:可以包含堆栈上值类型对象的地址,也可以包含指向CLR堆上某对象句柄的地址,还可以是本地类对象或本地指针,但不能是CLR堆上整个对象的地址。也就是说,可以使用内部指针存储作为CLR堆上对象组成部分的数值类对象(如CLR数组元素)的地址,也可以存储System::String对象跟踪句柄的地址,但不能存储String对象本身的地址

array<double>^ data = {1.5, 3.5, 6.7, 4.2, 2.1};
interior_ptr<double> pstart = %data[0];

跟踪引用运算符

跟踪引用 (%) 的行为类似于普通 C++ 引用 (&) 只不过当对象分配给跟踪引用时,对象的引用计数会递增。

  1. 将对象赋值给跟踪引用会导致对象的引用计数递增。
  2. 本机引用 (&) 是取消引用 * 时的结果。 跟踪引用 (%) 是取消引用 ^ 时的结果。 只要有指向对象的 %,此对象就会一直保留在内存中。
  3. 点 (.) 成员访问运算符用于访问对象的成员。
  4. 跟踪引用对值类型和句柄(例如 String^)有效。
  5. 无法为跟踪引用分配 null 或 nullptr 值。 根据需要,可以将一个跟踪引用重新分配给另一个有效对象,没有次数限制。
  6. 跟踪引用不能用作一元获取地址运算符。

     Foo^ spFoo = ref new Foo();
     Foo% srFoo = *spFoo;
     Foo^ spFoo2 = %srFoo;
    

MFC CLI

当再MFC程序中启用CLI后,由于生成代码的CWinApp子类构造函数中,会有一个#ifdef条件编译指令,改指令会促使程序引用System::Windows::Forms命名控件,由于此时MFC程序并没有添加改命名控件,就会导致报错。可以在pch.h中添加#include <afxwinforms.h>引用afxwinforms头文件指令,解决改问题。afxwinforms头文件会自动通过#using指令加载System.Windows.Forms.dll类库。

#ifdef _MANAGED
	// If the application is built using Common Language Runtime support (/clr):
	//     1) This additional setting is needed for Restart Manager support to work properly.
	//     2) In your project, you must add a reference to System.Windows.Forms in order to build.
	System::Windows::Forms::Application::SetUnhandledExceptionMode(System::Windows::Forms::UnhandledExceptionMode::ThrowException);
#endif

//afxwinforms.h
#using <mscorlib.dll>
#using <System.dll>
#using <System.Windows.Forms.dll>
#using <System.Drawing.dll>


#using <mfcmifc80.dll>

Step 1

设置MFC项目支持CLR,需要同时设置版本,否则Visual Studio会自动选择一个.Net版本。注意设置时,需要选择所有配置。 日志文件夹

Step 2

设置完CLR支持后,Visual Studio会自动引用mscorlib.dll,但是不会引用其它命名空间,可以通过右击项目的References节点添加System, System.Data, System.XML等命名空间。引用时确认.Net版本是否正确。

日志文件夹
日志文件夹

Step 3

在代码中引用命名空间,进行编程

using namespace System;

Exception

using namespace System;
using namespace System::IO;

try
{
	String^ sPath = Path::Combine(System::AppDomain::CurrentDomain->BaseDirectory, _T("RIS\\RIS2\\logs"));
	Directory::CreateDirectory(sPath);
}
catch (System::Exception^ ex)
{
	return FALSE;
}