Much has been written about the languages for Microsoft’s .NET platform, from C#, the new language created specifically for .NET, to the changes made to Visual Basic for .NET. Less has been written about Microsoft’s managed extensions for Visual C++, managed C++ for short. These extensions to the C++ language enable it to implement types that conform to the .NET common type system, and they're executable by the Common Language Runtime, or CLR.
Managed extensions for C++ are of strategic value because they enable the creation of thin, high-performance wrappers to encapsulate existing C++ classes and enable them to be used within the .NET environment. In this article, I’ll introduce some of the key features of managed C++. I’ll also discuss how using these features limits what C++ would otherwise permit.
Managed extensions are enabled in the Microsoft C++ compiler by specifying the /CLR switch. This switch also compiles the C++ source to managed code, or MSIL, Microsoft Intermediate Language. The MSIL instruction set is powerful enough to handle nearly any existing C++ program. I say “nearly” because there are situations where C++ code must be compiled to native, unmanaged code. These situations include functions that contain an __asm block or that call the setjmp function. This isn’t a problem, however, because managed and unmanaged code can be mixed within the same module. ManagedCode.cpp, shown in Listing A, uses templates and I/O streams, quintessential C++ features.
You can verify that specifying /CLR indeed produces managed code by compiling ManagedCode.cpp and viewing ManagedCode.exe with ildasm, the .NET MSIL disassembler. If you do, you’ll also notice the plethora of C++ types generated by the STL and I/O streams templates. Note that simply compiling to managed code in no way changes the semantics of the code, nor do the types, such as IntVector, automatically become .NET types.
ManagedIntVector.cpp, shown in Listing B, wraps IntVector in a managed type. The first difference from standard C++ is the preprocessor directive #using <mscorlib.dll> . This imports the metadata for the core .NET types from the mscorlib assembly, which acts as a kind of binary header file. Standard using directives make the names in the C++ standard library std namespace and the .NET System namespace available without qualification.
The next managed C++ differences occur in class declaration of ManagedIntVector with the public __gc keywords. The __gc keyword designates ManagedIntVector as a .NET reference type. As such, instances of ManagedIntVector are allocated on the CLR managed heap and are automatically deallocated during garbage collection. The public keyword specifies that access to the ManagedIntVector type is unrestricted. This level of access control is not offered in standard C++.
ManagedIntVector has the same characteristics as any other .NET reference type. It must derive, either directly or indirectly from System::Object, and it may inherit only from a single base class. Though it may implement multiple interfaces, multiple inheritance is not permitted. A managed C++ class implicitly inherits from System::Object if it does not specify a base class. ManagedIntVector inherits implicitly from System::Object. A managed C++ class may not inherit from an unmanaged class, nor may an unmanaged class be derived from it. Further, a managed C++ class may not declare a user-defined copy constructor, new operator, or delete operator.
ManagedIntVector encapsulates an instance of IntVector, which is allocated in the constructor and deallocated in the destructor in standard C++ fashion. A __gc class cannot contain an instance of an unmanaged type directly as a data member. Such a data member must instead be declared as a pointer to an unmanaged type.
Managed C++ provides direct support for properties through the __property keyword. Accessor methods to read and write the property have the form get_Prop() and set_Prop( type value ), where Prop is the name of the property. A property can be read-only or write-only by omitting the set or get accessor, respectively. ManagedIntVector defines the read-only property Count, which returns the number of elements in the encapsulated IntVector.
ManagedIntVector overrides the System::Object ToString method and returns the contents of its encapsulated IntVector as a string. The type String refers to the .NET reference type System::String. Because instances of a .NET reference type are always allocated on the managed heap, the local variable s is declared as a pointer to a String. Likewise, the return type of ToString is a pointer to String. An S preceding a string literal designates the literal as a .NET System::String and causes the string to be allocated on the managed heap.
McppClient.cpp, shown in Listing C, demonstrates the use of ManagedIntVector from a managed C++ client. Notice that the instance of ManagedIntVector is allocated using the new operator but isn’t deallocated using the delete operator. Garbage collection handles the deallocation automatically. CsClient.cpp, shown in Listing D, demonstrates the use of ManagedIntVector from a C# client.
Even though ManagedIntVector compiles to managed code, it is not type-safe code in the eyes of the CLR. Type-safe code accesses types only in well-defined, verifiable ways. For example, type-safe code ensures that memory occupied by an object is accessed only at locations that correspond to methods or fields. At runtime, as the CLR compiles MSIL to native code, it attempts to determine whether the code is type-safe. Some compilers simply cannot generate verifiably type-safe code. Because of the flexibility that C++ provides through pointer manipulation, the MSIL produced by the Microsoft C++ compiler is not verifiably type-safe. While this doesn’t mean that there is anything improper about the code, it does bump up against the default security policy of the CLR that allows only code that is not type-safe to execute if it originates from the local computer.
Managed extensions contain other useful features, such as the addition of a finally clause to the try/catch statement through the __finally keyword and the ability to define .NET value types through the __value keyword. The complete description of managed extensions can be found in the"Managed Extensions for C++ Specification." The "Managed Extensions for C++ Migration Guide" presents strategies for wrapping existing C++ classes and discusses wrapping the C++ implementation class of a COM component as an alternative to using the component through the COM interop facilities provided by .NET. These documents are available both online within the MSDN documentation for Visual C++ and separately as Word documents.
Bringing it together
As you make C++ a part of your .NET development effort, you should understand what it means for your code to be managed code. It can be limiting in some ways, and it's not perfect, but it does provide a means for integrating C++ to .NET.