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.
Single-threaded Apartments
-- each
thread that uses COM is in a
separate ``apartment'', and COM synchronizes all incoming calls with the windows
message queue.
A process with a single thread of execution is simply a special
case of this model.
Multi-threaded Apartments
-- Multiple
threads in a single
free-threaded apartment use COM and calls to COM objects are synchronized
by
the objects themselves.
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:
A process that consists of just one single-threaded apartment is referred to as a single-threaded process
A process that has two or more single-threaded apartments and no multi-threaded apartments is called an apartment model process
A process that has a multi-threaded apartment and no single-threaded apartments is referred to as a free-threaded process
A process that has a multi-threaded apartment and one or more single-threaded apartments is a mixed model process.
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.
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.
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:
Every object should live on only one thread (within a single-threaded apartment).
Initialize the COM library for each thread.
Marshal all pointers to objects when passing them between apartments.
Each single-threaded apartment must allow the, platform specific, message pump to handle calls from other processes and apartments within the same process.
DLL-based or in-process objects do not call the COM initialization
functions;
instead, they register their threading model with the
ThreadingModel
named-value under the
InprocServer32
key in the registry.
Apartment-aware objects must also write DLL entry points carefully.
There
are
special considerations that apply to threading in-process servers.
For more
information, see Section
Section 6.5.
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.
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.
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
.
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.
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.
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.
You don't call this interface directly. It's provided by the COM server or application and called by the COM.
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.
IUnknown Methods
|
Description
|
QueryInterface()
|
Returns a pointer to a specified interface. |
AddRef()
|
Increments the reference count. |
Release()
|
Decrements the reference count. |
|
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. |
CoRegisterMessageFilter()
,
OLEUIBUSY
, In the
WIN32 SDK: ``Messages
and Message
Queues''
- 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.
IMessageFilter::HandleInComingCall()
DWORD HandleInComingCall(
#include <objidl.h>
DWORD dwCallType,
HTASK threadIDCaller,
DWORD dwTickCount,
LPINTERFACEINFO lpInterfaceInfo
);
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.
[in] Kind of incoming call that has been received.
Valid values
are
from the
enumeration
CALLTYPE
.
See
Chapter 13
for details.
[in] Handle of the task calling this task.
[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.
[in] Pointer to an
INTERFACEINFO
structure,
which
identifies the object, the interface, and the method making the call.
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.
- 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.
IMessageFilter::MessagePending()
DWORD MessagePending(
#include <objidl.h>
HTASK threadIDCallee,
DWORD dwTickCount,
DWORD dwPendingType
);
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:
If calls are completed in a reasonable amount of time, type ahead will be treated correctly.
If the callee does not respond, type ahead is not misinterpreted and the user is able to act to solve the problem. For example, OLE 1 servers can queue up requests without responding when they are in modal dialog boxes.
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.
[in] Task handle of the called application that has not yet responded.
[in] Number of ticks since the call was made.
It is calculated
from
the Windows
GetTickCount
function.
[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).
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.
IStorage::Revert()
,
IStorage::Revert()
,
OLEUIBUSY
, In the
WIN32 SDK:
GetTickCount
- 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.
IMessageFilter::RetryRejectedCall()
DWORD RetryRejectedCall(
#include <objidl.h>
HTASK threadIDCallee,
DWORD dwTickCount,
DWORD dwRejectType
);
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
.
[in] Handle of the server task that rejected the call.
[in] Number of elapsed ticks since the call was made.
[in] Specifies either
SERVERCALL_REJECTED
or
SERVERCALL_RETRYLATER
, as returned by the object application.
The call should be canceled.
COM then returns
RPC_E_CALL_REJECTED
from the original method call.
The call is to be retried immediately.
COM will wait for this many milliseconds and then retry the call.
- Creates an aggregatable object capable of context-dependent marshaling.
CoCreateFreeThreadedMarshaler()
HRESULT CoCreateFreeThreadedMarshaler((
#include <objbase.h>
LPUNKNOWN punkOuter,
LPUNKNOWN * ppunkMarshaler
);
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:
Creates a free-threaded marshaler object.
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:
Checks the destination context specified by the
CoMarshalInterface()
function's
dwDestContext
parameter.
If the destination context is
MSHCTX_INPROC
,
copies the interface pointer
into the marshaling stream.
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:
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.
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.
[in] Pointer to the aggregating object's controlling
IUnknown()
.
[out] Address of
IUnknown()
* variable that
receives
the interface pointer to the
aggregatable marshaler.
This function supports the standard return value
E_OUTOFMEMORY
, as well
as the following:
S_OK
The marshaler was created.
CoMarshalInterThreadInterfaceInStream()
,
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.
CoGetInterfaceAndReleaseStream()
HRESULT CoGetInterfaceAndReleaseStream((
#include <objbase.h>
LPSTREAM pStm,
REFIID riid,
LPVOID * ppv
);
The
CoGetInterfaceAndReleaseStream()
function performs
the
following tasks:
Calls
CoUnmarshalInterface()
to unmarshal
an interface pointer
previously passed in a call to
CoMarshalInterThreadInterfaceInStream()
.
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.
[in] Pointer to the
IStream()
interface
on the stream
to be unmarshaled.
[in] Reference to the identifier of the interface requested from the unmarshaled object.
[out] Indirect Address of variable that receives the interface pointer requested in riid. Upon successful return, *ppv contains the requested interface pointer.
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()
.
CoMarshalInterThreadInterfaceInStream()
,
CoUnmarshalInterface()
- Marshals an interface pointer from one thread to another thread
in the same process.
CoMarshalInterThreadInterfaceInStream()
HRESULT CoMarshalInterThreadInterfaceInStream((
#include <objbase.h>
REFIID riid,
LPUNKNOWN pUnk,
LPSTREAM * ppStm
);
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:
Creates a stream object.
Passes the stream object's
IStream()
pointer
to
CoMarshalInterface()
.
Returns the
IStream()
pointer to the caller.
[in] Reference to the identifier of the interface to be marshaled.
[in] Pointer to the interface to be marshaled, which must
be derived
from
IUnknown()
; can be
NULL
.
[out] Address of
IStream*
variable that
receives
the interface pointer
to the stream that contains the marshaled interface.
This function supports the standard return value
E_OUTOFMEMORY
, as well
as the following:
S_OK
The interface was marshaled successfully.
CoGetInterfaceAndReleaseStream()
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;
Pointer to the
IUnknown()
interface on
the object.
Identifier of the requested interface
Interface method.
IMessageFilter::HandleIncomingCall()
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;
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.
NOT
be rejected
IMessageFilter::HandleInComingCall()
,
IMessageFilter()
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;
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.
IMessageFilter::MessagePending()
The
PENDINGTYPE
enumeration constants indicate the
level
of nesting in the
IMessageFilter::MessagePending()
method.
typedef enum tagPENDINGTYPE { PENDINGTYPE_TOPLEVEL = 1, PENDINGTYPE_NESTED = 2 } PENDINGTYPE;
PENDINGTYPE_TOPLEVEL
Top-level call.
PENDINGTYPE_NESTED
Nested call.
IMessageFilter::MessagePending()
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;
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.
IMessageFilter::HandleInComingCall()
,
IMessageFilter::RetryRejectedCall()