Overview
We start this section with a discussion about the abstract notion
of an 'execution context'.
Then we provide an overview of the primary execution context we
have in COM today, namely Apartments.
The crossing of execution context boundaries is tied to the COM
marshalling architecture in general and COM
"standard marshalling" in particular, so we look at that too. It
must be stressed here, though, that the marshalling trail is
optional and is there purely as background
information for the interested student.
Next, we will look at some of the basic
details of apartments.
Finally, we study how calls
are dispatched within an apartment.
Execution Context
This section aims to introduce the abstract concept of an
execution context and why it might be needed. The terms execution
context and context will be used synonymously.
Objects often have characteristics that define certain runtime
requirements in order for them to function correctly. They have
certain expectations about the ambient environment in which they
will execute. If such an object is executed in a different
environment, it will not work as expected. Examples might be as
follows.
- An object requires a transaction under which to perform its
work. If it doesn't have one, the integrity of the data it
manipulates may be compromised.
- An object has affinity to a thread and requires that its
methods always execute on that thread. This might be because it
makes use of thread local storage or window handles, each of which
has thread affinity.
- An object requires that it cannot ever be accessed
re-entrantly, else it will melt into a pool of object goo. Such an
object does not expect to have to lock down its state while it is
use.
To avoid complete anarchy we define the notion of an 'execution
context,' which allows us to group together objects with the same
characteristics to execute in the same controlled environment. An
object lives in exactly one context and there may be many
like-minded objects in the same context. We must also ensure that
this segregation is as transparent as possible, so that objects with
different characteristics can assume that all other objects are just
like themselves.
A caller has direct access to those objects in the same context
as itself. Either a caller always executes in that context or is
temporarily in that context for a short period of time. Either way,
its policies currently match those of the context it is in.
In order for a caller to access objects in a different context,
the caller must enter the target context via some controlled
mechanism that can alter the caller's environment to match those of
the target context. Once this context switch occurs, the objects in
the target context can be accessed directly. Emerging from the
target context back to the original context causes the callers
policies to be reset.
A context is necessarily a sub-division of a process, since we
can never obtain a direct pointer to an object in another
process.
A 'shim' object is used to call from one context to another. This
shim object maps between the different policies of the caller's
context and the target object's context. The shim is a lightweight
replacement for the target object in the caller's context - it must
look, feel, taste, and smell like the real target object but behave
according to the policies of the caller's context. When called, the
shim object transforms the caller's environment to use the policies
of the called object's context for the duration of the call. Finally
the shim restores the policies of the caller's context on return
from the call.
The shim object must be able to perform any necessary mapping of
parameters and return results as necessary between contexts. If the
target object's context cannot share the stack of the caller, this
may involve a technique called marshalling - serializing the call
context into a buffer to be transmitted between contexts. The
message-based protocol COM uses to transmit the call context (the
'remote' method call) is well defined. The mechanism used to
exchange messages will differ according to the exact differences
between contexts.
The process by which these shims are dynamically created when
required must be seamless enough to maintain the illusion that all
objects have the same characteristics. That is, the caller and
the called object must be unaware of the shim object between them.
This provides us with context transparency.
It might be worth mentioning here that the details of whether the
shim object requires a thread switch to facilitate the context
mapping is immaterial. Typically, this will depend on the policy
requirements of the disparate contexts. Some threads will execute in
the same context for the whole of their lifetime. Some will jump
between contexts as needed. The fact that the logical thread of
execution may need to switch physical OS threads is a detail that
doesnĄ¯t affect the programming model.

Diagram showing a caller and a called object living in
different execution contexts (with and without incurring a thread
switch).
| How does an object specify the context it
lives in? |
A class specifies declarative attributes that can be picked up by
the runtime environment such that the following can be enforced.
- An object of this class must be placed in an appropriate
context. If an appropriate context does not exist, one is created.
- A shim can be dynamically created when needed to interface
between an object of this class and a caller when they are in
different contexts.
| Examples of context (mismatch)
|
Let's look at some examples of different contexts where direct
access would be impossible, but would have to occur through a shim.
- The caller and target object are in different processes
This is the most obvious example. The target object lives in a
different address space than the caller. They cannot easily share
any resources. Certainly they do not share the same stack. The
call stack must be marshalled between one address space and the
other (possibly even across the network) by the shim.
- The caller and target object have different concurrency models
Some developers write thread-safe object code. They spend
man-months tuning multi-threaded access to their objects in order
to make them thread-safe and expect multiple threads to access
them concurrently in order to increase throughput to their object.
In turn, some threads expect everything they touch to be
thread-safe. Some developers do not write thread-safe code and
rely on the caller to sort out synchronization.The caller may not
want to; the shim must deal with the differences in concurrency
expectations.
- The object has affinity to a particular thread
Some objects maintain state in thread-local storage, often to
propagate information across function calls. This means the object
can only be accessed directly from one thread and it must be the
same thread every time. To ensure that this is the case, a
thread-switch may need to occur between the calling thread and the
thread used to directly access the object. The shim must sort this
out.
- The object and thread have different security
characteristics
A caller and a target object may need to represent two
different security principals. The shim between them may need to
perform access checks to ensure that the caller has the right to
execute a particular method. Also, the target object may want to
perform access checks on behalf of the caller to ensure that the
caller is in a role that is allowed access to the object's
resources. It may even want to impersonate the caller when
accessing those resources.
- Object and thread have different transaction requirements
An object may have a requirement to perform its work as part of
a transaction when the caller does not. A shim between them may be
able to start a new transaction and enlist into the transaction
any resources required by the object.
The rules of context are fairly simple.
- Every object lives in one, and only one context, for its
entire lifetime
- This defines the only place that direct calls to the object
can be dispatched
- Callers inside an object's context can access it directly
- Callers outside an object's context must access it through a
shim
- Any object reference passed out of the object's context must
be exported.
- Any exported object reference to be used outside of the
exporting objects context must be imported into the using
context
- Calling through a shim may or may not involve a thread
switch
To pass interface references between contexts, they must be
exported and imported. This is essentially the practice of remoting
an interface on an object by creating a proxy in one context which
talks to a stub in another in order to provide indirect access to
the object. Together, the proxy and stub represent the shim referred
to formerly.
It turns out that COM has a flexible protocol (ORPC) for invoking
functionality on remote objects and a well thought-out remoting
architecture with extensibility points for providing your own
interface remoting mechanisms. However, if you don't care to take
advantage of this, COM provides you with a default remoting
architecture that is called standard marshalling. We will
spend most of our time looking at this because it is simple and
because its implementation is tightly coupled to the idea of
context.
If you want to know more about marshalling and interface
remoting, have a look at the marshalling
and interface remoting trail now. Note that this trail
is optional and goes into more detail than is strictly necessary in
order to understand context and apartments.
If you would simply like to go on and learn about apartments,
click here.

|