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^); }
Dispose
=======
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.
Handles
=======
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");
Boxing
======
Old: Object* myObjArray __gc[] = { __box(1), __box(2), __box(3) };
New: array^ myObjArray = {1, 2, 3}; // Now implicit
Unboxing
========
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
{
public:
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)
{
MessageBox::Show(c->Name);
}
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;
NullPtr
=======
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:
Get
===
Find: get_{[^(]*}\(\)
Replace: \1::get()
Set
===
Find: set_{[^(]*}\({[^)]*}\)
Replace: \1::set(\2)
References