Managed Extensions

Snoops

   

 
Migrating to .NET: A Pragmatic Path to Visual Basic .NET, Visual C++ .NET, and ASP.NET
By Dhananjay  Katre, Prashant  Halari, Narayana  Rao  Surapaneni, Manu  Gupta, Meghana  Deshpande

Table of Contents
Chapter 8.   New Features in Visual C++ .NET


Managed extensions for C++ are a set of language extensions to help Microsoft Visual C++ developers write applications for Microsoft .NET. This is often referred to as managed code, and its compilation (using the /clr compiler option) produces CLR-compliant MSIL, which is compiled by the CLR to the native machine code.

The main design goals of the managed extensions are

  1. Enabling C++ developers to write .NET applications, thereby enhancing developer productivity

  2. Retaining (and leveraging on) the huge investment in unmanaged C++ components by enabling their access from .NET Framework applications

  3. Enabling unmanaged C++ components to access .NET Framework components

  4. Accomplishing phasewise migration of a large body of existing code from unmanaged C++ to the .NET platform

A managed class is a native .NET class that runs under the control of the CLR and can fully leverage on the rich features of the .NET Framework, whereas normal, or unmanaged, C++ classes run in the traditional Microsoft Windows environment.

Managed extensions are a set of new keywords and attributes that allow the developer to decide whether classes and functions are to be compiled as managed or unmanaged code. Managed classes or functions can interoperate smoothly with the code written in various languages supported by the .NET Framework. Managed extensions can also be used to wrap existing components as .NET components, thus reducing the cost overhead involved in integrating existing code with .NET.

The .NET Framework provides a rich set of library functions, which are neatly divided into namespaces, classes. System is the indispensable namespace and it contains Object , the root of all the managed classes in all CLR-compliant languages, along with nested namespaces for I/O, collection, threading, security, reflection, and serialization classes.

To use the namespace provided by the given library, say mscorlib.dll , the managed extensions provide the #using directive. This establishes that the current assembly refers to (and depends on) an imported assembly (say, mscorlib.dll). This makes available all the namespaces defined therein. Then we can selectively import an individual namespace.

As mentioned earlier, managed extensions is a set of keywords and attributes that mark code as managed and result in the generation of MSIL when compiled with the /clr compiler option. Here is the list of keywords introduced in managed extensions:

__gc

__value

__abstract

__box

__delegate

__event

__identifier

__interface

__nogc

__pin

__property

__sealed

__typeof

__gc

The keyword __gc on a class or struct specifies that it is created on the CLR managed heap using the built-in operator new . Its lifetime is managed by the CLR and the class or structure is garbage-collected . A __gc structure has public default access and inheritance, whereas a __gc class has private default access and inheritance. The CLR doesn't support private inheritance; hence we must explicitly make it public when inheriting in a __gc class. Other than this default behavior, classes and structures are treated similarly; hence any statement made for classes can be assumed to be true for structures too, unless specified otherwise .

A __gc class cannot inherit from a __nogc (i.e., unmanaged types) class, and it can have no more than one __gc base class. All __gc classes by default inherit from System::Object if none is specified. Inheriting from multiple __gc interfaces is allowed, and this is the mechanism to model multiple inheritances.

Do not call a delete on objects created on a managed heap because they are destroyed by the garbage collector (to be explained later) as and when appropriate. Still, if you insist on calling delete , you must provide a definition for the destructor in your class. Memory is still not freed and will be reclaimed by the garbage collector at its will. (Of course you can force the garbage collector to reclaim the memory immediately, but that involves the overhead of a full run of the garbage collector. There is really no need to be paranoid about freeing memory because it's unlikely to encounter an out-of-memory situation.)

As an interesting implication , you can call delete on a __gc object and still continue using its methods . We'll explore this issue further when discussing object finalization , destruction and garbage collector.

__gc classes are like normal C++ classes in most respects. They can have constructors and static or nonstatic fields and properties and contain visibility specifiers and whole classes, or individual methods can be declared, __abstract or __sealed . Additionally they can have a static constructor to be used for initializing static data members of the class. A static constructor is a constructor preceded by static keyword. It doesn't take any parameters and cannot have an initializer list. Static fields of a class don't have to be redeclared outside of the class, as mandated in C++. The Static constant members of a managed class must be initialized at the point of their declaration.

These classes come with their own set of limitations. They can't inherit or act as a base class for any unmanaged type. __gc classes cannot override new or delete operators and don't support the semantics of friend classes or functions. sizeof or offset operators cannot be applied to them, and they can't contain an embedded using directive. Also, the concept of union is not directly supported by the CLR.

__gc classes are always created on the runtime heap using the new operator; hence they can't be used in object or parameter declaration or as function return types. Also copy-constructor semantics are not supported for the same reason; hence we can't have a user -defined copy-constructor.

__value

The keyword __value on a class or structure specifies the ability to create objects on the runtime stack as well as the heap. This is used to represent small and short-lived data items, which should be created on the stack to enable their automatic destruction upon going out of scope. __value doesn't have to wait for the garbage collector and then start the costly process of finalization/destruction for every instance.

__value classes implicitly derive from System::ValueType (managed class) and hence can override the methods therein. They can inherit only from other __gc interfaces. Also they are implicitly __sealed , so they cannot act as a base class for any other class.

In most cases value types behave like __gc classes. As a quick review, they can have a visibility specifier and contain static or nonstatic data members and methods and properties. They can have a static constructor, which doesn't take any parameters and can't have an initializer list. They cannot override new or delete operators and don't support copy construction and friend functions. There can be no embedded using directive, and they can't have sizeof or offset operators applied on them. However, they differ from __gc classes in terms of certain additional restrictions. The restrictions that apply to __value classes are as follows :

  1. __nogc new keyword is required for explicit allocation of an object.

  2. Calling convention of member function cannot be redefined to a native C++ calling convention.

  3. Cannot be the argument type of __gc new .

  4. Cannot introduce any virtual methods.

  5. Cannot be a base class.

The semantics of constructors in __value classes differ from standard C++ for interoperability reasons. If a constructor is declared, the default constructor can still be called. All nonstatic data members of a __value class are initialized to 0 in the absence of a default constructor. These data types can be embedded in the __gc class as static or C++ heap-allocated variables. They can also be declared as return values, parameters, and local variables .

__abstract

The keyword __abstract can be applied only to __gc classes and structures or the __gc interface. The use of this keyword indicates that the class cannot be instantiated . This class must be further derived and specific implementations provided, as required. The members of the class or interface are not affected by this keyword. A class can have all concrete methods and still be declared abstract to disallow users to instantiate this class. There are some restrictions that apply to abstract classes or structures. They are

  1. This keyword cannot be applied with the __value keyword.

  2. It cannot be applied with the __sealed keyword.

  3. The use of this keyword on an interface is allowed though it's redundant.

__box

Boxing is the mechanism for wrapping a __value type object in an object box with a __gc class stub (created on the CLR heap). An object of __value type is passed as the sole argument to __box () . Boxed types cannot be directly defined by the user; instead they are generated by the compiler on demand:

 graphics/icon01.gif __value class CValueClass{};  void MyMethod(Object * pObj){}  void main() {  CValueClass lObj;  // Following line will generate compiler error as the  //MyMethod function is expecting an Object* and we are  //passing a __value type to it.  MyMethod(&lObj);  //If you still insist on passing the __value type to the  //MyMethod function, the correct usage is given below.  MyMethod(__box (lObj));  // we can even declare a __box type, but can't create its  //object directly  __box CValueClass *pObj = __box(lObj);  MyMethod(pObj);  } 

This is useful in passing a __value type to functions that expect an Object *, thus facilitating use of generic library classes with __value types. Use of boxed value classes is more precise and allows the user to avoid expensive dynamic_cast<> operations. There is a unique __gc class type for every __value class. A boxed __value class implicitly derives from System::ValueType and hence from System::Object . Conversion from a __gc pointer of a boxed value class to a __gc pointer of the underlying value class is done implicitly.

Boxing has its own limitations. A declaration of a boxed value class can have pointer-to type because a boxed value type is a __gc class. The compiler, upon encountering the __box keyword, creates a managed type on the CLR heap and does the bitwise copy for each field of the __value type before returning a __gc reference to it.

_unboxing

Unboxing is the reverse process of boxing using dynamic_cast or __try_cast wherein we can obtain a __gc pointer for the underlying object stored in the box on the CLR heap. A copy of the object of __value class can be obtained by dereferencing the __gc pointer.

__delegate

A delegate defines a reference type that can be used to encapsulate methods with a specific signature. Delegates can be single or multicast (more than one method attached to a single delegate. Managed C++ delegates are by default multicast). A delegate is roughly equivalent to a C++ function pointer, but it is to be used in managed contexts; hence it can be bound only to the methods within a __gc class. It can be a proxy for a pointer to a C++ function provided the function is defined in a native DLL and used in the managed extensions application via Platform Invoke.

Whenever the compiler encounters the __delegate keyword, a definition of a __gc class is generated, which inherits from System::MulticastDelegate . Its constructor takes two parameters, a pointer to a managed object (which can be Null in the case of binding static methods) and an address of a method of the specified type. It also implements a method called Invoke with the same signature as that of the delegate. MulticastDelegate provides two static methods, Combine and Remove , for the purpose of creating a delegate with multiple methods attached to it (by combining two delegates) or for selectively removing any method from a multicast delegate.

Default accessibility of delegates is private , but it can be explicitly declared to be public . It can take interior __gc pointers as a parameter and return any managed type. Upon invocation (calling the Invoke method on the delegate instance, passing appropriate parameters), functions are invoked in the order that they were attached and the return value of a delegate is the value returned from last method call.

Delegates cannot be overloaded (we cannot define two delegates with the same name but differing signature).

__event

CLR supports the publisher/subscriber events model. The source (publisher) can notify one or more subscribers that an event has occurred. The implementer or publisher doesn't have to know about number or type of subscribers. The event mechanism is based on runtime multicast delegates. The source (publisher) just advertises the ability to raise (publish) certain kinds of events, and the clients can subscribe to that event by adding their handler methods to it (by calling += or -= methods on the event, which internally translates to Combine or Remove on the underlying delegate).

Raising of an event (by the source) involves a call to the event, passing it appropriate parameters. This internally calls the method Invoke on the delegate, causing all the methods in its method list to be invoked one by one.

Events are declared by the __event keyword. As mentioned, the compiler internally implements the entire mechanism using delegates. Here is an example that should make it clearer:

 graphics/icon01.gif //delegates  WIN32_delegate void DeleteEventHandler(String*);  //Source event class  __gc class Source  {  public:      // Declaring the events OnDelete      __event DeleteEventHandler* OnDelete;      // Raise the event      void CallEvent()      {            OnDelete("Hello World");      }  }; 

Here we have declared a delegate type DeleteEventHandler for functions that accept a String * and return void . Next we define the __gc class Source that declares an event OnDelete . This is declared as public in the code; hence clients can directly add their handler methods to this event (by calling +=; more on that later). The compiler generates three functions ( public: add_ , remove_ , and protected: raise ) for each declared event and declares a private delegate instance corresponding to the event.

The compiler-generated code for the preceding event is as follows:

 graphics/icon01.gif __gc class Source  {  private:      // delegate instance      DeleteEventHandler* OnDelete;  public:      //subscribe to OnDelete      __event void add_OnDelete(DeleteEventHandler* eh)      {  OnDelete = static_cast<DeleteEventHandler*>  (Delegate::Combine(eh, OnDelete));      }      //unsubscribe to OnDelete      __event void remove_OnDelete(DeleteEventHandler* eh)      {  OnDelete = static_cast<DeleteEventHandler*>  (Delegate::Remove(eh, OnDelete));      }  //prevent invocations of raise externally  protected:      void raise_OnDelete(String* s)      {            if(OnDelete)                  OnDelete->Invoke(s);      }      Source()      {            OnDelete = 0;      }  }; 

Note the following characteristics of events:

  1. The raise_ method generated for an event is by default protected . But if the event is declared to be private , the raise method is also private . However, the generated raise method is never public .

  2. The raise_ method generated for an event is virtual only if the event is declared to be virtual .

  3. Only associated add_ and remove_ methods are generated if an event is declared in a __gc interface.

  4. Default accessibilities of add_ , remove_ , and raise_ can be overridden. This is achieved by explicitly declaring add_ , remove_ and raise_ and omitting the declaration of the event.

Here is how a client can subscribe to OnDelete event published by the Source class:

 graphics/icon01.gif //Client event class  __gc class Client  {  public:      void Print(String* l_pString)      {            Console::WriteLine(l_pString);      }      //Handler functions      void AddDeleteHandler(Source* l_pSource)      {  l_pSource->OnDelete += new DeleteEventHandler(this, &Cli- ent::Print);      }  }; 

It instantiates the delegate type by passing this and the address of handler function and adds them to the published event using +=, which in effect calls the add_ method.

Putting together the pieces of this jigsaw puzzle, we instantiate Source and Client classes, call the AddDeleteHandler method on the client, passing the Source instance as the parameter and then asking Source to fire the event:

 graphics/icon01.gif void main()  {      // Creating an instance of source and client classes  Source * l_pSource = new Source;      Client * l_pClient = new Client;      // Adding the handler functions      l_pClient->AddDeleteHandler(l_pSource);  // Fire the event  Console::WriteLine("Calling the CallEvent function of the  class");  l_pSource->CallEvent();  } 

__identifier

Language interoperatibility throws up the potential problem of encountering an identifier (from classes written in another language) that is a keyword in C++. The __identifier keyword provides a mechanism to treat a C++ keyword as an identifier. This keyword can be used at any place where a C++ identifier can legally appear. It takes as an argument a C++ keyword and facilitates its usage as an identifier.

__interface

A __gc interface embodies the COM notion of an interface. It's basically a collection of logically related methods or a declaration of an idea. The interfaces are putting an idea into words that can later be given a concrete form by any implementing class.

Interfaces are like classes without any concrete method. All methods of an interface can be considered to be pure virtual , although they are not required to be declared so. If you have begun to model interfaces as abstract classes, you are pretty much on the right track. However, the __abstract keyword is redundant for interfaces and they can't be declared as __sealed . They can explicitly inherit only from other __gc interfaces or the class System::Object (which is redundant).

Interfaces can contain __value constants , properties, and nested __value enum . However, they cannot contain data members (variables), or static members of any kind or nested classes, managed or unmanaged. The only access specifier allowed inside the interface is public , which is the default:

 graphics/icon01.gif __gc __interface IMyInterface  {      double MyMethod( int i);      __value enum MyEnum { abc, bcd, cde};      __property int get_id();  }; 

A __gc class can implement any number of __gc interfaces, but they must provide implementation for every method in all the interfaces.

In standard C++, we have seen all kinds of problems with multiple inheritance, especially when we reach the notorious diamond inheritance. The managed extensions rescue you because there are no variables in the base interfaces (if you decide to inherit your class from various interfaces) and no concrete methods to worry about having to choose between two different concrete methods by the same name. So now you are empowered to support two identical method signatures from two base interfaces:

 graphics/icon01.gif __gc __interface MyInteraface1 { int  MyMethod(); };  __gc __interface MyInteraface2 { void MyMethod(); };  __gc struct MyStruct : MyInteraface1, MyInteraface2 {     void MyInteraface1:: MyMethod () {}     int MyInteraface2:: MyMethod ()  { return 101; }  }; 

If you instantiate MyStruct and invoke MyMethod on it, the compiler yells at you. The key here is providing a fully qualified name for methods, and the least you can expect from users of a class is awareness about which interface's method they are interested in, and they can unambiguously state their intentions by appropriate casting:

 graphics/icon01.gif int main() {     MyStruct * l_pMyStruct = new MyStruct;     // l_pMyStruct->MyMethod();  // error: ambiguous call     int i = __try_cast < MyInteraface1 *>( l_pMyStruct)->                 MyMethod();     __try_cast < MyInteraface2 *>( l_pMyStruct)->                 MyMethod();  } 

For the uninitiated, __try_cast is a form of reinterpret_cast , which throws an exception in case of illegal casting.

Coming back to the question of modeling multiple inheritance, if you have more than one managed class that you need to inherit from, look for the interfaces that they are implementing. You need to inherit your class from all those interfaces and implement all the methods in your class. If this sounds like cheating because inheritance is meant to avoid rework and promote reuse, here is what you can do. You still have to implement all the methods in your class, but you can save on the actual effort by instantiating available classes (intended base classes) and passing function calls to their respective implementations in them.

__pin

The __pin keyword is explained in detail after introducing pointers. It's used for declaring pointers that are similar to __gc pointers and have the additional ability to indicate to the runtime that they should not be moved by the garbage collector during compaction (a process whereby the garbage collector internally moves the objects in the runtime heap so as to consolidate available memory). This is required while passing references from the managed heap to external DLLs, which expect standard C++ pointers (referred to as __nogc pointers in managed extensions).

__property

Data hiding is an important concept of OOP and it's advocated because it can maintain the integrity of your data by hiding the internal representation. The CLR properties provide an alternative approach. You define get_/set_ accessors for a hypothetical data member (not necessarily declared in your class). Now the client code can freely use the property as if referring to an accessible data member, and the calls will be suitably translated to get/set methods depending on the context. Needless to say, you can put whatever you feel like in your implementation of accessor methods rather than simply retrieving and setting the property. Here is an example:

 graphics/icon01.gif __gc struct MyStruct{  private:      int len;  public:      __property int get_Length(){                  return len;      };      __property void set_Length(int l){            len = l;      };      __property int get_Area(){                  return len * len;      };  };  void main(){      MyStruct * pMyStruct = new MyStruct;      pMyStruct->Length = 5;      Console::Write("Area = ");      Console::WriteLine(pMyStruct->Area);  } 

This is an example of Scalar properties. The set method of a scalar property takes a certain data type and the get methods take void and return the same data type. You can selectively implement a get or set property (making it Read only, Write only or Read-Write), and it can have a different access specifier (a public get method and a protected set method, for example). The methods can even be declared virtual (or for that matter, pure virtual). The __property keyword is what differentiates a property from an ordinary method. It can even appear out of the class body, just like an ordinary method.

There is another concept of Indexed properties where the usage syntax is similar to accessing a single or multidimensional array. The get method takes a series of integers and declares a certain data type as its return value. The first parameter of the set method is of that data type, and then it accepts the same number of indexes as the get method. A property returning an array or an array property cannot overload an indexed property. An additional constraint is that a single instance of a property cannot appear both as lvalue and rvalue in an expression.

__sealed

__sealed applies only to __gc classes and structures. The keyword is used to disallow further derivation from this class. __sealed can also be applied to individual methods, which disallows their overriding in a derived class. This keyword can only be applied to virtual methods. The constraints in using the __sealed keyword are as follows:

  1. It cannot be applied to the __abstract class.

  2. It cannot be applied to the __gc interface.

  3. Use of this keyword on the __value class is redundant (they are implicitly sealed).

__typeof

__typeof provides a syntactic and convenient access to the functionality of the System::Type::GetType() method. __typeof can take an abstract declarator as an argument and hence does not require an object to be created. The argument to this can be of managed type and it returns System::Type *:

 graphics/icon01.gif __gc class MyClass{};  void main()  {      MyClass *pMyClass = new MyClass ;      Type * pType1 = pMyClass->GetType();      Type * pType2 = __typeof (MyClass);  } 

System::Type provides much richer information than Runtime Type Information (RTTI), which isn't directly supported in managed extensions.

CUSTOM ATTRIBUTES

Metadata is user extensible. Managed extensions allow users to define their own attributes to embed additional information. This is a strongly typed technique to extend metadata. The modifier looks like a constructor except that it can have named arguments that can be any public field or property. All custom attributes are characterized by inheriting directly from System::Attribute and by the keyword attribute. This makes identifying the attribute in the metadata faster and easier:

 graphics/icon01.gif [ attribute(Class  Method, AllowMultiple = true) ]  __gc class AuthorInfo  // we can as well write "__gc class AuthorInfoAttribute :  public Attribute"  {  public:      AuthorInfo(String * nm)      {            Name = nm;      }      String * Name;      String * Organization;  }; 

Here we have defined one custom attribute AuthorInfo that can be applied to classes and member methods (first parameter to attribute). It stores the name and organization (public fields) of the author. We don't have to explicitly derive from the Attribute class, and we can omit Attribute at the end of our Attribute class (the compiler will put it there for us). This is done to avoid namespace collisions.

The attribute takes three parameters, the first one being a positional parameter, which specifies the usage of our custom attribute. It can take values specified in System::AttributeTargets enum . Additionally it can take two named parameters, AllowMultiple and Inherited . AllowMultiple gets or sets a boolean value that indicates whether one or more instance of the attribute can be specified for a single program. Inherited indicates whether the classes derived from the class in which the attribute is applied can inherit the attribute also.

Here is how we can use the AuthorInfo attribute we created:

 graphics/icon01.gif [AuthorInfo("Bob Joe", Organization="PATNI")]  __gc class MyClass  {      [AuthorInfo("Bob Joe")]      void MyMethod1(int i)      {};  }; 

Operator overloading

The operator overloading mechanism in managed C++ is different from that in unmanaged C++. We write a static method for overloading operators rather than using the operator keyword. Once overloaded, these operators can be used in their natural syntax:

 graphics/icon01.gif __value class CMyClass  {  private:      int m_nValue;  public:      __property int get_Value(){ return m_nValue;}      __property void set_Value(int n){ m_nValue = n;}  // Defining method op_Addition for overloading + operator      static CMyClass op_Addition(CMyClass LHS, CMyClass RHS)      {            CMyClass MyObj;            MyObj.Value = LHS.Value + RHS.Value;            return MyObj;      }  }; 

Note that the methods overloading any operator must be declared static, as opposed to standard C++ syntax for operator overloading:

 graphics/icon01.gif CMyClass o1, o2, o3;  o1.Value = 10;  o2.Value = 20;  o3 = o1 + o2;  Console::WriteLine(o3.Value);  o1 = CMyClass::op_Addition(o2, o3);  Console::WriteLine(o1.Value); 

Managed extensions don't support the explicit keyword for constructors, but you can define convert-from operators, which can be declared as op_Explicit . This will restrict usage of the unary constructor for implicit conversion from other CLS-compliant languages (it can still be used implicitly from within managed C++).

String literal

Managed extensions introduce a new string literal with the prefix S that is of the type System::String *. The S-prefixed string literal provides better performance than the standard C++ string literals in managed code. However, a standard C++ L-prefixed string can be assigned to a variable of type String*:

 String * str1 = S"Hello";  Str1 = L"World"; 

All instances of identical S string literals always refer to the same object but all L-prefixed string literals use an overloaded System::String constructor that creates a new String * object on the CLR heap and copies the unmanaged character array:

 graphics/icon01.gif String *s1 = S"Hello World!";  String *s2 = S"Hello World!";  String *l1 = L"Hello World!"; 

If s1 and s2 are now compared (s1==s2), they will evaluate to True, indicating that the two string literals refer to the same object. However, a comparison of s1 and l1 (s1==l1) will result in False, showing that a string literal of S type and a string literal of L type are different objects even if their contents are same.

Two adjacent string literals are concatenated by the C++ preprocessor. This is advantageous in situations where the string is very long and cannot fit in one single line:

 graphics/icon01.gif String *s1 = S"Hello world, how are you. "                  S" This is a really long string literal;"                  S" hence it's spread over many lines." ;  Console::WriteLine(s1); 

This produces the following output:

 Hello world, how are you.  This is a really long string lit- eral; hence it's spread over many lines. 
OBJECT CONSTRUCTION/DESTRUCTION

In managed extensions, objects are created much the same way as in standard C++, except that they are created on a managed heap and you don't have the responsibility of explicitly deleting them. Classes can contain a static constructor for initializing its static fields. The order of invocation of static constructors is undefined; hence, you should not access any static data from one class in the static constructor of another class. It's guaranteed that your static variables will be properly created and initialized before the execution of your program starts.

Static constructor fields must be assigned at the point of their declaration. One notable difference is in the process of creating a derived class object when the base class constructor calls a virtual method that has been overridden by a derived class. If involved classes are standard C++ classes ( __nogc ), the constructor of the base class will invoke the virtual method implemented in the base class itself (the derived class isn't constructed yet). However, in the case of __gc classes, the base class constructor will invoke the derived class implementation. This is illustrated in the following code:

 graphics/icon01.gif #using <mscorlib.dll>  using namespace System;  class UnmanagedBase{  public:      UnmanagedBase()   {Init();    }      virtual void Init()     {            Console::WriteLine("UnmanagedBase::Init");      }  };  class UnmanagedDerived : public UnmanagedBase{  public:      UnmanagedDerived()      {}      void Init() {            Console::WriteLine("UnmanagedDerived::Init");      }  };  __gc class ManagedBase{  public:      ManagedBase()     {Init();    }      virtual void Init()     {            Console::WriteLine("ManagedBase::Init");      }  };  __gc class ManagedDerived : public ManagedBase{  public:      ManagedDerived()  {}      void Init() {            Console::WriteLine("ManagedDerived::Init");      }  };  main()  {      UnmanagedBase * pUnmngd  = new UnmanagedDerived;      ManagedBase   * pManaged = new ManagedDerived;  } 

The output of this code is as follows:

 UnmanagedBase::Init  ManagedDerived::Init 

Memory leaks are the most worrisome aspect of C and C++, and Microsoft has tried to overcome this in managed extensions. Objects created in a managed heap are garbage collected when their reference count goes to zero (stack variables are deleted anyway when they go out of scope). Here is a quick overview of the garbage collection mechanism in managed extensions.

Garbage collection works on the simple principle that the chances of an object being referred to in the future is directly proportional to its age (how long it had been active in memory). Also, running the garbage collector on a small part of a managed heap is going to be faster than running it on the whole heap every time some memory is required. Based on these principles, the managed heap is broken into three parts , known as generations.

New objects are always created in Generation 0, and they are promoted to older generations as they grow older and still newer objects require accommodation in Generation 0. When Generation 0 is exhausted, the garbage collector runs on it to delete the unreferenced objects in it and pushes the objects up by one generation (to Generation 1) if they are still being referenced. Similarly, when Generation 1 is totally used up and can't accommodate more objects, the garbage collector runs on both Generation 0 and 1. Generation 3 accommodates the oldest objects and is the largest in terms of size .

The garbage collector does offer you some control on itself ( please refer to System::GC documentation). You can find the generation of an object by calling the GC::GetGeneration method or force the garbage collector to run by calling GC::Collect . The Collect() method is overloaded and allows you to specify the generation on which you want the garbage collector to run. Calling GC.Collect(2) will cause it to collect all three generations.

Finalization

Because the garbage collector determines the lifetime of a managed object, it doesn't support the semantics of a destructor. You may insist on providing a destructor for your class, but it'll be called only when an object is being garbage collected; hence, you cannot rely on it for effective cleanup and there is no guarantee that it'll be called at all. Your application may well terminate without ever facing any memory crunch, and thus your object may never actually be garbage collected.

As an experienced C++ programmer, you want to enable users of your class to explicitly release a resource whenever they are done with it. And you can never rely on the users to always remember to free the resources; hence, a fallback system is pretty much required for the cases where they forget to release the resources.

You can call delete (or explicitly invoke a destructor) on a managed object if (and only if) you have defined a destructor for the class. The user-defined destructor will be interpreted by the compiler as a finalize method and a call to GC.SuppressFinalize from within your destructor (so that the garbage collector doesn't invoke the finalize method when it finally collects the object):

 graphics/icon01.gif __gc class MyClass{  public:      ~MyClass(){  // release the resources acquired  }  } 

This is interpreted as

 graphics/icon01.gif __gc class MyCLass{  void Finalize(){      // release the resources acquired  }      ~MyClass(){            System::GC::SuppressFinalize();            Finalize();      }  } 

This neat approach ensures that cleanup code will be called, at the most once, whether explicitly by the user or by the garbage collector. A standard way to achieve the same result is by inheriting your managed class from IDisposable , overriding the Dispose method, and including a call to it in the user-defined destructor definition. Now you can call the Dispose method whenever you wish to explicitly release the resources, failing which they'll be automatically released when collected by the garbage collector. You have to remember to call GC::SuppressFinalize from within Dispose method.

__gc pointers

The CLR implements garbage collection on the managed heap. To work correctly, all storage locations that can point into this heap at runtime must be tracked. __gc pointers are introduced for this purpose because it is not possible for C++ pointers to track so precisely. __gc pointers are those whose variables are known to the CLR garbage collector.

__gc pointers can be confusing; hence, we'll try to keep our discussion very simple. Pointers to primitive C++ types are __nogc ; whereas pointers to __gc classes and structures are __gc pointers by default. Pointers to __value types will depend on their location.

The __gc and __nogc keywords are used to explicitly specify whether a pointer can or cannot point to the CLR heap. All __gc pointers are zero-initialized by the compiler at runtime before the user program or garbage collector can access them.

After having successfully declared and initialized these pointers, we can now shift our attention to pointer assignments. We can have __nogc pointers in managed classes to wrap unmanaged classes. A __gc pointer can refer to any location outside the runtime heap, and the garbage collector will properly deal with this situation. However, because the garbage collector can move around __gc pointers themselves during compaction, they cannot be converted to __nogc pointers unless they are pinned (using __pin ).

Referring to managed objects from the C++ heap has its own hazards. Declaring a __gc pointer type in an unmanaged class is illegal, but we can use a type-safe wrapper template, gcroot<> , declared in vcclr.h to access managed code from there. The gcroot<> is implemented using the value class System::Runtime::InteropServices::GCHandle and provides handles into the garbage collected heap. The handles are freed in the destructor of gcroot<> ; hence, we must have a robust memory management of such unmanaged types to avoid dangling references in the CLR.

Pinning Pointers

__gc pointers cannot be directly passed to the unmanaged code because the garbage collector moves the objects in the managed heap (garbage collection followed by compaction). This might make a passed reference invalid. When we want an object to remain in its position for a certain operation, we may request the runtime to do so by making a pinned reference to that object. This is the only way to convert a __gc pointer to a __nogc pointer and is achieved as follows:

 graphics/icon01.gif __gc class CmyClass {  public:  int m_MyValue;  };  void MyMethod(int * pNum){}  // Declare a pinning pointer and assign a new object to it.  //This will ensure that this object  // isn't moved around (by garbage collector) as long as  //there is a valid pinned reference to it.  CMyClass __pin * pMyObj1 = new CMyClass;  MyMethod(&pMyObj1->m_MyValue);// valid  // Creating a new object and adding a pinned reference to it.  //This will freeze the object inmemory  CMyClass * pMyObj2 = new CMyClass;  pMyObj1 = pMyObj2;  MyMethod(&pMyObj1->m_MyValue);  // now pMyObj2 can freely be moved by garbage colector  pMyObj1 = 0; 

The __pin keyword can be used only in variable declaration and not in a cast expression. The primary advantage of pinning is its ability to pass a __gc pointer to unmanaged functions in external DLLs.


Snoops

   
Top


Migrating to. NET. A Pragmatic Path to Visual Basic. NET, Visual C++. NET, and ASP. NET
Migrating to. NET. A Pragmatic Path to Visual Basic. NET, Visual C++. NET, and ASP. NET
ISBN: 131009621
EAN: N/A
Year: 2001
Pages: 149

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net