Method calls made on remote COM+ objects are considered true DCE RPC invocations in that a standard request Protocol Data Unit (PDU) is transmitted across the network, requesting that a specific method be executed. A PDU is the basic unit of communication between two machines. The request PDU contains all of the [in] parameters that the method expects to receive. When method execution is complete, a response PDU containing the method's [out] parameters is transmitted back to the client.
This process sounds rather obvious, but it is quite amazing when you consider how it actually works. A remote method call requires two packets to be transmitted across the network, one from the client to the server containing the [in] parameters and the other from the server to the client containing the [out] parameters. The 19 defined PDU types and their values are listed in the table below. The table also shows whether a particular PDU type is specific to a connection-oriented (abbreviated as CO here) protocol, a connectionless (CL) protocol, or both.
PDU Type | Protocol(s) | Type Value |
---|---|---|
request | CO/CL | 0 |
ping | CL | 1 |
response | CO/CL | 2 |
fault | CO/CL | 3 |
working | CL | 4 |
nocall | CL | 5 |
reject | CL | 6 |
ack | CL | 7 |
cl_cancel | CL | 8 |
fack | CL | 9 |
cancel_ack | CL | 10 |
bind | CO | 11 |
bind_ack | CO | 12 |
bind_nak | CO | 13 |
alter_context | CO | 14 |
alter_context_resp | CO | 15 |
shutdown | CO | 17 |
co_cancel | CO | 18 |
orphaned | CO | 19 |
A connection-oriented protocol, such as TCP, maintains a virtual connection for the client and server between transmissions and guarantees that messages are delivered in the order in which they were sent. A connectionless protocol, such as UDP, does not maintain a connection between the client and server and does not guarantee that a message from the client will actually be delivered to the server. And even if the messages are delivered, they might arrive in a different order from that in which they were sent. By default, COM+ uses the connectionless UDP. A connectionless protocol does not make COM+ unreliable, however, because RPC ensures robustness by using a customized mechanism for message ordering and acknowledgment.
An RPC PDU contains up to three parts, only the first of which is required:
The PDU header used for connectionless protocols is shown here in IDL notation:
typedef struct { unsigned small rpc_vers = 4; // RPC protocol major version unsigned small ptype; // Packet type unsigned small flags1; // Packet flags unsigned small flags2; // Packet flags byte drep[3]; // Data representation format label unsigned small serial_hi; // High byte of serial number GUID object; // Object identifier (contains IPID) GUID if_id; // IID GUID act_id; // Activity identifier unsigned long server_boot; // Server boot time unsigned long if_vers; // Interface version unsigned long seqnum; // Sequence number unsigned short opnum; // Operation number unsigned short ihint; // Interface hint unsigned short ahint; // Activity hint unsigned short len; // Length of packet body unsigned short fragnum; // Fragment number unsigned small auth_proto; // Authentication protocol ID unsigned small serial_lo; // Low byte of serial number } dc_rpc_cl_pkt_hdr_t; |
The packet type field (named ptype in this structure) of a PDU identifies the PDU type. This value is one of the 19 PDU types listed in the earlier table. ORPC uses the object identifier (OID) field (object) of a PDU to store an IPID. An IPID is a GUID that represents an interface of an object hosted by a component. The IID field (if_id) must contain the IID of the COM+ interface. This field is somewhat redundant given that the OID field contains the IPID, which already identifies the interface. However, placing the IID in the if_id field allows COM+ to work correctly when it is run on a standard implementation of OSF DCE RPC. On systems such as Windows, the RPC implementation has been optimized to enable method calls to be dispatched based solely on the information contained in the IPID, ignoring the IID. Finally, the interface version number (if_vers) must always be 0.0 because a COM+ interface can never be modified after it is published. COM+ interfaces are not versioned; a new interface is defined instead. All of these fields can be found in the RPC header section of the captured network packet (shown earlier in Figure 19-5).
All method invocations are transmitted across the network in a request PDU containing a special first parameter of type ORPCTHIS, which is inserted before all the other inbound parameters of the method. Thus, a COM+ method defined as HRESULT Sum(int x, int y, [out, retval] int* result) is transmitted in a request PDU as Sum(ORPCTHIS orpcthis, int x, int y). The definition of the ORPCTHIS structure is shown here:
// Implicit "this" pointer is the first [in] parameter of // every ORPC call. typedef struct tagORPCTHIS { COMVERSION version; // ORPC version number unsigned long flags; // ORPCF flags for presence // of other data. unsigned long reserved1; // Set to 0 CID cid; // Causality ID of caller [unique] ORPC_EXTENT_ARRAY *extensions; // Extensions } ORPCTHIS; |
The first field of the ORPCTHIS structure specifies the version of the ORPC protocol used to make the method call. Because each remote method call contains an ORPCTHIS structure, the version of ORPC on the client machine is always transmitted to the server. At the server, the client's version of ORPC is compared with the server's, and if the major version numbers don't match, the RPC_E_VERSION_MISMATCH error is returned to the client. The server is allowed to have a higher minor version number than the client, however. In such cases, the server must limit its use of the ORPC protocol to match the features available in the client's version.
A causality identifier (CID) is a GUID used to link together what might be a long chain of method calls. For example, if client A calls component B, and component B, before ever returning to client A, proceeds to call component C, these calls are said to be causally related. Every time a new method call is made (but not during the processing of an existing method), a new CID is generated by the ORPC protocol. The same CID is propagated in any subsequent calls made by component B on behalf of client A. This happens even if component B uses connection points or some other mechanism to call back into client A. The extensions field of the ORPCTHIS structure is designed to allow extra data to be sent with a COM+ method call. Currently only two extensions are defined, one for extended error information and the other for debugging control. You can also define custom extensions to the ORPCTHIS structure using channel hooks.8
In every response PDU for a method call, a special outbound parameter of type ORPCTHAT is inserted before all other [out] parameters of the method. Thus, a method defined as HRESULT Sum(int x, int y, [out, retval] int* result) is transmitted in a response PDU as HRESULT Sum(ORPCTHAT orpcthat, int result). The definition of the ORPCTHAT structure is shown here:
// Implicit "that" pointer is the first [out] parameter of // every ORPC call. typedef struct tagORPCTHAT { unsigned long flags; // ORPCF flags for presence // of other data. [unique] ORPC_EXTENT_ARRAY *extensions; // Extensions } ORPCTHAT; |