Distributed Applications Microsoft RFC Programming Guide A NUTSHELL* HANDBOOK John Shirley & Ward Rosen berry O Reilly & Associates, Inc. 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 vi Microsoft RFC Programming Guide 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 Table of Contents vii 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 viii Microsoft RFC Programming Guide 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 6-2 A simple use of a name service database 125 xii Microsoft RPC Programming Guide 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. Preface xiii 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 xvi Microsoft RFC Programming Guide 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. xviii Microsoft RFC Programming Guide 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 Preface xix 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. 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 Request to server Network Server System Server Response from server 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 Client Pro Application Code Remote cedure Call t Stub Code RFC Runtime Library . id Input p^ Output Network Server Remote procedure Application , ""X Code i r i L Stub Code RFC Runtime Library . L_ 1 Figure 1-2. RFC mechanism 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 Compile the interface definition to generate the application header and stub files. f midl 1 arith.h J 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. 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 */ 12 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 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 Figure 1-7. Binding protocol *? server *? *? endpoint sequence * host k k Binding Figure 1-8. Binding information 14 Microsoft RFC Programming Guide 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 Don am Controller ^ Microsoft Locator Sem ir System \ Initialization n /- r / s f- interface -Q Create binding information -Q Advertise server location -Q Register endpoints -0 Listen for calls \ RFC Runtime Library r r Endpoint Map 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. 16 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. Cli tat System ( Application Code i ) Make remote procedure call r Stub i i 1 Mind L server system r RFC Runtime Library ( i \ )Fi ^ P c r i nd server ocess ) Bind to r server Domain Control Microsoft Locator Server System 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. Client System Application * Code L 1 Stub ) Prepare input C i \ Convert output RFC I Runtime 1 Library i ) Transmit input i > Receive , output \ r Sera r System Execute remote procedure t i 1 ( r $ Prepare output ( I ) Convert i input i ( r ) Transmit output ) Receive and L dispatch to stub 1 r Endpoint Map Domain Controller Microsoft Locator Figure 1-11. Completing a remote procedure call 18 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" # inc lude " 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) */ Chapter 1: Overview of an RFC Application 75? 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_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 ( 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. 20 Microsoft RFC Programming Guide 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. Chapter 1: Overview of an RFC Application 21 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 22 Microsoft RFC Programming Guide 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 # ! 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 Chapter 1: Overview of an RFC Application 23 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 24 Microsoft RFC Programming Guide 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 Linker Linker Figure 1-12. Arithmetic application: complete development 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. 27 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. Chapter 2: Using a Microsoft RFC Interface 29 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. 30 Microsoft RFC Programming Guide 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 . ), /* 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. Chapter 2: Ush^ a Microsoft KPC Interface 37 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: - 32 Microsoft RFC Programming Guide 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 1 6-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*/ Chapter 2: Using a Microsoft RFC Interface 33 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 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. Chapter 2: Using a Microsoft RFC Interface 35 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. 36 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; par t_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 ; } quant ity_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 */ Chapter 2: Using a Microsoft RFC Interface 37 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 what is_part_pr ice ( [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. 40 Microsoft RFC Programming Guide 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 Compile the interface definition to generate the application header, stub, and auxiliary files. T midl 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. 42 Microsoft RFC Programming Guide 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 Chapter 2: Using a Microsoft RFC Interface 43 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. 44 Microsoft RFC Programming Guide @ 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. 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. 45 46 Microsoft RFC Programming Guide 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 Client Application Code Client Stub Automatic Implicit Explicit Call remote procedure Obtain binding information and set global binding handle Call remote procedure Obtain binding information and set binding handle Call remote procedure and pass binding handle to stub as first parameter Binding information obtained and binding handle set Binding handle passed to RPC runtime library Binding handle defined as global variable Binding handle passed to RPC runtime library Binding handle passed to RPC runtime library I 1 = Code that manages binding handle 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. 48 Microsoft RFC Programming Guide 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 Chapter 3: How to Write Clients 49 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 */ 50 Microsoft RFC Programming Guide 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_avail able (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. Chapter j; How to Write Clients 57 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. 52 Microsoft RFC Programming Guide 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. Chapter j; How to Write Clients 53 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. 54 Microsoft RFC Programming Guide (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 Chapter 3: How to Write Clients 55 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. 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> # inc lude " inv . h " boolean is_part_available(binding_h, number) /* */ 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 56 Microsoft RFC Programming Guide (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 ncacn_dnet_nsp DECnet (TM) Network Computing Architecture con nection over DECnet (TM). The underly ing software that implements DECnet must be purchased separately. ncacn_nb_tcp NetBIOS over Network Computing Architecture con TCP/IP nection using NetBIOS over TCP/IP. ncacn_nb_nb NetBIOS over Network Computing Architecture con NetBEUI nection using NetBIOS over the NetBEUI transport. ncacn_np Named pipes Network Computing Architecture con nection using named pipes. ncacn_spx Connection- Network Computing Architecture con oriented SPX nection using SPX. ncalrpc Local Windows Network Computing Architecture using NT communi local communications only. cations 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) 5S Microsoft RFC Programming Guide 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. Chapter 3: How to Write Clients 59 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. 60 Microsoft RFC Programming Guide 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 62 Microsoft RFC Programming Guide 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 */ Chapter 3: How to Write Clients 63 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 Def aultSyntax. 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. 64 Microsoft RFC Programming Guide 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 */ Chapter 3: How to Write Clients 65 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. 66 Microsoft RFC Programming Guide @ 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 Client , < Application Code ^ & Set customized binding information * Call remote procedure A Bind Unbind procedure procedure I Bind procedure called to obtain primitive binding handle < 7 Unbind procedure called Call completed ( with primitive Stub binding handle **^, -rt**** > , ** k N* ^MIMMVMMRRMMMM^^ pp- l r 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. Manipulate the data structure in your application the same as any structure, includ ing passing data in the remote procedure call. However, the client stub uses the special procedures to manage the binding. The customized binding handle must be the first parameter in a remote procedure (or the global handle for the implicit method) to act as the binding handle for the call. A customized handle acts as a standard parameter if it is not the first parameter. Example 3-10 shows how to define a customized binding handle in an interface definition. Example 3~10: Defining a Customized Binding Handle I* FILE NAME: search. idl */ [ uuid(2450F730-5170-10lA-9A93-08002B2BC829), version (1.0) , pointer_default (ref ) ] interface search /* Search remote file for data */ const long LINESIZE = 100; /* Constant for maximum line size */ 6# _ Microsoft RPC Programming Guide Example 3~ 10 - Defining a Customized Binding Handle (continued) const long FILENAME_SIZE = 100; /* Constant for file name length */ const long BINDING_SIZE = 32; /* Constant for host name size */ const short FILE_ERROR = -1; /* Status for search file error */ const short NO_MATCH =0; /* Status for no match found */ /* ** Customized binding handle definition ** contains the file name and the string ** binding to use. */ typedef [handle] struct { /* customized handle type O */ unsigned char binding [BINDING_SIZE] ; unsigned char filename [FILENAME_SIZE] ; } search_spec; /* */ /* ** Search for a string match on the file specified ** in the customized binding handle above. */ short searchit( /* search a file on the server */ [in] search_spec custom_handle , /* customized binding handle*/ [in, string] char search_string[LINESIZE] , /* target string */ [out, string] char return_string[LINESIZE] , /* results */ [out] error_status_t * error /* comm/ fault status */ O Use the handle attribute in the interface definition to associate a customized binding handle with a data type. The f ile_spec data type is a structure whose members are file specifications. This is application-specific information used by the bind procedure to obtain server binding information. The customized binding handle is the first parameter of a procedure declara tion. This is an example of explicit binding. You must implement bind and unbind procedures. Example 3-11 shows how you can implement these procedures inside a client file. Example 3~ 1 1: Bind and Unbind Procedures I* FUNCTION: search_spec_bind */ handle_t RPC API search_spec_bind(custom_handle) /* bind procedure for customized handle O */ search_spec custom_handle; rpc_binding_handle_t binding_h; printf ("\n\t ( Selecting server binding: %s)\n\n", /* Display server binding*/ custom_handle . binding) ; status = RpcBindingFromStringBinding ( /* Convert the character string */ custom_handle. binding, /* binding into an RPC handle */ &binding_h, Chapter 3: How to Write Clients 69 Example 3~H: Bind and Unbind Procedures (continued) ); CHECK_STATUS( status, "Invalid string binding", RESUME); exit (EXIT_FAILURE) ; return (binding_h) ; } /* FUNCTION: search_spec_unbind */ void RFC API search_spec_unbind ( /* unbind procedure for customized handle */ custom_handle , binding_h) search_spec custom_handle; handle_t binding_h; { status = RpcBindingFree ( /* Free the binding handle */ &binding_h) ; CHECK_STATUS( status, "Can t free binding handle :", RESUME); return; } O The bind procedure takes an input parameter of the customized handle data type, and returns a primitive binding handle. You construct the procedure name from the data type name, search_spec, to which you append _bind. In this example searcb_spec_bind constructs a binding handle from arguments passed on the client command line to obtain a primitive binding handle. The unbind procedure takes input parameters of the customized handle data type and a primitive binding handle. You construct the procedure name from the data type name, search_spec, to which you append _unbind. In this example search >_spec_unbind calls the RFC runtime routine, RpcBindingFree, to free the binding handle. Example 3-12 shows how an application client can use a customized binding han dle. Example 3~12: A Client with a Customized Binding Handle I* FILE NAME: client_send.c */ int MAIN_DECL main(ac, av) int ac ; char *av [ ] ; { short search_status ; /* status from search */ idl_char match [LINESIZE] ; /* string to look for */ search_spec custom_handle; /* customized binding handle O */ match[0] = \0 ; /* Initialize some strings */ custom_handle . binding [ ] = \ ; custom_handle.filename[0] = \0 ; 70 Microsoft RFC Programming Guide Example 3~12: A Client with a Customized Binding Handle (continued) I* ** There should be 4 parameters to searchit: ** ** searchit <hostname> <filename> <matchstring> ** ** where ** ** <hostname> is the hostname where the file to be searched ** exists. ** ** <filename> is the name of the file to be searched. ** ** <matchstring> is the string to search <filename> for. ** */ if (ac != 4) /* Exit if not the right number of parameters */ { printf ( "\t\nUsage: searchit <hostname> <filename> <matchstring>\n\n" ) ; exit (EXIT_FAILURE) ; } /* ** Set up the string binding, the filename, and the ** match string from the command line. */ strcpy ((char * ) custom_handle . binding, "ncacn_ip_tcp: ") ; /* */ strcat ((char * ) custom_handle . binding, av[l]); strcpy ( ( char * ) custom_handle . f i lename , av [ 2 ] ) ; strcpy ((char *)match, av[3] ) ; /* ** Search the given file on the given host for the ** given string. . . */ search_status = searchit ( /* Remote procedure with input */ custom_handle , match, result , &rpc_status O The application allocates the customized binding handle. @ Initialize the customized binding information in the client before calling the remote procedure. For this example, when we invoke the client, we input the server host name, remote data filename, and search string as arguments. The remote procedure is called with the customized binding handle as the first parameter. Chapter 3: How to Write Clients 77 Authentication Authentication in a distributed environment is a broad topic that is outside the scope of this book. Although we do not provide details on implementing security in Microsoft RFC applications, we will mention the major aspects and some trade offs involved in selecting various models. Microsoft RFC can use the security features of Microsoft Windows NT which are built into the named pipes (ncacn_np) and local RFC (ncalrpc) transports. You must restrict your application to using one of the two listed transports to use this security system. You can use the Windows NT security features by specifying options to the end- point parameter in a string binding. Options have names such as anonymous, identification, or impersonation, controlling which level of security to use. Alternatively, you can use RFC security available in Microsoft RFC. This form of security is transport-independent so your application can use other transports in addition to named pipes and local RFC. Microsoft RFC security currently uses the Windows NT Security Service as the only supported security provider. RFC security offers three kinds of protection: authentication, data integrity, and data privacy. Data integrity and data privacy involve extra encryption and decryp tion cycles which can be time consuming, so use these features only when neces sary. On client systems you can use RFC security by including the RpcBindingSetAuth- Info routine in your client program. Briefly, this routine places the client s identity information into the binding handle which is passed to the server as the first parameter in a remote procedure call. Servers extract the client authentication information from the client binding handle using the RpcBindinglnqAuth Client routine. Servers use this information to verify a client s authenticity. The server system supplies its identity information to clients by registering it with the RpcServerRegisterAuthlnfo routine. Clients or other servers can extract this information to authenticate the server s identity. Use the RpcBindinglnqAuthlnfo routine to extract server authentication information from the server binding han dle. To recap, using the transport level security built into named pipes and local RFC does not necessarily add lots of new code to an application. If you want to use security over transports other than named pipes or local RFC (for instance, TCP/IP or DECnet), you ll need to use RFC security features which can require extra pro gramming overhead. 72 Microsoft RFC Programming Guide Error Parameters or Exceptions Microsoft RFC client applications require special error-handling techniques to deal with errors that may occur during a remote procedure call. The following discus sion pertains to both client and server development. Server and communication errors are raised to the client as exceptions during a remote procedure call. RFC exceptions are similar to the RFC error status codes. Errors have names with S as the second component, as in RPC_S_ADDRESS_ERROR. Exceptions have X as the second component, as in RPC_X_NO_MEMORY. Types of exceptions include the following: Exceptions raised on the client system, such as when the client process is out of memory (RPC_X_NQ_MEMORY). Exceptions raised to the client application by the client stub, such as when the stub has received bad data (RPC_X_BAD_STUB_DATA). Exceptions raised by the client stub on behalf of the server. These errors can occur in the server stub, in the remote procedures, or in the server s RFC run time library. The server transport layer does not return exceptions to the client. A distributed application can have errors from a number of sources, so you will need to decide whether you want to handle errors with exception handling code or error parameters. This may simply be a matter of personal preference or consis tency. Using Exception Handlers in Clients or Servers You can handle exceptions by writing exception handler code in the application to recover from an error or gracefully exit the application. Microsoft RFC supplies a set of macros as a framework to handle exceptions in your client or server code. (Example 5-6, in Chapter 5, uses RFC exception handling macros.) If your applica tion is written for Win32 only, use the Win32 versions of these macros. Using Remote Procedure Parameters to Handle Errors In your ACF, you can add error parameters to remote procedures in order to con veniently handle communication and server errors. The RFC runtime library then stores errors values in these parameters rather than raising exceptions. You can also use a combination of exception handlers and error parameters. When the simple arithmetic application in Chapter 1 encounters some errors, it returns a hexadecimal number. You must convert this number to a decimal error code number and then look it up to find out what happened. By making three simple changes to the arithmetic application you can get it to return the actual RFC error code: Chapter 3: How to Write Clients 73 Add an error_status_t parameter to the remote procedure declaration in the MIDL file. Add an error_status_t parameter to the remote procedure implementation in the server. Declare the variable in the client file. The sum_arrays procedure declaration in the following MIDL file has an [out] parameter of the type error_status_t. void sum_arrays ( /* The sum_arrays procedure doesn t 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 */ [out] error_status_t *rpc_status /* error parameter is passed out */ We ve added the error_status_t parameter to the remote procedure in man ager. c. The initialized status value can be changed by the stubs if an error occurs. void sum_arrays (a, b, c, rpc_status) /* sum_arrays implementation */ long_array a; long_array b; long_array c; error_status_t *rpc_status ; /* error status parameter */ int i; *rpc_status = RPC_S_OK; /* initializes the status value */ for(i = 0; i < ARRAY_SIZE; i++) c[i] = a[i] + b[i]; /* array elements are added together */ The client code declares the variable along with the rest of the variables. Then a CHECK_STATUS macro converts the RFC error code to a status message. /* FILE NAME: client. c */ /* This is an arithmetic client module with error handling. */ #include <stdio.h> #include <stdlib.h> Mnclude "ari,th.h" /* header file created by MIDL compiler */ ttinclude "status. h" /* needed for CHECK_STATUS macro */ 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; /* declare variable and initialize */ error_status_t rpc_status=RPC_S_OK; /* remote procedure with status */ sum_arrays(a, b, result, &rpc_status ) ; 74 Microsoft RFC Programming Guide I* report error and abort */ CHECK_STATUS (rpc_status, "ERROR:", ABORT); puts ("sums: ") ; for(i =0; i < ARRAY_SIZE; i++) printf ( "%ld\n" , result [i] ) ; } The CHECK_STATUS macro shown in Example 3-13 converts the RFC error code to an error message. Example 3~ 13: The CHECK_STATUS Macro /* FILE NAME: status. h */ ttinclude <stdio.h> ttinclude <stdlib.h> #include "..\rpcerror.h" /* maps error codes to error messages O */ #def ine RESUME #def ine ABORT 1 #define CHECK_STATUS ( input_status , comment, action) \ { \ if (input_status != RPC_S_OK) { \ error_stat = DceErrorinqText ( input_status , error_string) ; \ /* */ fprintf (stderr, "%s %s\n", comment, error_string) ; \ if (action == ABORT) \ exit(l); \ } \ static int error_stat; static unsigned char error_string[DCE_C_ERROR_STRING_LEN] ; O The file rpcerror.h is shown in Appendix C, The Arithmetic Application. Although Microsoft RFC does not support the DCE RFC routine dce_error_inq_text , we ve emulated its function here. Compiling and Linking Clients Figure 3-3 shows the files and libraries required to produce an executable client. When complex data types are used, the MIDL compiler produces the client stub auxiliary file (appl_x.c) when the interface is compiled. Example 3-14 shows the portion of a makefile that: Compiles the C language stubs and client code along with the header file pro ducing server object files Links the server object files to produce the executable server file Chapter 3: How to Write Clients 75 Write client application files. Include the header file(s) produced by interface compilation. Generate client application and stub object files. Use the client stub and auxiliary files produced by interface compilation. Create the executable client file by linking the client application, stub, and auxiliary object files with the Microsoft RFC library. Text Editor Figure 3~3- Producing a client Example 3~14: Using a Makefile to Compile and Link a Client # FILE NAME: Makefile # Makefile for the inventory application implicit client # # definitions for this make file # APPL=inv IDLCMEfcmidl NTRPCLIBS=rpcrt4 . lib rpcns4.1ib libcmt.lib kerne!32.1ib ! include <ntwin32 .mak> ## NT c flags cflags = -c -WO -Gz -D_X86_=1 -EWIN32 -EMT /I. /I., /nologo 76 Microsoft RFC Programming Guide Example 3~14: Using a Makefile to Compile and Link a Client (continued) ## NT nmake inference rules $(cc) $(cdebug) $(cflags) $(cvarsmt) $< $(cvtomf ) # # CLIENT BUILD # client: client.exe client.exe: client. obj getbind.obj intbind.obj $ (APPL)_c.obj $ (APPL)_x.obj $(link) $(linkdebug) $(conflags) -out : client . exe -map: client. map \ client. obj getbind.obj intbind.obj $(APPL)_c.obj $ (APPL)_x.obj \ $(NTRPCLIBS) # client and server sources client. obj: client. c $(APPL).h getbind . obj : getbind . c intbind . obj : intbind . c Local Testing You can compile a local version of your client to test and debug remote proce dures without using remote procedure calls. To do a local test, compile the client object files and remote procedure implementations without the stub or auxiliary files. The code that finds a server is also unnecessary for a local test. Applications in this book use the compiler directive, /DLOCAL, to distinguish a test compilation used in a local environment from a compilation used in a distributed environment. Example 3-15 shows the portions of a makefile that produce the inventory applica tion for local testing. Example 3~15: Using a Makefile to Produce a Local Version of an Application # FILE NAME: Makefile # Makefile for the inventory application implicit client # # definitions for this make file # APPL=inv IDLCMD=midl NTRPCLIBS=rpcrt4 . lib rpcns4.1ib libcmt.lib kerne!32.1ib ! inc lude <ntwin3 2 . mak> ## NT c flags cflags = -c -WO -Gz -D_X86_=1 -DWTN32 -EMT /I. /I., /nologo ## NT nmake inference rules Chapter j. How to Write Clients 77 Example 3~15: Using a Makefile to Produce a Local Version of an Application (continued) $(cc) $(cdebug) $(cflags) $(cvarsmt) $< $ (cvtomf ) # # LOCAL BUILD of the client application to test locally # local : Iclient . exe lclient.exe: Iclient. obj Imanager.obj invntry.obj $(link) $(linkdebug) $(conflags) -out: Iclient. exe -map: Iclient .map \ Iclient. obj Imanager.obj invntry.obj \ $(NTRPCLIBS) # Local client sources invntry . obj : . . \ invntry . c $(cc) $(cdebug) $(cflags) $(cvarsmt) /DLOCAL /I. /I.. \ / Foinvntry . obj . . \ invntry . c Iclient. obj: client. c $(APPL).h $(cc) $(cdebug) $(cflags) $(cvarsmt) /DLOCAL /I. /I.. \ /Folclient.obj client. c Imanager.obj : . . \manager.c $ (APPL) .h $(cc) $(cdebug) $(cflags) $(cvarsmt) /DLOCAL /I. /I.. \ /Folmanager . obj . . \manager . c In this Chapter: Kinds of Pointers Kinds of Arrays Memory Usage Pointers, Arrays, and Memory Usage In C, pointers and arrays have a close correlation due to the way applications access the information they contain. Pointers and arrays work essentially the same in distributed and local applications. But there are a few restrictions in distributed applications because the client and server have different address spaces. In most of this chapter we discuss pointers and arrays for clients. See also Chapter 5, How to Write a Server, for a discussion of memory allocation for pointers and arrays in remote procedures. To make your applications more efficient, MIDL offers several kinds of pointers and arrays to reduce network traffic and stub overhead. This chapter uses the inventory application to demonstrate the use of pointers and arrays in distributed applications. Kinds of Pointers A pointer is a variable containing the address of another data structure or variable. As in C, you declare a pointer in an interface definition by using an asterisk (*) followed by a variable. For example, the inventory application has the following procedure declaration: void whatis_part_price( /* get part price from inventory */ [in] part_num number, [out] part_price *price ); In a distributed application, the client and server do not share the same address space. This means the data a pointer refers to in the client is not available in the remote procedure of the server. The opposite is also true. Therefore, pointer data is copied between the client and server address spaces during a remote procedure call. For the whatis_part_price procedure, data that the pointer argument refers to on the server is copied back to the client and placed in the memory referred to by 79 80 Microsoft RFC Programming Guide the price pointer. This copying of pointer data does not occur during a local pro cedure call. MIDL has three kinds of pointers: reference, unique, and full. We ll describe them here in order of increasing capability. Keep in mind, though, that more capabilities result in more stub overhead. A reference pointer is used to refer to existing data. It is the simplest kind of pointer. Consequently, it has a performance advantage over other kinds of point ers because stub overhead is minimal. For example, the whatis_part_price proce dure uses a reference pointer. This procedure passes by reference a pointer to an allocated part_price data structure. The remote procedure returns output data to the same memory location with the part price. Thus, for reference pointers, the data can change but not the address itself. The [ref ] attribute specifies a refer ence pointer in an interface definition. Sometimes you need a pointer that can do more, such as handling singly-linked lists in which the end of the list is marked with a null pointer. For this situation, a unique pointer might be used because it can be null. A unique pointer has many capabilities usually associated with pointers. Use unique pointers in interface defi nitions when a remote procedure call allocates new memory for the client. In this case, the client stub actually allocates the memory. Unique pointers cannot address data that is also addressed by other pointers in the remote procedure, so you should avoid using complex data structures with cycles (doubly-linked lists). The [unique] attribute specifies a unique pointer in the interface definition. A full pointer has all of the capabilities associated with unique pointers. In addi tion, it allows two pointers to refer to the same address, as in a double linked list. The [ptr] attribute specifies a full pointer in the interface definition. Full pointer capability incurs significant stub overhead, so use full pointers only when neces sary. A pointer attribute must be applied where the pointer is defined with an asterisk. For instance, if you define a typedef that resolves to a pointer, you cannot apply the pointer attribute where you use the typedef. The following sections discuss the use of pointers, and tell you when you need a reference or full pointer. Table 4-1 and Example 4-5 summarize what you need to know to declare and use pointers. Pointers as Output Parameters Due to the overhead of transmitting data, you have to declare MIDL parameters to be input, output, or both. In MIDL, as in C, input parameters are passed in by value, which means a copy of each input parameter is available in the procedure. Passing input parameters by value makes sense for a small amount of data. This technique offers simplicity since programmers don t have to deal with pointers or addresses. However, passing by value also means that any change to the variable Chapter 4: Pointers, Arrays, and Memory Usage in the procedure cannot reflect back to the original parameter when the call com pletes. To fill in data for an output parameter (or modify an input/output parameter), both C and MIDI must pass by reference a memory address using a pointer or array parameter. During a remote procedure call, the parameter refers to existing memory, which is passed by reference to the client stub. When the remote proce dure completes execution, data is sent back by the server stub to the client stub, which unmarshalls it into the memory referred to by the pointer. Therefore, the data is available to the client application when the client stub returns to the appli cation. Example 4-1 shows an output parameter in the whatis_part_price procedure dec laration from the inventory interface definition. Pointer parameters (*price) are reference pointers by default. Example 4-1: Defining an Output Parameter void whatis_part_price ( /* get part price from inventory */ [in] part_num number, [out] part_price *price /* reference pointer */ ); The part_price structure must be allocated in the client prior to the remote pro cedure call, but values are assigned in the remote procedure and transmitted back. The whatis_part_price remote procedure call in the client looks like this: part_record part; /* structure for all data about a part */ case p : whatis_part_price (part. number, & (part. price) } ; printf ( "price: %10.2f\n", part. price. per_unit) ; break; In the server, whatis_part_price reads a part record from the database for the part number input. It then assigns the values from the part record to the price structure members. Finally, the procedure returns and the price information is marshalled and transmitted by the server stub. The whatis_part_price remote procedure looks like this: void whatis_part_price (number, price) part_num number; part_price *price; { parc_record *part; /* a pointer to a part record */ . r ead_part_record ( number , &par t ) ; price->units = part->price. units; price->per_unit = part->price.per_unit; return; 82 Microsoft RFC Programming Guide You can see from the preceding explanation that an output parameter must refer to existing storage on the client, and therefore that it is always a reference pointer. In fact, the MIDL compiler refuses to let you declare an output-only parameter with the unique or ptr attribute. Suppose we don t know how much memory should be allocated for output data, so we want a procedure to return data in a parameter as newly allocated memory. We cannot just allocate some memory and hope it s enough because if the data output is greater, data will overwrite into other memory. To solve this, we pass a pointer to a pointer. We describe how to do this later in the chapter. A parameter used as both input and output is passed by reference. Programs com monly modify data by passing a pointer to a data structure into a procedure, which passes back the same pointer but with modified data. Optional (NULL) parameters can be used as input/output parameters. This feature is described in the following section. Pointers as Input Parameters Suppose our inventory interface has the following procedure declaration: void store_parts ( [in] part_record *partl, [in] part_record *part2 ); Assume this procedure adds new parts to the database. The procedure takes as parameters two pointers to structures of type part_record, (already defined in the interface) to store all data about a part. The remote procedure call in a client can look like the following: part_record *partl, *part2; parti = (part_record *)malloc(sizeof (part_record) ) ; part2 = (part_record *)itialloc(sizeof (part_record) ) ; /* part structures are filled in */ partl->nuniber = 123; part2->number = 124; store_parts (parti , part2 ) ; In this simple case, the client stub marshalls and transmits the data the pointers refer to. (This procedure is not implemented in any applications in this book, so no server code is shown.) One reason reference pointers reduce overhead is that the stubs make certain assumptions about the use of the pointer. Since pointer parameters are reference pointers by default, one of these assumptions is that a pointer parameter points to valid data of the type specified. Chapter 4: Pointers, Arrays, and Memory Usage 83 Suppose we want optional parameters in our procedure definition. In this case, the client passes a null pointer value for the parameter, so the remote procedure knows to ignore it. For the stubs to know the parameter is a null value, the param eter must be a unique pointer so the stubs do not attempt to copy any data for the parameter. Example 4-2 shows how to modify our store_parts procedure declaration so that both parameters are unique pointers. Example 4-2: Defining Optional Procedure Parameters void store_parts_l ( /* O */ [in, unique] part_record *partl, [in, unique] part_record *part2 typedef [unique] part_record *part_record_ptr; void store_parts_2 ( /* */ [in] part_record_ptr parti, [in] part_record_ptr part2 O To specify an optional parameter, use the unique attribute on an input (or input/output) parameter. As an alternative to method 1 for specifying an optional parameter, define a unique pointer data type and use the data type for the procedure parameter. The client can now supply a NULL pointer: store_part s_l ( part 1 , NULL ) ; If an input/output parameter is a unique pointer with a null value on input, it is also null on output because the client does not have an address to store a return value. Microsoft RFC allows two pointers to refer to the same data. This practice is known as pointer aliasing. To minimize overhead, stubs cannot manage more than one reference pointer referring to the same data in a single remote procedure call. For example, suppose our store_parts procedure does something useful if we pass in the same pointer 84 Microsoft RFC Programming Guide for both arguments. The following type of remote procedure call causes unpre dictable behavior: store_parts (parti, parti); /* WRONG do not use ref pointer aliasing */ This call will not work as expected because the parameters (reference pointers) both point to the same address. Reference pointers and unique pointers do not allow two pointers to refer to the same data. If store_parts_l were used instead of store_parts, the call would work correctly, because the arguments were specifically defined in the interface definition as full pointers with the ptr attribute. Using Pointers to Pointers for New Output A pointer refers to a specific amount of memory. For a procedure parameter to output newly allocated memory, we use a pointer to refer to another pointer that refers to data (or to another pointer and so on). This is also known as multiple levels of indirection. If you use just one pointer for a procedure parameter, you would have to make two remote procedure calls to allocate new memory. The first remote procedure call obtains the size of the server s data structure. Then the client allocates memory for it. The second remote procedure call obtains data from the server and fills the previously allocated memory. In a distributed application, using two pointers allows the client and server stubs to allocate all the necessary memory in one remote procedure call. The client stub must generate a copy of the memory allo cated on the server. The whatare_subparts procedure in the inventory application contains a parameter with a pointer to a pointer: [out] part_list **subparts The procedure allocates memory for the left pointer, and the right pointer is a parameter passed by reference to return the address of the left pointer. To accom plish this, MIDI must use two kinds of pointers: The right pointer is a reference pointer and the left pointer is a unique pointer. The reference pointer by itself cannot have new memory automatically allocated because it will point to the same address throughout the remote call. However, for the unique pointer, the amount of memory allocated by the server is allocated automatically by the client stub when the call returns. When a pointer attribute is applied in an interface definition where there are pointers to pointers, it applies only to the right pointer and does not propagate to any other pointers. Chapter 4: Pointers, Arrays, and Memory Usage 85 Example 4-3 demonstrates how to return data in a parameter by using two point ers. The procedure needs to output a data structure (in this case a structure with a conformant array). The final size of the data structure is unknown when you call the remote procedure. Example 4-3: Defining Pointers to Pointers for Memory Allocation pointer_default (unique) /* the pointer default is a unique pointer O */ ] interface inventory void whatare_subparts ( /* get list of subpart numbers for a part */ [in] part_num number, [out] part_list **subparts /* a pointer to a pointer ) */ ); O Parameters or type definitions with multiple pointers use a pointer default to specify the kind of pointer for all but the right one. To establish a pointer default, use the pointer_default attribute in the interface definition header. In this example, the unique argument establishes a unique pointer default. If memory is allocated during remote procedure execution, output parameters require multiple pointers. By default, the right pointer of a procedure parame ter is a reference pointer. The left pointer must be a unique pointer. This is accomplished through the pointer_default attribute. The part_list structure is allocated during the remote procedure call. On the server, the remote procedure allocates memory and assigns data. The server stub marshalls and transmits the data back to the client. The server stub then frees the memory allocated in the remote procedure. The client stub allocates memory and unmarshalls the transmitted data into the new memory. The remote procedure call in a client for whatare_subparts looks like: part_record part; . /* structure for all data about a part */ part_list *subparts; /* pointer to parts list data structure */ case s : whatare_subparts ( part. number, &subparts) ; for(i = 0; i < subpart s->size; i++) printf ( " %ld " , subpart s->numbers [ i ] ) ; printf ("\ntotal number of subpart s : %ld\n ", subparts->size) 86 Microsoft RFC Programming Guide When you finish with the data, free the memory allocated by unique pointers: free ( subparts ) ; break; See Example 5-9 in Chapter 5 for the server implementation of the remote proce dure whatare_subparts. Pointers as Procedure Return Values As we have described previously, the client must allocate memory for reference pointer data before it is used in a remote procedure call. This simplifies the client stub by giving unmarshalling code a place to put data after the server sends it. Now consider the following remote procedure call in client application code: unsigned long *a; a = proc ( ) ; The address of the procedure assignment, a, is available only when the procedure returns, and not during its execution. Therefore, we cannot use the method just described for a reference pointer to allocate memory in the client prior to the call, and expect the stub to complete the assignment for us. Procedures that return pointer results always return full pointers, so that the stub allocates any necessary memory and unmarshalls data into it for us. Example 4-4 shows a procedure that returns a pointer. Example 4-4: Defining a Procedure that Returns a Pointer typedef [string, unique] char *paragraph; /* description of part O */ paragraph get_part_description( /* return a pointer to a string */ [in] part_num number ); O A pointer attribute (unique) on a pointer data type (char *paragraph) speci fies the kind of pointer for that data type wherever it is used in the interface. (If a pointer data type does not have a pointer attribute, the pointer specified with the pointer_default attribute applies.) To specify a pointer to a string, apply the string attribute as well. @ Procedures that return a pointer result always return a full pointer. A proce dure result cannot be a reference pointer because new storage is always allo cated by the client stub, which copies data into it when the call returns. The call to get_part_description looks like: part_record part; /* structure for all data about a part */ Chapter 4: Pointers, Arrays, and Memory Usage 87 case d : part. description = get_part_description (part. number ); printf ( "description: \n%s\n" , part .description) ; When you finish with the data, free the memory allocated by unique pointers: if (part. description != NULL) free (part .description) ; /* free memory allocated */ On the server, the remote procedure allocates memory that the server stub copies and transmits back to the client. The server stub then frees the memory allocated. Example 5-8 shows how to allocate memory in the get_part_description remote procedure. Pointer Summary As reference pointers, unique pointers, and full pointers represent increases in capability, they also require increases in stub overhead needed to manage them. Therefore, you must differentiate among reference, unique, and full pointers in the interface definition. Table 4-1 summarizes and compares pointer types. Example 4-5 shows how to recognize which kind of pointer applies in an interface defini tion. A visible ref or unique pointer attribute overrides a default. Table 4-1: A Summary of Reference and Unique Pointers Reference Pointer Unique Pointer Full Pointer Attribute name ref unique ptr Characteristics Provides indirection Multiple levels of Indirection and full where the value is indirection pointer capabilities always the address of valid data Stub overhead Minimum Moderate Maximum Value of NULL Cannot be NULL Can be NULL Can be NULL Address value Never changes May change when a May change when a when a call returns call returns call returns Storage . Storage exists prior Storage is allocated Storage is allocated to the call automatically if automatically if needed needed Input and out- Data is written into The storage location The storage location put parameter existing storage of data on output of data on output when the call re may be different may be different turns from the storage lo from the storage lo cation on input. If cation on input. If the input value is the input value is NULL, the output NULL, the output value is also NULL. value is also NULL. 88 Microsoft RFC Programming Guide Table 4-1: A Summary of Reference and Unique Pointers (continued) Reference Pointer Unique Pointer Full Pointer Output parame ter Parameter is a refer ence pointer by de fault Not allowed Not allowed Input parameter Data is read from existing storage Data is read from existing storage; if the value is NULL, no data is read Data is read from existing storage; if the value is NULL, no data is read Pointer aliasing Not allowed Not allowed Allowed Example 4-5: How to Determine Kinds of Pointers pointer_default (unique) ; ] inventory interface /*O*/ typedef [string, unique] char *paragraph ;/**/ paragraph get_part_description ( [in] part_num number, /**/ void whatis_part_price( [in] part_num number, [out] part_price *price /* O */ void whatare_subparts ( [in] part_num number, [out] part_list **subparts /* */ typedef struct { [ref] part_num *number; /* */ Chapter 4: Pointers, Arrays, and Memory Usage 89 Example 4-5: How to Determine Kinds of Pointers (continued) [ref] part_quantity *quantity; [ref] account_num *account; } part_order; void store_parts_l ( [in, unique] part_record *partl, [in, unique] part_record *part2 O The MIDL compiler attempts to assign the appropriate kind of pointer to pointers without a full, unique, or ref attribute. The pointer_default interface header attribute specifies which kind of pointer applies when one cannot be automatically determined. You can give the pointer_default attribute an argument of ref, unique, or full. If a pointer attribute is not specified for the data type, the interface requires a pointer default to specify the kind of pointer for the following cases: Pointers in typedefs (see callout 2) Multiple pointers other than the right pointer (see callout 5) Pointers that are members of structures or cases of discriminated unions (see callout 6) A pointer type attribute specifies the kind of pointer used. In this example, all occurrences that use the paragraph data type are unique pointers. If none of the pointer attributes ref, unique or full is present in the typedef, the pointer_default attribute specifies the kind of pointer. A pointer return value of a procedure is always a unique pointer because new memory is allocated. The paragraph data structure is a pointer to a string. O A pointer parameter of a procedure is a reference pointer by default. Parame ter reference pointers must always point to valid storage (never null). (See also callout 7.) With multiple pointers, the pointer_default attribute specifies all pointers except the right-most pointer. In this example, the right pointer is a reference pointer because it is a parameter pointer. The left pointer is determined by the pointer default. In this procedure, the left pointer must be a unique pointer so the array of parts in the subparts structure is automatically allo cated by the client stub when the call returns. When a structure member or discriminated union case is a pointer, you must assign it a unique or ref attribute, either explicitly or through the attribute pointer_default. This interface definition specifies the structure members as reference pointers in order to override the unique pointer default. Unique or 90 Microsoft RFC Programming Guide full pointers are unnecessary for these structure members; therefore, it is more efficient to use reference pointers to minimize the overhead associated with unique pointers. An input or input/output pointer parameter can be made an optional proce dure parameter by applying the unique attribute. An attribute of either unique or ptr is required if you pass a value of NULL in a call. Kinds of Arrays You can use the following kinds of arrays in RFC applications: Fixed arrays contain a specific number of elements defined in the interface definition. They are defined just like standard C declarations. Varying arrays have a fixed size but clients and servers select a portion to transmit during a remote procedure call. The interface definition specifies sub set bound variables used by the clients and servers to set the bounds. Conformant arrays have their size determined in the application code. The interface definition specifies an array size variable that the clients and servers use to control the amount of memory allocated and data transmitted. Selecting a Portion of a Varying Array For some clients or servers you need to use only a portion of an array in a remote procedure call. If this is the case, it is more efficient to transmit only the needed portion of the array. Procedures or structures that use varying arrays with data limit variables allow you to select the portion of an array that is processed by a remote procedure call. A varying array has a fixed size when the application is compiled, but the portion of the array that contains the relevant, transmissible data is determined at runtime. For example, given the varying array arr [100] , you can specify any index values in the range < L < U < 99, where L represents the lower data limit of the array and (/represents the upper data limit. An array is varying if you declare it in your interface definition with two extra attributes: first_is to indicate where transmission starts (Z), and either length_is or last_is to indicated where transmission stops ((/). Whether you use length_is or last_is depends on convenience. Suppose that the following procedure appears in an interface definition: const long SIZE = 100; void proc ( [in] long first, [in] long length, Chapter 4: Pointers, Arrays, and Memory Usage 91 [in, first_is( first) , length_is (length) ] data_t arr[SIZE] ); To select a portion of the array to transmit, assign values to the variables first and length. For input parameters, the client sets them prior to the remote proce dure call. Be sure the upper data limit value does not exceed the size of the array, for example: long first = 23; long length = 54; data_t arr[SIZE] ; proc ( first, length, arr); The transmitted array portion is represented by the indices |23| . . . [76| (23 + 54 - 1). The entire array is available in the client and the server, but only the portion represented by the data limit variables is transmitted and meaningful for the given remote procedure call. If the data limit parameters are also output, the remote pro cedure can set them to control the portion of the array transmitted back to the client. A structure is an alternate way to define a varying array in an interface definition; for example: typedef struct varray_t { long first; long length; [first_is( first) , length_is ( length) ] data_t arr [SIZE] ; } varray_t; proc ([in] varray_t varray) ; Managing the Size of a Conformant Array Conformant arrays are defined in an interface definition with empty brackets or an asterisk (*) in place of the first dimension value. . . . cl[*] . . . . . . c2[] [10] . . . The conformant -array cl [ * ] has index values [o] ... \M\ in which the dimension variable, M, represents the upper bound of the array. The dimension variable is specified in the interface definition and used in the application code at runtime to establish the array s actual size. To specify an array size variable or a maximum upper bound variable, use one of the array size attributes, size_is or max_is, in an interface definition. These vari ables are used in the application to represent the size of the array. You can use either one, depending on which you find most convenient. Example 4-6 shows how a conformant array is defined in a structure. 92 Microsoft RFC Programming Guide Example 4-6: A Conformant Array in an Interface Definition typedef struct part_list{ long size; /" numoer or pares in array w "/ [size_is(size) ] part_num numbers [*]; /* conformant array of parts*/ } part_list; /* list of part numbers */ /* number of parts in array O */ typedef struct part_record { /* data for each part */ part_num number; part_name name; paragraph description; part_price price; part_guantity quantity; part_list subparts; /* Conformant array or struct must be last */ } part_record; void whatare_subparts ( [in] part_num number, [out] part_list **subparts /* get list of subparts numbers for a part */ /**/ O When an array member of a structure (numbers [*]) has an array attribute, the dimension variable (size) must also be a structure member. This assures that the dimension information is always available with the array when it is trans mitted. The dimension variable member must be, or must resolve to, an inte ger. The size_is attribute specifies a variable (size) that represents the number of elements the array dimension contains. In the application, the array indices are ... |size-l[ . For example, if size is equal to 8 in the application code, then the array indices are If a conformant array is a member of a structure, it must be last so that your application can allocate any amount of memory needed. A conformant struc ture (structure containing a conformant array member) must also be the last member of a structure containing it. O Use a conformant structure and multiple levels of indirection for remote pro cedures that allocate a conformant array. Chapter 5 implements this proce dure. To specify a variable that represents the highest index value for the first dimension of the array rather than the array size, use the max_is attribute instead of the size_is attribute. For example, the conformant structure defined in Example 4-6 can also be defined as follows: Chapter 4: Pointers, Arrays, and Memory Usage typedef struct part_list{ long max; [max_is (max) ] part_num numbers [*] ; } part_list; The variable max defines the maximum index value of the first dimension of the array. In the application, the array indices are \Q\ . . . |max| . For example, if max is equal to 7 in the application code, then the array indices are To avoid making mistakes in application development, be consistent in the inter face definitions you write. Use either the size_is attribute or the max_is attribute for all your conformant arrays. Conformant arrays as procedure parameters When you call a remote procedure that contains a conformant array, you must pass the number of elements that are contained by the array. When a client calls the whatare_subparts remote procedure of Example 4-3, the dimension informa tion is available in the part_list structure. However, if an array is passed as a parameter, the dimension information must also be an in parameter of the proce dure. For example, instead of obtaining an array of all the subparts for a part (as the ivhatare_subparts procedure does) you may want only the first five subparts. This procedure is defined as follows: void get_n_subparts ( /* get n subpart numbers for a part */ [in] part_num number, [in] long n, [out,size_is (n) ] part_num subparts [] ); In the client, the input includes the part number, a 5 representing the number of subparts desired, and a previously allocated array, large enough for the five sub- part numbers. The output is the array with the first five subpart numbers. (The get_n_subparts procedure is not defined in the inventory interface definition.) Dynamic memory allocation for conformant arrays Suppose the following procedures appear in interface definitions: procl([in] long size, [in, size_is (size) ] data_t arr[]); proc2([in] long max, [in, max_is(max)] data_t arr[]); You have to allocate memory for each array needed in the application. To allocate dynamic memory for conformant arrays, use a scheme such as the following: unsigned long s,m; data_t *s_arr, *m_arr; /* pointers to some data structures */ /* some application specific constants */ s = SIZE; m = MAX; 94 Microsoft RFC Programming Guide I* allocation of the arrays */ s_arr = (data_t *)malloc( (s) * sizeof (data_t) ) ; m_arr = (data_t *)malloc( (m+1) * sizeof (data_t) ) ; /* the remote procedure calls */ proc 1 ( s , s_ar r ) ; proc2 (m, m_arr) ; In this example, SIZE is defined in the client to represent an array size and MAX is defined to represent the maximum index value of an array. Notice an array that has the max_is attribute in its interface definition must have an extra array ele ment allocated because arrays begin with an index value of 0. Memory allocation for conformant structures Structures containing a conformant array require memory allocation in the client before they are input to a remote procedure call, because a statically allocated conformant structure has storage for only one array element. For example, the fol lowing is the part_list structure of the inventory interface: typedef struct part_list{ long size; [size_is (size) ] part_num numbers [*] } part_list; The structure in the header file generated by the MIDI compiler has an array size of only one, as follows: typedef struct part_list { unsigned long size; part_num numbers [ 1 ] ; } part_list; The application is responsible for allocating memory for as much of the array as it needs. Use a scheme such as the following to allocate more memory for a confor mant structure: part_list *c; /* a pointer to the conformant structure */ long s; s = 33; /* the application specific array size */ c = (part_list * )malloc( sizeof (part_list) + (sizeof (part_num) * (s-1) )); Notice that since the declared structure s size contains an array of one element representing the conformant array, the new memory allocated needs one array ele ment less than the requested array size. Memory Usage Distributed applications usually involve more complicated memory management than single-system applications because the address spaces are on separate machines. Fortunately, for many programming situations, Microsoft RPC s default Chapter 4: Pointers, Arrays, and Memory Usage 95 memory usage method can automate most of the memory management details, freeing programmers to concentrate on the application itself. In the default method, memory on clients and servers is allocated automatically by the stub code for each part of the data structure being stored. However, while this automation is certainly convenient, it can sometimes result in large stub code and slower performance, especially when the data structures being managed are complex. Consequently, Microsoft RFC offers alternative memory usage methods which can help optimize performance, decrease stub size, or let you tailor your application to specific programming circumstances. Before we look at specific methods, let s look at the kind of data structures that are passed between clients and servers. Sizeable amounts of data are usually passed between clients and servers as pointers. Simple pointer data can usually be handled by the stub code using the default memory management scheme. But more complex data structures such as linked lists might benefit from the use of alternative memory management methods. Linked lists can be made up of many nodes connected with pointers. The size of a linked list is often variable and mem bers need to be inserted or deleted in the middle easily. A two-dimensional linked list could represent a sparse array which your applica tion sends to a compute server to be multiplied. Tree structures are a natural form for parsed language data. For example, you might call a "parse server" with a file name and it could return a syntax tree of the data broken down according to grammar rules. Arithmetic expressions are often represented internally in tree form. Graphs of nodes are used in resource allocation problems, usually represent ing networks of computers, of cities, and so on. In any case, linked lists consist of multiple nodes which must be allocated storage space in both clients and servers. By default, the client and server stub code which marshalls and unmarshalls data uses a crude but effective algorithm to manage the pointers. It makes separate calls to midl_user_allocate and midl_user_free to allo cate and deallocate each individual node in the data structure. While this approach can add stub overhead to the application, it relieves you from having to concern yourself with memory management details. In addition to the default method, there are three other memory usage methods which you can use by including ACF attributes or by making slight changes to the IDL file. The methods together, are: node-by-node allocation and deallocation (the default) single buffer allocation client application allocated buffers persistent storage on the server Of the four methods, the first two rely solely on the stubs to allocate and free memory while the last two involve the application. In previous chapters we 96 Microsoft RFC Programming Guide explained that you must include user-written versions of midl_user_allocate and midl_user_free in both the client and server parts of your application. The reason for this is that the client and server stubs or, in some cases, your application code, calls these procedures to allocate and deallocate memory used by application parameters. Table 4-2 shows whether the stub code or the application is responsible for mem ory management in each method. Table 4-2: What Allocates Memory Client Stub Code Application Node by node alloca tion and deallocation user allocate Server Stub Code Application m idl_ user^free Single buffer alloca tion m idl_ user_allocate m idl_ user^free Client application- allocated buffers m idl_ user_allocate m idl_ user_Jree Persistent storage on the server midl_user_allocate l_ user_Jree The following sections examine the reasoning behind each memory usage method. The sections also describe how to use ACF attributes to select a method for use with a given situation. All of the alternative (non-default) memory usage methods use attributes that are extensions of DCE IDL. The use of these attributes requires the /ext MIDL compiler switch at compile time. Node-By-Node Allocation and Deallocation When you are passing simple pointers back and forth between a client and a server, you needn t worry about choosing a particular memory usage method. The stub code, which marshalls and unmarshalls parameters, will allocate and deallo cate memory for you on both the client and the server. On the other hand, separate stub calls to midl_user_allocate for each node in a complex linked list can add unnecessary stub overhead to the application. If you Chapter 4: Pointers, Arrays, and Memory Usage 97 are worried about the overhead, perhaps you could use this method to get your application up and running and then choose another method if you think memory usage is a bottleneck. Using Contiguous Server Memory When memory on the server is contiguous, as it ordinarily is with Microsoft Win dows NT, you might increase performance by directing the stub to allocate a sin gle linear buffer for the entire tree or graph. In this case, the client stub determines the size of the buffer needed by chasing all of the pointers in the structure. This approach relieves the server stubs from mak ing separate calls to midl_user_allocate for each node in the data structure. Because data can be accessed sequentially, memory performance might also be improved by using this technique. To use this technique, apply the ACF attribute allocate (all_nodes) to the pointer type in a typedef in the ACF file. /* ACF fragment */ typedef [allocate (all_nodes )] point er_name; Allocating Buffers with the Client Application When you know how big a data structure is, you can specify the buffer size in the client application and pass it to the server as a parameter to the remote procedure. This technique can help minimize the stub size on clients and servers and improve the performance of the affected remote procedure call because the client stub doesn t have to chase pointers. The server stub allocates the buffer space with one call to midl_user_allocate , using the size parameter taken from the remote proce dure call. The runtime library will raise an exception if insufficient memory is allo cated, however. After the call completes, the server stub frees the memory with one call to midl_user_Jree . The client side can benefit from this technique, too. For instance, say your applica tion has a multiplication interface that multiplies matrixes as in multiplyjnatrix (matrix *ml *m2 ) . -Now let s say that the client makes many calls to this same interface. In this case, it s probably more efficient for the client application to allo cate and control memory directly, reusing the memory that is allocated only once, rather than have the client stubs allocate and free memory with each call. Even when you know the buffer size, you might not want to take the time to use this technique. But if memory allocation causes a bottleneck in your application, the technique may help. This method requires two steps. First, add a size parameter to the procedure decla ration in the IDL file, as illustrated in the following IDL file fragment in which we include the parameter cBytes. Microsoft RFC Programming Guide I* IDL file function declaration (fragment) */ void GetEmployeeRecord ( [in, string] char EnployeeName [NAMESIZE] , [in] short cBytes, [out, ref] P_RECORD_TYPE pRecord /* record for named employee */ ); Second, in the ACF file, apply the ACF byte_count attribute to the parameter that will store the size of the buffer. /* ACF file (fragment */ GetEmployeeRecord ( [byte_count (cEytes) ] pRecord ) ; Now the server stub will make a single call to midl_user_allocate using the cBytes size parameter to allocate memory for this buffer. Persistent Storage on the Server Persistent state, or "context," offers a way to manage data on the server so that you can reuse it from call to call, and clean it up properly after you re done with it. One example of persistent state might be a dictionary server or a symbol table server. You pass the server a tree which it saves away, and then you make queries against it later. This technique can save time because your application does not need to copy the same data into a buffer each time it s needed. To use this method, apply the allocate(dont_free) attribute to the ACF typedef declaration in the ACF file, as in the following usage example. /* ACF fragment */ typedef [allocate (all_nodes, dont_free) ] pointer_name; Using this method, the server stub does not call midl_user_/ree when the remote procedure call completes. Instead, the server application must call midl_user_Jree when its procedures are finished using the data structure. To make the parameters available for use by other remote procedure calls on the server, you must copy the pointers to global variables. In Chapter 7, Context Handles, we ll see a different way of managing server con text through the use of context handle types. While context handles require more programming than the simpler persistent data technique mentioned here, they offer more automatic functions which you may want to use. For instance, context handles track and free memory resources automatically and they can associate server contexts with specific clients. In this Chapter: Some Background on Call Processing Initializing the Server Writing Remote Procedures Compiling and Linking Servers HOW tO W^tC d RPC servers are more complicated than clients at least at this introductory stage because the servers have a more complicated role: they have to be continuously active and be prepared to handle multiple calls in any order. This chapter uses the inventory example as the basis for showing the various issues required by servers. Before reading this chapter, it s a good idea to read Chapter 1, Overview of an RFC Application, for an overview of a distributed application, and Chapter 2, Using a Microsoft RPC Interface, for features of interface definitions. You should also read Chapter 3, How to Write Clients, to understand how clients use servers. You write the following two distinct portions of code for all servers: Server initialization includes most of the RFC-specific details including RPC runtime routines. This code is executed when the server begins, before it pro cesses any remote procedure calls. The manager portion, or remote procedure implementations, include special techniques for memory management. Some Background on Call Processing Chapter 1 describes how a typical distributed application works: Figure 1-9 shows the initialization steps to prepare a server before it processes remote procedure calls. Figure 1-10 shows how a client finds a server using the automatic binding method. Figure 1-11 shows the basic steps during a remote procedure call after the client finds the server. 99 100 Microsoft RFC Programming Guide To understand server initialization, it is useful at this point to explain how the RFC runtime library handles an incoming call. Figure 5-1 shows how the server system and RFC runtime library handle a client request. Server Host Server RFC Runtime Library Request Queue O Call is placed in request queue Call from client Figure 5- 1 . How the server runtime library handles a call A call request for the server comes in over the network. The request is placed in a request queue for the endpoint. (The server initialization can select more than one protocol sequence on which to listen for calls, and each protocol sequence can have more than one endpoint associated with it.) Request queues temporarily store all requests, thus allowing multiple requests on an endpoint. If a request queue fills, however, the next request is rejected. The RFC runtime library dequeues requests one at a time from all request queues and places them in a single call queue. The server can process remote procedures concurrently, using threads. If a thread is available, a call is imme diately assigned to it. (Server initialization can select the number of threads for processing remote procedure calls.) In this figure, only one thread is exe cuting. If all threads are in use, the call remains in the call queue until a thread is available. If the call queue is full, the next request is rejected. ) After a call is assigned to a thread, the interface specification of the client call is compared with the interface specifications of the server. An interface speci fication is an opaque data structure containing information (including the UUID and version number) that identifies the interface. Opaque simply means Chapter 5: How to Write a Server 101 the details are hidden from you. If the server supports the client s interface, processing goes to the stub code. If the server does not support the client s interface, the call is rejected. When the call finally gets to the stub, it unmarshalls the input data. Unmarshalling involves memory allocation (if needed), copying the data from the RFC runtime library, and converting data to the correct representation for the server system. Initializing the Server The server initialization code includes a sequence of runtime calls that prepare the server to receive remote procedure calls. The initialization code typically includes the following steps: 1 . Register the interface with the RFC runtime library. 2. Create server binding information by selecting one or more protocol sequences for the RFC runtime library to use in your network environment. 3. Advertise the server location so the clients have a way to find it. A client uses binding information to establish a relationship with a server. Advertising the server usually includes storing binding information in a name service database. Occasionally an application stores server binding information in an application-specific database, or displays it, or prints it. 4. Manage endpoints in a local endpoint map. 5. Listen for remote procedure calls. During server execution, no remote procedure calls are processed until the initial ization code completes execution. RFC runtime routines are used for server initial ization. (Table B-2 in Appendix B, RFC Runtime Routines Quick Reference, lists all the RFC runtime routines for servers.) Example 5-1 shows the necessary header files and data structures for server initial ization of the inventory application. Example 5-1: Server Header Files and Data Structures I* FILE NAME: server. c */ ftinclude <stdio.h> #include <stdlib.h> ftinclude <ctype.h> #include "inv.h" /* header created by the MIDL compiler O */ #include "status. h" /* contains the CHECK_STATUS macro */ #define STRINGLEN 50 main (argc, argv) int argc; char *argv [ ] ; { error_status_t status; /* error status (nbase.h) */ /* RFC vectors @ */ 102 Microsoft RFC Programming Guide Example 5-1: Server Header Files and Data Structures (continued) rpc_binding_vector_t *binding_vector; /* binding handle list (rpcdce.h) */ rpc_protseq_vector_t *protseq_vector; /*protocol sequence list (rpcdce.h) */ char entry_name[STRINGLEN] ; /* name service entry name */ char annotation [ STRINGLEN] ; /* annotation for endpoint map */ char hostname [ STRINGLEN] ; /* used to store the computer name */ EWORD hostname_size=STRINGLEN; /* required by GetComputerName */ /* For the rest of the server initialization, register interfaces, */ /* create server binding information, advertise the server, */ /* manage endpoints, and listen for remote procedure calls. */ O Always include the C language header file (created by the MIDL compiler) from all interfaces the server uses. This file contains the definitions of data types and structures that are needed by the RFC runtime routines. An unsigned32 variable is needed to report errors that may occur when an RFC runtime routine is called. Some RFC runtime routines use a data structure called a vector. A vector in RFC applications contains a list (array) of other data structures and a count of elements in the list. Vectors are necessary because the number of elements on the list is often unknown until runtime. The rpc_binding_vector_t is a list of binding handles in which each handle refers to some binding information. The list in rpc_protseq_vector_t contains protocol sequence information representing the communication protocols available to a server. RFC runtime routines create vectors, use vectors as input, and free the memory of vectors. Many header files such as rpc.h and rpcndr.h are included in the interface header inv.h. The rpc.h file in turn has included within it header files such as rpcdce.h, rpcnsi.h, and rpcnterr.h. Many of these header files are associated with RFC- specific interface definitions. These interface definitions contain data structure defi nitions you may need to refer to in order to access structure members and make runtime calls. Object UUIDs are scattered throughout the RFC runtime routines as parameters for developing certain kinds of applications. You do not need to use object UUIDs to develop many applications so they are not covered in this book. Registering Interfaces All servers must register their interfaces so that their information is available to the RFC runtime library. This information is used when a call from a client comes in, so the client is sure the server supports the interface, and the call can be correctly dispatched to the stub. Before a client makes a call, it checks its interface against the one advertised in the server s binding information. But that does not guarantee that the server supports Chapter 5: How to Write a Server 103 the client s interface. For example, it is possible for a complex server to temporar ily suspend support for a specific interface. Therefore, when a remote procedure call arrives, a comparison is made between the client s and server s interface speci fications. If the server supports the client s interface, the RFC runtime library can dispatch the call to the stub. Use an interface handle to refer to the interface specification in application code. An interface handle is a pointer defined in the C language header file and gener ated by midl. For example, the server interface handle for the inventory applica tion is inv_Vl_0_s_ifspec. The interface handle name contains the following: The interface name given in the interface definition header (inv). The version numbers in the version attribute (vl_0). If the interface definition has no version declared, version 0.0 is assumed. The letter s or c depending on whether the handle is for the server or client portion of the application. The word if spec. The default style of interface names generated by the midl version 2.0 compiler is compatible with names generated by the OSF DCE IDL compiler. Note that the midl version 1.0 compiler generates another form of interface handle name such as inv_ClientIfHandle and inv_Server If Handle. To generate older names that are compatible with midl version 1.0 interface names, you must use the /oldnames option with a midl version 2.0 compiler. Example 5-2 is a portion of C code that registers one interface. Example 5-2: Registering an Interface with the Runtime Library I* The header files and data structures precede registering interfaces. */ /************************** REGISTER INTERFACE ***************************/ status = RpcServerRegisterlf ( /* O */ inv_Vl_0_s_if spec , /* interface specification (inv.h) */ NULL, NULL ); CHECK_STATUS( status, "Can t register interface:", ABORT); /* */ /* For the rest of the server initialization, create server binding */ /* information, advertise the server, manage endpoints, and listen for */ /* remote procedure calls. */ 104 Microsoft RFC Programming Guide O The RpcServerRegisterlf routine is a required call to register each interface the server supports. The interface handle, inv_Vl_0_s_ifspec, refers to the inter face specification. The CHECK_STATUS macro is defined in the status. h file. It is an application- specific macro used in this book to process status values returned from RFC runtime calls (see Example 3-12). Multiple interfaces may be registered from a single server by calling the RpcSeruer- Registerlf routine with a different interface handle. The second and third arguments to the RpcServerRegisterlf call are used in com plex applications to register more than one implementation for the set of remote procedures. When only one implementation exists, these arguments are set to NULL. Also, in the event of a symbol name conflict between the remote procedure names of an interface and other symbols in your server (such as procedure names), you can use these arguments to assign different names to the server code s remote procedures. Creating Server Binding Information Server binding information is created when you select protocol sequences during server initialization. RFC uses protocol sequences (described in Chapter 3, How to Write Clients) to identify the combinations of communications protocols that RFC supports. Most servers offer all available protocol sequences so that you do not limit the opportunities for clients to communicate with the server. Recall that besides a protocol sequence, binding information includes a host net work address. A server process runs on only one host at a time, so this binding information is obtained from the system and not controlled in your server code. When a protocol sequence is selected, an endpoint is also obtained. You have sev eral choices when obtaining endpoints. Using dynamic endpoints Chapter 3 describes the difference between dynamic and well-known endpoints. Most servers use dynamic endpoints for their flexibility and to avoid the problem of two servers using the same endpoints. Dynamic endpoints are selected for you by the RFC runtime library and vary from one invocation of the server to the next. When the server stops running, dynamic endpoints are released and may be reused by the server system. Example 5-3 is a portion of the inventory server initialization showing the selection of one or all protocol sequences and dynamic endpoints. For this example, invoke the server with a protocol sequence argument to select a specific protocol sequence. If you invoke this server without an argument, the server uses all avail able protocols. Chapter 5: How to Write a Server 105 Example 5~3- Creating Server Binding Information I* Registering interfaces precedes creating server binding information. */ /****************** CREATING SERVER BINDING INFORMATION ******************/ if(argc > 1) { status = RpcServerUseProtseq( /* use a protocol sequence O */ (unsigned char *)argv[l], /* the input protocol sequence */ RPC_C_PROTSEC_MAX_REQS_DEFAULT, /* (rpcdce.h) */ NULL /* security descriptor (not reqd) */ ); CHEOK_STATUS( status, "Can t use this protocol sequence:", ABORT); } else { puts ("You can invoke the server with a protocol sequence argument."); status = RpcServerUseAllProtseqs ( /* use all protocol sequences */ RPC_C_PROTSEO_MAX_REQS_DEFAULT, /* (rpcdce.h) */ NULL /* security descriptor (not reqd) */ ); CHECK_STATUS( status, "Can t register protocol sequences:", ABORT); status = RpcServerlnqBindings ( /* get binding information for server*/ &binding_vector ); CHECK_STATUS ( status, "Can t get binding information:", ABORT); /* For the rest of the server initialization, advertise the server, */ /* manage endpoints, and listen for remote procedure calls. */ O The RpcServerUseProtseq routine is called with the chosen protocol sequence string. This call selects one protocol sequence on which the server listens for remote procedure calls. For this example, when the server is invoked, argc is the number. of arguments on the command line, and argv[l] is the protocol sequence string argument. The constant RPCjC_PROTSEQjyiAX_C^LLLS_DEFAULT sets the request queue size for the number of calls an endpoint can receive at any given moment. The RpcServerUseAllProtseqs routine is called to select all available protocol sequences on which the RFC runtime library listens for remote procedure calls. The RpcServerlnqBindings routine is a required call to obtain the set of bind ing handles referring to all of this server s binding information. 1 06 Microsoft RFC Programming Guide Dynamic endpoints must be registered with the server system s local endpoint map using the RpcEpRegister routine, so that clients can look them up when they try to find a server. Using well-known endpoints An endpoint is well-known if it is specifically selected and assigned to a single server every time it runs. Well-known endpoints are more restrictive than dynamic endpoints because, in order to prevent your servers from using the same end- points as someone else, you need to register well-known endpoints with the authority responsible for a given transport protocol. For example, the ARPANET Network Information Center controls the use of well-known endpoint values for the Internet Protocols. Well-known endpoints are often employed for widely-used applications. One server that needs well-known endpoints is the RFC service. This service runs on each system hosting RFC servers, maintaining the database that maps servers to endpoints. When a client has a partially bound handle, and it needs to obtain an endpoint for its application s server, the client RFC runtime library contacts the server system s RFC service. In short, the RFC service is required for finding dynamic endpoints. For clients to contact it, the RFC service itself must have a well-known endpoint. Although you do not need to register well-known endpoints in the server system s endpoint map, you are encouraged to, so that clients are unrestricted in finding your servers. Use the RpcEpRegister routine to register endpoints in the endpoint map. Table 5-1 shows the RFC runtime routines that create server binding information with well-known endpoints. Table 5-1: Creating Binding Information with Well-known Endpoints RFC Runtime Routine Description RpcServerUseProtseqEp Uses a specified protocol sequence and well-known endpoint, supplied in application code, to establish server binding information. Even though the endpoint is not dynamically generated, clients do not have an obvious way to get it. So the server must register the endpoint in the server system s endpoint map. RpcServerUseProtseqlf Uses a specified protocol sequence, but well-known endpoints are specified in the interface definition with the endpoint attribute. Both clients and servers know the endpoints through the interface definition. Chapter 5: How to Write a Server 107 Table 5-1: Creating Binding Information with Well-known Endpoints (continued) RFC Runtime Routine Description RpcSeruerUseAllProtseqsIf Uses all supported protocol sequences, but well- known endpoints are specified in the interface defini tion with the endpoint attribute. Both clients and servers know the endpoints through the interface def inition. Advertising the Server Advertising the server means that you make the binding information available for clients to find this server. You can advertise the server by one of the following methods: Export to a name service database. Store binding information in an application-specific database. Print or display binding information for clients. The method you use depends on the application, but the most common way is through a name service database. Binding information and the interface specifica tion are first exported to a server entry in the database. The information is associ ated with a recognizable name appropriate for the application. This information can now be retrieved by a client using this name. When the client imports binding information, the RFC runtime library compares the interface specifications of the client and the name service entries, to be sure the client and server are compati ble. Conventions for naming RFC server entries rely on associating a host computer name with the server entry name, thereby creating a unique server entry name. Unique server entry names allow multiple instances of a server to coexist in one NT domain. Although it s possible for multiple servers to share use of a single server entry, problems arise if the true owner of the entry removes the entry from the name service; binding information for all other servers is removed as well. Using this convention means that clients that use server entry names to find servers will need to know which computer a server is running on. Automatic clients usually seek servers based on the interface UUID so they are freed from having to know the server s computer name. When NT domains do not contain multiple instances of servers, you don t need to use the convention. If you plan to store your entry names in DCE CDS, you can also export a group entry name that is not associated with a computer name. The convention for nam ing RFC group entries includes the interface name. The server entry name is added as a member of the group. When the client imports binding information using the group name, the group members are searched until a compatible server entry is found. Microsoft RFC includes the API functions that control group and profile 108 Microsoft RFC Programming Guide operations for use with DCE CDS. Note, however, that the Microsoft Locator version 1.0 does not fully support group or profile operations. Example 5-4 is a portion of the inventory initialization code that uses the name service database to advertise the server. Example 5-4: Advertising the Server to Clients I* Registering interfaces and creating server binding information */ /* precede advertising the server. */ /*************************** ADVERTISE SERVER strcpy (entry_name, " / . : /inventory_" ) ; GetComputerNamet&hostname, &hostname_size) ; strcat (entry_name, hostname); status = RpcNsBindingExport ( /* export to a name service database O */ RPC_C_NS_SYNTAX_DEFAULT, /* syntax of entry name (rpcdce.h) */ (unsigned char *)entry_ name, /* name of entry in name service */ inv_Vl_0_s_ifspec / /* interface specification (inv.h) */ binding_vector, /* binding information */ NULL /* no object UUIDs exported */ ); CHECK_STATUS( status, "Can t export to name service database:", RESUME); /* For the rest of the server initialization, manage endpoints and */ /* listen for remote procedure calls. */ O The RpcNsBindingExport routine exports the server binding information to a name service database. The constant RPC_C_NS_SYNTAX_DEFAULT establishes the syntax the RFC runtime library uses to interpret an entry name. (Microsoft RFC currently has only one syntax.) The entry name is the recognizable name used in the database for this binding information. The interface handle (inv_Vl_0_s_ifspec) is needed so interface information is associated with the binding information in the name service database. The binding vector is the list of binding handles that represents the binding infor mation exported. (The NULL value represents an object UUID vector. For this application, no object UUIDs are used.) The RpcNsBindingExport routine exports well-known endpoints to the name ser vice database along with other binding information, but, because of their tempo rary nature, dynamic endpoints are not exported. Performance of the name service will degrade if it becomes filled with obsolete endpoints generated when servers restart. Also, clients will fail more often trying to bind to servers of nonexistent endpoints. Since dynamic endpoints are not in a name service database, clients need to find them from another source. The next section discusses how to manage endpoints. Chapter 5: How to Write a Server 109 Managing Server Endpoints When the server uses dynamic endpoints, clients need a way to find them, because neither the name service database nor the interface specification store dynamic endpoints. The endpoint map is a database on each RFC server system that associates endpoints with other server binding information. As a general rule, have your server store all endpoints (dynamic and well-known) in the endpoint map. If all endpoints are placed in the endpoint map, system administrators have an easier time monitoring and managing all RFC servers on a host system. When a client uses a partially bound binding handle for a remote procedure call, the RFC runtime library obtains an endpoint from the server system s endpoint map. (However, if a well-known endpoint is available in the interface specifica tion, the server s endpoint map is not used.) To find a valid endpoint, the client s interface specification and binding information (protocol sequence, host, and object UUID) are compared to the information in the endpoint map. When an end- point of an appropriate server is finally obtained, the resulting fully bound binding handle is used to complete the connection at that endpoint. Example 5-5 shows how a server registers its endpoints in the endpoint map. Example 5-5: Managing Endpoints in an Endpoint Map /* Registering interfaces, creating server binding information, and */ /* advertising the server precede managing endpoints. */ /*************************** MANAGE ENDPOINTS ****************************/ strcpy (annotation, "Inventory interface"); status = RpcEpRegister ( /* add endpoints to local endpoint map O */ inv_Vl_0_s_ifspec, /* interface specification (inv.h) */ binding_vector, /* vector of server binding handles */ NULL, /* no object UUIDs to register */ (unsigned char *) annotation /* annotation supplied (not required) */ ); CHECK_STATUS( status, "Can t add endpoints to local endpoint map:", RESUME); status = RpcBindingVectorFree ( /* free server binding handles*/ &binding_vector ); CHECK_STATUS( status, "Can t free server binding handles:", RESUME); open_inventory ( ) ; /* application specific procedure */ 110 Microsoft RFC Programming Guide Example 5- 5: Managing Endpoints in an Endpoint Map (continued) I* For the rest of the server initialization, listen for remote */ /* procedure calls. */ O The RpcEpRegister routine registers the server endpoints in the local endpoint map. Use the same interface handle, binding vector, and object UUID vector as you used in the RpcNsBindingExport routine (see Example 5-4). An annota tion argument is not needed because Microsoft RFC provides no way to retrieve this information from the endpoint map. The RpcBindingVectorFree routine is a required call that frees the memory of the binding vector and all binding handles in it. Each call to RpcServerlnq- Bindings (see Example 5-3) requires a corresponding call to RpcBinding VectorFree. Make this call prior to listening for remote procedure calls, so the memory is available when remote procedure calls are processed. The RpcEpRegister call is required if dynamic endpoints are established with the RpcServerUseProtseq or RpcServerUseAllProtseqs runtime routines, because each time the server is started, new endpoints are created (see Example 5-3). If well- known endpoints are established with the RpcServerUseProtseqEp runtime routine, you should use the RpcEpRegister routine, because even though the endpoint may always be the same, a client needs to find the value. If well-known endpoints are established with the RpcServerUseProtseqlf or RpcServerUseAllProtseqsIf call, they need not be registered, because the client has access to the endpoint values through the interface specification. When a server stops running, endpoints registered in the endpoint map become outdated. The RFC service maintains the endpoint map by removing outdated end- points. However, an unpredictable amount of time exists in which a client can obtain an outdated endpoint. If a remote procedure call uses an outdated end- point, it will not find the server and the call will fail. To prevent clients from receiving outdated endpoints, use the RpcEpUnregister routine before a server stops executing. The only way to actively manage endpoints in the endpoint map is by using Rpc EpRegister and other RFC runtime routines in the server initialization code (see Example 5-5). Listening for Remote Procedure Calls The final requirement for server initialization code is to listen for remote proce dure calls. Many of the RFC runtime routines used in this book have an error status variable, used to determine whether the routine executed successfully. However, when the server is ready to process remote procedure calls, the RpcServerListen runtime rou tine is called. The RpcServerListen runtime routine does not return unless the Chapter 5: How to Write a Server 111 server is requested to stop listening by one of its own remote procedures using the RpcMgmtStopServerListening routine. Any errors occurring during stub code or remote procedure execution are reported as exceptions, and, unless your code is written to handle exceptions, it will abruptly exit. You can use a set of RFC macros to help process some system exceptions that occur outside the application code. The macros RpcTryExcept, RpcExcept, and RpcEndExcept delineate code sections in which exceptions are controlled. If an exception occurs during the RpcTryExcept section, code in the RpcExcept section is executed to handle any necessary error recovery or cleanup such as removing outdated endpoints from the endpoint map. These macros are not likely to be invoked when exceptions occur within the server application code itself; exceptions within a server usually cause the server to abort before the exceptions are reported back to the application. The RpcExcept section contains clean-up code that does such things as remove outdated endpoints from the endpoint map. The RpcTryExcept and RpcExcept sections end with the RpcEndExcept macro. Example 5-6 is a portion of C code that shows how the inventory server listens for remote procedure calls and handles exceptions. Example 5-6: Listening for Remote Procedure Calls I* Registering interfaces, creating server binding information, */ /* managing endpoints, and advertising the server precede listening */ /* for remote procedure calls. */ /***************** LISTEN FOR REMOTE PROCEDURE CALLS *****************/ RpcTryExcept /* thread exception handling macro O */ { status = RpcServerListen ( /* */ 1, /* process one remote procedure call at a time */ RPC_C_LISTEN_MAX_CALLS_DEFAULT , NULL ); CHECK_STATUS( status, "rpc listen failed:", RESUME); } RpcExcept ( RpcExcept ionCodeO ) /* error recovery and cleanup */ { close_inventory ( ) ; /* application specific procedure */ status = RpcServerlnqBindings ( /* get binding information */ &binding_vector ); CHECK_STATUS( status, "Can t get binding information:", RESUME); status = RpcEpUnregister ( /* remove endpoints from local endpoint map O */ inv_Vl_0_s_ifspec, /* interface specification (inventory. h) */ binding_vector, /* vector of server binding handles */ NULL /* no object UUIDs */ 112 Microsoft RFC Programming Guide Example 5-6: Listening for Remote Procedure Calls (continued) ); CHECK_STATUS( status, "Can t remove endpoints from endpoint map:", RESUME); status = RpcBindingVectorFree ( /* free server binding handles */ &binding_vector ); CHECK_STATUS ( status , "Can t free server binding handles:", RESUME); puts ( " \nServer quit ! " ) ; } RpcEndExcept ; } /* END SERVER INITIALIZATION */ O The RpcTryExcept macro begins a section of code in which you expect exceptions to occur. For this example, the RpcTryExcept section contains only the RpcServerListen routine. If an exception occurs during the remote procedure execution, the code section beginning with the RpcExcept macro is executed to handle application-specific cleanup. @ The RpcServerListen routine is a required call that causes the runtime to listen for remote procedure calls. The first argument sets the number of threads the RFC runtime library uses to process remote procedure calls. In this example, the RFC runtime library can process one remote procedure call at a time. If your remote procedures are not thread safe, set this value to 1. The RpcServerlnqBindings routine obtains a set of binding handles referring to all of the server s binding information. O The RpcEpUnregister routine removes the server endpoints from the local end- point map. If the server registered endpoints with a call to RpcEpRegister, this call is recommended before the process is removed (see Example 5-5). The RpcBindingVectorFree routine is called to free the memory of a binding vector and all binding handles in it. Each call to RpcServerlnqBindings requires a corresponding call to RpcBindingVectorFree. The server initialization code for the inventory application is now complete. All of the server initialization code is shown in Example D-5. Table B-2 lists all the run time routines that servers can use. Writing Remote Procedures When writing your remote procedures, consider the issues of memory manage ment, threads, and client binding handles. Remote procedures require special memory management techniques. Suppose a procedure allocates memory for data that it returns to the calling procedure. In a local application, the calling procedure can free allocated memory because the procedure and calling procedure are in the same address space. However, the Chapter 5: How to Write a Server 113 client (calling procedure) is not in the same address space as the server (remote procedure), so the client cannot free memory on the server. Repeated calls to a remote procedure that allocates memory, without some way to free the memory, will obviously waste the server s resources. You must manage memory for remote procedures by calling programmer-supplied wrapper routines for malloc and free in remote procedures. These routines enable the server stub to free memory allocated in remote procedures, after the remote procedure completes execution. Recall that the RpcServerListen routine in server initialization determines the num ber of threads a server uses to process remote procedure calls. If the server listens on more than one thread, the remote procedures need to be thread safe. For example, the remote procedures should not use server global data unless locks are used to control thread access. In the inventory application, when reading from or writing to the inventory application database, a lock may be needed so data is not changed by one thread while another thread is reading it. The topic of multi threaded application development is beyond the scope of this book. So far, we have used server binding handles and server binding information to allow clients to find servers. When a server receives a call from a client, the client RFC runtime library supplies information about the client side of the binding to the server RFC runtime library. Client binding information is used in server code to inquire about the client. This client binding information includes: The RFC protocol sequence used by the client for the call. The network address of the client. The object UUID requested by the client. This can be simply a nil UUID. To access client binding information in remote procedures use a client binding handle. If the client binding handle is available, it is the first parameter of the remote procedure. If you require client binding information, the procedure decla rations in the interface definition must have a binding handle as the first parame ter. No further details of client binding information are described in this book. Managing Memory in Remote Procedures In typical applications, you use the C library routines, malloc and free, or your own allocation scheme, to allocate and free memory that pointers must refer to. In RFC servers, when implementing a remote procedure that returns a pointer to newly allocated memory to the client, use programmer-supplied wrapper routines to malloc and free to manage memory in the remote procedures. The routines, which are named midl_user_allocate and midl_user_jree, are also called by the stub code to allocate and free memory. 114 _ Microsoft RFC Programming Guide Example 5-7 shows how you can write the wrapper routines for malloc and free. Example 5- 7: Programmer-Supplied Wrapper Routines for malloc and free /*** midl_user_al locate / midl_user_free ***/ void * _RPC_API midl_user_al locate size_t size; { unsigned char * ptr; ptr = malloc ( size ) ; return ( (void *)ptr ); } void _RPC_API midl_user_free ( ob j ect ) void * object; { free (object) ; } Use the midl_user_allocate routine instead of the C library routine malloc, so bookkeeping is maintained for memory management. This also ensures that mem ory on the server is automatically freed by the server stub after the remote proce dure has completed execution. Memory allocation will not accumulate on the server and get out of control. For reference pointers, memory on the client side must already exist, so no mem ory management is required for remote procedures whose output parameters are reference pointers. After you make the remote procedure call, first the server stub automatically allocates necessary memory and copies the data for the reference pointer into the new memory. Then it calls the implementation of the remote pro cedure. Finally, the remote procedure completes, output data is transmitted back to the client stub and the server stub frees the memory it allocated. On both the client and server, more complex memory management occurs for unique pointers than for reference pointers. If a remote procedure allocates mem ory for an output parameter, the server stub copies and marshalls the data, then the stub frees the memory that was allocated in the remote procedure. When the client receives the data, the client stub allocates memory and copies the data into the new memory. It is the client application s responsibility to free the memory allocated by the client stub. Example 5-8 shows how to use the midl_user_allocate routine to allocate memory for unique pointers. The procedure get_part_description of the inventory Chapter 5: How to Write a Server 775 application returns a string of characters representing the description of a part in the inventory. The call in the client is as follows: part_record part; /* structure for all data about a part */ part. description = get_part_descript ion (part. number ); Example 5-8: Memory Management in Remote Procedures paragraph get_part_description (number) part_num number; part_record *part; /* a pointer to a part record */ paragraph description; int size; char *strcpy ( ) ; if( read_part_record( number, &part) ) { /* Allocated data that is returned to the client must be allocated */ /* with the midl_user_allocate routine. */ size = strlen((char *)part->description) + 1; /* O */ description = (paragraph) midl_user_allocate ( (unsigned) size) ; /* */ strcpy((char *) description, (char *)part->description) ; else description = NULL; return (description) ; O An additional character is allocated for the null terminator of a string. The remote procedure calls the midl_user_allocate stub support routine to allocate memory in the remote procedure. When the procedure completes, the server stub automatically frees the memory allocated by mid l_user_a I locate calls. When the remote procedure call returns, the client stub automatically allocates memory for the returned string. When the client application code is finished with the data, it frees the memory allocated by the client stub as follows: if (part. description != NULL) free (part .description) ; For more complex memory management, there is a programmer-supplied counter part to the C library routine free called midl_user_Jree. The only time you don t use the midl_user_allocate and midl_user_free routines for memory management is when you use context handles. Memory allocated for context on the server must not use these routines because subsequent calls by the client must have access to the same context as previous calls. See Chapter 7 for more information on context handles. 116 Microsoft RFC Programming Guide Allocating Memory for Conformant Arrays The whatare_subparts procedure of the inventory application allocates memory for a conformant array in a structure, and returns a copy of the conformant structure to the client. The whatare_subparts procedure is declared in the interface definition as follows: typedef struct part_list{ /* list of part numbers */ long size; /* number of parts in array */ [size_is(size) ] part_num numbers [*]; /* conformant array of parts */ } part_list; void whatare_subparts ( /* get list of subpart numbers for a part */ [in] part_num number, [out] part_list **subparts /* the structure containing the array */ ); Output pointer parameters are reference pointers, which must have memory allo cated in the client prior to the call. Therefore, you need a unique pointer in order for new memory to be automatically allocated by the client stub for the ** sub- parts structure when the whatarejsubparts procedure returns. A pointer to a pointer is required so that the reference pointer points to a full pointer, which in turn points to the structure. Example 5-9 shows how to allocate memory in the remote procedure for a confor mant structure. The call in the client is as follows: part_record part; /* structure for all data about a part */ part_list *subparts; /* pointer to parts list data structure */ whatare_subparts (part. number, &subparts) ; Example 5~9: Conformant Array Allocation in a Remote Procedure void whatare_subparts (number, subpart_ptr) part_num number; part_list **subpart_ptr; { part_record *part; /* pointer to a part record */ int i; int size; read_part_record( number, &part) ; /* Allocated data that is output to the client must be allocated with */ /* the midl_user_allocate stub support routine. Allocate for a part_list */ /* struct plus the array of subpart numbers. Remember the part_list */ /* struct already has an array with one element, hence the -1. */ size = sizeof (part_list) + (sizeof (part_num) * (part->subparts.size-l) ) ; /* O */ Chapter 5: How to Write a Server 117 Example 5-9: Conformant Array Allocation in a Remote Procedure (continued) *subpart_ptr = (part_list *)midl_user_allocate( (unsigned) size) ; /* */ /* fill in the values */ (*subpart_ptr) ->size = part->subparts.size; for(i =0; i < (*sutpart_ptr) ->size; i++) ( *subpart_ptr ) ->numbers [ i ] = part->subparts . numbers [ i ] ; return; } O The allocated memory includes the size of the conformant structure plus enough memory for all the elements of the conformant array. The conformant structure generated by the MIDL compiler already has an array of one element, so the new memory allocated for the array elements is one less than the num ber in the array. Use the RFC stub support routine midl_user_allocate to allocate memory so bookkeeping is maintained for memory management, and so the server stub automatically frees memory on the server after the remote procedure com pletes execution. When the data for the conformant structure is returned to the client, the client stub allocates memory and copies the data into the new memory. The client application code uses the data and frees the memory allocated, as follows: for(i =0; i < subparts->size; i++) printf ( " %ld " , subparts->numbers [ i ] ) ; printf ("\nTotal number of subparts:%ld\n" , subparts->size) ; free(subparts) ; /* free memory allocated for conformant structure */ Compiling and Linking Servers Figure 5-2 shows the files and libraries required to produce an executable server. When complex data types are used, the MIDL compiler produces the server stub auxiliary file (appl^y.c) when the interface is compiled. The auxiliary file contains data marshalling procedures that can be used by other interfaces. No stub auxiliary files are produced for the inventory application. Example 5-10 shows the portion of a makefile that: Compiles the C language stubs and server code along with the header file producing server object files. Links the server object files to produce the executable server file. 118 Microsoft RFC Programming Guide Write server application files containing initialization code and the remote procedures. Include the header files produced by interface compilation. Generate client application and stub object files. Use the server stub and auxiliary files produced by compilation. Create the executable server file by linking the server application, stub, and auxiliary object files with the Microsoft RFC library. Text Editor Linker server Figure 5-2. Producing a server Example 5-10: Using a Makefile to Compile and Link a Server # FILE NAME: Makefile # Makefile for the inventory application # # definitions for this make file # APPL=inv NTRPCLIBS=rpcrt4 . lib rpcns4.1ib libont.lib kerne!32.1ib ! include <ntwin32.mak> ## NT c flags cflags = -c -WO -Gz -D_X86_=1 -CWIN32 -EMT /nologo Chapter 5: How to Write a Server 119 Example 5~10: Using a Makefile to Compile and Link a Server (continued) ## NT nmake inference rules $(cc) $(cdebug) $(cflags) $(cvarsmt) $< $ (cvtomf ) # # SERVER BUILD # server : server . exe server.exe: server. obj manager. obj invntry.obj $(APPL)_s.obj $(APPL)_x.obj $(link) $(linkdebug) $(conflags) -out : server . exe -map: server. map \ server . obj manager . obj invntry . obj $ ( APPL ) _s . obj $ ( APPL ) _x . obj \ $(NTRPCLIBS) # client and server sources client. obj: client. c $(APPL).h manager . obj : manager. c $(APPL).h server. obj: server. c $(APPL).h invntry.obj: invntry. c $(APPL).h In this Chapter: Naming DefaultEntry Server Entries Some Rules for Using the Microsoft Locator Using a Name Service We have seen in earlier chapters that clients query a name service to find a host where a server is running. We have set up our environment in a simplistic, if not inconvenient, manner so that we could avoid discussing details about the name service. For instance, in Chapter 1, Overview of an RFC Application, our arithmetic server used a simple server entry name. While this simple name is easy to create and use, it makes it difficult for an NT domain to accommodate other identical servers because they ll all be exporting their binding information to the same entry name in the name service. In a production environment, you don t want to restrict the number of servers you can have in a domain. That would defeat the purpose of the name service, which is to allow servers to be moved, added, and removed without affecting end-users. In this chapter, we discuss how to use a name service to provide multiple servers in your domain, which increases reliability and availability. You accomplish this by giving a server different names when it runs on different hosts. This is necessary because each server should be uniquely identified in the name service. Towards the end, we discuss some rules and caveats for using the Microsoft Locator. In DCE, the Cell Directory Service uses group entries and profile entries as a way to organize servers and control a client s search for server entries. The Microsoft Locator Version 1.0 does not support the use of CDS group entries or profile entries. However, Microsoft RFC includes group and profile routines in its runtime library for compatibility with DCE, for situations when you are running on a net work where other machines have DCE and you want to store binding information in CDS. 121 122 Microsoft RFC Programming Guide Naming Microsoft RFC supplies the Locator as the name service used by Microsoft RFC applications to locate servers. Servers store their binding information in the Loca tor s RFC name service database where it can be retrieved by clients. Clients then use the binding information to connect to servers. Because so many servers can exist in a Locator s domain, the collection of names is hierarchically organized. In DCE, this hierarchy really corresponds to the way that the name service stores the entries in its distributed database. But with the Microsoft Locator, the names are simply strings. The hierarchy is merely an appear ance, just for the convenience of the users for instance, so that related servers can have similar names. But it is still useful. Here are examples of entry names in the Locator: /. . . /manufacturing/ services /graphics/ servers /gif_server / . : /services /graphics/ servers /gif_server Names like those in the example can help organize servers logically so clients can find them by using consistent naming patterns. The above example shows two ways to name a server. The first example includes the name of the domain, manufacturing, as part of the name. The second exam ple avoids using the / . . . /manufacturing prefix by beginning the name with / . : . The domain name prefix is implicit because a Locator maintains entry names only from its own domain in this case, the manufacturing domain. The second example allows server portability across domains that use similar naming conven tions. When a server starts, it can export its name to the Locator database along with its protocol sequence and host address. Unlike DCE, Microsoft RFC does not provide independent tools for administrators to manage the entries. Consequently, your applications must do any needed entry management. DefaultEntry Recall that if you use automatic binding, the client stub finds a server for you. By default, a client searches the Locator for a server offering an interface with a matching interface UUID. You can override this behavior by setting the Default- Entry Windows NT registry value to a valid server entry name. You can set the DefaultEntry on Windows NT client using the regedt32 program. In the HKEY_LOCAL_MACHINE on Local Machine window, you should select SOFTWARE/Microsoft/I^>c/NameService. If the DefaultEntry value exists in the right portion of the window, double click on it to invoke a dialog box for typing in the server entry name. Type in the name and then click on OK. Chapter 6: Using a Name Service 123 If the Def aultEntry value does not exist, you can add it by pulling down the Edit menu and clicking on Add Value .... In the Value Name field, type Def aultEntry. Then click on OK. In the resulting String dialog box, type in the server entry name and then click on OK. You can set the Def aultEntry on Microsoft DOS and Windows 3.1 clients by using a text editor to add a line like the following to the C:\RPCREG.DAT configu ration file. \Root\Software \Microsoft\Rpc \NameService\Def aultEntry=/ . : /arithmetic_RIGEL. Server Entries A name service server entry stores binding information for an RFC server. Figure 6-1 depicts server entries in the name service database. Server entry Interface identifier Binding information Server entry Interface identifier Binding information Figure 6-1. Server entries in the name service database A server entry contains the following information: An interface identifier consists of an interface UUID and a version number. During the search for binding information, RFC name service routines use this identifier to determine if a compatible interface is found. Binding information is the information a client needs to find a server. It includes one or more sets of protocol sequence and host address combina tions. Well-known endpoints can also be part of the binding information, but dynamic endpoints cannot. 124 Microsoft RFC Programming Guide Some applications use optional object UUIDs to identify application-specific objects or resources. A reasonable naming scheme for server entries combines the host system name and a meaningful definition of what the server offers. For example, the arithmetic interface on a host system named RIGEL can have the following name service entry: / . : /arithmetic_RIGEL In this way, using a simple convention that all servers can follow, you are assured that each server at your site has a unique name as long as you have only one server per host. Normally, a host should only provide one server for an interface. You can increase the number of clients a server handles by increasing the number of threads a server can spawn. In the unusual case in which your system has mul tiple servers offering the same interface, you need to distinguish each server with separate name service entries and unique entry names. For example, one server might be / . : /arithmeticl_RIGEL, and another / . : /arithmetic2_RIGEL. If you structure your entry names to included embedded host names, using the host name again in the right-most part of the name is redundant. In this case, the arithmetic application might have the following entry name: / . : /product_developnent/test_servers/host_RIGEL/arithmetic When your client uses the name service to find a server, it does an import or lookup for binding information, starting at an entry name known to be in the database. Entry names must be supplied to you in one of two ways: by the name service administrator who knows the name service database organization, or by the server administrator. You use RFC name service routines to search the name service database. These routines compare the client s interface identifier with inter face identifiers in the database. When there is a match and the entry contains com patible binding information, the compatible binding information is returned. Figure 6-2 shows how the arithmetic application uses a server entry in the name service database. The arithmetic server uses the RpcNsBindingExport runtime rou tine to export binding information to the / . : /arithmetic_RIGEL server entry. The arithmetic server s use of RpcNsBindingExport is shown in Example 1-4 in Chapter 1. The arithmetic client uses the automatic binding method, so the client stub finds the server without using the server entry name. Instead, the automatic client requests a binding for an interface with a matching interface UUID. When client application code assists the search, as when using implicit or explicit binding methods, you can set a programmer-supplied environment variable such as ARITHMETIC_SERVER_ENTRY, to something like / . : /arithmetic_RIGEL on the client system SIRIUS, so the client stub has a name to search for in the name ser vice. In this example, the name service simply searches for the server entry name Chapter 6: Using a Name Service 125 I . : /arithmetic_RIGEL. The server entry s binding information is returned, and the remote procedure call is completed. System SIRIUS Import System RIGEL Arithmetic Server O Export Sys tern QUASAR Nai ne Service Database A:/arithmetic_RIGEL Figure 6-2. A simple use of a name service database Creating a Server Entry and Exporting Binding Information Microsoft RFC offers flexible ways for servers to construct server entry names. A name can be hard-coded in the server itself, but this method makes it difficult to change a server name because the server must be recompiled. To make your server more portable, you can specify a server name outside the program by set ting an environment variable used by the server. For instance, you can use a batch file to set a server-specific environment variable and then start the server. @REM FILE NAME: arith.bat ECHO OFF set ARITHMETIC_SERVER_ENTRY=/ . : /arithmetic_rigel server The server constructs the entry name using the WIN32 API getenv routine to read in the environment variable. The server then uses the NSI routine RpcNsBinding- 126 Microsoft RFC Programming Guide Export to export binding information to the name service entry. If an entry does not already exist, the Locator creates one for you. entry_name = (unsigned char *)getenv("ARITHMETIC_SERVER_ENTRY") ; status = RpcNsBindingExport ( /* export entry to name service database */ RPC_C_NS_SYNTAX_DEFAULT, /* syntax of the entry name (rpcdce.h) */ entry_name, /* entry name for name service */ arith_ServerIf Handle, /* interface specification (arith.h) */ binding_vector, /* the set of server binding handles */ NULL ); CHECK_STATUS( status, "Can t export to name service database", ABORT); Alternatively your server can use the WIN32 API getcomputername routine to read the computer name and append it to a string that is associated with the ARITH- METIC_SERVER_ENTRY environment variable. This method can make servers even more portable because you don t have to modify the .BAT file if you move the server to a different host. EWDRD hostname_size=STRINGLEN; /* required by GetComputerName */ strcpy (entry_name, "ARITHMETIC_SERVER_ENTRY" ) ; GetComputerName ( khostname , &hostname_size) ; strcat (entry_name, hostname); status = RpcNsBindingExport ( /* export to a name service database */ RPC_C_NS_SYNTAX_DEFAULT, /* syntax of entry name (rpcdce.h) */ (unsigned char *)entry_name, /* name of entry in name service */ inv_ServerIf Handle, /* interface specification (inv.h) */ binding_vector, /* binding information */ NULL /* no object UUIDs exported */ ); CHECK_STATUS( status, "Can t export to name service database:", RESUME); If you expect the server to be removed from service for a long period of time or even permanently, you should remove the server binding information from the name service using the RpcNsBindingUnexport runtime routine. Some Rules for Using the Microsoft Locator When your Windows NT domain is large and contains several Advanced Servers, the Locator does not always work smoothly. Changes to the database can some times result in inconsistencies. A Windows NT domain is a group of users and their systems sharing common security and administration. A domain consists of one domain controller which maintains the master copy of the domain s user and group database. The controller also stores the master copy of the Microsoft Locator. Chapter 6: Using a Name Service 727 Domains should also contain one or more Windows NT Advanced Servers which maintain copies of the master databases stored on the domain controller. The Win dows NT Advanced Servers in the domain query the domain controller every five minutes asking whether changes have been made. The controller sends just the changes to the requesting server, minimizing network traffic. If the domain con troller becomes unavailable for some reason, for example it crashes, a Windows NT Advanced Server in the domain is promoted to be the new domain controller. If the new controller s RFC name service database is not up-to-date, missing entries must be re-exported by their servers to the new controller. Consequently, out-of- date data tends to stay out of date. Domain controllers also maintain group entry information while Windows NT Advanced Servers do not. When a server is promoted to be the new domain con troller, group entries that existed before the promotion are lost. Consequently, Microsoft encourages users to rely on server entries rather than group entries. The domain controller and Windows NT Advanced Servers maintain the RFC name service database in transient memory rather than in a file. This model cannot guar antee the integrity of the database structure. If the domain controller crashes, all unpropagated server entries and all group entries must be reconstructed. In this Chapter: The Remote _flle Application Declaring Context in an Interface Definition Using a Context Handle in a Client Managing Context in a Server Some applications require that a server maintain information between remote pro cedure calls. This is called maintaining context (or maintaining state). Global data is one way a local application can maintain information between procedure calls. In a distributed application, however, the client and server are in different address spaces, so the only data common to each are passed as parameters. Even if a set of remote procedures use server global data, there is nothing to prevent more than one client from making calls that modify the data. A context handle is the mecha nism that maintains information on a particular server for a particular client. An active context handle refers to valid (non-null) context, and includes binding infor mation because a specific server maintains information for a particular client. The Remote _file Application The rfile application is a simple file transfer example that copies text from the client to the server. A client uses a context handle to refer to server context. The server context is the file handle used by remote procedures to open, write, and close the file. In this application, the filename on the server may be the same or different from the filename on the client, but the server does not overwrite an existing file on the server system. If you do not select any filenames, this application uses standard input (stdiri) of the client and standard output (stdouf) of the server to transfer a message from the client to the server. The complete rfile application is shown in Appendix E, The Rfile Application. 129 Microsoft RFC Programming Guide Declaring Context in an Interface Definition A file handle in a local application is analogous to a context handle in a dis tributed application. The information a file handle refers to is maintained by the C library and the operating system, not your application. You call some library rou tines to open or close the file, and other routines to read from or write to the file. A context handle is maintained by the stubs and RFC runtime library, not by your application code. What you have to write is a remote procedure that returns an active context handle, and one that frees the context when you are finished with it. Other remote procedures can access or manipulate the active context. Example 7-1 shows how to define context handles in the rfile interface defini tion. Example 7-1: Defining Context Handles I* FILE NAME: rfile. idl */ [ uuid(A61E4024-A53F-101A-BLAF-08002B2E5B76), version (1.0) , pointer_default (unique) ] interface rfile /* file manipulation on a remote system */ { typedef [context_handle] void *filehandle; /* O */ typedef byte bufferf]; filehandle remote_open( /* open for write ) */ [in] handle_t binding_h, /* explicit primitive binding handle */ [in, string] char name[], /* if name is null, use stdout in server */ [in, string] char mode[] /* values can be "r", "w", or "a" */ ); long remote_send( [in] filehandle fh, /* */ [in, max_is(max)] buffer buf, [in] long max ); void remote_close ( [in, out] filehandle *fh /* O */ To define a context handle data type, apply the context_handle attribute to a void * type (or a type that resolves to void *) in a type definition. If the client-server communication breaks down or the client fails, a context handle data type allows the server to automatically clean up the user-defined context with a call to a context rundown procedure. If a context handle is applied in a type definition, then the server application developer must write a context rundown procedure. Chapter 7: Context Handles 737 @ At least one remote procedure initializes the context handle and returns it to the client for later use. A procedure returning a context handle result always returns a new active context handle. Also, if a parameter is an out-only con text handle, the procedure creates a new active context handle. A procedure with a context handle parameter that is input only must use an active context handle. When the client application is finished with the server context, the context must be freed. If the context handle is null upon return from a procedure, the remote procedure on the server has freed the context and the client stub has freed the context han dle. A remote procedure that frees a context handle requires the parameter to have the in directional attribute so the server can free the context, and the out direc tional attribute so the client stub can also free the client s copy of the context han dle. Using a Context Handle in a Client The client uses a context handle to refer to the server context through the remote procedure calls. In the client, the context handle refers to an opaque structure. This means that the data is hidden and cannot be manipulated by the client appli cation code. The context handle can be tested for null, but not assigned any val ues by the client application. The server code accomplishes all context modification, but the status of the context is communicated to the client through the context handle. The client stub manipulates the context handle in the client on behalf of the server. Example 7-2 shows a typical sequence of remote procedure calls when using context handles. Example 7-2: Using a Context Handle in a Client /* FILE NAME: client. c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include "rfile.h" #define MAX 200 /* maximum line length for a file */ main(argc, argv) int argc; char *argv [ ] ; { FILE *local_fh; /* file handle for client file input */ char host [100]; /* name or network address of remote host */ char remote_name[100] ; /* name of remote file */ rpc_binding_handle_t binding_h; /* binding handle */ filehandle remote_fh; /* context handle */ buffer *buf_ptr; /* buffer pointer for data sent */ int size; /* size of data buffer */ get_args(argc, argv, &local_fh, host, (char *)remote_name) ; Microsoft RFC Programming Guide Example 7-2: Using a Context Handle in a Client (continued) #ifndef LOCAL if (do_string_binding(host, &binding_h) < 0) { /* O */ f print f (stderr, "Cannot get binding\n" ) ; exit(l); } ttendif remote_fh = remote_open(binding_h, remote_name, (char *)"w"); /* */ if (remote_fh == NULL) { fprintf (stderr, "Cannot open remote file\n"); exit(l); /* The buffer data type is a conformant array of bytes; */ /* memory must be allocated for a conformant array. */ buf_ptr = (buffer *)nialloc( (MAX+1) * sizeof (buf f er) ) ; while ( fgets((char *)buf_ptr, MAX, local_fh) != NULL) { size = (int) strlen( (char *)buf_ptr) ; /* data sent will not include \0 */ if( remote_send(remote_fh, (*buf_ptr) , size) < 1) { /* */ fprintf (stderr, "Cannot write to remote file\n"); exit(l) ; remote_close(&remote_fh) ; /* O */ } O Before a context handle becomes valid, a client must establish a binding with the server that will maintain the context. For the explicit or implicit binding methods, your application has to perform this step. For the automatic binding method, binding occurs in the client stub during the first remote procedure call. Then, to find the server after the context handle is established, subse quent calls use it instead of a binding handle. The do_string_binding proce dure is an application-specific procedure that creates a binding handle from a host input and a generated protocol sequence. It is shown in Chapter 3, How to Write Clients. The symbol LOCAL is used in applications in this book, to distinguish compil ing this client to test in a local environment, from compiling to run in a dis tributed environment. To establish an active context handle, a procedure must either return the con text handle as the procedure result or have only the out directional attribute on a context handle parameter. The context handle cannot be used by any other procedure until it is active. For the remote_open procedure, an explicit binding handle is the first parameter. Procedures using only an active context handle can be employed in any way the application requires. Note that for a procedure to use the context handle, a context handle parameter must have at least the in attribute. The remote_send procedure sends a buffer of text data to the server, where the remote procedure writes the data to the file referred to by the context handle. Chapter 7: Context Handles 133 O When you have finished with the context, free the context handle to release resources. Binding Handles and Context Handles A procedure can use a binding handle and one or more context handles. How ever, make sure all handles in the parameter list refer to the same server because a remote procedure call cannot directly refer to more than one server at a time. Table 7-1 shows how to determine whether a binding handle or a context handle directs the remote procedure call to the server. Table 7-1: Binding Handles and Context Handles in a Call Other Procedure Format Parameters proc( . . . ) proc( [in] bh . . . ) proc( . . . [in]ch . . . proc( . . . [in,out]ch No binding or con text handles May include context handles May include other context handles but no binding handles May include other input/output or out put-only context handles but no binding handles or input-only context handles Handle that Directs Call The interface-wide auto matic or implicit binding handle directs the call. The explicit binding handle, bh, directs the call. The first context handle that is an input-only pa rameter directs the call. If it is null, the call will fail. The first non-null con text handle that is an in put/output parameter di rects the call. If all are null, the call will fail. Managing Context in a Server When more than one remote procedure call from a particular client needs context on a server, the server stub and server application maintain the context. This sec tion describes how to implement the procedures that manipulate context in a server. A server context handle refers to context in the server code. It communicates the status of the context back to the client. From the perspective of the server 134 Microsoft RFC Programming Guide developer, a server context handle is an untyped pointer that can be tested for null, assigned null, or assigned any value. Once the server context handle is active (non-null), the server maintains the con text for the particular client until one of the following occurs: The client performs a remote procedure call that frees the context. The client terminates while context is being maintained. Communication breaks between the client and server. If the client terminates or the client-server communication breaks while the server maintains context, the server s RFC runtime library may invoke a context rundown procedure to clean up user data. Writing Procedures That Use a Context Handle Example 7-3 shows how to implement a procedure that obtains an active context handle, one that uses the active context handle, and one that frees the context handle. Example 7~3: Procedures That Use Context Handles I* FILE NAME: manager. c */ ttinclude <stdio.h> ttinclude <string.h> # include <io.h> #include <errno.h> ttinclude "rfile.h" filehandle remote_open(binding_h, name, mode) /* O */ rpc_binding_handle_t binding_h; char name [ ] ; char mode [ ] ; { FILE *FILEh; if (strlen( (char *)name) == 0) /* no file name given */ if (strcmpt (char *)mode, "r") == 0) FILEh = NULL; /* cannot read nonexistent file */ else FILEh = stdout; /* use server stdout */ else if (access ( (char *)name, 0) == 0) /* file exists */ if (strcmpt (char *)mode, "w" ) == 0) FILEh = NULL; /* do not overwrite existing file */ else FILEh = fopen((char *)name, (char *)mode) ; /* open read/append */ else /* file does not exist */ if (strcmpt (char *)mode, "r") == 0) FILEh = NULL; /* cannot read nonexistent file */ else FILEh = fopen((char *)name, (char *)mode) ; /* open write/append */ return ( (filehandle) FILEh ); /* cast FILE handle to context handle */ Chapter 7: Context Handles 135 Example 7-3: Procedures That Use Context Handles (continued) long int remote_send(fh, buf, max) /* */ filehandle fh; buffer buf; long int max; { /* write data to the file (context) , which is cast as a FILE pointer */ return( fwrite(buf, max, 1, fh) ) ; void remote_close(fh) /*)*/ filehandle *fh; /* the client stub needs the changed value upon return */ { if( (FILE *) (*fh) != stdout ) f closet (FILE *) (*fh) ) ; (*fh) = NULL; /* assign NULL to the context handle to free it */ return; } O Initialize data as required by later calls, and assign the application context to the server context handle. In this example, a file handle is obtained and assigned to the context handle when the procedure returns. Outside of the server process this file handle is meaningless, but when the client makes sub sequent calls, the server uses this file handle to write data or close the file. Use the server context handle parameter defined with the in directional attribute. This procedure must have an active context handle as input. For this example, the buffer (buf) of max number of items is written to the file. Cast the server context handle to the context s data type (FILE *). ) Free the context by using a procedure whose context handle parameter is defined with the in and out directional attributes. This procedure must have an active context handle as input. To free the context, assign null to the server context handle and use the C library procedure free or a corresponding method to clean up your application. In this example, before freeing the file handle, the context is tested to be sure it does not refer to stdout. The server context handle is cast to the context s data type. When this procedure returns to the client, the client stub automatically frees the context handle on the client side if the server context handle is set to NULL. If memory must be allocated for the context, use the C library procedure malloc or another method. Do not use the stub support procedure midl_user_allocate because you do not want the allocated memory to be automatically freed by the server stub after the procedure completes. Microsoft RFC Programming Guide Writing a Context Rundown Procedure A context rundown procedure allows orderly cleanup of the server context. The server RFC runtime library automatically calls it when a context is maintained for a client, and either of the following occurs: The client terminates without requesting that the server free the context. Communication breaks between the client and server. In our example, the interface definition defines the following type as a context handle: typedef [context_handle] void *filehandle; Example 7-4 shows the context rundown procedure to implement in the server code. The procedure name is created by appending _rundown to the type name (filehandle). The procedure does not return a value and the only parameter is the context handle. In this example, when the context rundown procedure executes, it closes the file that represents the context. Example 7-4: A Context Rundown Procedure I* FILE NAME: crndwn.c */ #include <stdio.h> #include "rfile.h" void filehandle_rundown(remote_fh) filehandle remote_fh; /* the context handle is passed in */ { fprintf (stderr, "Server executing context rundown\n" ) ; if( (FILE *)remote_fh != stdout ) f close ( (FILE *)remote_fh ) ; /* file is closed if client is gone */ remote_fh = NULL; /* must set context handle to NULL */ return; } The context handle must be defined as a type in the interface definition in order for the server runtime to automatically call the context rundown procedure. And if you define the context handle as a type, then you must implement a context run down procedure in the server. MIDI and ACF Attributes Quick Reference All MIDI attributes are shown in Tables A-l through A-8, and all ACF attributes are shown in Table A-9, but not all are demonstrated in this book. Table A-l: MIDI Interface Header Attributes Attribute Description uuid ( uuid_string) version (major. minor) pointer_default (kind) endpoint (string) local A universal unique identifier is generated by the uuidgen utility and assigned to the interface to distin guish it from other interfaces. This attribute is re quired unless the local attribute is used. A particular version of a remote interface is identified when more than one version exists. The default treatment for pointers is specified. Kinds of pointers include reference (ref) and unique (unique). An endpoint is a number representing the transport- layer address of a server process. This attribute spec ifies a well-known endpoint on which servers will lis ten for remote procedure calls. Well-known end- points are usually established by the authority re sponsible for a particular transport protocol. The MIDI compiler can be used as a C language header file generator. When this attribute is used, all other interface header attributes are ignored and no stub files are generated by the MIDL compiler. 137 138 Microsoft RFC Programming Guide Table A-2: MIDI Array Attributes Attribute Description string An array is specified to have the properties of a string. size_is(size) max_is (max) first_is( first) last_is(Iast) length_is (length) Conformant Array Attributes A variable is defined in the interface definition and used at runtime to establish the array size. A variable is defined in the interface definition and used at runtime to establish the maximum index value. Varying Array Attributes A variable is defined in the interface definition and used at runtime to establish the lowest index value of transmit ted data. The value is not necessarily the lowest bound of the array. A variable is defined in the interface definition and used at runtime to establish the highest index value of trans mitted data. The value is not necessarily the highest bound of the array. A variable is defined in the interface definition and used at runtime to establish the number of elements transmit ted for a portion of the array. Table A- 3: MIDL Pointer Type Attributes Attribute Description unique A pointer is specified as a unique pointer with the unique attribute. Unique pointers provide basic indirection and they can be null. Unique pointers cannot contain cycles or loops. ref A pointer is specified as a reference pointer with the ref attribute. This attribute gives basic indirection without the implementation over head associated with unique pointers. string A pointer is specified as pointing to a string. Appendix A: MIDI and ACF Attributes Quick Reference 139 Table A- 4: MIDI Data Type Attributes Attribute Description pointer type attributes context_handle handle transmit_as ( type) A data type with a visible pointer operator may be speci fied with a pointer type attribute (See Table A-3). A state is maintained on a particular server between re mote procedure calls from a specific client by maintaining a context handle as a data type. The context handle iden tifies the state. A defined data type is specified as a customized handle so that the client-server binding information is associated with it. A data type that is manipulated by clients and servers may be specified so that it is converted to a different data type for transmission over the network. Table A-5: MIDI Structure Member Attributes Description Attribute array attributes pointer type attributes ignore A structure member can have array attributes if it has ar ray dimensions or a visible pointer operator. A structure member that has a visible pointer operator and the size_is or max_is attribute defines a pointer to a con formant array, not an array structure member (see Table A-2). A structure member can have a pointer type attribute if it has a visible pointer operator (see Table A-3). Do not transfer the data in this structure member (a pointer) during a remote procedure call. This can save the overhead of copying and transmitting data to which the pointer refers. Table A-6: MIDI Union Case Attributes Attribute i Description pointer type attributes A union case can have a pointer type attribute if it has a visible pointer operator. See Table A-3. 140 Microsoft RFC Programming Guide Table A- 7: MIDI Procedure Parameter Attributes Attribute Description in out array attributes pointer type attributes context_handle The parameter is input when the remote procedure is called. The parameter is output when the remote procedure re turns. A parameter with array dimensions can have array at tributes. A conformant array is a procedure parameter with a visible pointer operator and the size_is or max_is attribute (see Table A-2). A parameter with a visible pointer operator can have a pointer type attribute. See Table A-3. A parameter that is a void * type can have the context handle attribute. Table A-8: MIDI Procedure Attributes Attribute Description string ptr unique context handle Procedure Result Attributes A procedure result is specified to have the properties of a string with the string attribute. A procedure that returns a pointer result always returns a full pointer. It may be specified with the ptr attribute but this is not necessary. Full pointers provide basic indirection and they can be null. They can also contain cycles or loops. Unique pointers provide basic indirection and they can be null. Unique pointers cannot contain cycles or loops. Unique pointers can be specified with the unique attribute. A procedure returns a context handle result in order to indi cate a state on a particular server, which is then referred to in successive remote procedure calls from a specific client. Table A -9: ACF Attributes Attribute Description Binding Methods auto_handle iirplicit_handle ( type name) explicit_handle ( type name) The automatic binding method is selected. The implicit binding method is selected. The explicit binding method is selected. Appendix A: MIDI and ACF Attributes Quick Reference 141 Table A-9: ACF Attributes (continued) Exceptions as Parameters corrm_status Names a parameter or the procedure result to which a status code is written if a communica tion error is reported by the client runtime to the client stub. The client remote procedure call must include the error_status_t data type in its argument list. If an error is report ed and this attribute and error_status_t da ta type are not used, the client stub raises an exception. fault_status Names a parameter or the procedure result to which a status code is written if an error is re ported by the server runtime to the server stub, an exception occurs in the server stub, or an exception occurs in the remote proce dure. If an error is reported and this attribute is not used, the client stub raises an excep tion. Excluding Unused Procedures code All or selected procedures from the interface have the associated client stub code generated by the MIDI compiler, nocode All or selected procedures from the interface do not have the associated client stub code generated by the MIDL compiler. RFC Runtime Routines Quick Reference The following tables organize the RFC runtime routines. Table B-l shows all the routines that client applications can use, and Table B-2 shows all the routines that server applications can use. The following abbreviations are used in RFC runtime routine names: Auth authentication, authorization Elt element Ep endpoint Exp expiration Id identifier If interface Inq inquire Mbr member Mgmt management Ns name service Protseq protocol sequence Rpc remote procedure call Stats statistics Numbers next to the calls have the following meaning: Function is limited to using Windows NT Security. Function is supported on Microsoft Windows NT systems only. Function acts on only the local process with Microsoft RPC Version 1.0. 4 Function provided for compatibility with DCE CDS. Not supported by the Microsoft Locator Version 1.0. 143 144 Microsoft RFC Programming Guide Table B-l Client RFC Runtime Routines Manage Binding Handles Manage VUIDs RpcBindingCopy RpcBindingFree RpcBindingReset RpcEpResolveBinding RpcBindinglnqObject RpcBindingSetObject RpcBindinglnqAuthlnfo 1 RpcBindingSetAuthlnfo RpcBindingVectorFree RpcStringBindingCompose RpcBindingFromStringBinding RpcBindingToStringBinding RpcSsDestroyClientContext RpcStringBindingParse UuidCreate UuidFromString UuidToString UuidCompare UuidEqual UuidHash UuidlsNil General Utility RpcStringFree Manage Name Service Entries RpcNsMgmtEntryCreate 4 RpcNsEntryObjectlnqBegin RpcNsEntryObjectlnqNext RpcNsEntryObjectlnqDone RpcNsEntryExpandName RpcNsMgmtEntrylnqlflds RpcNsMgmtBindingUnexport RpcNsMgmtEntryDelete Find Servers from a Name Service RpcNsBindinglmportBegin RpcNsBindinglmportNext RpcNsBindinglmportDone RpcNsBindinglnqEntryName RpcNsBindingLookupBegin RpcNsBindingLookupNext RpcNsBindingSelect RpcNsBindingLookupDone Manage Name Service Groups RpcNsGroupMbrAdd 4 RpcNsGroupMbrlnqBegin 4 RpcNsGroupMbrlnqNext 4 RpcNsGroupMbrlnqDone 4 RpcNsGroupMbrRemove 4 RpcNsGroupDelete 4 Manage Name Service Expirations RpcNsMgmtlnqExpAge RpcNsMgmtSetExpAge RpcNsMgmtHandleSetExpAge Manage Endpoints RpcEpUnregister Appendix B: RFC Runtime Routines Quick Reference 145 Table B-l Client RFC Runtime Routines (continued) Manage Name Service Profiles Handle Exceptions RpcNsProfileEltAdd* RpcNsProfileEltlnqBegin 4 RpcNsProfileEltlnqNext 4 RpcNsProfileEltlnqDone 4 RpcNsProfileEltRemove 4 RpcNsProfileDelete 4 RpcAbnormalTermination RpcEndExcept RpcEndFinally RpcExcept RpcFinally RpcExceptionCode RpcRaiseException RpcTryExcept RpcTryFinally Manage the Client RpcMgmtEnableldleCleanup RpcMgmtlnqComTimeout RpcMgmtSetComTimeout RpcMgmtSetCancelTimeout RpcWinSetYieldlnfo YieldFunctionName Manage Memory RpcSmAllocate RpcSmClientFree RpcSmDestroyClientContext RpcSmDisableAllocate RpcSmEnableAllocate RpcSmFree RpcSmGetThreadHandle RpcSmSetClientAllocFree RpcSmSetThreadHandle RpcSmSwapClientAllocFree RpcSsAllocate RpcSsDestroyClientContext RpcSsDisableAllocate RpcSsEnableAllocate RpcSsFree RpcSsGetThreadHandle RpcSsSetClientAllocFree RpcSsSetThreadHandle RpcSsSwapClientAllocFree Manage Local or Remote Applications RpcMgmtlsServerListening^ RpcMgmtStopServerListening3 RpcMgmtInqStats3 Inquire of Protocol Sequences RpcNetworklsProtseq Valid Manage Interface Information Rpclflnqld RpcIfRegisterAuthlnfo RpcIfldVectorFree 146 Microsoft RFC Programming Guide Table B-2 Server RFC Runtime Routines Manage Binding Handles Manage UUIDs RpcServerlnqBindings RpcBindingToStringBinding RpcStringBindingParse RpcStringBindingCompose RpcBindinglnqAuthClient 1 RpcBindinglnqObject RpcBindingServerFromClient RpcBindingVectorFree UuidCreate UuidFromString UuidToString UuidCompare UuidEqual UuidHash UuidlsNil Manage Name Service Entries Manage Interfaces RpcNsMgmtEntryCreate RpcNsEntryObjectlnqBegin RpcNsEntryObjectlnqNext RpcNsEntryObjectlnqDone RpcNsEntryExpandName 4 RpcNsMgmtEntrylnqlflds RpcNsMgmtBindingUnexport RpcNsMgmtEntryDelete RpcServerRegisterlf RpcServerlnqlf RpcServerUnregisterlf Rpclflnqld RpcIfldVectorFree Manage Name Service Expirations RpcNsMgmtlnqExpAge RpcNsMgmtSetExpAge RpcNsMgmtHandleSetExpAge Manage Name Service Groups RpcNsGroupMbrAdd 4 RpcNsGroupMbrlnqBegin 1 RpcNsGroupMbrlnqNext 4 RpcNsGroupMbrlnqDone 4 RpcNsGroupMbrRemove 4 RpcNsGroupDelete 4 Create Binding Information RpcImpersonateClient 2 RpcRevertToSelf 2 RpcServerUseProtseq RpcServerUseAllProtseqs RpcServerUseProtseqEp RpcServerUseProtseqlf RpcServerUseAllProtseqsIf RpcNetworklnqProtseqs RpcProtseqVectorFree Manage Endpoints RpcEpRegister RpcEpRegisterNoReplace RpcEpResolveBinding RpcEpUnregister Appendix B: RFC Runtime Routines Quick Reference 147 Table B-2 Server RFC Runtime Routines (continued) Manage Name Service Profiles Manage the Server RpcNsProfileEltAdd 1 RpcNsProfileEltlnqBegin 4 RpcNsProfileEltlnqNext 4 RpcNsProfileEltlnqDone 4 RpcNsProfileEltRemove 4 RpcNsProfileDelete 4 RpcMgmtSetServerStackSize RpcMgmtWaitServerListen Handle Exceptions RpcAbnormalTermination RpcEndExcept RpcEndFinally RpcExcept RpcFinally RpcExceptionCode RpcRaiseException RpcTryExcept RpcTryFinally Listen for RPCs RpcServerListen General Utility RpcStringFree Manage Local or Remote Applications Manage Memory RpcMgmtLsServerListening 3 RpcMgmtStopServerListening 3 RpcMgmtlnqStats 3 RpcMgmtStatsVectorFree RpcSmAllocate RpcSmClientFree RpcSmDisableAllocate RpcSmEnableAllocate RpcSmFree RpcSmGetThreadHandle RpcSmSetThreadHandle RpcSsAllocate RpcSsDisableAllocate RpcSsEnableAllocate RpcSsFree RpcSsGetThreadHandle RpcSsSetThreadHandle Manage Object Types RpcObjectSetType RpcObjectSetlnqFn RpcObjectlnqType Manage Authentication RpcServerRegisterAuthlnfo 1 RpcBindinglnqAuthClient 1 Export Servers to a Name Service RpcNsBindingExport RpcNsBindingUnexport In this Appendix: How to Build and Run the Application Application Files The Arithmetic Application The arithmetic application makes a remote procedure call to a procedure named sum_arrays, which adds together the values for the same array index in two long integer arrays, and returns the sums in another long integer array. The application demonstrates the basics of a distributed application with a remote procedure call and includes these features: Denning a simple array in an interface definition Using the automatic binding method Exporting a server to the name service Checking the error status of RFC runtime calls How to Build and Run the Application To build the server of the distributed application, type the following: C:\SERVER> nmake server To run the server of the distributed application, type the following: C:\SERVER> arith To build the client of the distributed application, type the following: C:\CLIENTT> nmake client To run the client of the distributed application, type the following: C:\CLIENT> client 149 150 Microsoft RFC Programming Guide Application Files Makefile contains descriptions of how the application is compiled. Use the compi lation make all to create all the executable files for the application. See Example C-l. arith.bat is a batch file that sets the environment and executes the server. See Example C-2. arith.idl contains the description of the constants, data types, and procedures for the interface. See Example C-3. client. c initializes two arrays, calls the remote procedure sum_arrays, and displays the results of the returned array. See Example C-4. manager. c is the remote procedure implementation. See Example C-5. sewer. c initializes the server with a series of Microsoft RFC runtime calls. See Exam ple C-6. status. h defines the CHECK_STATUS macro, which interprets error status codes that may return from Microsoft RFC runtime calls. See Example C-7. Exa mple C- 1 : The Makefile for the A rith metic Application # FILE NAME: Makefile # Makefile for the arithmetic application # # definitions for this make file # APPL=arith IDLCMD=midl NTRPCLIBS=rpcrt4 . lib rpcns4.1ib libcmt.lib kerne!32.1ib # Include Windows NT macros ! include <ntwin32 .mak> # NT c flags cflags = -c -WO -Gz -D_X86_=1 -DWIN32 -DMT /nologo # NT nmake inference rules $(cc) $(cdebug) $ (cflags) $(cvarsmt) $< $(cvtomf ) # # COMPLETE BUILD of the application # #all: local interface client server all: lclient.exe interface client.exe server.exe # # INTERFACE BUILD # interface : $ ( APPL ) . h $ ( APPL ) _c . ob j $ ( APPL ) _s . ob j Appendix C: The Arithmetic Application 151 Example C- 1: The Makefile for the Arithmetic Application (continued) # # LOCAL BUILD of the client application to test locally # local : Iclient . exe lclient.exe: Iclient. obj Imanager.obj $(link) $(linkdebug) $(conflags) -out: Iclient. exe -map: Iclient .map \ Iclient. obj Imanager.obj \ $(NTRPCLIBS) # # CLIENT BUILD # client : client . exe client.exe: client. obj $ (APPL)_c.obj $(link) $ (linkdebug) $(conflags) -out: client. exe -map: client .map \ client. obj $(APPL)_c.obj \ $(NTRPCLIBS) # # SERVER BUILD # server : server . exe server.exe: server. obj manager. obj $ (APPL)_s.obj $(link) $ (linkdebug) $(conflags) -out : server . exe -map: server. map \ server . obj manager .obj $ ( APPL ) _s . obj \ ${NrRPCLIBS) # client and server sources client. obj: client. c $(APPL).h manager. obj: manager. c $(APPL).h server . obj : server . c $ (APPL) . h # Local client sources Iclient. obj: client. c $(APPL).h $(cc) $(cdebug) $(cflags) $(cvarsmt) /DLOCAL /Folclient.obj client. c Imanager . obj : manager . c $ (APPL ) . h $(cc) $(cdebug) $(cflags) $(cvarsmt) /DLOCAL /Folmanager . obj manager. c # client stubs $( APPL )_c. obj: $(APPL)_c.c $( APPL )_x. obj: $(APPL)_x.c # compile the server stub $ (APPL) _s. obj : $(APPL)_s.c # generate stubs, auxiliary and header file from the IDL file $(APPL).h $(APPL)_c.c $(APPL)_x.c : $(APPL).idl $ (IDLCMD) $ (APPL) . idl # clean up for fresh build clean: del $(APPL)_?.c del *.obj del $(APPL) .h del *.map 152 _ Microsoft RFC Programming Guide Example C-1-. The Makefile for the Arithmetic Application (continued) del *.pdb clobber: clean if exist client.exe del client.exe if exist lclient.exe del lclient.exe if exist server.exe del server.exe Example C-2: The Server Batch File for the Arithmetic Application ECHO OFF @KEM FILE NAME: arith.bat set ARITHMETIC_SERVER_ENTRY=/ . : /arithmetic_serverhost server Example C~3: The MIDI File of the Arithmetic Application /* 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 */ pointer_default (ref ) /* default pointer type is reference */ ] interface arith /* interface name is arith */ { const unsigned short ARRAY_SIZE = 10; /* an unsigned integer constant */ typedef long long_array [ARRAY_SIZE] ; /* an array type of long integers */ void sum_arrays ( /* The 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 */ Example C-4: The Client File of the Arithmetic Application I* FILE NAME: client. c */ /* This is the client module of the arithmetic example. */ #include <stdio.h> # include <stdlib.h> ttinclude "arith. h" /* header file created by IDL compiler 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: ") ; for(i =0; i < ARRAY_SIZE; i++) printf ( "%ld\n" , result [i] ) ; Appendix C: The Arithmetic Application 153 Example C-4: The Client File of the Arithmetic Application (continued) /it************************************************************************/ /*** MIDL_user_allocate / MIDL_user_free ***/ void * _RPC_API MIDL_user_allocate ( size ) size_t size; { unsigned char * ptr; ptr = malloc ( size ) ; return ( (void *)ptr ) ; void __RPC_API MIDL_user_free ( object ) void * object; { free (object) ; } Example C-5. Remote Procedure of the Arithmetic Application I* FILE NAME: manager. c */ /* An implementation of the procedure defined in the arithmetic interface. */ # include <stdio.h> #include "arith.h" /* header file produced by IDL compiler */ void sum_arrays(a, b, c) /* implementation of the 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 */ } Example C-6: Server Initialization of the Arithmetic Application /* FILE NAME: server. c */ #include <stdio.h> #include "arith.h" /* header created by the idl compiler */ ttinclude "status. h" /* header with the CHECK_STATUS macro */ main () { unsigned long status; /* error status */ rpc_binding_vector_t *binding_vector; /* set of binding handles */ 154 Microsoft RFC Programming Guide Example C-6. Server Initialization of the Arithmetic Application (continued) unsigned char *entry_name; /* entry name for name service */ status = /* error status */ RpcServerRegisterlf ( /* register interface with the RFC runtime */ arith_vO_0_s_ifspec, /* interface specification (arith.h) */ NULL, NULL ); CHECK_STATUS( status, "Can t register interface", ABORT); status = RpcServerUseAllProtseqs ( /* create binding information */ RPC_C_PROTSEQ_MAX_REQS_DEFAULT, /* queue size for calls */ NULL /* 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 */ RPC_C_NS_SYNTAX_DEFAULT, /* syntax of the entry name */ entry_name, /* entry name for name service */ arith_vO_0_s_ifspec, /* interface specification (arith.h)*/ binding_vector, /* the set of server binding handles */ NULL ); CHECK_STATUS( status, "Can t export to name service database", ABORT); status = RpcEpRegister ( /* register endpoints in local endpoint map */ arith_vO_0_s_ifspec, /* interface specification (arith.h) */ 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 */ Appendix C: The Arithmetic Application 155 Example C-6: Server Initialization of the Arithmetic Application (continued) CHECK_STATUS( status, "rpc listen failed", ABORT); /*** MIDL_user_al locate / MIDL_user_free ***/ void * RPC API MIDL_user_al locate size size_t size; unsigned char * ptr; ptr = malloc ( size ) ; return ( (void *)ptr ) ; } void RPC API MIDL_user_free ob j ect void * object; free (object); Example C- 7: The Check Error Status Macro /* FILE NAME: status. h */ ttinclude <stdio.h> #include <stdlib.h> #define RESUME #def ine ABORT 1 #define ERROR_TEXT_SIZE 1025 #define CHECK_STATUS ( input_status , comment, action) \ { \ if (input_status ! = RPC_S_OK) { \ error_stat = FormatMessage ( FORMAT_MESSAGE_FROM_SYSTEM \ ,NULL \ , input_status \ ,0 \ , error_string \ , ERROR_TEXT_SIZE \ ,NULL); \ fprintf (stderr, "%s %s\n", comment, error_string) ; \ if (action == ABORT) \ exit(l); \ } \ 156 Microsoft RFC Programming Guide Example C- 7: The Check Error Status Macro (continued) static int error_stat; static unsigned char error_string[ERROR_TEXT_SIZE] ; In this Appendix: How to Run the Application Application Files The Inventory Application The inventory application allows a user to inquire about, and order from, a simple inventory. Data structures are defined for the following items: Part number (to identify a part) Part name Part description Part price Quantity of part Part list Account number (to identify a user) Procedures are also defined in the interface definition to do the following: Confirm if a part is available Obtain a part name Obtain a part description Obtain a part price Obtain the quantity of parts available Obtain a list of subpart numbers Order a part The application demonstrates many features of Microsoft RFC application develop ment including: Using strings, pointers, structures, a union, and a conformant array. 157 158 Microsoft RFC Programming Guide Allocating new memory in a remote procedure for data returned to the client using stub support routines. The get_part_description and whatare_subparts remote procedures demonstrate server allocation of a string and a conformant structure. Managing protocol sequences, interpreting binding information, selecting binding information, and using exception handler macros. Variations on a client using ACFs and the automatic, implicit, and explicit binding methods. Finding a server by importing from a name service database. How to Run the Application To run the local test of the client, type the following: C:\> nmake local C:\> lclient.exe To run the server of the distributed application, type the following: C:\SERVER> nmake server C:\SERVER> server.exe To run the client that uses the automatic binding method, type the following: C:\CLIENT> nmake client C:\CLIENT> client.exe To run a nondistributed local test of the implicit client, type the following in the implicit subdirectory: C:\> nmake local C:\> lclient.exe To run the implicit client of the distributed application using the automatic server, type the following in the implicit subdirectory: C:\CLIENT> nmake client C:\CLIENT> client.exe To run the explicit server of the distributed application, type the following in the explicit subdirectory: C:\SERVER> nmake server C:\SERVER> server.exe To run the explicit client of the distributed application using the explicit server, type the following in the explicit subdirectory: C:\CLIENT> nmake client C:\CLIENT> client.exe Appendix D: The Inventory Application 159 Application Files Makefile contains descriptions of how the application is compiled. Some files depend on the header file status. h from the arithmetic application for the CHECK_STATUS macro. See Example D-l. inv.idl contains the description of the constants, data types, and procedures for the interface. See Example D-2. manager. c is the implementation of all the remote procedures defined in this inter face. See Example D-3. invntry.c is the implementation of the inventory database. For simplicity, only three inventory items are included. The part numbers for these are printed when the inventory is opened. See Example D-4. server. c initializes the server with a series of runtime calls prior to servicing remote procedure calls. In addition to the required calls, this server also selects a specific protocol sequence, uses exception handling macros, and does some basic cleanup when the server quits. See Example D-5. client. c displays the instructions for the user and processes user input in a loop until exit is selected. Each remote procedure is exercised depending on the input from the user. See Example D-6. implicit\Makefile contains descriptions of how the implicit client is compiled. Some files depend on the header file status. h from the arithmetic application for the CHECK_STATUS macro. See Example D-7. implici t\ inv_i.acf customizes how you use an interface. In this application it is used to select the implicit binding method. See Example D-8. implicit\client.c imports a binding handle from the name service database. See Example D-9. implicit\getbind.c contains the do_import_binding procedure, which shows how to import a binding handle from the name service database. See Example D-10. implicit\intbind.c- contains the do_interpret_binding procedure, which shows how to obtain the binding information to which a binding handle refers. See Example D-ll. The server for the implicit client is the same as the one for the automatic client. explicit\Makefile contains descriptions of how the explicit client is compiled. The compilation depends on some files from the implicit client development. See Example D-12. explicit\inv.idl contains the description of the constants, data types, and proce dures for the interface. All procedure declarations include a binding handle as the first parameter. See Example D-l 3. 160 Microsoft RFC Programming Guide explicit\manager.c is the implementation of all the remote procedures denned in this interface. All procedure implementations include a binding handle as the first parameter. See Example D-14. explicit\client.c imports a binding handle from the name service database. All pro cedures have a binding handle as the first parameter. See Example D-15. The server s main program for the explicit client is the same as the one for the automatic and implicit clients. Example D-l. The Makefile for the Inventory Application # FILE NAME: Makefile # Makefile for the inventory application # # definitions for this make file # APPL=inv NTRPCLIBS=rpcrt4.1ib rpcns4.1ib libcmt.lib kerne!32.1ib ! include <ntwin32.mak> ## NT c flags cflags = -c -WO -Gz -D_X86_=1 -DWIN32 -DMT /nologo ## NT nmake inference rules $(cc) $(cdebug) $ (cflags) $(cvarsmt) $< $(cvtomf ) # # COMPLETE BUILD of the application # all : local interface client server # # INTERFACE BUILD # interface: $ (APPL) .h $ (APPL)_c.obj $ (APPL)_s.obj # # LOCAL BUILD of the client application to test locally # local: lclient.exe lclient.exe: Iclient.obj litianager.obj invntry.obj $(link) $(linkdebug) $(conflags) -out: lclient.exe -map:lclient.map \ Iclient.obj Imanager.obj invntry.objX $(NTRPCLIBS) # # CLIENT BUILD # client: client.exe client.exe: client. obj $(APPL)_c.obj $(link) $(linkdebug) $(conflags) -out: client.exe -map: client .map \ client. obj $ (APPL) _c. obj \ $(NTRPCLIBS) Appendix D: The Inventory Application 161 Example D-l: The Makefile for the Inventory Application (continued) # SERVER BUILD # server : server . exe server.exe: server. obj manager. obj invntry.obj $ (APPL)_s.obj $(link) $(linkdebug) $(conflags) -out : server . exe -map: server. map \ server . obj manager . obj invntry . obj $ ( APPL ) _s . obj \ $(NTRPCLIBS) # client and server sources client. obj: client. c $(APPL).h manager. obj: manager. c $(APPL).h server . obj : server . c $ ( APPL ) . h invntry . obj : invntry . c $ (APPL) . h # Local client sources Iclient.obj: client. c $(APPL).h $(cc) $(cdebug) $(cflags) $(cvarsmt) /DLOCALX /Folclient.obj client. c Imanager . obj : manager . c $ (APPL ) . h $(cc) $(cdebug) $(cflags) $(cvarsmt) /DLOCAL \ /Folmanager . obj manager. c # client 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 IDL file $(APPL).h $(APPL)_c.c $(APPL)_x.c : $(APPL).idl midl $(APPL) .idl # clean up for fresh build clean: del $(APPL)_?.c del *.obj del $(APPL) .h del *.map del *.pdb clobber: clean if exist client.exe del client.exe if exist lclierit.exe del lclient.exe if exist server.exe del server.exe Example D-2. The MIDL File of the Inventory Application /* FILE NAME: inv.idl */ [ /* brackets enclose attributes */ uuid(A6lE3FCO-A53F-10lA-BlAF-08002B2E5B76) , /* universal unique identifier */ version(l.O) , /* version of this interface */ pointer_default (unique) /* pointer default */ ] interface inv /* interface name */ { const long MAX_STRING =30; /* constant for string size */ typedef long part_num; /* inventory part number */ 162 Microsoft RFC Programming Guide Example D-2. The MIDI File of the Inventory Application (continued) typedef [string] char part_naine[MAX_STRING+l] ; /* name of part */ typedef [string, unique] char *paragraph; /* description of part */ typedef enum { ITEM, GRAM, KILOGRAM } part_units; /* units of measurement */ typedef struct part_price { /* price of part */ part_units units; double per_unit; } part_price; typedef union switch (part_units units) total { /* quantity of part */ case ITEM: long int number; case GRAM: case KILOGRAM: double weight; } part_quantity; typedef struct part_list{ /* list of part numbers */ long size; /* number of parts in array */ [size_is(size) ] part_num numbers [*]; /* conformant array of parts */ } part_list; typedef struct part_record { /* data for each part */ part_num number; part_name name ; paragraph description; part_price price; part_quantity quantity; part_list subparts; } part_record; typedef long account_num; /* user account number */ ********************* Procedure Declarations *************************/ boolean is_part_available( /* return true if in inventory */ [in] part_num number /* input part number */ ); void whatis_part_name( /* get part name from inventory */ [in] part_num number, /* input part number */ [in, out] part_name name /* output part name */ ); paragraph get_part_description ( /* return a pointer to a string */ [in] part_num number ); void whatis_part_price ( /* get part price from inventory */ [in] part_num number, [out] part_price *price ); void whatis_part_quantity( /* get part quantity from inventory */ [in] part_num number, [out] part_quantity *quantity Appendix D: The Inventory Application __ 163 Example D-2. The MIDI File of the Inventory Application (continued) void whatare_subparts ( /* get list of subpart numbers */ [in] part_num number, [out] part_list **subparts /* structure containing the array */ /* Order part from inventory with part number, quantity desired, and /* account number. If inventory does not have enough, output lesser /* Order part from inventory with part number, quantity desired, and */ . , */ /* quantity ordered. Return values: l=ordered OK, */ /* -l=invalid part, -2=invalid quantity, -3=invalid account. */ 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 */ Example D-3- Remote Procedures of the Inventory Application 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 (number) 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) ; void what is_part_name ( number, name) part_num number; part_name name; { part_record *part; /* a pointer to a part record */ read_part_record( number, &part) ; strncpy ( (char *)name, (char *)part->name, MAX_STRING) ; return; paragraph get_part_descript ion ( number ) part_num number; { part_record *part; /* a pointer to a part record */ 164 Microsoft RPC Programming Guide Example D-3: Remote Procedures of the Inventory Application (continued) paragraph description; int size; if( read_part_record( number, &part) ) { /* Allocated data that is returned to the client must be allocated */ /* with the MIDL_user_allocate stub support routine. */ size = strlen((char *) part -xJescript ion) + 1; description = (paragraph) MIDL_user_allocate( (unsigned) size) ; strcpy((char *) description, (char *)part->description) ; } else description = NULL; return (description) ; void whatis_part_price (number, price) part_num number; part_price *price; { part_record *part; /* a pointer to a part record */ read_part_record ( number , &part ) ; price->units = part->price. units; price->per_unit = part->price.per_unit; return; void what is_part_quantity (number, quantity) part_num number; part_quantity *quantity; { part_record *part; /* a pointer to a part record */ read_part_record( number, &part) ; quantity->units = part -xjuantity. units; switch (quantity->units) { case ITEM: quantity->total. number = part -xguantity. total. number; break; case KILOGRAM: case GRAM: quantity- >total . weight = part -xjuantity. total. weight; break; } return; void whatare_subparts ( number, subpart_ptr) part_num number; part_list **subpart_ptr; { part_record *part; /* pointer to a part record */ int i; int size; read__part_record ( number , &part ) ; Appendix D: The Inventory Application _ 165 Example D- 3: Remote Procedures of the Inventory Application (continued) I* Allocated data that is output to the client must be allocated with */ /* the MIDL_user_al locate stub support routine. Allocate for a */ /* part_list struct plus the array of subpart numbers. Remember the */ /* part_list struct already has an array with one element, hence the -1. */ size = sizeof (part_list) + (sizeof (part_num) * (part->subparts.size-l) ) ; *subpart_ptr = (part_list *)MIDL_user_allocate( (unsigned) size) ; /* fill in the values */ (*subpart_ptr) ->size = part->subparts.size; for(i =0; i < (*subpart_ptr) ->size; i++) (*subpart_ptr) ->numbers[i] = part ->subparts. numbers [i ] ; return; long int orderjpart (number, quantity, account) part_num number; part_quantity *quantity; account_num account ; { part_record *part; /* pointer to a part record */ long error =1; /* assume no error to start */ /* Test for valid input */ if ( !read_part_record( number, &part) ) /* invalid part number input */ error = -1; else if (quantity->units == ITEM) /* invalid quantity input */ error = (quantity->total. number <= 0) ? -2 : error; else if (quantity->units == GRAM I I quantity->units == KILOGRAM) error = (quantity->total. weight <= 0.0) ? -2 : error; /* else if () invalid account, not implemented */ /* error = -3; */ if (error < 0) return (error) ; /* convert input quantity & units if units are not correct for part */ if (quantity- >units != part->quantity. units) { if (part-xjuantity. units == ITEM) /* convert weight to items */ quantity->total. number = (long int) quantity- >total. weight; else if (quantity->units == ITEM) /* convert items to weight */ quantity- >total .weight = (long float) quantity- >total. number; else if (quantity->units == GRAM && part-xjuantity. units == KILOGRAM) quantity- >total . weight /= 1000.0; /* convert grams to kilograms */ else if (quantity->units == KILOGRAM && part -xjuantity. units == GRAM) quantity->total. weight *= 1000.0; /* convert kilograms to grams */ quantity->units = part-xjuantity. units; /* check if enough in inventory for this order */ switch (part-xjuantity. units) { case ITEM: if (part-xjuantity. total. number > quantity->total. number) /* reduce quantity in inventory by amount ordered */ part -xjuantity. total. number -= quantity- >total . number; else { 166 Microsoft RPC Programming Guide Example D- 3: Remote Procedures of the Inventory Application (continued) /* order all available and reduce quantity in inventory to */ quantity- >total. number = part-xjuantity. total, number ; part-xjuantity. total. number = 0; } break; case KILOGRAM: case GRAM: if (part-xjuantity. total. weight > quantity- >total . weight) /* reduce quantity in inventory by amount ordered */ part-xjuantity. total, weight -= quantity- >total. weight; else { /* order all available and reduce quantity in inventory to 0.0 */ quantity->total .weight = part-xjuantity . total .weight ; part-xjuantity. total. weight = 0.0; } break; write_part_record(part) ; /* update inventory */ return (1); /* order ok */ Example D-4: The Inventory Implementation /* FILE NAME: invntry.c */ /* A sample implementation of an inventory. */ * For simplicity, a few inventory items are maintained in the inventory. */ * The valid numbers are printed when the open_inventory ( ) procedure is */ /* called so the user knows what numbers to test. */ #include <stdio.h> #include <stdlib.h> ttinclude "inv.h" #define MAX_PARTS 10 /* maximum number of parts in this inventory */ #define MAX_SUBPARTS 5 /* maximum number of subparts for a part */ static part_record *rec[MAX_PARTS] ; /* array of pointers for this inventory */ static inventory_is_open =0; /* flag is reset to non-zero when open */ * Data for empty record or unknown part number */ static part_record no_part = {0, "UNKNOWN"} ; static part_num no_subparts [MAX_SUBPARTS] ; void open_inventory ( ) /***** setup inventory *******************************/ int i , j ; unsigned size; /* Allocate memory for the inventory array. Each part gets the size of */ /* a part_record plus enough memory for a subpart list. Since the */ /* subpart list is already defined in the part_record as an array of 1, */ * the new array memory only needs to be MAX_SUBPARTS-1 in size. */ for(i =0; i < MAX_PARTS; i++) { size = sizeof (part_record) + (sizeof (part_num) * (MAX_SUBPARTS-1) ) ; rec[i] = (part_record *)malloc(size) ; Appendix D: The Inventory Application 167 Example D-4. The Inventory Implementation (continued) } /* assign some data to the inventory array (part of an exercise machine) */ rec [ ] ->number = 102 ; stmcpy((char *)rec[0] ->name / "electronics display module" , MAX_STRIN3) ; rec[0]->description = (paragraph) malloc( 1000 ); strcpy((char *)rec[0] ->description, "The electronics display module is a liquid crystal display containing\n\ a timer, counter, metronome, and calorie counter."); rec [ ] ->price . units = ITEM; rec[0]->price.per_unit = 7.00; rec [0]->quantity. units = rec [0] ->price. units; rec [ 0] -xjuantity. total. number = 432; rec [0]->subparts. size = 4; /* cannot be greater than MAX_SUBPARTS */ for(i =0; i < rec [0] ->subparts. size; i++) /* values used are not relevant */ rec[0]->subparts.numbers[i] = rec[0]->number + 1 + i; rec[l] ->number = 203; strncpy ( (char *)rec[l] ->name, "base assembly", MAX_STRHSJG) ; rec [1] -description = (paragraph) malloc( 1000 ); strcpy((char *)rec[l] -xiescription, "The base assembly rests on the floor to stabilize the machine. \n\ The arm and bench assemblies are attached to it."); rec[l] ->price. units = ITEM; rec[l]->price.per_unit = 85.00; recfl] -xjuantity. units = recfl] ->price. units; rec [1] -xjuantity. total. number = 1078; rec [l]->subparts. size = 5; /* cannot be greater than MAX_SUBPARTS */ forfi =0; i < rec [1] ->subparts. size; i++) /* values used are not relevant */ rectl] ->subparts.numbers[i] = rec [1] ->number + 17 + i; rec[2]->number = 444; strncpy ( (char *)rec[2] ->name, "ballast", MAX_STRIN3) ; rec [2 ] -xiescription = (paragraph) malloc ( 1000 ) ; strcpy ( (char * ) rec [2 ] -xJescription, "The ballast is used to counterbalance the force exerted by the user."); rec[2]->price.units = KILOGRAM; rec[2]->price.per_unit = 1.59; rec [2] -xjuantity. units = rec[2]->price.units; rec [2] -xjuantity. total. weight = 13456.2; rec[2]->subparts.size =0; /* cannot be greater than MAX_SUBPARTS */ for(i =0; i < MAX_^UBPARTS ; i++) /* zero out subpart array */ rec [2 ]->subparts. numbers [i] = no_subparts[i] ; /* fill in rest of inventory as "empty" data */ for(i =3; i < MAX_PARTS; i++) { rec [ i ] = &no_part ; for(j = 0; j < MAX_SUBPARTS; j++) rec[i]->subparts. numbers [j] = no_subparts[j] ; } puts ("Part numbers in inventory:"); for(i = 0; i < MAX_PARTS; i++) if (rec[i]->number > 0) printf ( "%ld\n" , rec [i] ->number) ; inventory_is_open = 1; 1 68 Microsoft RFC Programming Guide Example D-4: The Inventory Implementation (continued) return; void close_inventory ( ) /**** close inventory / /* Undo whatever is done in open_inventory . Free memory and so forth. */ /* (not implemented) */ return; int read_part_record ( number, part_ptr) /** get record for this part number **/ part_num number; part_record **part_ptr; int i; if (inventory_is_open == 0) open_inventory ( ) ; *part_ptr = &no_part; for(i =0; i < MAX_PARTS; i++) if (rec[i] ->number == number) { *part_ptr = rec[i]; break; /* initialize assuming no part */ /* search the inventory */ /* found the part */ if( (*part_ptr) ->number > 0) return ( 1 ) ; else return ( ) ; /* not a valid part int write_part_record(part) part_record *part; { int i; update inventory for this part number ***** if (inventory_is_open == 0) open_inventory ( ) ; for(i =0; i < MAX_PARTS; if (rec[i]->number == part->number) { rec[i] = part; /* overwrite inventory with new data */ return (1) ; } return (0) ; /* dump the part data to the screen. static dump_part_record( index) int index; { printf ( "number input : %ld part number : %ld\n" , number, rec [index] ->number) printf ( "part name: %s\n" , rec [index] ->name) ; printf ( "description : %s\n" , rec [ index] -Rescript ion) ; printf ("price :%f per %s\n", rec [ index] ->price.per_unit, Appendix D: The Inventory Application 169 Example D-4: The Inventory Implementation (continued) (rect index] ->price. units == ITEM) ? "item" : "gram"); printf ( "quantity: " ) ; switchfrec [index] ->quantity. units) { case ITEM: printf ("%ld items\n", rec[index] ->quantity. total. number) ; break; case GRAM: printf ("%f grams\n", rec [ index] -xjuantity. total. weight ) ; break; case KILOGRAM: printf ("%f kilos\n", rec [ index] -xjuantity. total. weight ) ; break; } print f ( " subparts : " ) ; for(i =0; i < rec [index] ->subparts.size; i++) printf ( "%ld " , rec [index] ->subparts .numbers [i] ) ; printf ("\n" ) ; }*/ Example D-5. Server Initialization of the Inventory Application /* FILE NAME: server. c */ tinclude <stdio.h> #include <stdlib.h> #include <ctype.h> #include "inv.h" /* header created by the IDL corrpiler */ #include "status. h" /* contains the CHECK_STATUS macro */ ttdefine STRINGLEN 50 main (argc, argv) int argc; char *argv [ ] ; { error_status_t status; /* error status */ /* RFC vectors */ rpc_binding_vector_t *binding_vector; /* binding handle list */ RPC_PROTSEO_VECTOR *protseq_vector; /*protocol sequence list */ char entry_name [ STRINGLEN ]; /* name service entry name */ char group_name [ STRINGLEN ]; /* name service group name */ char annotation [ STRINGLEN] ; /* annotation for endpoint map */ char hostname [ STRINGLEN] ; /* used to store the computer name */ DWDRD hostname_size=STRINGLEN; /* required by GetComputerName */ /************************** REGISTER INTERFACE ***************************/ status = RpcServerRegisterlf ( inv_vl_0_s_ifspec, /* interface specification (inv.h) */ NULL, NULL ); CHECK_STATUS( status, "Can t register interface:", ABORT); /****************** CREATING SERVER BINDING INFORMATION ******************/ if (argc > 1) { status = RpcServerUseProtseq( /* use a protocol sequence */ (unsigned char *)argv[l], /* the input protocol sequence */ RPC_C_PROTSEO_MAX_REQS_DEFAULT, /* the default number of requests*/ NULL /* security descriptor (not reqd)*/ Microsoft RFC Programming Guide Example D- 5: Server Initialization of the Inventory Application (continued) CHECK_STATUS( status, "Can t use this protocol sequence:", ABORT); } else { puts ("You can invoke the server with a protocol sequence argument."); status = RpcServerUseAllProtseqs ( /* use all protocol sequences RPC_C_PROTSEQ_MAX_REQS_DEFAULT, /* the default number of requests */ NULL /* security descriptor (not reqd) */ ); CHECK_STATUS ( status, "Can t register protocol sequences:", ABORT); } status = RpcServerlnqBindings ( /* get binding information for server */ &binding_vector ); CHECK_STATUS( status, "Can t get binding information:", ABORT); /*************************** ADVERTISE SERVER ****************************/ strcpy ( entry_name , " / . : / inventory_" ) ; GetComputerName(&hostname, &hostname_size) ; strcat (entry_name, hostname); status = RpcNsBindingExport ( /* export to a name service database */ RPC_C_NS_SYNTAX_DEFAULT, /* syntax of entry name */ (unsigned char *)entry_name, /* name of entry in name service */ inv_vl_0_s_ifspec, /* interface specification (inv.h) */ binding_vector, /* binding information */ NULL /* no object UUIDs exported */ ); CHECK_STATUS( status, "Can t export to name service database:", RESUME); ENDPOINTS w***"******* * * **** *** *** ^ strcpy (annotation, "Inventory interface"); status = RpcEpRegister ( /* add endpoints to local endpoint map */ inv_vl_0_s_ifspec, /* interface specification (inv.h) */ binding_vector, /* vector of server binding handles */ NULL, /* no object UUIDs to register */ (unsigned char *) annotation /* annotation supplied (not required) */ ); CHECK_STATUS( status, "Can t add endpoints to local endpoint map:", RESUME); status = RpcBindingVectorFree ( /* free server binding handles */ &binding_vector ); CHECK_STATUS( status, "Can t free server binding handles:", RESUME); open_inventory ( ) ; /* application specific procedure */ /******************* LISTEN FOR REMOTE PROCEDURE CALLS *******************/ RpcTryExcept /* thread exception handling macro */ Appendix D: The Inventory Application _ 171 Example D- 5: Server Initialization of the Inventory Application (continued) status = RpcServerListen ( 1, /* process one remote procedure call at a time */ RPC_C_LISTEN_MAX_CALLS_DEFAULT , NULL ); CHECK_STATUS ( status, "rpc listen failed:", RESUME); } RpcExcept (RpcExceptionCodeO ) /* error recovery and cleanup */ { close_inventory ( ) ; /* application specific procedure */ status = RpcServerlnqBindings ( /* get binding information */ &binding_vector ); CHECK_STATUS( status, "Can t get binding information:", RESUME); status = RpcEpUnregister ( /* remove endpoints from local endpoint map */ inv_vl_0_s_ifspec, /* interface specification (inv.h) */ binding_vector, /* vector of server binding handles */ NULL /* no object UUIDs */ ); CHECK_STATUS( status, "Can t remove endpoints from endpoint map:", RESUME); status = RpcBindingVectorFree ( /* free server binding handles */ &binding_vector ); CHECK_STATUS( status, "Can t free server binding handles:", RESUME); puts ( " \nServer quit ! " ) ; } RpcEndExcept ; } /* END SERVER INITIALIZATION */ /*** MIDL_user_al locate / MIDL_user_free ***/ void * __RPC_API MIDL_user_al locate " size_t size; { unsigned char * ptr; ptr = malloc ( size ) ; return ( (void * ) ptr void _RPC_API MIDL_user_free 172 Microsoft RFC Programming Guide Example D- 5: Server Initialization of the Inventory Application (continued) ( ob j ect ) void * object; { free (object) ; } Example D-6: The Automatic Client File of the Inventory Application /* FILE NAME: client. c */ /******************** client of the inventory application *******************/ #include <stdio.h> #include <stdlib.h> #include "inv.h" /* header file created by the IDL compiler */ char instructions [] = "Type character followed by appropriate argument (s) .\n\ Is part available? a [part_number] \n\ What is part name? n [part_number] \n\ Get part description. d [part_number] \n\ What is part price? p [part_number] \n\ What is part quantity? q [part_number] \n\ What are subparts of this part? s [part_number] \n\ Order part. o part_number quantity\n\ REDISPLAY r\n\ EXIT e\n" ; main() { part_record part; /* structure for all data about a part */ part_list *subparts; /* pointer to parts list data structure */ account_num account =1234; /* a user account number */ int i, num_args, done = 0; long result; char input [100] , selection [20 ], quantity [20] ; puts ( instructions ) ; part. number = 0; strcpy (quantity , " " ) ; while Udone) { /* user makes selections and each is processed */ printf ( " Selection : " ) ; f flush ( stdout ) ; gets ( input ) ; num_args = sscanf (input, "%s%ld%s", selection, & (part. number ), quantity); switch ( tolower (selection [0] )) { case a : if ( is_part_available (part. number )) puts ( "available : Yes " ) ; else puts ( " avai lable : No " ) ; break; case n : what is_part_name( part. number, part. name) ; printf ("name: %s\n", part. name); break; case d : part . description = get_part_description (part. number ); printf ( "description: \n%s\n" , part . description) ; Appendix D: The Inventory Application 173 Example D-6: The Automatic Client File of the Inventory Application (continued) if (part. description != NULL) f ree( part. descript ion ); /* free memory allocated */ break; case p : whatis_part_price (part. number, & (part. price) ) ; printf ("price :%10.2f\n", part. price. per_unit) ; break; case q : whatis_part_quantity (part. number, &( part. quant i ty )); if (part, quantity, units == ITEM) printf ("total items :%ld\n", part. quantity. total. number ); else if (part. quantity. units == GRAM) printf ("total grams:%10.2f\n" , part. quantity. total. weight ); else if (part. quantity. units == KILOGRAM) printf ("total kilos :%10.2f\n", part. quantity. total. weight ) ; break; case s : whatare_subparts (part. number, &subparts) ; for(i =0; i < subparts->size; i++) printf ( " %ld " , subparts->numbers [ i ] ) ; printf ("\ntotal number of subparts:%ld\n" , subparts->size) ; free(subparts) ; /* free memory for conformant struct */ break; case o : if(num_args < 3) { puts ("Not enough arguments"); break; /* Assume KILOGRAM units and assign quantity input */ part. quantity. units = KILOGRAM; part. quantity. total. weight = atof (quantity) ; result = order_part (part. number, & (part. quant i ty ), account); if (result > 0) { if (part. quantity. units == ITEM) printf ("order :%ld items\n", part. quantity. total. number ); else if (part. quantity. units == GRAM) printf ( "order : %10 . 2f grams\n" , part . quantity . total .weight ) , else if (part. quantity. units == KILOGRAM) printf ("order :%10.2f kilosYn", part. quantity. total. weight) , else { /* error cases */ if (result == -1) puts ( "Invalid part number"); else if (result == -2) puts ("Invalid quantity" ); else if (result == -3) puts ("Invalid account number"); break; case r : /* redisplay selection or bad input displays instructions */ default : puts ( instructions ) ; break ; case e : done = 1; break; } /*end case */ } /* end while */ } /* end mainO */ /**** /*** /***< MIDL_user_allocate / MIDL_user_free ****/ *** / ****/ U4 Microsoft RFC Programming Guide Example D-6: The Automatic Client File of the Inventory Application (continued) void * _RPC_API MIDL_user_al locate size_t size; { unsigned char * ptr; ptr = malloc ( size ) ; return ( (void *)ptr ); void __RPC_API MIDL_user_free ( ob j ect ) void * object; { free (object) ; } Example D- 7: The Makefile for the Implicit Client # FILE NAME: Makefile # Makefile for the inventory application implicit client # # definitions for this make file # APPL=inv IDLCMD=midl NTRPCLIBS=rpcrt4 . lib rpcns4.1ib libcmt.lib kerne!32.1ib ! include <ntwin32.mak> ## NT c flags cflags = -c -WO -Gz -D_X86_=1 -DWIN32 -DMT /I. /I., /nologo ## NT nmake inference rules $(cc) $(cdebug) $ (cflags) $(cvarsmt) $< $(cvtotnf) # # COMPLETE BUILD of the application # all: lclient.exe interface client.exe # # INTERFACE BUILD # interface : $ ( APPL ) . h $ ( APPL ) _c . ob j # # LOCAL BUILD of the client application to test locally Appendix D: The Inventory Application 775 Example D- 7: The Makefile for the Implicit Client (continued) # local: lclient.exe lclient.exe: Iclient.obj Imanager. obj invntry.obj $(link) $(linkdebug) $(conflags) -out: lclient.exe -map:lclient.map \ Iclient.obj Imanager.obj invntry.obj \ $(NTRPCLIBS) # # CLIENT BUILD # client : client . exe client.exe: client. obj getbind.obj intbind.obj $ (APPL)_c.obj $(link) $(linkdebug) $(conflags) -out : client. exe -map: client. map \ client. obj getbind.obj intbind.obj $ (APPL)_c.obj \ $(NTRPCLIBS) # client and server sources client. obj: client. c $(APPL).h getbind . obj : getbind . c intbind . obj : intbind . c # Local client sources invntry . obj : . . \ invntry . c $(cc) $(cdebug) $(cflags) $(cvarsmt) /DLOCAL /I. /I.. \ /Foinvntry . obj . . \ invntry . c Iclient.obj: client. c $(APPL).h $(cc) $(cdebug) $(cflags) $(cvarsmt) /DLOCAL /I. /I.. \ /Folclient.obj client. c Imanager . obj : . . \manager . c $ ( APPL) . h $(cc) $(cdebug) $(cflags) $(cvarsmt) /DLOCAL /I. /I.. \ /Fo Imanager . obj . . \manager . c # client stubs $( APPL )_c. obj: $(APPL)_c.c $( APPL )_x. obj: $(APPL)_x.c # generate stubs, auxiliary and header file from the IDL file $(APPL).h $(APPL)_i.acf $(APPL)_c.c $(APPL)_x.c : . . \$ (APPL) . idl $(IDLCMD) ..\$ (APPL) .idl /acf $ (APPL)_i.acf # clean up for fresh build clean: del $(APPL)_?.c del *.obj del $(APPL) .h del *.map del *.pdb clobber : clean if exist client.exe del client.exe if exist lclient.exe del lclient.exe if exist server.exe del server.exe 176 Microsoft RFC Programming Guide Example D-8: An ACF File for Implicit Binding I* FILE NAME: inventory .acf (implicit version)*/ /* This Attribute Configuration File is used in conjunction with the */ /* associated .idl file ( inventory . idl ) when the IDL compiler is invoked. */ implicit_handle(handle_t global_binding_h) /* implicit binding method */ ] interface inv /* The interface name must match the .idl file. */ Example D-9: The Implicit Client of the Inventory Application I* FILE NAME: client. c */ /******* Client of the inventory application with implicit method ***********/ #include <stdio.h> #include <stdlib.h> #include "inv.h" /* header file created by the IDL compiler */ # include " . . \status .h" char instructions [] = "Type character followed by appropriate argument ( s ). \n\ Is part available? What is part name? Get part description. What is part price? What is part quantity? What are subparts of this part? Order part. REDISPLAY EXIT [part_number] \n\ [part_number] \n\ [part_number] \n\ [part_number] \n\ [part_number] \n\ [part_number] \n\ o part_number quantity\n\ r\n\ e\n" ; main() part_record part; part_list *subparts; account_num account = 1234; unsigned long status; /* structure for all data about a part */ /* pointer to parts list data structure */ /* a user account number */ /* error status */ int i, num_args, done = 0; long result; char input [100] , selection [20 ], quantity [20] ; puts (instructions) ; part. number = 0; strcpy (quantity, " " ) ; #ifndef LOCAL do_import_binding ( #endif /* find server in name service database */ &global_binding_h) ; status = RpcBindingReset (global_binding_h) ; CHECK_STATUS( status, "Can t reset binding handle", ABORT); while Udone) { /* user makes selections and each is processed */ printf ( " Selection : " ) ; f flush ( stdout ) ; gets ( input ) ; num_args = sscanf (input, "%s%ld%s", selection, & (part. number ), quantity), switch (tolower (selection [0] )) { Appendix D: The Inventory Application 177 Example D-9: The Implicit Client of the Inventory Application (continued) case a : if ( is_part_available (part. number )) puts ( " available : Yes " ) ; else puts ( " avai lable : No " ) ; break; case n : what is_part_name( part. number, part. name); printf ("name: %s\n", part. name); break; case d : part. description = ge t_part_descript ion (part .number) ; printf ( "description: \n%s\n" , part .description) ; if (part. description != NULL) f ree (part. description) ; /* free memory allocated */ break; case p : whatis_part_price (part. number, & (part. price) ) ; printf ("price :%10.2f\n", part. price. per_unit) ,- break; case q : what is_part_quantity( part, number, & (part, quantity) ); if (part, quantity, units == ITEM) printf ("total items:%ld\n", part. quantity. total. number ); else if (part. quantity. units == GRAM) printf ("total grams:%10.2f \n" , part. quantity. total. weight ); else if (part. quantity. units == KILOGRAM) printf ("total kilos : %10. 2f \n" , part. quantity. total. weight ) ; break; case s : whatare_subparts (part. number, &subparts) ; for(i = 0; i < subparts->size; i++) printf ( " %ld " , subparts->numbers [ i ] ) ; printf ("\ntotal number of subparts:%ld\n" , subparts->size) ; free(subparts) ; /* free memory for conformant struct */ break; case o : if(num_args < 3) { puts ("Not enough arguments"); break; /* Assume KILOGRAM units and assign quantity input */ part, quantity, units = KILOGRAM; part. quantity. total. weight = atof (quantity) ; result = order_part (part. number, & (part. quant i ty ), account); if (result > 0) { If (part. quantity. units == ITEM) printf ( "order : %ld items\n" , part . quantity . total . number) ; else if (part. quantity. units -= GRAM) printf ("order :%10.2f grams \n ", part. quantity .total. weight ); else if (part. quantity. units == KILOGRAM) printf ("order :%10.2f kilos\n", part. quantity. total. weight ); else { /* error cases */ if (result == -1) puts ("Invalid part number"),- else if (result == -2) puts ("Invalid quantity"); else if (result == -3) puts ("Invalid account number"); break; case r : /* redisplay selection or bad input displays instructions */ Microsoft RFC Programming Guide Example D-9: The Implicit Client of the Inventory Application (continued) default: puts ( instructions ); break; case e : done = 1; break; } /*end case */ } /* end while */ } /* end main() */ ^ ************************************************************************* / /*** MIDL_user_al locate / MIDL_user_free /*************************************************************************/ void * __RPC_API MIDL user_al locate size_t size; { unsigned char * ptr; ptr = malloc ( size ) ; return ( (void *)ptr ); void __RPC_API MIDL_user_free ( ob j ect ) void * object; { free (object) ; Example D-10. The do_import_binding Procedure /* FILE NAME: getbind.c */ /* Get binding from name service database. */ #include <stdio.h> # include "inv.h" #include " . . \status .h" void do_import_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 iirport */ char protseq[20]; /* protocol sequence */ status = RpcNsBindinglmportBegin ( /* set context to import binding handles */ 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 */ Appendix D: The Inventory Application 775? Example D- 10: The do_import_binding Procedure (continued) &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 rpc_ns_binding_import_begin */ 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 */ status = RpcNsBindinglnportDone ( /* done with import context */ &import_context /* obtained from rpc_ns_binding_import_begin */ ); return; } Example D-ll. The do_interpret_binding Procedure /* FILE NAME: intbind.c */ /* Interpret binding information and return the protocol sequence. */ #include <stdio.h> # include <rpc.h> #include " . . \status .h" . void do_interpret_binding( binding, protocol_seq) rpc_binding_handle_t binding; /* binding handle to interpret */ cnar *protocol_seq; /* protocol sequence to obtain */ { unsigned long status; /* error status */ unsigned char *string_binding; /* string of binding information */ unsigned char *protseq; /* binding component of interest */ status = RpcBindingToStringBinding ( /* convert binding information to string */ binding, /* the binding handle to convert */ &string_binding /* the string of binding data */ Microsoft RFC Programming Guide Example D- 11: The do_interpret_binding Procedure (continued) ); CHECK_STATUS( status, "Can t get string binding :", RESUME); 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; Example D- 12: The Makefile for the Explicit Client # FILE NAME: Makefile # Makefile for the inventory application explicit client # # definitions for this make file # APPL=inv IDLCMD=midl NTRPCLIBS=rpcrt4.1ib rpcns4.1ib libcmt.lib kerne!32.1ib ! include <ntwin32 .mak> ## NT c flags cflags = -c -WO -Gz -D_X86_=1 -DWIN32 -DMT /nologo ## NT nmake inference rules $(cc) $(cdebug) $ (cflags) $(cvarsmt) /I. /I.. $< $(cvtomf ) # # COMPLETE BUILD of the application # all: local interface client server # # INTERFACE BUILD # interface: $(APPL) .h $ (APPL)_c.obj $ (APPL)_s.obj $ (APPL)_x.obj # # LOCAL BUILD of the client application to test locally # local : Iclient . exe Appendix D: The Inventory Application J81 Example D-12-. The Makefile for the Explicit Client (continued) lclient.exe: Iclient.obj manager. obj invntry.obj $(link) $(linkdebug) $(conflags) -out: lclient.exe -map:lclient.map \ Iclient.obj manager. obj invntry.obj \ $ (NTRPCLIBS) # # CLIENT BUILD # client : client . exe client.exe: client. obj getbind.obj intbind.obj $ (APPL)_c.obj $ (APPL)_x.obj $(link) $(linkdebug) $(conflags) -out: client. exe -map: client. map \ client. obj getbind.obj intbind.obj $(APPL)_c.obj $ (APPL)_x.obj \ $ (NTRPCLIBS) # # SERVER BUILD # server : server . exe server.exe: server. obj manager. obj invntry.obj $ (APPL)_s.obj $ (APPL)_x.obj $(link) $(linkdebug) $(conflags) -out : server . exe -map: server. map \ server. obj manager. obj invntry.obj $ (APPL)_s.obj $ (APPL)_x.obj\ $ (NTRPCLIBS) # client and server sources client. obj: client. c $ (APPL) .h manager. obj: manager. c $(APPL).h server . obj : . . \server . c $ (APPL) . h $(cc) $(cdebug) $(cflags) $(cvarsmt) /I. /I.. \ /Foserver . obj . . \ server . c getbind.obj : . . \implicit\getbind.c $(cc) $(cdebug) $(cflags) $(cvarsmt) /I. /I.. \ /Fogetbind. obj . . \implicit\getbind. c intbind.obj : . . \implicit\intbind.c $(cc) $(cdebug) $(cflags) $(cvarsmt) /I. /I.. \ /Fointbind.obj . .\implicit\intbind.c invntry . obj : . . \ invntry . c $(cc) $(cdebug) $(cflags) $(cvarsmt) /I. /I.. \ /Fo invntry . obj . . \ invntry . c # Local client sources Iclient.obj: client-.c $(APPL).h $(cc) $(cdebug) $(cflags) $(cvarsmt) /DLOCAL /I. /I.. \ /Folclient.obj client. c # client stubs $( APPL) _c. obj: $(APPL)_c.c $( APPL )_x. obj: $(APPL)_x.c # compile the server stub $(APPL)_s.obj : $(APPL)_s.c # generate stubs, auxiliary and header file from the IDL file $(APPL).h $(APPL)_c.c $(APPL)_x.c $(APPL)_s.c: $(APPL).idl $(IDLCMD) $(APPL) .idl Microsoft RFC Programming Guide Example D- 12: The Makefile for the Explicit Client (continued) # clean up for fresh build clean: del $(APPL)_?.c del * . obj del $(APPL) .h del * .map del *.pdb clobber: clean if exist client.exe del client.exe if exist lclient.exe del lclient.exe if exist server.exe del server.exe Example D- 13: The MIDI File, Explicit Binding /* FILE NAME: inv.idl */ /* brackets enclose attributes */ uuid(cbb7c850-0568-llce-b719-08002bl85ad7), /* universal unique identifier */ version(l.O) , /* version of this interface */ pointer_default (unique) /* pointer default ] interface inv /* interface name */ { const long MAX_STRING = 30; /* constant for string size */ typedef long part_num; /* inventory part number */ typedef [string] char part_name [MAX_STRING+1 ] ; /* name of part */ typedef [string, unique] char *paragraph; /* description of part */ typedef enum { ITEM, GRAM, KILOGRAM } part_units; /* units of measurement */ typedef struct part_price { /* price of part */ part_units units; double per_unit; } part_price; typedef union switch (part_units units) total { /* quantity of part */ case ITEM: long int number; case GRAM: case KILOGRAM: double weight; } part_quantity; typedef struct part_list{ /* list of part numbers */ long size; /* number of parts in array */ [size_is(size) ] part_num numbers [*]; /* conformant array of parts */ } part_list; typedef struct part_record { /* data for each part */ part_num number ; part_name name; paragraph description; part_price price ; part_quantity quantity; Appendix D: The Inventory Application 183 Example D- 13: The MIDI File, Explicit Binding (continued) subparts; part_list } part_record; typedef long account_num; /* user account number */ /************************ procedure Declarations ************************* boolean is_part_available( /* return true if in inventory */ [in] handle_t binding_h, /* binding handle for explicit client */ [in] part_num number /* input part number */ void whatis_part_name ( [in] handle_t binding_h, [in] part_num number, [in, out] part_name name /* get part name from inventory */ /* binding handle for explicit client */ /* input part number */ /* output part name */ paragraph get_part k _description ( [in] handle_t binding_h, [in] part_num number /* return a pointer to a string */ binding handle for explicit client */ void whatis_part_price ( [in] handle_t binding_h, [in] part_num number, [out] part_price *price /* get part price from inventory */ /* binding handle for explicit client */ void whatis_part_quantity ( [in] handle_t binding_h, [in] part_num number, [out] part_quantity *quantity /* get part quantity from inventory */ /* binding handle for explicit client */ void whatare_subparts ( [in] handle_t binding_h, [in] part_num number, [out] part_list **subparts /* get list of subpart numbers */ /* binding handle for explicit client */ /* structure containing the array */ /* 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. long order_part( /* order part from inventory, return OK or error code */ [in] handle_t binding_h, /* binding handle for explicit client */ [in] part_num number, [in, out] part_quantity *quantity, /* quantity ordered */ [in] account_num account } /* end of interface definition */ Microsoft RPC Programming Guide Example D- 14: Remote Procedures, Explicit Binding /* 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) handle_t binding_h; /* declare a binding handle */ part_num number; part_record *part; /* a pointer to a part record */ int found; found = read_part_record( number, &part) ; if (found) re turn (TRUE) ; else re turn (FALSE) ; void whatis_part_name(binding_h, number, name) handle_t binding_h; /* declare a binding handle */ part_num number; part_name name; { part_record *part; /* a pointer to a part record */ read_part_record ( number, &part) ; strncpy((char *)name, (char *)part->name, MAX_STRING) ; return; paragraph get_part_description(binding_h, number) handle_t binding_h; /* declare a binding handle */ par t_num number ; part_record *part; /* a pointer to a part record */ paragraph description; int size; if( read_part_record( number, &part) ) { /* Allocated data that is returned to the client must be allocated */ /* with the MIDL_user_al locate stub support routine. */ size = strlen((char *)part->description) + 1; description = (paragraph) MIDL_user_allocate( (unsigned) size) ; strcpy((char *) description, (char *)part->description) ; else description - NULL; return (description) ; } void whatis_part_price(binding_h, number, price) handle_t binding_h; /* declare a binding handle */ Appendix D: The Inventory Application 185 Example D- 14: Remote Procedures, Explicit Binding (continued) part_num number; partjprice *price; { part_record *part; /* a pointer to a part record */ read_part_record ( number , &part ) ; price->units = part->price. units; price->per_unit = part->price.per_unit; return; void whatis_part_quantity (binding_h, number, quantity) handle_t binding_h; /* declare a binding handle */ part_num number; part_quantity *quantity; { part_record *part; /* a pointer to a part record */ read_part_record ( number , &part ) ; quantity->units = part-xjuantity. units; switch ( quantity- >units) { case ITEM: quantity->total. number = part -xjuantity. total. number; break; case KILOGRAM: case GRAM: quantity- >total. weight = part-xjuantity. total. weight; break; } return; void whatare_subparts (binding_h, number, subpart_ptr) handle_t binding_h; /* declare a binding handle */ part_num number; part_list **subpart_ptr; { part_record *part; /* pointer to a part record */ int i; int size; read_part_record( number, &part) ; /* Allocated data that is output to the client must be allocated with */ /* the MIDL_user_al locate stub support routine. Allocate for a */ /* part_list struct plus the array of subpart numbers. Remember the */ /* part_list struct already has an array with one element, hence the -1. */ size = sizeof (part_list) + (sizeof (part_num) * (part->subparts.size-l) ) ; *subpart_ptr = (part_list *)MIDL_user_al locate ( (unsigned) size) ; /* fill in the values */ (*subpart_ptr)->size = part->subparts.size; for(i = 0; i < (*subpart_ptr) ->size; i++) (*subpart_ptr) ->numbers[i] = part ->subparts. numbers [i] ; return; Microsoft RFC Programming Guide Example D- 14: Remote Procedures, Explicit Binding (continued) long int order_part (binding_h, number, quantity, account) handle_t binding_h; /* declare a binding handle */ part_num number; part_quantity *quantity; account_num account ; { part_record *part; /* pointer to a part record */ long error = 1; /* assume no error to start */ /* Test for valid input */ if ( !read_part_record (number, &part) ) /* invalid part number input */ error = -1; else if (quantity->units == ITEM) /* invalid quantity input error = ( quant ity->total. number <= 0) ? -2 : error; else if (quantity->units == GRAM I I quantity->units == KILOGRAM) error = (quantity->total. weight <= 0.0) ? -2 : error; /* else if () invalid account, not implemented */ /* error = -3; */ if (error < 0) return (error) ; /* convert input quantity & units if units are not correct for part */ if (quantity- >units != par t-xguantity. units) { if (part-xjuantity. units == ITEM) /* convert weight to items */ quant ity->total. number = (long int }quantity->total. weight; else if (quantity- >units == ITEM) /* convert items to weight */ quantity- >total .weight = (long float) quantity- >total. number; else if (quantity- >units == GRAM && par t-xguantity. units == KILOGRAM) quant ity->total. weight /= 1000.0; /* convert grams to kilograms */ else if (quantity->units == KILOGRAM && par t-xguantity. units == GRAM) quantity- >total. weight *= 1000.0; /* convert kilograms to grams */ quantity->units = part-xguantity. units; /* check if enough in inventory for this order */ switch (part-xguantity. units) { case ITEM: if (part-xguantity. total. number > quantity- >total . number) /* reduce quantity in inventory by amount ordered */ part-xguantity. total. number -= quantity- >total . number; else { /* order all available and reduce quantity in inventory to */ quant ity->total. number = part-xguantity. total. number; part-xguantity. total. number = 0; } break; case KILOGRAM: case GRAM: if (part-xguantity. total. weight > quantity- >total . weight) /* reduce quantity in inventory by amount ordered */ part-xjuantity. total. weight -= quantity- >total. weight; else { /* order all available and reduce quantity in inventory to 0.0 */ quantity->total .weight = part-xjuantity . total .weight ; Appendix D: The Inventory Application 187 Example D- 14: Remote Procedures, Explicit Binding (continued) part-xjuantity. total, weight = 0.0; } break; write_part_record(part) ; /* update inventory */ return(l); /* order ok */ Example D- 15: The Explicit Client of the Inventory Application /* FILE NAME: client. c */ /******* Client of the inventory application with explicit method ***********/ #include <stdio.h> #include <stdlib.h> #include "inv.h" /* header file created by the IDL compiler */ # inc lude " status . h " char instructions [] = "Type character followed by appropriate argument ( s ). \n\ Is part available? a What is part name? n Get part description. d What is part price? p What is part quantity? q What are subparts of this part? s Order part. o REDISPLAY r\n\ EXIT e\n" ; [part_number] \n\ [part_number] \n\ [part_number] \n\ [part_number] \n\ [part_number] \n\ [part_number] \n\ part_number quantity\n\ main() part_record part; part_list *subparts; account_num account = 1234; unsigned long status; handle_t binding_h; /* structure for all data about a part */ /* pointer to parts list data structure */ /* a user account number */ /* error status */ /* declare a binding handle */ int i, num_args, done = 0; long result; char input [100] , . selection[20] , quantity [20] puts (instructions) ; part. number = 0; strcpy (quantity, " " ) ; Mfndef LOCAL do_import_binding ( " #endif /* find server in name service database */ &binding_h) ; status = RpcBindingReset (binding_h) ; CHECK_STATUS( status, "Can t reset binding handle", ABORT); while (! done) { printf ( "Selection: /* user makes selections and each is processed */ f flush ( stdout ) ; gets ( input ) ; 188 Microsoft RFC Programming Guide Example D- 15: The Explicit Client of the Inventory Application (continued) num_args = sscanf (input, "%s%ld%s", selection, & (part. number ), quantity); switch (tolower (selection [0] )) { case a : if (is_part_available(binding_h, part. number )) puts ( "available : Yes " ) ; else puts ( " avai lable : No " ) ; break; case n : whatis_part_name(binding_h, part. number, part. name); printf ("name: %s\n" , part. name); break; case d : part. description = get_part_description (binding_h, part . number ) ; printf ( "description: \n%s\n" , part .description) ; if (part. description ! = NULL) f ree (part. description) ; /* free memory allocated */ break; case p : whatis_part_price(binding_h, part. number, & (part. price) ) ; printf ( "price :%10. 2 f\n", part.price.per_unit) ; break; case q : whatis_part_quantity (binding_h, part. number, &( part, quantity) ) ; if (part. quantity. units == ITEM) pr int f ( " total i terns : % ld\n " , part . quant i ty . total . number ) ; else if (part. quantity. units == GRAM) printf ("total grams : %10. 2f \n" , part. quantity. total. weight ); else if (part. quantity. units == KILOGRAM) printf ("total kilos :%10.2f\n", part. quantity. total. weight ) ; break; case s : whatare_subparts (binding_h, part. number, &subparts) ; for(i =0; i < subparts->size; i++) printf ( "%ld " , subparts->numbers [i] ) ; printf ("\ntotal number of subparts:%ld\n" , subparts->size) ; free(subparts) ; /* free memory for conformant struct */ break; case o : if (num_args < 3) { puts ("Not enough arguments"); break; /* Assume KILOGRAM units and assign quantity input */ part. quantity. units = KILOGRAM; part. quantity. total. weight = atof (quantity) ; result = order_part (binding_h, part. number, & (part. quantity ), account) if (result > 0) { if (part. quantity. units == ITEM) printf ("order :%ld items \n ", part. quantity. total. number ); else if (part. quantity. units == GRAM) printf ( "order : %10 . 2 f grams \n" , part . quantity . total .weight ) ; else if (part. quantity. units == KILOGRAM) printf ("order :%10.2f kilos\n", part. quantity. total. weight ); else { /* error cases */ if (result == -1) puts ("Invalid part number"); Appendix D: The Inventory Application 289 Example D- 15: The Explicit Client of the Inventory Application (continued) else if (result == -2) puts ("Invalid quantity"); else if (result == -3) puts ("Invalid account number"); } break; case r : /* redisplay selection or bad input displays instructions */ default: puts ( instructions ); break; case e : done = 1; break; } /*end case */ } /* end While */ } /* end main() */ ^** *********************************************************************/ /*** MIDL_user_allocate / MIDL_user_free ***/ /***************** **********************^*^^^^^^^^^^^^^^. fc . fc . fr ^. Ar . fr . fr ^^,. fc . fr . fr . fr ^. void * RPC API MIDL_user_al locate size_t size; unsigned char * ptr; ptr = malloc ( size ) ; return ( (void *)ptr } void __RPC_API MIDL_user_free ob j ect void * object; free (object) ; In this Appendix: How to Run the Application Application Files The Rflle Application The remote file client (rfile.c) copies ASCII data from the client to the server. The source can be a data file or the standard input of the client. The target on the server system is either a file or the server standard output. The rf ile application demonstrates some advanced features of RFC application development including: Using a context handle with a context rundown procedure. Using the explicit binding method with a primitive binding handle. Finding a server using strings of binding information. How to Run the Application To run the server of the distributed application, type the following: C:\SERVER> nmake server C:\SERVER> server To run the client of the distributed application to transfer ASCII data, use an ASCII text file as input and a new data file on the server host as output. Type the follow ing: C:\CLIENT> nmake client C:\CLIENT> client input_file host output_file You can also send ASCII data from the client keyboard (stdin) by using the follow ing client command: C:\CLIENT> client "" host output_file Using stdin. Type input: data data 191 Microsoft RFC Programming Guide Application Files Makefile contains descriptions of how the application is compiled. Some files depend on the header file status. h from the arithmetic application for the CHECK_STATUS macro. See Example E-l. rfile.idl contains descriptions of the data types and procedures for the interface. See Example E-2. client, c interprets the user input by calling the application-specific procedure get_args. A binding handle representing the information about a client-server rela tionship is obtained from strings of binding information. The remote procedure remote_open is called to open the server target file. A buffer is allocated for a con formant array. The application loops, reading source data and sending the data to the target with a remote procedure call to remote_send. Finally, the remote proce dure remote_close is called to close the target file. See Example E-3. getargs.c interprets the user input to obtain the name of a local client ASCII file of source data, the server host to use, and the server target file. See Example E-4. strbind.c contains the do_string_binding procedure that shows how to find a server from strings of binding information. A host name or network address is input, and then combined with a generated protocol sequence to create a valid binding handle, which is returned as a parameter. See Example E-5. crndwn.c is the implementation of a context rundown procedure. The server stub calls this procedure automatically if communication breaks between a client and the server which is maintaining context for the client. For this application, the con text is a file handle of a server data file. This context rundown procedure closes the file. See Example E-6. manager.c is the implementation of the remote procedures defined in the rfile interface. See Example E-7. sewer. c initializes the server with a series of runtime calls prior to servicing remote procedure calls. In this application, all available protocol sequences are registered. The server is not advertised in a name service database. The server s dynamic end- points are added to the server s local endpoint map. A client finds this server by constructing a string binding containing a protocol sequence and the host name or network address. See Example E-8. Example E-l: The Makefile for the Remote File Application # FILE NAME: Makefile # Makefile for the remote file application # # definitions for this make file # APPL=rfile IDLCMD=midl NTRPCLIBS=rpcrt4 . lib rpcns4.1ib libcmt.lib kerne!32.1ib Appendix E: The Rflle Application Example E- 1: The Makefile for the Remote File Application (continued) # Include Windows NT macros ! include <ntwin32 .mak> # NT c flags cflags = -c -WO -Gz -D_X86_=1 -DWIN32 -DMT /nologo # NT nmake inference rules $(cc) $(cdebug) $ (cflags) $(cvarsmt) $< S(cvtocnf) # # COMPLETE BUILD of the application # all: interface client.exe server.exe # # INTERFACE BUILD # interface: $ (APPL) .h $ (APPL)_c.obj $ (APPL)_s.obj $ (APPL)_x.obj # # CLIENT BUILD # client : client . exe client.exe: client. obj getargs.obj strbind.obj $ (APPL)_c.obj $ (APPL)_x.obj $(link) $(linkdebug) $(conflags) -out: client. exe -map: client .map \ client. obj getargs.obj strbind.obj $ (APPL)_c.obj $ (APPL)_x.obj \ $(NTRPCLIBS) # # SERVER BUILD # server : server . exe server.exe: server. obj manager. obj crndwn.obj $ (APPL) _s. obj $ (APPL) _x. obj $(link) $(linkdebug) $(conflags) -out : server . exe -map: server. map \ server. obj manager. obj crndwn.obj $ (APPL) _s. obj $ (APPL) _x. obj \ $(NTRPCLIBS) # client and server sources client. obj: client. c $(APPL).h manager . obj : manager . c $ (APPL ) . h server . obj : server . c $ (APPL ) . h crndwn.obj: crndwn.c $(APPL).h getargs . obj : getargs . c strbind . obj : strbind . c # Local client sources Iclient.obj: client. c $(APPL).h $(cc) $(cdebug) $ (cflags) $(cvarsmt) /DLOCAL \ /Fold ient. obj client. c Imanager . obj : manager . c $ (APPL ) . h $(cc) $(cdebug) $ (cflags) $(cvarsmt) /DLOCAL \ /Folmanager . ob j manager. c 194 Microsoft RFC Programming Guide Example E-l: The Makefile for the Remote File Application (continued) # client stubs $(APPL)_c.obj: $(APPL)_c.c $(APPL)_x.obj: $(APPL)_x.c # compile the server stub $(APPL)_s.obj : $(APPL)_s.c # generate stubs, auxiliary and header file from the IDL file $(APPL).h $(APPL)_c.c $(APPL)_x.c : $(APPL).idl $(IDLCMD) $(APPL).idl # clean up for fresh build clean: del $(APPL)_?.c del *.obj del $(APPL) .h del *.map del *.pdb clobber: clean if exist client.exe del client.exe if exist lclient.exe del lclient.exe if exist server.exe del server.exe Example E-2: The MIDI File of the Remote File Application /* FILE NAME: rfile.idl */ [ uuid(A6lE4024-A53F-101A-BlAF-08002B2E5B76) , version (1.0) , pointer_default (unique) ] interface rfile /* file manipulation on a remote system */ { typedef [context_handle] void *filehandle; typedef byte buffer [ ] ; filehandle remote_open( /* open for write */ [in] handle_t binding_h, /* explicit primitive binding handle */ [in, string] char name[], /* if name is null, use stdout in server */ [in, string] char mode[] /* values can be "r", "w" , or "a" */ ); long remote_send( [in] filehandle fh, [in, max_is(max)] buffer buf, [in] long max ); void remote_close ( [in, out] filehandle *fh Appendix E: The Rflle Application 195 Example E~3: A Client File of the Remote File Application /* FILE NAME: client. c */ Mnclude <stdio.h> # include <stdlib.h> #include <string.h> #include "rfile.h" #define MAX 200 /* maximum line length for a file */ main(argc, argv) int argc; char *argv [ ] ; { FILE *local_fh; /* file handle for client file input */ char host [100]; /* name or network address of remote host */ char remote_name[100] ; /* name of remote file */ rpc_binding_handle_t binding_h; /* binding handle */ filehandle remote_fh; /* context handle */ buffer *buf_ptr; /* buffer pointer for data sent */ int size; /* size of data buffer */ get_args(argc, argv, &local_fh, host, (char *)remote_name) ; #ifndef LOCAL if (do_string_binding(host, &binding_h) < 0) { fprintf (stderr, "Cannot get binding\n" ) ; exit(l); } #endif remote_fh = remote_open(binding_h, remote_name, (char *)"w"); if (remote_fh == NULL) { fprintf (stderr, "Cannot open remote file\n"); exit(l) ; } /* The buffer data type is a conformant array of bytes; */ /* memory must be allocated for a conformant array. */ buf_ptr = (buffer *)malloc( (MAX+1) * sizeof (buffer) ); while( fgets((char *)buf_ptr, MAX, local_fh) != NULL) { size = ( int )strlen( (char *)buf_ptr); /* data sent will not include \0 */ if( remote_send(remote_fh, (*buf_ptr), size) < 1) { fprintf (stderr, "Cannot write to remote file\n"); exit(l) ; remote_close(&remote_fh) ; } Example E-4-. The get_args Procedure /* FILE NAME: getargs.c */ ttinclude <stdio.h> #include <stdlib.h> #include <string.h> get_args(argc, argv, local_fh, host, remote_name) int argc ; Microsoft RFC Programming Guide Example E-4-. The get_args Procedure (continued) char *argv[] ; FILE **local_fh; char host [ ] ; char remote_name [ ] ; { char local_name[100] ; switch (argc) { case 1: case 2: printf ("Usage: %s [local_file] host [remote_f ile] \n" , argv[0]) puts ("Use \"\" for local stdin."); exit(O); break; case 3: strcpy (local_nanie, argv[l]); /* use the same file name */ strcpy ( remote_name , local_name ) ; strcpy (host , argv [2 ] ) ; break; default : strcpy ( local_name , argv [ 1 ] ) ; strcpy (host , argv [2 ] ) ; strcpy ( remote_name , argv [ 3 ] ) ; break; } if (strlen(local_name) ==0) { (*local_fh) = stdin; puts ("Using stdin. Type input:"); } else if( ( (*local_fh) = fopen(local_name, "r")) == NULL ) { puts ("Cannot open local file"); exit ( 1 ) ; } return; Example E-5: The do_string_binding Procedure /* FILE NAME: strbind.c */ /* Find a server binding handle from strings of binding information */ /* including protocol sequence, host address, and server process endpoint. */ ttinclude <stdio.h> #include "rfile.h" ttinclude " 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 */ 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 */ int i , result ; status = RpcNetworklnqProtseqs ( /* obtain a list of valid protocol sequences */ &protseq_vector /* list of protocol sequences obtained */ Appendix E: The Rflle Application 197 Example E-5: The do_string_binding Procedure (continued) ); CHECK_STATUS( status, "Can t get protocol sequences:", ABORT); /* loop through protocol sequences until a binding handle is obtained */ for(i=0; i < protseq_yector->Count; i++) { status = RpcStringBindingCompose ( /* 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 */ 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 */ &prot seq_vector ); CHECK_STATUS( status, "Can t free protocol sequence vector :", RESUME); ret urn (result) ; } Example E-6: The Context Rundown of the Remote File Application /* FILE NAME: crndwn.c */ #include <stdio.h> #include "rfile.h" void filehandle_rundown(remote_fh) filehandle remote_fh; /* the context handle is passed in */ { fprintf (stderr, "Server executing context rundown\n" ) ; 198 Microsoft RPC Programming Guide Example E-6: The Context Rundown of the Remote File Application (continued) if ( (FILE *)remote_fh != stdout ) f close ( (FILE *)remote_fh ) ; /* file is closed if client is gone */ remote_fh = NULL; /* must set context handle to NULL */ return; } Example E- 7: Remote Procedures of the Remote File Application I* FILE NAME: manager. c */ # include <stdio.h> #include <string.h> # include <io.h> # include <errno.h> ttinclude "rfile.h" filehandle remote_open(binding_h, name, mode) rpc_binding_handle_t binding_h; char name t ] ; char mode [ ] ; { FILE *FILEh; if (strlen( (char *)name) == 0) /* no file name given */ if (strcmp( (char *)mode, "r") == 0) FILEh = NULL; /* cannot read nonexistent file */ else FILEh = stdout; /* use server stdout */ else if (_access ( (char *)name, 0) == 0) /* file exists */ if (strcmp( (char *)mode, "w" ) == 0) FILEh = NULL; /* do not overwrite existing file */ else FILEh = f open ((char *)name, (char *)mode) ; /* open read/append */ else /* file does not exist */ if (strcmp( (char *)mode, "r") == 0) FILEh = NULL; /* cannot read nonexistent file */ else FILEh = f open ( (char *)name, (char *)mode); /* open write/append */ return( (filehandle) FILEh ); /* cast FILE handle to context handle */ long int remote_send(fh, buf, max) filehandle fh; buffer buf; long int max; { /* write data to the file (context) , which is cast as a FILE pointer */ return( fwrite(buf, max, 1, fh) ) ; void remote_close(fh) filehandle *fh; /* the client stub needs the changed value upon return */ { if( (FILE *) (*fh) != stdout ) f close ( (FILE *) (*fh) ); (*fh) = NULL; /* assign NULL to the context handle to free it */ Appendix E: The Rflle Application 199 Example E- 7: Remote Procedures of the Remote File Application (continued) return; } Example E-8: Server Initialization of the Remote File Application /* FILE NAME: server. c */ ttinclude <stdio.h> #include "rfile.h" /* header created by the idl compiler */ # include " status. h" /* contains the CHECK_STATUS macro */ main () { unsigned long status; /* error status */ rpc_binding_vector_t *binding_vector; /* binding handle list */ status = /* error status */ RpcServerRegisterlf ( /* register interface with the RFC runtime */ rfile_vl_0_s_ifspec, /* handle for interface specification */ NULL, NULL ); CHECK_STATUS ( status, "Can t register interface", ABORT); status = RpcServerUseAllProtseqs ( /* establish protocol sequences */ RPC_C_PROTSEO_MAX_REQS_DEFAULT, /* queue length for remote calls */ NULL /* no security descriptor */ ); CHECK_STATUS( status, "Can t establish protocol sequences", ABORT); status = RpcServerlnqBindings ( /* get set of this server s binding handles */ &binding_vector ); CHECK_STATUS( status, "Can t get binding handles", ABORT); status = RpcEpRegister ( /* add endpoint to local endpoint map */ rfile_vl_0_s_ifspec, /* handle for interface specification */ binding_yector, /* vector of server binding handles */ NULL, /* no object UUIDs to register */ (unsigned char *) "remote_file server" /* annotation (not required) */ ); CHECK_STATUS( status, "Can t add endpoints to local endpoint map:", ABORT); puts ("Listening for remote procedure calls..."); RpcTryFinally { status = RpcServerListen ( /* listen for remote calls */ 1, /* Minimum number of threads */ RPC_C_LISTEN_MAX_CALLS_DEFAULT, /* Maximum number of threads */ NULL ); CHECK_STATUS( status, "rpc listen failed:", RESUME); 200 Microsoft RFC Programming Guide Example E- 8: Server Initialization of the Remote File Application (continued) RpcFinally { puts ( "Removing endpoints from local endpoint map."); status = RpcEpUnregister ( /* remove endpoints from local endpoint map */ rfile_vl_0_s_ifspec, /* handle for interface specification */ binding_vector, /* vector of server binding handles */ NULL /* no object UUIDs to unregister */ ); CHECK_STATUS( status, "Can t remove endpoints from endpoint map:", RESUME); status = RpcBindingVectorFree ( /* free set of binding handles */ &binding_vector ); CHECK_STATUS ( status , "Can t free binding handles and vector", ABORT); } RpcEndFinally } /*** MIDL_user_al locate / MIDL_user_free ***/ void * __RPC_API MIDL_user_al locate size_t size; { unsigned char * ptr; ptr = malloc ( size ) ; return ( (void *)ptr void __RPC_API MIDL_user_free ( object ) void * object; { free (object) ; In this Appendix: How to Build and Run the Application Application Files The Windows Phonebook Application The phonebook application demonstrates a simple Windows client interface to a Microsoft RFC application. The Windows client looks up names in a phonebook database file maintained by the phonebook server (phnbkd.exe). The client does not use the Microsoft Locator name service, so you need to supply the server host name or address to a dialog box in the client interface. How to Build and Run the Application To build and run the server of the distributed application, type the following: C:\SERVER> nmake phnbkd.exe C:\SERVER> phiibkd To build and run the Windows client of the distributed application, type the fol lowing: C:\CLIENT> nmake phnbk.exe C:\CLIENT> phnbk Enter a hostname or address into the Server Host Name dialog box. Try the browse feature first to see some names. Then enter names into the Search String dialog box. Application Files Makefile contains descriptions of how the application is compiled. See Example F-l. phnbk.idl contains descriptions of the data types and procedures for the interface. See Example F-2. 207 202 Microsoft RFC Programming Guide pbnbk.acf is an attribute configuration file that specifies implicit binding as the client binding method. See Example F-3. wclient.c provides a Windows user interface to the server (phnbkd.exe). The client invokes remote procedure calls based on user actions. See Example F-4. wpbnbk.defis a Windows module definition file. It defines the name of the appli cation, the type of image to be produced, and other attributes of the application. See Example F-5. wphnbk.h is a header file that defines constants used in wphnbk.c and in the resource file wpbnbk.rc. See Example F-6. wphnbk.rc is a Windows resource file. It describes the size and appearance of the Windows dialog box and of the controls (such as buttons and edit boxes) used by the application. See Example F-7. manager, c is the implementation of the remote procedures defined in the phnbk interface. The remote procedures look up names contained in the phnbk.txt database file. See Example F-8. server. c initializes the server with a series of runtime calls prior to servicing remote procedure calls. This application specifies to use the TCP/IP protocol sequence. The server is not advertised in a name service database. The server s dynamic end- points are added to the server s local endpoint map. A client finds this server by constructing a string binding containing a protocol sequence and the host name or network address. See Example F-9. phnbk.txt is an ASCII file containing the database of names used by the phonebook server. We created it using a text editor. You can add your own lines to this file. Make sure lines are under 100 characters in length. See Example F-10. Example F-l: The Makefile for the Windows Phonebook Application # # # Build phnbk client and server for Windows NT # # ! INCLUDE <ntwin32.mak> includes = -I. all : phnbk.exe phnbkd.exe # # Link simple client # phnbk.exe: wclient.obj wphnbk.obj phnbk_c.obj $(link) $(linkdebug) $(guiflags) -out: phnbk.exe \ wclient.obj phnbk_c.obj wphnbk.obj \ rpcrt4.1ib rpcns4.1ib rpcndr.lib $(guilibs) Appendix F: The Windows Phonebook Application 203 Example F-l: The Makefile for the Windows Phonebook Application (continued) # Link server # phnbkd.exe: server. obj manager. obj phnbk_s.obj $(link) $ (linkdebug) $(conflags) -out: phnbkd.exe \ server. obj manager. obj phnbk_s.obj \ rpcrt4.1ib rpcns4.1ib rpcndr.lib $(conlibs) # # .RES # wphnbk . obj : wphnbk . re re -r wphnbk. re cvtres -$(CPU) wphnbk. res # # Compile simple client source code # . wclient.obj: wclient.c phnbk.h $(cc) $(cflags) $(cvars) $(scall) $ (includes) wclient.c # # Compile server source code # server. obj: server. c phnbk.h $(cc) $(cflags) $(cvars) $(scall) $ (includes) server. c manager. obj: manager. c phnbk.h $(cc) $(cflags) $(cvars) $(scall) $ (includes) manager. c # # Compile client stubs # phnbk_c.obj : phnbk_c.c phnbk.h $(cc) $(cflags) $(cvars) $(scall) $ (includes) phnbk_c.c ## # $(cc) $(cflags) $(cvars) $(scall) $ (includes) phnbk_x.c # # Compile server stubs # phnbk_s.obj : phnbk_s.c $(cc) $(cflags) $(cvars) $(scall) $ (includes) phnbk_s.c #phnbk_v.obj : phnbk_y.c # $(cc) $(cflags) $(cvars) $(scall) $ (includes) phnbk_y.c # # Generate stubs and header file from interface definition # phnbk.h : phnbk.idl phnbk.acf midl phnbk.idl # # Clean up for fresh build # clean : Microsoft RFC Programming Guide Example F-l: The Makefile for the Windows Phonebook Application (continued) del phnbk_*.* del *.obj del phribk.h # # Clean up all byproducts of build # clobber : clean del phnbk.exe del phnbkd.exe del *.res Example F-2: The MIDI File of the Windows Phonebook Application /* ** Interface definition file for irrplicit phnbk client */ uuid(F2FE85AO-OC28-1068-A726-AA0004007EFF) , version (1.0) , pointer_default (ref ) ] interface phnbk { /* ** Constant for maximum line size */ const long LINESIZE = 100; /* ** Flag for hitting end of phonebook file */ const short END = -1; /* ** Flag for normal completion of operation */ const short NORMAL = 0; /* ** Define all possible operations on phonebook file */ typedef enum { FIRSTMATCH, NEXTMATCH, BROWSE, RESET, BROWSE_RESET } operations; /* ** Perform some operation on the phonebook */ short lookup Appendix F: The Windows Phonebook Application 205 Example F- 2: The MIDI File of the Windows Phonebook Application (continued) ( [in] short operation, [in, string] char search_string [LINESIZE] , [out, string] char return_string [LINESIZE] ); } Example F~3: The ACF File of the Windows Phonebook Application [inplicit_handle (handle_t xhandle) ] interface phnbk {} Example F-4: Client File of the Windows Phonebook Application /* ** ** ** MODULE: wclient.c ** PROGRAM: Windows wphnbk application ** ** ** ** */ ttinclude <windows.h> ttinclude <stdlib.h> # include <string.h> # include <ctype.h> Mnclude "phnbk. h" #include "wphnbk. h" int lookup_status ; /* lookup return status */ error_status_t status; /* rpc status */ unsigned char input [LINESIZE] ; /* find search string */ char output [LINESIZE] ; /* string returned from database */ char oldmatch [LINESIZE] ;/* previous find string */ unsigned char server [80]; /* string binding for server */ short operation; /* operation requested */ short no_handle; /* handle not initialized flag */ unsigned char hostname [ 32 ]; /* phnbk server host name */ long FAR PASCAL WndProc (HWND, WORD, WDRD, LONG) ; int PASCAL WinMain ( HANDLE hlnstance, HANDLE hPrevInstance, LPSTR IpszCmdLine, int nCmdShow 206 Microsoft RFC Programming Guide Example F-4: Client File of the Windows Phonebook Application (continued) char szAppName [] = "WPHNBK" HWND hwnd ; MSG msg; WNDCLASS wndclass ; /* ** Initialize strings */ input [0] output [0] oldmatch[0] = server [ ] = hostname [ ] = no_handle = TRUE; /* ** Standard Windows stuff. */ if ( IhPrevInstance) wndclass wndclass wndclass wndclass wndclass . wndclass . wndclass . wndclass . wndclass . wndclass . style IpfnWndProc cbClsExtra cbWndExtra hlnstance hlcon hCursor hbrBackground IpszMenuName IpszClassName CS_HREDRAW I CS_VREDRAW; (WNDPROC) WndProc ; = DLGWINDOWEXTRA ; hlnstance ; Loadlcon (hlnstance, szAppName) ; LoadCursor ( (HINSTANCE)NULL, IDC_ARRCW) (HBRUSH) (COLORJWINDOW + 1) ; NULL ; szAppName ; RegisterClass (&wndclass) ; hwnd = CreateDialog (hlnstance, szAppName, 0, NULL) ShowWindow (hwnd, nCmdShow) ; SetFocus ( GetDlgltem (hwnd, HOSTNAMEBOX ) ) ; / * ** Start accepting messages */ while ( GetMessage (&msg, NULL, 0, 0) ) TranslateMessage (&msg) ; DispatchMessage (&msg) ; return msg.wParam ; short InitHandle HWND hwnd Appendix F: The Windows Phonebook Application 207 Example F- 4: Client File of the Windows Phonebook Application (continued) I* ** Read server host name */ GetDlgltemText (hwnd, HOSTTSIAMEBOX, hostname, 16 ) ; /* ** Warn user if they haven t specified a host name */ if (hostname [0] == \0 ) { MessageBox ( hwnd, "Please enter server host name", "ERROR", MB_OK ); SetFocus ( GetDlgltem (hwnd, HOSTOAMEBOX) ) ; return (-1) ; /* ** Build server string binding */ strcat (server, "ncacn_ip_tcp: " ) ; strcat (server, hostname) ; /* ** Convert the character string binding into an RFC handle */ status = RpcBindingFromStringBinding ( server, &xhandle if (status) { MessageBox hwnd, "Invalid string binding", "ERROR", MB_OK exit (EXIT_FAILURE) ; } no_handle = FALSE; return (0); 208 Microsoft RFC Programming Guide Example F-4-. Client File of the Windows Phonebook Application (continued) void ShowResult HWND hwnd ) { /* ** Display lookup results, based on the context of ** the requested operation */ if (operation == BROWSE) /* ** BROWSE return next entry */ if ( lookup_status == NORMAL) /* ** Everything ok, display next entry */ SetDlgltemText (hwnd, RESULTSBOX, output ) ; else /* ** Otherwise, we hit end of file... */ SetDlgltemText (hwnd, RESULTSBOX, " " ) ; SetDlgltemText (hwnd, INFOBOX, "No more entries"); else /* ** Operation was a Find or Find Next. . .tailor message ** syntax to reflect the operation. */ if ( lookup_status == NORMAL) { /* ** Print results */ SetDlgltemText (hwnd, RESULTSBOX, output ) ; /* ** Determine if this was first match, or subsequent match */ if (operation == FIRSTMATCH) SetDlgltemText (hwnd, INFOBOX, "Match found" ) ; else SetDlgltemText (hwnd, INFOBOX, "Another match found" ) ; Appendix F: The Windows Phonebook Application 209 Example F- 4: Client File of the Windows Phonebook Application (continued) else /* ** Hit end of file during search */ if (operation == FIRSTMATCH) SetDlgltemText (hwnd, INFOBOX, "Match not found" ) ; else SetDlgltemText (hwnd, INFOBOX, "No other matches found" ) ; } return; long FAR PASCAL WndProc ( HWND hwnd, WORD message, WORD wParam, LONG iParam /* ** We switch cursors to the hourglass during ** a lookup RPC. This is for saving the ** regular pointer. */ HCURSOR OldCursor; /* ** First thing, save the match string from last time around */ strcpy (oldmatch, input) ; /* ** Switch on the incoming message type (standard Windows ** programming) */ switch (message) { /* ** Got a button pushed */ case WM_COMMAND: switch (wParam) { /* ** Either a Find or a Find Next */ case FINDBUTTON: if (no_handle) if (InitHandle(hwnd) ) break; 210 Microsoft RFC Programming Guide Example F- 4: Client File of the Windows Phonebook Application (continued) /* ** Clear current text */ SetDlglterrtText (hwnd,RESULTSBOX, " " ) ; SetDlgltemText (hwnd, HSJFOBOX, " " ) ; /* ** Read the search string */ GetDlgltemText (hwnd, SEARCHBOX, input , 32 ) ; /* ** Make sure user entered a search string */ if (input[0] == (unsigned char) \0 ) { MessageBox ( hwnd, "Missing Search String!", "ERROR", MB_OK ); /* ** Set focus back to SEARCHBOX so user can ** enter search string */ SetFocus ( GetDlgltem (hwnd, SEARCHBOX) ) ; else /* ** Search string is present. Save existing ** pointer and display hourglass */ OldCursor = SetCursor (LoadCursor (NULL,IDC_WAIT) ShowCursor (TRUE) ; /* ** Determine desired operation */ if (strcmpfoldmatch, input) ) operation = FIRSTMATCH; else operation = NEXTMATCH; /* ** Perform the requested operation */ lookup_status = lookup ( operation, input , output Appendix F: The Windows Phonebook Application 211 Example F- 4: Client File of the Windows Phonebook Application (continued) /* ** Restore pointer cursor */ ShowCursor (FALSE) ; SetCursor ( OldCursor ) ; /* ** Display lookup results */ ShowResult (hwnd) ; break; /* ** BROWSE return next entry */ case BROWSEBUTTON: if (no_handle) i f ( Ini tHandle ( hwnd ) ) break ; /* ** Clear existing text and display status */ SetDlgltemText (hwnd, RESULTSBOX, " " ) ; SetDlgltemText (hwnd, SEARCHBOX, "" ) ; SetDlgltemText (hwnd, INFOBOX, "Browsing. . . " ) ; /* ** Switch to hourglass cursor */ OldCursor = SetCursor (LoadCursor (NULL,IDC_WAIT) ShowCursor (TRUE) ; operation = BROWSE; /* ** Perform the requested operation */ lookup_status = lookup ( operation, input, output /* ** Restore pointer cursor */ ShowCursor (FALSE) ; SetCursor ( OldCursor ) ; 212 Microsoft RFC Programming Guide Example F- 4: Client File of the Windows Phonebook Application (continued) ** Display operation results */ ShowResult (hwnd) ; break; /* 1 ** User has requested a RESET. This clears all ** text and rewinds the phonebook file */ case RESETBUTTON: if (no_handle) if (InitHandle(hwnd) ) break; /* ** Clear all text */ SetDlgltemText (hwnd, RESULTSBOX, " " ) ; SetDlglteinText (hwnd, INFOBOX, " " ) ; SetDlgltemText (hwnd, SEARCHBOX, " " ) ; input [0] = \0 ; operation = RESET; /* ** Perform the requested operation */ lookup_status = lookup ( operation, input , output ); break; return ; /* ** User has closed the application */ case WM_DESTROY: if (!no_handle) { /* ** Free binding handle, post quit message and leave */ status = RpcBindingFree ( &xhandle PostQuitMessage (0) ; Appendix F: The Windows Phonebook Application 213 Example F-4: Client File of the Windows Phonebook Application (continued) return ; /* ** Ignore other messages */ default : return DefWindowProc (hwnd, message, wParam, iParam) ; Example F-5: Window Module Definition File ; WPHNBK.DEF module definition file NAME WPHNBK DESCRIPTION Windows RFC Phonebook EXETYPE WINDOWS STUB WINSTUB.EXE CODE PRELOAD FIXED DISCARDABLE DATA PRELOAD FIXED MULTIPLE HEAPSIZE 8192 STACKSIZE 8192 EXPORTS WndProc Example F-6: Header File #define SEARCHBOX 102 #define RESULTSBOX 104 #define INFOBOX 106 #define FINDBUTTON 113 #define BROWSEBUTTON 112 #define RESETBUTTON 110 ttdefine HOSTNAMEBOX 109 Example F- 7: Resource File ttinclude <windows.h> #include "wphnbk.h" WPHNBK DIALOG 15, 33, 315, 102 CAPTION "Windows RFC Phonebook" STYLE WS_OVERLAPPED | WS_BORDER I WS_CAPTION I WS_SYSMENU I WS_MINIMIZEBOX CLASS "WPHNBK" BEGIN CONTROL "Search String:", 100, "static", SS_LEFT I WS_CHILD, 13, 18, 47, 10 CONTROL "Input", 101, "button", BS_GROUPBOX I WS_TABSTOP I WS_CHILD, 5, 3, 173, 32 CONTROL "", 102, "edit", ES_LEFT I WS_BORDER I WSJTABSTOP I WS_CHILD, 63, 17, 108, 12 CONTROL "Search Results:", 103, "static", SS_LEFT | WS_CHILD, 6, 50, 58, 7 CONTROL "", 104, "edit", ES_LEFT | WS_BORDER | WS_TABSTOP I WS_CHILD, 64, 48, 239, 12 214 Microsoft RPC Programming Guide Example F- 7: Resource File (continued) CONTROL "Status:", 105, "static", SS_LEFT I WS_CHILD, 6, 80, 26, 8 CONTROL "", 106, "edit", ES_LEFT I WS_BORDER I WSJTABSTOP I WS_CHILD, 30, 78, 133, 12 CONTROL "Output", 108, "button", BS_GROUPBOX I WS_TABSTOP I WS_CHILD, 4, 36, 305, 31 CONTROL "Information", 111, "button", BS_GROUPBOX I WS_TABSTOP I WS_CHILD, 4, 68, 305, 31 CONTROL "Find / Find Next", 113, "button", BS_PUSHBUTTCN I WSJTABSTOP I WS_CHILD, 192, 6, 112, 14 CONTROL "Reset", 110, "button", BS_PUSHBUTTON I WS_TABSTOP I WS_CHILD, 192, 22, 50, 14 CONTROL "Browse", 112, "button", BS_PUSHBUTTCN I WS_TABSTOP I WS_CHILD, 258, 22, 46, 14 CONTROL "", HOSTNAMEBOX, "edit", ES_LEFT I WS_BORDER | WS_TABSTOP I WS_CHILD, 228, 78, 76, 12 CONTROL "Server Host Name: ",107, "static", SS_LEFT I WS_CHILD, 166, 80, 62, 8 END Example F-8: Remote Procedures /* ** MODULE: manager. c * PROGRAM: phnbk application I ** ** ** ** */ #include <stdio.h> #include <string.h> ttinclude <malloc.h> #include <stdlib.h> ttinclude "phnbk. h" #ifdef WIN32 #endif extern FILE *filehandle; /* Phonebook file filehandle */ extern short previous_operation; /* Keeps track of previous operation */ /* ** ** FUNCTION: getfileline ** ** PURPOSE: ** Retrieve Lines from input file Appendix F: The Windows Phonebook Application 215 Example F- 8: Remote Procedures (continued) */ int getfileline ( line, phone ) unsigned char * line; FILE * phone; { /* ** Each call of this routine returns a line of the ** phonebook file. On EOF, it returns -1. */ char ch; while ((ch = fgetc (phone) ) != \n && ch != EOF) { /* ** Tabs are unpredictable, so substitute ** three spaces if you run across a tab. . . */ if (ch == \t ) { *line++ = ; *line++ = ; *line++ = ; } else *line++ = ch; *line++ = \0 ; if (ch == EOF) return (END) ; else return (NORMAL) ; } /* ** ** FUNCTION: lookup ** ** PURPOSE: Look up entries in database ** */ short lookup ( op, stringin, Microsoft RFC Programming Guide Example F-8: Remote Procedures (continued) stringout ) short op; unsigned char stringin[LINESIZE] ; unsigned char stringout [LUSESIZE] ; { unsigned char buf [LINESIZE] ; /* ** Switch on requested operation */ switch (op) { case RESET: /* ** Reset context */ printf ( "Phonebook: \tRESET\n" ) ; rewind (filehandle) ; previous_operation = FIRSTMATCH; return (NORMAL) ; break; case FIRSTMATCH: /* ** Look for first match of a string, starting at the ** beginning of the file... */ printf ( "Phonebook: \tFIRSTMATCH\n" ) ; rewind (filehandle) ; break; case NEXTMATCH : /* ** Nothing special here, fall out and continue search */ printf ( "Phonebook: \tNEXTMATCH\n" ) ; break; case BROWSE : /* ** A BROWSE operation just returns the next entry... ** ** If the last operation was a BROWSE that got an EOF, ** then rewind and start cycling through again. */ printf ( "Phonebook: \tBROWSE\n" ) ; if (previous_operation == BROWSE_RESET) rewind (filehandle); Appendix F: The Windows Phonebook Application 217 Example F-8: Remote Procedures (continued) if ((getfileline(buf,filehandle)) != -1) { /* ** If not EOF, then just return next entry. */ strcpy ( ( char * ) stringout , ( char * ) buf ) ; printf ("Phonebook: \tFound %s\n", buf); previous_operation = BROWSE; return (NORMAL) ; } else { /* ** This allows the client to flag "no more entries" ** before cycling through the file again on ** another BROWSE request. */ previous_operation = BROWSE_RESET; return (END) ; /* ** Keep track of previous operation in p_context */ previous_operation = op; /* ** Either return the line of the file that contains a string ** match, or return -1... */ while ( (getfileline(buf,filehandle) ) != -1) { if ( (strstrf (char *)buf, (char *)stringin)) != (char *) NULL) { printf ("Phonebook: \tFound %s\n" , buf); strcpy ( (char *) stringout, (char *)buf) ; return (NORMAL) ; return (END) ; Example F-9: Server Initialization /* ** ** ** MODULE: server. c ** 218 Microsoft RFC Programming Guide Example F- 9: Server Initialization (continued) ** ** PROGRAM: phribk application ** ** ** ** ** */ #include <stdio.h> #include <string.h> ttinclude <stdlib.h> #include <malloc.h> #include "phnbk.h" #ifdef WIN32 #define MAIN_DECL _CRTAPIl #else #define MAIN_DECL #include <dce/rpcexc.h> #endif #define IFSPEC phnbk_vl_0_s_ifspec FILE * filehandle; /* File handle used for phonebook file */ short previous_operation; /* Keeps track of previous phonebook operation */ int MAIN_DECL main ( ac, av ) int ac; char *av [ ] ; { unsigned int i; error_status_t status ; unsigned char *string_binding; RPC_BINDING_VECTOR *bvec; /* ** ** Specify TCP/IP as a protocol sequences */ status = RpcServerUseProtseq ( "ncacn_ip_tcp" , 5, NULL ); if (status != RPC_S_OK) Appendix F: The Windows Phonebook Application _ 219 Example F-9: Server Initialization (continued) printf("No available protocol sequences \n "); exit (EXIT_FAILURE) ; } /* ** register the server interface */ status = RpcServerRegisterlf ( IFSPEC, NULL, NULL ); if (status != RPC_S_OK) { printf ("Can t register interface \n"); exit (EXIT_FAILURE) ; } /* ** find out what binding information is actually available */ status = RpcServerlnqBindings ( &bvec ); if (status != RPC_S_OK) { printf ("Can t inquire bindings \n"); exit (EXIT_FAILURE) ; } /* ** register with endpoint mapper */ status = RpcEpRegister ( IFSPEC, bvec, NULL, (unsigned char *)"phnbk endpoint" ); if (status != RPC_S_OK) { printf ("Can t register endpoint \n "); exit (EXIT_FAILURE) ; ** Get the string bindings and print them */ for (i = 0; i < bvec->Count; i++) 220 Microsoft RFC Programming Guide Example F-9: Server Initialization (continued) ** For each binding, convert it to a ** string representation */ status = RpcBindingToStringBinding ( bvec->BindingH [ i ] , &string_binding ); if (status != RPC_S_OK) { print f ("Can t get string binding \n"); exit (EXIT_FAILURE) ; } printf ( " %s\n" , string_binding) ; } /* ** Open the phonebook file */ f ilehandle = f open ( "phnbk . txt " , " r " ) ; /* ** Server is all ready to start listening for client ** requests. . . */ status = RpcServerListen 1, 2, if (status != RPC_S_OK) printf ( "Error: rpc_server_listen ( ) returned \n" ) ; return (EXIT_FAILURE) ; } #ifdef WIN32 /*** MIDL_user_allocate / MIDL_user_free ***/ void * __RPC_API MIDL_user_allocate size_t size; { unsigned char * ptr; Appendix F: The Windows Phonebook Application 221 Example F-9: Server Initialization (continued) ptr = malloc ( size ) ; return ( (void *)ptr ) ; void _RPC_API MIDL_user_free object void * object; free (object) ; #endif Example F- 1 0: Sample Input Data Mickey Mouse 555-2345 Donald Duck 555-2342 Pluto 555-4564 James T. Kirk 555-2342 Fred Flintstone 555-2342 Spider Man 555-2345 Bat Man 555-2342 George Jetson 555-2342 Peter Pan 555-4312 John Doe 555-8888 Charlie Brown 555-2374 Index [] (brackets) in MIDL, 30 ACF (attribute configuration file), 42-44 automatic binding, 49 binding handles, 53 binding methods, 48 controlling errors, 44 example of, 43 exceptions, 44 explicit binding, 53 implicit binding, 51, 176 separating client/server output, 42 windows phnbk application, 205 (see also binding methods) ACF attributes autojiandle, 43, 48-49, 140 byte_count, 98 code, 44, 140 comm_status, 44, 140 context_handle, 139-140 dont_free, 98 explicit_handle, 43, 48, 53, 140 fault_status, 44, 140 implicit_handle, 43-44, 48, 51, 140 nocode, 44, 140 (see also MIDL attributes) active context handles (see context handles) address, host network, 104 advertising the server, 107-109 aliasing, pointer, 83, 87 allocating memory buffers, 97-98 for conformant arrays, 93-94 for context handles, 135 freeing, 87 inventory application, 158 node-by-node, 96-97 (see also memory management) applications arithmetic, 3, 149-156 distributed, 149 files, 150-156 inventory, 30, 157-189 managing, routines for, 145, 147 memory management, 96 producing and running, 21-24 rfile, 129, 191-200 arith.bat, 150 arith.idl, 150 arithmetic application, 3, 149-156 CHECK_STATUS macro, 155 client file, 153 initialization, 153 interface, 152 Makefile, 150 remote procedure, 153 server shell script, 152 array attribute, 137-138 arrays, 34, 79, 90-94, 149 conformant, 90-94, 116-117 as procedure parameters, 93 managing size of, 91-94 223 224 Microsoft RFC Programming Guide arrays, conformant (cont d) memory allocation, 93-94 fixed, 34, 90 limiting transmission of, 34 max_is, 35 MIDI attributes of, 137-138 size_is, 35 specifying size of, 91-93 varying, 90-91 attribute configuration file (see ACF) attributes ACF (see ACF attributes) array, 137-138 binding methods, 48 data, 30-38, 139 dont_free, 98 header, 29, 137 interface definition, 28 MIDI (see MIDL attributes) pointer types, 138 procedure, 139-140 structure member, 139 union case, 139 authentication, 71 binding information, 47 managing, routines for, 43, 147 authorization information, 47 automatic binding, 49-50 finding server, 122 (see also binding methods) autojiandle attribute, 43, 48-49, 140 auxiliary files, MIDL compiler, 41 bind procedure, 68-69 binding handles, 20, 45-70 bind/unbind procedures, 68-69 client, 113 context handles, 133 customized, 66-70 designing, 67 defining, 53-54 endpoints in, 59 fully bound, 59 importing, 61-63 looking up, 64 managing, 46-55 by clients, 47 routines for, 43, 144 partially bound, 59 server initialization, 112 binding information, 45-48 client authentication, 47 client, in server code, 113 context handles, 48 creating for servers, 104-107 routines for, 43, 146 exporting, 20, 125-126 finding servers, 64-66 host network address, 104 in server entry, 123 interpreting, 60-6 1 inventory application, 158 NSI routines, 14 server endpoint map, 109 to name service, 108 with dynamic endpoints, 104-106, 123 binding methods, 13-17, 46-55 applying to interface(s), 47 attributes, 48 automatic, 46-50 finding server, 122 overriding, 50 choosing, 48-49 comparison of, 46 establishing, 48 explicit, 46, 52-55 implicit, 46-47, 50-52 and ACF, 51 overriding, 52 selecting with ACF, 43-44 BITFTP, xix buffers, allocating, 97-98 byte_count attribute, 98 case keyword, 37 Cell Directory Service (CDS), 14, 121 char data type, 34 CHECK_STATUS macro, 73-74, 104, 150, 155 client files, generating, 42 /client none, MIDL compiler, 42 client.c, 150 clients allocating buffers in, 97-98 authentication information, 47 authorization information, 47 binding handles, 113 binding information, 113 Index 225 clients (cont d) interpreting, 6l managing handles, 47 building, 149 compiling, 21-23, 74-77 context handles in, 131-133 copying text to server, 129 developing for automatic binding, 49-50 for explicit binding, 54 for implicit binding, 51-52 development, errors in, 72-74 example of, 9 exception handling, 72 finding from strings, 64-66 inventory application file, 172 linking, 21-23, 74-77 managing, routines for, 43, 145 of arithmetic application, 152 phonebook application, 205 producing, 74 protocol sequences for, 58 rfile applications, 195 server communication break, 134, 136 using discriminated unions, 38 using name service, 6l writing, 45-77 (see also servers) close_inventory procedure, 111 code attribute, 44, 140 communication breakdown, client/server, 134, 136 comm_status attribute, 44, 140 compiler, MIDL (see MIDL compiler) compiling clients, 21-23, 74-77 interfaces, 40-42 of interface definition, 7 servers, 21-23, 117-119 CompuServe, xvi conformant arrays, 34, 90 allocating memory, 93-94, 116-117 dynamic, 93 as procedure parameters, 93 managing size of, 91-94 MIDL attributes, 138 conformant strings, 34-35 conformant structure, 92, 94 const keyword, 33 constants, MIDL file, 33 context handles, 48, 129-136 active, 129, 134 allocating memory for, 135 establishing active, 132 freeing, 133, 135 in clients, 131-133 in interface definition, 130-131 in servers, 133-136 opaque structure, 131 with binding methods, 132 writing procedures with, 134-135 context rundown procedures, 130, 197 writing, 136 context storage, 98 context_handle attribute, 130, 139-140 contiguous server memory, 97 conventions for entry names, 23 crndwn.c, 192 customizing binding handles, 66-70 interface with ACF, 42-44 data describing with MIDL attributes, 28 limiting transmission, 34 marshalling, 32 privacy/integrity (see authentication) sharing between formats, 32 structures. 101-102 data attributes, MIDL, 30-38 user-defined, 33 datatypes, 139, 150 DCE Cell Directory Service (CDS), 107, 121 debugging remote procedures, 76 DECnet, protocols with, 56 DefaultEntry, 63 directory service, 14 discriminated unions, 36-38 application code example, 37 pointers as, 89 distributed applications, 149 do_import_binding, 54, 60, 62, 159, 178 do_interpret_binding, 60, 63, 159, 179 domain controllers, 126-127 dont_free attribute, 98 do_string_binding, 64, 68, 132, 196 dynamic endpoints, 59-60, 110 exporting, 108 226 Microsoft RFC Programming Guide dynamic endpoints (cont d) in binding information, 104-106, 123 endpoint attribute, 106, 137 endpoint map local, 15 system, 109-110, 112 endpoints dynamic, 104-106 exporting, 108 finding, 59-60 managing in server, 109-110, 112 routines for, 43, 144, 146 server process, 14 well-known, 106-107 with client call requests, 100 entry names, conventions, 23 enum keyword, 35 enumerated types, 35 errors, 155 ACF control, 44 handling, 72-74 reporting, routines for, 102 (see also exceptions) error_status_t, 73, 140 data type, 31, 44 exceptions ACF, 44 as parameters, 141 handling, 43, 72-74 routines for, 145, 147 listening for RPCs, 111-112 (see also errors) explicit binding, 52-55 inventory application, 180 MIDI file, 182 remote procedures, 184 (see also binding methods) explicitjiandle attribute, 43, 48, 53, 140 exporting binding information, 20, 125-126 endpoints, 108 servers to name service, 147 fault_status attribute, 44, 140 filehandle_rundown procedure, 136 finding servers, 13, 55-66 with name service, 61-64 nrst_is attribute, 90, 138 fixed arrays, 34, 90 floating-point numbers (see discriminated unions) free routine, 113 FTP (file transfer program), xviii FTPMAIL, xix full pointers, 33, 80, 86-90, 140 fully bound binding handle, 59 get_args, 131, 195 getargs.c, 192 getbind.c, 62 get_part_description, 115, 158 group entries, RFC, 107 handle attribute, 66, 139 handles binding (see binding handles) context (see context handles) interface (see interfaces, handles) handle_t data type, 31, 43, 51, 53 handling errors (exceptions), 72-74, 145 inventory application, 158 exceptions, 111-112 header attributes, 137 interface, 29-30 files, 101-102 generating a, 7 header files, 102 host network address, 104 /I option, MIDL compiler, 42 IDL (Interface Definition Language), 4 (see also MIDL) if spec, 103 ignore attribute, 139 implicit binding, 50-52 ACF file for, 176 (see also binding methods) implicitjiandle, 43-44, 48, 51, 140 in attribute, 38-40, 140 indirection, multiple levels of, 84 initializing arithmetic application, 153 context handles, 132 inventory application, 169 Index 227 initializing (cont d) servers, 15, 18-19, 99-112 advertising, 107-109 creating binding information, 104-107 header files, 101-102 listening for RPCs, 110-112 managing endpoints, 109-110, 112 registering interfaces, 102-104 input parameters, pointers as, 82-84 intbind.c, 61 interface definition, 4-7, 27-44 attributes, 28 binding methods, 48 compiling, 7 declaring varying array, 90 defining conformant arrays, 91 defining context handles, 130-131 definition of, 4 explicit binding and, 52 generating UUID in, 6 inventory application, 157 language (IDL), 4 specifying array size in, 91-93 structure of, 29 template for, 6 interfaces applying binding methods, 47 array attributes, 137 attributes of procedure parameters, 139 compiling, 40-42 customizing with ACF, 42-44 data type attributes, 139 data types, 150 defining binding handle, 54 defining strings in, 34 definition of, 2 developing for automatic binding, 49 for explicit binding, 53-54 for implicit binding, 51 handles, 19, 103 header attributes, 29-30, 137 identifying (naming), 30, 123 information management routines, 43, 145-146 inventory application, 16 1 pointer type attributes, 138 procedure attributes, 140 registering, 102-104 simple, 4 specification, client call, 100 structure member attributes, 139 union case attributes, 139 international character types, 31 Internet, protocols with, 56-57 inv.h, 101 inventory application, 30, 157-189 ACF file, 176 automatic binding, 172 do_import_binding, 178 do_interpret_binding, 179 explicit binding, 183 how to run, 158 inventory implementation, 166 Makefile, 160, 174 MIDL file of, 161 remote procedures, 163, 184 server, 169 invntry.c, 159 ISO_LATIN_1, 31 ISO_MULTI_LINGUAL, 31 ISOJJCS, 31 LAN for protocol sequences, 58 last_is attribute, 90, 138 length_is attribute, 90, 138 levels of indirection, 84 libraries for Microsoft RFC, 21 linked lists, 95 linking clients, 21-23, 74-77 servers, 21-23, 117-119 listening for RPCs, 110-112 local attribute, 137 local endpoint map, 15 local RFC (ncalrpc transport), 58 LOCAL symbol, 132 locating servers, 13, 55-66 with name service, 61-64 Locator (see Microsoft Locator) long integers (see discriminated unions) maintaining context, 129-136 in servers, 133-136 Makefile, 150, 159-160, 192 implicit client, 174 Windows phnbk application, 202 malloc, 113, 135 228 Microsoft RFC Programming Guide manager code (see remote procedures) manager, c, 150 max_is attribute, 35, 91-93, 138 memory management, 94-98 allocating buffers, 97-98 for conformant arrays, 93-94 conformant arrays, 116-117 context handle, 135 contiguous server, 97 in remote procedures, 112-115 inventory application, 158 node-by-node allocation, 96-97 persistent storage, 98 routines for, 145, 147 Microsoft Locator, 14, 52, 126-127 group operations, 108 (see also name service) Microsoft RFC, 46 libraries, 21 Microsoft Windows NT, 97 MIDL (Microsoft Interface Definition Lan guage) arithmetic application, 152 brackets in, 30 constants, 33 data types, 30-38 arrays, 34 denning new, 33 discriminated unions, 36-38 enumerated types, 35 international, 31 pointers, 33 strings, 34-35 structures, 35-36 void, 40 default names, 103 definition of, 4 file of, phonebook application, 204 generating template, 6 handle_t data type, 51 naming an interface, 30 /oldnames option, 103 parameter attributes, 38-40 pointers (see pointers) procedure declarations, 27, 38-40 rfile application, file of, 194 type definitions, 30-38 MIDL attributes, 28 array, 137-138 context_handle, 130, 139-140 data type, 139 endpoint, 106, 137 first_is, 90, 138 handle, 66, 139 in, 38-40, 140 interface header, 137 interface keyword, 30 last_is, 90, 138 length_is, 90, 138 local, 137 max_is, 35, 91-93, 138 out, 38-40, 140 pointer_default, 30, 137 pointer types, 138 procedure parameter, 139-140 ptr, 140 ref, 33, 138 size_is, 35, 91-93, 138 string, 34, 138, 140 structure member, 139 transmit_as, 139 union case, 139 unique, 33, 138, 140 uuid, 30, 137 version, 30, 103, 137 (see also ACF attributes) MIDL compiler, 7-21, 40-42, 141 auxiliary files, 41 client, 74 /client none option, 42 generating client/server files, 42 /I option, 42 inv.h, 101 /out option, 42 /server none option, 42 specifying ACF, 43 stub files, 41 midl_user_allocate, 19, 95, 113-115, 135 midl_user_free, 19, 95, 113-115 multi-threaded RFC, 48 multiple levels of indirection, 84 name service, 46, 50-54, 56, 121-127 advertising servers, 107 definition of, 14 entries, 122 finding servers, 61-64 Index 229 name service (cont d) importing from, 61-63 independent (NSI) routines, 14 managing, routines for, 144-147 names in, 122 selecting binding handles, 64 server entries, 123-126 named pipe (np transport), 58 nbase.h, 102 ncacn_dnet_nsp protocol, 56 ncacn_ip_tcp protocol, 56 ncacn_nb_nb protocol, 57 ncacn_nb_tcp protocol, 57 ncacn_np protocol, 57 ncacn_spx protocol, 57 ncadg_ip_udp protocol, 56 ncalrpc protocol, 57 ncalrpc transport (local RFC), 58 NetBEUI transport, 57 NetBEUI, NCA connection using, 56 NetBIOS, NCA connection using, 56 network address finding, 58-59 host, 104 RFC binding, 14 services protocol (nsp), 57 Network Computing Architecture (NCA), 56-57 Network Data Representation (NDR), 32 nocode attribute, 44, 140 node-by-node allocation, 96-97 np transport (named pipe), 58 NSI (name service independent) routines, 14 nsp (network services protocol), 57 null pointers, 80, 83 object types, managing, 43, 147 opaque structure, 100, 131 open_inventory procedure, 109 out attribute, 38-40, 140 /out option, MIDL compiler, 42 outdated endpoints (see endpoints, manag ing) output parameters, pointers as, 80-82 parameter attributes, 38-40 partially bound binding handle, 59 pass by reference, 7 value, 38 persistent memory storage, 98 phnbk.txt, 202 phonebook (phnbk) application, 201-221 ACF file, 205 client file, 205 header file, 213 input, 221 Makefile, 202 MIDL file of, 204 remote procedures, 214 resource file, 213 server, 217 window module definitions, 213 pipes, NCA connection using, 56 pointer attributes, 80, 86 pointer_default attribute, 30, 85, 137 pointers, 33 aliasing, 83, 87 as input parameters, 82-84 as output parameters, 80-82 as procedure return values, 86-87 default, 85, 89 definition of, 79 differentiating between, 87-90 full. 33. 80, 86-90. 140 interface handles, 103 managing, 87-90 in remote procedures, 113-115 MIDL attributes, 138 multiple, 89 multiple levels of indirection, 84 null, 80, 83 reference, 80, 114, 138 server context handles, 133 to other pointers, 84-86 to strings, 138 unique, 80, 83, 114-115, 138 privacy, data, 71 procedures conformant arrays as parameters, 93 context rundown (see context rundown procedures) declaration, 27, 38-40 contents of, 4 excluding unused, 44 parameter attributes, 139-140 230 Microsoft RFC Programming Guide procedures (cont d) remote (see remote procedures) returning pointers, 86-87 with context handles, 134-135 protocol sequences, 56 definition of, 14 finding, 56-58 inventory application, 158 LAN for, 58 RFC routines, 43, 145 selecting at server initialization, 104-107 timeouts for, 58 WAN for, 58 protocol, selecting a, 58 ptr attribute, 140 queue, client request, 100 ref attribute, 33, 138 reference pointers, 33, 80, 84, 87-90, 114 registering server interfaces, 102-104 remote file applications (see rfile applica tions) remote procedures calls, multi-threaded, 48 handling errors, 72-74 implementing, 11-12 inventory application, 163 managing memory in, 112-115 multiple implementations, 104 of arithmetic application, 153 phnbk application, 214 renaming in server code, 104 returning context handle, 130 rfile applications, 198 testing and debugging, 76 with binding handles, 133 with context handles, 133-135 writing, 112-117 remote_close RFC, 133 remote_open RFC, 132 remote_send RFC, 132 rfile applications, 129, 191-200 client, 195 context rundown procedures, 197 do_string_binding, 196 get_args, 195 how to run, 191 interface, 194 Makefile, 192 MIDL file, 194 remote procedures, 198 server, 199 RFC (remote procedure calls) client binding information in, 113 finding servers, 55 group entries, naming, 107 handling, 99-101 (see also servers, initializing) listening for, 110-112 multi-threaded, 48 runtime library context runtime procedures, 136 handling client request, 100-101 registering server interfaces, 102 role of, 17 runtime routines, 143-147 interpreting binding information, 60 name service database, 61-64 reporting errors, 102 RpcBindingFree, 63, 66, 68-69 RpcBindingFromStringBinding, 56, 59-60, 66 RpcBindinglnqAuthClient, 71 RpcBindinglnqAuthlnfo, 71 RpcBindingReset function, 51 RpcBindingSetAuthlnfo, 71 RpcBindingToStringBinding, 61 RpcMgmtlnqComTimeout, 58 RpcMgmtSetComTimeout, 58 RpcNetworklnqProtseqs, 56, 66 RpcNsBindinglmport, 56, 58 RpcNsBindinglmportBegin, 63 RpcNsBindinglmportDone, 63 RpcNsBindinglmportNext, 63 RpcNsBindingLookup, 56, 58, 64 RpcNsBindingSelect, 64 RpcProtseqVectorFree, 66 RpcStringBindingCompose, 56, 59, 66 RpcStringBindingParse, 61 RpcStringFree, 6l, 66 vector data structure, 102 security, 71 (see also authentication) selecting array portion, 90-91 service, server s, 106 rpc_binding_handle_t, 31, 53 RpcBindingVectorFree, 19, 110, 112 Index 231 rpc_binding_vector_t, 102 RPC_C_NS_SYNTAX_DEFAULT, 63, 108 RPC_C_PROTSEQ_MAX_CALLS_DEFAULT, 105 rpcdce.h, 102 RpcEndExcept macro, 111-112 RpcEpRegister, 19, 106, 110 RpcEpUnregister, 110, 112 RpcExcept macro, 111-112 rpc.h, 102 RpcMgmtStopServerListening, 110 RpcNsBindingExport, 19, 108, 125 RpcNsBindingUnexport, 126 rpc_protseq_vector_t, 102 RpcServerAllProtseqlf, 110 RpcServerlnqBindings, 105, 112 RpcServerListen, 19, 110-113 RpcServerRegisterlf, 19, 104 RpcServerUseAllProtseqs, 105 RpcServerUseAUProtseqsIf, 106 RpcServerUseProtseq, 105 RpcServerUseProtseqEp, 106, 110 RpcServerUseProtseqlf, 106, 110 RpcTry Except macro , 111-112 running applications (see applications) search_spec_bind, 69 search_spec_unbind, 69 security, 71 selecting binding method (see binding meth ods) sequences, protocol (see protocol sequences) server entries, 123-126 creating, 125-126 naming, 124 server files, generatiJng, 42 /server none, MIDL compiler, 42 server.c, 150, 159 servers advertising, 107-109 binding information automatic, 50 client, 113 creating, 104-107 explicit, 55 implicit, 52 interpreting, 61 with binding handles, 133 building, 149 client communication break, 134, 136 compiling and linking, 21-23, 117-119 context handles, 133-135 contiguous memory, 97 copying text from clients, 129 data structures, 102 developing, 11 errors in, 72-74 endpoint map, 106 finding/locating, 13 from strings, 64-66 host, 58-59 particular, 45-66 with name service, 61-64 handling client request, 100-101 exceptions, 72 header files, 102 initializing, 15, 18-19, 99-112, 153 data structures, 101-102 header files, 101-102 inventory application, 169 managing endpoints, 109-110, 112 rfile applications, 199 selecting protocol sequences, 104-107 listening for RPCs, 110-112 managing context in, 133-136 routines for, 147 naming conventions, 107 naming multiple, 122 phnbk application, 217 producing, 117 registering interfaces, 102-104 remote procedure implementations, 99 stub auxiliary file, 117 using discriminated unions, 38 writing, 99-119 (see also clients) size_is attribute, 35, 91-93, 138 spx transport, 58 SPX, NCA connection using, 57 status.h, 74, 150 strbind.c, 192 string attribute, 34, 138, 140 strings, 34-35 pointers to, 138 struct keyword, 35 232 Microsoft RFC Programming Guide structure members attributes, 139 pointers as, 89 structures, 35-36 stubs code for memory management, 96 data transmission, 32 definition of, 1 generating, 7 with MIDL compiler, 41 support routines, 113 sum_arrays, 11, 149 switch keyword, 37 tcp, 57 TCP/IP, protocols with, 56 testing remote procedures, 76 text variables (see strings) threads for processing client requests, 100 for RPCs, 112-113 timeouts, protocol sequences, 58 transmission control protocol, 57 transmit_as attribute, 139 transport protocol, 57 in RPC binding, 14 type definitions, MIDL, 30-38 typedef keyword, 33 unbind procedure, 68-69 union case attributes, 139 unique attribute. 33, 138, 140 unique pointers, 33, 80, 84, 87-90 allocating memory, 114-115 unsigned32 variable reporting errors, RPC, 102 UUID (universal unique identifier) definition of, 5 management routines for, 43, 144, 146 uuid attribute, 30, 137 uuidgen, use of, 5 varying arrays, 34, 90-91 declaring, 90 MIDL attributes, 138 selection portion of, 90-91 vectors, 102 version attribute, 30, 103, 137 version number, interface, 30 void data type, 40 WAN, protocol sequences, 58 wchar_t data type, 31 well-known endpoints, 59-60, 110, 137 creating binding information with, 106 exporting, 108 in binding information, 123 server binding information with, 106-107 whatare_subparts, 11 6, 158 Windows NT, Microsoft, 97 security, 71 Windows phonebook application, 201-221 wphnbk.def, 202 wphnbk.h, 202 wphnbk.rc, 202 writing clients, 45-77 procedures, 134-135 remote, 112-117 servers, 99-119 About the Author John Shirley is a consultant in the development of software and documentation, particularly in the field of distributed computing. He earned a B.A. from Alfred University with a dual major in mathematics and geology, an M.S. in geology from Miami University with a specialty in structural geology, and an M.S. in computer science from Pace University. John lives in Newtown, Connecticut. Prior to consulting, John s career included six years in the oil industry as a geophys- icist and international explorationist. His work included the analysis of seismic data from New Zealand, Australia, Turkey, Norway, the Dominican Republic, Jamaica, and the United States. He also worked as a software engineer developing programs for scientific instrument manufacturers. Ward Rosenberry is a technical writing consultant and author concentrating on distributed computing and computer networking technologies. Ward has distin guished himself writing about the Open Software Foundation s Distributed Computing Environment since 1989, when he helped write Digital Equipment Corporation s original DCE design documents. He has since co-authored two other O Reilly books about distributed computing: Understanding DCE and Distributing Applications Across DCE and Windows NT. He continues his close DCE involvement designing and developing DCE information both at Digital and at OSF and now operates a consulting firm, Rosenberry Associates, in Chelmsford, Massachusetts. Ward graduated from the University of Lowell in 1979 with a B.A. in English. Ward, his wife Patricia Pestana, and their two children, William and John, live in North Chelmsford, Massachusetts. Colophon The animal on the cover of Microsoft RFC Programming Guide is a starfish, a marine invertebrate animal of the phylum Echinodermata, class Asteroidea. The approxi mately 1500 known living species of starfish are found throughout the world, at all ocean depths, and range in size from 1 cm to 68 cm wide. Most starfish have five arms, but can have as few as four or as many as 50. Starfish are equipped with five double rows of outgrowths called tube feet. These tube feet, which are usually tipped with "suction cups," function in the respiratory process, enable the starfish to move, and are used to catch prey. The tube feet are connected via a water- vascular system unique to echinoderms. A ring canal in the disc-shaped body trunk connects to a radial canal in each arm, through which gaseous exchange takes place. When a starfish needs to move, pressure in the water-vascular system causes the tube feet to become erect, lifting up the body. The tube feet then take small steps, moving the starfish slowly forward. One arm takes the lead in movement; when the direction changes, the lead shifts to another arm. Most of the time, however, starfish are sedentary creatures who prefer to stay anchored in one place. They will move to search for food, or if there is a change in external conditions. The majority of starfish are predators, feeding on bivalves, crustaceans, and other echinoderms. By anchoring its arms on the sea floor, the starfish is able to use the suction pull of the tube feet to pry open the shells of bivalves. The starfish can then extrude its stomach through its mouth and into the tiny crevice of the bivalve shell, and begin the digestive process outside of its body. Many species of starfish can reject an arm if it is injured in an attack. The body will generate a new arm, but this is a slow process that can take more than a year to complete. In a few speciess, the arm that has broken off will generate a body trunk and four new arms. At least one species of starfish eschews sexual reproduction in favor of this asexual mode, and has developed the ability to break off an arm at will. Starfish usually reproduce by releasing eggs and sperm into the waves. The fertilized eggs form free-swimming larvae, although the female adult will provide some form of brood care in colder regions. Edie Freedman designed the cover of this book, using a 19th-century engraving from the Dover Pictorial Archive. The cover layout was produced with Quark XPress 3.3 using the ITC Garamond font. The inside layout was designed by Edie Freedman and Jennifer Niederst and imple mented in gtroff by Lenny Muellner. The text and heading fonts are ITC Garamond Light and Garamond Book. The illustrations that appear in the book were created in Aldus Freehand 4.0 by Chris Reilley. This colophon was written by Clairemarie Fisher O Leary, with assistance from Kiersten Nauman. FORM Books from O Reilly & Associates, Inc. Fortran/Scientific Computing Fall/Winter 1994-95 Migrating to Fortran 90 By James F. Kerrigan 1st Edition November 1993 389 pages, ISBN 1-56592-049-X Many Fortran programmers do not know where to start with Fortran 90. What is new about the language? How can it help them? How does a programmer with old habits learn new strategies? This book is a practical guide to Fortran 90 for the current Fortran programmer. It provides a complete overview of the new features that Fortran 90 has brought to the Fortran standard, with examples and suggestions for use. The book discusses older ways of solving problems both in FORTRAN 77 and in common tricks or extensions and contrasts them with the new ways provided by Fortran 90. The book has a practical focus, with the goal of getting the current Fortran programmer up to speed quickly. Two dozen examples of full programs are interspersed within the text, which includes over 4,000 lines of working code. Topics include array sections, modules, file handling, allocatable arrays and pointers, and numeric precision. Two dozen examples of full programs are interspersed within the text, which includes over 4,000 lines of working code. "This is a book that all Fortran programmers eager to take advantage of the excellent feature of Fortran 90 will want to have on their desk." FORTRAN Journal High Performance Computing By Ketin Dou d 1st Edition June 1993 398 pages, ISBN 1-56592-032-5 HigT Performance Computing High Performance Computing makes sense of the newest generation of work stations for application programmers and purchasing managers. It covers everything, from the basics of modern workstation architecture, to structuring benchmarks, to squeezing more perfor mance out of critical applications. It also explains what a good compiler can do and what you have to do yourself. The book closes with a look at the high-performance future: parallel computers and the more "garden variety" shared memory processors that are appearing on people s desktops. UNIX for FORTRAN Programmers By Mike Loukides 1st Edition August 1990 264 pages, ISBN 0-937 175-5 1-X This handbook lowers the UNIX entry barrier by providing the serious scientific programmer with an introduction to the UNIX operating system and its tools. It familiarizes readers with the most important tools so they can be productive as quickly as possible. Assumes some knowledge of FORTRAN, none of UNIX or C. FOR INFORMATION: 800-998-9938 707-829-0515; NUTS@ORA.COM C Programming Libraries POSIX.4 By Bill Gallmeister 1st Edition Winter 1994-95 (est.) 400 pages (est.), ISBN 1-56592-074-0 POSIX.4 A general introduction to real-time programming and real-time issues, this book covers the POSIX.4 standard and how to use it to solve "real-world" problems. If you re at all interested in real-time applications which include just about everything from telemetry to transation processing this book is for you. An essential reference. POSIX Programmer s Guide By Donald Lewine 1st Edition April 1991 640 pages, ISBN 0-937175-73-0 POSIX PROGRAMMER S GUIDE Most UNIX systems today are POSIX compliant because the Federal govern ment requires it for its purchases. Given the manufacturer s documenta tion, however, it can be difficult to distinguish system-specific features from those features defined by POSIX. The POSIX Programmer s Guide, intended as an explanation of the POSIX standard and as a reference for the POSIX. 1 programming library, helps you write more portable programs. "If you are an intermediate to advanced C programmer and are interested in having your programs compile first time on anything from a Sun to a VMS system to an MSDOS system, then this book must be thoroughly recommended." Sun UK User Understanding and Using COFF By Gintaros R. Gircys 1st Edition November 1988 196 pages, ISBN 0-9371 75-31-5 COFF Common Object File Format is the formal definition for the structure of machine code files in the UNIX System V environment. All machine code files are COFF files. This handbook explains COFF data structure and its manipulation. COFF Using C on the UNIX System By Dave Curry 1st Edition January 1989 250 pages, ISBN 0-937 175-23-4 This is the book for intermediate to experienced C programmers who want to become UNIX system programmers. It explains system calls and special library routines available on the UNIX system. It is impossible to write UNIX utilities of any sophistication without understanding the material in this book. "A gem of a book.... The author s aim is to provide a guide to system programming, and he succeeds admirably. His balance is steady between System V and BSD-based systems, so readers come away knowing both." SUN Expert Practical C Programming By Steve Oualline 2nd Edition January 1993 396 pages, ISBN 1-56592-035-X C programming is more than just getting the syntax right. Style and debugging also play a tremendous part in creating programs that run well. Practical C Programming teaches you not only the mechanics of programming, but also how to create programs that are easy to read, maintain, and debug. There are lots of introductory C books, but this is the Nutshell Handbook! In this edition, programs conform to ANSI C. "This book is exactly what it states a practical book in C programming. It is also an excellent addition to any C programmer s library." Betty Zinkarun, Books & Bytes Programming with curses By John Strung 1st Edition 1986 76 pages, ISBN 0-937175-02-1 Curses is a UNIX library of functions for controlling a terminal s display screen from a C program. This handbook helps you make use of the curses library. Describes the original Berkeley version of curses. TO ORDER: 800-889-8969 (CREDIT CARD ORDERS ONLY); ORDER@ORA.COM C Programming Tools Software Portability with imake By Paul DuBois 1st Edition July 1993 390 pages, ISBN 1-56592-055-4 imake is a utility that works with make to enable code to be compiled and installed on different UNIX machines. imake makes possible the wide portability of the X Window System code and is widely considered an X tool, but it s also useful for any software project that needs to be ported to many UNIX systems. This Nutshell Handbook the only book available on imake is ideal for X and UNIX programmers who want their software to be portable. The book is divided into two sections. The first section is a general explanation of imake, X configuration files, and how to write and debug an Imakefile. The second section describes how to write configuration files and presents a configuration file architecture that allows development of coexisting sets of configuration files. Several sample sets of configuration files are described and are available free over the Net. Managing Projects with make By Andrew Oram & Steve Talbott 2nd Edition October 1991 152 pages, ISBN 0-937175-90-0 make is one of UNIX s greatest contribu tions to software development, and this book is the clearest description of make ever written. It describes all the basic features of make and provides guidelines on meeting the needs of large, modern projects. Also contains a description of free products, that contain major enhancements to make, "I use make very frequently in my day to day work and thought I knew everything that I needed to know about it. After reading this book I realized that I was wrong! Rob Henley, Siemens-Nixdorf "If you can t pick up your system s yp Makefile, read every line, and make sense of it, you need this book." Rootjournal Checking C Programs with lint By Ian F. Darwin 1st Edition October 1988 84 pages. ISBN 0-937175-30-7 The lint program checker has proven time and again to be one of the best tools for finding portability problems and certain types of coding errors in C programs, lint verifies a program or program segments against standard libraries, checks the code for common portability errors, and tests the programming against some tried and true guidelines. Linting your code is a necessary (though not sufficient) step in writing clean, portable, effective programs. This book introduces you to lint, guides you through running it on your programs, and helps you interpret lint s output. "I can say without reservation that this book is a must for the system programmer or anyone else programming in C." Rootjournal lex & yacc By John Letine, Tony Mason & Doug Brown 2nd Edition October 1992 366 pages, ISBN 1-56592-000-7 Shows programmers how to use two UNIX utilities, lex and yacc, in program development. The second edition contains completely revised tutorial sections for novice users and reference sections for advanced users. This edition is twice the size of the first, has an expanded index, and now covers Bison and Flex. Power Programming with RPC By John Bloomer 1st Edition February 1992 522 pages, ISBN 0-937175-77-3 RPC, or remote procedure calling, is the ability to distribute the execution of func tions on remote computers. Written from a programmer s perspective, this book shows what you can do with RPCs, like Sun RPC, the de facto standard on UNIX systems. It covers related programming topics for Sun and other UNIX systems and teaches through examples. FOR INFORMATION: 800-998-9938, 70 7-829-05 15; HUTS@ORA.COM Multi-Platform Programming Guide to Writing DCE Applications By John Shirley, WeiHu & David Magid 2nd Edition May 1994 462 pages, ISBN 1-56592-045-7 A hands-on programming guide to OSF s Distributed Computing Environment (DCE) for first-time DCE application programmers. This book is designed to help new DCE users make the transition from conventional, nondistributed applications programming to distributed DCE programming. In addi tion to basic RFC (remote procedure calls), this edition covers object UUIDs and basic security (authentication and authorization). Also includes practical programming examples. "This book will be useful as a ready reference by the side of the novice DCE programmer." ; login Distributing Applications Across DCE and Windows NT By Ward Rosenbeny &Jim league 1st Edition November 1993 302 pages. ISBN 1-56592-047-3 This book links together two exciting technologies in distributed computing by showing how to develop an application that simultaneously runs on DCE and Microsoft systems through remote proce dure calls (RFC). Covers the writing of portable applications and the complete differences between RFC support in the two environments. Understanding DCE By Ward Rosenbeny, David Kmney & Gerry Fisher 1st Edition October 1992 266 pages, ISBN 1-56592-005-8 A technical and conceptual overview of OSF s Distributed Computing Environment (DCE) for programmers, technical managers, and marketing and sales people. Unlike many O Reilly & Associates books, Understanding DCE has no hands- on programming elements. Instead, the book focuses on how DCE can be used to accomplish typical programming tasks and provides explanations to help the reader understand all the parts of DCE. Encyclopedia of Graphics File Formats By James D. Murray & William vanRyper 1st Edition July 1994 928 pages (CD-ROM included), ISBN 1-56592-058-9 The computer graphics world is a veri table alphabet soup of acronyms; BMP DXF, EPS, GIF, MPEG, PCX, PIC, RTF, TGA, RIFF, and TIFF are only a few of the many different formats in which graphics images can be stored. The Encyclopedia of Graphics File Formats is the definitive work on file formats the book that will become a classic for graphics programmers and everyone else who deals with the low-level technical details of graphics files. It includes technical information on nearly 100 file formats, as well as chapters on graphics and file format basics, bitmap and vector files, metafiles, scene description, animation and multimedia formats, and file compression methods. Best of all, this book comes with a CD-ROM that collects many hard-to-find resources. We ve assembled original vendor file format specification documents, along with test images and code examples, and a variety of software packages for MS- DOS, Windows, OS/2, UND(, and the Macintosh that will let you convert, view, and manipulate graphics files and images. Multi-Platform Code Management By Kevin Jameson 1st Edition August 1994 354 pages (two diskettes included), ISBN 1-56592-059-7 For any programmer or team struggling with builds and maintenance, this book and its accompanying software (available for fifteen platforms, including MS-DOS and various UNIX systems) can save dozens of errors and hours of effort. A "one-stop-shop ping" solution for code management problems, it shows you how to structure a large project and keep your files and builds under control over many releases and platforms. The building blocks are simple: common-sense strategies, public-domain tools that you can obtain on a variety of systems, and special utilities developed by the author. The book also includes two diskettes that provide a complete system for managing source files and builds. TO ORDER: 800-889-8969 (CREDIT CARD ORDERS ONLY); ORDER@ORA.COM Database Understanding Japanese Information Processing By Ken Lutuie 1st Edition September 1993 470 pages. ISBN 1-56592-043-0 Understanding Japanese Information Processing provides detailed information on all aspects of handling Japanese text on computer systems. It brings all of the relevant information together in a single book and covers everything from the origins of modern-day Japanese to the latest information on specific emerging computer encoding standards. Appendices provide additional reference material, such as a code conver sion table, character set tables, mapping tables, an extensive list of software sources, a glossary, and more. "A programmer interested in writing a computer program which will handle the Japanese language will find the book indispensable." Multilingual Computing "Ken Lunde s book is an essential reference for everyone developing or adapting software for handling Japanese text. It is a goldmine of useful and relevant information on fonts, encoding systems and standards." Professor Jim Breen, Monash University, Australia Business Building a Successful Software Business By Dare Rodin 1st Edition April 1994 394 pages, ISBN 1-56592-064-3 This handbook is for the new software entrepreneur and the old hand alike. If you re thinking of starting a company around a program you ve written and there s no better time than the present this book will guide you toward success. If you re an old hand in the software industry, it will help you sharpen your skills or will provide a refresher course. It covers the basics of product planning, marketing, customer support, finance, and operations. "A marvelous guide through the complexities of marketing high-tech products. Its range of topics, and Radin s insights, make the book valuable to the novice marketeer as well as the seasoned veteran. It is the Swiss Army Knife of high-tech marketing." Jerry Keane, Universal Analytics Inc. ORACLE Performance Tuning By Peter Corrigan & Mark Gurry 1st Edition September 1993 642 pages, ISBN 1-56592-048-1 The ORACLE relational database management system is the most popular database system in use today. Organizations, ranging from government agencies to small businesses, from large financial institutions to universities, use ORACLE on computers as diverse as mainframes, minicomputers, work stations, PCs, and Macintoshes. ORACLE offers tremendous power and flexibility, but at some cost. Demands for fast response, particularly in online transaction processing systems, make performance a major issue. With more organizations downsizing and adopting client-server and distributed database approaches, perfor mance tuning has become all the more vital. Whether you re a manager, a designer, a programmer, or an administrator, there s a lot you can do on your own to dramatically increase the performance of your existing ORACLE system. Whether you are running RDBMS Version 6 or Version 7, you may find that this book can save you the cost of a new machine; at the very least, it will save you a lot of headaches. "This book is one of the best books on ORACLE that I have ever read.... [It] discloses many Oracle Tips that DBA s and Developers have locked in their brains and in their planners.... I recommend this book for any person who works with ORACLE, from managers to developers. In fact, I have to keep [it] under lock and key, because of the popularity of it." Mike Gangler FOR INFORMATION: 800-998-9938 707-829-0515; NUTS@ORA.COM O Reilly & Associates- GLOBAL NETWORK NAVIGATOR The Global Network Navigator (GNN) is a unique kind of information service that makes the Internet easy and enjoyable to use. We organize access to the vast information resources of the Internet so that you can find what you want. We also help you understand the Internet and the many ways you can explore it. Global Network Navigator Charting the Interj%M. In GNN you ll find: Navigating the Net with GNN The Whole Internet Catalog contains a descriptive listing of the most useful Net resources and services with live links to those resources. The GNN Business Pages are where you ll learn about companies who have established a presence on the Internet and use its worldwide reach to help educate consumers. The Internet Help Desk helps folks who are new to the Net orient themselves and gets them started on the road to Internet exploration. News NetNews is a weekly publication that reports on the news of the Internet, with weekly feature articles that focus on Internet trends and special events. The Sports, Weather, and Comix Pages round out the news. Special Interest Publications Whether you re planning a trip or are just interested in reading about the journeys of others, you ll find that the Travelers Center contains a rich collection of feature articles and ongoing columns about travel. In the Travelers Center, you can link to many helpful and informative travel-related Internet resources. The Personal Finance Center is the place to go for information about money manage ment and investment on the Internet. Whether you re an old pro at playing the market or are thinking about investing for the first time, you ll read articles and discover Internet resources that will help you to think of the Internet as a personal finance information tool. All in all, GNN helps you get more value for the time you spend on the Internet. S The Best of the Web GNN received "Honorable Mention" for Best Overall Site," "Best Entertainment Service," and "Most Important Service Concept." The GNN NetNews received "Honorable Mention" for "Best Document Design." Subscribe Today GNN is available over the Internet as a subscription service. To get complete information about subscribing to GNN, send email to info@gnn.com. If you have access to a World Wide Web browser such as Mosaic or Lynx, you can use the following URL to register online: http : / /gun . com/ If you use a browser that does not support online forms, you can retrieve an email version of the registration form automatically by sending email to form@gnn.com. Fill this form out and send it back to us by email, and we will confirm your registration. TO ORDER: 800-889-8969 (CREDIT CARD ORDERS ONLY); ORDER@ORA.COM O Reilly on the Net- ONLINE PROGRAM GUIDE O Reilly & Associates offers extensive information through our online resources. If you ve got Internet access, we invite you to come and explore our little neck-of-the-woods. Online Resource Center Most comprehensive among our online offerings is the O Reilly Resource Center. Here, you ll find detailed information and descriptions on all O Reilly products: titles, prices, tables of contents, indexes, author bios, software contents, reviews... you can even view images of the products themselves. We also supply helpful ordering information: how to contact us, how to order online, distributors and bookstores world wide, discounts, upgrades, etc. In addition, we provide informative literature in the field: articles, interviews, and bibliographies that help you stay informed and abreast. ^-M The Best of the Web The O Reilly Resource Center was voted "Best Commercial Site" by users participating in "Best of the Web 94." Ora-news An easy way to stay informed of the latest projects and products from O Reilly & Associates is to subscribe to "ora-news," our electronic news service. Subscribers receive email as soon as the information breaks. To subscribe to "ora-news": Send email to: listproc@online.ora.com and put the following information on the first line of your message (not in "Subject"): subscribe ora-news "your name" of "your company" For example: subscribe ora-news Jim Dandy of Mighty Fine Enterprises To access ORA s Online Resource Center: Point your Web browser (e.g., mosaic or lynx) to: http : / /gnn . com/ or a/ Email Many customer services are provided via email. Here s a few of the most popular and useful. For the plaintext version, telnet or gopher to: gopher . ora . com (telnet login: gopher) FTP The example files and programs in many of our books are available electronically via FTP. To obtain example flies and programs from O Reilly texts: ftp to: ftp.ora.com or ftp.uu.net cd published/oreilly nuts@ora.com For general questions and information. bookquestions@ora.com For technical questions, or corrections, concerning book contents. order@ora.com To order books online and for ordering questions. catalog@ora.com To receive a free copy of our magazine/catalog, "ora.com (please include a postal address). Snail mail and phones O Reilly & Associates, Inc. 103A Morris Street, Sebastopol, CA 95472 Inquiries: 707-829-0515, 800-998-9938 Credit card orders: 800-889-8969 (Weekdays 6a.m.- 6p.m. PST) FAX: 707-829-0104 FOR INFORMATION: 800-998-9938 707-829-0515: NUTS@ORA.COM O Reilly & Associates- LISTING OF TITLES INTERNET !%@:: A Directory of Electronic Mail Addressing & Networks Connecting to the Internet: An O Reilly Buyer s Guide Internet In A Box The Mosaic Handbook for Microsoft Windows The Mosaic Handbook for the Macintosh The Mosaic Handbook for the X Window System Smileys The Whole Internet User s Guide & Catalog SYSTEM ADMINISTRATION Computer Security Basics DNS and BIND Essential System Administration Linux Network Administrator s Guide (Winter 94/95 est) Managing Internet Information Services Managing NFS and NIS Managing UUCP and Usenet sendmail Practical UNIX Security PGP: Pretty Good Privacy (Winter 94/95 est.) System Performance Tuning TCP/IP Network Administration termcap & terminfo X Window System Administrator s Guide: Volume 8 The X Companion CD for R6 (Winter 94/95 est.) USING UNIX AND X BASICS Learning GNU Emacs Learning the Korn Shell Learning the UNIX Operating System Learning the vi Editor MH & xmh: Email for Users & Programmers SCO UNIX in a Nutshell The USENET Handbook (Winter 94/95 est.) Using UUCP and Usenet UNIX in a Nutshell: System V Edition The X Window System in a Nutshell X Window System User s Guide: Volume 3 X Window System User s Guide, Motif Ed.: Vol. 3M X User Tools (with CD-ROM) ADVANCED Exploring Expect (Winter 94/95 est.) The Frame Handbook Learning Perl Making TeX Work Programming perl sed&awk UNIX Power Tools (with CD-ROM) PROGRAMMING UNIX, C, AND MULTI-PLATFORM FORTRAN/SCIENTIFIC COMPUTING High Performance Computing Migrating to Fortran 90 UNIX for FORTRAN Programmers C PROGRAMMING LIBRARIES Practical C Programming POSIX Programmer s Guide POSIX.4: Programming for the Real World (Winter 94/95 est) Programming with curses Understanding and Using COFF Using C on the UNIX System C PROGRAMMING TOOLS Checking C Programs with lint lex & yacc Managing Projects with make Power Programming with RPC Software Portability with imake MULTI-PLATFORM PROGRAMMING Encyclopedia of Graphics File Formats Distributing Applications Across DCE and Windows NT Guide to Writing DCE Applications Multi-Platform Code Management ORACLE Performance Tuning Understanding DCE Understandingjapanese Information Processing BERKELEY 4.4 SOFTWARE DISTRIBUTION 4.4BSD System Manager s Manual 4.4BSD User s Reference Manual 4.4BSD User s Supplementary Documents 4,-iBSD Programmer s Reference Manual 4.4BSD Programmer s Supplementary Documents 4.4BSD-Lite CD Companion 4.4BSD-Lite CD Companion: International Version X PROGRAMMING Motif Programming Manual: Volume 6A Motif Reference Manual: Volume 6B Motif Tools PEXlib Programming Manual PEXlib Reference Manual PHIGS Programming Manual (soft or hard cover) PHIGS Reference Manual Programmer s Supplement for Release 6 (Winter 94/95 est) Xlib Programming Manual: Volume 1 Xlib Reference Manual: Volume 2 X Protocol Reference Manual, R5: Volume X Protocol Reference Manual, R6: Volume (Winter 94/95 est.) X Toolkit Intrinsics Programming Manual: Vol. 4 X Toolkit Intrinsics Programming Manual, Motif Edition: Volume 4M X Toolkit Intrinsics Reference Manual: Volume 5 XView Programming Manual: Volume 7A XView Reference Manual: Volume 7B THE X RESOURCE A QUARTERLY WORKING JOURNAL FOR X PROGRAMMERS The X Resource: Issues through 13 (Issue 13 available 1/95) BUSINESS/CAREER Building a Successful Software Business Love Your Job! TRAVEL Travelers Tales Thailand Travelers Tales Mexico Travelers Tales India (Winter 94/95 est) AlTDIOTAPES INTERNET TALK RADIO S "GEEK OF THE WEEK" INTERVIEWS The Future of the Internet Protocol, 4 hours Global Network Operations, 2 hours Mobile IP Networking, 1 hour Networked Information and Online Libraries, 1 hour Security and Networks, 1 hour European Networking, 1 hour NOTABLE SPEECHES OF THE INFORMATION AGE John Perry Barlow, 1.5 hours TO ORDER: 800-889-8969 (CREDIT CARD ORDERS ONLY): ORDER@ORA.COM O Reilly &Associates- INTERNATIONAL DISTRIBUTORS Customers outside North America can now order O Reilly & Associates books through the following distributors. They offer our international customers faster order processing, more bookstores, increased representation at tradeshows worldwide, and the high- quality, responsive service our customers have come to expect. EUROPE, MIDDLE EAST, AND AFRICA (except Germany, Switzerland, and Austria) INQUIRIES International Thomson Publishing Europe Berkshire House 168-173 High Holborn London WC1V7M United Kingdom Telephone: 44-71-497-1422 Fax: 44-71-497-1426 Email: ora.orders@itpuk.co.uk ORDERS International Thomson Publishing Services, Ltd. Cheriton House, North Way Andover, Hampshire SP10 5BE United Kingdom Telephone: 44-264-342-832 (UK orders) Telephone: 44-264-342-806 (outside UK) Fax: 44-264-364418 (UK orders) Fax: 44-264-342761 (outside UK) GERMANY, SWITZERLAND, AND AUSTRIA International Thomson Publishing GmbH O Reilly-International Thomson Verlag Attn: Mr. G. Miske Konigswinterer Strasse 418 53227 Bonn Germany Telephone: 49-228-970240 Fax: 49-228-441342 Email: anfragen@orade.ora.com ASIA (except Japan) INQUIRIES International Thomson Publishing Asia 221 Henderson Road #05 10 Henderson Building Singapore 03 15 Telephone: 65-272-64% Fax: 65-272-6498 ORDERS Telephone: 65-268-7867 Fax: 65-268-6727 AUSTRALIA WoodsLane Pty. Ltd. Unit 8, 101 Darley Street (P.O. Box 935) MonaValeNSW2103 Australia Telephone: 61-2-979-5944 Fax: 61-2-997-3348 Email: woods@tmx.mhs.oz.au NEW ZEALAND WoodsLane New Zealand Ltd. 21 Cooks Street (P.O. Box 575) Wanganui, New Zealand Telephone: 64-6-347-6543 Fax: 64-6-345-4840 Email: woods@tmx.mhs.oz.au THE AMERICAS, JAPAN, AND OCEANIA O Reilly & Associates, Inc. 103A Morris Street Sebastopol, CA 95472 U.S.A. Telephone: 707-829-0515 Telephone: 800-998-9938 (U.S. & Canada) Fax: 707-829-0104 Email: order@ora.com FOR INFORMATION: 800-998-9938, 707-829-0515; NUTS@ORA.COM Here s a page we encourage readers to tear out... Please send me the following: Q ora.com O Reilly s magazine/catalog, containing behind-the-scenes articles and interviews on the technology we write about, and a complete listing of O Reilly books and products. G Global Network Navigator Information and subscription. Which book did this card come from? Please print legibly Where did you buy this book? Q Bookstore Q Direct from O Reilly Q Bundled with hardware/software LI Class/seminar Your job description: G SysAdmin G Programmer Q Other What computer system do you use? Q UNIX QMAC QDOS(PC) LI Other Name Company/Organization Name Address Citv State Zip/Postal Code Country Telephone Internet or other email address (specify 7 network) vineteenth century wood engraving >f the horned owl from the O Reilly i Associates Nutshell Handbook .earning the i.MX Operating System NO POSTAGE NECESSARY IF MAILED IN THE UNITED STATES BUSINESS REPLY MAIL FIRST CLASS MAIL PERMIT NO. 80 SEBASTOPOL, CA Postage will be paid by addressee O Reilly & Associates, Inc. 103A Morris Street Sebastopol, CA 95472-9902 O Reilly & Associates, Inc. Microsoft RFC Programming Guide Remote Procedure Call (RFC) is the glue that holds together MS-DOS, Windows 3.x, and Windows NT. It is a client-server technology a way of making programs on two different systems work together like one. The advantage of RFC over other distributing programming techniques is that you can link two systems together using simple C calls, as in a single-system program. The most common use for client-server technology is to combine the graphical display capabilities of a desktop PC with the database and number-crunching power of a large central system. But peer-to-peer programs can run equally well. Like many aspects of Microsoft programming, RFC forms a small world of its own, with conventions and terms that can be confusing. But once you understand the purpose behind each feature, programming with RFC is not difficult. This book lays out the concepts and the programming tasks so that you can use this powerful API. Microsoft RFC is a new technology based on the RFC used in the Distributed Computing Environment (DCE). This book builds on O Reilly s successful DCE series. It provides a solid foundation for programmers learning to use Microsoft RFC, including: Controlling communications through the Microsoft Interface Definition Language (MIDL) and the Attribute Configuration File (ACF) How the server advertises itself How a client chooses a server (binding) Types of pointers and arrays Memory management Administration tasks for an RFC server Maintaining state through context handles This edition covers version 2.0 of Microsoft RFC. Four complete examples are included. ISBN 1-56592-070-8 RepKover, 90000 Printed on Recycled Paper 9 781565 920705 - - - 1 ISBN 1-56592-070-8