The Component Object Model (COM) is a specification that describes how to write components. The main characteristics of COM are the following:
While COM is mainly a specification for how components are written, it has a limited amount of implementation to provide support for COM components. The implementation consists of a small number of API functions, all starting with the prefix "Co." You will see the term OLE (Object Linking and Embedding) used in conjunction with COM. For example, the COM implementation functions are contained in ole32.dll. This is unfortunate, as the two technologies are different. COM, as explained, is a standard that allows components to be created. OLE is a different technology that allows documents or bits of documents and other data to be shared between applications. OLE happens to use COM for its implementation. COM ComponentsWith an API, functions are generally available for calling at any time. With COM, though, an instance of component (an "object") must be created before functionality can be accessed. In this respect, an API function is like a C function, while a COM component is like a C++ class. In C++ you need to create an instance of a class before its functionality can be used. This, however, is where the similarity ends. In C++, a programmer is very much involved in creating the class instance a decision is made whether to use new or, perhaps, declare the object variable on the stack. A pointer or reference to this class instance is managed and maintained. If new is used, the programmer decides when to delete the object using a pointer to the class instance. In COM the client application never has a reference to a component object. The client application calls the function CoCreateInstance to create the component object and receives back a pointer to an interface, not to the component itself. (See the next section for a description of interfaces.) When the client application has finished with the interface, the component is automatically deleted. The client application never directly deletes the component object. COM InterfacesA COM component implements one or more interfaces. An interface provides a connection between two different objects: the component and the client. An interface is a definition containing the list of functions and their parameters. ACOM component will implement. the interface by providing implementations of each of the functions. Several different components can implement the same interface, and their implementation details can be different. However, in implementing the same interface, the different components should honor the semantics of the interface as well as the functions and their parameters. Using an interface pointer, a client application can call functions that are contained in that component's implementation of the interface. These functions are called in C and C++ just like ordinary functions the same data types can be passed either by reference or by value. An interface is a specific memory structure containing an array of function pointers. This specific memory structure is identical to the virtual function table pointer structure used by C++ objects. However, this does not mean that COM interface functions can only be produced by C++ applications it is a little easier with C++, but other languages can produce the same structures. C++ pure abstract base classes are often used to describe COM interfaces since the classes parallel two important characteristics of interfaces:
A COM component can support one or more interfaces. When a component object is created using CoCreateInterface, a pointer to one of these interfaces is returned. COM does not provide a mechanism for obtaining the list of all interfaces supported by a component, but such interrogation is possible if "type library information" (described later in this chapter) is supplied with the component. Once defined, an interface definition cannot be changed by, for example, any of the following:
A new interface will need to be created if such changes have to be made. A component can then implement both the old interface definition (for backwards compatibility) and the new interface. The implementation of interface functions can change over time as long as the semantics of the implementation do not change. The IUnknown InterfaceThe IUnknown interface defines three functions that must be implemented by all interfaces in a component:
The AddRef and Release functions are used to maintain a usage count on each interface in a component. A component object will delete itself when the usage counts for all interfaces reach zero. A component object's client never deletes the component remember that a client does not have a reference to the component object and so cannot directly delete it. The QueryInterface function allows a client to obtain a pointer to another interface implemented by a component using a pointer to an interface. Often an interface function will itself return an interface pointer, so QueryInterface may not need to be used. By returning interface pointers, a COM component can create an "object model" like the Pocket Office Object Model described later in this chapter. Each interface in a component implements IUnknown, and so provides implementations for each of these three functions. Interface implementation is similar in some respects to class inheritance; its primary difference is that there are no assumptions about the interface being inherited, except for the number and nature of the functions it defines. An interface does not inherit implementation, only an interface's definition. Globally Unique Identifiers (GUIDs)It is essential that each interface definition be uniquely identified so that a component can specify precisely which interface definition is being implemented. Each interface is given a "Globally Unique Identifier" (a GUID) when it is created. GUIDs are stored in a 128-bit structure and can be generated using a tool called UUIDGEN.EXE. Interface GUIDs are stored in an IID (Interface Identifier) structure, and this IID is used when referring to an interface. For example, the IID for the IUnknown interface is IID_IUnknown. COM components also need to be uniquely identified using a GUID. These are known as "class identifiers" and are stored in CLSID structures. When CoCreateInstance is called, a client application uses a CLSID to specify from which component an object is to be created. In fact, this is the only time a client application refers to a component all other references are to the interfaces implemented by that component. Here is an example of the CLSID for the POOM object: DEFINE_GUID(CLSID_Application, 0x05058F23, 0x20BE, 0x11d2, 0x8F, 0x18, 0x00, 0x00, 0xF8, 0x7A, 0x43, 0x35); The macro DEFINE_GUID is used so that a variable containing this GUID is created when INITGUID is #defined, or externed when it is not. This allows you to avoid having multiple definitions of GUIDs in your application. GUIDs are generally passed by reference (since they are structures), so data types are defined for this purpose. For example, the data type REFCLSID defines a class identifier passed by reference. Programmatic Identifiers (ProgIDs)Each component has a globally unique identifier in the form of a GUID. These GUIDs are not particularly memorable, so components can have "human readable" names called Programmatic Identifiers, or ProgIDs. These are not guaranteed to be unique in the world but are easier to use than GUIDs. The ProgID for POOM is PocketOutlook.Application. The naming convention is "program.component", with the option of containing a version number, such as "program.component.2". COM provides functions for converting between CLSIDs and ProgIDs: CLSIDFromProgID and ProgIDFromCLSID. COM Components and the RegistryThe registry is used by COM to store information about all the components registered on a particular Windows CE device. This includes the file (such as a DLL or EXE) where a component is implemented. All COM information is stored inthe key HKEY_CLASSES_ROOT. The key HKEY_CLASSES_ROOT\CLSID contains a sub-key for each registered COM component, using the CLSID as the key's name. Figure 14.1 shows the sub-keys for the POOM class object. Figure 14.1. Registry entries for POOM class objectThe InProcServer32 value key contains the name of the file, pimstore.dll that implements the COM component. The HKEY_CLASSES_ROOT contains a sub-key for each PROGID, using the PROGID as the name of the key. For POOM there is a key called "PocketOutlook.Application". This has a single key with the name "CLSID" that contains the GUID related to the PROGID displayed in the following form: {05058F23-20BE-11D2-8F18-0000F87A4335} The HRESULT Data Type and Handling ErrorsNearly all COM interface functions return an HRESULT value that contains error information. An HRESULT is not a handle but rather a 32-bit value that contains three discrete pieces of information:
The FAILED or SUCCESS macros should be used to determine whether an HRESULT indicates failure or success, since HRESULTs can be used to return different success or failure codes. Common HRESULT values include the following:
Interface Definition Language and Type Library InformationCOM does not provide a mechanism by which an application can directly interrogate a component about the interfaces it supports, nor the functions implemented in those interfaces. For most applications this does not pose a problem. If the programmer does not know about an interface when the application is written, it is unlikely the program will need to call functions in the interface. However, there are times when it is essential to do so, including the following:
A COM component developer can use Interface Definition Language (IDL) to define the interfaces and functions implemented by a component. IDL is a language that looks like a C header file and can include structure, enumeration, interface, and function definitions. In addition to C-type information, additional information is provided on function parameters, such as whether they are "in", "out", or "in/out" parameters and how the size of parameter arrays is determined. This IDL code is sometimes hand-coded or, more often, is generated automatically when components are written using MFC or ATL. The Microsoft IDL compiler (MIDL) can be used to compile the IDL code and generate Type Library (TLB) information. This TLB information is a binary representation of the IDL code and can be included in DLL or EXE files. TLB information becomes more important for Automation using the IDispatch interface described later in this chapter.
|