Teach Yourself Database Programming
with Visual C++ 6 in 21 days


Day 9
      Understanding COM



The Component Object Model (COM) is the basis for much of the next generation of software on the Microsoft Windows platforms. An understanding of COM is a prerequisite for developing advanced applications with Microsoft technology.

COM is a huge topic. Those who learn it all must conquer a vast technical landscape. Fortunately, you don't need to learn all of COM. You can learn the fundamentals of COM and use that knowledge to be quite productive, particularly when it comes to database applications. Knowledge of the COM fundamentals will serve you well and is sufficient for building advanced database software.

Today you will learn

COM is used extensively in Microsoft's latest database client technologies. In fact, you used COM in Day 4, "Retrieving SQL Data Through a C++ API," Day 5, "Adding, Modifying, and Deleting Data," and Day 6, "Harnessing the Power of Relational Database Servers," when you used ADO. ADO sports a COM interface.

The first step in learning COM is understanding what problems COM solves. This gives you the context for COM technologies. You will learn what problems COM solves in the next section, which explains the limitations of Windows DLLs (dynamic link libraries).

After you understand the problems COM solves, you can study its technical foundation. Today, you will examine COM from the bottom up. A bottom-up approach to learning COM is a good approach for C++ programmers for at least three reasons.

First, COM is narrower at the bottom than at the top. There is less to learn at the foundation than at the top, where COM technology is applied in myriad different ways.

Another reason for the bottom-up approach is that you, being a C++ programmer, are capable of understanding COM's foundation. As you will see, COM's foundation rests on a few particular C++ techniques, with which you might already be familiar.

Last, if you understand the foundation of COM technology, it will be much easier for you to grasp the higher levels of COM. In COM, technologies are built on other technologies. If you learn the foundation, you are in a much better position to understand the rest of COM.

The Limitations of Traditional Windows DLLs

Windows DLLs do one thing really well. They enable code to be shared among applications at runtime in a very efficient manner.

However, there are some things that DLLs don't do very well. To understand the limitations of traditional Win32 DLLs, you must first understand what Win32 DLLs are and how they actually work. (When I say traditional DLLs, I am talking about DLLs that don't use COM.)

What Win32 DLLs Are and How They Work

The best way to think of a Win32 DLL is to picture it as a chunk of code sitting in memory. That chunk of code can be mapped into your application's address space, which is what happens when your application loads the DLL. When the DLL is mapped into your application's address space, your application can execute the code in the DLL by calling the functions that it exports.

A DLL in Win32 does not have a life of its own. A DLL doesn't have its own process. A DLL has no Windows message loop. Any objects created by code in the DLL are owned by the calling application. A DLL never owns anything. Remember that DLLs are just code-code that is inert until it is loaded into an application's address space and executed as part of that application's code.

An application can load a Win32 DLL into its address space in the following two ways:

With implicit load-time linking, the application statically links with the DLL's import library (a LIB file). The import library contains the list of functions that the DLL exports for applications to call. The import library also stores the addresses of the exported functions in the DLL file image. When the import library for the DLL is linked with an application, the information from the import library becomes part of the EXE file image.

When the application is executed, the operating system (the OS) loads the EXE file, and the OS looks in the EXE file image to see which DLLs are required for the application. The operating system loads these DLLs when it loads the application's EXE file.

With explicit runtime linking, the DLL is loaded by the application, not by the OS. The DLL is loaded at application runtime, not at application load-time. The application loads the DLL whenever it needs it, by calling the LoadLibrary Windows API function, like this:

HINSTANCE LoadLibrary(LPCTSTR lpszDLLFileName);

Also, the application can use the LoadLibraryEx function to load DLLs. The LoadLibraryEx function enables the application to specify some options in how the DLL is loaded. The LoadLibraryEx function is

HINSTANCE LoadLibraryEx(LPCTSTR lpszDLLFileName,
  HANDLE hFile, DWORD dwFlags);

When the operating system loads the DLL, it uses a search procedure to find the DLL file. The search procedure is the same whether the DLL is loaded when the application is loaded through implicit linking or whether the DLL is explicitly loaded by the application code with LoadLibrary. The OS looks in the following series of locations to find the DLL file:

With implicit load-time linking, if the OS cannot find the DLL file in any of these locations, it will present the user with a message box containing an error message. The message will state that it was unable to load the DLL. The application then will not load or run.

If the application is loading the DLL at runtime with explicit runtime linking, and the DLL file is not present, the LoadLibrary function will fail. It's up to the application to check for errors returned by LoadLibrary and act accordingly.

After the application loads the DLL, it must get the addresses of the DLL function(s). If the application uses implicit load-time linking (through the DLL's import library), the function addresses are already linked into the application's EXE file image.

If the application calls LoadLibrary to load the DLL at runtime, the application must call GetProcAddress to get the address of the function(s) in the DLL. The GetProcAddress function is

NOTE
Developers might be accustomed to exporting DLL functions by assigning each function an ordinal value. However, Microsoft wants you to link by using the function's name instead of an ordinal value. If you use ordinals to export your DLL functions, you are not guaranteed that your DLL will run on all Win32 platforms and versions.

FARPROC GetProcAddress(HINSTANCE hinstanceDLL, LPCSTR lpszProc);

I mentioned that DLLs enable code to be shared among applications in a very efficient manner. When multiple applications load the same DLL, the OS doesn't load multiple instances of the DLL. Rather, when the first application loads the DLL, it's loaded into memory and mapped into the application's address space. Figure 9.1 illustrates this.

Figure 9.1 : A DLL image mapped into an application's address space.

Figure 9.2 : A DLL image mapped into two applications' address spaces.

When subsequent applications load the DLL, the OS simply maps the already loaded DLL into those applications' address space. This is illustrated in Figure 9.2.

DLL code in memory can be shared by multiple applications. This is what DLLs do really, really well. They reduce code size at runtime by letting the code be shared among applications. The DLLs that contain the Win32 API functions are a perfect example of this. The Windows system DLLs are loaded once and are shared by and mapped into the address space of every Win32 application that runs on the machine.

TIP
You should use DLLs for sharing common code among concurrently running applications. DLLs do this very well.

The Limits of Using Win32 DLLs for Building Software Components

DLLs enable efficient code sharing among concurrently running applications. However, because of some limitations of DLLs, it's difficult to build truly modular, component-based software using traditional Win32 DLLs.

You need to understand what I mean when I talk about component-based software. A true software component would be a piece of software that does the following:

In essence, a software component is a piece of software that you can give (or sell) to other people to use with their software. You can also update your component, and your users can begin using the updated version without breaking their existing software.

Traditional Win32 DLLs, by themselves, do not enable component-based software. The limitations of using DLLs to build software components include

DLL Functions Must Be Exported by Name

I noted previously today that Microsoft wants functions exported from DLLs by their name, not by an ordinal value. When an application uses a DLL, the application must use absolutely unique names to identify the DLL function(s) it wants to call.

If you are going to distribute your DLL for use in other systems (systems with which you are unfamiliar), you need to make sure the function names in your DLL don't duplicate any function names from other DLLs that the system might use. With traditional DLLs, there is no easy way to do that. Your DLL could contain a function that has the same name as a function in another DLL.

Incompatible C++ Function Name Decorating

The need to export your DLL functions by name also causes a problem with C++ function decorating. (Function decorating is also called function name mangling, but decorating is a nicer word.)

As you know, C++ functions can be overloaded. Overloaded functions are functions that have the same name and differ only in their parameter list. The compiler and linker, however, must give each function a unique symbol name.

Visual C++ gives each function a unique symbol name by decorating the function's name with additional alphanumeric characters based on the function's parameters. These alphanumeric characters are derived from the function parameters and their types and enable the compiler and linker to differentiate between the overloaded functions.

With Visual C++ name decorating, the function

void Foo(int i)

becomes

?Foo@@YAXH@Z

You can witness C++ function name decorating in the following example. Open your ADOMFC1 project. Click the File View tab and add a header file called DbComponent.h. Put the code shown in Listing 9.1 in DbComponent.h.


Listing 9.1.  The DbComponent Class Declaration

 1:  class DbComponent
 2:  {
 3:  public:
 4:    DbComponent();
 5:    int BeepIfDbIsOk();
 6:  };

Next, include the DbComponent.h file in the CADOMFC1Doc.cpp file, like this:

#include "DbComponent.h"

Then add the code in Listing 9.2 to the CADOMFC1Doc::OnNewDocument function. (You should, of course, leave the existing code in OnNewDocument and merely add this code to it, perhaps near the beginning of the function.)


Listing 9.2.  Calling the DbComponent BeepIfDbIsOk Function

 1:  DbComponent * pDb = new DbComponent();
 2:  pDb->BeepIfDbIsOk();
 3:  delete pDb;

Now try to build the ADOMFC1 application. You should receive two linker errors for unresolved external symbols. You will notice that one symbol, which corresponds to the BeepIfDbIsOk function, looks like this:

?BeepIfDbIsOk@DbComponent@@QAEHXZ

The other symbol, which corresponds to the constructor for DbComponent class, looks like this:

??0DbComponent@@QAE@XZ

You are seeing C++ function name decorating in action. When the Visual C++ compiler builds ADOMFC1Doc.obj, it decorates the names of the DbComponent constructor and the BeepIfDbIsOk function. The linker then tries to find the code for those functions, but it cannot, and it generates the unresolved external symbol errors.

If a DLL existed with the DbComponent code in it, the DbComponent constructor and the BeepIfDbIsOk function would need to exist in the DLL with the decorated names that the Visual C++ linker expects. Otherwise, the linker would not find them and would still generate unresolved external symbol errors.

Unfortunately, function decorating is not uniform among C++ compilers. C++ functions exported from a DLL built with Visual C++ cannot be called in an application built with Borland C++. A Visual C++ DLL and a Borland C++ EXE are incompatible, at least in terms of C++ function calls.

NOTE
You can disable C++ name decorating for functions that you export from a DLL, by placing the DLL within external "C" { } blocks. This causes the exported functions to have the signature of a standard C language function. This will enable your DLL functions to be called from EXEs built with other compilers but will cost you the C++ features and the object-oriented nature of your component.

Therefore, if you put your C++ component in a traditional Win32 DLL, it can be used only in software built with the same compiler that you used.

Hard-Coded DLL Names

Whether an application uses implicit load-time linking or explicit runtime linking to call functions in a DLL, the DLL's name is hard-coded into the application's EXE file. This results in a few limitations from a component software standpoint.

The DLL file must exist somewhere that the OS can find it. One place is the application directory. However, if the DLL file existed in the application directory, no other application on the machine would be able to find it.

Another place to put the DLL might be the Windows System directory. If the DLL exists in the Windows System directory, it runs the risk of being overwritten by, or at least conflicting with, another DLL that happens to have the same name.

A true software component needs to be safely installable on any machine and accessible to all the appropriate applications on that machine. Hard-coded DLL names in the application EXE files are detrimental to this.

Build-Time Dependencies Between the DLL and the EXEs That Use it

The most common way for applications to use a DLL is to link with its import library (implicit load-time linking). The other method-explicit runtime linking with the LoadLibrary and GetProcAddress functions-is not used nearly as often. Using LoadLibrary and GetProcAddress isn't as convenient for the application developer as simply linking to the DLL's import library and having the OS automatically load the DLL.

However, a problem occurs when it comes time to update the DLL. A true software component can be updated independently of the rest of the system. In other words, you can install and use a new version of a component without breaking the existing system.

With traditional Win32 DLLs, when you modify the code and rebuild the DLL, the information in the import library can change. You will recall that the import library contains the function names exported from the DLL. The import library also contains import records for those functions that are fixed up when the DLL is loaded and the addresses are known.

This means that when a DLL is updated, the applications that use the DLL (by linking with the DLL's import library) need to be rebuilt also to ensure that the system is stable. Therefore, a build-time dependency exists between the application EXE and the DLLs that it uses.

NOTE
It might be possible to update a DLL without updating the EXEs that use it, if you don't change any existing functions in the DLL. However, there is no mechanism for the EXE to gracefully recover if the DLL does become out of sync. Also, replacing an existing DLL with an older version quite often causes problems that the application cannot deal with gracefully. The ease with which this problem can occur and the lack of mechanisms to enable a grace-ful recovery at runtime mean that, for most practical purposes, there is a build-time dependency between an EXE and the DLLs it uses.

With traditional Win32 DLLs, you cannot simply plug a new version of the DLL into an existing system without the risk of breaking it. If you place a new DLL in an existing system without rebuilding the applications that use the DLL, the applications could crash because of changes in the functions in the DLL.

Building Software Components by Using COM

COM addresses the limitations of traditional Win32 DLLs for component development. Using COM, you build software components that

COM uses some very nifty tricks to solve the problems of building component software. With COM components, there are none of the following:

COM accomplishes these technical feats through a rather ingenious application of some relatively simple technologies. You will explore these technologies next.

Using C++ Abstract Base Classes

Modify the code that you entered from Listing 9.1 in DbComponent.h so that it looks like the code in Listing 9.3.


Listing 9.3.  The Abstract Base Class DbComponent Class Declaration

 1:  class DbComponent
 2:  {
 3:  public:
 4:    virtual int BeepIfDbIsOk()=0;
 5:  };

You can see that DbComponent is now an abstract base class. As you know, an abstract base class is a class that contains at least one pure virtual function.

You specify a virtual function as pure by placing = 0 at the end of its declaration. You don't have to supply a definition for a pure virtual function.

You cannot declare an instance of an abstract base class; you can use it only as a base class from which to derive other classes.

Try to build the application now. You will receive an error from the compiler indicating that you cannot instantiate abstract class DbComponent because the BeepIfDbIsOk is a pure virtual function.

Modify the code you entered from Listing 9.2 (in the OnNewDocument function) so that it looks like the code shown in Listing 9.4


Listing 9.4.  Calling the DbComponent BeepIfDbIsOk Function

 1:  DbComponent * pDb;
 2:  pDb->BeepIfDbIsOk();
 3:  delete pDb;

Try to build the project now. It should compile and link with no errors, though you might receive a warning about using a local variable before it's initialized. Of course, you should not try to run the application, because it will fail. The DbComponent pointer defined in line 1 of Listing 9.4 is uninitialized. Line 2 tries to use this uninitialized pointer to call the BeepIfDbIsOk function.

The code will not run. However, perhaps somewhat surprisingly, the code will successfully compile and link. There is no code for the DbComponent class and its BeepIfDbIsOk function, so why did the application link successfully? How did the linker bind code that doesn't exist?

The short answer is that the linker didn't bind it. Because the DbComponent class is an abstract base class, and because BeepIfDbIsOk function is a pure virtual function, the linker knew that the code for line 2 of Listing 9.4 would be bound at runtime. Listing 9.5 is an example of runtime binding.

As you know, you can call a virtual function through a base class pointer to execute the function in a derived class. This is classic polymorphism in C++. See the code in Listing 9.5 for an example.


Listing 9.5.  Polymorphism in C++

 1:  #include <iostream>
 2:  using namespace std;
 3:  class myBaseClass
 4:  {
 5:  public:
 6:    virtual ~myBaseClass() { };
 7:    virtual void myFunc();
 8:  };
 9:
10:  class myDerivedClass : public myBaseClass
11:  {
12:  public:
13:    void myFunc();
14:  };
15:
16:  void myBaseClass::myFunc()
17:  {
18:    cout << "Executing myFunc in myBaseClass" <<endl ;
19:  }
20:
21:  void myDerivedClass::myFunc()
22:  {
23:    cout << "Executing myFunc in myDerivedClass" <<endl ;
24:  }
25:
26:  int main()
27:  {
28:    myBaseClass * myBaseClassPtrs[4];
29:    myBaseClassPtrs[0] = new myBaseClass;
30:    myBaseClassPtrs[1] = new myDerivedClass;
31:    myBaseClassPtrs[2] = new myBaseClass;
32:    myBaseClassPtrs[3] = new myDerivedClass;
33:
34:    for( int i = 0; i < 4; i++)
35:    {
36:      myBaseClassPtrs[i]->myFunc();
37:    }
38:
39:    delete myBaseClassPtrs[0];
40:    delete myBaseClassPtrs[1];
41:    delete myBaseClassPtrs[2];
42:    delete myBaseClassPtrs[3];
43:    return 0;
44:  }

Create a new Win32 console project in Visual Studio named Polymorph. Specify that AppWizard should create an empty project. Create a source file called main.cpp (specify that main.cpp should be added to the Polymorph project). Enter the code shown in Listing 9.5 in main.cpp

Lines 3-8 declare a base class named myBaseClass that has a virtual function named myFunc. Lines 10-14 derive a class from myBaseClass named myDerivedClass. Line 13 places a function named myFunc in myDerivedClass, which overrides myFunc in myBaseClass.

Lines 16-24 define the myFunc functions to simply output a string indicating that they are being executed. Line 28 defines an array of four pointers to myBaseClass class. Lines 29-32 initialize the pointers in the array by creating alternating instances of myBaseClass and myDerivedClass. Lines 39-42 delete the instances of the classes to free up the memory.

Lines 34-37 call the myFunc function through the pointers to myBaseClass. Note that you are calling a function in a derived class through a base class pointer. MyBaseClassPtrs[1] and myBaseClassPtrs[3] are pointers to myBaseClass but actually point to instances of myDerivedClass.

Build the Polymorph project. It should build without errors or warnings. Run Polymorph. The code in Listing 9.5 will produce this output:

Executing myFunc in myBaseClass
Executing myFunc in myDerivedClass
Executing myFunc in myBaseClass
Executing myFunc in myDerivedClass

Which class's myFunc was executed? This was determined at runtime, using C++ virtual function tables, or vtables (pronounced vee-tables).

In instances of myBaseClass, the vtable entry for myFunc points to myBaseClass's myFunc. In instances of myDerivedClass, the vtable entry for myFunc points to myDerivedClass's myFunc.

Using vtables, the question of which class's myFunc to execute is resolved at runtime. Therefore, the binding of that code doesn't happen at link time. It happens at runtime.

NOTE
Calling a derived class's functions through a pointer to its base class is an essential feature of COM.

This is a cool trick. Polymorphism is one of the pillars of object-oriented programming. Polymorphism's runtime binding is also one of the pillars of COM.

Open your ADOMFC1 project in Visual Studio. You will recall that the code in the OnNewDocument function defines a pointer to the abstract base class, DbComponent (refer to Listing 9.4).

You made it an abstract base class because if a DLL existed with the DbComponent object code in it, the DLL would need to have the DbComponent BeepIfDbIsOk function with the exact decorated name that the Visual C++ linker expects. Otherwise, the linker would not find it and would generate an unresolved external symbol error. Using an abstract base class eliminates this need for a compiled symbol with a name decorated the way Visual C++ expects.

NOTE
Using abstract base classes eliminates the problem of incompatible C++ function decorating between C++ compilers. Also, the code that implements these abstract base classes doesn't need to be present when applications that use the abstract base classes are built.

The code in your OnNewDocument function in Listing 9.4 attempts to call the BeepIfDbIsOk function through the DbComponent pointer, but there is another problem. The DbComponent pointer in the OnNewDocument function is uninitialized-and you can't create an instance of the DbComponent class because it's an abstract base class.

How can the code in your OnNewDocument function get a valid DbComponent pointer so that it can call the BeepIfDbIsOk function?

Creating Objects by Using an API Function

It is impossible to create an instance of DbComponent (because it's an abstract base class). However, it is possible to create an instance of a class that is derived from DbComponent. That derived class could reside in a COM DLL (I will describe what a COM DLL entails later today in the section on COM servers).

The derived class could override the BeepIfDbIsOk function in DbComponent and implement it with code to beep if the database is okay. The derived class could be named something like DbComponentImpl.

If you could somehow create an instance of DbComponentImpl, and if you could assign its address to the DbComponent pointer in your OnNewDocument function, you could call the DbComponentImpl's BeepIfDbIsOk function through your DbComponent pointer.

It would be very handy in this case to have some Windows API function that creates instances of classes for you. You could tell it that you want an instance of DbComponentImpl and that you want to assign its address to the DbComponent pointer in your OnNewDocument function.

For example, the code could look something like that shown in Listing 9.6.


Listing 9.6.  Calling the DbComponent BeepIfDbIsOk Function

 1:  DbComponent * pDb;
 2:  ::CreateInstance(DbComponentImpl_ID, (void**)&pDb);
 3:  pDb->BeepIfDbIsOk();
 4:  pDb->Release();

Line 2 in Listing 9.6 calls an imaginary Windows API function named CreateInstance. Line 2 passes an (imaginary) identifier to the CreateInstance function to indicate that it should create an instance of DbComponentImpl. The function returns a pointer to the DbComponentImpl instance in the second parameter, pDb.

This CreateInstance function would load the DLL containing the DbComponentImpl code, create an instance of the DbComponentImpl class, and assign its address to pDb. After the CreateInstance call, you would have a valid pDb pointer (which is a pointer to DbComponent) that you could use to call the DbComponentImpl BeepIfDbIsOk function, as shown in line 3.

Line 4 in Listing 9.6 calls an imaginary Release function to delete the object. You shouldn't use the delete operator on pDb because DbComponent doesn't have a virtual destructor. The destructor for DbComponent probably wouldn't be capable of properly cleaning up an instance of the DbComponentImpl class. The Release function in line 4 is an imaginary function that is capable of cleaning up an instance of DbComponentImpl.

Using a CreateInstance function like this would enable you to call the member functions of the DbComponent class. You would actually be executing code that resides in the DbComponentImpl class. The really big news is that the code for the DbComponentImpl class would not have to be present when you build your application. Also, the name of the DLL is not hard-coded into your application's EXE file image. Your application can use the code in the DLL without being tied to that particular DLL file.

NOTE
Calling an API function in your application code to create instances of components eliminates the problem of having DLL names hard-coded into the EXE file image of your application.

There are, in fact, Windows API functions that work like the CreateInstance function in Listing 9.6. These functions are part of the Windows COM libraries. The most frequently used function like this is the COM CoCreateInstance function.

Also, the Release function shown in line 4 of Listing 9.6 is authentic. COM components always implement a Release function to enable applications to delete (or free) them.

Actually, COM components free themselves. The purpose of Release is to allow the client application to announce that it won't be using the component anymore. This might result in the component deleting itself, if no other client applications are using it.

You will recall that in Day 4, you used the CoCreateInstance function. The smart pointer class's CreateInstance function internally calls CoCreateInstance. It also calls Release when the pointer goes out of scope, so you don't have to call it. Refer to the following days and their listings for examples of where you used a smart pointer class's CreateInstance function:

Day
Listings
4
4.1, 4.2, 4.6, and 4.8
5
5.1, 5.2, and 5.3
6
6.7 and 6.8

Using abstract base classes to declare the class in the client application, and calling API functions to load the DLL and instantiate the class, makes the application and the COM DLLs that it uses independent of each other.

Because all the code doesn't need to be present at the time the software is built and because the DLL names are not hard-coded in the EXE file image, you have more flexibility in updating the software. Single EXE or DLL files can be replaced with newer versions, without the need to rebuild and replace all the EXE and DLL files in the system every time.

NOTE
Breaking the build-time and load-time dependence between EXE files and DLL files enables them to be updated independently of each other.

COM Clients and COM Servers

In previous Days, you learned a few things about client/server systems. You learned that in client/server database applications, which run on multiple computers over a network, the machine where the database resides is called the server, and the machines that run the apps that use the database are called the clients.

A similar terminology exists in the COM world. In this example, the application that calls the BeepIfDbIsOk function would be called the COM client. The DLL that contains the DbComponentImpl class (and the BeepIfDbIsOk code) would be called the COM server.

In COM, a component that provides functions for other applications to call is a server.

An application that calls functions provided by COM components is a client.

So far today, you've learned a little about what the code looks like in COM clients. COM clients use abstract base classes to declare the COM components they use.

The abstract base classes that applications use to declare COM components are called COM interfaces.

COM clients call Windows API functions to create instances of the (server) component classes. COM clients typically must call Release to free the components when they are done with them. COM clients written in Visual C++ can also use a smart pointer class that internally calls CoCreateInstance and Release.

You haven't yet had much opportunity to see what the code looks like for COM servers. That is the topic of the next section.

COM Servers

COM servers have some required functions that they must implement and export and some required registry entries that they must make. The next two sections explain these requirements.

Registry Entries

You will recall that COM clients call a Win32 API function to create instances of classes from COM DLLs. How does the Win32 subsystem know which DLL contains the code for the classes?

The answer is that the COM libraries (part of the Win32 subsystem) look in the registry for the name of the DLL and the DLL's location. You will recall that in Listing 9.6, when the COM client called the API function to create the class instance, it passed in an identifier to tell the API function which class it wanted an instance of. That identifier is called a GUID.

A GUID is a globally unique identifier. It is a 128-bit number that is guaranteed to be unique. Microsoft provides a tool for generating GUIDs, called Guidgen.exe. It uses the worldwide unique ID of the computer's network card, combined with the current date and time, to create numbers that are always unique.

Figure 9.3 : Guidgen.

If the computer doesn't have a network card, the GUID is guaranteed to be unique on that computer and statistically unique across computers. This means it's very unlikely, but possible, for such a GUID to duplicate an existing GUID.

You can typically find Guidgen.exe in your Visual C++ Tools\Bin directory. Run Guidgen so that you can see what a GUID looks like (see Figure 9.3).

As you can see in the Result pane in the Guidgen window, GUIDs are simply 128-bit (16-byte) numbers. Guidgen makes it easy for you to generate GUIDs and copy them to the Clipboard. From there, you can easily paste them into your source code for use in building COM components.

A CLSID is a GUID that identifies a class. In every COM component, each class and each interface (remember, COM interfaces are C++ abstract base classes) is assigned a GUID. When a GUID is used in this context, it is called a CLSID.

The CLSIDs of the COM server classes are stored in the registry, under HKEY_CLASSES_ROOT\CLSID. You can best understand these registry entries by looking at a real-life example.

Suppose you want to create an instance of an ADO Connection object, as you did in Day 4 in Listing 4.1 and Listing 4.6. In this scenario, the ADO Connection object would be the COM server, and the application you are writing would be the COM client.

To create the object, you write some code that calls CoCreateInstance or the smart pointer class's CreateInstance function and pass it the CLSID of the ADO Connection object.

The code for the CoCreateInstance (in COM library) looks up that CLSID in the registry. Figure 9.4 shows the information in the registry for that CLSID.

Figure 9.4 : Registry entries for the ADO Connection COM object.

As you can see in Figure 9.4, under this CLSID, there is an entry called InprocServer32. The InprocServer32 entry indicates that the ADO Connection object is an in-process server, meaning the COM server is contained in a DLL. The location of the DLL is shown as

"C:\Program Files\Common Files\System\ado\msado15.dll"

When ADO was installed on this machine, this entry for ADO Connection object was placed in the registry. This registry entry is what enables applications to use the code in the DLL, without hard-coding the DLL name in the application EXE file image.

You can find this entry yourself on your machine. Open the Registry Editor and do a Find on the key ADODB.Connection. Under the ADODB.Connection key is a CLSID subkey. This entry contains the ADO Connection's CLSID. Next, do a Find on the key for this CLSID. You will find the entry shown in Figure 9.4.

The COM libraries use this registry entry for the CLSID to find the DLL filename and location. COM then calls LoadLibrary to load the DLL.

After the DLL is loaded, COM needs to create an instance of the ADO Connection object. To do this, COM needs some help from functions in the DLL.

Required Server Functions

When you call CoCreateInstance to create an instance of the ADO Connection object, how does COM know how to create instances of the ADO Connection object?

The answer is, it doesn't. However, COM does know how to call a standard function, which is implemented in all COM server DLLs, to get a pointer to an interface for an object that can create instances of ADO Connection. In other words, every COM DLL must export a standardized function that the OS can call to create instances of its classes.

The function name is DllGetClassObject. Its prototype is

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)

STDAPI is merely a macro that resolves to an HRESULT and a calling convention. DllGetClassObject actually takes two GUIDs as parameters, the first one being the CLSID and the second one being the GUID for a particular interface that object supports. COM calls DllGetClassObject with the parameters necessary to get a pointer to a class factory interface from the DLL.

A class factory is a class that knows how to create instances of other classes.

A class factory is implemented in every COM server. The class factory implements the IClassFactory interface, which includes the CreateInstance function. COM can call the CreateInstance function to create COM objects (instances of COM server classes).

The class factory in msado15.dll knows how to create ADO Connection objects. After COM loads the DLL by calling LoadLibrary, it calls GetProcAddress to get the address of DllGetClassObject. COM then calls DllGetClassObject to get a pointer to the IClassFactory interface.

Figure 9.5 : How a COM client obtains a pointer to a COM server.

After COM gets a pointer to the class factory in msado15.dll, COM calls the class factory CreateInstance function, passing it the ADO Connection CLSID to create an instance of the ADO Connection class. Finally, COM returns the pointer to the ADO Connection object the client application that called CoCreateInstance.

The process of a client calling CoCreateInstance to get a pointer to a COM server is illustrated in Figure 9.5.

You can see from Figure 9.5 that two calls are made into the COM server DLL. The DllGetClassObject function is called and the class factory CreateInstance function is called. That means a DLL that contains COM server(s) must implement the DllGetClassObject function, as well as a class factory class, in order to work.

A COM DLL needs three other functions in order to implement and expose. These functions are

Therefore, a DLL that contains COM server(s) must implement four functions (DllGetClassObject, DllRegisterServer, DllUnregisterServer, and DllCanUnloadNow) and one class (the class factory).

You can implement all this code yourself in every COM DLL you create, or you can use a tool that implements this code for you. You will next explore a tool that does most of this work for you. That tool is called ATL.

The Active Template Library (ATL)

You will learn more about ATL in Day 11, "Multitier Architectures." Today you will simply use the ATL Wizard to create a COM DLL that contains a COM server. You will see that ATL writes the code for you for the four required functions and the required class factory class in a COM DLL.

ATL is inspired by the C++ Standard Template Library (STL). To make it easy to create COM components, ATL uses C++ templates.

Figure 9.6 : A new ATL COM AppWizard project.

Despite ATL's use of templates, you don't need to use templates in your own code in order to use ATL. ATL provides two wizards and several default COM object types that generate much of the template code for you. With ATL, you are able to concentrate primarily on the implementation of your code and don't have to worry about writing very much of the plumbing that COM needs.

Create a new project in Visual Studio. Specify an ATL COM AppWizard application and ATLTest1 as the project name, as shown in Figure 9.6.

Click the OK button. In the next dialog, specify a server type of DLL, as shown in Figure 9.7. You can build COM servers into an EXE or an NT service, but don't worry about that yet. You will learn about COM servers in EXEs later today.

Check the check boxes for Allow Merging of Proxy/Stub Code and for Support MFC. Don't check the box for supporting MTS. You will learn more about MTS in Day 12, "Using Microsoft Transaction Server to Build Scalable Applications." Click the Finish button and then click the OK button to generate code for the project.

Figure 9.7 : Options for the ATL COM AppWizard project.

Figure 9.8 : The ATL Object Wizard.

After the wizard generates the code, select the Class View and expand the list of Globals in the tree control. You will see that the four required functions for COM DLLs have been generated for you.

Now you can build your own COM server(s) in this DLL. Select the InsertNew ATL Object... menu to open the ATL Object Wizard shown in Figure 9.8. The icons in the wizard might look different from those in Figure 9.8, depending on which version of Visual Studio you are using.

Select Simple Object from the pane on the right and click the Next button. You will then be presented with the ATL Object Wizard Properties dialog shown in Figure 9.9.

Figure 9.9 : The ATL Object Wizard Properties Names tab.

The Names tab is initially selected, as in Figure 9.9. Enter DbComponent as the short name. The other text boxes should fill in automatically.

Select the Attributes tab and select the radio buttons shown in Figure 9.10.

Figure 9.10: The ATL Object Wizard Properties Attributes tab.

Threading Model refers to the type of threading your COM server will support. Apartment is the default and will be fine for now.

Interface refers to whether your COM server will provide a dual interface so that it can be used both from scripting languages and from C++ or whether it will provide a custom interface only, which cannot be used from scripting languages. ATL gives you the dual interface for free, so you might as well take it.

Aggregation refers to whether your COM server will be aggregated inside other COM servers. It will not, so select No.

You also will not need support for ISupportErrorInfo, Connection Points, or the Free Threaded Marshaler. Do not check any of these check boxes.

When you click the OK button, the wizard will generate code for the DbComponent class, which will be a COM component that's housed in the DLL.

After the code is generated, you will see in the Class View an entry in the tree control called IdbComponent. I stands for Interface. This is the interface for the DbComponent COM server. IDbComponent will become a C++ abstract base class for clients that want to use DbComponent.

Right-click IDbComponent in the tree control and select the Add Method… menu. This will open the Add Method to Interface dialog shown in Figure 9.11.

Figure 9.11: The Add Method to Interface dialog.

Enter BeepIfDbIsOk for Method Name. Return Type is always an HRESULT for ATL COM functions. Leave the Parameters edit box empty. Click OK.

After you click OK, ATL will generate the infrastructure for this function. In the Class View, expand the CdbComponent; then expand the IDbComponent under CDbComponent. Under IdbComponent, you will see an entry in the tree control for the BeepIfDbIsOk function. This points to the code for this function in the COM server. Double-click BeepIfDbIsOk to edit its source code.

Edit the code for the BeepIfDbIsOk function so that it matches the code in Listing 9.7.


Listing 9.7.  The BeepIfDbIsOk Function

 1:  STDMETHODIMP CDbComponent::BeepIfDbIsOk()
 2:  {
 3:    AFX_MANAGE_STATE(AfxGetStaticModuleState())
 4:
 5:    // Good'Die Mite. 'Looks lock the die-ta bise is oak eye.
 6:    ::MessageBeep(MB_OK);
 7:
 8:    return S_OK;
 9:  }

You can see that this code isn't really doing anything to check a database to see whether it's okay. This is just a simple function that you can use to begin your discovery of COM programming.

Lines 5 and 6 are the only lines you need to add. The ATL Wizard automatically puts the rest of them there. You can see from line 5 that this code could be written in the land down undah. COM components can be written and used anywhere on the planet. Line 6 simply beeps.

Build your ATLTest1 project. It should build without errors or warnings. The build process will generate an ATLTest1.h file that contains the abstract base class (IDbComponent) that the COM clients will use. The build process will generate another file called ATLTest1_i.c, which holds the GUIDs that the COM clients will need in order to use this COM server.

The build process will run RegSvr32.exe for you to register the COM server DLL. This means that after each successful build, you have a COM server that has been registered on your machine and is ready to be used by COM clients.

You have now created a COM server component that can be called from COM clients. To try it out, copy the ATLTest1.h file and the ATLTest1_i.c file into the directory with your ADOMFC1 project. Open your ADOMFC1 project in Developer Studio.

Change the CADOMFC1Doc.cpp file so that it includes the ATLTest1.h file and the ATLTest1_i.c file (instead of the DbComponent.h file), like this:

#include "ATLTest1_i.c"
#include "ATLTest1.h"

Change the code in the OnNewDocument function so that it calls the BeepIfDbIsOk function in your COM server (see Listing 9.8). (You should, of course, leave the existing code in OnNewDocument and merely add this code to it-perhaps near the beginning of the function.)


Listing 9.8.  Calling the COM Server BeepIfDbIsOk Function from the OnNewDocument Function

 1:  hr = CoCreateInstance(CLSID_DbComponent, NULL,
 2:  CLSCTX_INPROC_SERVER, IID_IDbComponent,
 3:  (void**) &pDb);
 4:
 5:  if (SUCCEEDED(hr))
 6:  {
 7:    pDb->BeepIfDbIsOk();
 8:    pDb->Release();
 9:  }

Line 3 of Listing 9.8 defines a pointer to IDbComponent, the abstract base class that is the interface to the COM object. IDbComponent is declared in ATLTest1.h.

Lines 5, 6, and 7 call CoCreateInstance. The first parameter is the CLSID for this component. The second parameter is for the aggregating IUnknown interface and is NULL (except when using aggregation). The third parameter tells COM that you are working with a COM server in a DLL. The fourth parameter is the GUID for the interface you are requesting. (In this case, the CLSID for this component and the GUID for the interface are actually the same.) The last parameter is where the pointer to the instance of the class will be returned.

If the call to CoCreateInstance succeeds, line 10 calls BeepIfDbIsOk, and line 11 calls Release to free the COM server. Listing 9.8 does not check for or handle errors for the sake of code brevity and clarity.

Remember that you called the AfxOleInit function in CADOMFC1App::InitInstance to initialize the COM libraries. You must always do this before calling COM functions (such as CoCreateInstance), or they will fail.

The software should run without a hitch. You should be able to set a break point in the client code and step into the server code in the DLL, just as you can when COM isn't involved. Also, of course, the program should beep as expected. No worries.

NOTE
Every COM interface is inherited from IUnknown, which has three functions:
QueryInterface, AddRef, and Release.

You have now written a COM server component and a COM client application. Congratulations. You also understand COM at its foundation, which will enable you to understand more about COM later.

IUnknown, QueryInterface, AddRef, and Release

You might be wondering about that Release function call in line 12 of Listing 9.8. You didn't declare or implement a Release function in your DbComponent class for the COM server. How did Release become part of IDbComponent?

It might not be readily apparent in this example, but every COM interface (IDbComponent included) is derived from an abstract base class called IUnknown. IUnknown has three member functions: QueryInterface, AddRef, and Release.

QueryInterface enables a COM client to query a COM server to see whether the server supports the requested interface. The implementation of QueryInterface is standard among COM servers, and ATL implements it for you. If you weren't using ATL (or some other tool that automates the process of creating COM server code), you would have to write an implementation of QueryInterface into your server code.

The same thing goes for AddRef and Release. These two functions provide usage counts for COM servers. Their implementations are pretty standard. ATL writes the code for these functions as well, so you don't have to in your COM server code.

Interface Definition Language

I mentioned earlier today that you could build COM servers into an EXE. This means that the COM client, which is an EXE in the example you just went through, can call functions in another EXE. COM enables function calls across process boundaries.

The programming model is identical, whether your COM client is talking to a COM server in a DLL or in an EXE. About the only difference on the client side is the third parameter it passes to CoCreateInstance-CLSCTX_LOCAL_SERVER instead of CLSCTX_INPROC_SERVER. The difference at runtime is that making function calls across process boundaries can be about 50 times slower than making function calls within the same process. Nevertheless, the ability to call functions in an EXE from another EXE is quite a feat.

To accomplish this feat, COM has to make each EXE believe it's talking to code inside its own address space. COM creates a local proxy of a server inside the client's address space. The client talks to the proxy, the proxy talks to COM, and COM talks to the server EXE.

With COM crossing process boundaries like this, it was necessary to create some standard formats for sending function calls and their parameter values and types between client and server EXEs. Also needed was an object that understands how to pack and unpack the specific parameters of an interface's functions. This is called marshalling.

Proxy/stub objects, which are created by the MIDL compiler, handle this marshalling. The input to the MIDL compiler is Interface Definition Language (IDL).

You can see what IDL looks like by opening the ATLTest1 project and double-clicking the IDbComponent interface in the Class View. This will open ATLTest1.idl.

You can think of IDL as C++ header files on steroids. IDL defines the interface classes, their functions, and the functions' parameters, just as C++ headers do. IDL also specifies whether the parameters are In, Out, or Both, indicating whether a parameter is used by the client to pass in a variable that's filled in or modified by the server.

Automation (Formerly Called OLE Automation)

Automation is the name for a standard COM interface named IDispatch. If you look at ATLTest1.idl, you will notice that IDbComponent is derived from IDispatch (IDispatch is derived from IUnknown).

Remember that you told ATL that you wanted this COM server to provide a dual interface (refer to Figure 9.10). A dual interface enables your server to be used by clients such as scripting languages.

The IDispatch interface has a member function named Invoke. Invoke takes a function name, or ordinal value that represents a function, as a parameter and invokes the function on behalf of a COM client that can't call the function directly itself.

Calling functions in a COM server through IDispatch Invoke is a bit slower than making direct calls to a function through a pointer. However, the IDispatch interface opens up a COM server so that languages without pointers can use it.

If your COM server has a dual interface, it supports both the direct method through pointers and the indirect method though the IDispatch interface. ATL does all the work of implementing the IDispatch functions, so there's often no cost to supporting Automation in your COM server.

There is a need, however, for your COM server to use only the data types supported by Automation in its function parameters. This typically isn't a problem, but you should check the Automation types in the COM/VC++ documentation to make sure they meet the requirements of your server.

COM Type Libraries

If a COM server supports Automation, it can be used from client applications written in a wide variety of programming languages. If all that a COM server provides to document its interface is a C++ header file, that might not help some of the clients that want to use that server.

A type library is a language-independent header file.

A type library describes the interfaces to a COM server in a way that can be understood by most modern programming languages (on the Windows platform). ATL automatically creates a type library for COM servers. The type library ATL created for ATLTest1 is in the file ATLTest1.tlb.

You have actually used a type library already. When you used the #import directive with ADO in Days 4, 5, and 6, you were using the ADO type library. The ADO type library is stored as a resource in the ADO DLL file. The #import directive reads the type library from the resource and creates C++ classes for you that correspond to the interfaces described.

Summary

Today you learned the basics of COM. You learned that COM is based on C++ abstract base classes, called interfaces. You also learned that the COM libraries (part of the Win32 API) provide a way to create instances of classes indirectly, thereby avoiding any build-time dependencies between clients and servers.

Traditional DLLs are good for sharing common code among concurrently running applications. However, to build real component-based software, the capabilities afforded by COM are essential.

Q&A

Q
What is the difference between COM and OLE?
A
These names (acronyms) are a bit historical. OLE is the name for the original technology when it was introduced several years ago. OLE now applies mostly when talking about controls, in-place activation, and other application-level technologies. COM is more foundational and deals with the base parts of the technology. Today you learned about COM, not OLE.
Q
How does COM compare to other object technologies, such as CORBA and OpenDoc?
A
These object technologies have incredible depth and breadth. It would be difficult to provide an adequate comparison. However, in general, COM provides the easiest development model but not always the most robust performance. COM is most popular on the Windows platforms. CORBA finds most of its adherents on the UNIX platforms. For a while, it looked as though OpenDoc would be supported on the IBM platforms (OS/2 and mainframes) and on the Macintosh platform, but support for OpenDoc has waned considerably.
Q
Does MFC provide classes and wizards for COM?
A
Yes, it does. However, much of MFC's support is in two areas: OLE controls, both for building them and for using them, and Automation, both for building Automation servers and for building Automation clients. The OLE support in MFC is particularly helpful when you are building OLE components that have heavy user interface (UI) requirements. ATL is best used for COM components that have few, or no, UI requirements.

Workshop

The Workshop quiz questions test your understanding of today's material. (The answers appear in Appendix F, "Answers.") The exercises encourage you to apply the information you learned today to real-life situations.

Quiz

  1. Why can't you load a DLL into memory and send messages to it from your application?
  2. What makes a C++ class an abstract base class?
  3. What is a class factory?
  4. Why is it necessary for a COM client to call Release on a COM server after it's finished with it?
  5. What is a CLSID, and why must all CLSIDs be unique?

Exercises

  1. Add another method to the IDbComponent interface. Make this method take, as a parameter, an address to a variable of some sort. Modify this variable in the server code and make sure it gets back okay to the client.
  2. Use the ATL COM AppWizard to create a COM server in an EXE. Expose a function in its interface, similar to one in the DLL COM server. Compare the performance of the EXE-based COM server (the out-of-proc server) versus the DLL-based COM server (the inproc server).

© Copyright, Sams Publishing. All rights reserved.