Você está na página 1de 10

http://msdn.microsoft.com/en-us/library/aa373570(v=VS.85).

aspx

A case study

Microsoft RPC

Microsoft's RPC extends the conceptual elegance and simplicity of a function or


subroutine call by providing a similar mechanism for calls across the network. RPC
servers can offer a set of functions that can be called by RPC clients.

The RPC mechanism is used widely in Windows.

RPC Fundamentals

The key to the RPC mechanism is stub functions. When an RPC client calls an RPC
function, it issues a regular function call. However, the function that receives a call is
an RPC stub; this stub function converts function arguments for transmission across
the network (a procedure referred to as marshaling) and transmits the call request and
the arguments to the server.

Stub functions on the server side unmarshal function arguments and call the server's
implementation of the function. When the function returns, its return value is passed
back to the client using a reverse mechanism. This entire procedure is illustrated in
the figure below

When developing RPC applications, an element of central importance is the interface.


Clearly, the stub functions on the client and the server sides must be based on
identical function definitions; otherwise, the RPC process will surely fail. The tool
that ensures that the stub functions on the two sides are compatible is the Microsoft
Interface Development Language (MIDL) compiler.
Example

The following example clarifies the basic concepts of Microsoft RPC, which also
gives the general principles of RPC. This example, the RPC equivalent of the Hello,
World application, implements a simple server function that prints a string received
from client applications.

The first step in developing this example is to specify the interface. This is done in the
form of two files that represent the input files for the MIDL compiler. The MIDL
compiler will produce three files: a header file that must be included in both the client
and the server application, and two C source files that implement the client and server
stub functions. These files must be linked with our implementation of both the client
and the server application to produce the final executables. This process is illustrated
in the figure below

Specifying the Interface

The interface for an RPC implementation is specified in the form of two files: the
Interface Definition Language File and the Application Configuration File. These two
files together serve as input for the MIDL compiler.

The Interface Definition Language File for our example application is shown in

Listing 1

Listing 1. The Interface Definition Language File. (hello.idl)


[
uuid (6fdd2ce0-0985-11cf-87c3-00403321bfac),
version(1.0)
]

interface hello
{
void HelloProc([in, string] const unsigned char *pszString);
void Shutdown(void);
}

This file consists of two parts: the interface header and the interface body. The
interface header has the following syntax:

[ interface-attributes ] interface interface-name

Perhaps the most important of the interface attributes is the interface's GUID, or
globally unique identifier. The GUID is a 128-bit identifier that is supposed to be
world-unique; in other words, no two applications in the world are supposed to have
identical identifiers.

The Visual C++ distribution provides a tool, the executable program guidgen.exe, that
is supposed to create such unique identifiers using, in part, information obtained from
your computer's hardware, and in part a randomization algorithm.

Note: You can use Uuidgen.exe or Guidgen.exe to generate your own unique IDs.
(To run either of these tools, click Start and click Run on the menu. Then enter the
name of the required tool.)

The GUID is expressed in the form of a string of 32 hexadecimal digits. The specific
form is 8, 4, 4, 4, and 12 digits separated by hyphens. The guidgen.exe program
generates GUID strings in this form.

In addition to the GUID, another interface attribute specifies the interface's version
number. The function of the version number is to identify potentially incompatible
versions of the interface.

In the second part of the Interface Definition Language File, function prototypes are
defined. The prototype syntax is similar to the syntax of the C language but it also
contains extra elements. The in keyword indicates to the MIDL compiler that the
following parameter is an input-only parameter; that is, it is sent to the server by the
client. The string keyword indicates that the data sent is a character array.

The Application Configuration File is similar in syntax and appearance to the


Interface Definition Language File. However, this file contains information on data
and attributes not related to the actual transmission of RPC data.

The Application Configuration File for our project is shown in Listing 43.4.

Listing 2 The Application Configuration File (hello.acf)


[
implicit_handle(handle_t hello_IfHandle)
]
interface hello
{
}

In this file, we specify a binding handle for the interface. This handle is a data object
that represents the connection between the client and the server. Because the handle is
not transmitted over the network, it is specified in the Application Configuration File.

The use of the keyword implicit_handle prescribes a handle that is maintained as a


global variable. This handle will be used by the client in calls to RPC run-time
functions.

The two files, hello.idl and hello.acf, can be compiled by the MIDL compiler using a
single command:

midl hello.idl

The result of the compilation is three files produced by the MIDL compiler: hello_c.c,
hello_s.c, and hello.h. The files hello_c.c and hello_s.c provide the client and server-
side implementation of stub functions; the hello.h header provides necessary
declarations.

By looking at these generated files, one can easily see just how much of the network
programmer's work is automated by the use of the MIDL compiler. The generated
stub functions perform all the required run-time library calls to marshal and
unmarshal arguments and to communicate across the network. However, the real
beauty and elegant simplicity of the RPC mechanism are yet to become evident when
we implement our client and server.

Implementing the Server

The server application's implementation is shown below

Listing 3. A simple RPC server (hello_s.c)


#include <stdlib.h>
#include <stdio.h>
#include "hello.h"

void HelloProc(const unsigned char *pszString)


{
printf("%s\n", pszString);
}

void Shutdown(void)
{
RpcMgmtStopServerListening(NULL);
RpcServerUnregisterIf(NULL, NULL, FALSE);
}

void main(int argc, char * argv[])

{ RpcServerUseProtseqEp("ncacn_ip_tcp", 20, "8000", NULL);


RpcServerRegisterIf(hello_v1_0_s_ifspec, NULL, NULL);
RpcServerListen(1, 20, FALSE);
}
void __RPC_FAR * __RPC_USER midl_user_allocate(size_t len)
{
return(malloc(len));
}

void __RPC_USER midl_user_free(void __RPC_FAR * ptr)


{
free(ptr);
}

The RPC run-time library is initialized and the server is set up to wait for incoming
connections through the three RPC run-time library calls in the program's main
function. First of these calls is the RpcServerUseProtseqEp call; this call defines the
network protocol and endpoint that is to be used by the application. In our example, I
decided to use the TCP over IP protocol (ncacn_ip_tcp) as a protocol that is supported
both under Windows 95 and Windows NT. The RPC mechanism can utilize many
other protocols, as shown in Table 43.1.

Table 1. RPC protocols.

protocol name Description

ncacn_ip_tcp TCP over IP


ncacn_nb_tcp TCP over NetBIOS
ncacn_nb_ipx IPX over NetBEUI
ncacn_nb_nb NetBIOS over NetBEUI
ncacn_np Named pipes
ncacn_spx SPX
ncacn_dnet_nsp DECnet transport
ncadg_ip_udp UDP over IP
ncadg_ipx IPX
ncalrpc local procedure call

Protocols with a name that begins with ncacn are connection-oriented protocols; those
with a name that starts with ncadg are datagram (connectionless) protocols.

Because Windows 95 supports named pipes for the client side only, the ncacn_np
protocol is only supported for RPC client applications. Windows 95 does not support
ncacn_nb_ipx and ncacn_nb_tcp. The ncacn_dnet_nsp protocol is supported only for
16-bit Windows and MS-DOS clients.

The meaning of the endpoint parameter is dependent on the protocol. For example,
when the ncacn_ip_tcp protocol is used, the endpoint parameter represents a TPC port
number.

Starting up the server consists of two steps. First, the server interface is registered;
second, it enters a state where it listens for incoming connections. Registering the
server makes it available for incoming client connections.
The actual remote procedure, HelloProc, is implemented the same way as a local
function. In fact, it is possible to place the implementation of this function in a
separate file; this way, applications that locally call the function could be linked with
it, while applications that call this function through RPC link with the client-side stub
instead.

Another function, Shutdown, has been provided to facilitate remote shutdown of the
server. Server shutdown is accomplished by exiting the listening state and
unregistering the server.

In addition to these two functions, the Microsoft RPC specifications require that we
implement two additional memory management functions. The midl_user_allocate
and midl_user_free functions are used to allocated a block of memory and to free up
allocated memory. In simple cases, these can be mapped to the C run-time functions
malloc and free; however, in large, complex applications these functions enable finer
control over memory use. Both of these functions are used when arguments are
marshaled or unmarshaled.

To compile the server application, use the following command line:

cl hellos.c hello_s.c rpcrt4.lib

Implementing the Client

Implementing the RPC client is only slightly more complicated than implementing an
application containing a local function call. The client implementation for our
example is shown in Listing 43.6.

Listing 4 A simple RPC client (hello_c.c)


#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "hello.h"

void main(int argc, char *argv[])


{
unsigned char *pszStringBinding;
if (argc != 3)
{
printf("Usage: %s hostname string-to-print\n", argv[0]);
exit(1);
}
RpcStringBindingCompose(NULL, "ncacn_ip_tcp", argv[1], "8000",
NULL, &pszStringBinding);

RpcBindingFromStringBinding(pszStringBinding, &hello_IfHandle);
if (strcmp(argv[2], "SHUTDOWN")) HelloProc(argv[2]);
else Shutdown();
RpcStringFree(&pszStringBinding);
RpcBindingFree(&hello_IfHandle);
exit(0);
}
void __RPC_FAR * __RPC_USER midl_user_allocate(size_t len)
{
return(malloc(len));
}
void __RPC_USER midl_user_free(void __RPC_FAR * ptr)
{
free(ptr);
}

This client implementation takes two command-line parameters: the name of the
server to connect to and the string to be sent to the server. Because of the protocol
being used (TCP over IP), the server name must be the host's internet name or IP
address. If you are testing the client and the server on the same host, you can use the
default name for the local host, localhost, or its default IP address, 127.0.0.1.

The protocol name, host name, and endpoint are combined into a string binding using
the RpcStringBindingCompose function. For example, the string binding representing
the ncacn_ip_tcp protocol, the local host, and TCP port 8000 would appear as
follows:

ncacn_ip_tcp:localhost[8000]

The RpcStringBindingCompose function is merely a convenience function that frees


the programmer from the task of having to assemble the string binding from its
components by hand. This string binding is used to obtain the binding handle for the
interface in the call to RpcBindingFromStringBinding. The receipt of the binding
handle indicates that the connection is ready to be used.

Once the connection is established, using it is simplicity itself. Calling a remote


procedure becomes identical to calling a local function. In our client implementation,
we call the function HelloProc with the second command-line argument; that is,
unless that argument is the string SHUTDOWN, in which case the Shutdown function
is called instead to shut down the remote server.

Like the client, the server must also provide its implementations for
midl_user_allocate and midl_user_free.

The client application can be compiled using the following command line:

cl helloc.c hello_c.c rpcrt4.lib

Once you have compiled both the server and the client executables, you can test the
two programs from two DOS windows. After you started the server in one of the
windows, run the client in the other window as follows:

helloc localhost "Hello, World!"

To shut down the server from the client side, type:

helloc localhost SHUTDOWN


RPC Exception Handling

If you attempt to run the client application developed in the previous section alone,
without starting up a server, a serious deficiency of our implementation becomes
evident. Neither the client nor the server in this example performs any error handling;
in particular, this means that the client does not respond well to situations when no
server is available.

Unlike other errors, the unavailability of a server should be considered a likely


occurrence that must be handled. (Not that I am suggesting that handling of other
errors can or should be neglected in production applications!) The Microsoft RPC
implementation provides a special mechanism for this purpose that is very similar in
its appearance to Win32 structured exceptions or C++ exception handling.

By protecting the remote procedure calls using the RPC exception handling macros,
one can ensure graceful handling of network error conditions by a client application.
For example, in our Hello, World application, the calls to HelloProc and Shutdown on
the client side could be protected as follows:

RpcTryExcept
{
if (strcmp(argv[2], "SHUTDOWN")) HelloProc(argv[2]);
else Shutdown();
}

RpcExcept(1) {

printf("RPC run-time exception %08.8X\n", RpcExceptionCode());


}

RpcEndExcept

If you recompile the client application with this change, it will no longer crash when
the server is not available; it will gracefully terminate, printing the exception number.

Advanced RPC Features

Although this example application demonstrates the simplicity of using Microsoft


RPC adequately, it only hints at the power and rich features of the RPC mechanism.
This section mentions a few of the most notable features of Microsoft RPC.

The MIDL compiler can be used for specifying remote procedures that accept all
kinds of arguments. This includes pointers and arrays; however, pointers and arrays
require special consideration. Because the RPC stub functions must not only marshal
the pointer arguments themselves but also the data they point to, it is necessary to
define the size of the memory block a pointer points to in the interface specification.
A series of attributes is available for specifying an array's size. For example, consider
a remote procedure that takes the size of an array and a pointer to it as its parameters:

void myproc(short s, double *d);

You can identify the interface for such a procedure in your IDL file as follows:
void myproc([in] short s, [in, out, size_is ] double d[]);

This informs the MIDL to generate stub code that marshals s number of array
elements.

The size_is attribute is not the only attribute that assists in specifying array arguments.
Others include length_is, first_is, last_is, and max_is.

The RPC mechanism can utilize the Microsoft RPC Name Service Provider on
Windows NT. Through this mechanism, it is possible for clients to locate an RPC
server by name. In particular, the use of RPC Name Service enables you to develop
clients that do not use an explicit binding handle (like our Hello, World example did).
Such clients would contain no RPC run-time library calls whatsoever and apart from
being linked with the client-side stub, they look no different from programs that use
local functions.

The MIDL syntax enables you to define interfaces that are derived from other
interfaces. The syntax is similar to that used for deriving classes in C++:

[attributes] interface interface-name : base-interface

The derived interface inherits member functions, status codes, and interface attributes
from the base interface.

Summary

Pipes represent a simple, efficient interapplication communication mechanism


supported by Windows 95 (client side only) and Windows NT.

Pipes can be named and unnamed. Anonymous pipes are typically used between a
parent and a child process or two sibling processes. The use of anonymous pipes
requires communicating a pipe handle from one process to another (for example, by
inheriting the handle). Because anonymous pipes are identified by handle alone, they
cannot be used for communication across a network.

Unlike anonymous pipes, named pipes support overlapped I/O operations. A server
can use a combination of techniques including overlapped I/O and using separate
threads to serve several clients simultaneously.

Both servers and clients communicate on a named pipe using the pipe's handle and
standard Win32 input and output functions.

Microsoft RPC is a mechanism for applications to call functions remotely. It provides


a transparent interface where client applications can call remote functions in a fashion
that is very similar to the calling of local functions.

The key to Microsoft RPC, in addition to the RPC run-time library, is the Microsoft
Interface Definition Language (MIDL) compiler. The interface between a client and a
server can be specified using a simple C-like syntax, from which the MIDL compiler
generates server and client-side stub functions, freeing the programmer from the
burden of complex network programming tasks and from having to maintain
compatible versions of these functions on the server and client sides.

The actual implementation is through these stub functions. When a client calls a
remote procedure, the call is handled by the corresponding client-side stub function.
The stub function, in turn, calls the RPC run-time library that uses the underlying
network transport to invoke stub functions on the server side. The client-side stub
function also marshals function arguments for transmission over the network. The
server-side stub function unmarshals these arguments and calls the actual function
implementation. When the function returns, this process is played out in reverse, as
return values are transported back to the client application.

Advanced RPC features include RPC Name Service, pointer and array function
arguments, derived interfaces, and much more.

Você também pode gostar