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}\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;
  }
};

現在,要包裝它,我們遵循以下簡單准則:

  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\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;
}

結論

希望您在閱讀本文時學習並喜歡。它應該足以讓您在短時間內入門,其余的取決於您。確保您瀏覽了本文提供的參考文獻列表。

 

引用:

  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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM