Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
.gif)
| Implementing Handler Marshaling Under Windows 2000: DeviceClient Sample App | |||||||||
| Jeff Prosise | |||||||||
| Code for this article: Wicked0800.exe (127KB) | |||||||||
ver since COM made its debut in 1993, programmers have searched for ways to optimize the performance of COM method calls. Their never-ending quest for speed has produced a number of clever solutions, some as extreme as using custom marshaling to replace COM's built-in remoting infrastructure with remoting mechanisms of their own. But no technique is as effective at speeding the execution of cross-process and cross-machine method calls as preventing those method calls from leaving the caller's process in the first place. Knowledge of this fact led to the invention of the smart proxy, which replaces the standard object proxy in a client process with a custom proxy that handles selected calls in-process while allowing other calls to emanate as usual to remote server processes. The IStdMarshalInfo InterfaceThe first step in adding handler marshaling support to an object is to implement a special interface named IStdMarshalInfo on the object. IStdMarshalInfo is a simple interface that contains just one method. That method, GetClassForHandler, is called by COM to retrieve your handler's CLSID in preparation for creating a handler in the client process. If the handler's CLSID is CLSID_Handler, then the following GetClassForHandler implementation injects that handler into each client process that marshals an interface pointer from instances of CComClass:
When GetClassForHandler is called, the dwDestContext parameter contains an MSHCTX flag revealing whether the handler will be created on another machine (MSHCTX_DIFFERENTMACHINE), in another process (MSHCTX_LOCAL), or in another apartment in the same process (MSHCTX_INPROC). Most GetClassForHandler implementations won't care one way or the other, but if you want, GetClassForHandler can inspect dwDestContext and return a failure code to prevent a handler from being created. COM responds to failed GetClassForHandler calls by using standard marshaling to propagate method calls to the object. Implementing a Handler The second step in implementing handler marshaling is to write the handler itself. Before you code a handler, it helps to understand how COM will use it. Figure 2 illustrates the architecture of handler marshaling in Windows 2000. In this example, the object that the handler represents has two interfaces: IDeviceStatus and IDeviceControl. COM's proxy manager implements these interfaces in the client process by loading and aggregating interface proxies from your proxy/stub DLL. The handler, in turn, aggregates the proxy manager and hides the proxy manager's IDeviceStatus interface behind an IDeviceStatus interface of its own. All other interfaces implemented by the proxy manager are exposed on the handler through blind delegation of QueryInterface calls.
The handler itself is aggregated by a COM-provided identity object. The identity object exposes all the interfaces exposed by the handler and the proxy manager and throws in a controlling unknown as well as its own implementation of IMultiQI. The presence of the identity object enables COM to control the handler's lifetime and also solve a nasty race condition that occurs if two threads unmarshal interfaces implemented by the same handler at the same time. The identity object and the proxy manager are tightly coupled via private interfaces that are exposed by the proxy manager and known to the identity object. The handler neither knows nor cares about these interfaces.
The first requirement, aggregation support, is easy. ATL COM classes are aggregatable by default. A nonaggregatable ATL COM class can be made aggregatable by removing the DECLARE_NOT_ AGGREGATABLE statement from the class declaration.
Implementing in the handler some or all of the interfaces that the object implements is straightforward because implementing an interface in an ATL handler is no different than implementing an interface in any ATL COM class. One twist, however, is that when one of its methods is called, a handler can elect to process the method call itself or forward the call to the object that the handler is connected to. The latter is accomplished by using the IUnknown pointer returned by CoGetStdMarshalEx to query the proxy manager for a pointer to the corresponding interface on the object proxy and calling the method through that pointer. Here's how a handler's IDeviceStatus::GetCurrentPosition method would call the object's IDeviceStatus::GetCurrentPosition method via the controlling unknown stored in m_spUnkInner:
Delegating other QueryInterface calls can be accomplished with ATL's COM_INTERFACE_ENTRY_AGGREGATE_BLIND macro. The following ATL interface map handles QueryInterface calls for IDeviceStatus interface pointers locally, but delegates QueryInterface calls for all other interface types to the inner unknown stored in m_spUnkInner:
If m_spUnkInner holds the proxy manager's IUnknown interface pointer, then queries for interfaces other than IDeviceStatus will go to the proxy manager. This serves the dual purpose of providing the identity object with access to the proxy manager's non-public interfaces and providing the client with access to interfaces that aren't explicitly implemented by the handler. Registering a Handler The final step in preparing for handler marshaling is to register the handler. Handlers are registered just like normal in-proc COM objects, with one important exception: handlers require InprocHandler32 keys rather than InprocServer32 keys. This crucial distinction appears to be undocumented. I once lost half a day's work trying to figure out why COM wouldn't instantiate my handler until House of COM columnist Don Box pointed out that I needed to change the key name. The DeviceClient Application To test CDeviceHandler, you need a client. That client is in a separate Visual C++-created project named DeviceClient that you can download along with the other code for this column from MSDN® Magazine's Web site.
DeviceClient, shown in Figure 4, is a dialog-based MFC application that uses CoCreateInstance to instantiate the device object in Figure 1. Clicking the Get Last Position, Get Current Position, and Set Position buttons calls the object's IDeviceStatus::GetLastPosition, IDeviceStatus::GetCurrentPosition, and IDeviceControl::SetPosition methods, respectively. As you can see in DeviceClientDlg.cpp (shown in Figure 5), the client does absolutely nothing different to account for the fact that a handler is being used. It simply creates the object and places calls to it in the normal way. Behind the scenes, the handler intercepts calls to the object's IDeviceStatus methods and handles GetLastPosition calls locally. You can prove it by clicking the Get Last Position and Get Current Position buttons and comparing the ensuing process IDs to the client's process ID, which is shown in the box at the top of the dialog. The process ID returned by GetLastPosition should match the client's process ID, proving that the call was handled in-process. The process ID returned by GetCurrentPosition, however, should be that of the server process, demonstrating that COM's standard marshaling mechanism was invoked when the handler passed the call to the proxy manager. Drop Me a Line Are there tough Win32®, MFC, Microsoft Transaction Services, or COM(+) programming questions you'd like answered? If so, drop me a note at jeffpro@msn.com. Please include "Wicked Code" in the message title. I regret that time doesn't permit me to respond to individual questions, but rest assured that each and every one will be considered for inclusion in a future column. |
|||||||||
| Jeff Prosise is the author of Programming Windows with MFC (Microsoft Press, 1999). He is also a cofounder of Wintellect, a software consulting and education firm whose Web address is https://www.wintellect.com. |
|||||||||
From the August 2000 issue of MSDN Magazine.
.gif)
.gif)