Managed types.*

The managed extensions for C++ are extensions to the C++ compiler and linker to allow them to create .NET code. The managed extensions use C++ keywords and syntax, but they follow .NET rules for types and facilities. In some cases, .NET has concepts that are not available in standard C++, and in other cases, it has items that have similar names to items in C++ but with totally different behavior. To extend the language for these new facilities and to distinguish between .NET and native C++ items, some new keywords were added to the language. These new keywords, some new syntax, two new pragmas, and a compiler switch constitute the Managed Extensions.

New keywords in Visual C++ .NET

The following table contains the new keywords Microsoft has introduced in the managed extensions.
Keyword Description
__abstract Indicates that the class is abstract - that is, not all methods have an implementation, and to use the class, you must derive from it.
__box Used to box a value type. Boxing creates an object with the value of the value type that has been boxed.
__delegate Declares a delegate type. Delegates are essentially type-safe function pointers.
__event Declares an event, a notification mechanism as part of a class. This keyword indicates that the class can generate the event, and it adds code to store the delegates invoked when the event raised.
__gc Identifies that a class is managed by the .NET garbage collector or that a pointer points to a managed object.
__identifier Used when the name of a type or a member is a keyword in C++ and indicates to the compiler to ignore C++ meaning of the word.
__interface When combined with the __gc keyword, it allows you to declare a manage interface.
__nogc Used to indicate that the type in mot managed by the .NET garbage collector or to indicate that a pointer points to a non-managed object.
__pin Used on a pointer to pin the object it points to. This pinning means that for the scope of the pointer the object will not be moved in the managed heap during garbage collection.
__property Indicates that a method is the get or set method for a property.
__sealed Used on a class to indicate that the class is complete and cannot be extended through class derivation.
__try_cast This cast operation performs a run-time type check and throws a managed exception if the call fails.
__typeof Operator used to obtain the Type object for a particular type.
__value Indicates that the type is lightweight and is created on the stack rather than on the managed heap.

In addition, the compiler and linker have new switches for compiling .NET code. The most important new compiler switch is /clr. This switch tells the compiler to compile all code to Microsoft Intermediate Language (MSIL), regardless of whether the code is managed by the .NET garbage collector.

When you use the /clr switch, you also should have the following #using statement somewhere in the project:

#using <mscorlib.dll>
The #using statement and the /clr switch go hand in hand - if you use one, you have to use the other.

Managed types and value types

C++ allows you to create new types. .NET defines two different sorts of types, depending on where instances of the type are allocated and how they are used:

Garbage collected reference types appears to solve the problem of leaking memory - your code merely has to allocate the object, and the garbage collector does the deallocation. However, garbage collection is more important than just this. In distributed applications memory allocation is extremely important because objects can be accessed across process or machine boundaries, which introduces the issue of which code has the responsibility to perform the cleanup.

If your application uses many small objects with short lifetimes, individually allocating these objects on the heap can be a significant performance hit. For this reason .NET provides value types. Because value types are normally created on the stack, their lifetime is short.

Managed objects

To identify that a new class is managed by the garbage collector, you need to use the __gc modifier. This modifier can be used on classes or structures, and it can be used on pointers to explicitly specify that the is to a managed object. Here is an example of a managed class:
__gc class Robot {
   private:
      int ID;
      ...
   public:
      Robot() : ID(curID++) {};
      ~Robot() {};
};
Please note that instances of this class can be created only on the managed heap, as shown in the following code:
Robot *rbt = __gc new Robot();
Although, we explicitly used the __gc modifier on the new operator to indicate that the managed operator is used, you don't have to use this syntax. Even if you omit this modifier, the compiler will still use managed operator new because the class is being created as managed. In other words, the following line produces exactly the same result:
Robot *rbt = new Robot();

Instances of a __gc class cannot be created on the stack. If we attempt create a stack-based instance

Robot rbt;
the compiler will issue the following error:
error C3149: 'Robot' : illegal use of managed type 'Robot'; did you forget a '*'?

If you omit the __gc modifier or if you use the __nogc modifier in a class declaration, a native C++ class will be created. You can use pointers to native C++ objects in __gc classes, but you cannot use pointers to __gc objects in native C++ classes. All __gc classes look similar to native C++ classes, but they are subject to the .NET rules of reference types. Some of these rules are similar to C++; others apply more restrictions. The most significant restriction is that .NET allows only single-implementation inheritance, which means you cannot derive a class from more than one other class. Please find the example showing how to declare a managed class on the example page.

Methods and properties of managed types

In generally we can say that methods of reference types are very similar to methods of native C++ classes. There are several differences you should be aware of.

First of all, you are not allowed to use C++ const or volatile modifiers for __gc type methods. If a method marked as const

__gc class Robot{
   ...
   public: void Print() const { ... };
   ...
};
then the compiler will generate the error C3842:
error C3842: 'Print': 'const' and 'volatile' qualifiers on member functions of managed types are not supported
However, you still can mark parameters passed by reference and a value of a method returned by reference as const.

Second, you cannot use default values for methods' parameters. For example, the following code

   public: void GetHit(int damage=5) { ... };
will generate the error C3222:
error C3222: 'int damage' : cannot declare default arguments for member functions of a managed type

Managed types are normally passed as arguments of methods either by reference or by pointers. The following two examples show how this can be done:
__gc class Cannon {
public:
   void Shoot(Robot &rbt)
   {
      rbt.GetHit(10);
   };
};
...
Robot *robot = new Robot();
Cannon *cannon = new Cannon();
cannon->Shoot(*robot);
__gc class Cannon {
public:
   void Shoot(Robot *rbt)
   {
      rbt->GetHit(10);
   };
};
...
Robot *robot = new Robot();
Cannon *cannon = new Cannon();
cannon->Shoot(robot);

Managed types (both __gc and __values) can implement .NET properties. Strictly speaking, a property is not really a member of a type. It's a description that identifies methods on the type that can be called through property access. Properties are implemented with get_ and set_ methods. The get_ method is used to return the property, so its return type should be the same as the property. The set_ method is used to initialize the property, do typically, the method should not have a return type and its last parameter should be the same type as the property. To tell the compiler to generate a property, you use the __property modifier on the property methods:

__gc class Robot {
   ...
   public:
      __property int get_Energy() { return HP; };
      __property void set_Energy(int hp)
      {
         HP = (0<=hp && hp<=MaxHP) ? hp : HP;
      };
   ...
};
In this example we defined property Energy that lets users access private data member HP. Now we can use this property for reading and writing:
while( robot->Energy > 0 ){
   robot->Energy = robot->Energy - 5;
}
In fact, you can even use increment/decrement operators and various assignment operators. For example, the following code is valid:
robot->Energy += 10;
robot->Energy--;
You don't have to provide both get_ and set_ methods. If you decide to have only get_ method for a property, then the property becomes read only. Similarly, if you only have the set_ method for a property, this property is write only. The only restriction you need to remember about properties is that you cannot have a property with the same name as a data member of the class. For more details see [1].

Value types

As we discussed before, value types are typically small, short-lived objects and they are usually created on stack. In managed C++, you can define a value type as a class or a structure. The important point is that the value type is marked with the __value keyword, as shown:
__value class Location {
   private:
      float x;
      float y;
   public:
      Location() : x(0), y(0) {};
      Location(float X, float Y) : x(X), y(Y) {};
      ...
};
Objects of a value type can be created either on the stack or on the non-managed heap. You cannot create a value type directly on the managed heap. In other words, you can use declaration like
Location loc(100, 200);
but you cannot use declaration like
Location *loc = new Location(100, 200);
Code like this will generate the compiler's error C2716:
error C2716: 'operator new' allocates value types on the C++ heap; use '__nogc new Location'

All value types are automatically sealed. That is, you cannot derive from a value class. You don't need to use the __sealed modifier. A value type cannot derive from a reference type. Value types are typically used as records of data (the same way we use struct in C/C++). By default, the items are sequential - that is, in memory the fields appear in the order that they declared.

Enumerations are value types and have similar characteristics (allocated on the stack, implicitly sealed). However, enumerations do have some some differences. For example, we are able to convert enumerated values to other types, can get the names and values of members, and can create an enumerated value from a string**. We, however, cannot provide implementations of methods on enums. Enumerated values are integral types. You can specify the underlying type that will be used. The syntax looks like inheritance, but you don't specify an access level.

__value enum Direction : unsigned short int {
	East      =   0,
	NorthEast =  45,
	North     =  90,
	NorthWest = 135,
	West      = 180,
	SouthWest = 225,
	South     = 270,
	SouthEast = 315
};

How to retrieve type of a manage type

The typeid() operator we learned in lecture 9 doesn't work for managed types. Instead, managed extension for C++ provides operator __typeof(). However, there is a difference between these two operators. First of all, the __typeof() operator returns a pointer to the Type class. This is another managed class that contains all information about the type. For instance, an object of the Type class has these properties (among the list of others):

The second difference is that the managed operator __typeof() takes only the name of a managed type as an argument. That is, we can run

Type *type = __typeof( Robot );  // CORRECT
but we cannot run code like
Robot *robot = new Robot();
Type *type = __typeof( *robot ); // WRONG

So, how do we find out the type of an object? For example, we have an array of objects of the BattleObject class, but we know that these objects can be either of the type Robot, or Bomb, or Cannon since they all derived from the class BattleObject. For this purpose we would suggest deriving your base class from the managed class Object. The class Object provides public method GetType() that returns a pointer to an object of the Type class describing the object invoking the method. For example, if we derive class BattleObject from Object, and class Robot from BattleObject:

__gc class BattleObject : public Object {
   ...
};

__gc class Robot : public BattleObject {
   ...
};

__gc class Bomb : public BattleObject {
   ...
};

__gc class Cannon : public BattleObject {
   ...
};
then we can use the method GetType() inherited from the indirect base class Object:
Robot *robot = new Robot();
Type *type = robot->GetType();
For example, we can print the hierarchy of the class:
Type *type = robot->GetType();
while( type ){
   Console::Write(S" {0} -", type->Name);
   type = type->BaseType;
}

To be able to compare two Type objects, class Type has method Equals() that take one argument of the Type* type and returns true if the type specified by the argument and the type of the invoking object refer to the same type. For example, if we contain an array of BattleObjects, but we want to move only Robots but not Bombs or Cannons, then we can use a loop like:

BattleObject *list[];
...
Type *robot_type = __typeof( Robot );
for(i=0;i<list->Length;i++)
   if( robot_type->Equals( list[i]->GetType() ) )
      dynamic_cast<Robot*>(list[i])->Move();
Please find the complete example on the example page.

Restrictions on managed types

Managed arrays

In .NET, arrays are managed types - that is, each array is an object and is allocated on the managed heap. The syntax of declaring managed arrays is slightly different from the syntax for declaring native arrays. Declaring a one-dimensional array is straightforward:
Robot* robots[] = __gc new Robot*[14];
for(int i=0;i<14;i++)
   robots[i] = new Robot();
In fact, all managed arrays are actually objects derived from the Array object declared in the System name space. We can see the inheritance using the method GetType() of the array object:
Console::WriteLine(S"'robots' type:");
Type *t = robots->GetType();
while( t ){
   Console::WriteLine(S" {0}", t->ToString());
   t = t->BaseType;
}
For our managed array robots the output will be:
'robots' type:
 Robot[]
 System.Array
 System.Object
Please find the complete example on the example page.

You can find the complete documentation about the Array class in the MSDN library. Here we would like to shortly mention only several properties and methods:
Property Description
IsFixedSize Gets a boolean value indicating whether the Array has a fixed size.
Length Gets a 32-bit integer that represents the total number of elements in all the dimensions of the Array.
Rank Gets the rank (number of dimensions) of the Array.
Method Description
GetEnumerator() Returns an IEnumerator for the Array.
GetLength() Gets a 32-bit integer that represents the number of elements in the specified dimension of the Array.
GetLowerBound() Gets the lower bound of the specified dimension in the Array.
GetType() Gets the Type of the current instance.
GetUpperBound() Gets the upper bound of the specified dimension in the Array.
GetValue() Gets the value of the specified element in the current Array. The indexes are specified as an array of 32-bit integers.
SetValue() Sets the specified element in the current Array to the specified value.
static BinarySearch() Searches a one-dimensional sorted Array for a value, using a binary search algorithm.
static Clear() Sets a range of elements in the Array to zero, to false, or to a null reference (Nothing in Visual Basic), depending on the element type.
static IndexOf() Returns the index of the first occurrence of a value in a one-dimensional Array or in a portion of the Array.
static LastIndexOf() Returns the index of the last occurrence of a value in a one-dimensional Array or in a portion of the Array.
static Reverse() Reverses the order of the elements in a one-dimensional Array or in a portion of the Array.
static Sort() Sorts the elements in one-dimensional Array objects.

The syntax to declare and access multidimensional array is quite different to native C++.

RobotQueue* cells[,] = __gc new RobotQueue*[M, N];
This code creates two dimensional array of pointers to RobotQueue objects. This array contains M rows and N columns. Thus, the properties of this array are:
cells->Length = M * N       - the total number of elements in the array
cells->Rank = 2             - the number of dimensions
cells->GetUpperBound(0) = M - the number of rows
cells->GetUpperBound(1) = N - the number of columns
Please find the complete example on the example page.

We can sort one dimensional arrays using static methods Sort(). In order to provide the method with a comparing tool we need to create a new class (or struct to be exact) derived from the System::Collections::IComparer object. This new class should have only one method named Compare() that takes two pointers to the class Object and returns an integer value. This vale is to be negative, if the first argument is "less" than the second, positive if the first argument is "greater" than the second, and zero if they are "equal". The following example shows how to do that:

__gc struct RobotComparer : Collections::IComparer {
   int Compare(Object *a, Object *b)
   {
      Robot *left  = dynamic_cast<Robot*>(a);
      Robot *right = dynamic_cast<Robot*>(b);
      return left->getHP() - right->getHP();
   }
};
...
Array::Sort( robots, new RobotComparer );
Please find the complete example on the example page.

Managed strings

In .NET strings are managed objects. The System::String class encapsulates most of the action that you will want to do on a string However, it's important to realize that a String is immutable. That is, if you call a method that changes a string, you do not get back the original string modified; instead, you get a completely new string.

To declare a string and initialize its value, we usually use code like:

String *str = S"Hello, world!";
The S prefix indicates that the string is a managed string. The String class has constructors that take an unmanaged pointer to a char buffer and an unmanaged pointer to a wide char buffer, which will convert the strings into the managed string. However, to do so requires the compiler to generate extra code, so if possible, you should always use managed string literals.

If we have two different Strings a and b we cannot compare them just using the equality operator ==:

String *a = S"Hello";
String *b = S"hello";
if( a == b ) ...  // WRONG
Instead, we need to use method Equals():
if( a->Equals(b) ) ... // CORRECT
or
if( String::Equals(a, b) ) ... // CORRECT

Please consult the MSDN library to see all members of the String class.

Managed operators

Syntax for overloading operators for managed classes are very different from the native C++ syntax. Managed operators are static members of a class, and they return the result of the operation, which is an instance of the class. Managed classes do not support the operator keyword. Instead, you need to use the predefined names for the operators. The table below contains the list of names you can use. Here is an example that shows how to overload several operators:
__value class Complex {
   private:
      double x, y;
   public:
      Complex(double X, double Y) : x(X), y(Y) {};
      static Complex& op_Addition(Complex& l, Complex& r)
      {
         return Complex(l.x+r.x, l.y+r.y);
      };
      static Complex& op_Subtraction(Complex& l, Complex& r)
      {
	     return Complex(l.x-r.x, l.y-r.y);
      };
      static Complex& op_UnaryNegation(Complex& r)
      {
	     return Complex(-r.x, -r.y);
      };
      static Complex& op_Multiply(Complex& l, double r)
      {
         return Complex(r*l.x, r*l.y);
      };
      static Complex& op_Multiply(double r, Complex& l)
      {
         return Complex(r*l.x, r*l.y);
      };
};
Note that when we use value type, the operators will work exactly as we expect. However, if we use reference type, then you need to be careful because the following code is not correct:
__gc calss Complex {
  ...
};

Complex *a = new Complex(1, 3);
Complex *b = new Complex(2, 1);
Complex *c;
c = a + b;
This code will perform pointer arithmetic because all variables are pointers. The solution for this problem is to use references:
Complex &a = * new Complex(1, 3);
Complex &b = * new Complex(2, 1);
Complex &c = * new Complex(0, 0);
c = a + b;
but don't forget to overload the assignment operator:
__gc class Complex {
   private:
      double x, y;
   public:
      Complex(double X, double Y) : x(X), y(Y) {};
      static Complex& op_Addition(Complex& l, Complex& r)
      {
         Complex *res = new Complex(l.x+r.x, l.y+r.y);
         return *res;
      };
      static Complex& op_Subtraction(Complex& l, Complex& r)
      {
         Complex *res = new Complex(l.x-r.x, l.y-r.y);
         return *res;
      };
      static Complex& op_UnaryNegation(Complex& r)
      {
         Complex *res = new Complex(-r.x, -r.y);
         return *res;
      };
      static Complex& op_Multiply(Complex& l, double r)
      {
         Complex *res = new Complex(r*l.x, r*l.y);
         return *res;
      };
      static Complex& op_Multiply(double r, Complex& l)
      {
         Complex *res = new Complex(r*l.x, r*l.y);
         return *res;
      };
      static Complex& op_Assign(Complex &l, Complex &r)
      {
         l.x = r.x;
         l.y = r.y;
         return l;
      };
};

C++ operator
symbol
Name of alternative
method
Name of operator C++ operator
symbol
Name of alternative
method
Name of operator
Not defined ToXxx or FromXxx op_Implicit Not defined ToXxx or FromXxx op_Explicit
+ (binary) Add op_Addition - (binary) Subtract op_Subtraction
* (binary) Multiply op_Multiply / Divide op_Division
% Mod op_Modulus ^ Xor op_ExclusiveOr
& (binary) BitwiseAnd op_BitwiseAnd | BitwiseOr op_BitwiseOr
&& And op_LogicalAnd || Or op_LogicalOr
= Assign op_Assign << LeftShift op_LeftShift
>> RightShift op_RightShift Not defined LeftShift op_SignedLeftShift
Not defined RightShift op_UnsignedRightShift == Equals op_Equality
> Compare op_GreaterThan < Compare op_LessThan
!= Compare op_Inequality >= Compare op_GreaterThanOrEqual
<= Compare op_LessThanOrEqual *= Multiply op_MultiplicationAssignment
-= Subtract op_SubtractionAssignment      ^= Xor op_ExclusiveOrAssignment
<<= LeftShift op_LeftShiftAssignment %= Mod op_ModulusAssignment
+= Add op_AdditionAssignment &= BitwiseAnd op_BitwiseAndAssignment
|= BitwiseOr op_BitwiseOrAssignment , None assigned op_Comma
/= Divide op_DivisionAssignment -- Decrement op_Decrement
++ Increment op_Increment - (unary) Negate op_UnaryNegation
+ (unary) Plus op_UnaryPlus ~ OnesComplement op_OnesComplement

References

  1. Programming with Managed Extensions with Microsoft Visual C++ .NET by Richard Grimes. ISBN: 0-7356-1762-1.
  2. Visual C++ .NET
  3. Microsoft Visual C++ .NET Step by step by Julian Templeman and Andy Olsen. ISBN: 0-7356-1567-5. Chapters 7, 8, 9, 13.
  4. Managed Extensions Bring .NET CLR Support to C++ by Chris Sells
  5. Garbage Collection: Automatic Memory Management in the Microsoft .NET Framework by Jeffrey Richter
  6. Handling Language Interoperability with the Microsoft .NET Framework by Damien Watkins
  7. Memory Management (find out more about stack and heap on MSDN)
  8. Microsoft .NET Framework FAQ
  9. Operator Overloading Usage Guidelines