Page #72 (Call Context)

< BACK  NEXT >
[oR]

Object Activation

Now that we understand how contexts and interception work, let s look at the mechanism of object activation.

Object activation is handled by a COM+ provided service called the Service Control Manager (SCM). When a client requests that an object be activated using CoCreateInstance(Ex) or CoGetClasObject, for example, the SCM locates and loads the appropriate component, and hands the client an interface pointer to the raw object or to its proxy, as the case may be.

Object activation reduces to three basic scenarios:

  1. The activator and the component are both on the local machine, and the component belongs to a library application. In this case, the activated object will run in-process with the activator.

  2. The activator and the component are both on the same machine and the component belongs to a server application. In this case, the activated object will run out-of-process with the activator.

  3. The component is on a remote machine. In this case, the component can only be specified to belong to a server application.

Let s examine each of these cases in detail.

In-Process Activation

A client creates an instance by calling the API CoCreateInstance. This API was covered in Chapter 3. The following prototype is reprinted for your convenience:

 STDAPI CoCreateInstance(REFCLSID rclsid, LPUNKNOWN pUnkOuter,    DWORD dwClsContext, REFIID riid, LPVOID* ppv); 

If the run-time requirements of the object being activated are the same as that of the activator, the activator gets a raw reference to the actual object, as shown in Figure 5.5.

Figure 5.5. In-process activation (compatible context).
graphics/05fig05.gif

If the run-time requirements of the object being activated do not match that of the activator, the SCM creates a proxy-stub pair for the object and sets up one or more interception policies on the client side as well as on the server side. The proxy and the stub communicate with each other using the ORPC channel (see Chapter 2). The overall communication mechanism is illustrated in Figure 5.6.

Figure 5.6. Cross-context communication.
graphics/05fig06.gif

The interception policies are set up based on the delta between the contexts of the client (activator) and the server object. The policies on the client side are invoked when a method is called, and again after it returns. The policies on the server side are invoked when the call first arrives at the server side, and again before it leaves. These policies provide whatever interception services were requested by the server object. For example, if the server object has requested for synchronization service, the server-side policy might acquire a lock on entry and release the lock when it exits.

For intraprocess communication, the ORPC channel uses the most efficient communication transport.

Out-of-Process Activation (Local Host)

Here, the requested component belongs to a server application. The client still uses CoCreateInstance (or its friends such as CoGetClassObject) to activate the object. In this case, however, the SCM creates a surrogate process (called DllHost.exe) and loads the component in the surrogate process. The rest of the mechanism is similar to that of cross-context communication explained earlier and represented in Figure 5.6. The only difference is that the transport used by the ORPC channel is LRPC (Lightweight RPC). LRPC is a Microsoft proprietary variant of RPC which has the exact same calling syntax as the DCE RPC run time but is optimized to reduce data copying and eliminate access to the networking code altogether.

Remote Activation (Distributed Computing)

In order to activate a component remotely, the name of the remote machine has to be specified during instance creation. As CoCreateInstance API does not have any provision for this, the SDK defines an extended variant of this API called CoCreateInstanceEx. The following is its prototype:

 HRESULT CoCreateInstanceEx(   REFCLSID rclsid,                 //CLSID of the object to be created    IUnknown *punkOuter,             //If part of an aggregate,                                     //the controlling IUnknown    DWORD dwClsCtx,                  //CLSCTX values    COSERVERINFO *pServerInfo,       //Machine on which the object                                     //should be instantiated    ULONG cmq,                       //Number of MULTI_QI structures                                     //in pResults    MULTI_QI *pResults               //Array of MULTI_QI structures  ); 

The COSERVERINFO structure is primarily used to identify a remote machine. Machines are identified using the naming scheme of the network transport. By default, all UNC ( \\MYDEV or MYDEV ) and DNS names ( mydev.pvhome.local or 15.65.87.252 ) are allowed.

A secondary use of the COSERVERINFO structure is to specify a different security protocol or a different client identity during object activation. This type of use is covered in Chapter 7.

As round trips can be very expensive, CoCreateInstanceEx makes it possible to obtain a number of interface pointers (using MULTI_QI structure) in just one API call.

The following function code shows how to instantiate a remote object and obtain an IUnknown interface pointer to it. This code is available on the CD.

 HRESULT    CPLCreateInstance(   LPCOLESTR pwszMach,      // [in] Remote machine    const CLSID& clsId,      // [in] Class ID    IUnknown** ppOut,        // [out, retval] instance handle    DWORD dwClsCtx           // [in] CLSCTX values    )  {   *ppOut = NULL;    COSERVERINFO serverInfo;    serverInfo.dwReserved1 = 0;    serverInfo.pwszName = const_cast<LPOLESTR>(pwszMach);    serverInfo.pAuthInfo = NULL;    serverInfo.dwReserved2 = 0;    MULTI_QI mqiEntry;    mqiEntry.pIID = &IID_IUnknown;    mqiEntry.pItf = NULL;    mqiEntry.hr = 0;    HRESULT hr = ::CoCreateInstanceEx(clsId,      NULL,      dwClsCtx,      &serverInfo,      1,      &mqiEntry);    if (FAILED(hr)) {     return hr;    }    _ASSERT (NULL != mqiEntry.pItf);    *ppOut = mqiEntry.pItf;    return hr;  } 

The mechanism of setting up proxy-stub and interception policies is similar to that of out-of-process activation (Figure 5.6). The only difference is that, to communicate with a remote host, the ORPC channel prefers using Transmission Control Protocol (TCP) on Windows 2000 and User Datagram Protocol (UDP) on Windows NT 4.0.

Executing in a Different Context

Using Activator s Context

We know that if an object being activated has a configuration that does not match the activator s context, the object is activated in a different context. However, there are occasions when a component does not require any configured service of its own but would rather use the configured services of its activator. This could be useful for some utility components. It is also useful for components that do not support cross-context marshaling, or have very stringent performance requirements.

COM+ supports a configuration option that a developer can select to ensure that the object can only be activated within the context of its activator. If for some reason the creator s context has been configured in such a way that it can t support the new object, CoCreateInstance will fail and return the error CO_E_ATTEMPT_TO_CREATE_OUTSIDE_CLIENT_CONTEXT.

If CoCreateInstance succeeds, all calls on the new object will be serviced in the activator s context. This holds true even if the references to the new object are passed to other contexts.

graphics/01icon01.gif

Activating an object of a configured class using the activator s context looks very similar to activating an object of the non-configured component. However, there is a subtle difference in the activation mechanisms. In the former case, the object is guaranteed to use the activator s context. If this cannot be accomplished because the activator s context is not compatible with the class configuration, COM+ returns error code CO_E_ATTEMP_TO_CREATE_OUTSIDE_CLIENT_CONTEXT. In the latter case, if the object cannot run in the activator s context, COM+ will activate it in an appropriate default context and will return a proxy to the activator.


What if an object wants to run in the context of any of its callers?

Using a Caller s Context

A component implementor may decide that the component does not require any COM+ services. Instead, the implemntor goes through great lengths to ensure that the component s object is capable of residing in any context. However, it is impossible for the clients to know that such access is safe for a particular object; so all cross-context interface pointer sharing must be established using an explicit marshaling technique. This means that access to an in-process object could still be occurring via (somewhat expensive) ORPC calls.

Unlike clients, objects do know if they are safe to be used as raw references in any context. Such objects have the opportunity to bypass the standard marshaling technique provided by COM and implement their own custom marshaling.

A little background on custom marshaling is in order.

By default, when CoMarshalInterface is first called on an object (this happens automatically when an interface is first requested from a different context), COM+ asks the object if it wishes to handle its own cross-context communication. This question comes in the form of a QueryInterface request for the IMarshal interface. Most objects do not implement the IMarshal interface and fail this QueryInterface request. This indicates to COM+ that the objects are perfectly happy letting COM+ handle all communications via ORPC calls. Objects that do implement this interface are indicating that the object implementor would prefer handling all cross-context communications via a custom proxy.

graphics/01icon01.gif

Components that implement custom marshaling cannot be installed as a configured component.


For an object that wishes to run in the context of its caller, the object implementor could use custom marshaling to easily bypass the stub manager and simply serialize a raw pointer to the object into the marshaled object reference. The custom proxy implementation could simply read the raw pointer from the marshaled object reference and pass it to the caller in the importing context. The client would still pass the interface pointer across the context boundary by calling CoMarshalInterface (either explicitly or automatically by COM). However, the custom proxy will end up returning the raw reference into the new context. Although this technique will work perfectly for intraprocess marshaling, it will fail miserably for interprocess marshaling (recall from Chapter 2 that a raw address pointer from one process space cannot simply be used in another process space). Fortunately, when the marshaling request comes to the object (via IMarshal::MarshalInterface), the marshaling context is passed as a parameter (see MSHCTX flags in the SDK documentation). The object implementation can simply delegate to the standard marshaler for any marshaling context other than MSHCTX_INPROC.

As the behavior just described is useful for a large class of objects, COM provides an aggregatable implementation of IMarshal that accomplishes what was just described. This implementation is called the Free Threaded Marshaler (FTM) and can be created using the CoCreateFreeThreadedMarshaler API call.

 HRESULT CoCreateFreeThreadedMarshaler(IN LPUNKNOWN punkOuter,    OUT LPUNKNOWN *ppunkMarshal); 

A class that wishes to use the FTM typically aggregates an instance during the instantiation time. The following code snippet shows how it can be done within an ATL class:

 // CMyTestD  class ATL_NO_VTABLE CMyTestD :    ...  { public:    CMyTestD()    {     m_pUnkMarshaler = NULL;    }    ...    HRESULT FinalConstruct()    {     return CoCreateFreeThreadedMarshaler( GetControllingUnknown(), &m_pUnkMarshaler.p);    }    void FinalRelease()    {     m_pUnkMarshaler.Release();    }    CComPtr<IUnknown> m_pUnkMarshaler;    ...  }; 

Recall from Chapter 3 that, to support aggregation, the QueryInterface method has to be modified, as shown in the following code snippet:

 STDMETHODIMP CMyTestD::QueryInterface(REFIID riid, void** ppv)  {   // Check for usual IIDs first    if (riid == IID_IUnknown || riid == ... || riid == ...) {     do the necessary casting and return S_OK    }    // now check for IID_IMarshal    if (riid == IID_IMarshal) {     return m_pUnkMarshaler->QueryInterface(riid, ppv)    }    return S_FALSE; // requested interface not found  } 

Fortunately, the ATL wizard makes it very convenient to generate all the needed logic. When the wizard is run, one of the options on the Attributes tab is Free Threaded Marshaler. If this option is selected, the wizard will aggregate the FTM into your ATL class.

Once a component is implemented with FTM support, the object will always run in its caller s context, irrespective of which context the object was activated in. Figure 5.7 illustrates this context-neutrality of such an object.

Figure 5.7. Context neutrality.
graphics/05fig07.gif

In Figure 5.7, object E is shared between context Y and Z. Compare this to Figure 5.4 to see the difference between a context-sensitive object and a context-neutral object.

graphics/01icon01.gif

You must think very carefully before using FTM. If absolute care is not taken, it will result in unpredictable results. For example, if the object being FTMed contains an interface pointer to another object that is configured to run in a specific context, a call on this interface pointer may get executed in the wrong context.



< BACK  NEXT >


COM+ Programming. A Practical Guide Using Visual C++ and ATL
COM+ Programming. A Practical Guide Using Visual C++ and ATL
ISBN: 130886742
EAN: N/A
Year: 2000
Pages: 129

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