6    COM Threading Models

A process is a collection of virtual memory space, code, data, and system resources, while a thread is code that is to be serially executed within a process. A processor executes threads, not processes, so each application has at least one process and one thread. Prior to the introduction of multiple threads of execution, applications were all designed to run on a single thread of execution. Processes communicate with one another through messages, using RPC to pass information between processes. There is no difference to the caller in a call coming from a process on a remote machine, and a call from another process on the same machine.

While COM defines three multiple-threading models, some information applies to threads and processes in general. A process always has at least one thread of execution, known as the primary thread, and can have multiple threads in addition to this. Once a thread begins to execute, it continues until it is killed or until it is interrupted by a thread with higher priority (by a user action or the kernel's thread scheduler). Each thread can run separate sections of code, or multiple threads can execute the same section of code. Threads executing the same block of code maintain separate stacks. Each thread in a process shares that process's global variables and resources.

When and how often to a thread is executed is platform dependent.

Multi-threaded applications must avoid two threading problems: deadlocks and races. A deadlock occurs when each thread is waiting for the other to do something. A race condition occurs when one thread finishes before another on which it depends, causing the former to use a bogus value because the latter has not yet supplied a valid one.

The COM call control helps prevent deadlocks in calls between objects. COM supplies some functions specifically designed to help avoid race conditions in out-of-process servers; for information refer to Section Section 8.6.

While COM supports the single-thread-per-process model prevalent before the introduction of multiple threads of execution, writing code to take advantage of multiple threads make it possible to create more efficient applications than ever before by allowing a thread that is waiting for some time-consuming operation to allow another thread to be executed.

It is worth noting that using multiple threads is not a guarantee of better performance. In fact, because ``thread-factoring'' is a difficult problem, using multiple threads often causes performance problems . The key is to use multiple threads only if you are very sure of what you are doing.

In general, the simplest way to view COM's threading architecture is to think of all the COM objects in the process as divided into groups called apartments. A COM object lives in exactly one apartment, in the sense that its methods can legally be called directly only by a thread that belongs to that apartment. Any other thread that wants to call the object must go through a proxy.

There are two types of apartments: single-threaded apartments, and multi-threaded apartments.

A description of communication between single-threaded apartments and multi-threaded apartments within the same process is in Section Section 6.4.

Single-threaded apartments consist of exactly one thread, so all COM objects that live in a single-threaded apartment can receive method calls only from the one thread that belongs to that apartment. All method calls to a COM object in a single-threaded apartment are synchronized with the windows message queue for the single-threaded apartment's thread.

Multi-threaded apartments consist of one or more threads, so all COM objects that live in an multi-threaded apartment can receive method calls directly from any of the threads that belong to the multi-threaded apartment. Threads in a multi-theaded apartment use a model called ``free-threading''. COM does not provide any synchronization of method calls to COM objects in a multi-threaded apartment. In particular, this means that the COM object must provide its own synchronization if needed.

A process can have zero or more single-threaded apartments, and zero or one multi-threaded apartment. One way of looking at this is the following:

In reality, however, all process are apartment-model, it is just that some apartments have a single thread and some apartments have multiple threads. The threading model really applies to an apartment, not to a process. It can also apply to a class of objects, but it doesn't really apply to a component, such as a DLL, but to the object classes within the DLL. Different classes in a DLL can have different threading models.

In a process, the main apartment is the first to be initialized. In a single-threaded process, this remains the only apartment. Call parameters are marshaled between apartments, and COM handles the synchronization through messaging. If you designate multiple threads in a process to be free-threaded, all free threads reside in a single apartment, parameters are passed directly to any thread in the apartment, and you must handle all synchronization. In a process with both free-threading and apartment threading, all free threads reside in a single apartment, and all other apartments are single-threaded apartments. A process that does COM work is a collection of apartments with, at most, one multi-threaded apartment but any number of single-threaded apartments.

The threading models in COM provide the mechanism for clients and servers that use different threading architectures to work together. Calls among objects with different threading models in different processes are naturally supported. From the perspective of the calling object, all calls to objects outside a process behave identically, no matter how the object being called is threaded. Likewise, from the perspective of the object being called, arriving calls behave identically, regardless of the threading model of the caller.

Interaction between a client and an out-of-process object is straightforward even when they use different threading models because the client and object are in different processes and COM is involved in remoting calls from the client to the object. COM, interposed between the client and the server, can provide the code for the threading models to interoperate, with standard marshaling and RPC. For example, if a single-threaded object is called simultaneously by multiple free-threaded clients, the calls will be synchronized by COM by placing corresponding window messages in the server's message queue. The object's apartment will receive one call each time it retrieves and dispatches messages.

Some care must be taken to ensure that in-process servers interact properly with their clients. These issues are described in In-process Server Threading Issues.

The most important issue in programming with a multithreaded model is to ensure that the code is thread-safe, so messages intended for a particular thread go only to that thread, and access to threads is protected.

6.1    Choosing the Threading Model

Choosing the threading model for an object depends on the object's function. An object that does extensive I/O might support free-threading to provide maximum response to clients by allowing interface calls during I/O latency. On the other hand, an object that interacts with the user might support apartment threading to synchronize incoming COM calls with its window operations.

It is easier to support apartment threading in single-threaded apartments because COM provides synchronization. Supporting free-threading is more difficult because the object must implement synchronization, but response to clients may be better because synchronization can be implemented for smaller sections of code. In single-threaded apartments, COM provides synchronization on a per-call basis.

6.2    Single-threaded Apartments

Using single-threaded apartments (apartment model) offers a message-based paradigm for dealing with multiple objects running concurrently. It allows you to write more efficient code by allowing a thread that is waiting for some time-consuming operation to allow another thread to be executed.

Each thread in a process that is initialized as apartment-model, and which retrieves and dispatches window messages, is a single-threaded apartment thread. Each of these threads live within its own apartment. Within an apartment, interface pointers can be passed without marshaling. Thus, all objects in one single-threaded apartment thread communicate directly. Interface pointers must be marshaled when passed between apartments.

A logical grouping of related objects that all execute on the same thread, and so must have synchronous execution could live on the same single-threaded apartment thread. An apartment-model object cannot, however, reside on more than one thread. Calls to objects in other processes must be made within the context of the owning process, so distributed COM switches threads for you automatically when you call on a proxy.

The inter-process and inter-thread models are similar. When it is necessary to pass an interface pointer to an object in another apartment (on another thread) within the same process, you use the same marshaling model that objects in different processes use to pass pointers across process boundaries. By getting a pointer to the standard marshaling object, you can marshal interface pointers across thread boundaries (between apartments) in the same way you do between processes.

Rules for single-threaded apartments are simple, but it is important to follow them carefully:

While multiple objects can live on a single thread, no apartment-model object can live on more than one thread.

Each thread of a client process or out-of-process server must call or call CoInitializeEx(), and specify COINIT_APARTMENTTHREADED for the dwCoInit parameter. The main apartment is the thread that calls CoInitializeEx() first. For information on in-process servers, refer to Section Section 6.5.

All calls to an object must be made on its thread (within its apartment). It is forbidden to call an object directly from another thread; using objects in this free-threaded manner could cause problems for applications. The implication of this rule is that all pointers to objects must be marshaled when passed between apartments. COM provides two functions for this purpose. CoMarshalInterThreadInterfaceInStream(), marshals an interface into a stream object that is returned to the caller, and CoGetInterfaceAndReleaseStream() unmarshals an interface pointer from a stream object and releases it. These functions wrap calls to CoMarshalInterface() and CoUnmarshalInterface() functions, which require the use of the MSHCTX_INPROC flag.

In general, the marshaling is accomplished automatically by COM. For example, when passing an interface pointer as a parameter in a method call on a proxy to an object in another apartment, or when calling CoCreateInstance(), COM does the marshaling automatically. However, in some special cases, where the application writer is passing interface pointers between apartments without using the normal COM mechanisms, the application writer must be aware that he is passing a pointer between apartments, and must handle the marshaling himself.

If one apartment (Apartment 1) in a process has an interface pointer and another apartment (Apartment 2) requires its use, Apartment 1 must call CoMarshalInterThreadInterfaceInStream() to marshal the interface. The stream that is created by this function is thread-safe and must be stored in a variable that is accessible by Apartment 2. Apartment 2 must pass this stream to CoGetInterfaceAndReleaseStream() to unmarshal the interface, and will get back a pointer to a proxy through which it can access the interface. The main apartment must remain alive until the client has completed all COM work (because some in-process objects are loaded in the main-apartment, as described in In-process Server Threading Issues. After one object has been passed between threads in this manner, it is very easy to pass interface pointers as parameters. That way distributed COM does the marshaling and thread switching for the application.

To handle calls from other processes and apartments within the same process, each single-threaded apartment must have a message loop. This means that the thread's work function must have a message loop.

[Footnote 40] COM creates a hidden message sink in each single-threaded apartment. A call to an object is received as a message to this message sink [Footnote 40]. When the object's apartment retrieves and dispatches the message, the hidden window will receive it. The window procedure will then call the corresponding interface method of the object.

When multiple clients call an object, the calls are queued in the message queue and the object will receive a call each time its apartment retrieves and dispatches messages. Because the calls are synchronized by COM and the calls are always delivered by the thread that belongs to the object's apartment, the object's interface implementations need not provide synchronization. Single-threaded apartments can implement IMessageFilter() to permit them to cancel calls or receive messages when necessary.

The object can be re-entered if one of its interface method implementations retrieves and dispatches messages or makes an ORPC call to another thread, thereby causing another call to be delivered to the object (by the same apartment). COM does not prevent re-entrancy on the same thread but it provides thread safety. This is identical to the way in which a window procedure can be re-entered if it retrieves and dispatches messages while processing a message.

6.3    Multi-threaded Apartments

In a multi-threaded apartment, all the threads in the process that have been initialized as free-threading reside in a single apartment. Therefore, there is no need to marshal between threads. The threads need not retrieve and dispatch messages because COM does not use messages in this model.

Calls to methods of objects in the multi-threaded apartment can be run on any thread in the apartment. There is no serialization of calls - many calls may occur to the same method or to the same object simultaneously. Objects created in the multi-threaded apartment must be able to handle calls on their methods from other threads at any time.

Multi-threaded object concurrency offers the highest performance and takes the best advantage of multi-processor hardware for cross-thread, cross-process, and cross-machine calling, since calls to objects are not serialized in any way. This means, however, that the code for objects must provide synchronization in their interface implementations, typically through the use of platform dependent synchronization primitives. In addition, because the object doesn't control the lifetime of the threads that are accessing it, no thread-specific state may be stored in the object (in Thread-Local-Storage).

COM provides call synchronization for single-threaded apartments only. Multi-threaded apartments (containing free-threaded threads) do not receive calls while making calls (on the same thread). Multi-threaded apartments cannot make input synchronized calls. Asynchronous calls are converted to synchronous calls in multi-threaded apartments. The message filter is not called for any thread in a multi-threaded apartment.

To initialize a thread as free-threaded, call CoInitializeEx(), specifying COINIT_MULTITHREADED. For information on in-process server threading, see Section Section 6.5.

Multiple clients can call an object that supports free-threading simultaneously from different threads: In free threaded out-of-process servers, COM, through the RPC sub-system, creates a pool of threads in the server process and a client call (or multiple client calls) can be delivered by any of these threads at any time. An out-of-process server must also implement synchronization in its class factory. Free threaded, in-process objects can receive direct calls from multiple threads of the client.

The client can do COM work in multiple threads. All threads belong to the same multi-threaded apartment. Interface pointers are passed directly from thread to thread within a multi-threaded apartment so interface pointers are not marshaled between its threads. Message filters (implementations of IMessageFilter()) are not used in multi-threaded apartments. The client thread will suspend when it makes a COM call to out-of-apartment objects and will resume when the call returns. Calls between processes are still handled by RPC.

Threads initialized with the free-threading model must implement their own synchronization.

6.4    Single-/Multi-threaded Communication

A client or server that supports both single and multi-threaded apartments will have one multi-threaded apartment, containing all threads initialized as free-threaded, and one or more single-threaded apartments. Interface pointers must be marshaled between apartments but can be used without marshaling within an apartment. Calls to objects in a single-threaded apartment will be synchronized by COM. Calls to objects in the multi-threaded apartment will not be synchronized by COM.

All of the information on single-threaded apartments applies to the threads marked as apartment model, and all of the information on multi-threaded apartments applies to all of the threads marked as free -threaded. Apartment threading rules apply to inter-apartment communication, requiring that interface pointers be marshaled between apartments with calls to CoMarshalInterThreadInterfaceInStream() and CoGetInterfaceAndReleaseStream(), as described in the Single-threaded Apartments section. For information on free-threading, see Section Section 6.3. Some special considerations apply when dealing with in-process servers, as described in In-process Server Threading Issues.

6.5    In-process server Threading Issues

An in-process server does not call CoInitialize(), or CoInitializeEx() to mark its threading model. For thread-aware DLL-based or in-process objects, you need to set the threading model in the registry. The default model when you do not specify a threading model is single-thread-per-process. To specify a model, you add the ThreadingModel named-value to the InprocServer32 key in the registry.

DLLs that support instantiation of a class object must implement and export the functions DllGetClassObject() and DllCanUnloadNow(). When a client wants an instance of the class the DLL supports, a call to CoGetClassObject() (either directly or through a call to CoCreateInstance()) calls DllGetClassObject() to get a pointer to its class object when the object is implemented in a DLL. DllGetClassObject() should therefore be able to give away multiple class objects or a single thread-safe object (essentially just using the equivalent to the Win32 InterlockedIncrement/InterlockedDecrement APIs on their internal reference count).

As its name implies, DllCanUnloadNow() is called to determine whether the DLL that implements it is in use, so the caller can safely unload it if it is not. Calls to CoFreeUnusedLibraries() from any thread always route through the main apartment's thread to call DllCanUnloadNow().

Like other servers, in-process servers can be single-threaded, apartment-threaded, free-threaded, or both. Each of these servers can be used by any client, regardless of the threading model used by that client. There are certain considerations peculiar to client/inprocess-server interoperation.

All combinations of threading model interoperability are allowed between clients and in-process objects. Interaction between a client and an in-process object that use different threading models is exactly like the interaction between clients and out-of-process servers. For an in-process server, when the threading model of the client and inprocess server differ, COM must interpose itself between the client and the object.

When an in-process object that supports the single threading model is be called simultaneously by multiple threads of a client, COM cannot allow the client threads to directly access the object's interface because the object was not designed for such access. Instead COM must ensure that calls are synchronized and are made only by the client thread that created the object. Therefore, COM creates the object in the client's main apartment and requires all the other client apartments to access the object using proxies.

When a free-threaded apartment (multi-threaded apartment) in a client creates an apartment-threaded in-process server, COM spins up a single-threaded apartment model 'host' thread in the client. This host thread will create the object and the interface pointer will be marshaled back to the client's free-threaded apartment. Similarly, when a single-threaded apartment in an apartment-model client creates a free threading in-process server, COM spins up a free-threading host thread (multi-threaded apartment on which the object will be created and marshaled back to the client single-threaded apartment.

In general, if you design a custom interface on an in-process server, you should also provide the marshaling code for it so COM can marshal the interface between client apartments.

COM protects access to objects provided by a single-threaded DLL by requiring access from the same client apartment in which they were created. In addition, all of the DLL's entry points (like DllGetClassObject() and DllCanUnloadNow()) and global data should always be accessed by the same apartment. COM creates such objects in the main apartment of the client, giving the main apartment direct access to the object's pointers. Calls from the other apartments use inter-thread marshaling to go from the proxy to the stub in the main apartment (using inter-thread marshaling) and then to the object. This allows COM to synchronize calls to the object. Inter-thread calls are slow, so it is recommended that these servers be rewritten to support multiple apartments.

Like a single-threaded in-process server, an object provided by an apartment-model DLL must be accessed by the same client apartment from which it was created. Objects provided by this server, however, can be created in multiple apartments of the client, so the server must implement its entry points (like DllGetClassObject() and DllCanUnloadNow()) for multi-threaded use. For example, if two apartments of a client try to create two instances of the in-process object simultaneously, DllGetClassObject() can be called simultaneously by both apartments. DllCanUnloadNow() must be written so the DLL is protected from being unloaded while code is still executing in the DLL.

If the DLL provides only one instance of the class factory to create all the objects, the class factory implementation must also be designed for multi-threaded use because it will be accessed by multiple client apartments. If the DLL creates a new instance of the class factory each time DllGetClassObject() is called, the class factory need not be thread-safe.

Objects created by the class factory need not be thread-safe. Once created by a thread, the object is always accessed through that thread and all calls to the object are synchronized by COM. The apartment-model apartment of a client that creates this object will get a direct pointer to the object. Client apartments which are different from the apartment in which the object was created must access the object through proxies. These proxies are created when the client marshals the interface between its apartments.

When an in-process DLL's ThreadingModel named-value is set to Both, an object provided by this DLL can be created and used directly (without a proxy) in single- or multi-threaded client apartments. However, it can only be used directly within the apartment in which it was created. To give the object to any other apartment, the object must be marshaled. The DLL's object must implement its own synchronization and can be accessed by multiple client apartments at the same time.

To speed performance for free-threaded access to in-process DLL objects, COM provides the CoCreateFreeThreadedMarshaler() function. This function creates a free-threaded marshaling object that can be aggregated with an in-process server object. When a client apartment in the same process needs access to an object in another apartment, aggregating the free threaded marshaler provides the client with a direct pointer to the server object, rather than to a proxy, when the client marshals the object's interface to a different apartment. The client does not need to do any synchronization. This works only within the same process--standard marshaling is used for a reference to the object that is sent to another process.

An object provided by in-process DLL that supports only free threading is a free-threaded object. It implements its own synchronization and can be accessed by multiple client threads at the same time. This server does not marshal interfaces between threads so this server can be created and used directly (without a proxy) only by multi-threaded apartments in a client. Single-threaded apartments that create it will access it through a proxy.

6.6    Process and Thread Related Interface Descriptions

6.6.1    IMessageFilter

The IMessageFilter() interface provides COM servers and applications with the ability to selectively handle incoming and outgoing COM messages while waiting for responses from synchronous calls. Filtering messages helps to ensure that calls are handled in a manner that improves performance and avoids deadlocks. COM messages can be synchronous, asynchronous, or input-synchronized; the majority of interface calls are synchronous.

Synchronous calls require the caller to wait for a reply before continuing. COM enters a modal loop while waiting for the reply. During this time, the caller is still able to receive and dispatch incoming messages.

Asynchronous calls allow the caller to proceed without waiting for a response from the called object. Today, in COM, the only asynchronous calls are to an object's IAdviseSink() interface. While the object is processing an asynchronous call, it is prohibited from making any synchronous calls back to the calling object.

Input-synchronized calls require the called object to complete the call before relinquishing control, ensuring that behaviors such as focus management and type-ahead function correctly.

When to Implement

You will probably want to implement your own message filter. The default implementation provided by COM offers only minimal message filtering capability. Although message filtering is no longer as significant as it was with 16-bit applications, since the size of the message queue is now virtually unlimited, you still should implement IMessageFilter() as a way of resolving deadlocks.

COM will call your implementation of IMessageFilter() to find out if an application is blocking, so that you can task-switch to that application and give the user an opportunity to deal with the situation. For example, if you have Microsoft Word talking to Microsoft Excel, with Excel running in the background in formula mode, in which formulas are being applied to data on the worksheet to compute different or ``what if'' results, Excel won't check all calls, thereby blocking further action. IMessageFilter() would put up a dialog box indicating which task was blocking and provide the user with an opportunity to deal with the deadlock.

Although it is probably obvious from the method descriptions, it may still be useful to point out that HandleIncomingCall is an object-based method and RetryRejectedCall and MessagePending are client-based methods. Clearly, the object must have some way of handling incoming calls from external clients. HandleIncomingCall provides that functionality by allowing the object to handle or defer some incoming calls and reject others. The client also needs to know how an object is going to handle its call. so that it can respond appropriately. The client needs to know if a call has been rejected, or just deferred temporarily, so that it can retry rejected calls after some specified time. The client also needs to be able to respond to Windows messages, while at the same time waiting for replies to pending messages.

You will use CoRegisterMessageFilter() to register your message filter. Once registered, COM then calls your message filter instead of the default implementation.

When to Use

You don't call this interface directly. It's provided by the COM server or application and called by the COM.

Application Shutdown with WM_QUERYENDSESSION and WM_ENDSESSION

When a user exits Windows, each open application receives a WM_QUERYENDSESSION message followed by a WM_ENDSESSION message, provided the exit is not canceled. These messages are invoked with SendMessage(), which unfortunately restricts the initiation of all outgoing LRPC calls. This is a problem for container applications that have open embedded objects when they receive the shutdown request because LRPC is needed to close those objects.

Container and container/server applications with open documents typically display a message box on receipt of the WM_QUERYENDSESSION message that asks if the user wants to save changes before exiting. A positive response is usually the default. The recommendation for dealing with the situation described above is for the application to display an alternate message box asking if the user wants to discard changes; a negative response should be the default. If the user chooses to discard the changes, TRUE should be returned for WM_QUERYENDSESSION, which signals to Windows that it can terminate. If the user does not want to discard the changes, FALSE should be returned. No attempt should be made to close or release running embeddings.

Server applications should return TRUE for WM_QUERYENDSESSION without prompting the user.On receipt of a WM_ENDSESSION message, all COM applications should execute the normal close sequence for each application's documents and/or objects. At the same time, you should ignore any errors resulting from any cross-process calls or calls to IUnknown::Release(). All storage pointers (IStorage() and IStream() interface pointers) must be released to properly flush any temporary files maintained by the compound file implementation of structured storage.

Table 6-1:  IMessageFilter Methods in VTable Order

IUnknown Methods Description
QueryInterface() Returns a pointer to a specified interface.
AddRef() Increments the reference count.
Release() Decrements the reference count.

Table 6-2:  IMessageFilter Methods

IMessageFilter() Methods Description
HandleIncomingCall Provides a single entry point for incoming calls.
RetryRejectedCall Provides application with opportunity to display a dialog box offering retry or cancel or task switching options.
MessagePending Indicates a Windows message has arrived while COM is waiting to respond to a remote call.

See Also

CoRegisterMessageFilter(), OLEUIBUSY, In the WIN32 SDK: ``Messages and Message Queues''  

IMessageFilter::HandleInComingCall()

NAME

IMessageFilter::HandleInComingCall() - An object-based method that provides the ability to filter or reject incoming calls (or call backs) to an object or a process. This method is called prior to each method invocation originating outside the current process.

Synopsis

#include <objidl.h>

DWORD HandleInComingCall(
        DWORD dwCallType,
        HTASK threadIDCaller,
        DWORD dwTickCount,
        LPINTERFACEINFO lpInterfaceInfo );

Description

If implemented, IMessageFilter::HandleInComingCall is called by COM when an incoming COM message is received.

Depending on an application's current state, a call is either accepted and processed or rejected (permanently or temporarily). If SERVERCALL_ISHANDLED is returned, the application may be able to process the call, though success depends on the interface for which the call is destined. If the call cannot be processed, COM returns RPC_E_CALL_REJECTED.

Input-synchronized and asynchronous calls are dispatched even if the application returns SERVERCALL_REJECTED or SERVERCALL_RETRYLATER.

IMessageFilter::HandleInComingCall() should not be used to hold off updates to objects during operations such as band printing. For that purpose, use IViewObject::Freeze.

You can also use IMessageFilter::HandleInComingCall() to set up the application's state so the call can be processed in the future.

Parameters

dwCallType

[in] Kind of incoming call that has been received. Valid values are from the enumeration CALLTYPE. See Chapter 13 for details.

threadIDCaller

[in] Handle of the task calling this task.

dwTickCount

[in] Elapsed tick count since the outgoing call was made if dwCallType is not CALLTYPE_TOPLEVEL. If dwCallType is CALLTYPE_TOPLEVEL, dwTickCount should be ignored.

lpInterfaceInfo

[in] Pointer to an INTERFACEINFO structure, which identifies the object, the interface, and the method making the call.

Return Values

SERVERCALL_ISHANDLED

The application might be able to process the call.

SERVERCALL_REJECTED

The application cannot handle the call due to an unforeseen problem, such as network unavailability, or if it is in the process of terminating.

SERVERCALL_RETRYLATER

The application cannot handle the call at this time. For example, an application might return this value when it is in a user-controlled modal state.

See Also

CALLTYPE, INTERFACEINFO  

IMessageFilter::MessagePending()

NAME

IMessageFilter::MessagePending() - A client-based method called by COM when a Windows message appears in a COM application's message queue while the application is waiting for a reply to a remote call. Handling input while waiting for an outgoing call to finish can introduce complications. The application should determine whether to process the message without interrupting the call, continue waiting, or cancel the operation.

Synopsis

#include <objidl.h>

DWORD MessagePending(
        HTASK threadIDCallee,
        DWORD dwTickCount,
        DWORD dwPendingType );

Description

COM calls IMessageFilter::MessagePending() after an application has made a COM method call and a Windows message occurs before the call has returned. A Windows message is sent, for example, when the user selects a menu command or double-clicks an object. Before COM makes the IMessageFilter::MessagePending() call, it calculates the elapsed time since the original COM method call was made. COM delivers the elapsed time in the dwTickCount parameter. In the meantime, COM does not remove the message from the queue.

Windows messages that appear in the caller's queue should remain in the queue until sufficient time has passed to ensure that the messages are probably not the result of typing ahead, but are, instead, an attempt to get attention. Set the delay with the dwTickCount parameter--a two- or three-second delay is recommended. If that amount of time passes and the call has not been completed, the caller should flush the messages from the queue, and the COM UI busy dialog box should be displayed offering the user the choice of retrying the call (continue waiting) or switching to the task identified by the threadIDCallee parameter. This ensures that:

Handling input while waiting for an outgoing call to finish can introduce complications. The application should determine whether to process the message without interrupting the call, continue waiting, or cancel the operation.

When there is no response to the original COM call, the application can cancel the call and restore the COM object to a consistent state by calling IStorage::Revert() on its storage. The object can be released when the container can shut down. However, canceling a call can create orphaned operations and resource leaks. Canceling should be used only as a last resort. It is strongly recommended that applications not allow such calls to be canceled.

Parameters

threadIDCallee

[in] Task handle of the called application that has not yet responded.

dwTickCount

[in] Number of ticks since the call was made. It is calculated from the Windows GetTickCount function.

dwPendingType

[in] Type of call made during which a message or event was received. Valid values are from the enumeration PENDINGTYPE (where PENDINGTYPE_TOPLEVEL means the outgoing call was not nested within a call from another application and PENDINTGYPE_NESTED means the outgoing call was nested within a call from another application).

Return Values

PENDINGMSG_CANCELCALL

Cancel the outgoing call. This should be returned only under extreme conditions. Canceling a call that has not replied or been rejected can create orphan transactions and lose resources. COM fails the original call and returns RPC_E_CALL_CANCELLED.

PENDINGMSG_WAITNOPROCESS

Continue waiting for the reply and do not dispatch the message unless it is a task-switching or window-activation message. A subsequent message will trigger another call to IMessageFilter::MessagePending(). Leaving messages or events in the queue enables them to be processed normally, if the outgoing call is completed. Note that returning PENDINGMSG_WAITNOPROCESS can cause the message queue to fill.

PENDINGMSG_WAITDEFPROCESS

Because of the increased resources available in 32-bit systems, you are unlikely to get this return value. It now indicates the same state as PENDINGMSG_WAITNOPROCESS.

Keyboard and mouse messages are no longer dispatched, as was done with PENDINGMSG_WAITDEFPROCESS. However there are some cases where mouse and keyboard messages could cause the system to deadlock, and in these cases, mouse and keyboard messages are discarded. WM_PAINT messages are dispatched. Task-switching and activation messages are handled as before.

See Also

IStorage::Revert(), IStorage::Revert(), OLEUIBUSY, In the WIN32 SDK: GetTickCount  

IMessageFilter::RetryRejectedCall()

NAME

IMessageFilter::RetryRejectedCall() - A client-based method that gives the application an opportunity to display a dialog box so the user can retry or cancel the call, or switch to the task identified by threadIDCallee.

Synopsis

#include <objidl.h>

DWORD RetryRejectedCall(
        HTASK threadIDCallee,
        DWORD dwTickCount,
        DWORD dwRejectType );

Description

COM calls RetryRejectedCall on the caller's IMessageFilter() immediately after receiving SERVERCALL_RETRYLATER or SERVERCALL_REJECTED from the IMessageFilter::HandleInComingCall() method on the callee's IMessageFilter().

If a called task rejects a call, the application is probably in a state where it cannot handle such calls, possibly only temporarily. When this occurs, COM returns to the caller and issues IMessageFilter::RetryRejectedCall() to determine if it should retry the rejected call.

Applications should silently retry calls that have returned with SERVERCALL_RETRYLATER. If, after a reasonable amount of time has passed, say about 30 seconds, the application should display the busy dialog box; a standard implementation of this dialog box is available in the OLEDLG library. The callee may momentarily be in a state where calls can be handled. The option to wait and retry is provided for special kinds of calling applications, such as background tasks executing macros or scripts, so that they can retry the calls in a nonintrusive way.

If, after a dialog box is displayed, the user chooses to cancel, RetryRejectedCall returns -1 and the call will appear to fail with RPC_E_CALL_REJECTED.

Parameters

threadIDCallee

[in] Handle of the server task that rejected the call.

dwTickCount

[in] Number of elapsed ticks since the call was made.

dwRejectType

[in] Specifies either SERVERCALL_REJECTED or SERVERCALL_RETRYLATER, as returned by the object application.

Return Values

-1

The call should be canceled. COM then returns RPC_E_CALL_REJECTED from the original method call.

Value >= 0 and <100

The call is to be retried immediately.

Value >= 100

COM will wait for this many milliseconds and then retry the call.

6.7    Process and Thread Related API Descriptions

 

CoCreateFreeThreadedMarshaler()

NAME

CoCreateFreeThreadedMarshaler() - Creates an aggregatable object capable of context-dependent marshaling.

Synopsis

#include <objbase.h>

HRESULT CoCreateFreeThreadedMarshaler((
        LPUNKNOWN punkOuter,
        LPUNKNOWN * ppunkMarshaler );

Description

The CoCreateFreeThreadedMarshaler() function enables an object to efficiently marshal interface pointers between threads in the same process. If your objects do not support interthread marshaling, you have no need to call this function. It is intended for use by free-threaded DLL servers that must be accessed directly by all threads in a process, even those threads associated with single-threaded apartments. It custom-marshals the real memory pointer over into other apartments as a bogus ``proxy'' and thereby gives direct access to all callers, even if they are not free-threaded.

The CoCreateFreeThreadedMarshaler() function performs the following tasks:

  1. Creates a free-threaded marshaler object.

  2. Aggregates this marshaler to the object specified by the punkOuter parameter. This object is normally the one whose interface pointers are to be marshaled.

The aggregating object's implementation of IMarshal() should delegate QueryInterface() calls for IID_IMarshal() to the IUnknown of the free-threaded marshaler. Upon receiving a call, the free-threaded marshaler performs the following tasks:

  1. Checks the destination context specified by the CoMarshalInterface() function's dwDestContext parameter.

  2. If the destination context is MSHCTX_INPROC, copies the interface pointer into the marshaling stream.

  3. If the destination context is any other value, finds or creates an instance of COM's default (standard) marshaler and delegates marshaling to it.

Values for dwDestContext come from the MSHCTX enumeration. MSHCTX_INPROC indicates that the interface pointer is to be marshaled between different threads in the same process. Because both threads have access to the same address space, the client thread can dereference the pointer directly rather than having to direct calls to a proxy. In all other cases, a proxy is required, so CoCreateFreeThreadedMarshaler() delegates the marshaling job to COM's default implementation.

Great care should be exercised in using the CoCreateFreeThreadedMarshaler() function. This is because the performance of objects which aggregate the free-threaded marshaler is obtained through a calculated violation of the rules of COM, with the ever-present risk of undefined behavior unless the object operates within certain restrictions. The most important restrictions are:

  1. A free-threaded marshaler object cannot hold direct pointers to interfaces on an object that does not aggregate the free-threaded marshaler as part of its state. If the object were to use direct references to ordinary single-threaded aggregate objects, it may break their single threaded property. If the object were to use direct references to ordinary multi-threaded aggregate objects, these objects can behave in ways that show no sensitivity to the needs of direct single-threaded aggregate clients. For example, these objects can spin new threads and pass parameters to the threads that are references to ordinary single-threaded aggregate objects.

  2. A free-threaded marshaler object cannot hold references to proxies to objects in other apartments. Proxies are sensitive to the threading model and can return RPC_E_WRONG_THREAD if called by the wrong client.

Parameters

punkOuter

[in] Pointer to the aggregating object's controlling IUnknown().

ppunkMarshaler

[out] Address of IUnknown()* variable that receives the interface pointer to the aggregatable marshaler.

Return Values

This function supports the standard return value E_OUTOFMEMORY, as well as the following:

S_OK

The marshaler was created.

See Also

CoMarshalInterThreadInterfaceInStream(), CoGetInterfaceAndReleaseStream()  

CoGetInterfaceAndReleaseStream()

NAME

CoGetInterfaceAndReleaseStream() - Unmarshals a buffer containing an interface pointer and releases the stream when an interface pointer has been marshaled from another thread to the calling thread.

Synopsis

#include <objbase.h>

HRESULT CoGetInterfaceAndReleaseStream((
        LPSTREAM pStm,
        REFIID riid,
        LPVOID * ppv );

Description

The CoGetInterfaceAndReleaseStream() function performs the following tasks:

  1. Calls CoUnmarshalInterface() to unmarshal an interface pointer previously passed in a call to CoMarshalInterThreadInterfaceInStream().

  2. Releases the stream pointer. Even if the unmarshaling fails, the stream is still released because there is no effective way to recover from a failure of this kind.

Parameters

pStm

[in] Pointer to the IStream() interface on the stream to be unmarshaled.

riid

[in] Reference to the identifier of the interface requested from the unmarshaled object.

ppv

[out] Indirect Address of variable that receives the interface pointer requested in riid. Upon successful return, *ppv contains the requested interface pointer.

Return Values

This function supports the standard return value E_INVALIDARG, as well as the following:

S_OK

Indicates the output interface was unmarshaled and the stream was released.

This function can also return any of the values returned by CoUnmarshalInterface().

See Also

CoMarshalInterThreadInterfaceInStream(), CoUnmarshalInterface()  

CoMarshalInterThreadInterfaceInStream()

NAME

CoMarshalInterThreadInterfaceInStream() - Marshals an interface pointer from one thread to another thread in the same process.

Synopsis

#include <objbase.h>

HRESULT CoMarshalInterThreadInterfaceInStream((
        REFIID riid,
        LPUNKNOWN pUnk,
        LPSTREAM * ppStm );

Description

The CoMarshalInterThreadInterfaceInStream() function enables an object to easily and reliably marshal an interface pointer to another thread in the same process. The stream returned in ppStm is guaranteed to behave correctly when a client running in the receiving thread attempts to unmarshal the pointer. The client can then call the CoGetInterfaceAndReleaseStream() to unmarshal the interface pointer and release the stream object.

The CoMarshalInterThreadInterfaceInStream() function performs the following tasks:

  1. Creates a stream object.

  2. Passes the stream object's IStream() pointer to CoMarshalInterface().

  3. Returns the IStream() pointer to the caller.

Parameters

riid

[in] Reference to the identifier of the interface to be marshaled.

pUnk

[in] Pointer to the interface to be marshaled, which must be derived from IUnknown(); can be NULL.

ppStm

[out] Address of IStream* variable that receives the interface pointer to the stream that contains the marshaled interface.

Return Values

This function supports the standard return value E_OUTOFMEMORY, as well as the following:

S_OK

The interface was marshaled successfully.

See Also

CoGetInterfaceAndReleaseStream()

6.8    Process and Thread Related Structure Descriptions

6.8.1    INTERFACEINFO

The INTERFACEINFO structure contains information about incoming calls. The structure is defined as follows:

typedef struct tagINTERFACEINFO
{
    LPUNKNOWN  pUnk;
    IID        iid;
    WORD       wMethod;
} INTERFACEINFO,  * LPINTERFACEINFO;

Members

pUnk

Pointer to the IUnknown() interface on the object.

iid

Identifier of the requested interface

wMethod

Interface method.

See Also

IMessageFilter::HandleIncomingCall()

6.9    Process and Thread Related Enumeration Descriptions

6.9.1    CALLTYPE

The CALLTYPE enumeration constant specifies the call types used by IMessageFilter::HandleInComingCall().

typedef enum tagCALLTYPE
{
    CALLTYPE_TOPLEVEL             = 1,
    CALLTYPE_NESTED               = 2,
    CALLTYPE_ASYNC                = 3,
    CALLTYPE_TOPLEVEL_CALLPENDING = 4,
    CALLTYPE_ASYNC_CALLPENDING    = 5
} CALLTYPE;

Elements

CALLTYPE_TOPLEVEL

A top-level call has arrived and that the object is not currently waiting for a reply from a previous outgoing call. Calls of this type should always be handled.

CALLTYPE_NESTED

A call has arrived bearing the same logical thread identifier as that of a previous outgoing call for which the object is still awaiting a reply. Calls of this type should always handled.

CALLTYPE_ASYNC

An aysnchronous call has arrived. Calls of this type cannot be rejected. COM always delivers calls of this type.

CALLTYPE_TOPLEVEL_CALLPENDING

A new top-level call has arrived with a new logical thread identifier and that the object is currently waiting for a reply from a previous outgoing call. Calls of this type may be handled or rejected.

CALLTYPE_ASYNC_CALLPENDING

An asynchronous call has arrived with a new logical thread identifier and that the object is currently waiting for a reply from a previous outgoing call. Calls of this type cannot be rejected.

async call - can NOT be rejected

See Also

IMessageFilter::HandleInComingCall(), IMessageFilter()

6.9.2    PENDINGMESSAGE

The PENDINGMSG enumeration constants are return values of IMessageFilter::MessagePending().

Defined in the IMessageFilter() interface (msgflt.idl).

typedef enum tagPENDINGMSG
{
    PENDINGMSG_CANCELCALL        = 0,
    PENDINGMSG_WAITNOPROCESS     = 1,
    PENDINGMSG_WAITDEFPROCESS    = 2
} PENDINGMSG;

Elements

PENDINGMSG_CANCELCALL

Cancel the outgoing call.

PENDINGMSG_WAITNOPROCESS

Wait for the return and don't dispatch the message.

PENDINGMSG_WAITDEFPROCESS

Wait and dispatch the message.

See Also

IMessageFilter::MessagePending()

6.9.3    PENDINGTYPE

The PENDINGTYPE enumeration constants indicate the level of nesting in the IMessageFilter::MessagePending() method.

typedef enum tagPENDINGTYPE
{
    PENDINGTYPE_TOPLEVEL    = 1,
    PENDINGTYPE_NESTED      = 2
} PENDINGTYPE;

Elements

PENDINGTYPE_TOPLEVEL

Top-level call.

PENDINGTYPE_NESTED

Nested call.

See Also

IMessageFilter::MessagePending()

6.9.4    SERVERCALL

The SERVERCALL enumeration constants indicate the status of server call--returned by IMessageFilter::HandleInComingCall() and passed to IMessageFilter::RetryRejectedCall().

Defined in the IMessageFilter() interface (msgflt.idl).

typedef enum tagSERVERCALL
{
    SERVERCALL_ISHANDLED     = 0,
    SERVERCALL_REJECTED      = 1,
    SERVERCALL_RETRYLATER    = 2
} SERVERCALL;

Elements

SERVERCALL_ISHANDLED

The object may be able to process the call.

SERVERCALL_REJECTED

The object cannot handle the call due to an unforeseen problem, such as network unavailability.

SERVERCALL_RETRYLATER

The object cannot handle the call at this time. For example, an application might return this value when it is in a user-controlled modal state.

See Also

IMessageFilter::HandleInComingCall(), IMessageFilter::RetryRejectedCall()