Remote procedure calls bring network applications together.
Tom Yager
Developers can no longer pretend that their applications run on isolated systems. While today's workers are used to seamless data exchange among applications running on the same system, it won't be long before they demand the same effortless data connectivity across networks.
Every network-capable OS has its pet scheme for passing applications data through network links. Sometimes you can graft one vendor's method onto another vendor's OS. The TCP/IP ("Berkeley") sockets API has taken up with PCs (even those running DOS) and Macs, staying transparently compatibl
e with its Unix birthplace. However, sockets programming takes some time to master. Develo
pers yearn for a simpler, more elegant networking API.
That API does exist. It's called remote procedure call (RPC). RPC was born at Sun as part of its NFS and Open Network Computing (ONC) distribution. The Open Software Foundation (OSF) made a significantly enhanced RPC part of its Distributed Computing Environment (DCE). There is also an emerging ISO RPC specification.
RPC, old or new, comes with most OSes. The focus of this article is the implementation Microsoft delivers with Windows NT Server 4.0. If you're a Unix or Mac user, you should have little trouble applying what you read here to your target OS. Note that Microsoft ships only the RPC portion of the DCE standard with NT. A complete DCE implementation is available in source code from the Open Group/OSF (
http://www.osf.org
).
Embrace Diversity
NT's version of RPC is sweet. It supports NT's standard networking protocols and can converse effortlessly with clients running DOS; Windows 3.x, 95, or NT; Unix; or the Mac OS. Data knows no platform preference, because RPC smooths the data representation, such as byte ordering and INT size differences. As long as both client and server speak DCE RPC, all lines are open. The Win32 Software Development Kit (SDK) includes the tools and header files needed to support RPC, and Microsoft's Visual C++ development environment supports and documents RPC development.
In describing RPC, it helps to start with a broad brush and work down to the detail. A one-line summary of RPC is that it lets you call virtually any C function from across a network. If you let RPC do all the work, your client code simply makes ordinary function calls, even though the target function resides and executes on a remote server. You can pass arguments from client to server, and the server can send back re
turn values.
The server can even modify data that is passed as arguments, just as a local function can. You can pass pointers, structures, and unions as data. RPC takes care of sending the data, making the call, and returning the results.
If your network is running an RPC directory service (DCE defines its standard Cell Directory Service [CDS]; NT includes Microsoft's proprietary RPC Locator), the client searches the directory for a server that is offering the requested function. A server can upgrade its functions and still remain compatible with older clients. Each RPC interface (group of functions) is tagged with a version number. The server invokes the function with the matching version number, or it refuses the request if a version is no longer supported.
In the NT environment, a server can make an RPC function available to all types of network clients simultaneously. That means one server application can advertise its functions to TCP/IP, Microsoft NetBIOS, NetWare IPX/SPX, and AppleTalk clie
nts. The table
"Microsoft-Supported Network Protocols"
shows the OSes that offer RPC and which protocols they support.
Fetch the Marshal
RPC's programming interface is partly implemented in a way not often seen today: as a C-code generator. There are libraries and header files, of course, but RPC's simplicity comes from its generated code. Every RPC application is tagged with a Universal Unique Identifier (UUID), which is derived randomly as the first step of code generation. This is part of the identification string that servers register with the directory service. Clients searching for a server must supply a registered UUID, interface name, and version number to get a match.
The RPC code generator works from a template written in the RPC interface definition language (IDL). Microsoft made its mark with some enhancements, turning IDL into MIDL.
"Sample MIDL Listing"
shows a MIDL file that defines the UUID, version number, and interface
name for the application. It also defines the data that passes between client and server through expanded C-style function templates.
Earlier implementations of RPC permitted one outgoing and one incoming data structure. DCE RPC permits any number of function arguments. Each argument can send data to the server (
[in]
), return data to the client (
[out]
), or both (
[in, out]
). MIDL files can contain quite ordinary-looking C variable definitions, even
typedefs
. You can use any data types that are defined in the MIDL file as arguments and return values in RPCs.
The MIDL compiler takes in the MIDL definitions and churns out
stubs
, the C source code that does RPC's hard work. This includes three files: a server stub, a client stub, and a header file. Using RPC's simplest model, you need only compile your stubs to object files and then link those objects into your code. You link the server stub with your server application and the client stub with your client appli
cation. You include the header file in both client and server to import the data definitions and to gain access to the RPC API.
The stub code converts data for transmission through marshaling and unmarshaling operations. When a client calls a server, the client stub marshals the arguments by combining the data and converting numeric information into an architecture-independent Network Data Representation (NDR). When the server receives the data, the stub breaks apart the incoming arguments and converts NDR-encoded numerics back to the host's native format. The server calls the requested function, and the server stub then marshals modified (
[out]
) arguments and the return value for transmission back to the client.
Ties That Bind
When you're building an RPC application, you must choose a binding method. This determines how the client locates the remote call on the server. Automatic binding takes care of everything. The server can advertise itself on all protocols simultaneo
usly. The client consults its configured directory service (RPC Locator or CDS) to find the server it needs. It lines up matching network protocols, marshals the arguments, makes the call, and unmarshals the returned data with practically no effort. Furthermore, a connection based on automatic binding can often automatically retry a call that fails due to a temporary network outage.
Implicit binding gives your client access to a binding handle. Calls in the RPC API use this binding handle to identify and manage the server connection, but the stub code holds the handle for you. If you choose explicit binding, your client is responsible for the binding handle and must pass it as an argument with every remote call. Implicit binding exposes more of the RPC API, while explicit binding lets a single client maintain connections with multiple servers.
Of the three binding methods, automatic binding is clearly the slowest one. It exacts enough of a performance penalty to force you to use another method for fre
quent calls or performance-critical code.
While it's a little slow, automatic binding creates the shortest, most easily understood code. The sample code (which is available at
http://www.byte.com/art/download/download.htm
) includes a Visual C++ make file, a MIDL file, and client and server code for a simple RPC application. The server is written for NT, but the client can run on any version of Windows. If you run the client under Windows 95, check the readme file for instructions on modifying the registry to point to an RPC Locator or CDS on your network.
Protocol name
Description
DOS
Win
Win
Win
Mac
Unix
3.x
95
NT
ncacn_nb_tcp NetBIOS over TCP C C N C,S N N
ncacn_nb_ipx NetBIOS over IPX C C N C,S N N
ncacn_nb_nb NetBEUI C C C,S C,S N N
ncacn_ip_tcp TCP/IP C C C,S C,S C C,S
ncacn_np Named pipes C C C C,S N N
ncacn_spx SPX C C C,S C,S N N
ncacn_dnet_nsp DECnet C C N N N N
ncacn_at_dsp AppleTalk N N N N C N
ncacn_vns_spp Banyan Vines N N N C,S N N
ncadg_ip_udp Datagram UDP/IP C C N C,S N C,S
ncadg_ipx Datagram IPX C C N C,S N N
ncalrpc Local procedure call N N
C,S C,S N N
Each protocol's OS support is shown with a
C
for client support,
S
for server support, and
N
for no support.
After the header, the MIDL file follows C-language conventions with
some extensions.The RPCDemo function prototype shows one string
argument passed to the server, one string shared by the client and
server (the server may modify it), and a Boolean (
true/false
) return
value.
//file RPCDemo.idl
[
uuid(7a98c250-6808-11cf-b73b-00aa00b677a7),
version(1.0),
pointer_default(unique)
]
interface RPCDemo
{
const long MAX_LEN = 255;
typedef [string] char pszArg1[MAX_LEN + 1];
typedef [string] char pszArg2[MAX_LEN + 1];
Boolean RPCDemoProc(
[in] pszArg1,
[in, out] pszArg2
);
}
Tom Yager is a freelance writer and senior so
ftware developer in Dallas, Texas. You can reach him at
tyager@maxx.net
.