January 1998
Download HostHook.exe (16KB)
Don Box is a co-founder of DevelopMentor where he manages the COM curriculum. Don is currently breathing deep sighs of relief as his new book, Essential COM (Addison-Wesley), is finally complete. Don can be reached at http://www.develop.com/dbox/default.asp. |
Q
Is there any way to find out the host address of the caller inside a method?
Barbara Box
Redondo Beach, CA A The short answer is no. I am required by law to remind you that if your object implementation were to make assumptions about which host machine happened to be invoking the current method, you'd be tempted to violate the fundamental principle of location transparency, causing the distributed object police to revoke your license to instantiate across host boundaries.That said, here are a few reasons why you may want to know which host machine issued a method call:
One highly inelegant way to discover the host name of the caller is to require the caller to pass its host name as an explicit method parameter. This technique actually works. However, it requires excessive grunge work on the part of the caller, the caller could easily spoof your object and pass an incorrect host name, and it doesn't work for arbitrary interfaces that you don't define. Given these three irritating limitations, I will assume that a more automated approach for sending the network address is in order. Before examining the technique proposed in this article, a brief detour through the ORPC protocol is required. When a proxy needs to remote a method call to another apartment (whether a local or remote apartment), the proxy sends an ORPC request message to the stub and waits for an ORPC response message to signal that the method has completed execution. The ORPC protocol is designed to be layered over either DCE RPC or a local RPC mechanism. In either case, the ORPC protocol introduces a header in both the RPC request message and the RPC response message. |
Figure 1 ORPC Messages |
As shown in Figure 1, each RPC request message begins with the RPC-specific headers and is followed by a data structure called an ORPCTHIS. The ORPCTHIS appears immediately before the marshaled [in] parameters that are part of the payload of the message. Similarly, the ORPCTHAT data structure appears after the RPC headers in the RPC response message, but immediately before any marshaled [out] parameters and HRESULTs. The ORPCTHIS and ORPCTHAT act as COM-specific subheaders for the request and response messages. Both of these structures are documented in the DCOM wire protocol Internet draft standard, which you can find at http://msdn.microsoft.com/library/specs/distributedcomponentobjectmodelprotocoldcom10.htm. |
Figure 2 ORPCTHIS and ORPCTHAT Structures |
As shown in Figure 2, the ORPCTHIS header begins with a version number (5.2) to make sure the incoming message is using a compatible version of DCOM. It is followed by a flags field that is largely uninteresting. Following a reserved field, the ORPCTHIS contains something called the Causality ID (CID) of the caller. The CID is used to create a logical thread ID across the network. Each time a client issues a remote method call in response to some non-COM-related activity, COM allocates a new CID that is propagated in each nested call issued on behalf of the client. The CID is used to detect nested callbacks for use in IMessageFilter::HandleIncomingCall and is generally helpful for low-level COM plumbing.
|
|
As shown in Figure 2, the ORPCTHAT header only contains an array of ORPC_EXTENTs, allowing response messages to contain extension information as well. While the Internet draft describes the syntax for protocol extensions, it does not describe how to write your own custom protocol extensions. However, the SDK header files reveal a little-known interface, IChannelHook, that offers some clues. Having come this far, I must clearly state that while IChannelHook is found in the SDK header files, it is not documented, not supported, and indeed may not be available in the future of COM+ and Windows NT 5.0.
Each process that uses COM maintains a list of registered protocol extensions. For each registered protocol extension, the COM library holds a pointer to an IChannelHook interface that is used to read and write the ORPC_ EXTENT for each ORPC message sent to or received from the process. To allow registration of the new protocol extensions, the COM library exposes an API that binds an Extension ID to the channel hook object that will read and write the ORPC_EXTENTs for that extension: |
|
Once a channel hook as been registered with COM, it will receive notifications each time an ORPC message is sent to or received from the process. Because most channel hooks need to manipulate the thread-local storage of the caller/callee, the channel hook methods may be invoked from any apartment in the process.
The IChannelHook interface is fairly straightforward (see Figure 3). When a client issues a remote method call, heres what happens. First, the proxy asks the COM channel to allocate a transmission buffer via the channels IRpcChannelBuffer::GetBuffer method. Inside the channels GetBuffer method, each registered channel hook is asked (via IChannelHook::ClientGetSize) if it needs to send extension information for this call. If the hook returns a nonzero size, space is allocated for the extension and the hook is given an opportunity to write its extension data when the channel calls IChannelHook::ClientFillBuffer. The channel arranges to write the extension ID and size prefix for the extension. After marshaling the [in] parameters into the transmission buffer returned from GetBuffer, the proxy sends the message to the stub via a call to IRpcChannelBuffer::SendReceive. When the message is received in the objects apartment, the server-side channel notifies each registered channel hook that an incoming call has arrived via a call to IChannelHook::ServerNotify. This call happens whether or not the message contained any hook-specific information. If the message did contain an ORPC_EXTENT for the hook object, one of the ServerNotify parameters will be a pointer to the data written by the client-side hook. After the stub dispatches the call to the actual object, the stub allocates a transmission buffer for the ORPC response by calling IRpcChannelBuffer::GetBuffer on the server-side channel. The server-side channels GetBuffer implementation walks the list of registered channel hooks asking (via IChannelHook::ServerGetSize) if any extensions need to be added to the header of the response. When a hook returns a nonzero size, the channel allocates space for the extension and asks the hook object to write its data by calling IChannelHook::ServerFillBuffer. The channel handles writing the extension ID and size for the ORPC_EXTENT. When the stub returns from dispatching the call, the ORPC response (including any ORPC_EXTENTs written by the channel hooks) is sent back to the clients apartment. When the client-side channel receives the ORPC response, it walks the list of registered channel hooks and notifies each one that a response message has arrived via a call to IChannelHook::ClientNotify. If the server-side hook wrote an ORPC_EXTENT in the message, one of the parameters to ClientNotify will contain a pointer to the data written by the server-side hook. In general, most processes have a small number of registered hooks, and most hooks do not write extension information for every ORPC call. So whats the channel hook mechanism good for? Plenty. In general, hooks are useful for sending additional out-of-band data that are not traditional method parameters in ORPC requests and responses. The COM exception mechanism (SetErrorInfo/GetErrorInfo) uses channel hooks to let one thread throw an exception and another thread catch the exception. To support cross-apartment exceptions, COM preregisters a channel hook that calls GetErrorInfo in its ServerFillBuffer to retrieve the current IErrorInfo interface for the thread, and then calls CoMarshalInterface to transmit the marshaled IErrorInfo pointer back to the clients apartment. The client-side of this hook then calls CoUnmarshalInterface and SetErrorInfo to set the current exception in its ClientNotify implementation. Because the default exception object returned from CreateErrorInfo implements IMarshal to marshal by value, most programmers are oblivious that anything out of the ordinary has happened. This brings me back to the original question of discovering the host name of the caller. If I wrote a custom channel hook and arranged to register it in every process in my application, I could magically add additional data to each ORPC request message that is sent on behalf of my objects and clients. Assuming my custom hook writes the host address of the caller in its IChannelHook::ClientFillBuffer implementation, all I need is some way for IChannelHook::ServerNotify to communicate the received host address to the object. The most direct way to do this is via thread-local storage. If my server-side hook writes the host address to a well-known TLS location in its ServerNotify routine, the object could simply read the host address from the TLS slot. Of course, this approach is somewhat crude and virtually inaccessible from Visual Basic®. A more elegant approach would be to expose a COM class that simplifies reading this information from any language without concern for the details of how the TLS memory is managed. If this COM class was found in the same DLL that contained the custom channel hook, I could arrange to register the hook behind the back of the caller in DllMain. This means that a simple call to CoCreateInstance will trigger the registration of a custom channel hook that will send host information in every ORPC call. Figure 4 shows the implementation of a simple channel hook. It uses a basic data type that describes a call site: |
|
The following is the wire representation of the hooks ORPC_EXTENT data for ORPCTHIS headers: |
|
The custom channel hook sends the NODE_INFO of the immediate caller, as well as information about the original top-level client that began the current causality. The custom channel hook reimplements the ORPCTHIS CID because CIDs are really interesting and it was easy to add one once the custom channel hook was in place. Additionally, the custom channel hook transmits node information about where the call was executed in its OPRCTHAT extension: |
|
By sending the host information back in the ORPCTHAT, the client can then determine on which node a given call has executed.
To provide users an easy mechanism for accessing the information exchanged by the custom channel hook, a simple COM class exposes the interface shown in Figure 5. This interface allows objects to determine where the current call was issued from as well as where the call chain originated. Given this interface (and the corresponding coclass), programmers using Visual Basic can determine their callers as follows: |
|
A similar interface is provided for clients to find out where a call actually was dispatched to: |
|
Given this interface, a Java client can issue a COM method call and find out where it was dispatched: |
|
Figure 6 shows the implementation of this COM class.
So thats a basic channel hook that sends information about the call site in ORPC protocol extensions. Other interesting applications for channel hooks include:
|
Have a question about programming with ActiveX or COM? Send your questions via email to Don Box at dbox@develop.com or http://www.develop.com/dbox/default.asp |
From the January 1998 issue of Microsoft Systems Journal.