Introduction to the Component Object Model (COM)

< BACK  NEXT >
[oR]

The Component Object Model (COM) is a specification that describes how to write components. The main characteristics of COM are the following:

  • Language neutral. Components may be written in any language and be used by client applications written in any language.

  • Dynamic linking. A component can be updated without the need to recompile a client application that is using the component.

  • Encapsulation. A client application has no knowledge of how a component is implemented, the internal data structures it uses, and where it is implemented or the language in which it is implemented. Any implementation issues can change without a client application recognizing the changes.

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 Components

With 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 Interfaces

A 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:

  • Objects of an abstract base class cannot be created.

  • Classes that inherit from an abstract base class must implement all the functions in the abstract base class, although these classes are free to implement the functions in any way they choose.

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:

  • Adding new functions

  • Changing the number or nature of the parameters the functions take

  • Changing the order of functions in an interface's definition

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 Interface

The IUnknown interface defines three functions that must be implemented by all interfaces in a component:

  • AddRef Used to increment the usage count on the interface

  • Release Used to decrement the usage count on the interface

  • QueryInterface Used to obtain another interface supported by the component from an interface pointer

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 Registry

The 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 object
graphics/14fig01.gif

The 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 Errors

Nearly 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:

  • Bit 31 The severity flag. If set (value 1), then the HRESULT represents an error, otherwise success.

  • Bits 16 30 Facility. This defines what has generated the error. FACILITY_ITF specifies an application-generated error; FACILITY_WINDOWS specifies a Windows error message.

  • Bits 0 16 Error Code. An error code unique to the facility.

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:

  • S_OK or NOERROR The function succeeded.

  • S_FALSE The function succeeded and returned a FALSE value.

  • E_UNEXPECTED An unexpected error.

  • E_NOIMPL The requested functionality was not implemented.

  • E_NOINTERFACE QueryInterface was used to request an interface not implemented by the component.

  • E_OUTOFMEMORY The component encountered an out-of-memory error. This is often a catchall error that has nothing to do with a memory error.

  • E_FAIL Unspecified general error.

Interface Definition Language and Type Library Information

COM 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:

  • For interpreted languages that need to determine which functions and interfaces are available when the code is run

  • When producing class wrappers (so-called "smart pointer" classes) around COM components

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.


< BACK  NEXT >


Windows CE 3. 0 Application Programming
Windows CE 3.0: Application Programming (Prentice Hall Series on Microsoft Technologies)
ISBN: 0130255920
EAN: 2147483647
Year: 2002
Pages: 181

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net