Click Here to Install Silverlight*
United StatesChange|All Microsoft Sites
MSDN
|Developer Centers|Library|Downloads|Code Center|Subscriptions|MSDN Worldwide
Search for


Advanced Search
MSDN Home > MSJ > January 1998
January 1998


Code for this article: 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:

  1. You want to co-locate a new object at the client's host machine.
  2. You are writing instrumentation code and want to note which host machines are active.
  3. You want to prioritize or prohibit access based on machine name instead of user name.
  4. You don't believe in location transparency (after all, the inventors of getpeername didn't).
If one of these four reasons resonates with you, read on. If not, read on anyway, since this column uncovers one of the most interesting aspects of the DCOM Object RPC (ORPC) protocol and demonstrates an extremely elegant extensibility feature of the COM remoting architecture.
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
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://www.microsoft.com/oledev/olecom/draft-brown-dcom-v1-spec-01.txt.

Figure 2 ORPCTHIS and ORPCTHAT Structures
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.
The ORPCTHIS ends with an array of ORPC_EXTENTs. ORPC_EXTENTs are protocol extensions that allow third parties to extend the DCOM wire protocol beyond what was originally envisioned by the protocol designers. (Protocol extensions are a common facility in network protocols. Both TCP and IP have room for protocol extensions in their headers.) An ORPC_EXTENT is simply a <tag-length-value> tuple that is sent as part of an ORPC message:


 typedef struct tagORPC_EXTENT {
    GUID  id;     // Extension identifier
    DWORD size;   // Extension size
    byte  data[]; // [size_is((size+7)&~7)]
 } ORPC_EXTENT;
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:

 HRESULT CoRegisterChannelHook(REFGUID rextentid,
                               IChannelHook *pHook);
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, here’s what happens. First, the proxy asks the COM channel to allocate a transmission buffer via the channel’s IRpcChannelBuffer::GetBuffer method. Inside the channel’s 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 object’s 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 channel’s 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 client’s 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 what’s 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 client’s 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:
 
 struct NODE_INFO {
     DWORD pid; // process ID
     DWORD tid; // thread ID
     DWORD ip;  // IP address (network byte order)
 };
The following is the wire representation of the hook’s ORPC_EXTENT data for ORPCTHIS headers:

 struct HOOK_THIS {
   GUID  cid;           // simulated causality ID
   NODE_INFO niDirect;  // node of immediate caller
   NODE_INFO niIndirect;// node of original caller
 };
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:

 struct HOOK_THAT {
   NODE_INFO niTarget; // node where call executed
 };
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:

 Public Sub BobClass_Method(ByVal n as Long)
   Dim ci as ICallInfo
   Set ci = new CallInfo
   MsgBox "You called from " & ci.DirectHostName
   MsgBox "This causality began at " &_
                        ci.OriginalHostName
 End Sub
A similar interface is provided for clients to find out where a call actually was dispatched to:

 interface IClientCallInfo : IUnknown {
   [propget] HRESULT TargetProcessID(
                         [out, retval] long *pVal);
   [propget] HRESULT TargetThreadID(
                         [out, retval] long *pVal);
   [propget] HRESULT TargetHostID(
                         [out, retval] long *pVal);
   [propget] HRESULT TargetHostName(
                         [out, retval] BSTR *pVal);
 }
Given this interface, a Java client can issue a COM method call and find out where it was dispatched:

 public void CallBob(IBob bob) {
   // get a call info object
   Object ci = new CallInfo();
   IClientCallInfo cci = (IClientCallInfo)ci;
   // issue the call
   bob.Method();
   // get the info
   alert("Bob is at " + cci.getTargetHostName());
 }
Figure 6 shows the implementation of this COM class.
So that’s a basic channel hook that sends information about the call site in ORPC protocol extensions. Other interesting applications for channel hooks include:
  • Instrumentation hooks that record method execution time as well as network transmission time
  • Distributed tracing of method calls
  • Distributed transmission of control and context information
  • Propagation of environment variables
  • Low-priority data transfer (piggybacking background data on each call)
  • Anytime extra parameters are needed on an existing interface
Channel hooks are low-level COM grunge that exist at the fringe of the COM remoting architecture. If I left you in the dust, don’t feel bad. Instead, download a working channel hook from http://www.develop.com/dbox/com/hooks/hosthook. Walk through the critical code path in a debugger and all will be revealed.

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. Get it at your local newsstand, or better yet, subscribe .

© 1997 Microsoft Corporation. All rights reserved.
Terms of Use
.

© 2014 Microsoft Corporation. All rights reserved. Contact Us |Terms of Use |Trademarks |Privacy & Cookies
Microsoft