The COM Programming Model

The programming model specified by COM is quite simple and powerful. Sometimes it's difficult to see the simplicity and elegance of the basic model underneath all the services built on top of it. So we won't talk about any of those services here; instead, we'll focus on the programming model itself.

COM, OLE, and Microsoft ActiveX

You might be more familiar with the terms "OLE" and "ActiveX" than you are with the term "COM." And you might be confused about what technologies these terms refer to and how they are related. This wouldn't be surprising—Microsoft has changed its definitions of these terms over the past couple of years even though the technologies themselves have not changed. So let's get this tangle straightened out once and for all:

  • COM is the fundamental component object model, introduced in 1992. The COM specification is available on the Microsoft Web site (at http://www.microsoft.com/com/comdocs-f.htm), and only those items defined in the specification are part of COM proper.

  • OLE (Object Linking and Embedding) is built on top of COM and is the mechanism used for compound documents. For example, when you insert a Microsoft Excel spreadsheet into a Microsoft Word document, you are using OLE.

  • ActiveX was originally introduced with Microsoft's COM-based Internet technologies in 1996 and was essentially a marketing label used to identify these technologies. Then things got a little crazy, and everything COM-based got grouped under the ActiveX umbrella. That just confused everyone. Today some degree of normalcy has returned and ActiveX is only used in the phrase "ActiveX Controls," which is a specific technology built on top of COM for programmatic controls. When you put a control on a Microsoft Visual Basic form or embed an <OBJECT> tag in an HTML page, you are using ActiveX Controls.

In this book, we talk only about COM. We might use ActiveX Controls in our presentation layer, but we don't need to know how to write these controls ourselves. And we don't discuss OLE at all.

Objects

To start with, let's go over some terminology. "Object" is one of the most overloaded terms in programming. As in most object-oriented models, objects in COM are run-time instances of some class that represents a real-world entity. We'll come back to exactly what a class is a little later, but conceptually it's a type of object—for example, a Customer, an Order, or even a SalesTaxCalculator. Each Customer object represents a specific real-world customer, each Order object represents a specific real-world order, and so on.

Objects have identity, state, and behavior. Identity is the unique "name" that distinguishes one object from any other object. State is the data associated with a particular object. Behavior is the set of methods that can be called to query or manipulate an object's state.

To help clarify, let's examine a C++ object. C++ objects are run-time instances of C++ classes. The C++ class defines member variables and methods that apply to objects of that class. When an object is created, a contiguous block of space is allocated for the member variables. The address of that memory is the object's identity. The contents of the memory block are the object's state. The object's behavior is defined by the method implementation code, which is located elsewhere in memory.

Most language-based object models are similar to the C++ object model, but COM objects are a little different. Recall that two of the challenges faced by COM are language independence and location independence. When you start looking at interprocess and remote communications, memory addresses are not sufficient to identify objects. And getting every programming language and tool to agree on a memory layout for object member variables sounds nearly impossible. Add in the need to update components without breaking existing applications, and you'll realize that locating component method implementations is a challenge too.

So COM takes a different approach than C++. In COM, the notion of an object's public interface and its implementation are completely separate. Applications can interact with objects only through their public interfaces, using something called an interface pointer. COM defines how that happens. Since all interaction must go through the interface pointer, COM doesn't need to worry about where an object's state is located or how it is laid out in memory. And since the only thing an application has to reference the object is the interface pointer, the object's identity must somehow be related to that pointer. Clearly, an understanding of interfaces is essential to understanding COM.

Interfaces

A COM interface is a collection of logically related operations that define some behavior. When you define an interface, you are providing only the specification of the set of operations, not any particular implementation. For example, the components for computing sales tax and looking up zip codes in our earlier example might have interfaces with just one method each, as shown in the following code. (Don't worry about the syntax, this is just pseudocode to give you an idea of what's going on.)

 interface IComputeSalesTax    Compute([in]long salesAmount, [in]long originatingZipCode,             [in]long destinationZipCode, [out, retval]long* salesTax) interface ILookupZipCode    Lookup([in]long zipcode, [out]String city, [out]String state)

A more complex interface might look like this:

 interface ICustomerMaintenance    Add([in]String firstName, [in]String lastName,         [in]String address, ..., [out]String accountNumber)    UpdateAddress([in]String accountNumber, [in]String address,                  [in]long zipCode)    Delete([in]String accountNumber)

Interface definitions represent a contract between the caller and implementer: if a component implements a particular interface, the caller can expect the component to obey the interface specification. The specification includes a strict definition of the syntax of the interface methods, as well as a definition of the semantics of the interface.

To be considered a COM interface, an interface must satisfy the following requirements:

  • The interface must be identified by a unique identifier.

  • The interface must ultimately derive from the special interface IUnknown.

  • Once published, the interface is immutable (meaning that it can't be changed).

We'll look at these requirements in detail throughout this section.

COM identifiers

Just as unique identifiers are needed to locate components, we also need to provide a unique identifier for each interface. One approach would be to use some type of string identifier, but this raises several problems, the most important being that it's very difficult to guarantee you've picked a truly unique identifier. Even if a naming convention is imposed on these strings—for example, to include a company name—there is always a chance that someone else will use the same name for a different purpose. To guarantee uniqueness, you would need a central authority to hand out prefixes—say, one prefix per company. Each company would in turn need a central registry of names to prevent any duplicates within the company. This method seems much too complicated.

In COM, whenever we need a unique identifier, we use something called a globally unique identifier (GUID, pronounced "goo-id" or "gwid"). A GUID is a 128-bit integer. The algorithm used to generate GUIDs is statistically guaranteed to generate unique numbers. Better yet, anyone can create as many GUIDs as he or she wants.

NOTE

According to the COM specification, GUIDs can be generated at the rate of 10,000,000 per second per machine for the next 3240 years, without risk of duplication.

GUIDs can be generated using a tool such as GUIDGEN, which comes with the Microsoft Platform Software Development Kit (SDK). GUIDGEN calls the system API function CoCreateGuid to generate the GUID and then provides several output options. For example, the following GUID was generated using the static const output option and is suitable for inclusion in a C++ source file:

 // {45D3F4B0-DB76-11d1-AA06-0040052510F1} static const GUID GUID_Sample = { 0x45d3f4b0, 0xdb76, 0x11d1,     { 0xaa, 0x6, 0x0, 0x40, 0x5, 0x25, 0x10, 0xf1 } }; 

The first line is a comment showing how the GUID appears in string form. Whenever GUIDs are displayed to a user, they appear in this form. The remaining lines define the GUID as a constant that can be used in C++ code.

NOTE

Most development tools automate the process of creating skeleton COM components. These tools also take care of generating the appropriate GUIDs for you in a format that the skeleton code understands.

So, every interface is identified by a GUID. To make it clear that we're talking about interfaces, we call these interface IDs (IIDs). Whenever we need to uniquely identify an interface to COM, we use its IID. Every interface also has a string name, because while IIDs are very nice for the computer, they're kind of difficult for us mere mortals to use. (Just imagine asking someone, "Hey, Bob, is that sales tax interface {45D3F4B0-DB76-11d1-AA06-0040052510F1} or {45D3F4B1-DB76-11d1-AA06-0040052510F1}?") By convention, these names usually start with "I"—for example, IComputeSalesTax. The string name is what developers use in their source code. String names aren't guaranteed to be unique in the world, but it's unlikely that a developer would need to use two different interfaces with the same string name in one source code file.

Defining interfaces

You might be wondering at this point exactly how interfaces are defined so that component developers know how to implement them and application developers know how to use them. The truth of the matter is that COM doesn't really care how, as long as the caller and the implementer agree on the definition. In practice, however, COM interfaces are usually defined using the Interface Definition Language (IDL).

IDL is a C++-like language that lets you describe the exact syntax of your interface. For example, here's an IDL definition of the IComputeSalesTax interface:

 [  object,    uuid(45D3F4B0-DB76-11d1-AA06-0040052510F1) ] interface IComputeSalesTax : IUnknown {    import "unknwn.idl"    HRESULT Compute([in]long salesAmount,                    [in]long originatingZipCode,                     [in]long destinationZipCode,                    [out, retval]long* salesTax); }

The interface definition begins with the keyword interface. The attributes of the interface are contained in square brackets preceding the interface keyword. The object attribute indicates that the COM IDL extensions should be used. The uuid attribute specifies the IID for this interface.

NOTE

The attribute is named uuid because COM IDL is based on the Open Software Foundation's Distributed Computing Environment (DCE) IDL, which uses the term "universally unique identifier" (UUID) instead of GUID.

The interface statement itself specifies the human-readable name for the interface, IComputeSalesTax, followed by a colon and the name of the base interface, IUnknown. This syntax indicates that IComputeSalesTax is derived from IUnknown and inherits all its methods. (As mentioned, all COM interfaces must ultimately derive from IUnknown.)

The next line in this example is an import statement, which is used to locate the definition of the IUnknown interface. Following the import statement is the definition of the interface method Compute. The Compute method returns an HRESULT value, which is the standard data type used to report success or failure for COM method calls. Compute has four parameters: salesAmount, originatingZipCode, destinationZipCode, and salesTax. COM methods can have any number of parameters, of arbitrarily complex data types. However, not all development languages will support all the data types that can be specified using IDL, so most interfaces use a fairly restricted set of data types. We'll discuss this limitation in more detail in the section "Automation" later in this chapter. In addition to a name and data type, you can specify attributes for each parameter—for example, the in, out, and retval attributes in the IComputeSalesTax interface. These attributes provide clues to development tools about how data should be copied from one place to another. In this example, the attribute in specifies that the first three parameters are passed to the Compute method and the attributes out and retval specify that the last parameter is filled in by the method.

IDL is a convenient text format for documenting the interface syntax. Once the IDL code is written, it can be compiled using the Microsoft IDL (MIDL) compiler to generate the following equivalent representations of the interface definition, which are more useful to your development tools:

IDL isn't difficult to use, but it's new to many developers, so most development tools offer some assistance. Some, like Visual Basic, completely hide IDL. Developers define interfaces directly in Visual Basic syntax, and Visual Basic generates a type library for the interface. Other tools generate the IDL file for you. These tools usually provide some sort of wizard to help you define an interface and its methods; they then spit out the correct IDL syntax. We'll see a lot more IDL later in the book, when we start talking about designing and implementing components.

Defining interfaces using IDL is a first step toward language independence, but it doesn't get us all the way there. Given an IDL file, or the equivalent header file or type library, a developer can code an implementation of an interface or a client application that uses the interface in any language that understands the types used in the interface. To ensure that the client and implementation can talk to each other, both sides must agree on what an interface pointer represents and how to make method calls through that pointer.

COM as binary standard

For this reason, COM is called a binary standard. COM defines the exact binary representation of an interface. Any programming language or tool that supports COM must create object interfaces that correspond to this standard representation. Figure 2-1 shows what this representation looks like for the IComputeSalesTax interface.

click to view at full size.

Figure 2-1. The binary representation of the IComputeSalesTax interface.

The client's interface pointer is actually a pointer to a pointer to a table of more pointers. This table of pointers is called a vtable. Each pointer in the vtable points to the binary code for a method in the interface. If you're a C++ programmer, this should sound familiar—it's exactly the same as a C++ virtual function table.

The pointer to the vtable is appropriately called a vtable pointer. Each COM object contains a vtable pointer for each interface it supports. When a client asks for an interface pointer to an object, it gets a pointer to the appropriate vtable pointer. Why a pointer to the vtable pointer? Why not the vtable pointer itself? Well, the component needs some way to identify the object it should be working on. When a COM object is created, usually a single block of memory is allocated for both the vtable pointers and any internal data members needed by the object. The component "knows" the relationship between the location of the vtable pointer and the location of the object's entire memory block, and thus the component can identify the appropriate object. COM further specifies that the first parameter passed to each method call is a pointer to the object; this can easily be accomplished by using the interface pointer.

Fortunately, you usually don't have to worry about any of this. Most programming languages and tools that support COM map interface pointers and vtables to equivalent concepts in the language. For example, in C++ interfaces are equivalent to abstract base classes. Developers can implement an interface by deriving a class from the abstract base class. Calling COM methods through an interface pointer is exactly like calling C++ methods through an object pointer. In Visual Basic, interfaces are almost completely hidden within the Visual Basic language. Developers can implement an interface by using the implements keyword and implementing the interface's methods. To use a COM object, developers declare an object variable of the interface type, create the object, and make normal function calls.

The binary standard for interfaces, in combination with a common interpretation of interface definitions, gives us language independence. This particular binary standard also puts us well on the way to location independence. Recall that we want a way to make in-process, interprocess, and remote calls look identical to the client. Clearly, within a single process the interface pointer can point directly to the real vtable pointer and we can call methods directly. But this technique probably won't work cross-process or cross-machine. We could, however, redirect the interface pointer to point to a proxy vtable pointer. The client-side proxy would understand how to make interprocess or remote calls to an equivalent server-side object, and that object would make in-process calls to the real object. To the client and component, method calls would look the same. In COM, we call the server-side object a stub. We'll return to proxies and stubs in the section "Distributed COM" later in this chapter.

The IUnknown interface

You might have noticed that the vtable in Figure 2-1 contains three methods that don't appear to be part of the IComputeSalesTax interface: QueryInterface, AddRef, and Release. These three methods are provided by the IUnknown interface. IUnknown defines the fundamental behavior for COM interfaces. Because all COM interfaces derive from IUnknown, clients can rely on this fundamental behavior. Recall that one of COM's technical challenges is to provide a standard way to interact with objects; the IUnknown interface is a big part of the solution. IUnknown provides three features: interface navigation, interface versioning, and object lifetime management.

Interface navigation Interface navigation is provided by the QueryInterface method. As we've seen, COM objects can support more than one interface. Now assume that you have one interface pointer to an object and you want a second pointer. What do you do? Simple: you ask the object for the second interface pointer—and the way you ask is with the QueryInterface method. Remember that all interfaces derive from IUnknown, so every interface supports QueryInterface. Convenient, yes?

To use QueryInterface, a client passes the IID of the interface it wants to an object. If the object supports that interface, it passes back the interface pointer. If not, it returns an error. QueryInterface is an extraordinarily powerful mechanism. It lets clients and components created independently negotiate a common way to communicate. It is also the key to solving the challenge of versioning.

Interface versioning Because components and applications can be built independently, once an interface has been published, it is immutable—no changes to syntax and no changes to semantics are allowed. Changing a published interface is evil. Imagine what would happen if you had a method definition that looked like this:

 HRESULT DoSomeWork([in]short inParam1, [out,retval]long* outParam);

and you changed it to look like this:

 HRESULT DoSomeWork([in]short inParam1, [in]short inParam2,                     [out,retval]long* outParam);

An existing client application would think that two parameters needed to be passed to the method, but the component implementing the new version of the method would be expecting three parameters. At best, you would get some recognizably bad value in inParam2 or outParam and could return an error. More likely, your component would crash.

Even changes to the number of methods in an interface could be dangerous. Consider a new client application that thinks an interface has five methods attempting to call an old component that implements only four methods. The results wouldn't be pretty…

COM interfaces are immutable in order to avoid these problems. To "version" an interface, you actually define a new interface. Existing clients don't know anything about the new interface, so they don't care whether components implement the new interface. New clients can implement support for the new interface and access the new features when talking to a new component. If a new client happens to access an older component, it can use QueryInterface to safely detect that the component does not support the new interface and avoid using the new features.

Object lifetime management At this point, you might be wondering how objects are created and how they are destroyed. Let's defer the subject of object creation just a little while longer. But assuming that a client has managed to create an object and holds an interface pointer to it, how does that object get destroyed?

It might seem that the easiest solution would be for the client that created the object to destroy it. Unfortunately, this doesn't work. A single client can use QueryInterface to obtain multiple interface pointers to the same object. It might be difficult for the client to keep track of when it had finished using all of the interface pointers so that the object could safely be destroyed. Also, more than one client might use the same object. No single client can tell when all clients have finished using the object. The only element that can tell when all clients have finished using the object is the object itself—with some help from each client.

To address these issues, IUnknown provides a third feature: object lifetime management by keeping track of the number of clients using an interface—commonly referred to as reference counting. When a new interface pointer to an object is created, the creator is responsible for calling IUnknown AddRef to increment the object's reference count. When a client has finished with an interface pointer, it calls IUnknown Release, which decrements the object's reference count. When the reference count goes to 0, the object knows that all clients have finished using it and it can destroy itself. This neatly solves both the problem of a single client with multiple interface pointers and the problem of multiple independent clients. From the client's perspective, all it needs to do is create, use, and release—that is, create an object to get an interface pointer, use the interface pointer to make method calls, and release the interface pointer using IUnknown Release.

To understand how things work from the component's perspective, we need to move beyond interfaces and talk about interface implementations.

Classes

Remember that all COM objects are instances of COM classes. A COM class is nothing more than a named implementation of one or more COM interfaces. A COM class is named using a class identifier (CLSID), which is a type of GUID. Like IIDs, CLSIDs are guaranteed to be unique but are difficult for humans to use. So COM classes can also have a string name, called a programmatic identifier (ProgID).

Every COM class has an associated class object. A class object knows how to create instances of a single COM class. The COM specification defines a standard API function, CoGetClassObject, for creating class objects and a standard interface, IClassFactory, for communicating with class objects. (For this reason, class objects are sometimes called class factories.) Thus, clients need to learn only one mechanism in order to create any type of COM object. The most important method of IClassFactory is CreateInstance, which creates an object and returns a specified interface pointer. So a client can create a COM object simply by calling CoGetClassObject to get an IClassFactory interface pointer, calling IClassFactory CreateInstance to get an interface pointer to the object, and then releasing the IClassFactory interface pointer. As you might imagine, this sequence happens a lot in COM applications, so COM provides a wrapper function that lets you do it all in one call: CoCreateInstanceEx.

Figure 2-2 shows what happens at run time when an object is created. The client calls CoGetClassObject, specifying a CLSID. A portion of the COM run time called the Service Control Manager (SCM, pronounced "scum") looks in its internal data structures to locate the requested class object. If it can't find the class object, it looks in the system registry to locate the code that knows how to create the requested class object and does whatever is necessary to load and execute that code. Once it has a class object, the SCM returns an IClassFactory interface pointer to the client. Now the client can call IClassFactory CreateInstance, requesting a specific interface pointer. The class object's CreateInstance implementation creates the actual object and returns the specified interface pointer.

COM classes are quite different from most language-based classes in the sense that once an object has been created, its class is irrelevant to the client. All interaction with the object occurs through interface pointers. The interface pointers know nothing about the implementation class used to create the object. This rigorous separation of interface and implementation is one of the key features of COM.

click to view at full size.

Figure 2-2. Creating a COM object.

Components

If COM classes contain interface implementations, what are components? "Component" is another one of those words that means different things to different people. A COM component is a binary unit of software that can be used to create COM objects. For a given CLSID, a component will include the COM class, the code to implement the class object, and usually code to create the appropriate entries in the system registry. Components are also sometimes called servers—yet another overloaded term!

On the Microsoft Windows platform, there are three basic ways to package COM components or servers: as Windows services, as executables, or as DLLs. Components are built as Windows services in situations in which the component must always be running, even if no one is logged on to the host machine. Windows executables are often used where the application provides a user interface in addition to serving up COM objects. Microsoft Word is an example of a COM server built as an executable. In most other scenarios, components are packaged as DLLs. In particular, most of the components used to construct three-tier applications, as discussed in Chapter 1, will be packaged as DLLs. The ActiveX Controls used in a presentation layer are DLLs, as are all middle-tier components that run within the MTS environment.

Another way to categorize servers is by where they are located relative to the client, as described here:

For the remainder of our discussion of COM, we will focus on components implemented as DLLs since these are most prevalent in three-tier applications.

The structure of a DLL server

In addition to the implementations of COM classes and class objects provided by all types of components, DLL servers are expected to implement four well-known entry points:

DllGetClassObject and DllCanUnloadNow are called by the COM run time; your applications should never need to call these functions directly. DllRegisterServer and DllUnregisterServer are usually called by installation programs and developer tools.

Registry entries

The system registry maintains a mapping between CLSIDs and COM components, so that the SCM can locate the code that knows how to create class objects. Figure 2-3 illustrates the basic entries for a SalesTaxCalculator component. The CLSID map is maintained under the predefined registry key HKEY_CLASSES_ROOT, in the CLSID key. For each COM component installed on the machine, there is one CLSID subkey for each COM class in the component. This subkey is named using the text form of the CLSID. The SalesTaxCalculator component contains one COM class, with CLSID {45D3F4B1-DB76-11d1-AA06-0040052510F1}. Each COM class key has a subkey indicating where its COM component is located. In this case, the component is implemented as a DLL and will be run in process, so the InprocServer32 subkey is used to specify the DLL name. This COM class also has a ProgID, which is specified using the ProgID subkey.

click to view at full size.

Figure 2-3. Registry entries for the SalesTaxCalculator component.

The system registry also maintains a ProgID-to-CLSID map under the HKEY_CLASSES_ROOT registry key (also shown in Figure 2-3). Each ProgID gets a named key under HKEY_CLASSES_ROOT. In our example, the ProgID is SalesTax.SalesTaxCalculator, so this is the name of the key. The ProgID key has a CLSID subkey whose value specifies the corresponding CLSID.

For the SCM to locate the component for a CLSID, these registry entries must be installed with the component. For DLL servers, this is usually done by running the program REGSVR32, as shown here:

 regsvr32 salestax.dll

REGSVR32 looks in the DLL server for the DllRegisterServer entry point. The implementation of DllRegisterServer is responsible for writing all the required registry entries for all the COM classes provided by the component. This code is not difficult to write, but it is tedious. Again, we are fortunate to have programming languages and tools that take care of all the dirty work for us, providing standard implementations of DllRegisterServer and its partner, DllUnregisterServer. Even if you never need to implement registration code yourself, you will find an understanding of COM registry entries useful—poking around in the registry is often part of debugging COM applications.

Threading

COM supports multiple threading models for components. A threading model defines what threads a component's objects can run on and how COM will synchronize access to the objects. On the Windows platform, COM components run in a multi-threaded environment. Components must be written to run correctly in this environment.

All COM threading models are based on the notion of apartments. An apartment is an execution context for objects. Every object resides in exactly one apartment for its entire lifetime. One or more apartments reside within each process. All threads in the process must be associated with an apartment before making COM calls, by calling CoInitialize or CoInitializeEx.

All calls to an object are made in the object's apartment. If the calling application is running in a different apartment, COM takes care of synchronizing access to the object's apartment. In addition, cross-apartment calls must be marshaled. We'll talk more about marshaling in the section "Remote Activation and Marshaling" later in this chapter; in essence, marshaling means that COM intercepts the call, packages the call stack into some standard format, does some work, and then converts the package back to a method call in the object's apartment. In-process, cross-apartment calls can have a substantial impact on performance. That's why it's important to understand the COM threading models.

In Microsoft Windows 95 and Microsoft Windows NT 4.0, COM supports two types of apartments. A single-threaded apartment (STA) is associated with one thread for the lifetime of the apartment. The apartment is created when CoInitialize or CoInitializeEx is called by the thread. One process can have multiple STAs; the first STA created is called the main STA. The threading model based on STAs is called the apartment model. Each process can also have one multi-threaded apartment (MTA). Multiple threads can be associated with the MTA. The threading model based on MTAs is called the free-threaded model. Figure 2-4 illustrates two processes, each with objects created in both an STA and an MTA.

click to view at full size.

Figure 2-4. Processes, threads, apartments, and objects.

Because an STA has a single thread associated with it for its entire lifetime, apartment model objects residing in an STA will never be accessed concurrently and are guaranteed to always run on the same thread. In fact, no matter how many objects are in the apartment, only one method call will be executing concurrently. This helps component developers considerably: they do not need to synchronize access to per-object state, and they can use thread-local storage if necessary. (Developers do need to synchronize access to global variables, however.)

Synchronization in the STA is based on Windows messages. Calls are queued as special messages and processed by a hidden window created by COM. This means that threads associated with an STA must have a message pump. A message pump is a special program loop that retrieves Windows messages from the current thread's message queue, translates them, and dispatches the messages to other parts of the application. If the thread does not have a message pump, no calls will be processed. The STA synchronization model does not prevent re-entrancy; it just provides thread safety. It is exactly the same model used by window procedures. During method calls to other apartments or processes, COM continues to pump messages, so the STA can process incoming calls and the thread's windows are responsive. Called objects can call back to other objects in the STA without fear of deadlock.

Components obeying the apartment model are easy to write, but the concurrency constraints can create a performance bottleneck that is unacceptable. In this case, the components might need to support the free-threaded model. COM does not synchronize access to objects in the MTA. Threads are dynamically allocated as needed to service all concurrent calls into the MTA. Thus, it is possible for free-threaded objects to be accessed concurrently by multiple threads, which can improve performance.

Writing thread-safe code can be difficult. Developers must protect both global variables and per-object state. They must also worry about whether functions from run-time or statically linked libraries are thread-safe. One of the nice things about COM is that it gives you a choice of threading models when you write your code; it also takes care of any mismatches between the threading models supported by a caller and an object.

This feature is particularly interesting for in-process servers. In-process servers normally use the threads of their client process instead of creating their own threads. And in order for the client process to create a COM object, it must have already called CoInitialize or CoInitializeEx to establish the apartment associated with the calling thread. But then how does COM ensure that the caller's apartment is compatible with the threading model supported by the created object? When a developer creates a component, he or she specifies the threading model it supports using a named registry value on the InprocServer32 key, as shown here:

 HKCR\CLSID\{45D3F4B1-DB76-11d1-AA06-0040052510F1}\InprocServer32    @="salestax.dll"    ThreadingModel="Apartment"

COM uses the ThreadingModel value to determine which apartment an object will be created in, as shown in Table 2-1. For example, if the caller's apartment is an STA and the component's ThreadingModel is Apartment, the object will be created in the caller's STA. All calls from the caller to the object will be direct calls; no marshaling is needed. If the caller's apartment is the MTA and the component's ThreadingModel is Apartment, however, the object will be created in a new STA. All calls to the object from the caller will be marshaled.

Table 2-1. In-process server threading model options used to determine where new objects are created.

Caller's Apartment
ThreadingModel Value Main STA STA MTA
Unspecified Main STA Main STA Main STA
Apartment Main STA Calling STA New STA created
Free MTA MTA MTA
Both Main STA Calling STA MTA

Most applications and components available today either support the apartment model or are single-threaded (that is, they use the main STA). Apartment model components offer a nice balance of ease of development and performance. And as we'll see in Chapter 4, MTS provides features to help apartment model components scale to support large numbers of users.



Designing Component-Based Applications
Designing Component-Based Applications
ISBN: 0735605238
EAN: 2147483647
Year: 1997
Pages: 98
Authors: Mary Kirtland
BUY ON AMAZON

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