Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++/CLI

Quick C++/CLI - Learn C++/CLI in less than 10 minutes

4.87/5 (47 votes)
26 Jun 2007CPOL7 min read 4  
Learn C++/CLI in less than 10 minutes.

Introduction

Welcome to my second article on .NET programming, specifically about C++/CLI. Shortly after I wrote the previous article, the C++/CLI was becoming more in use, thus rendering MC++ obsolete. If you've read the previous MC++ article, then reading this one is easier and will only update your knowledge.

The goal of this article is to present you with most of the information needed for you to start using C++/CLI in a short amount of time (given that you come from a C++ and .NET background). The information will be presented in a "how-to" manner, and you will be provided with an example for every topic introduced.

Before you proceed, it is recommended that you warm-up your neurons and take a look at these two articles [1] & [8].

You may build each example through the following command line (or what is equivalent to it):

"C:\Program Files\Microsoft Visual Studio 8\Common7\Tools\vsvars32.bat"

Setup the environment (do this once), then compile with the CL tool as:

cl your_file.cpp /clr

Table of contents

  1. What's 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
  9. Wrapping Around C Callbacks
  10. The Other Way Round: From Managed to C Callbacks

So, What's C++/CLI?

C++, as you already know, is a high-level language that is mainly considered a superset of the C language, which adds many features such as OOP and templates, but what's CLI?

CLI stands for Common Language Infrastructure. It is explained thoroughly all over the web, but in short: It is an open specification that describes the executable code and runtime environment that allows multiple high-level languages to be used on different computer platforms without being rewritten for specific architectures. [2]

Now, C++/CLI is the means to program .NET in C++, similarly like C# or VB.NET are used.

Handles and Pointers

You may have seen the punctuator "^" symbol in C++/CLI code and wondered about it. As you know, in C++, we have the "*" to denote a pointer, and in C++/CLI, we have the ^ to denote a handle. Now, a "*" designates a native pointer which resides on the CRT heap, whereas the handles designate "safe pointers" and reside on the managed heap. The handles can be thought of as references, and unlike native pointers, they won't cause memory leaks if they are not properly deleted, since the GC will take care of that, and they don't have a fixed memory address and thus will be moved around during execution.

To create a new reference of a certain class or value type, we have to allocate it with the "gcnew" keyword; for example:

MC++
System::Object ^x = gcnew System::Object();

It is worthwhile noting that the "nullptr" keyword designates a null reference. In addition to the punctuator "^" symbol, we have the percent "%" which stands for a tracking reference; I would like to quote the ECMA-372:

MC++
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

In general, the punctuator % is to ^ as the punctuator & is to *.

Let us get started: Hello World

In this section, you will learn how to create a simple skeleton C++/CLI program. To start, you need to know how to define a correct "main". As you will notice, both prototypes (C's main and C++/CLI's main) require an array of strings to be passed to them.

MC++
#using <mscorlib.dll>

using namespace System;

int main(array<System::String ^> ^args)
{
  System::Console::WriteLine("Hello world");
  return 0;
}

Classes and UDTs

Classes

In this example, we will illustrate how to create classes and user defined types. To create a managed class, all you have to do is prefix the class definition with a protection modifier followed by "ref", thus:

MC++
public ref class MyClass
{
private:
public:
  MyClass()
  {

  }
}

And to create a native class, you simply create it the way you know. Now, you may wonder about destructors in C++/CLI and if they still behave the same, and the answer is yes, destructors (which are deterministic) are still used the same way they are used in C++; however, the compiler will translate the destructor call into a Dispose() call after transparently implementing the IDisposable interface for you. In addition to that, there is the so called finalizer (non-deterministic) that is called by the GC, and it is defined like this: "!MyClass()". In the finalizer, you may want to check if the destructor was called, and if not, you may call it.

MC++
#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

Value types are a means to allow the user to create new types beyond the primitive types; all value types derive from System::ValueType. The value types can be stored on the stack, and can be assigned using the equal operator.

MC++
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;
  }
};

Enums

Similarly, you can create enums with the following syntax:

MC++
public enum class SomeColors { Red, Yellow, Blue};

Or even specify the type of the elements, as:

MC++
public enum class SomeColors: char { Red, Yellow, Blue};

Arrays

Array creation cannot be simpler, this example will get you started:

MC++
cli::array<int> ^a = gcnew cli::array<int> {1, 2, 3};

which will create an array of three integers, whereas:

MC++
array<int> ^a = gcnew array<int>(100) {1, 2, 3};

will create an array of 100 elements, with the first three elements initialized. To walk in an array, you can use the Length attribute and the index as if it were a normal array, and you use the foreach:

MC++
for each (int v in a)
{
  Console::WriteLine("value={0}", v);
}

To create a multi-dimensional array, 3D in this case, like a 4x5x2, all initialized to zero:

MC++
array<int, 3> ^threed = gcnew array<int, 3>(4,5,2);

Console::WriteLine(threed[0,0,0]);

An array of classes of strings can be done like this:

MC++
array<String ^> ^strs = gcnew array<String ^> {"Hello", "World"}

An array of strings initialized in a for loop [3]:

MC++
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() );
}

For more reference about cli::array, check the System::Array class, and if you want to add/remove elements, check the ArrayList class.

Parameter Array

This is equivalent to the variable parameters in C++. The variable parameter must be one in a function and the last parameter. Define it by putting "...", followed by the array of the desired type:

MC++
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

MC++
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

In this section, we will illustrate how to create a C++/CLI wrapper for a native C++ class. Consider the following native class:

MC++
// 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;
  }
};

Now, to wrap it, we follow this simple guideline:

  1. Create the managed class, and let it have a member variable pointing to the native class.
  2. In the constructor or somewhere else you find appropriate, construct the native class on the native heap (using "new").
  3. Pass the arguments to the constructor as needed; some types you need to marshal as you pass from managed to unmanaged.
  4. Create stubs for all the functions you want to expose from your managed class.
  5. Make sure you delete the native pointer in the destructor of the managed class.

Here's our managed wrapper for the Student class:

MC++
// 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

In this section, we will demonstrate how to have C callbacks call .NET. For illustration purposes, we will wrap the EnumWindows() API. This is the outline of the code below:

  1. Create a managed class with delegates or a function to be called when the native callback is reached.
  2. Create a native class that has a reference to our managed class. We can accomplish this by using the gcroot_auto from the vcclr.h header.
  3. Create the native C callback procedure, and pass it as a context parameter (in this case, the lParam) a pointer to the native class.
  4. Now, inside the native callback, and having the context which is the native class, we can get the reference to the managed class and call the desired method.

A short example is provided below:

MC++
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);
  }
};

The native class created for the purpose of holding a reference to our managed class and for hosting the native callback procedure:

MC++
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;
  }
};

Putting it all together:

MC++
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

Now, this problem is even easier, since we can have a pointer to a native class within the managed class. The solution can be described as:

  1. Create a managed class with the desired delegates that should trigger the native callback.
  2. Create a managed class that will bind between the native class (containing the callback) and the previous managed class (the event generator).
  3. Create a native class containing the given callback.

For demonstration purpose, we created a "TickGenerator" managed class which will generate an OnTick event every while, then an INativeHandler class (interface) that should be called by the managed class TickGeneratorThunk. A MyNativeHandler class is a simple implementation of the INativeHandler to show you how to set your own handler.

The tick generator delegate:

MC++
public delegate void delOnTick(int tickCount);

The managed tick generator class:

MC++
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;
  }
};

Now, the unmanaged tick handler interface:

MC++
#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;
};
A simple implementation:
MC++
class MyNativeHandler: public INativeOnTickHandler
{
public:
  virtual void OnTick(int tickCount)
  {
    printf("MyNativeHandler: called with %d\n", tickCount);
  }
};

Now, back to managed to create the thunk, bridging between managed and unmanaged:

MC++
#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);
  }
};

Putting it all together:

MC++
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;
}

Conclusion

I hope you learned and enjoyed while reading this article. It should be enough to get you started in little time, the rest is up to you. Make sure you skim through the list of references provided in this article.

Reference

  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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)