COM Without the complexity
Microsoft's Component Object Model (COM) has emerged as a vital tool. It's the basis of Microsoft's approach to distributed computing. It's a powerful method for customizing applications, present and future. And it's the foundation of OLE and ActiveX. In short, COM helps unlock the future of development with an interest in component design and programmers who want to use COM when it's ported to UNIX, MVS, and other environments. To put it simply, COM-based interfaces are spreading fast and you work it.
The COM/DCOM technologies for you: COM stands for Component Object Model and DCOM stands for Distributed Object Model. Simply put, DCOM is COM on a longer wire. We will begin our journey of understanding COM and DCOM by looking at the evolution of component technology. The story began with the introduction of custom controls. It is nothing but a DLL that exports a set of functions. Unlike a DLL, a custom control can manipulate properties, and handle the firing of events in response to user or programmatic input. Unfortunately, these C language DLLs could not be used by Visual Basic since the DLLS had no way of allowing VB to query the control for information on the properties and methods supported by the control. Visual Basic Extension (VBX) contained reusable software components. They were able to provide a wide range of functionality, from a simple list label to a complex multimedia control. VBXs thus provided a wide variety of capabilities that otherwise would not have been possible in VB applications. VBXs were written in C and C++.
VBXs were successful and a demand grew to port them to non-Intel platforms. Unfortunately, VBXs were built on a 16-bit architecture. Hence they could not be easily ported to a 32-bit environment. Therefore, Microsoft introduced a new specification called OLE1.0. OLE stood for Object Linking and Embedding. OLE 1.0 provided a method for handling a compound document. A compound document is a way of storing data in multiple formats such as text, graphics, video, and sound, in a single document. OLE 1.0 met with limited success because its complex API made it difficult to program.
Unfazed by the limited success of OLE 1.0 in 1993, Microsoft released OLE 2.0. It provided architecture of object-based services that could be extended or customized. Because of the extensible architecture, there will be no OLE 3.0 or any other new versions of OLE model. OLE 2.0 not only provided specification for writing component software but also implemented some services to simplify the use of this technology. These services include OLE drag and drop, OLE automation, OLE controls, etc. MFC has wrapped the OLE API in a C++ class library, thus making it much easier for the programmer to use the OLE architecture. Realizing the term OLE does not communicate the full potential of the technology, Microsoft has renamed it as ActiveX. If an application consists of a single monolithic binary file then any change in operating systems, hardware, or customer desires, would need recompilation of the application. Instead, if the application were broken into separate pieces (components), it would be possible to replace existing components with new components easily.
The whole idea of computing today is to put together applications rapidly from reusable maintainable code (preferably written by someone else). Object-oriented languages help developers to write reusable, maintainable code. The benefits of object-oriented techniques are there for all to see. However, an object-oriented language is not sufficient for widespread reuse. This is because developers throughout the world are programming in different languages. Developing all applications through a single programming language is a dream that is never going to come true. There are several reasons for this. They are:
- Some languages are better suited to a particular problem domain than others.
- Some programmers have a natural preference for a particular language because it matches their way of thinking.
- Some languages are able to exploit the capabilities of some hardware better than the others.
- New languages may evolve to take advantage of the new processor power.
Moral! We use and would continue to use many languages for writing programs. Though using multiple languages has its benefits, when it comes to integrating code developed in different languages, there are serious problems. For example, a Java class is of little use to a C++ developer. Likewise, a chunk of Visual Basic code won’t help a COBOL programmer. Also, a system language that may evolve five years hence.
All this leads to the need of a system that would allow developers to write code in the languages of their choice, and still make the code reusable in any other language. This is precisely what COM permits us to do. COM provides a language-neutral binary standard for building components.
Is COM The Only Solution?
COM is not the first nor is it the only way of reusing compiled code. DLLs have been used extensively in Windows programming for a very long time now. However, DLLs suffer from a few disadvantages. They are:
- Although DLLs can be called from most languages, relatively few languages permit us to create them.
- As a new version of a DLL is installed, there is a non-zero probability of breaking an existing application if enough attention is not paid to the issue of backward compatibility. As there are no standard rules or procedures for coming up with new versions, things go wrong more often than not.
On the plus side, a DLL offers the following advantages:
- It allows parts of an application to be upgraded without the need for recompilation.
- A DLL can be loaded on a just-in-time basis, hence it doesn’t occupy memory unnecessarily.
- A DLL can be shared amongst several applications, these advantages of DLLs can be leveraged by packaging the COM components as DLLs.
COM Components As DLLs
To exploit the advantages of DLLs, COM components can be packaged as DLLs. There is one hitch however. Traditionally, DLLs have been loaded by filename; this means that if the location or the name of a DLL changes, the application will not be able to load that DLL. This reliance on the client’s knowledge of a DLL's filename also makes it impossible to provide different versions of a DLL on the same system, and can cause conflicts between different vendor products. COM solves this problem by registering the location of a COM component in the system registry under an identifier (ID). The ID of a COM component is guaranteed to be unique, so conflicts with different products are avoided, and the client doesn’t need to know anything about the physical location of the component.
What COM Is
Quite often, programmers develop the ability to write COM components but are still hazy about what COM really is. So, let's make a one-line definition of COM. COM is a specification and a set of services that permit us to create applications that are language-independent, modular, object-oriented, distributed, customizable, and upgradeable.
Let us now take apart this definition word by word.
COM Is A Specification
The COM specification is backed up by a set of services or APIs. On Win32 platforms, these services are provided as a part of the operating system in the form of a COM library. In other operating systems, they are provided as a separate package.
COM Components Are Language independent
COM is a binary standard. The components can be written in any programming language as long as they follow a standard memory layout prescribed by COM. As of this writing, the number of languages and tools that support COM include C, C++, Java, JScript, Visual Basic, VBScript, Delphi, PowerBuilder, COBOL, etc.
COM Allows Modular Programming
COM components can be distributed either as DLLs or as executables. Components present in different modules can communicate with each other through the communication mechanism provided by COM.
COM is Object-oriented
COM components are like normal objects. They have an identity, state, and behavior. COM components along with COM interfaces support the concept of encapsulation and polymorphism.
COM Enables Easy Customization
COM components are linked with one another dynamically. Also, there is a standard way of locating a component. Hence, we can replace an existing component with a new one without being required to recompile the entire application.
COM Enables Distributed Applications
The components can be transparently relocated on a remote computer without affecting the clients, i.e., local and remote components are treated in the same manner. This makes distributed computing very easy.
What COM Is Not
- COM is not a computer language. In fact, COM can be implemented through a variety of languages.
- COM doesn’t replace DLLs. In fact, components are often implemented as DLLs to exploit the ability of a DLL to link dynamically.
- COM doesn’t stand for OLE controls (that's ActiveX), compound documents (that's OLE DB and ADO), or game and graphics programming (that's DirectX). All these technologies are however based on COM.
Need For The Standard
To make the software customizable, it becomes necessary to reuse the pieces of software that have already been written. With the increasing complexity of software, it becomes necessary to divide the work of developing it amongst different people/organizations. Each can develop different components parallel. Unless these components are written to some standard, it will be difficult to integrate them in a program. If software components can be handled in a uniform manner, it would be easy to develop and assemble them. To build such components, we need software integration standards like OLE. An OLE component can be created in a standard way. It can link dynamically, and hence can be distributed in binary format. It can be programmed in any language. A new component can replace an existing component without being required to recompile the application.
Benefits of COM
- As all the COM components behave uniformly, it is very easy to put together new applications using off the shelf component libraries.
- It is also very easy to customize an application for different needs.
A COM interface is a group of related functions that forms the means of communication between a client and a server. While a DLL publishes its services via the exported functions, a COM component provides its services by implementing one or more COM interfaces. A client connects to a component (server) through a COM interface. If the component changes without changing the interface, the client does not have to change at all. Similarly, if the client changes without changing the interface, the component doesn’t have to change.
A COM interface is defined as a C++ abstract base class derived from another interface called
IUnknown. Every function supported by an interface is a pure virtual function. The interface itself doesn't provide an implementation of its member functions. If you want to use these functions in your application, you have to find a COM component that implements the interface. In this case, your application will be a client of the COM component communicating with the component via the interface.
What is so special about COM interfaces? How are the functions included in an interface different from the functions exported by a DLL? COM interfaces obey the following rules that make them very special:
- Every interface is identified by a unique 128-bit identifier called Interface Identifier (or IID).
- An interface is immutable. Once an interface is published, it cannot be changed. Any change to an existing interface gives rise to a new interface and is assigned a new IID. This ensures that the new interface can never conflict with an old interface. It also makes version management straightforward.
- Every interface must follow a standard memory layout. This requirement is necessary to make the implementation of a COM interface language independent. Programmers have the liberty to use any programming language (C, C++, Java, etc.) to implement the functions as long as the specific compiler being used follows the memory layout specified by COM. VC compiler generates a memory layout that follows the specification.
What Is ATL?
ATL stands for Active Template Library. However, this expansion doesn’t fully convey what ATL contains. The word Active in ATL is really a remnant of Microsoft's old marketing strategy when everything related with COM was ActiveX. Today, the meaning of ActiveX is limited to controls. These controls can be developed, using ATL. But ATL can do much more than merely building ActiveX controls.
COM components can be developed using MFC. However, such components suffer from two limitations:
- Size of the COM component often becomes unduly large. This is because most COM components developed using MFC, require run-time libraries like mfc42.dll to execute. Bundling this library along with the COM object results in a very large file size. When such components are used over networks, the performance suffers.
- It becomes very difficult to change the default functionality of the component.
Instead of using MFC, if we use Active Template Library (ATL), we can easily build small, self-contained, and distributable COM objects. ATL uses C++ template library and is meant specifically to facilitate the creation of COM objects. In addition to COM components, using ATL we can also create Automation server and ActiveX controls.
ATL facilitates component development by providing the following:
- Classes for handling data types such as interface pointers,
- Classes that provide implementation of basic COM interfaces such as
- Classes for managing COM servers, that is, for exposing class objects, self-registration, and server lifetime management.
- Wizards to make COM development easier.
Without much ado, let us now try to develop a COM server using ATL. The process of creating components in ATL consists of three steps:
- Create a module
- Add component to the module
- Add methods to the component
We would try to create a module (usually a DLL) called AtlServer. Then we will create a component called
MyMath within this server. Lastly, we would add two methods
MySubtract() to it. The first method would add two integers passed to it by the client and return the resulting integer. Similarly, the second method would subtract the two numbers and return the result. Let us carry out these steps now.
Create A Module
To create a module, the Developer Studio provides an ATL COM AppWizard. Creating a module is one of the easiest jobs. Carry out the following steps:
- Select 'New' from the 'File' menu. A dialog box shown below is popped up. Select 'ATL COM AppWizard' as the project. Type AtlServer as the project name and click 'OK' to continue.
- Select type of module as 'Dynamic Link Library' (refer following figure) and click 'Finish'.
Now, a 'New Project Information' dialog is displayed which lists the name of files that the wizard would create. Click on 'OK'.
If we build the project using F7, an AtlServer.dll file would be created but it would do nothing since we have not added any components to it so far. The next step shows how this can be done.
Add Component To The Module
To add component to the module, we can use 'ATL Object Wizard'. Carry out the following steps for adding the component named
MyMath using this wizard:
- Select 'Insert | New ATL Object..' menu item. This would display the 'ATL Object Wizard' dialog as shown below.
- Select Simple Object' from the various object categories, and click on 'Next'.
- An 'ATL Object Wizard Properties' dialog is displayed as shown below:
The property sheet contains two tabs: Names and Attributes. The Names tab is divided into two sections. The first section displays the 'C++ names' and the second section the 'COM names'. Enter the 'Short Name' as MyMath. As soon as you do this, all other edit controls would be filled automatically.
The names filled under the edit controls of 'C++ names' indicate that the class
CMyMath will implement the object
MyMath in the files MyMath.h and MyMath.cpp.
The name filled under the edit controls of 'COM names' indicate that the
CoClass name (component class) remains the same as the short name. The interface name will be
ImyMath. The type is a description for the class.
The ProgID will be <Project Workspace Name>. <CoClass name>. When the 'OK' button is clicked, the class
CMyMath and the interface
IMyMath are created. They can be viewed form the Class View tab.
Add Methods To The Component
The component that has been added does not contain any functionality. To provide functionality, we should add two methods
MySubtract() as indicated below:
- Switch to Class View tab and expand the tree. Select button. From the menu that pops up, select 'Add Method'. Note that if you expand the
CMyMath tree, you will find another
IMyMath. Any one can be used to add methods.
- In the 'Add Method to Interface' dialog, specify the method name as
MyAdd, and parameters as
[in] int n1, [in] int n2, [out, retval] int *n3. Click on 'OK'. Here,
[in] specifies the value to be passed to the method, and
[out, retval] specifies the return value.
- Follow a similar procedure for adding the
MySubtract method, which has same parameters as
MyAdd method creates a function definition in MyMath.cpp. To view the code, switch to File View tab, expand the tree, expand the 'Source Files' tree, and double-click on MyMath.cpp to open it. (The same method can be viewed by expanding the View tab, and double clicking on the method name). The
MyAdd method is shown below:
STDMETHODIMP CMyMath::MyAdd(int n1,int n2, int *n3)
Now, if we build the project, a DLL file would be created containing the component having methods
MySubtract(). Let us now build a client that would call these methods.
Creating a COM Client
We would develop the client as a simple dialog-based application. The steps involved in building the client are given below:
- Create Project
- Create a dialog-based project AtlClient using AppWizard(EXE)
- Add three edit boxes and three buttons to the dialog as shown below:
Import Type Library
Import the server’s Type Library into the client. The is done by adding the following two statements to the file StdAfx.h before the last
using namespace ATLSERVERLib;
Note that the client should be created in the same parent directory in which the server was created. This is necessary because in the
#import statement, we have specified "..\\AtlServer\AtlServer.tlb"; means look into the AtlServer directory, if it is located somewhere else, specify the full path in the
When we compile the client project,
#import keyword creates the header for the wrapper class in a file with '.tlh' extension, and the implementation in another file with extension '.tli'. These files are present in the Debug directory of the client's project workspace. The wrapper class contains information about the server that is used while accessing the methods defined in the server from the client. This wrapper class is embedded in a namespace
Initialize COM Library
Initialization of COM library permits the application to call COM functions. The client can interact with the server through the COM Runtime Library. To initialize the COM Library, call
CoInitialize() function as shown below:
Retrieve CLSID of Server
To be able to instantiate the server, we need to retrieve its
CLSID. As it is difficult to remember the
CLSID, it can be retrieved using the ProgID through the function
CLSIDFromProgID() as shown below:
MessageBox("Unable to access server");
Create Component Object
CLSID of the component, create an instance of the COM Server component. The
CoCreateInstance() function is used for this purpose as shown below:
hr=CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER,
CoCreateInstance() creates an instance of the component
IMyMath and returns the interface address in the pointer math. The parameter
CLSCTX_INPROC_SERVER indicates that our server is a DLL. The
__uuidof() function has been used to obtain the ID of the interface. Add
math as a private variable of
Use COM Object
Now that we have obtained the interface pointer, the client application can call the methods of the COM server object from the handlers of 'Add' and 'Subtract' buttons, and click on 'OK' in the dialog that appears. Their code is as follows:
Note that the methods of the COM server object
MySubtract() have been called using the interface pointer math obtained in the call to
Un-initialize COM Object
This is possibly the simplest job. Simply call the function
CoUninitialize() at the end of
InitInstance() function as shown below:
With that we are through with the creation of the client. Now, you can compile and execute the client and check out whether it is able to interact with the methods in the server.
A word of caution! Once you run the client, it would load the server DLL into memory. Now, if you wish to make any changes in the server, you must close the client, so that the server DLL that the client was using would be unloaded from memory. If you don’t do this, the DLL would remain in memory and any attempt to modify the server would lead to a compile time error “Cannot open Debug\AtlServer.Dll for writing”.
There is another article that holds information about how to write a COM program in VC++ and using it in VB. Know the importance of it [ComCalCulator]. This simple program may help you understand COM coding and using it in VB.