Microsoft RFC Programming Guide

Microsoft RFC Programming Guide

John Shirley and Ward Rosenberry

Digital Equipment Corporation

O'Reilly & Associates, Inc.

103 Morris Street, Suite A

Sebastopol, CA 95472

Microsoft RFC Programming Guide

by John Shirley and Ward Rosenberry

Copyright © 1995 O'Reilly & Associates, Inc. All rights reserved. Printed in the United States of America.

£ ditor Andy Oram

Production Editor: Clairemarie Fisher O'Leary

Printing History:

March 1995: First Edition.

O'Reilly & Associates and the author specifically disclaim all warranties, expressed or implied, including but not limited to implied warranties of merchantability and fitness for particular purpose with respect to the diskettes and the programs therein contained, and in no event shall O'Reilly & Associates or the author be liable for any loss of profit or any other commercial damage, including but not limited to special, incidental, consequential, or other damages.

Nutshell Handbook and the Nutshell Handbook logo are registered trademarks of O'Reilly & Associates, Inc.

Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks. Where those designations appear in this book, and O'Reilly & Associates, Inc. was aware of a trademark claim, the designations have been printed in caps or initial caps.

While every precaution has been taken in the preparation of this book, the publisher assumes no responsibility for errors or omissions, or for damages resulting from the use of the information contained herein.

This book is printed on acid-free paper with 85% recycled content, 15% post-consumer waste. O'Reilly & Associates is committed to using paper with the highest recycled content available consistent with high quality.

ISBN: 1-56592-070-8

Table of Contents

Preface xi

Conventions xii

Book Organization xii

How to Use This Book xiii

Obtaining the Example Programs xiv

CompuServe xiv

FTP xvi

FTPMAIL xvii

BITFTP xvii

Acknowledgments xviii

Joint Venture xix

1: Overview of an RFC Application 1

A Simple-Interface 4

Universal Unique Identifiers 5

The Interface Definition 6

Stub and Header Generation Using the MIDI Compiler 7

A Simple Client 8

A Minimal Server 11

Remote Procedure Implementation 11

A Distributed Application Environment 13

Server Initialization 18

Producing the Application 21

Microsoft RFC Libraries . .21

Compile and Link the Client and Server Code 21

Running the Application 23

2: Using a Microsoft RFC Interface 27

Microsoft Interface Definition Language (MIDI) 28

Attributes 28

Structure of an Interface Definition 29

Interface Header Attributes 29

The Inventory Application 30

Type Definitions, Data Attributes, and Constants 30

Procedure Declarations and Parameter Attributes 38

Using the MIDI Compiler 40

Generating Client Files 42

Generating Server Files 42

Using an ACF to Customize Interface Usage 42

Selecting a Binding Method 43

Controlling Errors and Exceptions 44

Excluding Unused Procedures 44

3: How to Write Clients 45

Binding 45

Implementing a Binding Method 46

Automatic Binding Management 49

Implicit Binding Management 50

Explicit Binding Management 52

Steps in Finding Servers 55

Finding a Protocol Sequence 56

Finding a Server Host 58

Finding an Endpoint 59

Interpreting Binding Information 60

Finding a Server from a Name Service Database 6l

Finding a Server from Strings of Binding Data 64

Customizing a Binding Handle 66

Authentication 71

Error Parameters or Exceptions 72

Using Exception Handlers in Clients or Servers 72

Using Remote Procedure Parameters to Handle Errors 72

Compiling and Linking Clients 74

Local Testing 76

4: Pointers, Arrays, and Memory Usage 79

Kinds of Pointers 79

Pointers as Output Parameters 80

Pointers as Input Parameters 82

Using Pointers to Pointers for New Output 84

Pointers as Procedure Return Values 86

Pointer Summary 87

Kinds of Arrays 90

Selecting a Portion of a Varying Array 90

Managing the Size of a Conformant Array 91

Memory Usage 94

Node-By-Node Allocation and Deallocation 96

Using Contiguous Server Memory 97

Allocating Buffers with the Client Application 97

Persistent Storage on the Server 98

5: How to Write a Sewer 99

Some Background on Call Processing 99

Initializing the Server 101

Registering Interfaces 102

Creating Server Binding Information 104

Advertising the Server 107

Managing Server Endpoints 109

Listening for Remote Procedure Calls 110

Writing Remote Procedures 112

Managing Memory in Remote Procedures 113

Allocating Memory for Conformant Arrays 116

Compiling and Linking Servers 117

6: Using a Name Service 121

Naming 122

DefaultEntry 122

Server Entries 123

Creating a Server Entry and Exporting Binding Information 125

Some Rules for Using the Microsoft Locator 126

7: Context Handles 729

The Remote_file Application 129

Declaring Context in an Interface Definition 130

Using a Context Handle in a Client 131

Binding Handles and Context Handles 133

Managing Context in a Server 133

Writing Procedures That Use a Context Handle 134

Writing a Context Rundown Procedure 136

A: MIDI and ACF Attributes Quick Reference 13 7

B: RFC Runtime Routines Quick Reference 143

C: The Arithmetic Application 149

How to Build and Run the Application 149

Application Files 150

D: The Inventory Application 757

How to Run the Application 158

Application Files 159

E: The Rfile Application 191

How to Run the Application 191

Application Files 192

F: The Windows Phonebook Application 201

How to Build and Run the Application 201

Application Files 201

Index . 223

Table of Contents

List of Figures

1-1 Client-server model 2

1-2 RFC mechanism 3

1 ~3 Application development 3

1-4 Arithmetic application: interface development 5

1-5 Arithmetic application: client development 9

1-6 Arithmetic application: server development 12

1-7 Binding 13

1-8 Binding information 13

1-9 Server initializing 15

1-10 Client finding a server 16

1-11 Completing a remote procedure call 17

1-12 Arithmetic application: complete development 25

2-1 Producing an interface 41

3-1 A comparison of binding management methods 47

3-2 How a customized binding handle works 67

3~3 Producing a client 75

5-1 How the server runtime library handles a call 100

5-2 Producing a server 118

6-1 Server entries in the name service database 123

Conventions

Throughout the book we use the following typographic conventions:

Constant width

indicates a language construct such as a MIDI keyword, a code example, system output, or user input. Words in constant width also represent application-specific variables and procedures.

Constant Bold

is used in examples to indicate text that is literally typed by the user.

Bold introduces new terms or concepts.

Italic in command syntax or examples indicates variables for which the user supplies a value. Italicized words in the text also represent system ele ments such as filenames and directory names, and user functions or RFC-specific routines.

[] enclose attributes in interface definitions and Attribute Configuration Files

(ACFs) and are part of the syntax. Note that this is different from the com mon convention in which brackets enclose optional items in format and syntax descriptions.

C:\> represents system prompts.

C:\SERVER>

represents a server system prompt to distinguish it from a client system prompt.

C:\CLIENT>

represents a client system prompt to distinguish it from a server system prompt.

Book Organization

This book is divided into the following seven chapters and six appendices:

Chapter 1, Overview of an RPC Application, shows a complete, simple RPC applica tion.

Chapter 2, Using a Microsoft RPC Interface, shows how to read an RPC interface definition (a file ending in .idl}, which is a file that declares the remote proce dures of an interface.

Chapter 3, How to Write Clients, discusses how to develop client programs for RPC interfaces. Topics include binding methods, finding servers, customizing binding handles, handling errors or exceptions, and compiling clients.

Chapter 4, Pointers, Arrays, and Memory Usage, shows how pointers and arrays are defined in an interface and how to develop applications to use them.

Chapter 5, How to Write a Server, discusses how to develop a server program for an RFC interface. Topics include initializing a server, writing remote procedures, and compiling servers.

Chapter 6, Using a Name Service, describes a name service database and how to use it with distributed applications.

Chapter 7, Context Handles, shows how to maintain a state (such as a file handle) on a specific server between remote procedure calls from a specific client.

Appendix A, MIDI and ACF Attributes Quick Reference, shows all the attributes in the Microsoft Interface Definition Language (MIDL) and Attribute Configuration File (ACF).

Appendix B, RFC Runtime Routines Quick Reference, shows all the RFC runtime routines organized into convenient categories.

Appendix C, The Arithmetic Application, is a small application that shows the basics of remote procedure calls.

Appendix D, The Inventory Application, is a somewhat richer application than that in Appendix C, showing different MIDL data types, how to use ACFs, and how to find servers by importing information from a name service database.

Appendix E, The Rfile Application, shows how to use context handles and how to find servers using strings of network location information.

Appendix F, The Windows Phonebook Application, offers a simple Windows-based client that uses RFC to get phone numbers from a database on the server.

How to Use This Book

If you are developing just a client for an existing RFC interface and server, read the following chapters first:

• Chapter 1, Overview of an RFC Application

• Chapter 2, Using a Microsoft RFC Interface

• Chapter 3, -How to Write Clients

Read other chapters as needed to learn how to develop applications that use more features of interface definitions.

If you are developing a network interface with accompanying server, read the fol lowing:

• Chapter 1, Overview of an RFC Application

• Chapter 2, Using a Microsoft RFC Interface

• Chapter 3, How to Write Clients

Enter choice !off

Thank you for using CompuServe!

Off at 06:59 EST 11-Jan-95 Connect time = 0:06

FTP

To use FTP, you need a machine with direct access to the Internet. A sample ses sion is shown, with what you should type in boldface.

% ftp ftp.uu.net

Connected to ftp.uu.net.

220 ftp.UU.NET FTP server (Version 6.34 Thu Oct 22 14:32:01 EOT 1992) read/.

Name (ftp.uu.net:andyo): anonymous

331 Guest login ok, send e-mail address as password.

Password: janetv@xyz.ccm (use your user name and host here)

230 Guest login ok, access restrictions apply.

ftp> cd /published/oreilly/nutshell/ms_rpc

250 CWD command successful.

ftp> binary (Very important 1 . You must specify binary transfer for compressed files.)

200 Type set to I.

ftp> prompt (Convenient, so you are not queried for every file transferred)

Interactive mode off.

ftp> mget *

200 PORT command successful.

ftp> quit 221 Goodbye. %

Each .Z archive contains all source code and configuration information required for building one example. Extract each example through a command like:

% zcat arith.dec94.tar.Z I tar xf -

System V systems require the following tar command instead:

% zcat arith.dec94.tar.Z | tar xof -

If zcat is not available on your system, use separate uncompress and tar com mands.

The tar command creates a subdirectory that holds all the files from its archive. The README.dec94 file in this subdirectory describes the goals of the example and how to build and run it; the text is an ASCII version of the introductory mate rial from the corresponding appendix in this book.

Preface xvn

FTPMAIL

FTPMAIL is a mail server available to anyone who can send electronic mail to and receive it from Internet sites. This includes any company or service provider that allows email connections to the Internet. Here's how you do it.

You send mail to ftpmail@online.ora.com. In the message body, give the FTP com mands you want to run. The server will run anonymous FTP for you and mail the files back to you. To get a complete help file, send a message with no subject and the single word "help" in the body. The following is an example mail session that should get you the examples. This command sends you a listing of the files in the selected directory, and the requested example files. The listing is useful if there's a later version of the examples you're interested in.

% mail ftpmail@online.ora.com

Subject:

reply-to janetv@xyz.com Where you want files mailed

open

cd /published/oreilly/nutshell/ms_rpc

dir

get REAEME.dec94

mode binary

uuencode (or btoa if you have it)

get arith.dec94.tar.Z

get inv.dec94.tar.Z

get rfile.dec94.tar.Z

get phnbk.dec94.tar.Z

quit

A signature at the end of the message is acceptable as long as it appears after "quit."

All retrieved files will be split into 60KB chunks and mailed to you. You then remove the mail headers and concatenate them into one file, and then uudecode or atob it. Once you've got the desired .Z files, follow the directions under FTP to extract the files from the archive.

BITFTP

BITFTP is a mail server for BITNET users. You send it electronic mail messages requesting files, and it sends the files back to you by electronic mail. BITFTP cur rently serves only users who send it mail from nodes that are directly on BITNET, EARN, or NetNorth. BITFTP is a public service of Princeton University. Here's how it works:

To use BITFTP, send mail containing your ftp commands to BITFTP@PUCC. For a complete help file, send HELP as the message body.

The following is the message body you should send to BITFTP:

FTP ftp.uu.net NETDATA

USER anonymous

PASS your Internet email address (not your bitnet address)

CD /published/oreilly/nutshell/ms_rpc

DIR

GET README

BINARY

GET arith.dec94.tar.Z

GET inv.dec94.tar.Z

GET rfile.dec94.tar.Z

GET phnbk.dec94.tar.Z

QUIT

Once you've got the desired .Z files, follow the directions under FTP to extract the files from the archive. Since you are probably not on a UNIX system, you may need to get versions of uudecode, uncompress, atob, and tar for your system.

Questions about BITFTP can be directed to Melinda Varian, MAINT@PUCC on BIT-NET.

Acknowledgments

This book can be traced back to the DCE documentation set put out by Digital Equipment Corporation. John Shirley, working with Steve Talbott and Andy Oram from O'Reilly & Associates, wrote a DCE version of this book called Guide to Writ ing DCE Applications. Ward Rosenberry then took it over and thoroughly revised it to cover Microsoft RFC. While at first glance, it might seem that relatively little effort was required to write this new version, the work put into it was nevertheless considerable and required the cooperation and support of many individuals.

First off, I want to thank my editor at O'Reilly & Associates, Andy Oram, for his excellent advice and his persistence on this lengthy project.

For supporting this project I want to thank folks at Digital Equipment Corporation, in particular Jeff Shrieshiem, Frank Willison, and Michelle Chambers for funding various portions of the project. Also at Digital Equipment corporation, other major contributors to this book include Neil Miranda, who converted several DCE appli cations to Microsoft RPC Version 1.0 for use in this book. Riaz Zolfonoon later modified these applications for use with Microsoft RPC Version 2.0. Riaz also pro vided helpful advice on numerous aspects of Microsoft RPC.

Others at Digital who played central roles in developing the book include Jerry Harrow and Will Lees, who provided painstaking reviews of various drafts of sec tions of the book. Jim Teague provided a Microsoft RPC version of the phonebook application which was originally written for another O'Reilly book titled Distribut ing Applications Across DCE and Windows NT. Jim is a co-author of that book. Larry Friedman, Dick Annicchiarico, Michael Blackstock, Rob Philpott, and Andy Ferris provided bits and pieces of technical advice along the way. I also want to

thank Ladan Pooroshani, Beth Benoit, and Brian Shimpf for their cooperation and support.

Credit for logistical support goes to several folks at Digital including Gerry Fisher, Evelyn McKay, Lisa Cozins, and Madeline Cormier, all of whom made sure I had what I needed to get things done.

Several people at Microsoft Corporation also deserve thanks for providing various inputs to the book. These people include Debbie Black, Dave Tanaguchi, and Craig Link (from Microsoft's Win32 SDK forum on CompuServe).

Additional help and support for the DCE version of the book came from Tony Hinxman, Al Simons, David Magid, Margie Showman, Ken Ouellette, Mary Orcutt, Marll McDonald, Mark Heroux, Clem Cole, Marty Port, Ram Sudama, Diane Sher man, Susan Scott, David Strohmeyer, Karol Mclntyre, Wei Hu, Susan Hunziker, Vicki Janicki, Beth Martin, Dan Cobb, Lois Frampton, Steve Miller, Eric Jendrock, Gary Schmitt, Ellen Vliet, Judy Davies, Judy Egan, Collis Jackson, David Kenney, Suzanne Lipsky, Darrell Icenogle, Terry Tvrdik, Howard Mayberry, and John Shirley's wife, Linda McClary.

Joe Scandora was very helpful on the Microsoft version of the book.

Book design and production credits go to lots of the folks at O'Reilly & Associates who artfully turned many pieces of a stark manuscript into a real book. Edie Freedman designed the cover. Jeff Robbins and Chris Reilley created the figures. Kismet McDonough, Eileen Kramer, and Clairemarie Fisher O'Leary did the copy-editing and production management. Kiersten Nauman assisted with the produc tion work. Seth Maislin refined the index.

Finally, I want to thank Frank Willison for giving me the opportunity to work on this book.

Joint Venture

This book was produced as a cooperative effort between Digital Equipment Cor poration and O'Reilly & Associates. While we at O'Reilly & Associates frequently work closely w r ith vendors of hardware and software, this book gave us an oppor tunity for much more extensive cooperation and mutual support than is custom ary. It is a model we like, and we believe the end result testifies to the value of sharing one's resources in this way.

picture0

In this Chapter: A Simple Interfac A Simple Client A Minimal Server Producing the Application Running the Application

Overview of an

RFC Application

A traditional application is a single program running on a single computer system, where a procedure and its caller execute in the same address space. In contrast, the client-server model for distributed applications embodies a client program and a server program, usually running on different systems of a network. The client makes a request to the server, which is usually a continuously running daemon process, and the server sends a response back to the client (see Figure 1-1).

The remote procedure call (RFC) mechanism is the simplest way to implement client-server applications, because it keeps the details of network communications out of your application code. The idea is that each side behaves, as much as possi ble, the way it would within a traditional application: the programmer on the client side issues a call, and the programmer on the server side writes a procedure to carry out the desired function. To convey the illusion that you are working in a single address space, some hidden code has to handle all the networking. Many related issues are also involved, such as converting data between formats for dif ferent systems, and detecting communication errors.

Figure 1-2 shows the relationship between your application code and the RFC mechanism during a remote procedure call. In client application code, a remote procedure call looks like a local procedure call, because it is actually a call to a client stub. (A stub is surrogate code that supports remote procedure calls. Later in this chapter we'll discuss how stubs are created and what they do.) The client stub communicates with the server stub using the RFC runtime library, which is a set of standard runtime routines that supports all Microsoft RFC applications.

The server's RFC runtime library receives the remote procedure call and hands the client information to the server stub. The server stub invokes the remote proce dure in the server application.

Microsoft RFC Programming Guide

picture1

Request to server

Network

Server System

Server

picture2

Response from server

picture3

Figu re 1-1. Client-server model

When the server finishes executing the remote procedure, its stub communicates output to the client stub, again by using the RFC runtime library. Finally, the client stub returns to the client application code.

Figure 1-3 shows the three phases required to develop a distributed application. An essential part of the RFC mechanism is an interface, which is a set of remote procedure declarations. Given the same interface, client and server development of an application can occur in parallel and on separate systems of the network.

In this chapter we will create an entire RFC application from scratch. Naturally, we'll use every shortcut and simplification the system offers to accomplish this feat. When you are done with the chapter, you will know the place of all the major RFC features, and how an application is developed.

You may not need to develop an entire application as shown in this chapter. If the interface and server already exist, your development may require only the client.

Chapter 1: Overview of an RFC Application

Figure 1-2. RFC mechanism

picture4

Figure 1~3- Application development

The arithmetic example in this chapter demonstrates a very simple one-client/one-server RFC application. Suppose a remote server system uses special hardware, such as an array processor. In our example, the client performs an arithmetic oper ation on arrays by calling a remote procedure that uses the array processor. The remote procedure executes on the server system, taking two arrays as arguments and adding together the elements of the arrays. The remote procedure returns the results to the client in a third array argument. Finally, the results of the remote pro cedure are displayed on the client system.

Microsoft RFC Programming Guide

The arithmetic example is deliberately limited to demonstrate the basics of a dis tributed application implemented with RFC. We describe each portion of the appli cation in this chapter, and Appendix C shows the complete code. The Preface tells you how to obtain source code online for this and other examples in the book.

A Simple Interface

When writing a local application, should you start by deciding exactly what func tions you'll call and what arguments they take? Well, if you were dividing the work among multiple programmers and needed to clarify the interfaces between their work, you probably would proceed that way. The same reasoning applies to a dis tributed program: the client and server are being developed separately. Since the boundary or interface between them is the procedure call itself, you have to spec ify its attributes at the start.

So an interface consists of what the client and the server have to agree on; it con tains some identifying information and a few facts about the remote procedures. Each procedure declaration includes the name of the procedure, the data type of the value it returns (if any), and the order and data types of its parameters (if any). An interface definition contains a set of procedure declarations and data types.

Just as programmers select functions from libraries, client application writers use interface definitions to determine how to call remote procedures. Server applica tion writers use interface definitions to determine the data type of the remote pro cedure's return value, and the number, order, and data types of the arguments. The interface definition is like a design document that ties the client and server application code together. It is a formal definition describing the set of procedures offered by the interface.

You write the interface definition in Microsoft Interface Definition Language

(MIDL). The MIDL closely resembles the declaration syntax and semantics of C, with the addition of attributes that allow information to be sent over a network.

You may think that we have introduced an unnecessary level of complexity here, but you will see that keeping the salient features of a distributed application in one file—the interface definition—makes it easier to scale up development to mul tiple servers and many clients for those servers.

Figure 1-4 shows the utilities used and the files produced when developing the arithmetic interface. The uuidgen utility generates a universal unique identifier (UUID) used in the interface definition to distinguish this interface from any other interface on the network. You use a text editor to write the rest of the interface definition, arith.idl. When the interface definition is complete, compile it with the MIDL compiler (midt) to generate stubs and a C header file that you use to develop the client and server programs.

Chapter 1: Overview of an RFC Application

Generate a universal unique identifier.

Write an interface definition.

uuidgen

Text Editor

arith.idl

picture5

Compile the interface definition to generate the application header and stub files.

f midl

picture6

1

arith.h

J

picture7

Figure 1-4. Arithmetic application: interface development

Universal Unique Identifiers

When you write a new interface, you must first generate a UUID with uuidgen. A UUID is simply a number that the uuidgen utility generates using time and net work address information so that no matter when or where it is generated, it is guaranteed to be unique. A UUID is like a fingerprint that uniquely identifies some thing—such as an interface—across all network configurations.

An interface UUID is an excellent example of how you tie a client and server together through the MIDL file. When a client makes a remote procedure call, its UUID has to match that of the server. The RFC runtime library performs this check; this way you don't get unexpected results.

Microsoft RFC Programming Guide

Generating a UUID in an interface definition template

To generate and display a UUID in a template for an interface definition, type the following command:

C:\> uuidgen -i [

uuid(6AF85260-A3A4-10lA-BlAE-08002B2E5B76), version (1.0) ] interface USTTERFACENAME

In this example, the output appears at the terminal, but generally you save it in a file with the extension .idl. Replace the template name INTERFACENAME with a name you choose for the new interface. In the next section, we use a template like this to develop the arithmetic interface definition.

The Interface Definition

Now we are ready to write an interface definition. Here we put data type defini tions and procedure declarations that need to be shared between server and client. Later, the MIDI compiler creates the header file and stubs from the interface defini tion, for use in your application.

The interface definition includes syntax elements called attributes, which specify features needed for distributed applications. Attributes convey information about the whole interface or items in the interface, including data types, arrays, pointers, structure members, union cases, procedures, and procedure parameters. For exam ple, the in attribute specifies an input parameter for a remote procedure. You can pick out attributes in the file because they're enclosed in square brackets.

Example 1-1 shows a simple interface definition. The text consists of a header and body. The header contains a uuid attribute and the name assigned to the interface. The body specifies all procedures for the interface; it contains the procedure dec larations with their data types and constants. There is only one procedure declared in our example. It adds two input arrays and returns the results in a third array.

Example 1-1: A Simple Interface Definition

I* FILE NAME: arith.idl */

/* This Interface Definition Language file represents a basic arithmetic */ /* procedure that a remote procedure call application can use. */ [

uuid(6AF85260-A3A4-10lA-BLAE-08002B2E5B76) , /* Universal Unique ID O */ pointer_default(ref) /* default pointer type is reference @ */

]

interface arith /* interface name is arith © */

{

const unsigned short AKRAY_SIZE = 10; /* unsigned integer constant O */

Chapter 1: Overview of an RPC Application

Example 1-1: A Simple Interface Definition (continued)

typedef long long_array [ARRAY_SIZE] ; /* array type of long integers®*/

void sum_arrays ( /* sum_arrays procedure does not return a value © */ [in] long_array a, /* 1st parameter is passed in */

[in] long_array b, /* 2nd parameter is passed in */

[out] long_array c /* 3rd parameter is passed out */

O The uuid attribute specifies the interface UUID. The interface definition header for any distributed application requires a uuid attribute.

© RPC provides three types of pointer, offering varying levels of complexity and overhead. Here, the point er_default attribute specifies reference pointers as the default, because they offer the lowest overhead and are sufficient for our purposes.

© The last part of the interface definition header contains the keyword inter face followed by the name chosen for the interface (arith).

O You can define constants for type definitions and application code. In this example, we define AKRAY_SIZE to set the bounds of arrays.

© You can define data types for use in other type definitions and procedure declarations. In this example, we define a data type that is an array of ten long integers. The indexes of arrays begin at zero, so the index values for this array range from zero to nine.

© The remainder of this interface definition is a procedure declaration. A proce dure of type void does not return a value. The in and out parameter attributes are necessary so the MIDL compiler knows in which direction the data need to be sent over the network.

[in] : A value is passed in to the remote procedure when it is called from the client.

[out] : A value is passed back from the server to the calling procedure on the client when the procedure returns. A parameter with the out directional attribute must be a pointer or array so that the parameter can be passed to the client stub by reference. Note that the MIDL compiler requires more complex pointer types to have [in, out] attributes.

Stub and Header Generation Using the MIDL Compiler

When the interface definition is complete, you compile it with the MIDL compiler, which creates the following:

• AC language header file that contains definitions needed by the stubs and your application code. You can now include the header file in client and server application code.

Microsoft RFC Programming Guide

• A client stub file, which you will link with the client portion of the applica tion. During a remote procedure call, the client stub code is intermediate between your client application code and the RFC runtime library.

• A server stub file, which you will link with the server portion of the applica tion. During a remote procedure call, the server stub code is intermediate between your server application code and the RFC runtime library.

• Client and server auxiliary stub files linked with the client and server portions of the application. The auxiliary stub files convert complex data structures like pointers to and from a data stream suitable for transmission over the network.

When you invoke the MIDL compiler, it generates the header file and intermediate C language stub files. Although we show a midl command by itself here, we rec ommend that you use a tool like nmake and a makefile to automate your entire build procedure. Such tools can hide differences between different hardware plat forms making your code more portable. They can also relieve you from the drudgery of typing in long command strings over and over. Later, we'll show a makefile for use in building client and server applications.

To invoke the MIDL compiler and create the header and stub files for the arith metic interface, type the following:

C:\> midl arith.idl

In this example, we generate the header file and the C language stub files of the client and server in one operation. The MIDL compiler produces auxiliary stub files by default, but you may suppress their generation by using appropriate MIDL com piler options.

If you develop the client and server on different systems, copies of the interface definition and the MIDL compiler must reside on both the client and server sys tems. To generate code correctly for different kinds of systems, compile the inter face definition for the client stub on the client system, and for the server stub on the server system.

A Simple Client

We'll start our coding with the client, because it's so simple. In fact, you will not be able to detect any difference between our client and a traditional, single-system program! That's one of the beauties about Microsoft RFC—it hides most of the net working complexity from the client developer.

To develop a client, you must be able to read and interpret the interface definition. To use all the capabilities of RFC, you must also know the RFC runtime routines. The client in our simple example, however, requires no RFC runtime routines.

Figure 1-5 shows the files and utilities needed to produce a client. You write the client application code ( client.c) in C. Currently, Microsoft RFC provides libraries only for C. Remote procedure calls in a client look like local procedure calls. (The

Chapter 1: Overview of an RFC Application

server portion of the application implements the remote procedures themselves.) You must include the header file (arith.h) produced by the MIDI compiler, so that its type and constant definitions are available.

Write the client application file.

Text Editor

Include the header file produced by interface compilation.

Generate the client application object file and client stub object file.

midl

Create the executable client by linking the client application and stub object files with the Microsoft RFC library.

picture8

Linker

client

J

Figure 1-5. Arithmetic application: client development

After compiling client.c and arith_c.c with the C compiler, you can create the exe cutable client by linking the client stub (arith_c.o) with the client object file and the Microsoft RFC library. Example 1-2 shows a simple client.

Example 1-2: A Simple Client

I* FILE NAME: client.c */

/* This is the client module of the arithmetic example. */

#include <stdio.h>

#include <stdlib.h>

#include "arith.h" /* header file created by MIDL compiler O */

Microsoft RFC Programming Guide

Example 1-2: A Simple Client (continued)

long_array a ={100,200,345,23,67,65,0,0,0,0}; long_array b ={4,0,2,3,1,7,5,9,6,8};

main () {

long_array result;

int i;

sum_arrays (a, b, result) ; /* A Remote Procedure Call © */

puts ( "sums: ") ;

forfi =0; i < ARRAY_SIZE; i++) printf ( "%ld\n" , result [i] ) ;

/*** mi dl_user_al locate / midl_user_free ***/

void * RPC.__API

midl_user_al locate /* Procedures called by the stubs © */

size_t size;

{

unsigned char * ptr; ptr = malloc( size ); return ( (void *)ptr )

void RPC API midl_user_free (

obj ect )

void * object; {

free (object); }

O The client code includes the header file produced by the MIDL compiler.

© The client calls the remote procedure sum_arrays using the two initialized arrays as input. It then displays the elements of the resulting array.

© Two programmer-supplied procedures— midl_user_allocate and midl_user_ free —may be called by client and server stubs for certain memory manage ment functions. Although this simple application does not require these rou tines, they are essential parts of many Microsoft RPC applications. Usually these are just wrapper routines for malloc and free. Chapter 4, Pointers, Arrays, and Memory Usage, contains more information about these proce dures.

Chapter 1: Overview of an RFC Application

The following section shows how to write the server for the arithmetic application.

A Minimal Server

Developing a server requires you to know the interface definition and some RFC runtime routines. You write two distinct portions of code:

• The actual remote procedures — this portion is sometimes called the manager

• Code to initialize the server

You make calls to the RFC runtime routines mainly in the server initialization, which prepares the server to listen for remote procedure calls. For our arithmetic application, server initialization is the only code that requires the use of runtime routines.

Figure 1-6 shows the files and utilities needed to produce a server. You must write the remote procedures (manager.c) and server initialization code (seruer.c) in C. You need the header file (arith.h) produced by the MIDI compiler because it con tains definitions required by the remote procedures and runtime calls.

After compiling the server application with the C compiler, you create the exe cutable server by linking the server stub (arith_s.o) with the server application object files and the Microsoft RFC library.

Remote Procedure Implementation

The programmer who writes a server must develop all procedures that are declared in the interface definition. Refer to the interface definition (aritb.idl) and the header file generated by the MIDI compilation (aritb.h) for the procedure's parameters and data types. Example 1-3 shows the code for the remote procedure of the arithmetic application.

Example 1-3: A Remote Procedure Implementation

I* FILE NAME: procedure. c */

/* Implementation of procedure defined in the arithmetic interface. */

ttinclude <stdio.h>

#include "arith.h" /* header file produced by MIDL compiler O */

void sum_arrays(a, b, c) /* implementation of sum_arrays procedure €) */ long_array a; long_array b; long_array c; { int i;

for(i =0; i < ARRAY_SIZE; i++)

c[i] = a[i] + b[i]; /* array elements are each added together © */

Microsoft RFC Programming Guide

Write server application files containing initialization code and remote procedures.

Include the header file produced by interface compilation.

Text Editor

Generate the server application object files and client stub object files.

Create the executable server file by linking the server application and stub object files with the Microsoft RFC library.

midl

picture9

Figure 1-6. Arithmetic application: server development

O The server code includes the header file produced by the MIDL compiler.

© The procedure definition matches its corresponding declaration in the inter face definition.

€) The procedure implementation is completed.

So far, the client and server application code has been much like any other appli cation. In fact, you can compile and link the client and remote procedures, and run the resulting program as a local test.

Chapter 1: Overview of an RFC Application

13

Before going on to write the server initialization code, we found it useful to dis cuss how the arithmetic application works in a distributed environment. This is the subject of the next section.

A Distributed Application Environment

When a client makes a remote procedure call, a binding relationship is established with a server (see Figure 1-7). Binding information is network communication and location information for a particular server. Conveniently, in the arithmetic applica tion, the client stub and the RFC runtime library automatically find the server for you during the remote procedure call. Figure 1-8 illustrates how binding informa tion acts like a set of keys to a series of gates in the path a remote procedure call takes toward execution.

Client

Binding

picture10

Figure 1-7. Binding

protocol *? server *? *? endpoint sequence * host k k

picture11

Binding

picture12

Figure 1-8. Binding information

Binding information includes the following:

1. Protocol Sequence

A protocol sequence is an RFC-specific name containing a combination of communication protocols that describe the network communication used between a client and server. For example, ncacn_ip_tcp represents the pro tocol sequence for a Network Computing Architecture connection-oriented protocol, over a network with the Internet Protocol and the Transmission Control Protocol for transport.

2. Server Host

The client needs to identify the server system. The server host is the name or network address of the host on which the server resides.

3. Endpoint

The client needs to identify a server process on the server host. An endpoint is a number representing a specific server process running on a system.

To help clients find servers in a flexible and portable manner, Microsoft RFC pro vides a name service to store binding information. Name service is a general term for a database service that stores information for distributed applications—that is, a service that offers the same information to applications running on different sys tems. Using the name service, a server can store binding information that a client on another system can retrieve later. The particular name service offered with Microsoft RFC is called the Locator.

The RFC runtime library contains a general set of functions called name service independent (NSI) routines. Thus, to store binding information, your server calls an NSI routine. This routine internally communicates with the Locator to put infor mation into the database. NSI routines are a level of abstraction above the particu lar name service on a system, and thus can be used to access whatever name service your system uses. For instance, if you shared a network with DCE systems, you could configure your Microsoft RFC system to use the DCE Cell Directory Ser vice (CDS).

Distributed applications do not require the name service database, but we recom mend that you use it. Alternatives to using the name service are to manage bind ing information directly in client and server code, or to create your own application-specific method of advertising and searching for servers. These alterna tives present more maintenance problems than if you use the name service rou tines.

Figures 1-9, 1-10, and 1-11 show how the arithmetic application uses binding information, and how the remote procedure call completes.

A server must make certain information available to clients. Figure 1-9 shows the typical steps needed each time a server starts executing. A server first registers the interface with the RFC runtime library, so that clients later know whether they are

Chapter 1: Overview of an RFC Application

15

Client System

Figure 1-9. Server initializing

compatible with the server. The runtime library creates binding information to identify this server process. The server places the binding information in appropri ate databases so that clients can find it. The server places communication and host information in the name service database. The server also places process informa tion (endpoints) in a special database on the server system called the local end-point map, which is a database used to store endpoints for servers running on a given system. In the final initialization step, a server waits while listening for remote procedure calls from clients.

Microsoft RFC Programming Guide

When the server has completed initialization, a client can find it by obtaining its binding information, as illustrated in Figure 1-10. A remote procedure call in the client application code transfers execution to the client stub. The client stub looks up the information in the name service database to find the server system. The RFC runtime library finds the server process endpoint by looking up the information in the server system's endpoint map. The RFC runtime library uses the binding infor mation to complete the binding of the client to the server. Chapter 3, How to Write Clients, discusses variations on how to obtain server binding information.

Server System

picture13

Figure 1-10. Client finding a server

Chapter 1: Overview of an RFC Application

17

As shown in Figure 1-11, the remote procedure executes after the client finds the server. The client stub puts arguments and other calling information into an inter nal RFC format that the runtime library transmits over the network. The server run time library receives the data and transfers it to the stub, which converts it back to a format the application can use. When the remote procedure completes, the con version process is reversed. The server stub puts the return arguments into the internal RFC format, and the server runtime library transmits the data back to the client over the network. The client runtime library receives the data and gives it to the client stub, which converts the data back for use by the application.

Domain Controller

Microsoft Locator

Figure 1-11. Completing a remote procedure call

Microsoft RFC Programming Guide

Server Initialization

As illustrated in Figure 1-9, a server must make certain information available to the RFC runtime library and clients before it can accept remote procedure calls. Exam ple 1-4 contains the server initialization code for the arithmetic application, illus trating the sequence of steps to initialize a typical RFC server.

Example 1-4: A Minimal Server Initialization

/* FILE NAME: server.c */ Mnclude <stdio.h> ttinclude "arith.h" # include "status.h"

main ()

/* header created by the MIDL compiler */ /* header with the CHECK_STATUS macro */

unsigned long status; rpc_binding_vector_t *binding_vector; unsigned char *entry_name;

/* error status */

/*set of binding handles */

/*entry name for name service */

status = RpcServerRegisterlf (

arith_vl_0_s_i f spec ,

NULL,

NULL

/* error status */

/* register interface with the RFC runtime O */ /* interface specification (arith.h) */

CHECK_STATUS( status, "Can't register interface", ABORT);

status = RpcServerUseAllProtseqs(

RPC_C_PROTSEO_MAX_REQS_DEFAULT,

NULL

/* create binding information © */

/* queue size for calls */

/* no security descriptor is used */

CHECK_STATUS(status, "Can't create binding information", ABORT);

status =

RpcServerlnqBindings ( /* obtain this server's binding information©*/ &binding_vector

CHECK_STATUS(status, "Can't get binding information", ABORT);

entry_name = (unsigned char *)getenv("ARITHMETIC_SERVER_ENTRY") ,-

status =

RpcNsBindingExport( /* export entry to name service database O */

RPC_C_NS_SYNTAX_DEFAULT, entry_name, arith_vl_0_s_i f spec, binding_vector, NULL

/* syntax of the entry name /* entry name for name service /* interface specification (arith.h) /* the set of server binding handles

CHECK_STATUS(status, "Can't export to name service database", ABORT);

status = RpcEpRegister(

arith_vl_0_s_if spec,

/* register endpoints in local endpoint map© */ /* interface specification (arith.h) */

Example 1-4: A Minimal Server Initialization (continued)

binding_vector, /* the set of server binding handles */

NULL,

NULL ); CHECK_STATUS(status, "Can't add address to the endpoint map", ABORT);

status =

RpcBindingVectorFree( /* free set of server binding handles© */

&binding_vector ); CHECK_STATUS(status, "Can't free binding handles and vector", ABORT);

puts("Listening for remote procedure calls.. .") ;

status =

RpcServerListen( /* listen for remote calls © */

1, /* minimum number of threads */

RPC_C_LISTEN_MAX_CALLS_DEFAULT, /* concurrent calls to server */ NULL /* continue listening until explicitly stopped */

);

CHECK_STATUS(status, "rpc listen failed", ABORT); }

/*** midl_user_allocate / midl_user_free ***/

void * _RPC_API

midl_user_allocate /* Procedures called by the stubs © */

size_t size; {

unsigned char * ptr; ptr = malloc ( size ) ; return ( (void *)ptr ) ;

void __RPC_API midl_user_free (

object )

void * object; {

free (object) ; }

O Register the interface. Register the interface with the RPC runtime library using the RpcServerRegisterlf routine. The arith_vl_0_s_ifspec variable is called an interface handle. It is produced by the MIDL compiler and refers to infor mation that applications need, such as the UUID. We describe the NULL argu ments in Chapter 5, How to Write a Server.

The CHECK_STATUS macro is defined in the status.h header file for the appli cations in this book. It is used to interpret status codes from runtime calls. (See Example 3-12 in Chapter 3.) Figure 1-9, step 1 is now complete.

@ Create binding information. To create binding information, you must choose one or more network protocol sequences. This application, like most, calls RpcServerUseAllProtseqs so that clients can use all available protocols. During this call, the RFC runtime library gathers together information about available protocols, your host, and endpoints to create binding information. The system allocates a buffer for each endpoint, to hold incoming call information. Microsoft RFC sets the buffer size when you use the RPC_C_PROTSEQ_ MAX_CALLS_DEFAULT argument.

@ Obtain the binding information. When creating binding information, the RFC runtime library stores binding information for each protocol sequence. A bind ing handle is a reference in application code to the information for one possi ble binding. A set of server binding handles is called a binding vector. You must obtain this information through the RpcServerlnqBindings routine in order to pass the information to other runtime routines. Figure 1-9, step 2 is now complete.

O Advertise the server location in the name service database. In this example, the server places (exports) all its binding information in the name service database using the RpcNsBindingExport runtime routine.

The RPC_C_NS_SYNTAX_DEFAULT argument tells the routine how to interpret an entry name. (The current version of Microsoft RFC has only one syntax.) The entry_name is a string obtained in this example from an environment variable set by the user specifically for this application, ARITHMETIC_SERVER_ENTRY (discussed at the end of this chapter when the application is run). The inter face handle, arith_Server If HANDLE, associates interface information with the entry name in the name service database. The client later uses name ser vice routines to obtain binding information by comparing the interface infor mation in the name service database with information about its own interface. Figure 1-9, step 3 is now complete.

© Register the endpoints in the local endpoint map. The RFC runtime library assigns endpoints to the server as part of creating binding information. The RpcEpRegister runtime routine lets the endpoint map on the local host know that the process running at these endpoints is associated with this interface. Figure 1-9, step 4 is now complete.

© Free the set of binding handles. Memory for the binding handles was allocated with a call to the RpcServerlnqBindings routine. When you have finished passing binding information to the other routines, release the memory using the RpcBindingVectorFree routine.

O Listen for remote calls. Finally, the server must wait for calls to arrive. Each system has a default for the maximum number of calls that a server can accept at one time. Microsoft RFC sets this maximum default number when you use the RPC_C_LISTEN_MAX_CALLS_DEFAULT argument. Figure 1-9, step 5 is now complete.

© Two programmer-supplied procedures— midl_user_allocate and midl_user_ free —may be called by client and server stubs for certain memory manage ment functions. Although this simple application does not require these rou tines, they are essential parts of many Microsoft RFC applications. Usually these are just wrapper routines for malloc and free. Chapter 4 contains more information about these procedures.

All of the server code is now complete. The compilation of the application is shown in the next section.

Producing the Application

So far we have written the interface definition, produced the stubs and header file from the interface definition with the MIDL compiler, and written the client and server portions of the application. To produce the application, compile and link the client and server separately, each on the system where you want its executable to run.

Microsoft RFC Libraries

Microsoft RFC-distributed applications must be linked with the Microsoft RFC libraries, which may vary depending on your system and vendor. This book uses the following libraries for a link on a Microsoft Windows NT system:

rpcrt4.lib rpcns4.lib libont.lib kerne!32.1ib

The rpcrt4.lib library provides Windows runtime library functions. The rpcns4.lib library provides-name service functions. The libcmt.lib library provides standard C library functions. The kernel32.lib library provides threads functions.

The following sections assume that your client and server files are available to the respective client and server systems.

Compile and Link the Client and Server Code

Recall that Figures 1-5 and 1-6 show the utilities used and files produced when developing a client and a server. Here, we show a portion of a makefile we use

with nmake to compile and link the client and server code. The order in which these commands execute is:

O A midl command builds .c and .h files from the j'd/file. © The compiler generates object files for the client and server. @ The linker produces client and server executables. Example 1-5: A Makefile for Building a Client and Server

# FILE NAME: Makefile

# Makefile for the arithmetic application #

# definitions for this makefile #

APPL=arith

NTRPCLIBS=rpcrt4.lib rpcns4.1ib libcmt.lib keme!32.1ib

# Include Windows NT macros # 0 !include <ntwin32.mak>

# NT c flags

cflags = -c -WO -Gz -D_X86_=1 -DWIN32 -DMT /nologo # @

# NT nmake inference rules

•c.obj: # ©

$(cc) $(cdebug) $(cflags) $(cvarsmt) $< $(cvtomf)

#

# COMPLETE BUILD of the application #

all: client.exe server.exe # Q

#

# CLIENT BUILD #

client: client.exe

client.exe: client.obj $(APPL)_c.obj $(APPL)_x.obj # ©

$(link) $(linkdebug) $(conflags) -out:client.exe -map:client.map \

client.obj $ (APPL)_c.obj $(APPL)_x.obj \

$(NTRPCLIBS)

#

# SERVER BUILD #

server: server.exe

server.exe: server.obj manager.obj $(APPL)_s.obj $(APPL)_x.obj # © $(link) $(linkdebug) $(conflags) -out.-server.exe -map:server.map \

server.obj manager.obj $(APPL)_s.obj $(APPL)_x.obj\

$(NTRPCLIBS)

# client and server sources # Q client.obj: client.c $(APPL).h

manager.obj: manager.c $(APPL).h server.obj: server.c $(APPL).h

Example 1-5: A Makefile for Building a Client and Server (continued)

# client and server stubs # © $(APPL)_c.obj: $(APPL)_c.c

$(APPL)_x.obj: $(APPL)_x.c $(APPL)_s.obj : $(APPL)_s.C

# generate stubs, auxiliary and header file from the MIDL file # © $(APPL).h $(APPL)_c.c $(APPL)_x.c : $(APPL).idl

midl $(APPL).idl

O ntwin32.mak contains machine specific-variables for portability.

© This line defines compiler options.

© The inference rules assign values to nmake options and flags.

O This line builds client and server executables.

© Link the client object files with the runtime libraries defined by S(NTRPCLIBS) to produce the executable client application.

© Link the server object files with the runtime libraries defined by S(NTRPCLIBS) to produce the executable server application.

© Compile the client and server application C source files to produce applica tion object files. The server sources include both the remote procedure imple mentation and the server initialization, to create the server object files.

© Compile the client and server C language stub files to produce stub object files.

© Use the midl compiler to produce the client and server stub files and the header file.

Running the Application

We designed the arithmetic application for simplicity. One of our short-cuts was to let the client automatically find the server by using the name service to retrieve server binding information. The client stub obtains the binding information exported by the server to the name service database, and the client RFC runtime library completes the remote procedure call.

To run the distributed arithmetic application, follow these steps:

1. This server exports binding information to a name service database. Make sure a Microsoft Locator is running in your Windows NT domain.

2. Execute the server. For this example, the application-specific environment variable, ARITHMETIC_SERVER_ENTRY, is set prior to running the server. This variable represents a name for the entry that this server uses when exporting the binding information to the name service database. The usual convention for entry names is to concatenate the interface and host names. We use an

environment variable here because the name can vary depending on which host you use to invoke the server. If you do not supply a valid name, the binding information will not be placed in the name service database, and the program will fail. The prefix /.:/ (or alternatively / . . . /, represents the global portion of a name and is used for compatibility with OSF DCE naming conventions. For this example, assume that the server resides on the system moxie.

C:\SERVER> set ARITHMETIC_SERVER_ENrRY=/.:/arithmetic_moxie C:\SERVER> server

3. After the server is running, execute the client on the client system:

C:\CLIENT> client

sums:

104

200

347

26

68

72

5

9

6

8

4. The server is still running and, for now, should be terminated by typing "C (Ctrl-C). In Chapter 5 we'll show a way to gracefully terminate your server so that it removes its endpoint information from the local endpoint map.

Figure 1-12 summarizes the development of the arithmetic application.

Chapter 1: Overview of an RFC Application

25

uuidgen

picture14

Linker

Linker

Figure 1-12. Arithmetic application: complete development

picture15

In this Chapter:

• Microsoft Interface Definition Language (MIDI)

• Using the MIDI Compiler

• Using an ACF to Customize Interface

Usa s e Using a Microsoft

RFC Interface

As we discussed in Chapter 1, Overview of an RFC Application, the first step in cre ating a distributed application is to write an interface definition. This is also known as an IDL or MIDI file because it is written in the Microsoft Interface Definition Language and ends in the suffix .idl. This file contains definitions that the client and server share, and a list of all the procedures offered by the server. This chap ter explains what interface definitions need to contain.

An interface definition is usually written by the person developing the server because it describes the procedures offered by that server. Client developers need to read and interpret the definition. All servers that support the interface must implement the remote procedures using the same data types and parameters. All clients must call the remote procedures consistently.

A procedure declaration in an interface definition specifies the procedure name, the data type of the value it returns (if any), and the number, order, and data types of its parameters (if any).

Interface definitions are compiled with the MIDI compiler (midl) to create the header and stub files. Use the header file with your application C code, and link the stub files with your application object code and the RFC runtime library to cre ate a distributed application. If you make a mistake when writing an interface defi nition, the MIDL compiler gives useful messages to help you correct what is wrong.

Microsoft RFC Programming Guide

Microsoft Interface Definition Language (MIDI)

Use the Microsoft Interface Definition Language (MIDL) to define the necessary data types and declare the remote procedures for an interface. Declarations in MIDL are similar to declarations in C,* with the addition of attributes.

Attributes

Interface definition attributes are special keywords that offer information to help distribute an application. They are enclosed in square brackets in the MIDL file. All of them facilitate network use in one way or another:

• Some attributes distinguish one interface from another on a network. They guarantee that a client finds the servers that implement the proper remote procedures. For example, the uuid attribute declares the UUID for the inter face.

• Some attributes explicitly describe data transmitted over a network. Some aspects of data in C that you take for granted must be described explicitly for a distributed application. For example, a union is a data structure that allows different data types in the same area of memory. Your application uses another variable to keep track of which data type is valid. In a distributed program, this additional variable must be specified in MIDL so it is transmitted with a union parameter.

• Some attributes make data transmission more efficient. In a local application, procedures have access to both parameters and global variables so that any amount of data can be accessed efficiently. In a distributed application, all data used by the client and the remote procedure must be passed as parame ters and transmitted over the network. Since most parameters are passed in only one direction, you use attributes to specify whether each parameter is used for input, output, or both.

Tables A-l through A-8 in Appendix A, MIDL and ACF Attributes Quick Reference, show all MIDL attributes with brief descriptions of each. In this chapter, we discuss the MIDL attributes so you know how to write an interface definition. But to really understand how those attributes reflect your use of data in an application, you have to see them along with the application's C code — and that will appear in later chapters.

* MIDL is currently designed to work with C. However, MIDL has features such as boolean and byte data types, so that it will work in future versions for languages other than C.

Structure of an Interface Definition

An interface definition includes some or all of the following:

• The interface header

Interface header attributes Interface name

• The interface body

Import statements Constant definitions Data type definitions Procedure declarations

Interface Header Attributes

Interface header attributes specify 7 RFC features that apply to an entire interface. One is the name that you have chosen, such as arith in the application shown in Chapter 1. But choosing a name is not enough, because someone could easily cre ate another application called arith, and a client would be confused about which to use. That is where the interface UUID and the version number come in.

As we saw in Chapter 1, you generate a UUID through uuidgen. This distinguishes your arith even when someone else steals your name to create a different inter face. But the creators of DCE and Microsoft RFC recognized that an interface does not stay the same forever; you are likely to update it regularly. So they also allow for a version number in the interface header. A complete version number consists of a major and minor version number. For example, if a version number is 2.1, the major version is 2 and the minor version is 1.

During a remote procedure call, the following rules determine whether a client can use an interface that a server supports:

• The UUID of the client and server must match.

• The major version number of the client and server must match.

• The minor version number for the client must be less than or equal to the minor version number for the server. A client minor version number that is less than the server minor version number indicates an upwardly compatible change to the interface on the server.

When you create new versions of an interface by adding new declarations and definitions, increase the minor version number. Any other changes to an interface require a major version number change, essentially creating a different interface.

The Inventory Application

The application we use in this chapter is a simple inventory: a product database is stored on the server system, and a client makes inquiries based on a part number. The complete application is shown in Appendix D, The Inventory Application.

Example 2-1 shows the header in the interface definition of the inventory applica tion.

Example 2-1. Interface Header Attributes

I* FILE NAME: inv.idl */

[ /* brackets enclose attributes O */

uuid(008B3C84-93A5-HC9-85BO-08002B147A61) ,/* universal unique identifier©*/ version(1.0), /* version of this interface©*/

pointer_default(unique) /* pointer default O */

] interface inventory /* interface name © */

{

/* The body of an interface definition consists of iitport statements, */

/* constant definitions, data type definitions, and procedure declarations. */

O Brackets enclose attributes in interface definitions.

© The uuid is a required attribute that uniquely identifies an interface. All copies of this interface definition contain the same UUID.

© The version is an optional attribute used to identify different versions of an interface. In this example the major version number is 1 and the minor ver sion number is 0.

O The pointer_def ault is an optional attribute needed by some interface defi nitions so that pointer data is efficiently transmitted.

© The keyword interface and a name are required to identify the interface. The MIDI compiler uses this name to construct data structure names. Client and server code use these data structures to access information about the interface.

Table A-l in Appendix A lists and describes all interface header attributes.

Type Definitions, Data Attributes, and Constants

In C, a data type can map to different sizes on different systems. For example, a long data type in C may be 16, 32, or 64 bits, depending on the system. The size of a MIDI data type, however, must be the same on all systems so that Microsoft applications can exchange data. Consequently, you might need to change data types if you port your application code platforms with differing data type sizes.

7able2-l WLi Basic Data Types

MIDL Data Type - zc

bc/slear - - :

byte 8 bits

zhar void

"."1. i "i * '

e -' - r _ -. -. - -_ :=_- 32 bit*

.'. '-_-;.-•-

ssall

r- ~.v Fktttir^Pomt

Ooat 32 bits

:ic-J:le - • :

ImernatkiruJ Characters

;

..-- -.-_-_ - - .;.. . >

MIDL_T>pe Notes

byte Data is not automatics - "

i"_i: -

Table 2-2: Notes on MIDI Data Types (continued) MIDL_Type Notes

void * Used with the context_handle attribute to define context

handles. It refers to opaque data, the details of which are hid den from you. See Chapter 7, Context Handles.

handie_t Data that denotes a binding handle. Chapter 3, How to Write

Clients, describes how to use this data type to define binding handles in an interface definition.

error_status_t Data that denotes an RFC communication status.

wchar_t 16-bit unsigned data element.

How do the MIDL data types help to distribute an application? The explanation lies in how the client and server stubs handle data that might need to change as it moves from one computer system to another.

During a remote procedure call, the client stub prepares input parameters for transmission, and the server stub converts the data for use by the server applica tion. When the remote procedure completes execution on the server system, the server stub prepares the output parameters for transmission and the client stub converts the data for the client application.

Marshalling is the process during a remote procedure call that prepares data for transmission across the network. Marshalling converts data into a byte-stream for mat and packages it for transmission using a Network Data Representation (NDR). NDR allows successful data sharing between systems with different data formats. It handles differences like big-endian versus little-endian (byte order), ASCII charac ters versus EBCDIC characters, and other incompatibilities.

Data transmitted across the network undergoes a process called unmarshalling. If the data format of sender and receiver is different, the receiver's stub converts the data to the correct format for that system, and passes the data to the application.

Example 2-2 shows a constant and two type definitions for the inventory interface.

Example 2-2: MIDL Type Definitions

[

/* The header of an interface definition consists of interface header */

/* attributes and the name of the interface. */

] interface inventory {

const long MAX_STRING =30; /* constant for string size O */

typedef long part_num; /* inventory part number © */

typedef [string] char part_name[MAX_STRING+l] ; /* name of part©*/

Example 2-2: MIDI Type Definitions (continued)

/* The remainder of the interface definition consists of other data */ /* type definitions and the procedure declarations. */

}

O Use the keyword const followed by a data type to declare a constant to use in type definitions and application code.

@ Use the keyword typedef followed by a data type to define a new data type.

© A data type is not sufficient to completely describe some kinds of data. Attributes provide the necessary extra information. In this example, the string attribute enclosed in brackets applies to the character array part_name, so that it becomes a null-terminated string.

Table A-4, in Appendix A, lists and describes all the data type attributes. So far we have seen only basic MIDI data types. Now we will explain how to construct more complex data types in an interface definition.

Pointers

In a distributed application, a pointer does not provide the same convenience and efficiency that it does in a local application because there is stub overhead such as memory allocation, copying, and transmitting all the data the pointer refers to. MIDL contains three kinds of pointers to balance efficiency with more complete pointer capabilities.

A full pointer has all of the capabilities associated with pointers. They can be null or point to existing data. They can contain cycles or loops and they can be aliased to another pointer in the argument list. The full pointer attribute is the default pointer type. You can override this setting by using the pointer_default attribute.

A unique pointer can be null or point to existing data. But unique pointers cannot contain cycles or loops and they cannot be aliased to another pointer in the argu ment list. In Microsoft Extension mode, the unique pointer attribute is the default pointer type assigned to pointers that are not parameters. You can override this setting using the pointer_default attribute.

A reference pointer is a simpler pointer that refers to existing data. A reference pointer has a performance advantage, but limited capabilities compared to a unique pointer. No new memory can be allocated for the client during the remote procedure call, so memory for the data must exist in the client before the call is made.

The unique attribute represents a unique pointer and the ref attribute represents a reference pointer. Chapter 4, Pointers, Arrays, and Memory Usage, discusses how to use pointers.

Microsoft RFC Programming Guide

Arrays

Array index values begin at 0 in MIDI, as in C. For example, the array arr[10] defined in an interface definition has elements arr[0] , arr[l] , . . . , arr[9] when you use it in the client or server code.

Arrays are expensive to transmit, so MIDI provides some sophisticated ways to keep down the amount of data actually sent over the network. Here are the kinds of arrays provided:

fixed array A fixed array has constant index values for its dimensions.

This is like a standard C array.

varying array A varying array has a maximum size determined at compile

time, just like a fixed array. But it also has subset bounds represented by variables. Only the portion of the array you need is transmitted in a remote procedure call.

conformant array The size of a conformant array is represented by a dimen

sion variable so that the actual size is determined when the application is running.

Chapter 4 discusses arrays in more detail.

Strings

In C code it is convenient to use strings to manipulate character data. C library routines, such as strcpy, recognize a null character as the end of a string in the character array. In MIDL, all characters in an array are transmitted, including null characters. Therefore, you must explicitly define strings with the string attribute, so that only the characters up to a null character are transmitted. Example 2-3 shows some string definitions.

Example 2~3: Defining Strings in MIDL

const long MAX_STRING = 30; /* a constant for string size */

typedef [string] char part_name[MAX_STRING+l]; /* name of part O */ typedef [string, unique] char *paragraph; /* description of part ® */

To specify a string, apply the string attribute to a character or byte array. In this example, the string size is 31 in order to accommodate the terminating null byte, but the maximum string length is 30. The data type of the array ele ments must be a char or byte, or defined with a type definition that resolves to a char or byte. The data type can also be a structure whose fields all resolve to a char or byte.

This example specifies a conformant string by applying the string attribute to a pointer to a char or byte data type.

A conformant string has the maximum length allocated in the application code. You can also specify a conformant string using array syntax. For example, the fol lowing is another way to define the conformant string paragraph:

typedef [string] char paragraph^];

When you use a conformant string as an input parameter to a remote procedure, the amount of data that is transmitted is determined from the current string length. If the string parameter is both input and output, however, apply an array attribute size_is or max_is to the string so the length can increase when the remote pro cedure completes. Chapter 4 discusses array attributes in greater detail.

Enumerated types

MIDL provides an enumerated type, just as modern versions of the C language do. The idea is to provide a set of symbolic names to make source code more self-documenting. These names are associated by the compiler to a set of integer val ues, but the values usually have no more significance than to distinguish one name from another. In Example 2-4, the keyword enum, followed by a list of iden tifiers, maps the identifiers to consecutive integers starting with 0. For this exam ple, we use enumeration to specify more than one kind of measurement unit for parts in the inventory. Some parts are counted as whole items, while other parts are measured by weight.

Example 2-4: Defining an Enumerated Type in MIDL

typedef enum {

ITEM, GRAM, KILOGRAM } part_units; /* units of measurement */

Microsoft RFC extensions allow you to attach specific integer values to identifiers in an enumeration. In Example 2-5, flight numbers are attached to specific flights in an air traffic application.

Example 2-5: Attaching Specific Integer Values to Enumerators

typedef enum {

BOS-CHI=716, BOS-DEN=432, BOS-SFO510 /* flight numbers */

} flights;

Structures

You define structures in MIDL the same way you do in C. In Example 2-6 the struct keyword is followed by a list of typed members that define a structure. For this example, two structures are shown. The structure part_price contains a units-of-measurement member and a price-per-unit member. The part_units data type is an enumerated type. The structure part_record represents all the data for a particular part number. As in C, any user-defined types such as part_num must be defined before they are used.

Microsoft RFC Programming Guide

Example 2-6: Defining Structures in MIDI

typedef struct part_price {

part_units units;

double per_unit; } part_price;

/* price of part */

typedef struct part_record {

part_num number;

part_name name ;

paragraph description;

part_price price;

part_quantity quantity;

part_l i st subpart s; } part_record;

/* data for each part */

Discriminated unions

In C a union is a data structure that stores different types and sizes of data in the same area of memory. For example, this union stores a long integer or a double precision floating-point number:

typedef union {

long int number;

double weight; } quantity_t;

To keep track of what type is stored in the union, the application must use a dis criminator variable that is separate from the union data structure. This creates a special requirement for a distributed application. If a remote procedure call includes a union parameter, the remote procedure has no way of knowing which member of the union is valid unless it receives the discriminator along with the union.

In MIDL, a discriminated union includes a discriminator as part of the data struc ture itself, so that the currently valid data type is transmitted with the union. When you define a discriminated union, it looks like a combination of a C union and a switch statement. The switch defines the discriminator, and each case of the switch defines a valid data type and member name for the union.

Example 2-7 shows how to define a discriminated union. Example 2- 7: Defining a Discriminated Union in MIDL

typedef enum {

ITEM, GRAM, KILOGRAM } part_units;

/* units of measurement */

Example 2- 7: Defining a Discriminated Union in MIDI (continued) O © ©

typedef union switch(part_units units) total { /* quantity of part */

case ITEM: long int number;

case GRAM: O

case KILOGRAM: double weight; } part_quantity; ©

O You begin the definition of a discriminated union data type with the key words typedef union.

© Use the keyword switch to specify the data type and name of the discrimina tor variable, units. The data type part_units is a previously defined enu merated type. A discriminator can be Boolean, character, integer, or an enumerated type.

© Define the name of the union, total, prior to listing the union cases.

O Use the keyword case followed by a value to specify the data type and name of each union member. The case value is the same type as the discriminator variable. In this example, a union defines the quantity of a part in an inven tory. Some parts are counted as whole items while other parts are weighed. This union offers a choice between defining the quantity as a long integer or as a double precision floating-point number. The union case GRAM has the same data type and name as the case KILOGRAM.

© The name of the new data type is part_quantity, which you use in applica tion code to allocate a discriminated union variable.

In application code, the discriminated union is a C structure. The MIDI compiler generates a C structure with the discriminator as one member and a C union as another member. Example 2-8 shows the structure in the generated header file for the corresponding discriminated union in Example 2-7.

Example 2-8: A Discriminated Union Generated by the MIDI Compiler

typedef struct { part_units units; union {

/* case(s):.0 */ idl_long_int number; /* case(s): 1, 2 */ idl_long_float weight; } total; } part_quantity;

You must set the union discriminator in the application code to control which union case is valid at any time in the application. Example 2-9 shows how you can use the discriminated union in application code.

Microsoft RFC Programming Guide

Example 2-9: Using a Discriminated Union in Application Code

part_record part; /* structure for all data about a part */ O

result = order_part(part.number" "&(part.quantity), account); © if(result > 0) {

if(part.quantity.units == ITEM) ©

printf("ordered %ld items\n", part.quantity.total.number); O else if(part.quantity.units == GRAM)

printf("ordered %10.2f grams\n", part.quantity.total.weight); else if(part.quantity.units == KILOGRAM)

printf("ordered %10.2f kilos\n", part.quantity.total.weight); }

O In the inventory application the part_quantity discriminated union is a member of the part_record structure shown in Example 2-5.

© The part.quantity structure member is the discriminated union. In this example, you request a quantity of a part to order, and the remote procedure returns the actual quantity ordered.

© The part.quantity.units member is the discriminator for the union.

O The part.quantity.total member is the union, which contains number and weight cases.

If you omit the union name (total in Example 2-7), then the MIDL compiler gen erates the name tagged_union for you. You can access the structure members in application code as follows:

part.quantity.units = ITEM;

part.quantity.tagged_union.number = 1;

Procedure Declarations and Parameter Attributes

At the heart of an interface definition are the procedures that a server offers. The inventory application contains several remote procedures; you can find them in the interface definition in Appendix D.

Each parameter of a remote procedure is declared with its own attributes. The most important ones are the directional attributes in and out.

In the C language parameters of procedure calls are passed by value, which means a copy of each parameter is supplied to the called procedure. The variable passed is an input-only parameter because any manipulation of the procedure's copy of the variable does not alter the original variable. For a variable to be a parameter, a pointer to the variable is passed.

With a remote procedure call, we must be concerned with whether a parameter is input, output, or both. It is more efficient if the RFC runtime library can transmit data only in the relevant direction. The attributes in and out are used in an

Chapter 2: Using a Microsoft RFC Interface

39

interface definition to distinguish data transmission direction for a parameter. All parameters must have at least one directional attribute. An output parameter must be a pointer or an array, as it must be in C.

Complex pointer types must have both directional attributes (in and out). This enables the client and server stubs to coordinate duplication of the unique or full pointer in the server's address space.

Example 2-10 shows procedure declarations and some associated parameter attributes.

Example 2-10: Procedure Declarations and Parameter Attributes

] interface inventory

{

/* The beginning of the interface definition body usually contains */

/* constant and type definitions (and sometimes import declarations).*/

y************************ Procedure Declarations ************************/ boolean is_part_available( /* return true if in inventory O */ [in] part_num number /* input part number */

void whatis_part_name(

[in] part_num number, [in, out] part_name name

/* get part name from inventory © /* input part number */ /* output part name */

paragraph get_part_description( [in] part_num number

/* return a pointer to a string €) */

void whatis_part_price(

[in] part_num number, [out] part_price *price

/* get part price from inventory */

void whatis_part_quantity( /* get part quantity from inventory */ [in] part_num number, [out] part_quantity *quantity

void whatare_subparts(

[in] part_num number, [out] part_list **subparts

/* get list of subpart numbers */ /* structure containing the array O */

/* Order part from inventory with part number, quantity desired, and

/* account number. If inventory does not have enough, output lesser */

/* quantity ordered. Return values: l=ordered OK,

/* -l=invalid part, -2=invalid quantity, -3=invalid account.

Example 2-10: Procedure Declarations and Parameter Attributes (continued)

long order_part( /* order part from inventory, return OK or error code */

[in] part_num number,

[in,out] part_quantity *quantity, /* quantity ordered © */

[in] account_num account ); } /* end of interface definition */

O As in C, a MIDL procedure can return a value. In this example, the is_part_available procedure returns a Boolean value of idl_true if the part number is available in the inventory.

© Procedures defined with the void type do not return a value. Input parame ters have the in directional attribute and output parameters have the out directional attribute. Here, Microsoft RFC is treating this pointer to the array element as a unique pointer because the pointer_default was set to unique (see Example 2-1). MIDL does not allow unique or full pointers to have only the [out] directional attribute because the client and server stubs need to coordinate the establishment of complex pointers in the server address space. Consequently, the directional attribute is set to [in, out]. As in C, arrays and strings are implicitly passed by reference, so the string name does not need a pointer operator.

© Some procedures return a data structure or a pointer to a data structure. In this example, the data type paragraph has been defined in the interface defi nition as a char * type. It is a full pointer to a string representing the descrip tion of the part. This remote procedure allocates new memory on the client side.

O Output parameters require pointers to pointers when new memory is allo cated. Pointers to pointers are discussed in Chapter 4.

© Parameters that are changed by the remote procedure call use both in and out. In this example, a part is ordered with the part number, the quantity, and an account number. If the input quantity units are wrong or the quantity requested is more than the inventory can supply, the remote procedure changes the quantity on output.

Table A-7 in Appendix A shows all parameter attributes and Table A-8 shows all procedure attributes.

Using the MIDL Compiler

The MIDL compiler generates the header and stub files needed to incorporate the interface in a client or server. The input for a MIDL compilation is an interface defi nition file, ending in .idl. Figure 2-1 shows the utilities used and files produced during interface production.

Chapter 2: Using a Microsoft RFC Interface

41

An attribute configuration file (ACF) is an optional file, ending in .acf. It contains information that changes how the MIDI compiler interprets the interface definition. We'll look at the ACF file later in this chapter.

Generate a universal unique identifier.

Write an interface definition and an optional attribute configuration file (ACF).

uuidgen

Text Editor

f

app/.idl

JL

appl.atf

picture16

Compile the interface definition to generate the application header, stub, and auxiliary files.

T

midl

picture17

Figure 2-1. Producing an interface

Depending on which compiler options you use, the MIDL compiler produces the C language client stub, server stub, or both sets of stub files. The stub file names contain the _c suffix for clients and the _s suffix for servers. By default, the MIDL compiler also produces the header file (ending in .h) which will be used by both the client and server:

The MIDL compiler produces auxiliary files automatically when certain features are used. Auxiliary file names contain the _x suffix for clients and the _y suffix for servers.

Auxiliary files contain special routines required for certain complex data types, such as unique pointers, to prepare the data for transmission. You have to link the auxiliary object files with your application when these data types are used. The routines are placed in auxiliary files rather than in the stub, so that you can use the data types in other interface definitions without linking in the entire stub.

Generating Client Files

To generate the interface header file and client stub file for the inventory interface, type the following command:

C:\> invntry> midl inv.idl /server none /I explicit /out explicit

Here is an explanation of the options:

/server none This option suppresses the generation of stub none and auxiliary files for the server.

/I explicit The /I option causes the MIDL compiler to use the additional

directory when it searches for files. For one of the clients of the inventory application an ACF in the explicit directory is needed.

/out explicit This option places the output files in the chosen directory, explicit.

Generating Server Files

To generate the interface header file and server stub file for the inventory inter face, type the following command:

C:\> invntry> midl inventory.idl /client none

Here is an explanation.

/client none This option suppresses the generation of stub and auxiliary files for the client.

Using an ACF to Customize Interface Usage

You can control some aspects of RFC on the client side without affecting the server. The opposite is also true. These aspects should not be in the interface defi nition because we do not want to force them on all clients and servers. A client or server developer can use an optional attribute configuration file (ACF) to modify the way the MIDL compiler creates stubs without changing the way the stubs inter act across the network. This assures that all copies of an interface behave the same when clients and servers interact.

The most significant effect an ACF has on your application code can be the addi tion of parameters to remote procedure calls not declared in the interface defini tion. For example, the explicit_handle attribute adds a binding handle as the first parameter to some or all procedures. Also, the comm_status and fault_status attributes can add status parameters to the end of a procedure's parameter list. See Table A-9 in Appendix A for a complete list of ACF attributes.

If you develop both clients and servers for an interface, you can use different ACFs (or no ACF) for the client and server. Since this can cause differences between the

header files generated for the client and server, it is good development practice to separate the client and server output when using ACFs.

You do not specify an ACF when you compile an interface; instead, the MIDL com piler automatically uses an ACF if one is available in the search directories. The name of an ACF must match the name of the MIDL file it is associated with. The file extension must be .acf.

An ACF is useful for a number of situations: selecting binding methods, controlling errors, excluding procedures, and controlling marshalling.

Selecting a Binding Method

As will be explained in Chapter 3, three different binding methods exist. You can choose how much to let the stub do for you and how much to control binding within your own code.

The auto_handle ACF attribute selects the automatic binding method which causes the client stub to automatically select the server for your client. In the arith metic application in Chapter 1, for instance, any server found by the client stub would be sufficient. An additional advantage offered by automatic binding is error recovery: if server communication is disrupted, the client stub can sometimes find another server, transparent to the application code.

The irtplicit_handle ACF attribute selects the implicit binding method which allows you to select a specific server for your remote procedure calls. For exam ple, if many inventory servers representing different warehouses are available on the network, you may want your client to select a specific one.

The explicit_handle ACF attribute selects the explicit binding method which lets you select a specific server for each remote procedure call. For example, if your client needs data from many servers simultaneously, you need a way to control which remote procedure call uses which server.

Example 2-11 is an ACF used by the MIDL compiler to produce the header and stub files for the implicit client example of the inventory application.

Example 2-11: An Attribute Configuration File (ACF)

/* FILE NAME: inv.acf (implicit version)*/

/* This Attribute Configuration File is used in conjunction with the */

/* associated MIDL file (inv.idl) when the MIDL conpiler is invoked. */

[

implicit_handle (handle_t global_binding_h) /* implicit binding method O */

]

interface inv /* The interface name must match the MIDL file. © */

O The irtplicit_handle attribute applies to the entire interface. A global bind ing handle of type handle_t is established in the client stub to refer to bind ing information a client uses to find a server.

@ The interface name (inv) must match the interface name in the corresponding MIDI file.

Controlling Errors and Exceptions

An exception is a software state or condition that forces the application to go out side its normal flow of control. Such an event may be produced by hardware (such as memory access violations) or software (such as array subscript range checking). Microsoft RFC applications cause communication and server errors to be raised as exceptions. Unless you design your program to handle the exceptions, the program will exit.

An ACF can save you the trouble of writing extra layers of exception handling code.

The coirm_status and fault_status attributes apply to procedure parameters or procedure return results of the type error_status_t. If this attribute is present and you've added a variable of the data type error_status_t to the argument list of your remote procedure call communication and server errors are communicated to the client as values in the named parameter rather than raised as exceptions. Error codes for comm_status and fault_status are different to allow correct interpretation of the error codes. Chapter 3 discusses error and exception control in greater detail.

Excluding Unused Procedures

The code and nocode ACF attributes allow you to define which procedures the client stub supports. For example, if a client uses only four out of twenty remote procedures declared in the interface, the client stub code does not need the over head of the other procedures. However, all the procedures of an interface defini tion must be implemented by the server.

picture18

picture19

In this Chapter: Binding Steps in Finding Servers

Customizing a Binding Handle Authentication Error Parameters or

£^^0^ How to Write Clients

Compiling and Linking Clients

In this chapter we discuss how to develop client programs for Microsoft RFC inter faces. It is a good idea to read Chapter 1, Overview of an RFC Application, for a complete overview of a distributed application, and Chapter 2, Using a Microsoft RFC Interface ', to familiarize yourself with features of interface definitions.

We discuss client development before server development because you may develop a client for an existing interface and server. We describe server develop ment in Chapter 5, How to Write a Server. The code for all applications is shown in Appendices C through F.

Binding

The first question that probably comes to mind when you begin to develop a client is: How does a remote procedure call find the server it needs? Essentially, the client must create a binding, as described in Chapter 1, and load it with infor mation that lets the RFC runtime library find the server.

Binding information mainly includes a communication protocol sequence, a host name or address, and a server process address on the host (endpoint). If you are familiar with using named pipes, these are similar to a protocol family, a computer name, and a pipe name.

Binding information can be obtained automatically and be completely invisible to your client application code. To the other extreme, you can obtain binding infor mation by calling RFC runtime routines and using a binding handle as a parameter in a remote procedure call. The level of control you need depends on the needs of your client program.

A binding handle is the data structure that manages binding in applications. The handle is a reference (pointer) to information for one possible binding.

Microsoft RFC supplies the Locator as a simple and convenient name service database to store names and locations of network services. Servers use RFC run time routines to store binding information in the name service database. Clients use other RFC runtime routines to retrieve binding information from the name ser vice database and create binding handles for remote procedure calls.

A server's binding information can also be stored in an application-specific database or supplied to client programs by some other means, for example, as arguments when the client is invoked. If your client would not benefit from a name service (or your client system does not have a running name service), you can use RFC runtime routines in applications to convert strings of binding informa tion to binding handles used by remote procedure calls.

Implementing a Binding Method

For each remote procedure call, the binding handle is managed in one of the fol lowing ways.

Automatic method

The client stub automatically manages bindings after the application calls a remote procedure. The client stub obtains binding information from a name service database and passes the binding handle to the RFC runtime library. If the connec tion is disrupted, new binding information can sometimes be automatically obtained and the call is tried again.

Implicit method

A binding handle is held in a global area of the client stub. After the application calls a remote procedure, the stub passes the binding handle to the RFC runtime library. You write application code to obtain the binding information and set the global binding handle with RFC runtime routine calls.

Explicit method

An individual remote procedure call in the application passes a binding handle explicitly as its first parameter. You write application code to obtain the binding information and set the binding handle with RFC runtime routine calls.

Figure 3-1 shows a comparison of binding methods in relation to the client code. For each method, the top portion of the box represents the client application code you write. The bottom portion of each box represents the client stub code that the MIDI compiler generates. The shading represents the portion of the client where binding handles are managed. For any given client instance, different methods may be employed for different remote procedure calls. For example, one remote procedure call can use the automatic method and another remote procedure call can use the explicit method.

Chapter 3: How to Write Clients

47

Figure 3~1- A comparison of binding management methods

The automatic and implicit methods apply to an entire interface. If you use either the automatic or implicit method for an interface, you can also use the explicit method for some or all remote procedure calls to that interface. The explicit method takes precedence over the automatic and implicit methods because the binding handle is visible as the first parameter in the procedure.

If a client uses more than one interface, you can use the automatic method for all remote procedure calls to one interface and the implicit method for all remote pro cedure calls to the other interface. However, a client cannot use the automatic and implicit methods simultaneously, for remote procedure calls to the same interface.

The implicit and explicit methods require that your application code obtain bind ing information and manage the binding handles. Binding handles need to be obtained and managed in the client application code under the following circum stances:

• The client uses a specific server.

• The client needs to set authentication and authorization information for spe cific binding handles.

• The server has more than one implementation of the same remote procedure. An application uses object UUIDs to distinguish between different remote pro cedure implementations.

Use an attribute configuration file (ACF) to establish a binding method with the attributes auto_handle, irrplicit_handle, or explicit_handle.

A context handle is a special remote procedure parameter defined in an interface definition with the context_handle attribute. Applications use a context handle in a sequence of remote procedure calls to refer to a context (state) on a specific server. We mention context handles briefly here with binding methods because they carry with them binding information and thus can act as a binding handle for remote procedure calls. When the context handle is active, it carries with it the binding information necessary to find the same server as it did before, and the server maintains the context for that particular client. (Chapter 7, Context Handles, describes context handle use.)

Deciding on binding methods

Automatic binding does the most work for you, so MIDL makes it the default. Another binding method is chosen in the following situations:

• The first parameter of a procedure declaration is a binding handle (in that case, the binding method has to be explicit)

• The procedure declaration has an input context handle

• An ACF establishes a different binding method

You can force explicit binding when you're sure that you want every client to specify a server when calling a particular procedure. Make a binding handle the procedure's first parameter in the MIDL file. A client cannot take away a parameter declared in the interface definition, so this remote procedure cannot use either the automatic or implicit methods. For the same reason, a context handle forces the client to use explicit binding.

The next decision is whether to use the automatic or implicit method for other procedures. If you're satisfied with using any valid server for your remote proce dure calls—any server that exports the interface described in your MIDL file—the automatic method should be adequate. In particular, the automatic method works fine if the network is relatively small. However, you have no control over which server you get, so applications that use servers scattered over a wide area may be inefficient. If most of your remote procedure calls need to use a specific server, the implicit method is appropriate.

Suppose you have determined that individual remote procedure calls need control over which server each uses. For example, if you use a print server application, one call may request a server near you to print a file. Your next call may request a server in a different location to print another copy for your department manager. If you have determined that you need this kind of binding control for individual remote procedure calls, use the explicit method.

The explicit method is also necessary for clients that make multi-threaded remote procedure calls. For example, a commodity trade application may request a

commodity price with remote procedure calls to many locations at the same time. This server selection control also lets you balance network load in your applica tion. All the clients in this book are single-threaded.

Automatic Binding Management

The automatic binding management method is the simplest because you don't have to manipulate the binding handle in your interface definition, ACF, or appli cation code. The binding handle and the complexity of its management is hidden from you in the client stub and the RFC runtime library. If you lose a server con nection, the automatic method will try to rebind for you. With this method there is a relatively short learning curve to get a distributed application running.

Many applications do not require that you control binding, so it is easier to let the underlying RFC mechanism find a server. The server is selected from a set of servers that support the interface. If the particular server makes no difference, use the automatic method. For example, for a mathematics interface, the first server that supports it is probably sufficient.

The automatic method is demonstrated in the arithmetic application and shown in detail in Chapter 1. For this chapter, however, we use one of the clients for the inventory application, so you can compare client development between different methods for the same application. The application is shown in detail in Appendix D, The Inventory Application.

Interface development for automatic binding

There are no special requirements in the interface for automatic binding. If you wish, you can use the auto_handle attribute in an ACF for documentation.

Client development for automatic binding

The client requires you to:

1. Include the MIDL-generated header file with the #include compiler directive in the client application code:

/* FILE NAME: 'client.C */

/****** Client of the inventory application ******/

#include <stdio.h>

#include <stdlib.h>

#include "inv.h" /* header file created by the MIDL conpiler */

2. Link the client application object code with the client stub, client stub auxil iary file (if available), and the following Microsoft RFC libraries:

rpcrt4.1ib rpcns4.lib libcmt.lib kerne!32.1ib

The client system must have access to a Microsoft Locator name service database on the network. Your system administrator can tell you if you have access to a name service.

The remote procedure call looks just like a local procedure call. The procedure returns a Boolean value of true if the part number is in the inventory or false if it is not:

case 'a': if (is_part_available(part.number)) /* Remote Procedure Call */

puts("available: Yes"); else

puts("available: No"); break;

If your client uses the automatic method for an interface, you can override it for specific procedures by using a binding handle as the first parameter in the call.

See Chapter 6, Using a Name Service, for more information on the name service.

Server development for automatic binding

For clients to use the automatic method, a server must advertise binding informa tion to a name service entry with the RpcNsBindingExport runtime routine in the server initialization code.

Implicit Binding Management

Implicit binding gives you the control of binding management in the client appli cation without a visible binding handle parameter in a remote procedure call. Use the implicit method for applications that need the same server for all or most remote procedure calls of an interface. An ACF defines the binding handle, and the MIDI compiler generates it as a client-global variable in the client stub. The client application code sets the binding handle before any remote procedure calls. Dur ing a remote procedure call, the client stub uses the global binding handle to com plete the call to the RFC runtime library.

In this part of the chapter, we'll develop a client for the inventory application that uses the implicit method. The rationale is that, in this application, you may need to choose a specific server to access the right data base. Once a server is found, the rest of the remote procedure calls can use the same one.

Interface development for implicit binding

Use the irtplicit_handle attribute in an ACF to declare the global binding handle for the client, as shown in Example 3-1. When you compile the interface definition with the ACF available, a global binding handle is defined in the client stub. The stub uses the handle every time the client calls a remote procedure for this inter face.

Example 3-1: An ACF for the Implicit Binding Method

I* FILE NAME: inv_i.acf (implicit version)*/

/* This Attribute Configuration File is used in conjunction with the */

/* associated MIDL file (inv.idl) when the MIDL compiler is invoked. */

[

implicit_handle(handle_t global_binding_h) /* irrplicit binding method */

]

interface inv /* The interface name must match the MIDL file. */

The handle_t type is a MIDL data type that is used to define a binding handle named global_binding_h.

Client development for implicit binding

The client code includes the MIDL-generated header file, obtains a binding handle, and assigns the binding handle to the global binding handle. (See Example 3-2.)

Example 3-2: A Client with the Implicit Binding Method

I* FILE NAME: client. c */

/***** Client of the inventory application with implicit method *****/

#include <stdio.h>

#include <stdlib.h>

ttinclude "inv.h" /* header file created by the MIDL compiler O */

do_import_binding("inventory_", &global_binding_h); /* seek matching */

/* uuid @ */

status = RpcBindingReset(global_binding_h); /* remove endpoint © */ CHECK_STATUS{status, "Can't reset binding handle", ABORT);

case 'a': if (is_part_available(part.number)) /* © */

puts("available: Yes"); else

puts("available: No"); break;

The MIDL-generated header file must be included with the ^include compiler directive.

© The client must obtain binding information and assign its handle to the global binding handle. The binding information can be obtained from the name ser vice database as in this example, or it can be constructed from strings of bind ing information. The do_import_binding procedure is developed later in this chapter.

@ The Microsoft Locator included with our pre-release version of Microsoft RFC unexpectedly returned server endpoints. Sometimes the endpoints were stale (left from previous server instances) and caused communication problems. We used the RpcBindingReset function which removes the endpoint, forcing the client to look in the server host's endpoint map for a fresh endpoint. Your application should not need this function if the Locator does not return server endpoints.

O A remote procedure call looks just like a local procedure call.

If your client uses the implicit method for an interface, you can override it for spe cific procedures by including a binding handle as the first parameter of the proce dures in the MIDI file.

Server development for implicit binding

Although there are no special requirements in server development, a server must export to a name service database if the clients use a name service to find servers. The server for the inventory application exports binding information.

Explicit Binding Management

Explicit binding manages each remote procedure call separately. The first parame ter of the remote procedure call is a binding handle. Use the explicit method when your application needs to make remote procedure calls to more than one server. This method is the most visible in an application because a binding handle is passed as the first parameter of the remote procedure. You completely control the binding management in the client application code.

If the procedure declaration in the interface definition file has a binding handle as the first parameter, you must use the explicit method. If the procedure declaration does not have a binding handle parameter, you can add one by using an ACF. In this case, after you compile the interface definition, the remote procedure is defined in the header file with an additional binding handle as the first parameter.

We'll use another client from the inventory application to demonstrate the explicit method.

Interface development for explicit binding

An interface definition or an ACF uses the handle_t data type to define binding handle parameters. Application code uses the rpc_binding_handle_t data type to represent and manipulate binding information.*

Suppose we want to use the explicit method for a remote procedure that has no explicit binding handle as the first parameter. We use an ACF with the explicit_handle attribute, making the MIDL compiler add a binding handle as the first parameter. At the time this book went to press, we were not able to com pletely test the use of the explicit_handle attribute. Keep in mind that the final release of Microsoft RFC Version 2.0 might differ slightly from the behavior described here.

The is_part_available procedure is defined in the interface as follows:

boolean is_part_available ( /* return true if in inventory */

[in] part_num number /* input part number */ );

An ACF that adds a binding handle parameter is shown in Example 3-3.

Example 3~3: Adding Binding Handles with an ACF

I* FILE NAME: inv.acf (explicit version)*/

/* This Attribute Configuration File is used in conjunction with the */

/* associated MIDL file (inv.idl) when the MIDL compiler is invoked.*/

[

explicit_handle /* explicit binding method */

]

interface inventory /* The interface name must match the MIDL file. */

When the MIDL compiler uses this ACF, all procedure declarations in the header file have a binding handle of type handle_t added as the first parameter. If you use the explicit_handle attribute this way, none of the remote procedure calls to this interface can use the automatic or implicit method for this client instance.

You can also use the explicit_handle attribute on a specific procedure in the ACF to add a binding handle as the first parameter. For example, this ACF associ ates a binding handle parameter only with the is_part_available procedure:

interface inventory {

[explicit_handle] is_part_available() ; }

Example 3-4 defines a binding handle explicitly in the interface definition. Other clients cannot use the automatic or implicit methods of binding for the procedure.

* The handle_t and rpc_binding_handle_t data types are equivalent. The handle_t data type exists for compatibility with earlier RFC versions. The rpc_binding_handle_t data type exists for consistency in data type naming for the RFC runtime routines.

(The is_part_available procedure is not declared this way for the inventory inter face.)

Example 3~4: Defining a Binding Handle in the Interface Definition

boolean is_part_available( /* return true if in inventory */

[in] handle_t binding_h, /* explicit, binding handle */

[in] part_num number /* input part number */ );

Later in this chapter we'll show how to create an application-specific, customized binding handle in the interface definition through the handle attribute.

Client development for explicit binding

Before making the remote procedure call, the client must obtain binding informa tion and set the binding handle. The methods of obtaining binding information for the explicit method are almost the same as for the implicit method. For the explicit method, you use a specific binding handle instead of assigning the binding infor mation to the implicit global binding handle.

Example 3-5: A Client with the Explicit Binding Method

/* FILE NAME: client.c */

/***** Client of the inventory application with explicit method *********/

#include <stdio.h>

ftinclude <stdlib.h>

Mnclude "inv.h" /* header file created by the MIDL compiler O */

rpc_binding_handle_t binding_h; /* declare a binding handle © */

do_import_binding("/.:/inventory", &binding_h); /* find server © */ status = RpcBindingReset(global_binding_h); /* remove endpoint O */ CHECK_STATUS(status, "Can't reset binding handle", ABORT);

case 'a': if (is_part_available(binding_h, part.number)) /* © */

puts("available: Yes"); else

puts("available: No"); break;

O Include the MIDL-generated header file with the tfinclude compiler directive. © Declare binding handles of type rpc_binding_handle_t in the application.

© The client must obtain binding information from the name service database, or it can be constructed from strings of binding information. Example 3-7

shows how the application-specific procedure do_inport_binding uses the name service database.

© The RpcBindingReset function fixes a problem we discovered with the Microsoft Locator. See Example 3-2 for more information.

0 The first parameter is the binding handle.

Server development for explicit binding

To use explicit binding, the ACF must include the explicit_binding attribute or the interface definition must have a binding handle parameter for the remote pro cedure. Servers use the binding handle parameter to obtain client binding informa tion for use in authentication and authorization.

Example 3-6 shows how to include a binding handle parameter in a server remote procedure.

Example 3~6: Manager Procedures with the Explicit Binding Method

I* FILE NAME: manager.c */

/** Implementation of the remote procedures for the inventory application. **/

#include <stdio.h>

#include <stdlib.h>

# include "inv.h"

boolean is_part_available(binding_h, number) /* 0 */

handle_t binding_h; /* © */

part_num number; {

part_record *part; /* a pointer to a part record */

int found;

found = read_part_record(number, &part); if(found)

return(TRUE); else

return(FALSE); }

O Include a binding handle as the first parameter in a remote procedure imple mentation..

© Declare a binding handle as a parameter.

Steps in Finding Servers

Recall that Figure 1-10, in Chapter 1, shows one way to find a server. In this figure, the client stub and the RFC runtime library handle all binding management outside of the application code. The client stub automatically finds the server system bind ing information in a name service database. The binding handle is set and passed to the RFC runtime library, which finds the server process binding information

(endpoint) in the server system's endpoint map. The RFC runtime library uses the complete binding information to bind to the server.

The key to finding a server is to obtain a protocol sequence, a server host name or address, and an endpoint. A binding handle for the remote procedure call is set to point to this binding information.

The following discussion is a generalization of what happens during the server finding process. It includes the choices you (or the RFC runtime library) have about where to obtain the necessary binding information. Where these steps are executed (client application, client stub, or RFC runtime library) depends on the kind of binding handle and binding method used.

Finding a Protocol Sequence

A client and server can communicate over a network if they both use the same network communication protocols. A protocol sequence is found in one of two ways:

• The preferred method is to use a name service database to import or look up both a host address and protocol sequence at the same time. To set the bind ing handle, use the RFC runtime routines that begin with RpcNsBindinglmport or RpcNsBindingLookup. If your application uses the automatic method, the client stub does this for you.

• The other method is to use a protocol sequence string obtained from your application or from a call to the RpcNetivorklnqProtseqs routine. Use the RFC runtime routines RpcStringBindingCompose and RpcBindingFromString-Binding to set the binding handle.

A protocol sequence is a character string containing three items that correspond to options for network communications protocols. RFC represents each valid combi nation of these protocols as a protocol sequence. The protocol sequence consists of a string of the options separated by underscores. The only current, valid option combinations are shown in Table 3-1.

Table 3~1: Valid Protocol Sequences

Protocol Sequence Common Name Description

ncacn_ip_tcp Connection Network Computing Architecture con-

protocol nection over an Internet Protocol with a

sequence

ncadg_ip_udp Datagram

protocol sequence

Transmission Control Protocol for trans port.

Network Computing Architecture data gram over an Internet Protocol with a User Datagram Protocol for transport.

Chapter 3-' How to Write Clients

57

Table 3~1: Valid Protocol Sequences (continued) Protocol Sequence Common Name Description

The three protocols of a protocol sequence are for RPC communication, network host addressing, and network transport.

1. The RPC protocol for communications has two options:

• Network Computing Architecture connection-oriented protocol (ncacn)

• Network Computing Architecture local interprocess communication (ncalrpc)

The network address format used as part of the binding information has three options:

• the Internet protocol (ip)

• the DECnet (TM) protocol (dnet)

• the NetBIOS (Artisoft's Network Basic Input Output System) protocol (nb)

2. The transport protocol for communications has five options:

• Transmission control protocol (tcp)

• Network services protocol (nsp)

• NetBEUI (NetBIOS Extended User Interface)

• Named pipes (np)

• spx (sequenced packet exchange)

Most servers should use all available protocol sequences so clients using the inter face will have every opportunity to find and use a server.

In general, your choice of protocols on the client side should not be a big con cern. If most traffic on your network is TCP/IP, use that protocol. When several protocols are available to clients, you can usually just pick the one most com monly used for communications in your network.

If you want to be selective, here are some guidelines to help you choose a suit able protocol.

• Use TCP/IP or DECnet when clients and servers must communicate over a wide-area network (WAN). These protocols have long timeouts that can han dle the network delays inherent in WANs. Use TCP/IP when debugging your client during remote procedure calls. Otherwise, the process could time out when the debugger stops it. Clients can control timeouts using the RPC run time routines RpcMgmtSetComTimeout and RpcMgmtlnqComTimeout.

• Use UDP/IP when clients need to bind to many servers. That's because this protocol has relatively low overhead. If a remote procedure broadcasts its call to all hosts on a local network, it must use UDP/IP. The broadcast attribute on the procedure declaration in the interface definition declares the broadcast capability.

• Use NetBIOS over NetBEUI for local area network (LAN) connections because it can be faster than TCP/IP or DECnet in some networks. Avoid using NetBIOS over NetBEUI when clients and servers are separated by network routers.

• Use named pipes (ncacn_np) in local area networks when you want to rely on the security built in to named pipes. Named pipes' extra security overhead can slow down remote procedure calls, so use it only when you need secu rity.

• Use Local Windows NT RPC Communication (ncalrpc) when clients and servers reside on the same system, because it's generally faster than other pro tocols for interprocess communication.

Finding a Server Host

You can find a server host name or network address in two different ways:

• Use a name service database to import or look up a host address and at the same time get a protocol sequence. Use the RPC runtime routines that begin with RpcNsBindinglmport or RpcNsBindingLookup to set the binding handle. If your application uses the automatic method, the client stub does this for you.

• Use a host name or host network address string obtained from your applica tion. Use the RFC runtime routines called RpcStringBindingCompose and Rpc-BindingFromStringBinding to set the binding handle.

A partially bound binding handle is one that contains a protocol sequence and server host, but not an endpoint. This handle is what you get from the Microsoft Locator. It means you have identified the server's system, but not the server pro cess on that system. The binding to a server cannot complete until an endpoint is found.

When a partially bound binding handle is passed to the RFC runtime library, an endpoint is automatically obtained for you from the interface or the endpoint map on the server's system.

Finding an Endpoint

A binding handle that has an endpoint as part of its binding information is called a fully bound binding handle. Endpoints can be well-known or dynamic. A well-known endpoint is a pre-assigned system address that a server process uses every time it runs. Usually a well-known endpoint is assigned by the authority responsi ble for a transport protocol. A dynamic endpoint is a system address of a server process that is requested and assigned by the RFC runtime library when a server is initialized. Most applications should use dynamic endpoints to avoid the network management needed for well-known endpoints.

You can use your application code to obtain an endpoint, but it is best to let the RFC runtime library find an endpoint for you. An endpoint is found in one of four ways:

• If the binding information obtained during an import or lookup of the proto col sequence and host in the name service database includes an endpoint, the binding handle is fully bound in one step. The name service database can be used to store well-known endpoints. But dynamic endpoints are never stored in the name service database because their temporary nature requires signifi cant management of the database, which degrades name service performance.

• A well-known endpoint is found that was established in the interface defini tion with the endpoint attribute. The RFC runtime library (or your applica tion) finds the endpoint from an interface-specific data structure.

• An endpoint is found from the endpoint map on the server system. These endpoints can be well-known or dynamic. The RFC runtime library first looks for an endpoint from the interface specification. If one is not found, the RFC runtime library looks in the server's endpoint map. When an endpoint is found, the binding to the server process completes. To obtain an endpoint from a server's endpoint map, use the RpcEpResolveBinding routine or rou tines beginning with RpcMgmtEpEltlnq in your application.

• You can use a string from your application that represents an endpoint, and then you can use the RFC runtime routines RpcStringBindingCompose and RpcBindingFromStringBinding to set the binding handle. These endpoints can be well-known or dynamic.

Interpreting Binding Information

This section reveals what goes on in the do_import_binding procedure shown ear lier in the chapter. When you use implicit or explicit binding, you need to interpret the binding information. To take a simple case, suppose you want to use a server on a particular host—this means you need to extract the host from the binding handles you get from CDS and isolate the host name in each handle.

Binding handles refer to the following binding information:

• Object UUID

• Protocol sequence

• Network address or host name

• Endpoint

• Network options

Object UUIDs are part of an advanced topic not discussed in this book. Network options are specific to a protocol sequence.

Example 3-7 shows how to use RFC runtime routines to interpret binding informa tion. You use these routines in either a server or client. The do_interpret_binding procedure is called in the do_import_binding procedure.

Example 3~ 7: Interpreting Binding Information

/* FILE NAME: intbind.c */

/* Interpret binding information and return the protocol sequence. */

ftinclude <stdio.h>

#include <rpc.h>

• include "status.h"

void do_interpret_binding(binding, protocol_seq)

rpc_binding_handle_t binding; /* binding handle to interpret */

char *protocol_seq; /* protocol sequence to obtain */

{

unsigned long status; /* error status */

unsigned char *string_binding; /* string of binding info. */

unsigned char *protseq; /* binding conponent of interest */

status =

RpcBindingToStringBinding( /* convert binding information to string O */ binding, /* the binding handle to convert */

&string_binding /* the string of binding data */

);

CHECK_STATUS(status, "Can't get string binding:", RESUME);

Chapter 3: How to Write Clients

Example 3~ 7: Interpreting Binding Information (continued)

status =

RpcStringBindingParse ( /* get components of string binding©*/

string_binding, /* the string of binding data */

NULL, /* an object UUID string is not obtained */

&protseq, /* a protocol sequence string IS obtained */

NULL, /* a network address string is not obtained */

NULL, /* an endpoint string is not obtained */

NULL /* a network options string is not obtained */

);

CHECK_STATUS( status, "Can't parse string binding:", RESUME);

strcpy (protocol_seq, (char *)protseq) ;

/* free all strings allocated by other runtime routines © */

status = RpcStringFree(&string_binding) ;

status = RpcStringFreef&protseq ) ;

return;

O The RpcBindingToStringBinding routine converts binding information to its string representation. The binding handle is passed in and the string holding the binding information is allocated.

© The RpcStringBindingParse routine obtains the binding information items as separate allocated strings. The components include an object UUID, a protocol sequence, a network address, an endpoint, and network options. If any of the components are null on input, no data is obtained for that parameter.

© The RpcStringFree routine frees strings allocated by other RFC runtime rou tines.

Finding a Server from a Name Service Database

The usual way for a client to obtain binding information is from a name service database using the name service RFC runtime routines (routines beginning with RpcNs). This method assumes that the server you want has exported binding infor mation to the name service database.

The name service database contains entries of information, each identified by a name used in programs, environment variables, and commands. Clients can use a name called a server entry name to begin a search for compatible binding informa tion in the database. Entries contain binding information about specific servers. Use RFC name service runtime routines to search entries in the name service database for binding information. The example in this section does a very simple search. See Chapter 6 for a more detailed name service description.

Importing a binding handle

Since the same interface can be supported on many systems of the network, a client needs a way to select one system. The runtime import routines obtain

information for one binding handle at a time from the name service database, selecting from the available list of servers supporting the interface.

Example 3-8 shows how an application obtains binding information from a name service database.

Example 3~8: Importing a Binding Handle

/* FILE NAME: getbind.c */

/* Get binding from name service database. */

ttinclude <stdio.h>

ttinclude "inv.h"

#include "status.h"

void do_iinport_binding(entry_name, binding_h)

char entry_name[]; /* entry name to begin search */

rpc_binding_handle_t *binding_h; /* a binding handle */

{

unsigned long status; /* error status */

RPC_NS_HANDLE import_context; /* required to import */

char protseq[20]; /* protocol sequence */

status =

RpcNsBindinglmportBegin( /* set context to import binding handles O */

RPC_C_NS_SYNTAX_DEFAULT, /* use default syntax */

(unsigned char *)entry_name, /* begin search with this name */

inv_Vl_0_c_ifspec, /* interface specification (inv.h) */

NULL, /* no optional object UUID required */

&import_context /* import context obtained */

);

CHECK_STATUS(status, "Can't begin import:", RESUME);

while(1) { status = RpcNsBindinglmportNext ( /* import a binding handle© */

import_context, /* context from RpcNsBindinglmportBegin */

binding_h /* a binding handle is obtained */

); if(status != RPC_S_OK) {

CHECK_STATUS(status, "Can't import a binding handle:", RESUME);

break; }

/** application specific selection criteria (by protocol sequence) © */

do_interpret_binding(*binding_h ,protseq);

if(strcmp(protseq, "ncacn_ip_tcp") == 0) /*select connection protocol*/

break; else {

status =

RpcBindingFree( /* free binding information not selected©*/ binding_h

);

CHECK_STATUS(status, "Can't free binding information:", RESUME); } } /*end while */

Example 3~8: Importing a Binding Handle (continued)

status =

RpcNsBindinglnportDone( /* done with import context ® */

&inport_context /* obtained from RpcNsBindinglmportBegin */ );

return; }

O The RpcNsBindinglmportBegin routine establishes the beginning of a search for binding information in a name service database. An entry name syntax of RPC_C_NS_SYNTAX_DEFAULT uses the syntax in the RFC-specific environment variable DefaultSyntax.

In this example, the entry to begin the search is /. : /inventory_, which is passed as a parameter. If you use a null string for the entry name, the search begins with the name in the RFC environment variable Def aultEntry.

If you use a null string for the entry name, and the DefaultEntry is null, the Locator searches for an entry name that offers the interface UUID.

In this example, an object UUID is not required, so we use a null value. The interface handle Inv_Vl_0_c_ifspec refers to the interface specification. It is generated by the MIDI compiler and defined in file inv.h.

Finally, the import context and error status are output. You use the import context in other import routines to select binding information from the name service database, or to free the context memory when you are done with it.

@ The RpcNsBindinglmportNext routine obtains binding information that sup ports the interface, if any exists. The routine accesses the database and does not communicate with the server. The import handle, established with the call RpcNsBindinglmportBegin, controls the search for compatible binding han dles.

© Once binding information is obtained, any criteria required by the application may be used to decide whether it is appropriate. In this example, the applica tion-specific procedure, do_interpret_binding, shown in Example 3-6, is used to interpret binding information by returning the protocol sequence in a parameter. The do_import_binding procedure then selects the binding infor mation if it contains the connection protocol.

O Each call to RpcNsBindinglmportNext requires a corresponding call to the RpcBindingFree routine that frees memory containing the binding information and sets the binding handle to null. Free the binding handle after you finish making remote procedure calls.

@ The RpcNsBindinglmportDone routine signifies that a client has finished look ing for a compatible server in the name service database. This routine frees the memory of the import context created by a call to RpcNsBindinglmport Begin. Each call to RpcNsBindinglmportBegin must have a corresponding call to RpcNsBindinglmportDone.

Looking up a set of binding handles

Runtime routines whose names begin with RpcNsBindingLookup obtain a set of binding handles from the name service database. You can then select individual binding handles from the set with the RpcNsBindingSelect routine or you may use your own selection criteria. Lookup routines give a client program a little more control than import routines because RpcNsBindinglmportNext returns a random binding handle from a list of compatible binding handles. Use the lookup routines when you want to select a server or servers by more specific binding information; for example, to select a server that is running on a system in your building or to use servers supporting a specific protocol sequence.

Finding a Server from Strings of Binding Data

If you bypass the name service database, you need to construct your own binding information and binding handles. Binding information may be represented with strings. You can compose a binding handle from appropriate strings of binding information or interpret information that a binding handle refers to.

The minimum information required in your application to obtain a binding handle is:

• A protocol sequence of communication protocols

• A server network address or host name

Remember that an endpoint is required for a remote procedure call to complete, but you can let the RFC runtime library obtain one for you. To set a binding han dle, obtain and present the binding information to RFC runtime routines.

Example 3-9 shows a procedure to set a binding handle from strings of binding information. The rfile application uses this procedure. A network address or host name is input for this procedure and the protocol sequence is obtained. This pro cedure creates a partially bound binding handle, so the RFC runtime library obtains the endpoint when a remote procedure uses the binding handle.

Example 3~9: Setting a Binding Handle from Strings

/* FILE NAME: strbind.c */

/* Find a server binding handle from strings of binding information */

/* including protocol sequence, host address, and server process endpoint. */

#include <stdio.h>

#include "rfile.h"

#include "status.h" /* contains the CHECK_STATUS macro */

int do_string_binding(host, binding_h) /*return=0 if binding valid, else -1 */ char host [ ]; / * server host name or network address input O * /

rpc_binding_handle_t *binding_h; /* binding handle is output */ {

RPC_PROTSEQ_VECTOR *protseq_vector; /* protocol sequence list */ unsigned char *string_binding; /* string of binding information */ unsigned long status; /* error status */

Example 3~9: Setting a Binding Handle from Strings (continued)

int i, result;

status =

RpcNetworklnqProtseqs ( /* obtain a list of valid protocol sequences© */ &protseq_vector /* list of protocol sequences obtained */

); CHECK_STATUS(status, "Can't get protocol sequences:", ABORT);

/* loop through protocol sequences until a binding handle is obtained */ for(i=0; i < protseq_vector->Count; i++) {

status =

RpcStringRindingCompose( /* make string binding from components @ */ NULL, /* no object UUIDs are required */

protseq_vector->Protseq[i], /* protocol sequence */

(unsigned char *)host, /* host name or network address */ NULL, /* no endpoint is required */

NULL, /* no network options are required */

&string_binding /* the constructed string binding */

);

CHECK-STATUS(status, "Can't compose a string binding:", RESUME);

status =

RpcBindingFromStringBinding( /* convert string to binding handle O */

string_binding, /* input string binding */

binding_h /* binding handle is obtained here */

);

CHECK_STATUS(status, "Can't get binding handle from string:", RESUME); if(status != RPC_S_OK) {

result = -1;

CHECK_STATUS(status, "Can't get binding handle from string:", RESUME); } else

result = 0;

status =

RpcStringFree( /* free string binding created©*/

&string_binding );

CHECK_STATUS(status, "Can't free string binding:", RESUME); if(result == 0) break; /* got a valid binding */

}

status =

RpcProtseqVectorFree( /* free the list of protocol sequences©*/

&protseq_vector );

CHECK_STATUS(status, "Can't free protocol sequence vector:", RESUME); return(result); }

O The network address or host name on which a server is available is required binding information. For this example, the information is input as a parame ter.

@ The RpcNetworklnqProtseqs routine creates a list of valid protocol sequences. This example uses each protocol sequence from the list until a binding handle is created.

© The RpcStringBindingCompose routine creates a string of binding information in the argument string_binding from all the necessary binding information components. The component strings include an object UUID, a protocol sequence, a network address, an endpoint, and network options.

O The RpcBindingFromStringBinding routine obtains a binding handle from the string of binding information. The string of binding information comes from the RpcStringBindingCompose routine or from the RpcBindingToString-Binding routine.

When you are finished with the binding handle, use the RpcBindingFree rou tine to set the binding handle to null and to free memory referred to by the binding handle. In this example, another part of the application frees the binding handle.

© The RpcStringFree routine frees strings allocated by other RFC runtime rou tines. This example frees the string string_binding allocated by the Rpc StringBindingCompose routine.

© The RpcProtseqVectorFree routine is called to free the list of protocol sequences. An earlier call to RpcNetworklnqProtseqs requires a corresponding call to RpcProtseqVectorFree.

Customizing a Binding Handle

The basic binding handles we have seen so far are primitive binding handles. A customized binding handle adds some information that your application wants to pass between client and server. You can use a customized binding handle when application-specific data is appropriate to use for finding a server, and the data is also needed as a procedure parameter.

For example, in an application that acts on remote files, a structure could contain a host name and a remote filename. The application creates the necessary binding information from the host name, and the filename is passed with the binding infor mation so the server knows what data file to use. You can use a customized bind ing handle with the explicit or implicit binding methods, but the automatic method uses only primitive binding handles.

Figure 3-2 shows how a customized binding handle works during a remote proce dure call. To define a customized binding handle, apply the handle attribute to a type definition in an interface definition.

You can use a customized binding handle in a client just like a primitive binding handle, but you must write special bind and unbind procedures. Your code does not call these procedures; the client stub calls them during each remote procedure

Chapter 3: How to Write Clients

67

Figure 3~2. How a customized binding handle works

call. For a primitive binding handle, the client stub already has the necessary code to prepare the binding information for the call. For application-specific binding information, you must supply the code. The tasks of the bind and unbind proce dures are to obtain a primitive binding handle and do application cleanup when finished with the binding handle.

Get Microsoft RPC Programming Guide now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.