Windows NT Systems Programming: Spring 2000

[ Home | Syllabus | Course Notes | Assignments | Search]


Motivating the Component Object Model

 


What's Wrong with C++

One of the objectives of Object Oriented Programming is the reuse of Objects.

Traditional Compiler/Linker model:

Dynamically Linked Libraries (DLLs):


// Old specification for the object.
class myString {
   public:
      myString(const char* psz);
      //...
      int length() const;
   private:
      char *m_string; // points to string
}

 


When a program defines an instance of this object, the compiler needs to know how much space to allocate. thus
        myString firstName, lastName;
would require at least enough memory to store a machine address (4 bytes with 32 bit addressing)
(ever wonder why the header file included the private data members even though the user does nto need to know this "implementation" detail?)

Presumably in this implementation, you might use strlen to calculate the length of the string. But this function is linear in the length of the string.

Suppose you decide to store the length as a second private data member. there is no change in the public interface and no change in the way the program works, so it seems safe enough to change the header to:

 


// New specification for the object.
class myString {
   public:
      myString(const char* psz);
      //...
      int length() const;
   private:
      char *m_string; // points to string
      int myLength;
}

The new version no requires memory for a pointer and an integer (say 8 bytes),   and the new constructor will initialize both data members.

The problem is that projects compiled using the old header, will only allocate memory for the pointer. Using the new DLL will erroneously overwrite memory when initializing myLength.

Requiring all old projects to recompile is not acceptable.

Could put version names on the DLL to distinguish - but this is ackward.

Problem si that C++ supports source level encapsulation (via objects, private and public) BUT NOT BINARY LEVEL ENCAPSULATION.

 


Separating Interface From Implementation (at the Binary Level)

Define object as tow C++ classes: one for the interface and one for the implementation.

The interface should not contain data members, only prototypes for the public functions.

 


// this is the header file that the project programmer uses.
class myStringInterface {
   class myString; // implementation class - forward reference
   myString *m_pthis; // pointer to actual object (only data member, never changes)
public:
   myStringInterface(const char* psz);
   int length() const;
};
// here is the implementation (in DLL)
myStringInterface::myStringInterface(const char *psz)
{
   m_pthis = new myString(psz);
   assert(m_pthis != NULL); // check if can be allocated
}
myStringInterface::length() const
{
   return m_pthis->length();
}

 


Need a binary compatible layout for

Vtable = table of function addresses - implements polymorphism

Now we can define the interface class as a base class with pure virtual functions that are defined in the implementation class which is derived from the base class, thus


class ImyString { // interface class
   public:
      virtual int length() const = 0; // pure virtual function
};
// now the implementation class 
class myString : public ImyString {
public:
   myString(const char *psz);
   int length() const;
private:
   int myLength;
   char *m_psz;
};

// getting warm - but still need implementation class header
// to create a new object - so add a function to hide this

 

class ImyString { // interface class
public:
   virtual int Length() const = 0; // pure virtual function
   virtual void Delete() = 0; // used to release memory allocated to implementation object
};
extern "C" // to guarantee compiler independence
ImyString *CreateMyString(const char *psz);
// the definition of the last function (in the DLL) would be
ImyString *CreateMyString(const char *psz) {
    return new myString; // need to know how my memory myString requires
// but since we are in DLL - we can always recompile to handle changes in size of
// underlying implementation class myString
// now the implementation class (hidden from project programmer)
class myString : public ImyString {
public:
   myString(const char *psz);
   int Length() const;
   void Delete();
private:
   int myLength;
   char *m_psz;
};
// with the following definitions
myString::myString(const char* psz) {
   myLength = strlen(psz);
   m_psz = new char[myLength + 1];
   strcpy(m_psz, psz);
}
void myString::Delete(){
   delete this;
}
myString::~myString() {
   delete[] m_psz"
}
int myString::Length() const {
   return myLength;
}

 


 


Object Evolution

As objects change over time, need to manage existing projects which were developed using the old interface while allowing new projects to take advantage of newer features available in a updated interface.

Extending old interface is dangerous. new projects loading old DLL may crash.

Basic rule never modify a published interface (immutability of interfaces). Publish a new interface instead.


Copyright chris wild 1999/2000.
For problems or questions regarding this web contact [Dr. Wild].
Last updated: February 28, 2000.