1. Introduction
The original language binding to the CLI was called Managed Extensions to C++. The revised language binding is referred to as C++/CLI, and an ECMA standard of this binding is currently progressing well.
The language revisions fall into the following general categories:
1. Syntax. This represents changes in the way we define and manipulate our CLI types to make programming more elegant and pleasant.
2. CLI conformance: The CLI object model differs in some significant ways from that of C++. The MC++ binding at times resisted that, such as in its treatment of string literals, its handling of the CLI enum, and in the definition of value types. These changes, we believe, improve the fidelity of the dynamic programming model. Unfortunately, they do not in all cases provide a mechanical translation from MC++ to C++/CLI.
3. CLI enhancements. The absence of support for copy construction and the automatic invocation of a destructor on a reference type is not just aggravating but error-prone. These represent beneficial patterns of object management that enhance the integrity of our programs. These and other design patterns from ISO-C++ have been integrated within the CLI binding
2. New Keywords
|
Keyword |
Purpose |
|
ref class ref struct |
Defines a CLR reference class |
|
value class value struct |
Defines a CLR value class |
|
interface class interface struct |
Defines a CLR interface |
|
enum class enum struct |
Defines a CLR enumeration |
|
property |
Defines a CLR property |
|
delegate |
Defines a CLR delegate |
|
event |
Defines a CLR event |
3. Override Specifiers
|
Keyword |
Purpose |
|
abstract |
Indicates functions or classes are abstract |
|
new |
Indicates that a function is not an override of a base class version |
|
override |
Indicates that a method must be an override of a base-class version |
|
sealed |
Prevents classes from being used as base classes |
4. Generic Related Keywords
|
Keyword |
Purpose |
|
generic |
Defines a generic type |
|
where |
Specifies the constraints of a generic type |
5. Miscellaneous Keywords
|
Keyword |
Purpose |
|
finally |
Indicates default exception handlings behavior |
|
gcnew |
Allocates types on the garbage-collected heap |
|
initonly |
Indicates a member can only be initialized at declaration or in a static constructor |
|
literal |
Creates a literal variable |
|
nullptr |
Indicates that a handle or pointer does not point at an object |
6. Non-Keyword Language Constructs
|
Keyword |
Purpose |
|
array |
Type for representing CLR arrays |
|
interior_ptr |
Points to data inside reference types. |
|
pin_ptr |
Points to CLR reference types to temporarily suppress the garbage collection system |
|
safe_cast |
Determines and executes the optimal casting method for CLR types |
|
typeid |
Retrieves a Type object describing the given type or object |
7. New C++ Operators
| Keyword |
Purpose |
|
^ |
Indicates a handle to an object located on the garbage-collected heap |
|
% |
Indicates a tracking reference |
8. Description of some important features
Unified Type System: In this version all value types and handles are derived from System::Object.
Pointer Model: In C++/CLI we have nullptr which is a pointer to a null value
Example: Object^ obj1 = nullptr
Reference Types: An object on the managed Heap can be moved by the GC, so its location must be tracked, A reference to such an object is called as tracking reference (%).
Example: R^ h = gcnew R;
R% r = *h;
Generics: Generics Types and Functions are defined to allow parameterized types. Generics declaration is meant for CLR objects (not with Native classes).
Example:
generic <typename ItemType>
public ref class Stack
{
array<ItemType>^ items;
public:
Stack(int size)
{
items = gcnew array<ItemTypes>(size);
}
void Push(ItemType data) {.......}
ItemType Pop() {.......}
}
While creating a new instance of Stack class use following code
Stack<int>^ s = gcnew Stack<int>(5);
If we want to store an object of type Customer, we can use following code
Stack <Customer^>^ s = gcnew Stack<Customer^>(10);
s -> Push(gcnew Customer);
Customer^ c = s -> Pop();
Note: Generic type declaration can have any number of parameters.
Constraints: In generics type, we can apply constraints using a keyword where, for example if we want the parameter in a Generic type to implement an interface IComparable, we can achieve the same as shown in the code below
generic<typename KeyType, typename ValueType>
where KeyType : IComparable
public ref class Dictionary
{
public :
void Add(KeyType key, ValueType val)
{
...
if (key ->CompareTo(x) < 0) {.....}
...
}
}
Generic Functions: In certain cases its not needed for an entire class to be parameterized, in such cases we can use generic functions as shown below.
generic <typename ItemType>
void PushMultiple(Stack<ItemType>^ s, array<ItemType>^ v value)
{
for each (ItemType v in values)
{
s ->Push(v);
}
}
9. Interoperability in C++:
C++ supports interoperability features that allow managed and unmanaged code to coexist and interoperate within the same assembly or in the same file. C++ assembly which has both native and managed code is called as mixed assembly. They contain machine instructions as well as MSIL instruction. An existing application consisting entirely of unmanaged functions can be brought to the .NET platform by re-compiling just one module with the /clr compiler switch. This module is then able to use .NET features, but remains compatible with the remainder of the application. In this way, an application can be converted to the .NET platform in a gradual, piece-by-piece fashion. It is even possible to decide between managed and unmanaged compilation on a function-by-function basis within the same file. Assemblies compiled with /clr can call managed and unmanaged functions at will, including CRT functions such as printf, and are free to use .NET Framework Platform Invoke features to call unmanaged functions inside DLLs.
C++ supports the use of ATL, MFC, SCL, and the CRT libraries as mixed assemblies compiled with /clr. These mixed libraries allow you to use all of their existing functions when your code contains a mixture of native code and MSIL code.
Performance Consideration while using Interoperability: Regardless of the interop technique used, special transition sequences, called "thunks", are required each time a managed function calls an unmanaged function, and vise-versa. These thunks are inserted automatically by the C++ compiler, but it's important to keep in mind that cumulatively, these transitions can be expensive in terms of performance.
One way to avoid or reduce the cost of interop thunks is to re-factor the interfaces involved to minimize managed/unmanaged transitions. Dramatic performance improvements can be made by targeting "chatty" interfaces--those which involved frequent calls across the managed/unmanaged boundary. A managed function that calls an unmanaged function in a tight loop, for example, is a good candidate for re-factoring. If the loop itself is moved to the unmanaged side, or if a managed alternative to the unmanaged call is created (perhaps be queuing data on the managed side and then marshalling it to the unmanaged API all at one after the loop), the number of transitions can be reduced significantly.
For .NET languages such as Visual Basic and C#, the prescribed method for interoperating with native components is P/Invoke. Since P/Invoke is supported by the .NET Framework, C++ supports it as well, but C++ also provides its own interoperability support, which is referred to as C++ Interop. C++ Interop is preferred over P/Invoke because P/Invoke is not type-safe, so errors are primarily reported at run-time, but C++ Interop also has performance advantages over P/Invoke.
Both techniques require two things to happen whenever a managed function calls an unmanaged function:
- The function call arguments are marshaled from CLR to native types
- A managed-to-unmanaged thunk is executed
- The unmanaged function is called (using the native versions of the arguments)
- An Unmanaged-to-managed thunk is executed
- The return type and any "out" or "in,out" arguments are marshaled from native to CLR types
The managed/unmanaged thunks are necessary for interop to work at all, but the data marshalling required depends on the data types involved, the function signature, and how the data is to be used.
The data marshalling performed by C++ Interop is the simplest possible form: the parameters are simply copied across the managed/unmanaged boundary in a bitwise fashion; no transformation is performed at all. For P/Invoke, this is only true if all parameters are simple, blttable types. Otherwise, P/Invoke performs very robust steps to convert each managed parameter to an appropriate native type, and vise-versa if the arguments are marked as "out", or "in,out".
In other words, C++ Interop uses the fastest possible method of data marshalling, whereas P/Invoke uses the most robust method. This means that C++ Interop (in a fashion typical for C++) provides optimal performance by default, and the programmer is responsible for addressing cases where this behavior isn't safe or appropriate.
C++ Interop therefore requires that data marshalling must be provided explicitly, but the advantage is that the programmer is free to decide what is appropriate, given the nature of the data, and how it is to be used. Furthermore, although the behavior of P/Invoke data marshalling can be modified at customized to a degree, C++ Interop allows data marshalling to be customized on a call-by-call basis. This isn't possible with P/Invoke.
10. References
1. http://msdn2.microsoft.com/library/60k1461a.aspx