C ++ / CLI 语法

目录:

  1. What's C++/CLI  什么是C++/CLI
  2. Handles and Pointers 句柄和指针
  3. Hello World
  4. Classes and UDTs  类和用户自定义类型
  5. Arrays  数组
  6. Parameter Array  可变参数
  7. Properties  属性
  8. Wrapping Around a Native C++ Class     包装C++类
  9. Wrapping Around C Callbacks   包装C回调
  10. 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}
LastName: {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;
  }
};

现在,要包装它,我们遵循以下简单准则:

  1. 创建托管类,并使其具有指向本机类的成员变量。
  2. 在构造函数或其他合适的地方,在本机堆上构造本机类(使用“ new”)。
  3. 根据需要将参数传递给构造函数;从托管变为非托管时,您需要封送某些类型的数据。
  4. 为要从托管类公开的所有功能创建存根。
  5. 确保删除托管类的析构函数中的本机指针。

这是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。这是下面的代码的概述:

  1. 创建具有委托或到达本机回调时要调用的函数的托管类。
  2. 创建一个引用我们托管类的本机类。我们可以通过使用做到这一点gcroot_autovcclr.h头。
  3. 创建本机C回调过程,并将其作为上下文参数(在本例中为lParam)传递给本机类的指针。
  4. 现在,在本机回调内部,并具有作为本机类的上下文,我们可以获取对托管类的引用并调用所需的方法。

以下是一个简短的示例:

 

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回调)

现在,这个问题甚至更容易了,因为我们可以在托管类中拥有一个指向本机类的指针。解决方案可以描述为:

  1. 创建具有所需委托的托管类,该委托应触发本机回调。
  2. 创建将在本机类(包含回调)和上一个托管类(事件生成器)之间绑定的托管类。
  3. 创建一个包含给定回调的本机类。

为了演示的目的,我们创建了一个TickGenerator托管类,该托管类OnTick每次都会生成一个事件,然后是一个INativeHandler由托管类调用类(接口)TickGeneratorThunkMyNativeHandler类是一个简单实现的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
", 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;
}

结论

希望您在阅读本文时学习并喜欢。它应该足以让您在短时间内入门,其余的取决于您。确保您浏览了本文提供的参考文献列表。

引用:

  1. Pure C++: Hello, C++/CLI
  2. Common Language Infrastructure - Wikipedia
  3. C++/CLI - Wikipedia
  4. Pro Visual C++/CLI
  5. Applied Microsoft .NET Framework Programming
  6. ECMA 372 - C++/CLI Specification
  7. A first look at C++/CLI
  8. Managed C++ - Learn by Example - Part 1
  9. MSDN
原文地址:https://www.cnblogs.com/jshchg/p/12899793.html