C++/CLI for the C# programmer

Introduction

Since .NET first appeared in Visual Studio 2002, Microsoft has supported three main languages for managed development: C#, VB and C++. Support for a fourth language, F#, was added in Visual Studio 2010.

In this article (the first of an occasional series), I'd like to take a basic look at the managed version of C++ from the perspective of a C# programmer. No previous knowledge of C++ is needed.

A brief history of managed C++

In Visual Studio 2002 and 2003, a dialect of C++ known as 'Managed Extensions for C++' was used for .NET development. The syntax for this was very ugly and it was (thankfully) replaced by a new dialect called C++/CLI from Visual Studio 2005 onwards.

You may still find Managed Extensions used in some old articles but today it's largely forgotten though still supported by means of an 'oldSyntax' compiler switch.

C++/CLI is a superset of unmanaged C++ in that it supports not only .NET extensions but the whole of Microsoft's version of unmanaged C++ as well. Like C# it has been standardized by ECMA. The CLI part refers to the Common Language Infrastructure.

What can you do in C++/CLI that you can't do in C#?

A major advantage of C++/CLI over other .NET languages is that you can mix managed and unmanaged code in the same program. This makes it ideal for scenarios where significant interoperation with unmanaged code is needed.

This ability to mix code is sometimes referred to as 'IJW' ("It just works") because it seems like a technical miracle that you can do it at all!

There are a few other things you can do in C++/CLI but not in C#. However, none of them are particularly important, in my opinion.

If I'm not bothered about interop is it still worth learning C++/CLI?

I think it's still worth knowing a bit about it so you can read such articles as there are for the language.

However, a more pressing reason has just surfaced, namely the ability to write Metro applications for Windows 8. These can be written using a combination of Windows Presentation Foundation (WPF) and a new dialect of C++ known as C++/CX (the CX part stands for Component Extensions). Although this is an unmanaged dialect which uses 'reference counting' rather than garbage collection to clean up unused objects, the syntax is remarkably similar to C++/CLI and so a knowledge of the latter should certainly help anyone who is interested in developing applications using C++/CX.

In what ways is C++/CLI similar to C#?

Although at first sight the syntax of C++/CLI looks daunting, there are in fact a number of similarities between the two languages.

Both have all the statements commonly found in C-family languages: if/else, for, do, while, switch, break, continue and goto. Both also have try/catch/finally and foreach though, in C++/CLI, the latter is split into two words: 'for each'.

Both share the fundamental types: int, short, float, double and bool. However, there are some differences in the other fundamental types which are summarized in the following table:

C# Fundamental Type

 

C++/CLI Equivalent

byte

unsigned char

sbyte

char

char

wchar_t

int

int  OR  long

long

long long  OR  _int64

The equivalence between int and long in C++/CLI is a historical anomaly which can catch you out if you're not careful.

For unsigned types, instead of prefixing them with a 'u' precede them by the 'unsigned ' keyword. So 'uint' in C# is equivalent to 'unsigned int' or 'unsigned long' in C++/CLI.

As in C#, you can also refer to the fundamental types by the .NET Framework types for which they are aliases. So 'int' is also System::Int32 and 'float' is System::Single. C++/CLI lacks a 'decimal' keyword but you can use System::Decimal instead.

Notice that C#/CLI uses the :: operator for nested namespaces and also uses the same operator when referring to static members; for example Console::WriteLine rather than C#'s Console.WriteLine.

C++/CLI has all the same kinds of type as C# though most of them have slightly different names to distinguish them from unmanaged types.

C# Keyword

 

C++/CLI Equivalent

class

ref class

interface

interface class

struct

value class

enum

enum class

delegate

delegate

event

event

C++/CLI also has ref structs and value structs. However, these are exactly the same as ref classes and value classes except that, by default, all their members are public rather than private.

The first four types in the table always end with a closing semi-colon which is optional in C#.

C++/CLI has the same access modifiers as C# except that protected internal is referred to as protected public. It also has an extra modifier, protected private, which means that a member is accessible to derived classes within the same assembly but not outside it.

Instead of being applied to individual members, access modifiers in C++/CLI are followed by a colon and then refer to all following members until a new access modifier or the end of the type's definition is reached.

Other keywords such as: static, abstract, sealed, virtual and override mean what you'd expect. 'literal' is used instead of const and 'initonly' instead of readonly.

What about strings, arrays and other reference types?

C++/CLI makes the same distinction between reference types and value types as C# does. value classes and enum classes are value types and are treated in a similar fashion to C#. However, the remainder are references types which also include strings and arrays.

In C#, when you have a line like this:

object obj = new object();

it means that a new object is created on the heap and then a reference to it is stored in the variable 'obj'. This reference is tracked by the garbage collector and may change if the heap is compacted during a garbage collection. It is not therefore the same as an ordinary 'unmanaged' pointer which always refers to the same memory address and never changes.

In C# the reference is implicit but in C++/CLI it needs to be explicit. Consequently, the same line in C++ would be:

Object^ obj = gcnew Object();

where the '^' symbol refers to a reference tracked by the garbage collector and the 'gcnew' operator refers to the creation of an object on the managed heap.

Strings are similar except that they are usually initialized using literals rather than gcnew followed by the constructor:

String ^s = "Hello";

Notice that C++/CLI has no 'string' keyword so we use System::String instead. You'll sometimes see strings prefixed by the letter 'L' which symbolizes a unicode rather than an ASCII string but this is unnecessary since all managed strings are unicode in any case.

Notice also that you can place the '^' symbol where you like. In the rest of this article, I'll always place it immediately after the variable which is consistent with the use of the pointer symbol (*) in C#.

When you create an instance of a reference type and then call a member on it, C++/CLI uses the '->' operator rather than the '.' operator. So:

int len = s.Length; // C#
int len = s->Length; // C++/CLI

Arrays in C++/CLI are created using the following syntax:

 int[] integers = {1, 2, 3, 4}; // C#
 array<int>^ integers = {1, 2, 3, 4};
// C++/CLI

 int[,] moreIntegers = { {5,6}, {7, 8} }; // C#
 array<int, 2>^ moreIntegers = { {5,6}, {7, 8} };
// C++/CLI

 string[] strings = new string[2]{"Hello", "World"}; // C#
 array<String^>^ strings = gcnew array<String^>(2){"Hello", "World"};
// C++/CLI


OK, that's enough syntax, how do I write a program in C++/CLI?

Consider the following C# console program:

using System;

enum Gender
{
    Male,
    Female
}

class Person
{
    public string Name;
    public int Age;
    public Gender Sex;

    public Person(string name, int age, Gender sex)
    {
        Name = name;
        Age = age;
        Sex = sex;
    }

    public override string ToString()
    {
        return String.Format("Name = {0, -10} Age = {1}  Sex = {2}", Name, Age, Sex.ToString()[0]);
    }
}

class Program
{
    static void Main(string[] args)
    {
        Person[] persons = new Person[4];
        persons[0] = new Person("John", 25, Gender.Male);
        persons[1] = new Person("Freda", 35, Gender.Female);
        persons[2] = new Person("Bill", 45, Gender.Male);
        persons[3] = new Person("Danielle", 55, Gender.Female);
        foreach (Person person in persons) Console.WriteLine(person);
        Console.ReadKey();
    }
}


Let's try and rewrite it using C++/CLI.

First create a new CLR Console Application project using Visual C++ and name it 'personlister'.

The following "Hello World" code template should be displayed by default:

// personlister.cpp : main project file.

#include "stdafx.h"

using namespace System;

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


Don't worry about stdafx.h which is a standard header file which needs to be included in Visual C++ projects.

Notice that C++/CLI supports the 'using namespace' directive which is the same as the 'using' directive in C#.

Notice also that C++/CLI supports global functions i.e. functions which are not members of a class and the main() method itself needs to be a global function. The main() method here is the version which returns an integer to indicate to the operating system whether an error occurred or not. Zero is returned if there was no error.

Applying the syntax changes referred to earlier in the article, leads us to the following C++/CLI version of our C# program:

// personlister.cpp : main project file.

#include "stdafx.h"

using namespace System;

enum class Gender
{
    Male,
    Female
};

ref class Person
{
public:
    String^ Name;
    int Age;
    Gender Sex;

    Person(String^ name, int age, Gender sex)
    {
        Name = name;
        Age = age;
        Sex = sex;
    }

    virtual String^ ToString() override
    {
        return String::Format("Name = {0, -10} Age = {1}  Sex = {2}", Name, Age, Sex.ToString()[0]);
    }
};

int main(array<System::String ^> ^args)
{
    array<Person^>^ persons = gcnew array<Person^>(4);
    persons[0] = gcnew Person("John", 25, Gender::Male);
    persons[1] = gcnew Person("Freda", 35, Gender::Female);
    persons[2] = gcnew Person("Bill", 45, Gender::Male);
    persons[3] = gcnew Person("Danielle", 55, Gender::Female);
    for each (Person^ person in persons) Console::WriteLine(person);
    Console::ReadKey();
    return 0;
}



The output of both programs is:

Name = John Age = 25 Sex = M
Name = Freda Age = 35 Sex = F
Name = Bill Age = 45 Sex = M
Name = Danielle Age = 55 Sex = F

Notice that enum class constants are regarded as static and so need the :: operator e.g. Gender::Male.

Notice also how to override the virtual method ToString(); the keyword virtual comes first and override is placed at the end of the line.

When developing in C++/CLI, it helps if you have a good memory because there's no IntelliSense support. However, I gather that it will be included in the next version of Visual Studio.

Conclusion

I think you'll agree that this wasn't too difficult.

In future articles, I intend to explore other aspects of C++/CLI but hope that this initial article will have whetted your appetite to learn more.


Similar Articles