Introduction
In part one, some background information regarding COM technology was explained, and a simple example showed how a client can use a component's functionality through its interface. In this part, I'll guide the reader to separate the implementation of the component from its client such that the client is no longer bound to the component, and is able to create it through the class factory.
Part two-Breaking the chain
Serving components (Distribution)
Software component servers provide a way so that functionality can be reused more easily, besides they reduce memory overhead when several applications use the same functionality at the same time, because although each application gets its own copy of the data, they can share the code. By putting a component into a DLL, it's possible to make a kind of component distribution and the DLL becomes the component's server and will contain the implementations of interfaces supported by the component.
Building of the component's Server (DLL)
In the example, the client and the component were both in the same file, now they should be separated, the client will be in a .exe file, which loads the component into its address space in order to use it, and the component will be served by a DLL. The client should load the DLL into its process and create the component before it can get an interface pointer. If the client links to the CreateInstance()
function in the DLL, all other functions of the component are accessible through an interface pointer. So, the solution is just to export the CreateInstance()
function from the DLL such that the client can link explicitly to it on the fly. The DLL will be built from the command prompt using one of Microsoft�s command-line tools.
The following figure shows the Server files which are going to be used in making the DLL:
Step 1:
Create a source file (Component.cpp) and put the definition and the implementation of the component's class into it.
Step 2:
Export the CreateInstance()
function by adding the following piece of code at the end of the file:
Step 3:
The linker should be informed that the CreateInstance
function will be exported, and that can be done by using a module-definition file. A module-definition file is a file with the "def" extension, which contains information about exports, attributes and other information for linking an .EXE file (which has exports) or DLL. In the .def file, the CreateInstance
function's export ordinal is chosen to be 1. The following part shows the contents of this file.
;component.def
; Component module-definition file
; LIBRARY Component.dll
DESCRIPTION 'Components windows dynamik library'
EXPORTS ; Explicit exports can go here
CreateInstance @1 PRIVATE
Step 4:
The interface identifier and the interface definition should be known for both the client and the component, and they can be put into two separate files, which are shared between the client and the component. Create another source file (GUID.cpp), which can hold the interface ID:
#include "objbase.h"
extern "C"
{
extern const IID IID_IComponent =
{ 0x853b4626, 0x393a, 0x44df,
{ 0xb1, 0x3e, 0x64, 0xca, 0xbe, 0x53, 0x5d, 0xbf } };
}
Step 5:
Create a header file (interface.h) with the following content:
interface IComponent : IUnknown
{
virtual void __stdcall Print(const char* msg) = 0 ;
} ;
extern "C"
{
extern const IID IID_IComponent ;
}
Step 6:
Create a "make" file containing the options for making the DLL with the following content:
#Makefile
################################################################################
# Compiler options:
# /c compile without linking
# CL cl.exe is a 32-bit tool that controls the Microsoft C
# and C++ compilers and linker.
# The compilers produce Common Object File Format
# (COFF) object (.obj) files.
# The linker produces executable (.exe) files
# or dynamic-link libraries (DLLs).
#
##################################
# Linker options:
#
# /DEF Passes a module-definition (.def) file to the linker
# /DEBUG Creates debugging information
# /DLL Builds a DLL
CPP_FLAGS=/c /MTd /Zi /Od /D_DEBUG
EXE_LINK_FLAGS=/DEBUG
DLL_LINK_FLAGS=/DLL /DEBUG
LIBS=UUID.lib
#############################################
# Targets:
# CodeProject is just a pseudotarget
#
CodeProject : component
component : Component.dll
#########################################
# Shared source files:
#
GUID.obj : GUID.cpp
Cl $(CPP_FLAGS) GUID.cpp
##########################################
# Component source files:
#
Component.obj : Component.cpp Interface.h
Cl $(CPP_FLAGS) Component.cpp
########################################
# Link component:
#
Component.dll : Component.obj GUID.obj Component.def
link $(DLL_LINK_FLAGS) Component.obj GUID.obj $(LIBS) /DEF:Component.def
Step 7:
Make the DLL from the Command line by using Microsoft Program Maintenance Utility (NMAKE.EXE). This program is a tool that can build projects based on commands contained in a description file.
- Open the Command window (Click Start choose Run menu item and then write cmd in the dialog box).
- From the Command line, change to the directory which contains the server files.
- From the Command line, enter: nmake /f makefile.
The NMAKE utility will create the DLL in the same folder:
Building the Client
The following figure shows the files which are used in making the Client. The Client will be built within the Visual C++ development environment.
Step 1:
Using the AppWizard, create a simple Win32 Console application and choose an empty project.
Step 2:
Create a new source file (Create.cpp) and make a function which takes the DLL's name as parameter, loads the "DLL" and then calls the exported function CreateInstance()
. The function's return value would be the return value of the CreateInstance()
function, which is an IUnknown
interface pointer. In order to link explicitly to the "DLL", the function calls the GetProcAddress
function to get the address of the exported function. The GetProcAddress
function takes two parameters. The first parameter is a handle to the "DLL" module and the second parameter is the "DLL" name. By calling the LoadLibrary
function, it would be possible to obtain the module handle.
#include "iostream.h"
#include "unknwn.h"//IUnknown definition file.
#include "Create.h"
typedef IUnknown* (*CREATEFUNCPTR)();
IUnknown* CallCreateInstance(char* dllname)
{
HMODULE hm = ::LoadLibrary(dllname);
if (hm ==NULL)
return NULL;
CREATEFUNCPTR Function =
(CREATEFUNCPTR)::GetProcAddress(hm, "CreateInstance");
if (Function == NULL)
return NULL;
return Function();
}
Step 3:
Create a new header file (Create.h) with the following contents:
IUnknown* CallCreateInstance(char* dllname) ;
Step 4:
Create a new header file (interface.h) with the following contents:
interface IComponent : IUnknown
{
virtual void __stdcall Print(const char* msg) = 0 ;
} ;
extern "C"
{
extern const IID IID_IComponent ;
}
Step 5:
Create another source file (GUID.cpp), which can hold the interface ID:
#include "objbase.h"
extern "C"
{
extern const IID IID_IComponent =
{ 0x853b4626, 0x393a, 0x44df,
{ 0xb1, 0x3e, 0x64, 0xca, 0xbe, 0x53, 0x5d, 0xbf } };
}
Step 6:
Create a source file (Client.cpp) and implement the main
function. Call the function made in step 2, in order to instantiate the component and use its methods:
int main()
{
HRESULT hr ;
char dllname[20];
cout << "Enter the filename of component's server [component.dll]:";
cin >> dllname;
...
TRACE("Getting an IUnknown interface pointer...") ;
IUnknown* pIUnknown = CallCreateInstance(dllname) ;
...
IComponent* pIComponent ;
hr = pIUnknown->QueryInterface(IID_IComponent, (void**)&pIComponent);
if (SUCCEEDED(hr))
{
...
pIComponent->Print("COM from scratch.") ;
pIComponent->Release() ;
...
}
...
return 0 ;
}
Step 7:
Put the component's server (Component.dll) into the same directory of the client. Now the client is able to load the DLL into its address space and get the address of the CreateInstance function
by using LoadLibrary
and GetProcAddress
functions. Build and run the client program.
The following screen shot shows the client application, after loading the DLL and calling the component's Print
method:
Conclusion: Distribution of COM components by servers makes it easy for clients to reuse components' functionality.
Extending component's functionality without rebuilding Clients
One of the advantages of COM components is that it is easy to extend functionalities of an application without rebuilding. As long as an interface is not changed, the client application can still use the component, although its functionality is extended by new changes to its methods. In order to show this advantage of COM components, it's better to view the problem of rebuilding of client applications by a simple example. In the following, a DLL is linked to a client application, you may notice that whenever changes are made to the DLL (for example, by adding a new member variable to a class in the DLL and modifying a member function), the client application will fail to run if it is not rebuild.
Step 1: Make the DLL
- Using the AppWizard, create a new project with type Win32 Dynamic Link Library:
- In step two from the wizard, choose 'A simple DLL project' and click Finish:
- Create a new header file and define a class with a member variable and a member function which can be exported from the DLL:
class CMyclass
{
long m_cRef;
public:
_declspec(dllexport) void Print(const char*msg);
};
- Create a source file (myclass.cpp) and implement the member function:
- Build the DLL.
Step 2: Make the Client and load the DLL
- Make a new empty project of type Win32 Console Application.
- Create a new source file in order to load and test the DLL (client.cpp).
- Include the header, which contains the class definition in the DLL:
#include"iostream.h"
#include"..\DLL\myclass.h"
void main()
{
CMyclass classObj;
classObj.Print("COM from scratch.");
}
- Add the DLL.lib file from the DLL project to the Client project (Project->Add to Project->Files, and then choose Library Files (.lib) as the file type):
- Copy the DLL into the same folder of the client's executive file. If you build and run the client application, "COM from scratch." will be written on the screen, and the DLL will be loaded without any problem.
Step 3: Viewing the Rebuild problem
- Return to the implementation of the
CMyclass
class and add a new member variable:
class CMyclass
{
long m_cRef;
int m_i;
public:
_declspec(dllexport) void Print(const char* msg);
};
- Modify the implementation of the
Print
member function:
- Rebuild the DLL and copy it into the same folder of the client's executive file.
- Execute the client application with the new version of the DLL without rebuilding, and if you run the client application, you will confront with a problem, and rebuilding of the client application solves this problem. Rebuilding of client applications is a big problem, because it requires source codes. Now if you make the same changes to the component's class from the example in part one and rebuild the DLL and test it with the client application (without rebuilding it), you will notice that the client runs without any problem, although a new member variable is added to the component's class and the implementation of the
Print
method has been changed. Since method calls in COM components are indirectly and through their interfaces, there will not be any problem if the methods are modified.
Conclusion: COM extends functionalities of applications without rebuilding.
Improvement of the example
In the example, although the client and the component have been separated, the client is closely related to the component's implementation and should know about the DLL's name, and changing the DLL's name will affect the client. An improvement is that we can move the component from one DLL to another or to other directories. The solution is to replace the CallCreateInstance
function with a COM Library function called CoCreateInstance
. COM Runtime Library is an integral component of the Windows operating system, which provides the means for clients to locate and instantiate COM objects. COM class objects can be identified by CLSIDs (globally unique identifiers), which are used in order to locate and create an instance of an object. Once the CLSID is obtained, a client application submits the CLSID to the COM run-time library to load the COM object and retrieve an interface pointer. Using the CLSID and the registry, CoCreateInstance
locates the specified object, creates an instance of that object, and returns an interface pointer to that object. In order to use CoCreateInstance
to create an object, the object must be registered with the system.
CoCreateInstance
The COM Library contains this function. The easiest way of creating a component is by the use of CoCreateInstance
function. CoCreateInstance
uses a class factory when it creates a component. It takes a CLSID, creates an instance of the corresponding component, and returns an interface for this instance of the component. CoCreateInstance
takes 4 in
parameters and 1 out
parameter (IUnknown*
). By passing an IID to CoCreateInstance
, the client doesn't need to call QueryInterface
on the component after creating it.
CoCreateInstance
's parameters:
- The first parameter is the CLSID of the object.
- The second parameter is used to aggregate the object as part of another object.
- The third parameter specifies the execution context of the object.
- The 4th parameter is the IID (Interface ID) of the requested interface.
- The last parameter which is an
out
parameter is an interface pointer to the created object.
Registration of components
Objects that can be created with CoCreateInstance
must also be registered with the system. Registration maps a CLSID to the automation component file (.dll or .exe) in which the object resides. If clients will want to obtain CLSIDs at run-time, there must be a way to dynamically locate and load CLSIDs for accessible objects. Furthermore, there has to be some system-wide method for the COM Library to associate a given CLSID (regardless of how the client obtained it) to the server code that implements that class. In other words, the COM Library requires some persistent store of CLSID-to-server mappings that it uses to implement its locator services. The COM implementation on Microsoft Windows uses the Windows system registry as a store for such information. In that registry, there is a root key called "CLSID" under which servers are responsible to create entries that point to their modules. Usually, these entries are created at installation time by the application's setup code, but can be done at run-time if desired. When a server is installed under Windows, the installation program will create a sub-key under "CLSID" for each class the server supports, using the standard string representation of the CLSID as the key name. So the primary entry for a CLSID is a sub-key under CLSID key, which is the CLSID spelled in hex digits within braces. We may also want to associate a CLSID with what is called a programmatic identifier or ProgID, which effectively identifies the same class. A ProgID is a text string without spaces that can be used instead of the CLSID string. The standard ProgID format is <Vendor>.<Component>.<Version>, such as Codeproject.Cmpnt1.1. This format is reasonably unique, and if everyone follows it, there will generally not be a collision. There is also the "VersionIndependentProgID", which has the same format without the version number. Both the ProgID and the VersionIndependentProgID can be registered, with a human-readable name as a value, below the root key. The VersionIndependentProgID is mapped to the ProgID, which is mapped to the CLSID. To create registry entries, you can either write code or create a REG file and simply run it to merge its entries with the registry. The following image shows the registry entries for the Component1 from the Demo Application.
The Class Factory
The CoCreateInstance
function does not create COM components directly. Instead, it creates a component called class factory, which then creates the desired component. A class factory is a component that creates other components. A particular class factory creates components that correspond only to a single, specific CLSID. The client uses interfaces supported by the class factory for controlling how the class factory creates each component. The standard interface for creating components is IClassFactory
interface. IClassFactory
like other COM interfaces is derived from IUnknown
interface and has two methods:
CreateInstance
, which creates an un-initialized object of a specified CLSID.
LockServer
, which locks the object's server in memory, allowing new objects to be created more quickly.
In the following, a class factory is defined in order to create the COM component in the example:
class CFactory : public IClassFactory
{
public:
virtual HRESULT __stdcall QueryInterface(const IID& iid,void** ppv) ;
virtual ULONG __stdcall AddRef() ;
virtual ULONG __stdcall Release() ;
virtual HRESULT __stdcall CreateInstance(IUnknown* pUnkOuter,
const IID& iid,
void** ppv) ;
virtual HRESULT __stdcall LockServer(BOOL bLock) ;
CFactory() : m_cRef(1) {}
~CFactory() {}
private:
long m_cRef ;
} ;
HRESULT __stdcall CFactory::QueryInterface(const IID& iid,LPVOID* ppv)
{
if ((iid == IID_IUnknown) || (iid == IID_IClassFactory))
*ppv = static_cast<IClassFactory*>(this) ;
else
{
*ppv = NULL ;
return E_NOINTERFACE ;
}
reinterpret_cast<IUnknown*>(*ppv)->AddRef() ;
return S_OK ;
}
ULONG __stdcall CFactory::AddRef()
{
return ::InterlockedIncrement(&m_cRef) ;
}
ULONG __stdcall CFactory::Release()
{
if (::InterlockedDecrement(&m_cRef) == 0)
{
delete this ;
return 0 ;
}
return m_cRef ;
}
HRESULT __stdcall CFactory::CreateInstance(IUnknown* pUnkOuter,
const IID& iid,void** ppv)
{
HRESULT hr;
if (pUnkOuter != NULL)
{
return CLASS_E_NOAGGREGATION ;
}
CComponent* pComponent = new CComponent ;
if (pComponent == NULL)
{
return E_OUTOFMEMORY ;
}
hr = pComponent->QueryInterface(iid,(void**) ppv) ;
if(FAILED(hr))
pComponent->Release() ;
return hr ;
}
HRESULT __stdcall CFactory::LockServer(BOOL bLock)
{
return S_OK ;
}
Before going to more details, it's better to have an overview about creation of the component via COM Library:
- The client calls
CoCreateInstance
, which is implemented in COM Library.
CoCreateInstance
is implemented using CoGetClassObject
function.
CoGetClassObject
calls DllGetClassObject
, which is implemented in DLL server and its job is to create the class factory for the component.
DllGetClassObject
queries the class factory for IClassFactory
interface, which is returned to CoCreateInstance
function.
CoCreateInstance
uses IClassFactory
interface to call its CreateInstance
method.
- The
IClassFactory::CreateInstance(...)
uses the new
operator to create the component and it queries the component for its interface.
- After getting the component's interface,
CoCreateInstance
releases the class factory and returns an interface pointer to the client.
- The client uses the interface pointer in order to call the
Print
method of the component and uses its functionality.
The following image illustrates these steps:
So, in order to improve the example, we need to:
- Implement the
CFactory
methods.
- Implement the
DllGetClassObject
in the component server or the DLL instead of CreateInstance
function.
- Write the necessary code (or use a registration file) in order to register the component in the Windows registry system.
It's also easier to make the DLL within the Visual C++ development environment. In the following, these steps will be implemented:
Step 1:
- Using the AppWizard, create a new project (with the name "Component") for the DLL, and select MFC AppWizard (DLL). Consider that the DLL now will reside in a directory (C:\CodeProject) different form its client:
- In step one, select "MFC Extension DLL" and click the "Finish" button:
- Open the Component.cpp file and replace its content with the following part:
#include "stdafx.h"
#include <afxdllx.h>
#include "interface.h"
#include <objbase.h>
#include "iostream.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
BOOL APIENTRY DllMain(HINSTANCE InsModule,
DWORD dwReason,
void* lpReserved)
{
return TRUE;
}
- Copy and paste the definitions of the component's class and the class factory and their implementations in the source file Component.cpp, and ignore the export of the
CreateInstance
function, because the class factory is going to create the component.
Step 2: Getting the Class Factory - DllGetClassObject
The DllGetClassObject
function will be called from within the CoGetClassObject
function, when the class context is a DLL, and as mentioned before, its job is to create the class factory for the component. Implement this function in the Component.cpp file:
STDAPI DllGetClassObject(const CLSID& clsid, const IID& iid, void** ppv)
{
if (clsid != CLSID_Component)
return CLASS_E_CLASSNOTAVAILABLE;
CFactory* pFactory = new CFactory ;
if (pFactory == NULL)
return E_OUTOFMEMORY;
HRESULT hr = pFactory->QueryInterface(iid, ppv);
pFactory->Release();
return hr;
}
Compile and build the DLL (Component.dll).
Step 3: Registration
Using the GUIDGEN.EXE, create a CLSID for the Component's class:
{49BF12F1-5041-48da-9B44-AA2FAA63AEFB}
static const GUID CLSID_Component =
{ 0x49bf12f1, 0x5041, 0x48da,
{ 0x9b, 0x44, 0xaa, 0x2f, 0xaa, 0x63, 0xae, 0xfb } };
Create a file with ".reg" extension (component.reg) in order to create registry entries for the component (using the CLSID):
REGEDIT
HKEY_CLASSES_ROOT\Codeproject.Component.1 =
Codeproject Component Version 1.0
HKEY_CLASSES_ROOT\Codeproject.Component.1\CLSID =
{49BF12F1-5041-48da-9B44-AA2FAA63AEFB}
HKEY_CLASSES_ROOT\Codeproject.Component = Codeproject Component
HKEY_CLASSES_ROOT\Codeproject.Component\CurVer = Codeproject.Component.1
HKEY_CLASSES_ROOT\CLSID\{49BF12F1-5041-48da-9B44-AA2FAA63AEFB} =
Codeproject Component 1.0
HKEY_CLASSES_ROOT\CLSID\{49BF12F1-5041-48da-9B44-AA2FAA63AEFB}\InprocServer32 =
c:\codeproject\component.dll
HKEY_CLASSES_ROOT\CLSID\{49BF12F1-5041-48da-9B44-AA2FAA63AEFB}\ProgID =
Codeproject.Component.1
HKEY_CLASSES_ROOT\CLSID\{49BF12F1-5041-48da-9B44-AA2FAA63AEFB}\
VersionIndependentProgID = Codeproject.Component
Activate the registry file by clicking on that. After running the registry file, the entries will be stored in Windows registry system. The following image shows these entries:
That's it; the chain now is totally broken to pieces. Check it out with the client.
The Client
Now, although the component's server (component.dll) resides in the directory C:\codeproject, the client can easily load it and use its functionality through the class factory and COM Library, and this is the way COM components usually are created and used by their clients. The following shows how the client uses the component through the COM Library:
void main()
{
HRESULT hr;
IUnknown* pIUnknown;
IComponent* pIComponent;
IClassFactory* pIClassFactory;
::CoInitialize(NULL);
hr=CoGetClassObject(CLSID_Component,CLSCTX_INPROC_SERVER,
NULL,IID_IClassFactory,(void**)&pIClassFactory);
if (SUCCEEDED(hr))
{
hr=pIClassFactory->CreateInstance(NULL,
IID_IComponent,(void**)&pIComponent);
if(SUCCEEDED(hr))
pIComponent->Print("COM from scratch.");
}
::CoUninitialize ();
}
Part three is explained in the next article.