The ORPC protocol transmits method parameters in the NDR format specified by OSF DCE RPC. NDR specifies exactly how all the primitive data types understood by IDL should be marshaled into data packets for network transmission. The only extension made to the NDR standard by ORPC is support for marshaled interface pointers. Use of the iid_is IDL keyword9 in an interface definition constitutes what can be considered a new primitive data type that can be marshaled: an interface pointer.
The term interface pointer is problematic because it conjures up a mental picture of a pointer to a pointer to a v-table structure that contains pointers to functions. But once it is marshaled into a data packet, an interface pointer does not look like that at all. It is a symbolic representation of access to an object and is therefore called an object reference. The format of a marshaled interface pointer is governed by the MInterfacePointer structure defined below.
// Wire representation of a marshaled interface pointer typedef struct tagMInterfacePointer { ULONG ulCntData; // Size of data [size_is(ulCntData)] BYTE abData[]; // Data (OBJREF) } MInterfacePointer; |
After the ulCntData field, which defines the size of the structure, comes the abData byte array, which contains the actual object reference in a structure called an OBJREF. An OBJREF is the data type used to represent a reference to an object. The definition of the OBJREF structure is shown in the following code. Notice that OBJREF takes one of three forms, depending on the type of marshaling being employed: standard, handler, or custom.10
// OBJREF is the format of a marshaled interface pointer. typedef struct tagOBJREF { unsigned long signature; // Must be OBJREF_SIGNATURE unsigned long flags; // OBJREF flags GUID iid; // IID [switch_is(flags), switch_type(unsigned long)] union { [case(OBJREF_STANDARD)] struct { STDOBJREF std; // Standard OBJREF DUALSTRINGARRAY saResAddr; // Resolver address } u_standard; [case(OBJREF_HANDLER)] struct { STDOBJREF std; // Standard OBJREF CLSID clsid; // CLSID of handler code DUALSTRINGARRAY saResAddr; // Resolver address } u_handler; [case(OBJREF_CUSTOM)] struct { CLSID clsid; // CLSID of unmarshaling code unsigned long cbExtension; // Size of extension data unsigned long size; // Size of data that follows [size_is(size), ref] byte *pData; // Extension plus class- // specific data } u_custom; } u_objref; } OBJREF; |
The OBJREF structure begins with a signature field that is defined as the unsigned long hexadecimal value 0x574F454D. Interestingly, if you arrange this value in little endian format (4D 45 4F 57) and then convert each byte to its ASCII equivalent, the resulting characters spell MEOW. The great thing about the MEOW structure (a popular nickname for the OBJREF structure) is that when you scan through the mountains of packets captured by the Network Monitor utility it is easy to tell when you've hit upon an object reference: just say MEOW. Note that regardless of the format of the remainder of the NDR data, the wire representation of a marshaled interface pointer is always stored in little endian format.
Following the MEOW signature field is the flags field of the OBJREF structure, which identifies the type of object reference. You can set the flags field to OBJREF_STANDARD (1), OBJREF_HANDLER (2), or OBJREF_CUSTOM (4) to indicate the type of interface marshaling. The iid field is the last omnipresent field of the OBJREF structure; it specifies the IID of the interface being marshaled. Figure 19-8 shows a network packet that was captured as a result of the request PDU shown previously. There the CoCreateInstanceEx function had been called to request the object's IUnknown interface pointer. In this figure, you can see that the response PDU has returned to the client. It contains the marshaled interface pointer (decorated by MEOW and labeled 5) of the object's IUnknown interface.
Figure 19-8. The response PDU captured when IRemoteActivation::RemoteActivation returns an OBJREF.
As you can see in Figure 19-8, the flags field of the OBJREF structure indicates that standard marshaling (OBJREF_STANDARD) is being used. Based on this field, the remainder of the structure contains a structure of the type STDOBJREF followed by a DUALSTRINGARRAY structure. Here is the definition of the STDOBJREF structure:
typedef struct tagSTDOBJREF { unsigned long flags; // SORF_ flags unsigned long cPublicRefs; // Count of references passed OXID oxid; // OXID of server with this OID OID oid; // OID of object with this IPID IPID ipid; // IPID of interface } STDOBJREF; |
The first field of the STDOBJREF structure specifies flags relating to the object reference. Although most of the possible settings for the flags parameter are reserved for use by the system, you can use the SORF_NOPING flag (0x1000) to indicate that the object does not need to be pinged. The ORPC network protocol uses pinging to implement a sophisticated garbage collection mechanism.11 The second field of the STDOBJREF structure, cPublicRefs, specifies the number of reference counts on the IPID that are being transferred in this object reference. You can allocate multiple reference counts on an interface as an optimization to avoid making remote method calls every time the client calls IUnknown::AddRef.12
The third field of the STDOBJREF structure specifies the OXID of the server that owns the object. Although an IPID is used to identify an interface of an object hosted by a component, an IPID alone does not contain enough information to actually carry out a method invocation because the RPC infrastructure uses strings to specify the binding information needed to carry out a remote call. These strings, called RPC string bindings, contain information such as the underlying network protocol and security subsystem that should be used to carry out the call as well as the network address of the server machine on which the component is running. An unsigned hyper (64-bit integer), also called an OXID, is used to represent this connection information. Before making a call, the client translates an OXID into a set of string bindings that the RPC system understands.13
The fourth field of the STDOBJREF structure specifies the OID of the object that implements the interface being marshaled. OIDs are 64-bit values used as part of the pinging mechanism. The final parameter of the STDOBJREF structure is the actual IPID of the interface being marshaled.
As part of an object reference, the STDOBJREF structure is followed by the DUALSTRINGARRAY structure. The DUALSTRINGARRAY structure is a container for a large array that contains two parts, STRINGBINDING structures and SECURITYBINDING structures. The definition of the DUALSTRINGARRAY structure is shown here:
// DUALSTRINGARRAYs are the return type for arrays of network // addresses, arrays of endpoints, and arrays of both used in // many ORPC interfaces. typedef struct tagDUALSTRINGARRAY { unsigned short wNumEntries; // Number of entries // in array unsigned short wSecurityOffset; // Offset of security // info // The array contains two parts, a set of STRINGBINDINGs // and a set of SECURITYBINDINGs. Each set is terminated by // an extra 0. The shortest array contains four 0s. [size_is(wNumEntries)] unsigned short aStringArray[]; } DUALSTRINGARRAY; |
The first two fields of the DUALSTRINGARRAY structure simply specify the total number of entries in the array (wNumEntries) and the offset at which the STRINGBINDING structures end and the SECURITYBINDING structures begin (wSecurityOffset). The array itself is pointed to by the aStringArray field.
A STRINGBINDING structure represents the connection information needed to bind to an object. The layout of the STRINGBINDING structure is shown here:
// This is the return type for arrays of string bindings or // protocol sequences (protseqs) used by many ORPC interfaces. typedef struct tagSTRINGBINDING { unsigned short wTowerId; // Cannot be 0 unsigned short aNetworkAddr; // Zero-terminated } STRINGBINDING; |
The first field of the STRINGBINDING structure, wTowerId, specifies the network protocol that can be used to reach the server using the second parameter, aNetworkAddr. The aNetworkAddr parameter is a Unicode string specifying the network address of the server. For example, if the wTowerId value is set to the tower identifier NCADG_IP_UDP,14 a valid network address for aNetworkAddr would be 199.34.58.4. The following table lists the valid tower identifiers for common protocols that can be used with the wTowerId parameter.
Tower | Identifier Value | Description |
---|---|---|
NCADG_IP_UDP | 0x08 | Connectionless UDP |
NCACN_IP_TCP | 0x07 | Connection-oriented TCP |
NCADG_IPX | 0x0E | Connectionless Internetwork Packet Exchange (IPX) Protocol |
NCACN_SPX | 0x0C | Connection-oriented Sequenced Packet Exchange (SPX) Protocol |
NCACN_NB_NB | 0x12 | Connection-oriented NetBEUI over NetBIOS |
NCACN_NB_IPX | 0x0D | Connection-oriented NetBIOS over IPX |
NCACN_HTTP | 0x1F | Connection-oriented HTTP |
Each STRINGBINDING structure ends with a null character to indicate the end of the aNetworkAddr string. The last STRINGBINDING in a DUALSTRINGARRAY is indicated by the presence of two extra 0 bytes. After that come the SECURITYBINDING structures. The definition of the SECURITYBINDING structure is shown here:
// This value indicates that the default authorization // should be used. const unsigned short COM_C_AUTHZ_NONE = 0xffff; typedef struct tagSECURITYBINDING { unsigned short wAuthnSvc; // Must not be 0 unsigned short wAuthzSvc; // Must not be 0 unsigned short aPrincName; // NULL terminated } SECURITYBINDING; |
The SECURITYBINDING structure contains fields indicating the authentication service, wAuthnSvc, and the authorization service, wAuthzSvc, to be used. The wAuthzSvc field is typically set to 0xFFFF, which indicates that default authorization should be used.
IRemUnknown is a COM+ interface that handles reference counting and interface querying for remote objects. As its name suggests, IRemUnknown is the remote version of the holy IUnknown interface. Clients use the IRemUnknown interface to manipulate reference counts and request new interfaces based on IPIDs held by the client. Following standard reference counting rules in COM+, references are kept per interface rather than per object. The definition of the IRemUnknown interface is shown in the following IDL notation:
// The remote version of IUnknown is used by clients to // query for new interfaces, get additional references (for // marshaling), and release outstanding references. [ object, uuid(00000131-0000-0000-C000-000000000046) ] interface IRemUnknown : IUnknown { HRESULT RemQueryInterface ( [in] REFIPID ripid, // Interface to QueryInterface on [in] unsigned long cRefs, // Count of AddRefs requested [in] unsigned short cIids, // Count of IIDs that follow [in, size_is(cIids)] IID* iids, // IIDs to QueryInterface for [out, size_is(,cIids)] REMQIRESULT** ppQIResults // Results returned ); HRESULT RemAddRef ( [in] unsigned short cInterfaceRefs, [in, size_is(cInterfaceRefs)] REMINTERFACEREF InterfaceRefs[], [out, size_is(cInterfaceRefs)] HRESULT* pResults ); HRESULT RemRelease ( [in] unsigned short cInterfaceRefs, [in, size_is(cInterfaceRefs)] REMINTERFACEREF InterfaceRefs[] ); } |
A component developer never implements the IRemUnknown interface because the OXID object associated with each apartment already provides an implementation of this interface. The standard IUnknown interface is never remoted in COM+. The IRemUnknown interface is remoted in its place and results in local calls to QueryInterface, AddRef, and Release on the server. Client applications can call the IUnknown::AddRef method as often as they want. COM+ remotes calls to IUnknown::AddRef only when the first AddRef call is made; IUnknown::Release is called only for the final Release.
The IRemUnknown::RemQueryInterface method differs from the IUnknown::QueryInterface method in that it can request several interface pointers in one call. The standard IUnknown::QueryInterface method is actually used to carry out this request on the server side. This optimization is designed to reduce the number of round-trips executed. The array of REMQIRESULT structures returned by RemQueryInterface contains the HRESULT from the QueryInterface call executed for each requested interface, as well as the STDOBJREF structure containing the marshaled interface pointer itself. The definition of the REMQIRESULT structure is shown here:
typedef struct tagREMQIRESULT { HRESULT hResult; // Result of call STDOBJREF std; // Data for returned interface } REMQIRESULT; |
The IRemUnknown::RemAddRef and IRemUnknown::RemRelease methods increase and decrease, respectively, the reference count of the object referred to by an IPID. Like RemQueryInterface, RemAddRef and RemRelease differ from their local counterparts; they can increase and decrease the reference count of multiple interfaces by an arbitrary amount in a single remote call. Imagine a scenario in which an object that receives a marshaled interface pointer wants to pass that pointer to some other object. According to the COM+ reference counting rules, AddRef must be called before this interface pointer can be passed to another object, resulting in two round-trips, one to get the interface pointer and another to increment the reference counter. The caller can optimize this process by requesting multiple references in one call. Thereafter, the interface pointer can be given out multiple times without additional remote calls to increment the reference counter.
The Windows implementation of COM+ typically requests five references when marshaling an interface pointer, which means that the client process receiving the interface pointer can marshal it to four different apartments in the current process or in other processes. Only when the client attempts to marshal the interface pointer for the fifth time does COM+ make a remote call to the object to request an additional reference. Also, in the interest of performance on the client side, COM+ typically does not immediately translate each call to IUnknown::AddRef or IUnknown::Release into a remote call to IRemUnknown::RemAddRef or IRemUnknown::RemRelease. Instead, it defers a remote call to the RemRelease method until all interfaces on an object have been released locally. Only then is a single RemRelease call made, with instructions to decrement the reference counter for all interfaces by the necessary amount.
It is important to note that in this scenario, when one component returns the interface pointer of another component to a client process, COM+ never allows one proxy to communicate with another proxy. For example, if client process A calls object B, which then returns an interface pointer for object C, any subsequent calls made by client A to object C are direct. This happens because the marshaled interface pointer contains information about how to reach the machine on which the actual object instance exists. In order for object B to call object C, object B must keep track of object C's OXID, IP address, IPID, and so on. When object B hands client A a pointer to object C, object B scribbles all that information into a new object reference (OBJREF) for client A. Object B is no longer part of the relationship, which saves network bandwidth and improves overall performance.
After calling the CoCreateInstanceEx function to instantiate the remote component, our client process possesses an initial IUnknown interface pointer. Typically, this call is followed by a call to the IUnknown::QueryInterface method to request another interface, as shown in the following code fragment:
hr = pUnknown->QueryInterface(IID_ISum, (void**)&pSum); |
When the client process calls the IUnknown::QueryInterface method to request an interface pointer for ISum, the proxy manager in the client's address space calls the IRemUnknown::RemQueryInterface method on the server. Figure 19-9 shows the network packet that is transmitted for the RemQueryInterface method call. In this packet, you can clearly see that ORPC is requesting a count of five references for the ISum interface pointer.
Figure 19-9. The request PDU transmitted for the IRemUnknown::RemQueryInterface(IPID, 5, 1, IID_ISum) call.
On the server side, the actual IUnknown::QueryInterface call is executed to request an ISum interface pointer from the component. This interface pointer is then returned to the client in the marshaled form of a standard object reference (STDOBJREF). Figure 19-10 shows the response PDU that is returned to the client.
Figure 19-10. The response PDU transmitted for the IRemUnknown::RemQueryInterface call.
To prevent a malicious application from making a call to IRemUnknown::RemRelease and purposefully trying to force an object to unload while other clients might still be using it, a client can request private references. Private references are stored with the client's identity so that one client cannot release the private references of another. However, when you pass an interface pointer, private references cannot be provided from one object to another. Each client must request and release its own private references by explicitly calling the RemAddRef and RemRelease methods. These methods accept an argument that is an array of REMINTERFACEREF structures. The REMINTERFACEREF structure specifies an IPID and the number of public and private references that are being requested or released by the client. The definition of the REMINTERFACEREF structure is shown here:
typedef struct tagREMINTERFACEREF { IPID ipid; // IPID to AddRef/Release unsigned long cPublicRefs; unsigned long cPrivateRefs; } REMINTERFACEREF; |
The IRemUnknown2 interface was introduced in version 5.2 of the ORPC protocol. Derived from the IRemUnknown interface, IRemUnknown2 adds the RemoteQueryInterface2 method, which enables clients to retrieve interface pointers to objects that supply additional data beyond the STDOBJREF in their marshaled interface packets. Like RemQueryInterface, this method queries for zero or more interfaces using the interface behind the IPID. Instead of returning the STDOBJREF marshaled interface packet, this method can return any marshaled data packet in the form of a byte array (including a traditional STDOBJREF). The IDL definition of the IRemUnknown2 interface is shown in the following code:
interface IRemUnknown2 : IRemUnknown { HRESULT RemQueryInterface2 ( [in] REFIPID ripid, [in] unsigned short cIids, [in, size_is(cIids)] IID *iids, [out, size_is(cIids)] HRESULT *phr, [out, size_is(cIids)] MInterfacePointer **ppMIF ); } |