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.