C++ Bindings for the Common Language Runtime

Submit New Article

Last Modified On :   March 11, 2008 6:26 PM PDT
Rate
 


by 3 Leaf Solutions, LLC


Introduction

To judge by media coverage, one might suppose that C++ is a minor language compared to C#*, Java*, and Visual Basic*. The classified section of any newspaper tells a different story: experience with C++ is consistently the most common language qualification desired [1]. A mature language (2004 marks its 20th birthday), C++ is the sharpest programming scalpel short of assembly language and is the lingua franca of performance-oriented programming.

Although C++ has a reputation as being notoriously complex, this reputation largely emerges from a devotion to maintaining compatibility with existing code, and most programmers can avoid (or at least delay confronting) the vast majority of these minutiae. In practice, developers at every level, from neophyte to expert, can find the capabilities, tutorials, and reference materials appropriate to their needs, while employers can trust that development done in C++ gives them access to the broadest community of talent available, as well as to the full capabilities of the target system.


C++/CLI Will Bring the Full Power of C++ to .NET*

The use of C++ with the .NET Framework has suffered in terms of productivity. The .NET Framework relies on a virtual machine that is strongly typed and provides automatic memory management. These characteristics are in contrast to C++'s programming model, which allows programmers to cast away type safety and places upon the programmer the burden of allocating and freeing memory.

In the initial releases of the .NET Framework, Microsoft has provided "Managed C++," a language that uses standards-compliant but awkward keywords to bridge the two programming models. The problem with Managed C++ is that use of the .NET Framework in a program is not usually done narrowly; .NET types and facilities are generally used throughout the program, and the proliferation of double-underscored keywords and type qualifications make Managed C++ programs difficult to read and more prone to typing errors.

Recognizing this difficulty and the great benefits of a vendor-neutral low-level language that is compliant with the Common Language Infrastructure (CLI)*, Microsoft, working with Edison Design Group and Dinkumware, has submitted to ECMA a specification for language extensions that bind C++ to the CLI. Known as "C++/CLI," the new language will allow programmers to access managed types and .NET facilities with C++'s characterstic and appealing terseness.

C++/CLI is at an early stage of development. Although discussed at the 2003 Los Angeles Professional Developers Conference, Microsoft has not yet made a C++/CLI compiler broadly available, and only a base draft of the specification* is available. The stated goals of C++/CLI are to give "a natural feel for C++ programmers" while accomplishing the following:

  • provide first-class support for all current and upcoming CLI features
  • extend first-class support for all C++ features (notably, deterministic destruction and templates) to all CLI types
  • use pure extensions wherever possible (and thus preserve the meaning of existing programs)

 

Although details will certainly change, the broad strokes of C++/CLI are set. The most dramatic change is the adoption of the CLI's unified type system to C++ and the addition of new keywords and operators to support tracking objects whose location and lifespan are under the control of the Common Language Runtime (CLR)*.


Memory Handling in C++/CLI

In C++/CLI, objects allocated on the native heap use the traditional facilities of new and delete, and pointers and references. Managed objects, however, are allocated with gcnew and accessed with handles and tracking references. Handles use the punctuator ^ as opposed to C++’s *, and tracking references use % where ordinary references use &:

NativeType* pointerToNative = new NativeType;
NativeType& referenceToNative = *pointerToNative;

ManagedType^ handleToManaged = gcnew ManagedType;
ManagedType% referenceToManaged = *handleToManaged;

 

Notice that referencedToManaged dereferences the handle handleToManaged with a unary * on the right-hand side of the assignment, not the unary ^ that one might expect. Other than that, the symmetry between handles and pointers and between the two types of references is very consistent.

Furthermore, when delete is called on a handle, the managed object’s destructor is called immediately, without waiting for garbage collection. C++/CLI supports deterministic finalization. Memory is only one of the limited resources on a computer – file handles, database connecti ons, and graphics handles are some others – and the task of releasing such resources in languages such as C# and Visual Basic .NET is just as error-prone as deallocating memory in a non-managed language.

In these languages, one option is to define a finalizer method. The .NET garbage collector guarantees a call to this method on objects before the runtime releases their memory. The garbage collector is non-deterministic, however, and the only guarantee as to when your objects’ finalizers will run is sometime before your program exits. There are many times when it is desirable to have finer-grained control over releasing resources, which is the province of the IDisposable interface, its associated patterns, and C#’s using keyword.

C++/CLI's language-level support for deterministic finalization guarantees that, if a native or ref class has defined a destructor method (which is distinct from a finalizer method), that method is called at the following times:

  • if it is a stack-based object, when it goes out of scope
  • if it is a statically-stored object, when the program terminates
  • if it is allocated on the native heap, when delete is called on a pointer to the object
  • if it is allocated with gcnew, when delete is called on a handle to the object
  • if it is a member of another object, when the enclosing object is destroyed

 

This is an example of the willingness in C++ to introduce complexity for the sake of flexibility. Since it would be anathema for C++ to forbid access to a low-level facility such as finalizers, a C++/CLI class can define both a destructor ( ~ClassName() ) and a finalizer ( !ClassName() ) that runs before memory is recovered, but the finalizer is not run if the destructor has run.

Handles can only point to whole, managed objects. If you wish to point “into” a managed object, you must use either an interior pointer or a pinning pointer. An interior pointer maintains its reference even if the managed object is moved in memory by the garbage collector. A pinning pointer is a type of interior pointer that prevents the enclosed object from moving and can be converted to a native pointer:

value struct V {
int data;
void f();
};

void V::f() {
int* pi;
interior_ptr<V> ipv = this;
pi = &(ipv->data); // error
pin_ptr<V> ppv = this;
pi = &(ppv->data); // OK
V* pv;
pv = ipv; // error
pv = ppv; // OK
}

 

In this example, the native pointers pi and pv can point into the managed heap by converting the pinned pointer ppv, but not from interior pointer ipv, since only the pinned pointer can guarantee the integrity of the destination.

The final notable difference between handles and pointers is that they cannot be cast to integral types, into a void*, and there is no void^. In effect, this means that one cannot hide a handle from the garbage collector and cannot subvert the CLR type system.


Support for C++ Templates and .NET-Style Facilities

Moving on from memory-related facilities, a characteristic of sophisticated C++ programs is the use of templates. Templates allow parameterized type declarations:

template<class T>
void swap(T% t1, T% t2){
T tmp(t1);
t1 = t2;
t2 = tmp;
}

 

works with any type that is copyable:

Object ^o1, ^o2;        swap(o1, o2);   //swap handles
int ^i1, ^i2; swap(i1, i2); //swap handles
swap(*i1, *i2); //swap values
MessageQueue *q1, *q2; swap(q1, q2); //swap pointers
swap(*q1, *q2); //swap values
value class V {} v1, v2; swap(v1, v2); //swap values
ref class R {} r1, r2; swap(r1, r2); //swap values
class Native{} n1, n2; swap(n1, n2); //swap values

 

There are many other ".NET-like" facilities in C++/CLI, such as properties, delegates, a distinction between null and 0, boxing and unboxing, events, initonly fields, a distinction b etween native and CLR-based arrays, delegating constructors, and single-implementation / multiple-interface inheritance. The lack of support for multiple inheritance is a dramatic restriction to the language, but the large majority of programmers have come to believe that multiple implementation inheritance is more trouble than it is worth, especially when templates and generics facilities are available to "mix in" implementations.


Why Would a C++ Programmer Move to C++/CLI?

Although garbage collection and improved resource management are attractive in and of themselves, the primary attraction of C++/CLI to C++ programmers is access to the Base Class Library and the rich functionality of the .NET Framework, which is rapidly becoming the preferred way to interact with Microsoft operating-system facilities. Most programming now centers around manipulating higher-level types and facilities, such as XML and email messages, database connections, and security evidence. C++/CLI, with its first-class extensions, makes interacting with .NET’s high-level types much more productive.

C++/CLI will be the lowest-level language for the CLI. Unless you are interested in tracking stack variables on your fingers and writing IL code in Notepad*, C++/CLI is going to be the most powerful way to exploit the .NET Framework. Performance always matters, and Microsoft’s “Whidbey” version of C++/CLI will debut some fascinating optimization technology.

The initial releases of languages for the .NET Framework have not emphasized optimization technology; rather, they have relied primarily on the Just-In-Time compiler (“JITter”) to transform Common Intermediate Language (CIL)-based assemblies into high-performance native code.

While JITting is extremely powerful, providing every programmer’s code with access to the unique performance characteristics of the CPU on the end-user’s machine, Microsoft’s “Whidbey” version of C++/CLI provides significant additional optimization. First, the front-end is an optimizing CIL compiler so that universally applicable performance translations will be applied before the assembly is written. Second, Microsoft will provide “profiler-guided optimization,” in which the program’s actual performance profile on a specific target architecture (such as the Intel® Itanium® processor for a server-side application or systems based on Intel® Centrino® mobile technology for a mobilized software solution) will be used as input to a second-order offline performance transformation.

Because this profiler-guided optimization is done offline, the optimization analysis and transforms are not time- or resource-constrained, the final deployed code is not (necessarily) instrumented, and there is no profiler or optimizer overhead at runtime. Many high-performance applications running on Itanium processors, such as transaction servers fronting high-volume databases, should benefit significantly from this type of optimization.

This optimization will highlight the parallel-processing strengths of the Explicitly Parallel Instruction Computing (EPIC) technology of the Itanium processor family, as well as Hyper-Threading Technology, available on Intel® Xeon® processors and Pentium® 4 processors. Itanium processors should particularly benefit, as profiler-guided optimization should allow confirmation of EPIC static branch prediction and predication, where wrong guesses have relatively high costs.


Conclusion

As exciting as Microsoft's forthcoming technology is, it is also very likely that C++/CLI will be a language for which multiple vendors will provide compilers. No company other than Microsoft has announced a C++/CLI compiler, but IBM, for instance, is a member of the ECMA technical group working on the C++/CLI standard. As the .NET Framework becomes increasingly ubiquitous, it seems a good bet that programmers will have their choice of C++/CLI compilers and IDEs.

Finally, perhaps the most important reason for a .NET programmer to learn C++/CLI is the observation with which this article began: no language is mentioned more in job ads than C++. Knowledge of C++ opens up the broadest range of programming positions on the broadest range of platforms. C++/CLI, which allows one to defer tackling the complexities of memory management while enjoying the powerful and familiar libraries of .NET, may be the ideal stepping-stone into this common language of professional software development.


Additional Resources and References

 

Intel Developer Centers

 

Articles