Converting Managed C++ to C++/CLI

What is Managed C++?

Managed C++, also known as Managed Extensions for C++, was a set of syntactic extensions and keywords released with Visual Studio .NET in 2002 which allows you to target the .NET Framework in C++. In other words, with the release of Managed C++, it became possible to write .NET programs in C++. This is particularly interesting because unlike pure managed languages like C# and Visual Basic .NET, Managed C++ is also directly compatible with native C++ code, without having to use PInvoke or COM. In fact you can write code that mixes both unmanaged and managed C++ in the same assembly, and even within the same function! This kind of “mixed mode” interop is what makes Managed C++ so interesting.

So how does it work? Basically in addition to the standard C++ built-in types, references, and pointers, you can now declare what is known as a garbage-collected pointer to point to a managed .NET object that has been allocated with a managed new operation. As long as you keep your regular unmanaged classes and pointers separate from your managed, garbage-collected ones, you’re good to go. Here’s an example:

__gc class G { public: int i; }
G __gc* pG = new G;
pG->i = 5;
int i = pG->i;

The __gc keyword is by far the most ubiquitous in Managed C++, but there are a number of other ones:

  • __abstract: for declaring a class as abstract
  • __box: for “boxing” value types
  • __delegate: for declaring a .NET delegate
  • __event: for declaring a .NET event
  • __identifier: for using a C++ keyword as an identifier
  • __interface: for declaring a .NET interface
  • __nogc: for explicitly declaring an unmanaged type
  • __pin: for declaring a “pinning pointer”
  • __property: for declaring a .NET property
  • __sealed: protects a class or method from derivation
  • __try_cast: for a dynamic cast that throws an exception on failure
  • __typeof: for determining the .NET type of an object
  • __value: for declaring a value class

Used correctly, these keywords are all you need to write code in Managed C++.

What is C++/CLI?

Sometime after introducing Managed C++ to the world via Visual Studio .NET, Microsoft started working on a newer revision of the language specification in an attempt to supersede Managed C++. Why did they decide to revise the language? For one thing, although Microsoft chose the common extension prefix “__” for its managed keywords, developers generally thought that the syntax was ugly. Really ugly. Additionally, the code was often difficult to read and understand in ambiguous situations. So for Visual Studio 2005, Microsoft created a new language specification called C++/CLI which completely revised the language both for the aesthetics of prettier code and the clarity of unambiguous syntax.

How did the syntax change?

Handles: Where before you had to refer to managed classes using gc pointers (__gc*), now when you use a managed type you use the syntax ClassName^, to clearly differentiate managed and unmanaged objects.

Contextal and Spaced Keywords: Instead of introducing a slew of new .NET keywords, and marking them as off limits for identifiers, Microsoft chose only three new keywords (gcnew, generic, and nullptr), and the rest are either spaced keywords (for each, enum class, interface class, ref class, value class) or contextual keywords which only appear where an identifier would never appear. This was a great decision because it means that the syntax is prettier but you break very few existing programs, unless they call their variables nullptr and gcnew. Contextual keywords include abstract, finally, in, override, sealed, and where.

Tracking Reference to No Object: The keyword nullptr was introduced to represent the managed equivalent of null. Because of this change, the integral value zero no can no longer be used as a tracking reference; nullptr must be used instead.

Garbage-Collected New: To differentiate the standard C++ new keyword from a managed allocation which will automatically be garbage collected, the keyword gcnew was introduced. Anytime you allocate a new .NET object you must use this gcnew keyword.

Generics: Just as C# 2.0 added support for generics, so too did C++/CLI add support for .NET generics in C++ using the generic keyword.

Dispose and Finalize: The changes to destructor semantics in C++/CLI are complicated; destructors now go to Dispose instead of Finalize, but you can still declare an explicit finalizer if necessary. See the MSDN translation guide for more details.

There are other changes that are too numerous to list here; consult my References for more information.

Quick and Dirty Translation Guide

Managed Class Declaration
Old: public __gc class MyClass
New: public ref class MyClass

Property Declaration
Old: __property String *get_Name();
New: property String^ Name { String^ get(); }

Old: __property System::String __gc* get_Description();
Old: __property void set_Description(System::String __gc* value);
New: property String^ Description { String^ get(); void set(String^); }

Old: void Dispose();
New: ~MyClass();

Note: The Dispose/Finalize pattern has some tricky changes. See Translation Guide: Moving Your Programs from Managed Extensions for C++ to C++/CLI.

Old: String* ToString();
New: String^ ToString();

Pin Ptr
Old: std::list* __pin *pinner = &m_stringList;
New: pin_ptr<std::list > pinner = m_stringList;

Property Definition
Old: String *MyClass::get_Name() { }
New: String^ MyClass::Name::get() { }

Old: System::Double MyClass::get_Values() __gc[] { }
New: Array^ MyClass::Values::get() { }

Old: double MyReader::EndRead(IAsyncResult* asyncResult) __gc [,] { }
New: Array MyReader::EndRead(IAsyncResult^ asyncResult) { }

Array Declaration
Old: Object* args[] = { GetType()->Name, this->VirtualName };
New: array^ args = { GetType()->Name, this->VirtualName };

Managed Memory Allocation
Old: throw new ObjectDisposedException(S"MyClass");
New: throw gcnew ObjectDisposedException("MyClass");

Old: Object* myObjArray __gc[] = { __box(1), __box(2), __box(3) };
New: array^ myObjArray = {1, 2, 3}; // Now implicit

Old: System::Object* o = __box(123);
Old: int a = *(dynamic_cast(o));
New: Object^ o = z; // implicit boxing
New: int y = *reinterpret_cast(o); // unboxing

Dereferencing a GC Pointer / Handle
Old: this->Name
New: this->Name // no change

Public Inheritance
Old: public __gc class SubClass : public MyClass {
New: public ref class SubClass : MyClass { // Public inheritance by default

Implementing ICollection (New Syntax Only)
ref class MyCollection : ICollection
virtual IEnumerator^ GetEnumerator() = IEnumerable::GetEnumerator { return nullptr; }
virtual property int Count { int get() = ICollection::Count::get { return 0; } }
virtual property Object^ SyncRoot { Object^ get() = ICollection::SyncRoot::get { return nullptr; } }
virtual property bool IsSynchronized { bool get() = ICollection::IsSynchronized::get { return false; } }
virtual void CopyTo(Array^, int) = ICollection::CopyTo { return; }

For Each (New Syntax Only)
array^ classes = { gcnew MyClass() };
for each (MyClass^ c in classes)

String Literals
Old: S"Managed String"
New: "Managed String"

Try-Cast -> Safe-Cast
TypeOf -> TypeID
Old: m_bigFloat = *__try_cast(info->GetValue(S"_bigFloat",__typeof(f64)));
New: m_bigFloat = safe_cast(info->GetValue("_bigFloat", f64::typeid));

CLI Types
Old: __int64
New: Int64

Delegates / Events
Old: public __delegate void MyEventHandler(Object *sender, MyEventArgs *e);
Old: __gc class Task {
__event void add(MyEventHandler *eh);
__event void remove(MyEventHandler *eh);

New: public delegate void MyEventHandler(Object^ sender, MyEventArgs^ e);
New: ref class Task {
event MyEventHandler^ MyEvent { void add(MyEventHandler^ eh); void remove(MyEventHandler^ eh); }

Private Public
Old: private public:
New: internal:

Operator Overloading
Old: static bool op_Equality(DataFrequency d1, DataFrequency d2);
New: static bool operator ==(DataFrequency d1, DataFrequency d2);

Conversion Operators
Old: static double op_Implicit(int i);
Old: static float op_Explicit(int i);
New: static implicit operator double(int i);
New: static explicit operator float(int i);

CLI Enums
Old: __value enum Result { Pass, Fail };
Old: Result r = Pass;
New: enum class Result { Pass, Fail };
New: Result r = Result::Pass;

Old: Object* obj = 0;
New: Object^ obj = nullptr;

Visual Studio Search & Replace Strings

Use these in order, using Visual Studio 2005 with Regular Expressions, per .h file. Voila.

String RegExp
Find: String __gc\*
Replace: String^

Property RegExp
Find: __property
Replace: property

Get Property RegExp
Find: property {[^ ]*} get_{[^\(]*}\(\);
Replace: property \1 \2 { \1 get(); }

Set Property RegExp
Find: property void set_{[^\(]*}\({[^ ]*} value\);
Replace: property \1 { void set(\2 value); }

Get Array
Find: property {[^ ]*} get_{[^\(]*}\(\) __gc\[\];
Replace: property array^ \2 { array^ get(); }

Set Array
Find: property void set_{[^\(]*}\({[^ ]*} value __gc\[\]\);
Replace: property \1 { void set(array^ value); }

Combine RegExp
Find: property {[^ ]*} {[^ ]*} \{ [^ ]* get\(\); \}\n[^p]*property \2 \{ void set\(\1 value\); \}
Replace: property \1 \2 { \1 get(); void set(\1 value); }

Use the following in order per .cpp file:

Find: get_{[^(]*}\(\)
Replace: \1::get()

Find: set_{[^(]*}\({[^)]*}\)
Replace: \1::set(\2)


MSDN Translation Guide

Herb Sutter’s Blog


Published by


Seattleite. Climber. Snowboarder. Traveler. Party rocker. Technologist. Spanish enthusiast. Fun-seeker.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s