[Previous] [Contents] [Next]

Calling All Remote Objects

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).

The ORPCTHIS and ORPCTHAT Structures

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;