Understanding the Component Object Model

Understanding
the
Component Object Model

Sample Chapters

By Steven Roman

Version 1.0.0

An electronic book from

The Roman Press, Inc.

www.romanpress.com

Copyright Ó 2000 by The Roman Press, Inc.

All Rights reserved.


About the Author

Dr. Steven Roman is an Emeritus Professor of Mathematics at the California State University, Fullerton. He has taught at a number of other universities, including MIT, the University of California at Santa Barbara, and the University of South Florida. He received his B.A. degree from the University of California at Los Angeles and his Ph.D. in mathematics (specializing in combinatorics and graph theory) from the University of Washington in Seattle.

Dr. Roman has written 35 books, including 10 books on mathematics, ranging from a series of three books on college algebra to graduate level and research level books on coding theory and various areas of abstract algebra. He has also written a series of 15 small books entitled Modules in Mathematics, designed for the general college-level liberal arts student.

Besides his mathematics books, Dr. Roman has written several books on computing, to wit:

  • Access Database Design & Programming, Second Edition, O’Reilly & Associates

  • Writing Word Macros, O’Reilly & Associates

  • Writing Excel Macros, O’Reilly & Associates

  • Developing Visual Basic Add-Ins, O’Reilly & Associates

  • Win32 API Programming with Visual Basic, O’Reilly & Associates

  • Concepts of Object-Oriented Programming with Visual Basic, Springer-Verlag

  • Understanding Personal Computer Hardware, Springer-Verlag

  • The Binary Book, an electronic book from The Roman Press, Inc.

  • Understanding the Component Object Model, an electronic book from The Roman Press, Inc.

Dr. Roman has also written several Windows applications, including Object Model Browser. When not writing books or software, he does programming for companies on a consulting basis.

Dr. Roman’s hobbies include making period furniture, copying Van Gogh paintings and listening to classical music.


Preface

The purpose of this book is to acquaint the Visual Basic and Visual C++ programmer (and perhaps other interested parties) with one of the most active, and certainly one of the most de riguer, aspects of the Windows environment — the Component Object Model, also known as COM.

COM is the underpinning for several technologies that have become the main focus of a very large contingent of the PC world. These technologies are ActiveX Automation, ActiveX controls and ActiveX documents.

I will assume that the reader of this book has some familiarity with Visual Basic or Visual C++ programming, although the book can be profitably read by anyone who is not afraid of reading a few lines of code in either language and is really interested in understanding what COM is about.

While the Visual Basic programmer cannot access all of the features of COM (for this you must program in a language such as Visual C++), he or she can do quite a lot, thanks to the release of Visual Basic Version 5.0 (and later versions). While previous versions of Visual Basic allowed the VB programmer to create compound documents and ActiveX (OLE) Automation servers and clients, Version 5 allows the creation of the most sophisticated (to date) COM software components — ActiveX controls.

The book is organized into four chapters. The first chapter presents an overview of COM from a relatively high level. The second chapter provides a much more detailed look at COM, with a more-or-less complete example of how a traditional COM server and client might be constructed, using a pseudo C++ like code, which should be readable by those who do not program in Visual C++. Chapter 3 takes a careful look at an ActiveX (COM) technology called Automation and Chapter 4 takes a look at ActiveX controls.

Let me reemphasize the fact that the goal of the book it to provide the reader with a solid understanding of the principles of COM, not the details. This is the information that will stand the Visual Basic and Visual C++ programmer is good stead. However, if you just want to be told how to program in COM, then this book is probably not for you.


Introduction


What is COM?

Briefy put, COM, which stands for Component Object Model, is a model that describes how software components can communicate with each other.

Generally speaking, a software component is a self-contained collection of code that is designed for a specific purpose. Put another way, a software component is designed to provide a set of related services. Software components come in many styles. For instance, a Dynamic Link Library (DLL) that exports one or more functions is a software component. A Visual Basic control (VBX or OCX), such as a text box or command button, is also a software component. Even a full-fledged application such as Microsoft Word can be thought of as a software component under certain circumstances.

A software component may be relatively small, such as a DLL with only a single function, or quite large, encompasing an entire application, such as a Visual Basic spreadsheet control. It may or may not have a visual interface. For instance, a VB text box has a visual interface, but a DLL does not have a visual interface.


Communication Between Software Components

It is hard to overemphasize the importance of communication between software components. We can summarize this importance with two observations.

  • These days, application programs have grown so large that no one person can be responsible for programming the entire application. Also, it is sometimes beneficial (or unavoidable) that different portions of a single application be programmed in different languages. Consequently, different portions of an application must be coded as separate software components, and therefore must communicate with each other in order to produce a complete application.

  • There is an incredible amount of duplication of services in the software world. For instance, it is hard to conceive of an application that does not have the capability of saving and loading files, or one that does not need to sort something or other. In order to avoid having to recode the same services over and over again, it is desirable to be able to use (or reuse) the same software components in many different applications. This is referred to as software component reuse. It provides application developers the ability to "plug in" components to provide common functionality that would otherwise require time-consuming and repetitive recoding.

The development and use of component software and the concomitant need for communication between components has been an evolutionary process. It did not begin with COM. We can see this development as a natural extension of the trend from old-style linear coding through modern object-oriented programming. In fact, we can even trace this development within the BASIC language itself.


Linear Programming with GOSUBs

Programmers of early versions of Basic (GWBASIC on the PC, for instance) will remember programs that were written, as a single component, in a more-or-less linear fashion, with an occassional GOSUB (or even GOTO) to break the monotony.


Programming with Procedures

Subsequent versions of Basic encouraged programmers to use procedures (subroutines and functions) to modularize a program, thus making it easier to read and to maintain. In this context, each procedure is a software component. (I will never forget the first time I typed the keyword SUB in the middle of a QuickBASIC program, only to see all of the code on the screen instantly disappear, to make room for my subroutine. I didn’t like it! I wanted to see all of my code at the same time.)

In this simple context, communication between software components (procedures) is easy. For instance, to communicate with the function

Function Sum(Number1, Number2)

just make the function call

Let X = Sum(1,2)

The simple rules for communication with a function are that you must supply arguments (of the correct data type) for each parameter in the function definition, as well as a place (of the correct data type) to put the return value.


Programming with Code Modules

The next evolutionary step was to allow code to be located in different files, or code modules. For instance, in QuickBASIC we can define a function in one code module

FUNCTION Sum(Number1, Number2)

Sum = Number1 + Number2

END FUNCTION

and use it in another module, provided that we include a DECLARE statement to inform QuickBASIC that Sum is a function name and not a variable name:

DECLARE FUNCTION Sum(Number1, Number2)

Let X = Sum(1,2)

The business of maintaining separate code modules for an application was somewhat of a pain in the neck under QuickBASIC, but is very natural in Visual Basic, where we can open and view several code module windows at one time. However, the rules of communication are a bit more envolved in Visual Basic. For instance, until version 4, you could not call a function defined in one form module from another form module. Beginning with version 4, you can do so, but only by making the function public and qualifying the function name in the calling module. For instance, if in Form2 we write

Public Function Sum(n1 As Integer, n2 As Integer) _ As Integer

Sum = n1 + n2

End Function

then in Form1 we can call this function as follows

Form2.Sum(2,3)

The point here is that there are always rules of communication between software components, even when they lie in the same application and are coded within the same development environment.


Programming with Class Modules

With the release of Visual Basic 4.0, programmers were encouraged to take another, rather significant conceptual step, and encode an application into separate classes. Distinct software components within a single application can communicate with each other by instancing objects and invoking methods. For example, here is a simple class module to sum integers

Option Explicit

' Class CPair - ordered pairs of integers

Private mxValue As Integer

Private myValue As Integer

Public Property Get yValue() As Integer

yValue = myValue

End Property

Public Property Let yValue(pyValue As Integer)

myValue = pyValue

End Property

Public Property Get xValue() As Integer

xValue = mxValue

End Property

Public Property Let xValue(pxValue As Integer)

mxValue = pxValue

End Property

Public Function Sum() As Integer

Sum = mxValue + myValue

End Function

To use this class module from another code module, we could proceed as follows

Private Sub Form_Load()

Dim Pair As New CPair

Pair.xValue = 5

Pair.yValue = 6

MsgBox Pair.Sum

Set Pair = Nothing

End Sub

There are some relatively sophisticated rules of communication taking place here, at least compared to QuickBASIC. In particular, to invoke the Sum function for a pair of integers, a new Pair object must be created and the value of its two properties must be set to the summands. Also, after use, the Pair object should be destroyed.

Since, as a Visual Basic programmer, you are familiar with the rules relating to object creation, this bit of communication between a client module (the Form module to which the Form_Load event belongs) and the server class (CPair) should seem pretty straightforward. In fact, if you have tried the next step in sophistication — getting an Visual Basic OLE Automation server to communicate with another program, or creating and using a Visual Basic AddIn, then you will appreciate how simple this little bit of communication really is!


The Big Leap — COM

The next step in software modularization and communication is a bold one, for it involves communication between software components that are separated perhaps both by language and by distance.

To be specific, software components may be written in any number of languages, such as C, C++, Visual Basic, Delphi or assembly language. To complicate matters even more, components may need to communicate over various types of distance barriers. Communication is easiest when the components are running in the same process and hence share in the same memory space. However, often components are running in different processes, which imples that they must overcome the natural barriers to communication, set up specifically to prevent one process from interferring with another. Software components may even reside on different machines, and be required to communicate over networks, such as the Internet.


Chapter 1 — An Overview of COM

The purpose of this chapter is to give an overview of the Component Object Model (COM) and its ActiveX technologies, such as ActiveX Automation and ActiveX controls. In the next chapter, we will take a much deeper look at COM. In subsequent chapters, we will look more closely at the ActiveX technologies.

It might be reasonable to think of COM as having three distinct parts: the COM concept, the COM design and the COM implementation (although there are many ways to implement COM and it can be done using different programming languages). It seems fair to say — although I am sure that I would get an argument from many — that the basic concept of COM is quite simple, the design of COM has a certain modest elegance, but the implementation of COM is somewhat of a nightmare. Fortunately, our emphasis in this book will be on the concept and the design of COM, not on its implementation (which requires an in-depth knowledge of C or C++, as well as the Windows programming environment).


Providing Services By Exposing Functions

As we have said in the Introduction, COM is a model that describes how software components can communicate with each other. A software component is a piece of code that is designed to provide a set of related services. Imagine, for instance, a software component that is designed to supply dictionary and thesaurus services. How should this component provide, say, a word look-up service?

Why, through a function, of course. The component should expose a function, that is, it should declare and implement a public function that can be called by other software components. It must make the function declaration public, so that other components will know how to call the function, that is, other components will know which parameters the function requires and how the function returns its value (in this case, the word’s definition). However, the implementation of the function, that is, how the function actually performs its service, need not be made public.

More generally, in order for a software component to supply a set of related services, it must expose a set of related functions. In our example, the component might expose the following dictionary services (functions)

LookUpWord()

AddToDictionary()

DeleteFromDictionary()

and the following thesaurus services

GetSynonyms()

GetAntonyms()

While it may not seem so at first glance, providing services through functions implements a very general and powerful model for software component communication.

To illustrate, suppose that we need a senario in which Component1 wants to be informed when some client invokes a service of Component2. To accomplish this, Component2 can define and implement a function that takes, as a parameter, the address of a function in Component1. The function in Component1 is called a callback function. Component1 then invokes this function in Component2, passing the address of the callback function to Component2, thus activating the "return path." Then, when a client calls a service in Component2, this component can not only perform the desired service for the client, but also call the callback function of Component1, thus informing it that a client has requested the service.

Indeed, this is precisely what happens in a Visual Basic event! For instance, when a user clicks a command button on a running Visual Basic form, the button (Component2) sends a message to VB (Component1) by invoking a callback function that was provided earlier to the button by Visual Basic. This signals Visual Basic that the user has clicked the button. Visual Basic can then execute whatever code was written by the programmer in the Click event of the command button.

Thus, we see that providing services through functions sets up quite a general model for software component communication. It is the Component Object Model that provides a standardized model for setting up these component services (functions), so that clients will know just how to invoke the services of other components and thus provide a standardized method of software component communication.


COM Objects and COM Classes

Software components that follow the COM specification for inter-component communication are often called COM components. There is also a recent trend to refer to COM components as ActiveX components. (Unfortunately, COM terminology seems to be in an almost constant state of flux.)

As we will see in the next chapter, typically, when a client of a COM component requests some services from that component, a COM object is created by the component. This COM object is an instance of a particular COM class. The COM class provides the actual implementation of the services requested by the client and the COM object can be used to invoke those services.

The term object is used in a somewhat different sense for COM as it would be used in a traditional object-oriented language. In fact, COM objects can be created using a non-object-oriented language, such as C, in which there are no classes or objects per se. However, COM is implemented most often (and most naturally) using C++, where a COM object is a true object and a COM class is a true class, in the object-oriented sense. We will discuss this further in the next chapter, where we will see actual examples of COM objects and COM classes.

We should also mention that the COM model itself provides a set of services, collectively referred to as the COM library, which are used to manage the COM model itself. We will discuss some of these services in due course.


Interface Confusion

The most fundamental concept in COM is that of an interface, for it is through interfaces that COM components communicate. Unfortunately, this term carries with it at least three separate (but highly related) meanings, adding to the confusion. However, once we have carefully described these meanings, the reader should have no trouble determining which one is most appropriate in a given context.


Definition 1

The simplest use of the term interface is to denote a collection of related functions, that is, functions that are designed to supply a set of related services. In keeping with an object-oriented spirit, we use the term method as a synonym for function.

For instance, our earlier component for supplying dictionary and thesaurus services might contain two interfaces — a dictionary interface consisting of the functions (services)

LookUpWord()

AddToDictionary()

DeleteFromDictionary()

and a thesaurus interface consisting of the functions

GetSynonyms()

GetAntonyms()


Definition 2

The main problem with the previous definition of interface is that it fails to make one of the most important distinctions in COM — the distinction between a function declaration and an implementation of that function.

To make that distinction clear, consider the following sorting function

Function Sort(a() as Integer, iSize as Integer) as Boolean

For i = 1 to iSize

For j = i+1 to iSize

If a(j) < a(i) Then swap a(i), a(j)

Next j

Next I

Sort = True

End Function

The first line in this definition

Function Sort(a() as Integer, iSize as Integer) as Boolean

is the function declaration. It supplies information on the number and types of parameters and the return type of the function. The body of the function

For i = 1 to iSize

For j = i+1 to iSize

If a(j) < a(i) Then swap a(i), a(j)

Next j

Next i

Sort = True

represents the implementation of the function. It describes how the function carries out its intended purpose.

Note that it is possible to alter the implementation of the function without changing the declaration. In fact, the current function implementation sorts the array a() using a simple selectionsort algorithm, but we could replace that sorting method with any one of a number of other methods (bubblesort, insertionsort, quicksort and so on).

Now consider a client of the sort function. The client only needs to know the function declaration in order to use the function. It need not know (and probably doesn’t want to know) anything about the implementation. Thus, it is the function declaration, and not the implementation, that forms the interface for the function. In summary, the function’s declaration describes its interface and the function’s body describes its implementation.

The signature of a function is the function name, its return type and the names, order and return types of its parameters. A function declaration is a clear way of describing the function’s signature.

Now, an interface can be defined as a collection of function signatures (or function declarations, if you wish) that are designed to supply a set of related services. For instance, here is a simple interface for a set of statistical services

Function Sort(a() as Integer, iSize as Integer) as Boolean

Function Max(a() as Integer, iSize as Integer) as Integer

Function Min(a() as Integer, iSize as Integer) as Integer

Function Mean(a() as Integer, iSize as Integer) as Single

Function Median(a() as Integer, iSize as Integer) as Single

Function StdDev(a() as Integer, iSize as Integer) as Single

Under the previous definition of interface, the interface above would simply be written

Sort()

Max()

Min()

Mean()

Median()

StdDev()

but this list clearly falls short of containing the information needed to call any of these functions.

While we may fall into the habit of referring to an interface simply as a set of related functions, it is extremely important to always maintain a clear distinction (at least in one’s mind) between a function’s interface and its implementation.


Definition 3 — COM Interfaces

COM components communicate with each other through COM interfaces. A COM interface is realized in the memory of a computer through the use of a special data structure called a virtual function table, abbreviated vTable or vtbl. This is nothing more than a table consisting of the addresses of the functions that make up the interface. These addresses can be used to invoke the corresponding functions.

Sometimes the term COM interface refers to the associated vTable. (A vTable is also referred to as a vTable interface.) However, this table does not contain the signatures of the functions — it contains only their addresses. Additional information is still needed in order to call any of the functions in a vTable, since the stack must be populated by the appropriate arguments to the function before it is called. We will investigate the nature of vTables in the next chapter.

Thus, we arrive at three common uses of the term interface — a set of related functions, a set of related function declarations and a table of function addresses (a vTable).


Picturing a COM Object

However we use the term interface, it is common to picture a COM object and its interfaces as shown in Figure 1.

Figure 1 - A COM object

The message of this picture is that the internal workings of a COM object (that is, its implementations) are hidden to the clients of the object, as though the object were a black box. The only public information about the COM object is its interfaces, along with some documentation that describes the precise nature of the services that the functions in the interfaces are intended to supply.

Let us emphasize the last point. While it is true that the signature of a function is sufficient information to call the function, it does not tell us why we might want to call the function in the first place. To illustrate, consider the function

Function ABC(x as Integer, y as Integer) as String

This is enough information to call this function, as in

MsgBox ABC(2,5)

but what does the function do? To learn this, the programmer must resort to some form of documentation, whether in written form or in electronic form in a help system. Thus, there are really two levels of knowledge required in order to effectively use the services (functions) in an interface. Concisely put, the programmer must know both the syntax of a function, given by the function's signature and the semantics of the function, given by the function's purpose. (Alas, all too often, the COM programmer seems to assume that the name of the function is sufficient documentation to describe the function’s purpose.)


The Binary Standard

The COM model provides a standard for COM interfaces, so that any client application that wishes to use any COM object knows exactly how to get information about the interfaces supported by that object and how to access the services of each supported interface. This standard is referred to as a binary standard in order to convey the idea that the standard is independent of any language or platform used to create the object.


The COM Interface Contract and Versioning

COM interfaces play a sacred role in the COM model. In fact, there is an implicit contract between COM and its programmers that says that a COM programmer will never change a COM interface once a component using that interface has been distributed! Let us explain.

Imagine that you have created and distributed the COM component described earlier that provides writing-related services. There are several ways in which you can update the component.

  • Update the implementation of a service without changing its interface. If you find a more efficient strategy for performing one of the services (such as searching the dictionary), or if you find a bug in one of the implementations, you might want to change the implementation without changing the interface. You can simply make these changes and distribute a new version of the component. Existing clients can use the new server, since its interface has not changed.

  • Add a new set of services to the component. Suppose you decide to add a new interface to your component, for instance to do basic grammar checking. The new interface might look something like this:

CheckSubjectVerbMatch()

CheckPlurals()

CheckPossessiveUse()

You can distribute a new version of your component that includes the new interface, as well as the old interfaces. Since you have not changed the old interfaces, clients that have been using the old component can use the new component as though nothing has happened. Of course, they will not be able to use the new grammar interface without making changes to the client code. But the point is that existing clients will not be broken by the new component. New clients, of course, are free to either implement or not implement any of your component’s interfaces.

  • Enhance an existing set of services, resulting in a change in the interface. Suppose, for example, that you want to enhance your dictionary services by including word etymology (word origin) along with the definition. One way to do this is to add a new parameter to the LookUpWord function requesting etymology. Doing so means that the function interface has changed. Alternatively, you might want to simply add a new function, called GetWordOrigin() to the dictionary interface. In either case, the dictionary interface has changed.

This leaves two possibilities. You could change the existing interface and distribute the component. However, clients of the old interface will likely be very upset because client software will no longer work with the new component. In COM lingo, the new component will break existing client applications. Also, you will have broken the implicit contract with COM that says you will never change an interface once it has been distributed.

The proper solution is to create a brand new dictionary interface, called dictionary2, that has the services

LookUpWord()

AddToDictionary()

DeleteFromDictionary()

GetWordOrigin()

Note that this interface includes the services of the previous dictionary interface as well. Also, you must keep the old dictionary interface in the new component! By doing so, old clients will not be broken by the new component, since they can continue to use the old interface, oblivious to the new interface. Existing clients can be updated to take advantage of the new dictionary2 interface at their leasure. New clients can use the new dictionary2 interface, ignoring the older interface. It is the "perfect" solution to the versioning problem, with the one exception that it tends to clutter everyone’s system with old, unused interfaces! Oh well, nothing is perfect.


The Client Side of COM

By now, you are probably wondering how the client gets information about available COM components and their interfaces. Type information is information about a component’s interfaces, interface methods (parameter types and return types) and relevant data structures.

We will see that there are two common ways in which a client can get type information about a COM component. The method used depends upon the type of client. One way is that a client programmer gets this information from documentation provided by the component programmer, and can then incorporate it directly into the client application. The information often takes the form of a C or C++ header or source file (when the component was written in C or C++, which is often the case). We will see an example of this in the next chapter.

A second method is through a type library.


Type Libraries

Type information can be stored in a binary file called a type library (or object library). A type library can be part of another file, such as a DLL or EXE file, or it can be a stand-alone file, with extension TLB or OBL. Object browsers, such as the one provided with Visual Basic, can be used by end-users to peek inside type libraries to get information about objects and their methods.

While a client application can also access the information in a type library at run time through a set of services provided by the COM library, this information is of limited value to the C++ client programmer, since it comes too late to be used easily. (Imagine trying to write a client program to call a certain method when you first need to call another function to determine the parameter number and types for the method!)

But for programmers writing client programs in Visual Basic (or Visual Basic for Applications — the macro language of Microsoft Office), the story is different. If a type library is available, then Visual Basic (not the client programmer) can use it to get type information at compile time for the client application, which is, after all, run time for Visual Basic!

As an example, consider the following Visual Basic code

Dim wrd As Object

Dim wrdDoc As Object

Set wrd = CreateObject("Word.Application.8")

wrd.Visible = True

Set wrdDoc = wrd.Documents.Add

wrdDoc.Content.InsertAfter Text:="COM is fun"

This code will cause Word 8.0 to start, if it is not already running, and then create a new document, into which the words "COM is fun" will be inserted. It does not use a type library, because the objects are declared generically in the lines

Dim wrd As Object

Dim wrdDoc As Object

As we will discuss in a later chapter, these two lines cause Visual Basic to use late binding (also called run-time binding) to bind the object variables wrd and wrdDoc to Word and to a Word document, respectively. After all, Visual Basic cannot tell at compile time to what these variables refer.

Now, if we change the two declaration lines as follows

Dim wrd As Word.Application

Dim wrdDoc As Word.Document

Set wrd = CreateObject("Word.Application.8")

wrd.Visible = True

Set wrdDoc = wrd.Documents.Add

wrdDoc.Content.InsertAfter Text:="COM is fun"

then the first line will produce the error message "User-defined type not defined."

However, if we first set a reference in Visual Basic to the Word 8.0 object library by selecting that entry under the Project…References… menu choice in VB, then Visual Basic will be able to use Word's type library to compile the code above. As we will see later in the book, this allows Visual Basic to perform early binding (also called compile-time binding) of the variables wrd and wrdDoc to Word and to a Word document, respectively. This can improve performance, since the executable code does not need to contain additional code to do the binding at run time.

We will go into more detail on the subject of binding when we discuss Automation.

Type libraries can be constructed by the COM programmer in a language specifically designed for this purpose. Two such languages are Object Description Language (ODL) and the newer Microsoft Interface Description Language (MIDL or IDL). In either case, the source text file is compiled into a type library using an appropriate utility program — either MKTYPLIB.EXE (for ODL) or the MIDL compiler (for MIDL). (These programs actually call special COM interfaces to create the type library from the source code.)

We should also mention that type libraries can contain additional useful information, such as help strings for COM interface methods.

The use of a type library, even by Visual Basic, still presupposes that a client has some information about the available COM components. This information must be registered in the system registry.


The Role of the System Registry

COM uses the system registry to allow applications to gain access to information about COM components. For instance, given the "name" of a registered COM component (that is, given the component’s programmatic ID, a concept we will define in a moment), an application can look in the registry to find such things as the name and location of the component’s server (so the component can be loaded or executed) and the name and location of the component’s type library (if it has one).

Every COM class and every COM interface registered in the system registry is assigned a unique 128-bit identification number, called a globally unique identifier (GUID) or universally unique identifier (UUID). This number is generated by the COM programmer using a special utility called GUIDGEN.EXE, which produces a new GUID based on information from the computer’s network card (if it has one) and the time and date, and copies it to the clipboard for use by the programmer. We will see an example of how to use this utility in the next chapter.

The GUID for a COM class is known more specifically as a class ID (CLSID) and the GUID for an interface is known as an interface ID (IID). A GUID for a type library is called a library ID (LIBID).

CLSID’s are specifically chosen to be so large (128 bits) that there is a very remote chance that two pseudorandomly created CLSIDs are the same. Hence, for all intents and purposes, CLSIDs provide a means of unique identification throughout the world!

Indeed, to get an idea just how unlikely it is that two pseudorandomly generated 128-bit numbers are identical, consider the following. An ordinary deck of 52 playing cards is 5/8-inch thick and the nearest star, Proxima Centauri, is 4.3 light-years from the Earth. Since a light-year is about 5.9 ´ 1012 miles, a straightforward calculation shows that a stack of 2128 playing cards has height 1.1 ´ 1019 light years, which would reach about 2.6 billion billion times the distance to Proxima Centauri!

On the other hand, CLSIDs are clearly too unwieldy for humans to use (even in hexadecimal notation they require 32 hex digits), a COM class can also be recognized by its fully qualified class name, also called its Programmatic ID (ProgID). This string usually has the form

ServerName.ComponentName

where ServerName is the name of the application that is supplying the COM component. A version-dependent ProgID generally has the form

ServerName.ComponentName.VersionNumber

However, there is no rule that a ProgID must have either of these forms. Here are some examples from my system:

RICHTEXT.RichtxtCtrl.1

Excel.Application.5

Office.Binder.95

OmniPage.Document

Word.Basic

Word.Document.8

Figure 2 shows a portion of my system registry (gathered together from several different locations), illustrating the entries for an ActiveX Automation server that exposes a single COM interface. For ease of comparison, repeated GUIDs (shown in hexadecimal by the registry editor) are marked with a small geometric symbol.

Figure 2

We will construct this Automation server as an example in the chapter on Automation. For now, observe that there are four branches to the Registry entries for the StudentServer project. The first branch provides a means to obtain the CLSID from the programmatic ID StudentServer.CStudent. In case you are interested, this can be achieved using a COM library function called CLSIDFromProgID, which is defined (roughly) as follows in the C++ documentation

HRESULT CLSIDFromProgID(ptrProgID, ptrclsid);

Before calling this function, the programmer puts the address of a variable containing the programmatic ID in the pointer variable ptrProgID. After calling the function, the variable ptrclsid will hold the address of the variable containing the CLSID.

The second branch in Figure 2 supplies information about the CStudent class from its CLSID. In particular, it gives the ProgID, the name and location of the executable file (known as the local server) and the type library's LIBID. Since this server is a stand-alone EXE file, it will run in a separate process (memory space) from any of its clients. Hence, it requires an in-process handler, which is a DLL that runs in the client’s address space and facilitates communication between the client and the server. In our case, the in-process handler is supplied by COM, in the form of the DLL named OLE32.DLL. (A server programmer can write a custom in-process handler if desired.)

The third branch gives information about the type library, including the name of the library file (in this case, the server itself). Note the type library version number (1.0), which we will discuss when we talk about version compatibility. The final branch contains a IID for the CStudent class' CStudent interface.

Although we are getting a bit ahead of ourselves, consider the following line of Visual Basic code

Set objStudent = _

CreateObject("StudentServer.CStudent")

This line will cause Visual Basic to use the ProgID (in quotes) to obtain the CLSID for the CStudent class. Visual Basic uses this to get the file name and location of the server, which can be started if it is not already running. Visual Basic then uses COM library functions (discussed in the next chapter) to instance a COM object of type CStudent. The programmer can then get Visual Basic to invoke an interface method by writing something like

objStudent.MethodName(method parameters)

We will go into the details of these COM processes in the next chapter.


What are OLE and ActiveX?

Originally, OLE stood for Object Linking and Embedding, which is a technology for creating compound documents. It was becoming clear at the time that documents needed to consist of more components than could be provided by a single application. For instance, a report prepared with a word processor might need to contain data from a spreadsheet or database table. A compound document is a document that has components created by more than one application. Figure 3 shows a compound document, initially created with Microsoft Word, but also containing an embedded chart created in Microsoft Excel.

Figure 3 - An OLE compound document

As OLE matured, it became clear that the issue of object embedding and linking was just part of a broader issue — that of communication between software components. As we have seen, this issue was addressed by the Component Object Model (COM). OLE version 2 was built upon the COM foundation. However, for marketing (or perhaps legal) reasons, Microsoft decided to use the term OLE for the entire COM-related technology, claiming that it was no longer just an acronym for Object Linking and Embedding.

In 1996, Microsoft christened the term ActiveX, and linked it with the Internet. Initially, ActiveX was intended to refer to software components (in particular software controls) that could communicate over the Internet. Since its introduction, Microsoft has broadened the meaning of the term, using it now to refer to any technology based upon the COM model. Moreover, the term OLE has come full circle, once again referring just to the object embedding and linking technology.

Thus, at least for the time being, it appears that

  • COM (an acronym for Component Object Model) refers to a specific model that describes how software components can communicate with each other. It defines a binary standard that can be used across languages, across distances (process boundaries, machine boundaries or network boundaries) and across platforms (PC, Macintosh, UNIX platforms, etc.). Any software component that subscribes to this model is called a COM component or an ActiveX component.

  • OLE refers specifically to the technology, built upon the COM model, for creating compound documents.

  • ActiveX refers to any technology, including OLE, Automation and ActiveX controls, that is based upon the COM model of communication.


What is Automation?

The traditional COM client-server model is shown in Figure 4. For concreteness, imagine that the COM server is serving up a COM component that implements the aforementioned dictionary and thesaurus services.

Figure 4

Under this model, the client is written, most likely in C or C++, and then compiled into an executable file, by the client programmer, who is very likely not the same person as the user of the program. As we will see in the next chapter, the client programmer incorporates all of the type information necessary to invoke the methods of the server directly within the client at compile time. When executed, the client program might present a dialog box to the user asking for a word and presenting the options available from the server (word look-up, synomym look-up and so on). In this senario, the client is static. It is a compiled application that does not change.

However, this setting is not suitable to all occassions. In particular, Microsoft would like end-users of high-level application such as Visual Basic and the Office Suite applications Word, Excel, Access and PowerPoint to be able to create COM clients as well.

Actually, the idea started when Microsoft decided to expose the macro (or scripting) languages of its Office Suite applications through COM, so that clients could programmatically control these applications. However, Microsoft wanted this control to be available to more than just hard-core C or C++ COM client programmers. It wanted other users of the these high-level applications to have this programmatic control. Simply put, Microsoft wants users of Word, for example, to be able to write macros (or scripts) that run Access or Excel.

To illustrate, the following code can be entered into Visual Basic, or into a Word or Excel macro (after setting a reference to the DAO object model) to programmatically control the Data Access Object model, which is the programming arm of Microsoft Access

Sub Test()

' Declare object variables

Dim dbE As DBEngine ' the DAO engine

Dim wks As Workspace ' a workspace

Dim db As Database ' a database

' Create the references

Set dbE = New DBEngine ' start the DAO engine

Set wks = dbE.Workspaces(0) ' get default workspace

Set db = wks.OpenDatabase("example.mdb") ' open db

' Now display the names of all tables in the

' database example.mdb

Dim td As TableDef

Dim s As String

For Each td In db.TableDefs

s = s & vbCrLf & td.Name

Next td

MsgBox s

End Sub

The general setting exemplified by the code above is shown in Figure 5.

Figure 5

This model is more dynamic than that shown in Figure 4, since the user is a programmer, who can therefore do more than just respond to the predetermined questions from the COM client. In a sense, the user becomes a COM client programmer, with a translation unit (Visual Basic in Figure 5) in between, translating relatively simple code such as that shown above into the much more complicated code required to get the COM machinery in motion.

However, it is probably a law of nature that we cannot access the full power of a complicated system such as COM from within a simpler system such as provided by a scripting language like Visual Basic for Applications. Hence, some compromise is required. This compromise comes in the form of Automation.

Simply put, Automation is an ActiveX technology that simplifies the process of using COM by providing a simpler (and therefore less powerful) wrapper for the full-featured COM interface approach. In a sense we will make precise in the chapter on Automation, the approach replaces multiple vTable interfaces by a single COM interface called IDispatch. The Automation model that is built around IDispatch is called a dispinterface, which is short for dispatch interface. We will discuss the details of this model in the chapter on Automation.

Servers that support Automation, by implementing IDispatch and the concomitant dispinterface, are called Automation servers. Clients that can invoke the methods of IDispatch, and thus use Automation are called Automation controllers. Visual Basic and the Microsoft Office Suite applications are both Automation servers and Automation clients. Thus, it can be said that an Automation controller has programmatic control over an Automation server. This control is provided through the ActiveX technology known as Automation.

Actually, as with so many COM terms, the term Automation is a moving target. Originally, Automation referred specifically to programmability through the IDispatch interface. However, more recently the term refers to any means of programmatic control over an application, whether it be by the traditional dispinterface approach or not. In this book, we will stick to the more concrete earlier definition.


What is an ActiveX Control?

As a Visual Basic or Visual C++ programmer, you have already had a lot of experience with ActiveX controls. A client program, such as Visual Basic, a Microsoft Office Suite application or a Web browser that can use an ActiveX control, is called a control container.

Generally speaking, an ActiveX control is a special type of COM component that has some of the following features:

  • It generally (but not always—think about timer controls) provides a visual interface for two-way communication with the client.

  • It allows the container to invoke the control’s methods.

  • It sends events to the container.

  • It allows its properties to be read and set by the container.

Let us make a few comments about these features.

Providing a visual (or user) interface is done, for an ActiveX control (as it would be done in an OLE compound document), by implementing a rather intimidating list of COM interfaces. A control typically runs in the same address space as its client and so it can avoid many of the conflicts that can arise when processes in two different address spaces are trying to use the screen at the same time.

An ActiveX control’s methods are invoked through an IDispatch interface. While a control may implement other methods, the IDispatch interface is required. Thus, an ActiveX control is an Automation object.

One of the unique features of a control is that it sends messages to its container through the event model. When the user of a control manipulates the control (clicks a command button, writes text in a text box, and so on), the control sends an event message to its container, by invoking one of the container’s methods. Thus, the roles of client and server are temporarily reversed—the control becomes the client and the container becomes the server. Thus, control containers must implement interfaces that controls can use to "express" their events.

Most controls have properties, although these are not required for a control. Visual Basic displays the (design time) properties of a control in a property window, for easy viewing and changing. Not all control container applications provide a property window, however. For this reason, the ActiveX control specification provides a standardized way for a control to provide access to its properties, through the use of property pages. We will discuss this in detail in the chapter on ActiveX controls.


O'Reilly RTF Template for Microsoft Word

COM Concept, Design and Implementation

Now that we have a bit of background, we are in a position to state the basic concept behind COM.

COM provides a standardized model for software component communication through the use of interfaces. It defines how interfaces should be made accessible to clients of COM components. It provides rules such as the COM interface contract mentioned earlier.

To realize this concept, the COM design provides a number of predefined interfaces to implement a number of different ActiveX technologies. Here are some examples:

  1. COM provides a set of interfaces for the purpose of storing data persistently (on disk), in a form known as structured storage. These interfaces are used by compound documents and ActiveX controls, for instance.

  2. COM provides a collection of interfaces for two-way communication between components, known as connection-point services. These are used by ActiveX controls, for instance, to implement the event programming model, so familiar to Visual Basic programmers.

  3. COM provides interfaces for establishing a visual interface for a COM component. These services are used by ActiveX controls that require a visual interface, for instance. They are also used in compound documents.

  4. COM provides a set of interfaces for defining, storing and manipulating properties. These are used by ActiveX controls.

  5. COM provides a set of (dispatch) interfaces that make the invocation of COM component services easier, allowing applications (such as Visual Basic) that are not as COM aware as C++ for instance, to invoke a component’s services. These interfaces are used by Automation objects and by ActiveX controls, for instance.

It is important to emphasize that, in general, COM does not implement the aforementioned interfaces; it only defines them. It is up to the COM programmer to provide the implementation. You may have heard it said that COM has a reputation for being very difficult to program. There is no question that programming COM at the C or C++ level is both very challenging and very frustrating, due largely to what seems like endless cases requiring individual treatment, exhasperated dramatically by the lack of decent thorough documentation. Fortunately, Visual Basic programmers only need put up with a very small (but definitely positive) percentage of this frustration.