|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionThis article is about COM and COM components. It explains some of the details behind COM technology, through implementation of a simple COM component. The article begins with some background information about COM and the reader is guided to implement and improve a simple COM component in an example. The example starts with implementation of a COM component and a COM client in a common file. The improvement of the example is about the separation of the COM component form its client. The client and the component are first separated to two different files, then the component will be put on a DLL, which can be loaded into the address space of the client and the final improvement is to register the component in Windows registry, such that the client is no longer bound to the component, and is able to create it through the class factory. In the following illustration, the relation between the component and its client is shown with a chain, which will be broken totally when the component is created through the class factory. In the last part, COM containment is explained by reusing the implemented component in a new COM component. This article is only about part one and the other parts are explained in two other articles. The demo application's code (Client + 3 component Servers) is very similar to the example explained in the article and only a window is used to visualize the component itself. The following image illustrates part one and part two:
AssumptionYou are familiar with basic C++ and Windows programming. Part one-BackgroundA binary standard for making software componentsIn object oriented programming, software objects created by different vendors cannot interact with each other if a common standard framework has not been used, and COM or Component Object Model is a binary standard for making software component language independent, and it's about breaking the applications into separate pieces. COM's implementation is hidden for users (COM clients) and it means that COM components are shipped in a binary form and are already compiled, linked and ready to use and they are just a bunch of 1s and 0s, i.e. they are just machine code, which can be executed whenever a client interact with them. Here are some advantages of COM:
Functionality of COM componentsAn object or component in COM is any structure that exposes its functionality through the interface mechanism. In C++ applications, interfaces are defined as abstract base classes. An interface is a C++ class that contains nothing but pure virtual member functions. This means that the interface carries no implementation and only prescribes the function signatures for some other class to implement. The pure abstract base classes define the specific memory structure that COM requires for an interface, and when we define a pure abstract base class, we are actually defining the layout for a block of memory. Memory is not allocated for the structure until the abstract base class is implemented in a derived class. So in the following piece of code, //----------------------------------------------------------------//
// Definition of component's functionallity through an interface //
//----------------------------------------------------------------//
// The keyword "interface" is just an alias for "struct"
interface IComponent
{
// Methods for the implementation of the component's functionalities:
virtual void __stdcall Function1()=0;
virtual void __stdcall Function2()=0;
virtual void __stdcall Function3()=0;
};
As shown in the following image, there are two parts to the block of memory defined by a pure abstract base class which makes the interface of a COM component in C++ (the top figure illustrates Component2 from the demo application):
Conclusion: In COM, functionality of components is obtained through their interfaces, which are addresses or entries to components' methods. A common need for COM objects and interfaces (Basic operations)In a component which supports variety of functionalities and interfaces, there is a need to be able to access these interfaces easily. Another requirement is that the clients should be able to manage the existence of components and free them once they have finished using them. A COM component is a component, which supports these operations through a so-called
These three methods make up the //------------------------------------------------------------// // Being a COM interface by inheriting from IUnknown interface// //------------------------------------------------------------// interface IComponent :public IUnknown; { virtual void __stdcall Function1()=0; virtual void __stdcall Function2()=0; virtual void __stdcall Function3()=0; }; Because every COM component inherits from
Conclusion: The The Class of COM components (Implementation)When the component's functionality is defined through its methods (like //----------------------------------// //Definition of the Component's class //----------------------------------// class CComponent:public IComponent { public: //-------------------------------------------------------------------------// //IUnknown methods,which are the basic oparation //required for all COM components: //-------------------------------------------------------------------------// virtual HRESULT __stdcall QueryInterface(const IID& iid,void** ppv); virtual ULONG __stdcall AddRef(); virtual ULONG __stdcall Release(); //-------------------------------------------------------// //The new methods, which the interface supports: //-------------------------------------------------------// virtual void __stdcall Function1(); virtual void __stdcall Function2(); virtual void __stdcall Function3(); private: // Component's member data long m_cRef;// The reference count variable }; The following image illustrates the memory layout for the
As the image shows, an Interface pointer like the " //---------------------------------------------// //Getting a pointer to the IComponent interface: //---------------------------------------------// IComponent* pIComponent=NULL; //The "IID_IComponent" is the interface ID for the IComponent interface pIUnknown->QueryInterface(IID_IComponent,(void**) &pIComponent; // Calling the component's method: pIComponent->Function1(); As explained before, we can navigate between interfaces on a COM object through the A window object has been used as a member data of the component in order to visualize the component, and as the image shows, the component is a window with a menu which operates as its interface, and the component's methods are accessible through menu items using the mouse pointer like an interface pointer.
Conclusion: A COM class is a particular implementation of certain interfaces. Instantiating of COM ObjectsAs explained before, the only way of getting access to a component's functionality is just through its interfaces, so, once we get a pointer to a component's //When new operator is used to allocate a single object, //it gives a pointer to that object IUnknown* pIUnknown = static_cast<IComponent*>(new CComponent); Interface IdentifierAn interface identifier is a structure of type GUID (Globally Unique Identifier). GUIDs exist in two formats: string and numeric. In Windows registry, the string format of the GUIDs appears in various locations, however, the numeric representation of GUIDs are necessary when they are used within client applications or within the actual COM object implementation. The //-------------------------------------------// // GUID definition: //------------------------------------------// typedef struct _GUID { unsigned long Data1; // 32 bits unsigned short Data2; // 16 bits unsigned short Data3; // 16 bits unsigned char Data4[8];// 8*8 = 64 bits //-----------------------------------------// // Total bits = 128 bits } GUID; Your development environment will include a tool called UUIDGEN.EXE or GUIDGEN.EXE, which will give you one or more GUIDs that you can incorporate into source code. I have used GUIDGEN.EXE to create GUIDs in all the examples in this article. An example from scratchBy now, you are able to make a simple COM object and let a client to use its functionality. Using the following steps, you may make the example, which is shown in the illustration at the beginning of the article. You may download the code in each part and copy-paste some parts in order to make your application quickly. Step 1:Using the AppWizard, create a simple Win32 Console application and choose an empty project. This application will act as the COM client and will also hold the component itself. Step 2:Create a source file with the extension of "cpp" and give it a name. I used the name ClientAndComponent.cpp. Step 3:Let's decide a primitive functionality for the component, for example, the component should be able to print the phrase "COM from scratch" on the screen. As mentioned before, a COM component exposes its functionality through the interface mechanism, so the need is to make an interface, and this interface should be derived from the //-------------------------// //Interface definition: //-------------------------// interface IComponent:IUnknown { //The new method for assigning component's functionality virtual void __stdcall Print(const char* msg)=0; }; Step 4:In order to identify the //-----------------------------------------------// //Interface identifier, which is a 128-bit value.// //-----------------------------------------------// // {853B4626-393A-44df-B13E-64CABE535DBF} string format static const IID IID_IComponent = { 0x853b4626, 0x393a, 0x44df, //Data1,Data2,Data3 { 0xb1, 0x3e, 0x64, 0xca, 0xbe, 0x53, 0x5d, 0xbf } }; //Data4 Step 5:The component's class can be defined by deriving it from the defined interface ( //----------------------------------// //Definition of the Component's class //----------------------------------// class CComponent:public IComponent { public: //---------------------------------------------------------------------------// //IUnknown methods,which are the basic oparation //required for all COM components: //---------------------------------------------------------------------------// virtual HRESULT __stdcall QueryInterface(const IID& iid,void** ppv); virtual ULONG __stdcall AddRef(){return 0;} virtual ULONG __stdcall Release(){return 0;} //-------------------------------------------------------// //The new method, which the interface should support. //-------------------------------------------------------// virtual void __stdcall Print(const char* msg); }; Step 6:Now it's necessary to implement the methods:
Step 7:Create a function in order to instantiate an object from the component class:
Step 8:The final step is just to make the client, which uses the component's functionality through the component's interface: //----------------// // Client //----------------// void main() { IUnknown* pIUnknown=CreateInstance(); IComponent* pIComponent=NULL; pIUnknown->QueryInterface(IID_IComponent,(void**)&pIComponent); pIComponent->Print("COM from scratch."); } A better implementation of the IUnknown interface's methods
The following screen shot is made from the example in part one:
Summary
Part two is explained in the next article. | ||||||||||||||||||||