|< BACK  NEXT >|
The configuration settings at all levels application, component, interface, and interface method essentially specify certain run-time requirements for a COM+ object. For example, a component might be (or might not be) interested in participating in a transaction. The component might be interested in using the synchronization or the security services provided by COM+. All these run-time requirements for the component s object are collectively referred to as the object s context. The context represents the environment in which the COM+ object lives, the sea in which it swims.
An object s context is created and attached to the object by COM+ during the object activation. This context remains fixed and immutable until the object is deactivated.
I have deliberately chosen the words object activation and deactivation, as opposed to object creation and destruction. As these terms are used quite frequently throughout the remainder of the book, it is important to understand their precise meaning.
Activating an object would really have been synonymous with creating an object, if not for object pooling, a feature provided by COM+. If object pooling is enabled on a component, a pool of objects is created upfront (the minimum and maximum sizes are configurable attributes on the component). When a client requests an object, using CoCreateInstance for example, the object is fetched from the pool. When the client releases the object, the object goes back to the pool. Pooling objects improves performance in some cases.
Object activation implies either fetching an object from the pool (if object pooling is enabled on the component) or creating a new object. Likewise, object deactivation implies either returning the object to the pool (if object pooling is enabled) or destroying the object.
Let s get back to our context (pun intended).
Earlier, I said that an object s context is created and attached to an object when COM+ activates an object. Well, I lied about the creation of an object context every time an object is activated. Actually, COM+ checks if a compatible context exists in the process space. If one is found, this context is attached to the object. Only if a compatible context is not found does COM+ create a new context and attach it to the object. Thus, as different components can be configured with different requirements, a process often contains more than one context. Some configuration settings allow an object to reside in a shared context with other like-minded objects these objects are geared to swim in the same sea. Other configuration settings force an object to swim in a private sea that no other object can swim in. This is illustrated in Figure 5.3.
This brings us to an important question what if the object s activator has a context that is incompatible with that of the object?
An object can live only within its context it is geared to swim only in a specific sea. Clearly, the object cannot just be created in a different sea. The incompatible environment will produce unexpected behavior.
The mechanism that COM+ uses to solve this problem is called interception. Instead of creating the actual object in the incompatible context, COM+ automatically creates a proxy that is geared to swim in the new sea. The proxy implements the same interfaces as the real object. This mechanism provides COM+ the ability to intercept method calls on the proxy.
Following is the basic interception algorithm:
The configuration settings on a component dictate its run-time requirements or its context.
At activation time, COM+ checks if the activator (that is, the code that called CoCreateInstance) has a context that is compatible with the component s configuration.
If the activator s context is compatible, the object is activated in the activator s context, that is, the activator gets a raw reference to the object.
If the activator s context is not compatible, COM+ switches to a context that is compatible (it may have to create a new context if one doesn t already exist), and activates the object in this context. COM+ then creates a proxy to this object in the activator s context and returns this proxy to the activator.
In case of a raw reference, no interception takes place. A method call invoked by the client goes directly to the method implementation logic.
In case of a proxy reference, each time the client invokes a method on the proxy, COM+ intercepts this method call, switches the run-time environment to the context that is compatible with the object, executes the call, switches the run-time environment back to the client s context, and returns the value.
Figure 5.4 illustrates this behavior. Here object C belongs to context Y. When it tries to activate object E whose component settings are compatible with context Z, COM+ creates object E in context Z and a proxy to object E in context Y. COM+ then returns a pointer to this proxy to object C.
This simple interception algorithm is the key to ensuring that the services the component requested are available to the object(s).
I am using the term proxy very loosely. Technically speaking, the incompatible context gets a proxy manager that loads an interface proxy for each actual interface being queried on the actual object. The proxy manager acts as the client-side identity of the object. The interface proxy translates method invocations into ORPC calls. To maintain the correct identity relationships, the interface proxies are aggregated into the proxy manager s identity.
Also note that object activation is not the only case when a proxy might get created. A proxy could also be created when an interface pointer is passed from one context to another via some interface method call. Once again, COM+ will take care of creating the proxy.
There is one exception to the above-mentioned interception algorithm. An object from a non-configured component is always activated in the activator s context, provided the threading model of the component matches the apartment type of the activator. Threading models and apartments are covered in the next chapter.
At this point, it should be reasonably clear that the activator gets either a raw reference to the activated object (in case of compatible contexts) or a proxy (in case of incompatible contexts). This now brings us to the next question can this obtained reference (either raw or proxy) be shared with other contexts?
Consider the case when the activator gets a raw reference. This implies that the object resides in the current context and depends on the current run-time environment for proper operation. The current sea is the only sea it can swim in. If this raw reference were shared with another context, for example, by using a global variable to store the reference, and if the other context were to use the raw reference, the object s method would execute without the benefit of the interception, that is, the context that is used during method execution is that of the caller, not that of the object. This will result in completely unpredictable behavior. For example, if the object relied on some transaction service or security service to be available during method execution, it may not get one. Worse yet, it may get the caller s settings (which may be completely different). Almost all configured services would malfunction if the call were processed in the wrong context.
Now let s consider the case when the activator gets the proxy. At first glance, it would appear that sharing a proxy across contexts is okay. After all, it is just a proxy. The actual object still resides in the right sea. COM+ would still be able to intercept the calls on the proxy.
Not so fast. The proxy that is returned by any API or method call is configured to run a certain set of interception services based on the differences between the object s context and the context where the proxy was first initialized. Simply put, even the proxy is geared to swim in a specific sea. Passing this proxy to another context is not guaranteed to work, as this third context may need a different set of interception services.
One could argue that a proxy should be smart enough to work from any context. However, this would make the proxy implementation very inefficient. There is yet another problem. Developers rarely wish to distinguish between the raw reference and the proxy. Had we had such a smart proxy, developers would then need to treat it differently than raw references.
So, how do you pass object references (either raw or proxy) from one context to another?
COM allows interface pointers to be marshaled across context boundaries. Marshaling an interface pointer transforms the interface pointer into a block of memory suitable for transmitting to any context on the network. The importing context can unmarshal this block of data, that is, decode it to obtain an object reference that is suitable to swim in the new sea.
The SDK provides two APIs, CoMarshalInterface and CoUnmarshalInterface, to marshal an interface pointer from one context and unmarshal it in another context, respectively. In general, application programmers rarely deal with marshaling APIs themselves, as the most common cases of marshaling interface pointers are handled by COM implicitly. These cases are:
when an object is activated and the activator s context is incompatible with the component configuration, and
when a reference is passed as an interface method parameter from one context to another.
In both cases, COM does the needed marshaling and unmarshaling automatically.
The only time explicit marshaling is needed is if a reference is passed from one context to another using a mechanism other than the interface method call, such as by means of a global variable. In such cases, the developers have to explicitly marshal and unmarshal the interface pointer. In the next chapter, we will examine one such case and show the usage of the marshaling APIs mentioned earlier. We will also look at another facility provided by COM+ called the Global Interface Table (GIT) to simplify marshaling logic.
Recall from the previous section that when a method call is made on a proxy, COM+ intercepts the call, switches the run-time environment to the context that is compatible with the real object, executes the call, and switches the runtime environment back to the caller s context on return. If need be, the call stack is serialized, that is, the method parameters are marshaled, and an OS thread switch is performed.
In terms of performance, OS thread-switching is the most expensive operation, followed by call-stack serialization. The rest of the interception services are not as expensive.
Much thought went into designing the proxies in order to make the cross-context method calls as efficient as possible. In general, the smaller the delta between the caller s context and the real object s context, the lower the performance cost. A proxy runs only those interception services a boundary crossing requires. If OS thread-switching or call-stack serialization is not needed, it will be avoided, and the stack frame can simply be shared across the context boundary. Of course, if the method contains an object reference as a parameter, it would have to be marshaled.
|< BACK  NEXT >|