|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionIn 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 chainServing 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 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
Step 3:The linker should be informed that the ;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: // // GUID.cpp - Interface ID // #include "objbase.h" extern "C" { extern const IID IID_IComponent = { 0x853b4626, 0x393a, 0x44df, //Data1,Data2,Data3 { 0xb1, 0x3e, 0x64, 0xca, 0xbe, 0x53, 0x5d, 0xbf } }; //Data4 // The extern is required to allocate memory for C++ constants. } Step 5:Create a header file (interface.h) with the following content: // // Interface.h // interface IComponent : IUnknown { virtual void __stdcall Print(const char* msg) = 0 ; } ; // Forward references for GUID 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.
Building the ClientThe 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 // Create.cpp #include "iostream.h" #include "unknwn.h"//IUnknown definition file. #include "Create.h" typedef IUnknown* (*CREATEFUNCPTR)(); ////////////////////////////////////////// IUnknown* CallCreateInstance(char* dllname) { //-----------------------------------------------------------------// // Load dynamic link library into client's process. //Loadlibrary maps a DLL module and return a handle //that can be used in GetProcAddress //to get the address of a DLL function //-----------------------------------------------------------------// HMODULE hm = ::LoadLibrary(dllname); if (hm ==NULL) return NULL; // Get the address of CreateInstance function. 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: // Create.h IUnknown* CallCreateInstance(char* dllname) ; Step 4:Create a new header file (interface.h) with the following contents: // // Interface.h // interface IComponent : IUnknown { virtual void __stdcall Print(const char* msg) = 0 ; } ; // Forward references for GUID extern "C" { extern const IID IID_IComponent ; } Step 5:Create another source file (GUID.cpp), which can hold the interface ID: // GUID.cpp - Interface ID #include "objbase.h" extern "C" { extern const IID IID_IComponent = { 0x853b4626, 0x393a, 0x44df, //Data1,Data2,Data3 { 0xb1, 0x3e, 0x64, 0xca, 0xbe, 0x53, 0x5d, 0xbf } }; //Data4 // The extern is required to allocate memory for C++ constants. } Step 6:Create a source file (Client.cpp) and implement the //--------// // Client //--------// int main() { HRESULT hr ; // Get the name of the component to use. char dllname[20]; cout << "Enter the filename of component's server [component.dll]:"; cin >> dllname; ... // calling the CreateInstance function in the // DLL in order to create the component. 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.") ; //using the component's functionality 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 The following screen shot shows the client application, after loading the DLL and calling the component's
Conclusion: Distribution of COM components by servers makes it easy for clients to reuse components' functionality. Extending component's functionality without rebuilding ClientsOne 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
Step 2: Make the Client and load the DLL
Step 3: Viewing the Rebuild problem
Conclusion: COM extends functionalities of applications without rebuilding. Improvement of the exampleIn 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 CoCreateInstanceThe COM Library contains this function. The easiest way of creating a component is by the use of
Registration of componentsObjects that can be created with
The Class FactoryThe
In the following, a class factory is defined in order to create the COM component in the example: /////////////////////////////////////////////////////////// // // Class factory // class CFactory : public IClassFactory { public: // IUnknown virtual HRESULT __stdcall QueryInterface(const IID& iid,void** ppv) ; virtual ULONG __stdcall AddRef() ; virtual ULONG __stdcall Release() ; // IClassFactory virtual HRESULT __stdcall CreateInstance(IUnknown* pUnkOuter, const IID& iid, void** ppv) ; virtual HRESULT __stdcall LockServer(BOOL bLock) ; // Constructor CFactory() : m_cRef(1) {} // Destructor ~CFactory() {} private: long m_cRef ; } ; // // Class factory IUnknown implementation ////////////////////////////////////////////////////////////////////// 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 ; } // // IClassFactory implementation /////////////////////////////////////////////////////////////// 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 ; } // Get the requested interface. hr = pComponent->QueryInterface(iid,(void**) ppv) ; if(FAILED(hr)) pComponent->Release() ; return hr ; } //-----------------------------------------------------------------------// // LockServer // Called by the client of a class object to keep a server open in memory, // allowing instances to be created more quickly. //-----------------------------------------------------------------------// /////////////////////////////////////////////////// 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 following image illustrates these steps:
So, in order to improve the example, we need to:
It's also easier to make the DLL within the Visual C++ development environment. In the following, these steps will be implemented: Step 1:
Step 2: Getting the Class Factory - DllGetClassObjectThe ///////////////////////////////////////////////////////////////////////// STDAPI DllGetClassObject(const CLSID& clsid, const IID& iid, void** ppv) { if (clsid != CLSID_Component) return CLASS_E_CLASSNOTAVAILABLE; // Create class factory. CFactory* pFactory = new CFactory ; if (pFactory == NULL) return E_OUTOFMEMORY; // Get requested interface. HRESULT hr = pFactory->QueryInterface(iid, ppv); pFactory->Release(); return hr; } Compile and build the DLL (Component.dll). Step 3: RegistrationUsing 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 ClientNow, 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: //-----------// // Client //-----------// void main() { HRESULT hr; IUnknown* pIUnknown; IComponent* pIComponent; IClassFactory* pIClassFactory; ::CoInitialize(NULL); /* //Once the CoCreateInstance is called, the component //will be created and the client can not //control it, that's why CoCreateInstance is inflexible //and the solution is to call CoGetClassObject function hr = ::CoCreateInstance(CLSID_Component,NULL, CLSCTX_INPROC_SERVER,IID_IUnknown,(void**)&pIUnknown) ; if (SUCCEEDED(hr)) { hr=pIUnknown->QueryInterface(IID_IComponent,(void**)&pIComponent); if(SUCCEEDED(hr)) pIComponent->Print("COM from scratch."); } */ //-------------------------------// // improvement of the client code //------------------------------// // By calling the CoGetClassObject function, the client can control // creation of the component 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. | ||||||||||||||||||||