Escolar Documentos
Profissional Documentos
Cultura Documentos
aspx
A case study
Microsoft RPC
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
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
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
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:
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 for our project is shown in Listing 43.4.
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 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.
void Shutdown(void)
{
RpcMgmtStopServerListening(NULL);
RpcServerUnregisterIf(NULL, NULL, FALSE);
}
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.
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.
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.
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]
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:
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:
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.
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) {
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.
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:
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++:
The derived interface inherits member functions, status codes, and interface attributes
from the base interface.
Summary
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.
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.