Migrating an Existing C Class to .NET Framework

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 9.   Migrating to Visual C++ .NET


Migrating an Existing C++ Class to .NET Framework

We learned the various programming constructs of managed extensions in the last chapter. Let's put everything in perspective by developing a utility class in managed C++. There are a couple of C++ features that are not directly supported in managed C++ (unions, copy constructor, default arguments, and function pointers, to name a few). In this section we'll first develop a C++ class-implementing dynamic array that takes care of memory allocation and deallocation. It'll have multiple constructors and various accessor methods to provide the basic functionality. Let's start by discussing the design goals of our class.

We are going to call our class Vector and implement it as a template class ( template<class T> class Vector {} ) so that it can be useful for all the data types. Internally it'll contain a pointer to the specified data type ( T * m_pHead ). The users of this class can specify its initial size ( m_nCurrCapacity ) and by how much the capacity should be incremented when initial allocated space is all used up ( m_nIncrement ). Also, it'll keep track of the number of elements currently stored in the vector ( m_nCurrLength ). The first constructor takes an integer as the capacity of the vector and has a default value of 10. This constructor is defined as explicit . The second constructor lets the user specify capacity and increment step. Because this class does dynamic memory allocation (contains embedded pointer members ), we shall be writing a copy constructor, assignment operator, and virtual destructor as well.

Elements can be added to this vector by calling the Append() method. There is an InsertAt() method, which right-shifts the elements at or after the specified index and then copies the new element at the specified position. It takes care of available memory and allocates more space as and when required. Also, there is one Replace() method, which simply overwrites the new element at the specified index.

There are two methods, GetLast() and RemoveObjectByIndex() , that retrieve an element. Both these methods remove the retrieved element from vector. Elements are always accessed and inserted by zero-based indices.

Additionally, there is one Reset() method, which doesn't destroy the vector object itself but frees all the memory pointed to by m_pHead and resets the current length and capacity. Here is the code for our implementation of the vector template class. Of course there could be many more methods but the idea here is to demonstrate the use of various C++ constructs and then migrate them to managed extensions. This example can serve as a good example to explore various migration issues and show how to implement the similar functionality. Here is the code for C++ template class; it is in the directory Migrating_Unmanaged_Class/VC6ConsoleApp :

 graphics/icon01.gif //  VC6ConsoleApp.h :  #if !defined VC6_CONSOLE_APP  #define VC6_CONSOLE_APP  template <class T>  class Vector {      private:            // methods            // variables      protected:            // methods            // variables            T * m_pHead;            int m_nCurrCapacity;            int m_nCurrLength;            int m_nIncrement;      public:            // c'tor            explicit Vector(int nCapacity = 10);            Vector(int, int);            Vector(const Vector& rhs);            virtual ~Vector();            // Operators            Vector & operator=(const Vector& rhs);            // methods            int GetCapacity() {return m_nCurrCapacity;      }            int GetLength()         {return m_nCurrLength;  }            virtual void Append(T t1);            virtual void InsertAt(int nIndex, T t1);            virtual void Replace(int nIndex, T t1);            virtual T *  GetLast();            virtual T *  RemoveObjectByIndex(int);            virtual void Reset();            virtual void Display();            // variables  }; // template class Stack  #endif  //VC6ConsoleApp.cpp:  #include "stdafx.h"  #include "VC6ConsoleApp.h"  #include "iostream.h"  enum Exception {INVALID_INDEX_EXCEPTION=1,                INVALID_CAPACITY_EXCEPTION,                INVALID_INCREMENT_EXCEPTION };  template<class T>  Vector<T>::Vector(int nCapacity)  {      if (nCapacity < 0)            throw INVALID_CAPACITY_EXCEPTION;      m_pHead = new T[nCapacity];      m_nCurrCapacity = nCapacity;      m_nCurrLength = 0;      m_nIncrement = 10;  }  template<class T>  Vector<T>::Vector(int nCapacity, int nIncrement)  {      if (nCapacity < 0)            throw INVALID_CAPACITY_EXCEPTION;      if (nIncrement <= 0)            throw INVALID_INCREMENT_EXCEPTION;      m_pHead = new T[nCapacity];      m_nCurrCapacity = nCapacity;      m_nIncrement = nIncrement;      m_nCurrLength = 0;  }  // Destructor  template<class T>  Vector<T>::~Vector()  {      for(int i=0; i<m_nCurrLength; i++)            delete m_pHead[i];      delete [] m_pHead;  }  template<class T>  Vector<T>::Vector(const Vector<T>& rhs)  {      this->m_nCurrCapacity   = rhs.m_nCurrCapacity;      this->m_nCurrLength           = rhs.m_nCurrLength;      this->m_nIncrement            = rhs.m_nIncrement;      m_pHead = new T[m_nCurrCapacity];      for(int i=0; i<m_nCurrLength; i++)            this->m_pHead[i] = rhs.m_pHead[i];  } // copy c'tor  template<class T>  Vector<T>& Vector<T>::operator=(const Vector<T>& rhs)  {      // Check for self copy      if (this == &rhs)            return *this;      delete [] m_pHead;      this->m_nCurrCapacity   = rhs.m_nCurrCapacity;      this->m_nCurrLength           = rhs.m_nCurrLength;      this->m_nIncrement            = rhs.m_nIncrement;      m_pHead = new T[m_nCurrCapacity];      for(int i=0; i<m_nCurrLength; i++)            this->m_pHead[i] = rhs.m_pHead[i];      return *this;  } // Assignment operator  template<class T>  void Vector<T>::Append(T t1)  {      InsertAt(m_nCurrLength, t1);  } // Vector::Append()  template<class T>  void Vector<T>::InsertAt(int nIndex, T t1)  {      if(nIndex < 0)            throw INVALID_INDEX_EXCEPTION;      if(nIndex > m_nCurrLength)            nIndex = m_nCurrLength;      T * pTempOld = m_pHead;      bool bResize = false;      if (m_nCurrLength == m_nCurrCapacity)      {            // Vector full. Allocate some more space            bResize = true;            m_nCurrCapacity += m_nIncrement;            T * pTempNew = new T[m_nCurrCapacity];            for(int i=0; i<nIndex; i++)                  pTempNew[i] = pTempOld[i];            m_pHead = pTempNew;      }      for(int j = m_nCurrLength; j > nIndex; j--)            m_pHead[j] = pTempOld[j-1];      m_pHead[nIndex] = t1;      m_nCurrLength++;      if(bResize)            delete [] pTempOld;  } // Insert  template<class T>  void Vector<T>::Replace(int nIndex, T t1)  {      if( (nIndex < 0)  (nIndex >= m_nCurrLength) )            throw INVALID_INDEX_EXCEPTION;      m_pHead[nIndex] = t1;  } // Insert  template<class T>  T * Vector<T>::GetLast()  {      return RemoveObjectByIndex(m_nCurrLength-1);  } // Vector::GetLast()  // It takes zero-based index.  template<class T>  T * Vector<T>::RemoveObjectByIndex(int nIndex)  {        if ( (nIndex < 0)  (nIndex >= m_nCurrLength) )            throw INVALID_INDEX_EXCEPTION;      if (m_nCurrLength == 0)      {            cout << "Vector is EMPTY for now." << endl;            return NULL;      }      T * pObj = new T;      *pObj = m_pHead[nIndex];  // left shifts elements from nIndex+1 till m_nCurrLength-1,      for(int i=nIndex; i < m_nCurrLength-1; i++)            m_pHead[i] = m_pHead[i+1];      m_nCurrLength--;      //check if the capacity far exceeds current requirement      if (m_nCurrLength < ( 2 * m_nCurrCapacity / 3) )      {            // Shrink the vector            m_nCurrCapacity = m_nCurrLength + 5;            T * pTempNew = new T[m_nCurrCapacity];            for(int i=0; i<m_nCurrLength; i++)                  pTempNew[i] = m_pHead[i];            T * pTempOld = m_pHead;            m_pHead = pTempNew;            delete [] pTempOld;      }      return pObj;  } // Vector<T>::RemoveObjectByIndex(int)  template<class T>  void Vector<T>::Display()  {      for(int i=0; i<m_nCurrLength; i++)            cout << m_pHead[i];      cout << endl;  }  template<class T>  void Vector<T>::Reset()  {      for(int i=0; i<m_nCurrLength; i++)            delete m_pHead[i];      delete[] m_pHead;      m_pHead = NULL;      m_nCurrLength = 0;      m_nCurrCapacity = 0;  } 

There is an additional Display() method that displays the contents of all the elements in this vector. Of course, the instantiating type of our template class must have an overridden operator << to exploit this function. The preceding class touches upon templates, multiple constructors, default function parameters, copy constructor and assignment operator, virtual methods and destructor, enum, and exceptions. Additionally there is a heavy dose of memory management. This should serve as the perfect example for migration. Now let's discuss how to go about achieving the similar functionality using managed extensions.

Migrating a Template Class

The first issue to confront is that managed C++ doesn't support templates (i.e., __gc and template keywords cannot be used for the same class). Here the CTS comes to our rescue. Object is the root of all __gc classes; hence we'll write our Vector class in terms of Object * :

 graphics/icon01.gif __gc class Vector  {  protected:  // Variables      Object * m_pHead[];      // A managed array of Object __gc * pointers  .  .  .  }; 

Templates give us type safety by ensuring that all elements in our vector are of the same type. Because Object is the root of all __gc classes, we can potentially pass any class to our managed vector. To ensure type safety, we must explicitly check the type of elements before inserting them. Let us add another data member, Type * m_pType , which is initialized as soon as the first element is added to our vector. This is the type of our first element and we must compare the type of any element with this type before putting it in our managed vector:

 graphics/icon01.gif __gc class Vector  {  protected:  // Variables      Object * m_pHead[];  // A managed array of Object __gc * pointers      Type   * m_pType;  // TYPE of objects stored in this vector  .  .  };  void Vector::InsertAt(int nIndex, Object* pT)  {      // Do error checking here      .      if (this is first element being inserted)      {            m_pType = pT->GetType();      }      else      {            if (!m_pType->Equals(pT->GetType()))                  throw new CIncompatiableTypeException;      }      .      .  } // This method inserts a new element at the specified position 

Migrating Overloaded Constructors

Our first constructor in the unmanaged class takes a default parameter, which is not supported in managed extensions. The way out is to use an overloading constructor. In managed C++, this unmanaged constructor will be implemented as two separate constructors:

 graphics/icon01.gif __gc class Vector  {  protected:  // Variables      Object * m_pHead[];  // A managed array of Object __gc * pointers      Type   * m_pType;  // TYPE of objects stored in this vector.  Public:  // Variables  // Methods  // Constructors      Vector();         // No-arg c'tor      Vector(int);      // Single argument c'tor      Vector(int, int);  .  .  }; 

This means that we have to write a total of three constructors in managed C++. To avoid duplication of construction code, we should write one Init() method and call it from all constructors:

 graphics/icon01.gif // No-arg constructor  Vector::Vector()      {      Init(10, 10);  }  // single argument constructor  Vector::Vector(int nCapacity)    {      if(nCapacity < 0)            throw new CInvalidCapacityException;      Init(nCapacity, 10);  }  // Same as in unmanaged class  Vector::Vector(int nCapacity, int nIncrement) {      if(nCapacity < 0)            throw new CinvalidCapacityException;      if(nIncrement < 0)            throw new CinvalidIncrementException;      Init(nCapacity, nIncrement);  }  void Vector::Init(int nCapacity, int nIncrement)    {      m_nCurrCapacity = nCapacity;      m_nIncrement = nIncrement;      m_nCurrLength = 0;  m_pHead = new Object __gc * [m_nCurrCapacity];  } 

In case you haven't noticed, we have safely ignored the explicit keyword for the constructor. A managed object can only be created explicitly, by using managed new .

Supporting Copy Constructor and Assignment Operator

The next bottleneck comes in supporting the copy constructor and assignment operator. Managed extensions doesn't support automatic copy construction. To support this, we can write a method that creates a new vector, properly initializes it, and returns. Later we can explicitly call this method to create the copy of a vector whenever we need it. The .NET Framework approach for this is to publicly inherit from an interface and provide an implementation of Clone() :

 graphics/icon01.gif // Copy constructor : Implemented as ICloneable::Clone  // The class of objects stored in this Vector must im//plement  ICloneable.  Object* Vector::Clone()  {      // Makes sense to clone only if it has nonzero elements      if (m_nCurrLength <= 0)            return new Vector(m_nCurrCapacity, m_nIncrement);      // Non zero elements.      Vector *pVector = new Vector;      pVector->m_nCurrCapacity = this->m_nCurrCapacity;      pVector->m_nCurrLength   = this->m_nCurrLength;      pVector->m_nIncrement    = this->m_nIncrement;  pVector->m_pHead = new Object __gc * [m_nCurrCapacity];  // Using reflection to invoke the Clone method on each  //element. If the elements are not cloneable, it'll  //throw System::MissingMethodException  BindingFlags BFlag = (BindingFlags)  (BindingFlags::Public  BindingFlags::InvokeMethod  Binding- Flags::Instance);      for(int i=0; i < m_nCurrLength; i++)            pVector->m_pHead[i] =  m_pType->InvokeMember("Clone", BFlag, NULL, m_pHead[i], NULL);      return pVector;  } 

In this code, we are using reflection to invoke the Clone() method on each element stored in the vector. For the cloning to succeed, the elements in our vector themselves must be cloneable. This somewhat restricts the utility of our class to only those types that publicly inherit from the ICloneable interface. This is not a big restriction, though, because any well-written managed class should support cloning. For the value types or such classes that don't inherit from ICloneable , we can write a wrapper around them and support cloning.

For now our Vector class will only support the ICloneable -derived types. When inserting any element in the vector, we must check to see if it supports cloning. In fact we need to check it only for the first element because we are ensuring that all the elements in our vector are of the same type. Let us modify our InsertAt() method to include this check:

 graphics/icon01.gif void Vector::InsertAt(int nIndex, Object* pT)  {      // Do error checking here      .      if (this is first element being inserted)      {  // First element. Check if it's cloneable.  if (dynamic_cast<ICloneable *>(pT) == NULL)                  throw new CinvalidDatatypeException;      m_pType = pT->GetType();      }      else      {            if (!m_pType->Equals(pT->GetType()))                  throw new CIncompatiableTypeException;      }      .      .  } // This method inserts a new element at the specified position 

Next we come to the implementation of ICloneable::Clone in our Vector class. We can be sure that elements in our vector are cloneable (they won't get added to our vector otherwise , throwing CInvalidDatatypeException ). So we create a new instance of Vector , allocate required memory space and start populating it with the clones of all the elements in original vector (this vector). Here is another hitch. Our Vector class is implemented in terms of Object * , and Object class, being the root of all managed classes, doesn't inherit from ICloneable . Hence, we can't directly call the Clone() method on the elements even though we know that they support cloning. This is why we have used reflection to invoke Clone() method on each element. Reflection is used again while retrieving any element from the vector. We create a copy, using Clone() , before returning it.

Migrating Accessor (Get/Set) Methods

Managed C++ provides an easier and more intuitive way of implementing accessor methods, in terms of properties. Properties behave as if they are public (or protected/public, if you so define) data members of your class, but attempts to access them result in a function call. Therefore we provide separate get_ and set_ methods (preceded by the keyword __property ) that are invoked depending on whether the used property is an lvalue or rvalue in the given expression. We can migrate the GetCapacity() and GetLength() methods in the following manner:

 graphics/icon01.gif __gc class Vector : public ICloneable  {      .  public:      //methods      __property int get_Capacity(){return m_nCurrCapacity;}      __property int get_Length() {return m_nCurrLength;)      .      .  }; // managed c++ implementation of class Vector 

This is pretty much all you need to do to migrate the Vector class to managed C++. Unmanaged code requires very careful memory management. Failure to explicitly delete objects when no longer needed results in memory leaks; hence, you have to carefully delete them before references to such objects are lost. Managed code takes care of reclaiming the memory of unreferenced objects. Therefore our responsibility translates to making sure that an object is no longer referenced anywhere if it's not needed any longer. This means explicitly setting to null all references to an object that is not likely to be used again. This is exactly what we are doing in the Reset() method (contrast it with its implementation in unmanaged C++). An unmanaged C++ implementation of Reset is shown in the following code:

 graphics/icon01.gif template<class T>  void Vector<T>::Reset(){      for(int i=0; i<m_nCurrLength; i++)            delete m_pHead[i];      delete[] m_pHead;      m_pHead = NULL;      m_nCurrLength = 0;      m_nCurrCapacity = 0;  } 

The managed C++ implementation of Reset is as follows :

 graphics/icon01.gif void Vector::Reset(){      for(int i=0; i<m_nCurrLength; i++)            m_pHead[i] = NULL;      m_pHead = NULL;      m_pType = NULL;      m_nCurrLength = 0;      m_nCurrCapacity = 0;  } 

So far we haven't talked about implementing the destructor. As our final responsibility we might want to make sure that all memory used by a vector is freed as soon as we are done with it. This is really not needed because the garbage collector will reclaim the unreferenced memory whenever our application requires more memory. We can write a destructor that will be called by the garbage collector, thereby giving our implementation a chance to clean up and release acquired resources before destroying the vector object. But we have nothing worthwhile to do in the destructor of our Vector class. Note that we cannot even attempt to do in the destructor whatever we are doing in the Reset() method. The garbage collector gives no guarantee about the sequence in which an unreferenced object will be collected. What this means is that m_pHead itself might be an invalid pointer by the time the destructor is invoked by the garbage collector. It's an error to attempt to dereference the managed pointers inside a destructor because the referred objects might have already been garbage collected. The following is a trivial implementation of the destructor:

 graphics/icon01.gif Vector::~Vector(){  //Never dereference internal managed pointers from destructor.  //The commented code is error prone.  //  for(int i=0; i<m_nCurrLength; i++)  //        m_pHead[i] = NULL;      m_pHead = NULL;      m_pType = NULL;  } 

For all practical purposes, we discharge our duties responsibly if we just remember to call the Reset() function on a vector that we are not going to use again. In the Reset() function, we are marking all the elements as ready for garbage collection (by setting their referents to null). At this point we must avoid the overhead of finalization (garbage collector-initiated process before destroying an object). Finalization is a costly operation; hence, providing user-defined destructors is discouraged. In the situations where explicitly providing the destructor is necessary (for example, if our class acquires some resources, it makes sense to free them in the destructor), we can still bypass the finalization process by implementing a method similar to Reset() and suppressing the finalization from within it. In this case, users who call Reset() can explicitly release the resources at will and can the supress finalization. If they fail to call the Reset() method, the destructor will be invoked and do the cleanup. The code duplication can be avoided by embedding a call to Reset() in our destructor.

 graphics/icon01.gif void Vector::Reset()  {      for(int i=0; i<m_nCurrLength; i++)            m_pHead[i] = NULL;      m_pHead = NULL;      m_pType = NULL;      m_nCurrLength = 0;      m_nCurrCapacity = 0;      // Let the increment be there      GC::SuppressFinalize(this);  } 

There is a small catch here though. Our vector object is very much alive even after we have invoked Reset() on it. We can still call Append() and InsertAt() methods on it. The worrying aspect is the ability to add more elements to the vector after it has been taken out of finalization queue (remember GC::SupressFinalize() ). Hence, we must make sure that it's garbage collected if the vector is used after calling Reset() on it. This process is called resurrection. Let's modify our InsertAt() method to put our vector object in the finalization queue when the first element is added to it:

 graphics/icon01.gif void Vector::InsertAt(int nIndex, Object* pT)  {      if(nIndex < 0)            throw new CInvalidIndexException;      // We can insert objects of the same type only      if (m_nCurrLength == 0)      {            // First element. Check if it's cloneable.  if (dynamic_cast<ICloneable *>(pT) == NULL)                  throw new CInvalidDatatypeException;            m_pType = pT->GetType();            GC::ReRegisterForFinalize(this)      }     else      {            if (!m_pType->Equals(pT->GetType()))                  throw new CIncompatiableTypeException;      }  .  .  } // Insert 

This will make sure that our vector is always in the finalization queue if it has any element in it.

Our analysis has focused on how to migrate a C++ class to managed extensions. The tips and tricks discussed are important steps to guide you through the process. If you have found our Reset() method useful, note that this is the standard way to implement user-defined destructors while avoiding the finalization overhead. The .NET way to implement this is deriving your managed class from the IDisposable interface and implementing the Dispose() method. The situation becomes little tricky when a class owns both managed and unmanaged resources. Special considerations are required for proper cleanup with a fallback system. We'll be learning a design pattern for defensive object destruction in the next section. Here is the full code of our migrated Vector class. It is in the directory Migrating_Unmanaged_Class/ ManagedExtentionsApp :

 graphics/icon01.gif  //VECTOR.H :  #using <mscorlib.dll>  using namespace System;  #pragma once  __gc class Vector : public ICloneable  {  private:      //methods      //variables  protected:      //method      //variables      Object * m_pHead[];      int m_nCurrCapacity;      int m_nCurrLength;      int m_nIncrement;      Type * m_pType;  public:      //constructor / destructor      Vector ();      Vector(int  nCapacity);      Vector(int, int);      ~Vector();      //For copy constructor or Assignment      virtual Object* Clone();      //operators      //methods      void Init(int, int);      __property int get_Capacity(){return m_nCurrCapacity;}      __property int get_Length() {return m_nCurrLength;}      virtual void Append(Object* t1);  virtual void InsertAt(int nIndex, Object* t1);  virtual void Replace(int nIndex, Object* t1);      virtual Object* GetLast();  virtual Object* RemoveObjectByIndex(int nIndex);      virtual void Reset();      virtual void Display();  };  __gc class CInvalidIndexException : public Exception  {   };  __gc class CInvalidCapacityException : public Exception  {   };  __gc class CInvalidIncrementException : public Exception  {   };  __gc class CIncompatiableTypeException : public Exception  {   };  __gc class CCloningNotSupportedException : public Exception  {   };  __gc class CInvalidDatatypeException : public Exception  {   };  //VECTOR.CPP :  #include "StdAfx.h"  #include "vector.h"  #using <mscorlib.dll>  using namespace System;  using namespace System::Reflection;  __gc class A : public ICloneable  {  public:      A()   {     m_pObj = new Object;    }      A(Object * pObj)  { m_pObj = pObj;  }      virtual Object * Clone()      {            A * pTempObj = new A(m_pObj);            return pTempObj;      }  //protected:      Object * m_pObj;  };  void main()  {      Vector * pVec = new Vector;      int nNum = 10;      A * pA = new A(__box (nNum));      pVec->Append(pA);      pVec->Clone();      pVec->Reset();      pVec->Append(pA);  /*  int nNum = 10;      A * pTemp =  new A(__box (nNum));      A * pTemp1 = (A*) pTemp->Clone();      nNum = 20;      pTemp->m_pObj = __box (nNum);      Console::WriteLine(pTemp->m_pObj->ToString());      Console::WriteLine(pTemp1->m_pObj->ToString());  */  }  // No-arg constructor  Vector::Vector()      {      Init(10, 10);  }  // single argument constructor  Vector::Vector(int nCapacity)     {      if(nCapacity < 0)            throw new CInvalidCapacityException;      Init(nCapacity, 10);  }  // Same as in unmanaged class  Vector::Vector(int nCapacity, int nIncrement) {      if(nCapacity < 0)            throw new CInvalidCapacityException;      if(nIncrement < 0)            throw new CInvalidIncrementException;      Init(nCapacity, nIncrement);  }  void Vector::Init(int nCapacity, int nIncrement)    {      m_nCurrCapacity = nCapacity;      m_nIncrement = nIncrement;      m_nCurrLength = 0;  m_pHead = new Object __gc * [m_nCurrCapacity];  }  Vector::~Vector()  {      m_pHead = NULL;      m_pType = NULL;      Console::WriteLine("Destructor called");  }  // Copy constructor  // Implemented as ICloneable::Clone  // The class of objects stored in this Vector must implement  //ICloneable.  Object* Vector::Clone()  {      // Makes sense to clone only if it has nonzero elements      if (m_nCurrLength <= 0)            return new Vector(m_nCurrCapacity, m_nIncrement);      // Nonzero elements. We know that they are cloneable.      Vector *pVector = new Vector;      pVector->m_nCurrCapacity = this->m_nCurrCapacity;      pVector->m_nCurrLength   = this->m_nCurrLength;      pVector->m_nIncrement    = this->m_nIncrement;  pVector->m_pHead = new Object __gc * [m_nCurrCapacity];  BindingFlags BFlag = (BindingFlags)  (BindingFlags::Public  BindingFlags::InvokeMethod  Binding- Flags::Instance);      for(int i=0; i < m_nCurrLength; i++)            pVector->m_pHead[i] =  m_pType->InvokeMember("Clone", BFlag, NULL, m_pHead[i], NULL);      return pVector;  }  Object * Vector::GetLast()  {   return RemoveObjectByIndex(m_nCurrLength-1);    }  // It takes zero-based index.  Object * Vector::RemoveObjectByIndex(int nIndex)  {      if ( (nIndex < 0)  (nIndex >= m_nCurrLength) )            throw new CInvalidIndexException;      if (m_nCurrLength == 0)      {            Console::WriteLine("Vector is EMPTY for now.");            return NULL;      }      BindingFlags BFlag = (BindingFlags)  (BindingFlags::Public  BindingFlags::InvokeMethod  Binding- Flags::Instance);      Object * pObj =  m_pType->InvokeMember("Clone", BFlag, NULL, m_pHead[nIndex],  NULL);  // left shifts elements from nIndex+1 till m_nCurrLength-1,      // No need to clone      for(int i=nIndex; i < m_nCurrLength-1; i++)            m_pHead[i] = m_pHead[i+1];      m_nCurrLength--;      // check if the capacity far exceeds current requirement      if (m_nCurrLength < ( 2 * m_nCurrCapacity / 3) )      {            // Shrink the vector            m_nCurrCapacity = m_nCurrLength + 5;  Object* pTempNew[] = new Object __gc* [m_nCurrCapacity];            //No cloning needed here.            for(int i=0; i<m_nCurrLength; i++)                  pTempNew[i] = m_pHead[i];            m_pHead = pTempNew;      }      return pObj;  }  void Vector::Append(Object *pT)  {   InsertAt(m_nCurrLength, pT);}  void Vector::InsertAt(int nIndex, Object* pT)  {      if(nIndex < 0)            throw new CInvalidIndexException;      // We can insert objects of the same type only      if (m_nCurrLength == 0)      {            // First element. Check if it's cloneable.  if (dynamic_cast<ICloneable *>(pT) == NULL)                  throw new CInvalidDatatypeException;            m_pType = pT->GetType();            GC::ReRegisterForFinalize(this);      }      else      {            if (!m_pType->Equals(pT->GetType()))                  throw new CIncompatiableTypeException;      }      if(nIndex > m_nCurrLength)            nIndex = m_nCurrLength;      Object * pTempOld[] = m_pHead;      BindingFlags BFlag = (BindingFlags)  (BindingFlags::Public  BindingFlags::InvokeMethod  Binding- Flags::Instance);      if (m_nCurrLength == m_nCurrCapacity)      {            // Vector full. Allocate some more space            m_nCurrCapacity += m_nIncrement;            Object * pTempNew[];  pTempNew = new Object __gc* [m_nCurrCapacity];            for(int i=0; i < nIndex; i++)            {                  // Cloning not needed.                  pTempNew[i] = pTempOld[i];            }            m_pHead = pTempNew;      }      for(int j = m_nCurrLength; j > nIndex; j--)      {            // Cloning not needed.            m_pHead[j] = pTempOld[j-1];      }      // Clone it  m_pHead[nIndex] = m_pType->InvokeMember("Clone", BFlag, NULL,  pT, NULL);      m_nCurrLength++;      pTempOld = NULL;  }  // Insert  void Vector::Replace(int nIndex, Object* pT)  {      if( (nIndex < 0)  (nIndex >= m_nCurrLength) )            throw new CInvalidIndexException;      // Clone it      if (!m_pType->Equals(pT->GetType()))            throw new CIncompatiableTypeException;      BindingFlags BFlag = (BindingFlags)  (BindingFlags::Public  BindingFlags::InvokeMethod  Binding- Flags::Instance);  m_pHead[nIndex] = m_pType->InvokeMember("Clone", BFlag, NULL,  pT, NULL);  }  void Vector::Display()  {      for(int i=0; i<m_nCurrLength; i++)            Console::WriteLine(m_pHead[i]);  }  void Vector::Reset()  {      for(int i=0; i<m_nCurrLength; i++)            m_pHead[i] = NULL;      m_pHead = NULL;      m_pType = NULL;      m_nCurrLength = 0;      m_nCurrCapacity = 0;      // Let the increment be there      GC::SuppressFinalize(this);  } 

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