Components
Application consists of a single monolithic binary file. Once the application is shipped, it does not change until it is recompiled and shipped again (new version). There has to be a way out to be able to change the application without recompiling and re-shipping. The solution is to break it into components. As new additions are demanded, the older components are replaced by new components.
Traditional Application Architecture :
Files / Modules / Classes -> Compiled -> Linked -> Monolithic Application
Component Architecture: A component is a mini-application that has code already compiled, linked and ready to use. Modifying the application is simply replacing the old component by a new one. One component connects to other at run time an form an application. Thus the application is broken down into separate components connecting to each other at run time and forming the complete application.
The two main conditions that components must satisfy are:-
They should link dynamically. This is essential if we want to support the change in components at run time.
Data hiding. If a component is changed for some reason then it should connect to the other components in the same way as the old one did. If it does not happen then the system has to be recompiled and the whole purpose of using this architecture is lost. A component using other component is called as a client. A client is connected to a component using interface. If there is any change in the client of the component that does not affect the interface, everything works fine, but if the change affects the interface as well then the other side of the interface must change as well. The implementation is hidden from the client.
Components should be language independent and must be able to be written by anyone, in any language.
The application should keep supporting the old version even after a new one is released. It should still be possible to drastically change the behavior of the component while still supporting the old application.
COM components:
Consists of DLLs or EXEs
Are dynamically loaded
Are language independent
Hide data
Are shipped in binary form
Can be upgraded without breaking the old clients.
Can be relocated on the network, work in the same way for a local machine or a network machine.
An interface provides a connection between two different objects. An interface is an array of function pointers. Each array element contains the address of a function that is implemented by the component.
An interface has all pure virtual functions meaning that there are no objects of interfaces. In C++, multiple inheritance is used and the implementation of the pure virtual functions is done in the derived class. Using multiple inheritance, there can be many interfaces adhering to a single component as the definition suggests.
COM interfaces are implemented in C++ as pure abstract classes.
A class is not a component. A COM component can be implemented using several classes. Actually COM does not require C++ classes to implement a component, it just makes it easy to do it this way.
It is not necessary to inherit an interface to be able to implement it. The client does not see what is going on in the background anyway.
An interface is a set of functions, a component is a set of interfaces and the system is a set of components.
The client does not connect to the interface based on its name or the function name but based on the location in the block of memory representing the interface.
Interface never changes. For new additions you write a new interface and publish it. This can be easily done since COM supports multiple interfaces.
A small interface represents single behavior. A large interface represents many behaviors. The bigger the interface, the specialized it gets and it is likely that not many components would be using it.
Polymorphism:
If two different components support the same interface then the client can use the same code to manipulate either of the components. Thus the client can treat these two components polymorphically.
Virtual Function Table:
A pure abstract class is basically a block of memory, which has the same basic layout as that of a COM interface. For this reason can C++ pure abstract classes be used as COM interfaces.
The virtual function table is an array of pointers to the member functions. In short, a pointer to an abstract class points to a virtual table pointer which in turn points to the virtual function table which has pointers to the member functions. Hope you get this right.
CA *pA;
Lets say CA is a pure abstract class and pA is a pointer to it then eventually,
pA->virtual table pointer->virtual table->member functions.
Data members if any for a class sit with the vtbl pointer. The same vtbl is shared for different objects(instances) of the same class. Each instance pointer points to a different vtbl pointer but the table where it points is the same.
Query Interface
A client communicates with the component using interfaces. Even to get another interface that the component has implemented, it has to do it using an interface. All interfaces derive from an interface known as IUnknown. IUnknown has declaration for three methods namely QueryInterface, AddRef and Release.
The client calls the QueryInterface function to see if the component supports an interface. If it does then it can now call the functions declared by the requested interface and implemented by the component.
To get the first interface pointer, i.e. the IUnknown pointer, the following is done;
IUnknown * CreateInstance(); This creates the component and returns a pointer to the IUnknown interface. This pointer can now be used to get other interfaces using QueryInerface()should they be adhering to a particular component.
QueryInterface function takes two argument, one is the IID of the required interface and other is a pointer that it will return of the requested interface if is succeeds. The body of the QueryInterface function is a simple if-then-else statements checking for the matching IID and returning the pointer where the IID matches. If nothing matches it returns E_NOINTERFACE.
Steps in implementation: -
Define as many interfaces you want, derived from IUnknown
Define a class which will be your component. It will have multiple inheritance from interfaces that you want to implement in this class.
Implement all the functions declared in all the interfaces from which this class is derived. Also implement QueryInterface() function.
Define the CreateInstance() function outside the class. This function will be used by the client, which will create an instance of the component and return an IUnknown pointer.
On the client side call the CreateInstance function to get the interface pointer.
Using this pointer call the QueryInterface function passing in the IID of the interface that you want to use. You will get a valid pointer if this interface is implemented by the component.
After getting the pointer you can call the methods declared in this interface.
You can query for another interface using this pointer using QueryInterface.
QueryInterface returns the same pointer in response to all requests for IUnknown.
Every interface has its own unique IID.
Reference Counting
Once a component is created and used, it has to be deleted, if not then it keeps using the valuable memory space. The client has to perform a very complicated procedure to know whether or not there are no interfaces using the component. It has to also find out if more than one interface pointer points to the same object. Hence the best way to do it is to inform the component when the client is done with all the interfaces and the component will take care of its deletion.
The AddRef and Release keep track of the interfaces that the client uses.
Memory Management:
When a client gets an interface, the reference count is incremented using AddRef. When the client is done with the interface, the reference count is decremented using Release. When the reference count goes down to zero, the component deletes itself.
The client does the reference counting whenever it creates another reference on the existing interface.
Call AddRef before the function returns an interface. This includes QueryInterface and CreateInstance functions.
When finished with the interface, Release must be called, to release the memory and decrement the reference counting.
If an interface pointer is assigned to other interface pointer, AddRef must be called. In short, AddRef should be called when another reference of the same interface is created.
Thus, the CreateInstance and QueryInterface will call AddRef function for us before returning. Only Release operation now remains to be done when finished with the interface.
Reference counting is usually done at Component level but it can also be done at Interface level under cases like when the interface uses a lot of memory etc. Under these cases reference counting is individually done for each interface and the interface is deleted when its reference count goes down to zero.
Reference counting is not required if life of an interface is contained in that of the other one. For e.g.
PIUnknown->QueryInterface(IID_IX, (viod**)&pIX);
pIX2=pIX;
pIX2->AddRef();
pIX2->Release();
pIX->Release();
Here the AddRef and Release for pIX2 is not required since the component will not be deleted until pIX is deleted.
Rules for Reference Counting:
An out parameter is a function parameter that returns a value to the function caller and not used by the function itself for other purposes. Any function that returns an interface pointer in an out parameter or return value must do an AddRef.
an in parameter is a function parameter that passes a value to the function that is used by the function. The value is not modified or returned. In this case the lifetime gets nested within the caller’s life and AddRef and Release are not required.
An in-out parameter is both an in and out parameter. The function uses this value, changes it and returns it. AddRef and Release must be called in this case.
AddRef and Release not required for local interface pointers passed as in arguments.
AddRef and Release should be called for an interface pointer stored in a global variable.
When in doubt, AddRef and Release should be called. This is because these two functions are not too expensive but the component not getting deleted at all can be expensive.


