MAINSAIL STREAMS User's Guide, Chapter 5

previous   next   top   contents   index   framed top   this page unframed


5. Remote MODULEs and Remote PROCEDURE Calls (RPC)

The remote MODULE binding mechanism is a high-level interface that simplifies the construction of algorithms that consist of multiple cooperative processes. The remote MODULE facility handles the required communication automatically through what look like ordinary PROCEDURE calls, allowing the programmer to concentrate on solving the application problem.

Remote MODULEs are a high-level concept built using the interprocess communication methods described in Chapter 7. The idea is to make an interaction with another process look like the creation of a new MAINSAIL MODULE instance (the “remote MODULE”) in the remote process. Intermodule PROCEDURE calls to the remote MODULE are called “remote PROCEDURE calls” (RPC).

The remote MODULE may be created in any process with which a program is communicating using STREAMS. Commonly, the remote MODULE is created in either a global server process, in a child process, or in a parent process.

In the discussion that follows, the process in which the remote MODULE actually runs is often termed the “RPC server”, and the process that makes the calls the “RPC client”. However, these processes need not be servers and clients in the senses given in Section 7.3. The server may be thought of as the “callee process” and the client as the “caller process”.

RPC clients may be written in MAINSAIL or in C. The MAINSAIL case is described first, but many of the MAINSAIL client concepts are applicable to C as well. RPC servers must be written in MAINSAIL.

To create a new instance of a remote MODULE, a MAINSAIL program must know how to open a stream that communicates with the process that is providing that remote MODULE, or else it must already have a stream that is open to that process. In the case of a remote MODULE provided by a global server, the user needs to know the name of the service and possibly the host system, if the service is provided on multiple systems.

5.1. Overview

To provide remote computation using RPC, the programmer writes a MAINSAIL MODULE (the server) with interface PROCEDUREs that provide the desired computation. The MODULE's interface declaration must use the prefix CLASS $remoteModuleCls. The remote MODULE is compiled with the RPC subcommand (with no arguments) to the MAINSAIL compiler to produce two MAINSAIL source MODULEs. By default, the source MODULE names are derived from the remote MODULE name by appending SRV and CLI to the remote MODULE name, and the output file names are derived from the MODULE names by converting them to lower case and adding .msl. For example, if the remote MODULE is FOO, the compilation produces the two MAINSAIL source MODULEs FOOCLI and FOOSRV in the files foocli.msl and foosrv.msl. (You can change the default names by specifying the OUTOBJFILE compiler subcommand; see Section 4.26 of the MAINSAIL Compiler User's Guide for details.)

In the case of a remote MODULE FOO, the FOO and FOOSRV MODULEs are compiled for the system that is to execute the remote MODULE, and FOOCLI is compiled for the system that will call the remote MODULE.

Figure 5–1 shows a process A creating a new instance of a remote MODULE FOO and calling a PROCEDURE proc1 in FOO.

Figure 5–1. How a Remote Procedure Is Routed
+----------------------+    +------------------------+
|PROCESS A (CLIENT)    |    |MODULE fooCli           |
|----------------------|    |------------------------|
|POINTER(fooCls) p;    |    |MODULE(fooCls) fooCli;  |
|                      |    |                        |
|p := $newRemoteModule(| +->|PROCEDURE proc1(....);  |
|    "service>abc",    | |  |# Communication with B  |
|    "foo");           | |  |...                     |-+
|p.proc1(....);        |-+  |                        | |
+----------------------+    +------------------------+ |
=======================================================|=
                                                       |
                          +----------------------------+
                          |
+----------------------+  |  +-----------------------+
|PROCESS B (SERVER)    |  |  |MODULE fooSrv          |
|----------------------|  |  |-----------------------|
|INIT modNames         |  |  |MODULE(fooCls) fooSrv; |
|    ("foo",           |  |  |                       |
|     "infMod");       |  |  |                       |
|                      |  +->|PROCEDURE proc1(....); |
|$becomeServer(        |     |BEGIN                  |
|    "service>abc",    |     |# Communication with A |
|    modNames);        |     |fooPtr.proc1(....);    |--+
|                      |     |END;                   |  |
+----------------------+     +-----------------------+  |
                                                        |
                          +-----------------------------+
                          |
                          |  +-----------------------+
                          |  |MODULE foo             |
                          |  |-----------------------|
                          |  |MODULE(fooCls) foo;    |
                          |  |                       |
                          +->|PROCEDURE proc1(....); |
                             |# actual code for proc1|
                             +-----------------------+

The MODULE FOOCLI is a “stub” that runs in the “client” process. It has the same interface PROCEDUREs as FOO and it implements those PROCEDUREs by communicating with the “server” process, sending and receiving the arguments as necessary, and returning the values produced by the PROCEDURE call.

The MODULE FOOSRV is the companion stub to FOOCLI that runs in the server process. It communicates with FOOCLI to receive the arguments, then it invokes the correct PROCEDURE in the “real” MODULE, FOO, to do the actual work. It sends back the resultant modified arguments to FOOCLI.

The remote MODULE itself, FOO, is written much as any other MAINSAIL MODULE would be written, except that certain restrictions on the argument types of PROCEDUREs apply, argument transmission efficiency considerations must be taken into account, its interface declaration must use prefix CLASS $remoteModuleCls, and no MODULE interface variables are allowed.

When developing distributed applications, it is often useful to write the MODULE that will eventually be remote (e.g., FOO) as a local MODULE and debug it that way. Then, to distribute the application, the remote MODULE can be compiled with the RPC compiler and either installed into a server or made available to the process that is to provide it.

The call to $newRemoteModule in the process A actually creates a new instance of the MODULE FOOCLI in A's address space. Recall that FOOCLI was produced by the RPC compiler from the interface of the “real” FOO MODULE. In its INITIAL PROCEDURE, FOOCLI uses STREAMS to connect to the service abc in the process B. B creates a new instance of the FOOSRV MODULE. FOOSRV in turn creates a new instance of the FOO MODULE. The connection is maintained until the instance of FOO is disposed.

When the process A makes the intermodule call FOOCLI.proc1, FOOCLI.proc1 communicates with FOOSRV in B, telling it that proc1 is being called. FOOSRV in B invokes FOOSRV.proc1 to read the USES arguments from FOOCLI.proc1. FOOSRV.proc1 then calls the “real” proc1, FOO.proc1, and communicates the results back to FOOCLI.proc1, which returns them to the original caller in A.

During the call to proc1, the current coroutine in A is blocked, just as it would be for a local PROCEDURE call. Because STREAMS communication is used, other scheduled coroutines in A, if any, can run while FOOCLI.proc1 is waiting for the results from FOOSRV.proc1.

In the example above, B is a global server process that uses the $becomeServer PROCEDURE to become an RPC server that provides the MODULEs FOO and INFMOD under the service name abc. The $becomeServer call does not return until the server is killed.

5.2. Remote MODULE PROCEDUREs

Figure 5–2. Remote MODULE PROCEDUREs
POINTER($remoteModuleCls)
PROCEDURE   $newRemoteModule
                        (
STRING serviceStreamName;
                         
STRING moduleName;
                         
OPTIONAL LONG INTEGER cliVersion,
                             
oldestSrvVersion;
                         
OPTIONAL BITS ctrlBits;
                         
OPTIONAL PRODUCES STRING msg);

POINTER($remoteModuleCls)
PROCEDURE   $newRemoteModule
                        (
POINTER($streamst;
                         
STRING moduleName;
                         
OPTIONAL LONG INTEGER cliVersion,
                             
oldestSrvVersion;
                         
OPTIONAL BITS ctrlBits;
                         
OPTIONAL PRODUCES STRING msg);

BOOLEAN
PROCEDURE   $becomeServer
                        (
STRING servicePortName;
                         
STRING ARRAY(*) moduleNames;
                         
OPTIONAL BITS ctrlBits;
                         
OPTIONAL PRODUCES STRING msg);

BOOLEAN
PROCEDURE   $becomeServer
                        (
POINTER($streamst;
                         
STRING ARRAY(*) moduleNames;
                         
OPTIONAL BITS ctrlBits;
                         
OPTIONAL PRODUCES STRING msg);

$newRemoteModule creates a new remote instance of the MODULE named moduleName. It returns a POINTER to a new instance of the client end of the desired MODULE. If errorOK is set in ctrlBits, errors cause a NULLPOINTER return with msg set; otherwise, a fatal error message is issued.

By default, the instance of the server end of the MODULE is a nonbound MODULE. However, if the bit $rpcBindModules is set in ctrlBits, the instance of the server end is bound instead of nonbound.

Currently, when the POINTER returned from a call to $newRemoteModule with the $rpcBindModules bit set is disposed, the bound remote MODULE instance is also disposed. This has undefined consequences for any other client that may be communicating with the same bound remote MODULE. This behavior is likely to change in the future.

The first instance of $newRemoteModule takes a stream name that can be used to establish a connection with the desired server process or create the desired child process. It calls $openStream to open serviceStreamName and creates a new local instance of moduleName to talk to a new remote instance of moduleName created in the server.

The second instance of $newRemoteModule takes an already open stream that is connected to a process that is about to call or has already called $becomeServer on the other end of the stream. It creates a local instance of moduleName to talk to a new remote instance of moduleName created by the process at the other end of the stream st. This form of $newRemoteModule is useful for talking to child or parent processes when the streams are already open.

$becomeServer becomes a server for the remote MODULEs named by the elements of moduleNames. If errorOK is set in ctrlBits, errors in becoming a server cause a FALSE return with msg set; otherwise, a fatal error message is issued.

The first form of $becomeServer sets up one or more ports on which new clients are accepted as they connect to the service of which the name is given as part of servicePortName. This form is used by a global server process. For each client that connects, a service coroutine is started to provide one of the remote MODULEs to that client. The service coroutines run scheduled so that more than one client can be serviced at once. When the client disconnects, the service coroutine is killed automatically.

The port name may specify an explicit network protocol, e.g., tcp>abc, or it may specify the generic protocol service, e.g., service>abc, to serve clients using every network protocol that the server is declared to serve. The service abc must have been previously declared as a valid service name in the services table as described in Appendix A.

The second form of $becomeServer may be used by a process that wishes to provide a remote MODULE to the process at the other end of the stream st. It provides any of the remote MODULEs in moduleNames to the process at the other end of the stream st. The process on the other end of the stream must call $newRemoteModule to create a new remote MODULE. When the process at the other end disposes of the remote MODULE, the second form of $becomeServer returns with the stream still open.

In either case of $becomeServer, the server program can perform independent computation and/or stream I/O on its own in parallel by calling $becomeServer in an independent scheduled coroutine that it sets up.

The caller of $becomeServer should handle $killServerExcpt if any of the remote MODULEs might raise $killServerExcpt (see Section 5.12).

$becomeServer should be called at most once in a given MAINSAIL process; otherwise, the effects are undefined.

5.3. Remote MODULE Interfaces

The following restrictions apply to MODULE interface declarations for remote MODULEs:

The semantics of USES, MODIFIES, and PRODUCES for POINTERs and dynamic ARRAYs is slightly different between local calls and remote calls. In local calls (standard MAINSAIL), a PRODUCES or MODIFIES POINTER or dynamic ARRAY means that the POINTER value itself can be changed. The object referred to by the POINTER or dynamic ARRAY can be changed even through a USES parameter. Since RPC involves a transfer of data, USES, MODIFIES, and PRODUCES refer to the entire structure being pointed to; i.e., a USES POINTER or dynamic ARRAY sends the structure to the remote MODULE, but does not read it back. A PRODUCES POINTER or dynamic ARRAY does not send a structure, but reads a structure back from the remote MODULE. A MODIFIES POINTER or dynamic ARRAY both writes and reads the structure.

5.4. STRING RPC Parameter Limitations

A Version 16.20 or later MAINSAIL RPC client talking with a Version 16.20 or later MAINSAIL RPC server can send and receive STRING values up to 2147483647 characters long. A client older than Version 16.20 can talk with a 16.20 or newer server or vice versa, but STRING values are then limited to a maximum of 32767 characters.

RPC currently does not handle $PROCVARs or inplace records or ARRAYs.

5.5. Buffer Passing Conventions

A buffer is a block of storage units, portable storage units (PDF data), or character units. The start of a buffer of storage units is given by an ADDRESS parameter. The start of a buffer of portable storage units or characters is given by a CHARADR parameter. The data types ADDRESS and CHARADR are thus reserved for buffer passing between the two processes.

Buffers containing characters are distinguished from buffers containing portable data by the name of the CHARADR parameter. Parameter names ending with pdf (case is not distinguished in parameter names) indicate portable data; other names indicate characters. Characters are translated by the remote PROCEDURE call interface automatically, while PDF and storage unit data are not.

The length of the buffer is given in parameters following the ADDRESS or CHARADR parameter. If the buffer contains storage units, the length and size are given in storage units; if the buffer contains characters or PDF data, the length and size are given in character units.

Like other PROCEDURE parameters, buffers may be USES, MODIFIES, or PRODUCES. A USES buffer passes the data in the buffer to the PROCEDURE. A PRODUCES buffer receives data from the PROCEDURE. A MODIFIES buffer passes data to the PROCEDURE and receives data back (the returned data overwrite the original data).

USES buffers are passed as a USES ADDRESS or CHARADR parameter followed by a USES LONG INTEGER buffer length. The caller is responsible for allocating and deallocating the buffer.

MODIFIES and PRODUCES buffers each come in two forms: in one form, a MODIFIES or PRODUCES ADDRESS or CHARADR parameter is followed by a single MODIFIES or PRODUCES LONG INTEGER length parameter (MODIFIES-ADDRESS and PRODUCES-ADDRESS buffers); in the other, a USES ADDRESS or CHARADR parameter is followed by two LONG INTEGERs, a USES parameter size and a MODIFIES or PRODUCES parameter length (MODIFIES-content and PRODUCES-content buffers).

A MODIFIES-ADDRESS buffer must originally be allocated by the caller of a remote PROCEDURE, but may be reallocated by the remote PROCEDURE; a PRODUCES-ADDRESS buffer is always originally allocated by the remote PROCEDURE. By contrast, MODIFIES-content and PRODUCES-content buffers are always allocated and deallocated by the caller of the remote PROCEDURE, never by the remote PROCEDURE itself.

Special PROCEDUREs must be used to allocate and deallocate MODIFIES-ADDRESS and PRODUCES-ADDRESS buffers. These special PROCEDUREs may optionally be used to allocate and deallocate the other kinds of buffers as well.

For USES, MODIFIES-content, and PRODUCES-content buffers, the ADDRESS or CHARADR parameter must itself be declared as a USES parameter. The caller of the PROCEDURE must thus supply a valid ADDRESS or CHARADR; i.e., the parameter may not be NULLADDRESS or NULLCHARADR.

A USES buffer is indicated by following the ADDRESS or CHARADR by a single USES LONG INTEGER parameter giving the amount of data supplied in the buffer. The parameter name must end with length.

PRODUCES-ADDRESS and MODIFIES-ADDRESS parameters are indicated by following the ADDRESS or CHARADR by a single LONG INTEGER parameter. Both parameter are qualified with MODIFIES in the case of a MODIFIES-ADDRESS buffer or PRODUCES in the case of a PRODUCES-ADDRESS buffer. The name of the LONG INTEGER parameter must end in length; it represents the amount of data in the buffer. In the case of a MODIFIES-ADDRESS parameter, the length parameter is originally set by the caller, and may be changed by the remote PROCEDURE if the amount of data to be sent back is different from amount originally set; the remote PROCEDURE may choose to reallocate the buffer to be of a different size in this case. In the case of a PRODUCES-ADDRESS parameter, the buffer is originally allocated by the remote PROCEDURE and the remote PROCEDURE sets the length parameter to tell how much information it is producing.

It is permissible for a MODIFIES-ADDRESS parameter to be NULLADDRESS and for a PRODUCES-ADDRESS buffer to produce NULLADDRESS. The length parameter should be zero in these cases.

PRODUCES-content and MODIFIES-content buffers are indicated by following the ADDRESS or CHARADR by two LONG INTEGER parameters. The first must be a USES parameter giving the allocated size of the buffer; its name must end with size. For PRODUCES buffers, the second parameter must be a PRODUCES parameter that will be set to the length of data supplied by the PROCEDURE call. For MODIFIES buffers, the second parameter must be a MODIFIES parameter that is initially set to the length of data in the buffer and is modified to the length of data supplied by the PROCEDURE call. In both cases, the name of the second parameter must end with length.

Example 5–3 shows an example MODULE declaration of a remote MODULE with USES, PRODUCES-ADDRESS, MODIFIES-ADDRESS, PRODUCES-content, and MODIFIES-content buffer parameters for storage unit, character, and portable data buffers.

Example 5–3. Example USES, PRODUCES-ADDRESS, MODIFIES-ADDRESS, PRODUCES-Content, and MODIFIES-Content Buffer Parameters
MODULE($remoteModuleClsxxx (

    # 
USES Buffers

    
PROCEDURE   writeDataBuf
                            (
ADDRESS buf;
                             
LONG INTEGER bufLength);
    
PROCEDURE   writeTextBuf
                            (
CHARADR buf;
                             
LONG INTEGER bufLength);
    
PROCEDURE   writePdfBuf (CHARADR bufPdf;
                             
LONG INTEGER bufLength);

    # 
PRODUCES-ADDRESS Buffers

    
PROCEDURE   readDataBuf (PRODUCES ADDRESS buf;
                             
PRODUCES LONG INTEGER
                                 
bufLength);
    
PROCEDURE   readTextBuf (PRODUCES CHARADR buf;
                             
PRODUCES LONG INTEGER
                                 
bufLength);
    
PROCEDURE   readPdfBuf  (PRODUCES CHARADR bufPdf;
                             
PRODUCES LONG INTEGER
                                 
bufLength);

    # 
MODIFIES-ADDRESS Buffers

    
PROCEDURE   writeAndReadDataBuf
                            (
MODIFIES ADDRESS buf;
                             
MODIFIES LONG INTEGER
                                 
bufLength);
    
PROCEDURE   writeAndReadTextBuf
                            (
MODIFIES CHARADR buf;
                             
MODIFIES LONG INTEGER
                                 
bufLength);
    
PROCEDURE    writeAndReadPdfBuf
                            (
MODIFIES CHARADR bufPdf;
                             
MODIFIES LONG INTEGER
                                 
bufLength);

    # 
PRODUCES-Content Buffers

    
PROCEDURE   readDataBufContent
                            (
ADDRESS buf;
                             
LONG INTEGER bufSize;
                             
PRODUCES LONG INTEGER
                                 
bufLength);
    
PROCEDURE   readTextBufContent
                            (
CHARADR buf;
                             
LONG INTEGER bufSize;
                             
PRODUCES LONG INTEGER
                                 
bufLength);
    
PROCEDURE   readPdfBufContent
                            (
CHARADR bufPdf;
                             
LONG INTEGER bufSize;
                             
PRODUCES LONG INTEGER
                                 
bufLength);

    # 
MODIFIES-Content Buffers

    
PROCEDURE   writeAndReadDataBufContent
                            (
ADDRESS buf;
                             
LONG INTEGER bufSize;
                             
MODIFIES LONG INTEGER
                                 
bufLength);
    
PROCEDURE   writeAndReadTextBufContent
                            (
CHARADR buf;
                             
LONG INTEGER bufSize;
                             
MODIFIES LONG INTEGER
                                 
bufLength);
    
PROCEDURE    writeAndReadPdfBufContent
                            (
CHARADR bufPdf;
                             
LONG INTEGER bufSize;
                             
MODIFIES LONG INTEGER
                                 
bufLength);

);

5.5.1. When Buffers Are Allocated and Deallocated

In the case of USES, MODIFIES-content, and PRODUCES-content buffers, the client program is responsible for explicitly allocating and deallocating the buffer that is to be sent to the server. In the case of PRODUCES-ADDRESS buffers, the remote PROCEDURE allocates the buffer, and the calling PROCEDURE disposes it. In the case of MODIFIES-ADDRESS buffers, the client program allocates the buffer initially. It sends it to the remote PROCEDURE, which may dispose the buffer and allocate a new one; alternatively, the remote PROCEDURE may reuse the buffer it received from the client, if that buffer is already the right size. When the remote PROCEDURE returns, the calling PROCEDURE is responsible for disposing the buffer. Of course, there must really be two copies of each buffer, one in the client process and one in the server process; however, the client and server stubs handle the details of allocating and deallocating buffers in such a way as to hide this fact, insofar as possible.

The exact sequence of allocations and deallocations for the four types of buffers is shown in Table 5–4. The figure shows the sequence of events (including allocations and deallocations) that takes place with each kind of buffer. Allocations or deallocations for which “client program” or “remote PROCEDURE” appears in the “Whose Responsibility” column must be done explicitly in the client or server code; allocations and deallocations done by the client or server stub are done automatically.

Table 5–4. Order of Events with MODIFIES/PRODUCES-Content and MODIFIES/PRODUCES-ADDRESS Buffers
MODIFIES/PRODUCES-Content Buffers
Step # Whose Responsibility What Happens
1 client program allocate buffer to pass to remote call
2 client program make remote call
3 client stub send buffer contents to server, if MODIFIES
4 server stub allocate buffer to receive data from client
5 server stub send data back to client after call in server
6 server stub deallocate buffer
7 client program deallocate buffer passed to remote call
PRODUCES-ADDRESS Buffer
Step # Whose Responsibility What Happens
1 client program make remote call (no buffer needs to be allocated first)
2 remote PROCEDURE allocate buffer to produce
3 client stub allocate buffer to receive data from server
4 server stub deallocate buffer produced by call in server
5 client program deallocate buffer received from server when buffer no longer needed
MODIFIES-ADDRESS Buffer
Step # Whose Responsibility What Happens
1 client program allocate original buffer to pass to remote call
2 client program make remote call
3 server stub allocate buffer to receive data from client
4 remote PROCEDURE deallocate buffer received from client after data processed (optional)
5 remote PROCEDURE allocate new buffer in which to return data to client (optional)
6 client stub deallocate buffer originally passed to remote call
7 client stub allocate new buffer to receive data from server
8 server stub deallocate buffer that was sent to client
9 client program deallocate buffer received from client after data processed

5.5.2. How to Allocate and Deallocate Buffers

Special PROCEDUREs are required for allocating and deallocating MODIFIES-ADDRESS and PRODUCES-ADDRESS buffers passed to remote PROCEDUREs. The PROCEDURE rpcNewBuffer must be called to allocate such a buffer instead of a PROCEDURE like newPage or newScratch; the PROCEDURE rpcDisposeBuffer must be called instead of pageDispose or scratchDispose. These PROCEDUREs may also be called to allocate USES, MODIFIES-content, and PRODUCES-content buffers; this is recommended but not required.

Figure 5–5. rpcNewBuffer and rpcDisposeBuffer
BOOLEAN PROCEDURE rpcNewBuffer (
    
PRODUCES ADDRESS                buffer;
    
LONG INTEGER                    bufSize;
    
POINTER($remoteModuleCls)       rem);

BOOLEAN PROCEDURE rpcNewBuffer (
    
PRODUCES CHARADR                buffer;
    
LONG INTEGER                    bufSize;
    
POINTER($remoteModuleCls)       rem);

PROCEDURE rpcDisposeBuffer  (
    
MODIFIES CHARADR                buffer;
    
LONG INTEGER                    bufSize;
    
POINTER($remoteModuleCls)       rem);

PROCEDURE rpcDisposeBuffer (
    
MODIFIES ADDRESS                buffer;
    
LONG INTEGER                    bufSize;
    
POINTER($remoteModuleCls)       rem);

Although it is possible to call newPage or newScratch directly to allocate USES, MODIFIES-content, and PRODUCES-content RPC buffers, rpcNewBuffer is better because it can be redirected to a user-provided MODULE to improve performance if buffers are large or allocation is frequent; this is not possible if calls to newPage and newScratch are hardwired.

rpcNewBuffer sets buffer to NULLADDRESS or NULLCHARADR if bufSize is less than or equal to zero. rpcDisposeBuffer does not do anything if buffer is Zero or bufSize is less than or equal to zero.

5.5.3. Redirecting Calls to rpcNewBuffer and rpcDisposeBuffer

Default versions of rpcNewBuffer and rpcDisposeBuffer are provided. However, in some situations you may find that performance is improved if you supply special versions of these calls for a particular remote MODULE. To do this, you must write a MODULE that provides your versions of rpcNewBuffer and rpcDisposeBuffer.

In order to redirect calls to rpcNewBuffer and rpcDisposeBuffer to your own MODULE and to cause the allocations that occur in the client or server also to be redirected to your MODULE, you must set the bufferMgrModule field of $remoteModuleCls to point to a data section of your MODULE:

POINTER($rpcBufferMgrClsbufferMgrModule;  # Set by user

Once you set the bufferMgrModule interface field of a MODULE, all allocations of MODIFIES-ADDRESS and PRODUCES-ADDRESS buffers in that MODULE must go through that MODULE's bufferMgrModule field. In the client, this will happen if you pass p (where p is the POINTER to the remote MODULE) as the rem parameter to rpcNewBuffer and rpcDisposeBuffer; in the remote MODULE, it will happen if you pass thisDataSection as the rem parameter.

The CLASS $rpcBufferMgrCls is defined as:

CLASS $rpcBufferMgrCls (

    
BOOLEAN
    
PROCEDURE   rpcNewBufferAdr     (PRODUCES ADDRESS buffer;
                                     
LONG INTEGER bufSize);

    
PROCEDURE   rpcDisposeBufferAdr (ADDRESS buffer;
                                     
LONG INTEGER bufSize);

    
BOOLEAN
    
PROCEDURE   rpcNewBufferChr     (PRODUCES CHARADR buffer;
                                     
LONG INTEGER bufSize);

    
PROCEDURE   rpcDisposeBufferChr (CHARADR buffer;
                                     
LONG INTEGER bufSize);
);

If you want to provide your own allocation and deallocation PROCEDUREs, you must write a MODULE with this interface. The interface PROCEDUREs should not be called directly but should be called through rpcNewBuffer and rpcDisposeBuffer and implicitly through the client or server stub.

A potentially useful thing to do if you have your own $rpcBufferMgrCls MODULE is allocating storage from a pool that you maintain instead of calling newPage or newScratch (to prevent garbage collection from being triggered by such calls).

You can have a bufferMgrModule in either the client or the server or both (but see below if you are actually calling the “remote” MODULE from within the same process). Assume that you have a MODULE myMod of $rpcBufferMgrCls that you want to perform allocation in both the client and the server. To set it up as bufferMgrModule in the client, set the client's bufferMgrModule field immediately after the call to $newRemoteModule:

p := $newRemoteModule(...);
p.bufferMgrModule := bind(myMod);

Once you do this, all automatic buffer allocations in the client stub go through myMod.

To set it up as bufferMgrModule in the server, set the interface variable in the remote MODULE's INITIAL PROCEDURE:

INITIAL PROCEDURE; # of remote MODULE
...
bufferMgrModule := bind(myMod);
...

Once you do this, all automatic buffer allocations in the server stub go through myMod.

If you are calling a MODULE that is written as a remote MODULE from within the same process as the client, the client and “remote” MODULEs' bufferMgrModule fields must be the same. If you allocate bufferMgrModule as a bound MODULE and use the same MODULE name as shown above, you will get the desired effect.

5.5.4. Low-Level Forms of rpcNewBuffer and rpcDisposeBuffer

There are additional, lower-level forms of rpcNewBuffer and rpcDisposeBuffer that take an $rpcBufferMgrCls POINTER instead of a $remoteModuleCls POINTER; in this case, the specified $rpcBufferMgrCls instance is used:

BOOLEAN PROCEDURE rpcNewBuffer (
    
PRODUCES ADDRESS                buffer;
    
LONG INTEGER                    bufSize;
    
OPTIONAL POINTER($rpcBufferMgrClsp);

BOOLEAN PROCEDURE rpcNewBuffer (
    
PRODUCES CHARADR                buffer;
    
LONG INTEGER                    bufSize;
    
OPTIONAL POINTER($rpcBufferMgrClsp);

PROCEDURE rpcDisposeBuffer  (
    
MODIFIES CHARADR                buffer;
    
LONG INTEGER                    bufSize;
    
OPTIONAL POINTER($rpcBufferMgrClsp);

PROCEDURE rpcDisposeBuffer (
    
MODIFIES ADDRESS                buffer;
    
LONG INTEGER                    bufSize;
    
OPTIONAL POINTER($rpcBufferMgrClsp);

It should rarely be necessary for a program to use the low-level forms of rpcNewBuffer and rpcDisposeBuffer; the forms that take a $remoteModuleCls POINTER should be satisfactory for most applications.

5.6. Example of Remote MODULEs between a Parent and Child Process

Example 5–6 shows an example remote MODULE that provides a single trivial remote PROCEDURE hello.

Example 5–6. Example Remote MODULE
BEGIN "foo"

RESTOREFROM "rpcHdr";

MODULE($remoteModuleClsfoo (
    
STRING PROCEDURE hello (STRING s);
);

STRING PROCEDURE hello (STRING s);
RETURN("How do you do, " & s);

$remoteModuleDefaults

END "foo"

Examples 5–7 and 5–8 are an example of the use of FOO by executing it in a child and calling it from a parent.

Example 5–7. Parent Executing Remote MODULE FOO in a Child
BEGIN "parent"

RESTOREFROM "rpcHdr";

CLASS($remoteModuleClsfooCls (
    
STRING PROCEDURE hello (STRING s);
);

POINTER($streamchTty;
POINTER($coroutinercwc;

PROCEDURE reader;
BEGIN
STRING s;
WHILE $success($readStream(chTty,s,errorOK)) DO
    
$writeStream($tty,s,$line);
IF wc AND NOT $killedCoroutine(wcTHEN
    
$killCoroutine(wc);
$reschedule(delete);
END;



PROCEDURE writer;
BEGIN
STRING s;
DO  $readStream($tty,s)
    
UNTIL NOT $success($writeStream(chTty,s,errorOK));
IF rc AND NOT $killedCoroutine(rcTHEN
    
$killCoroutine(rc);
$reschedule(delete);
END;



INITIAL PROCEDURE;
BEGIN
STRING s;
POINTER(fooClsm;
POINTER($streamchCtl;

$initializeStreams;

Create the child
$openStream(chTty,chCtl,"process>" & $executableBootName &
    " 
child");

Handle its tty
rc := $createCoroutine(thisDataSection,"reader");
$queueCoroutine(rc);
wc := $createCoroutine(thisDataSection,"writer");
$queueCoroutine(wc);

Use it to supply FOO
m := $newRemoteModule(chCtl,"foo");
s := m.hello("xxx");
...
dispose(m);

Kill the child (kills the reader and writer implicitly)
$closeStream(chTty); $closeStream(chCtl);
END;

END "parent"

In its INITIAL PROCEDURE, the parent, shown in Example 5–7, creates a child process running the MODULE CHILD under the current version of MAINSAIL. It sets up a coroutine to multiplex the child's TTY (for error messages and debugging) to the parent's TTY. It then creates a new instance of the remote MODULE FOO in the child and calls the PROCEDURE hello in the remote MODULE. Eventually it disposes the remote MODULE, and kills the child process.

Example 5–8 shows how the MODULE CHILD might be written. It becomes a server for the remote MODULE FOO.

Example 5–8. Child Providing the FOO Remote MODULE
BEGIN "child"

RESTOREFROM "rpcHdr";

CLASS($remoteModuleClsfooCls (
    
STRING PROCEDURE hello (STRING s);
);

INITIAL PROCEDURE;
BEGIN
STRING ARRAY(1 TO 1) modNames;

new(modNames);
INIT modNames ("foo");

$initializeStreams;

$becomeServer($parent,modNames);
END;

END "child"

A generic MODULE RPCSRV (see Section 5.13) is supplied with the RPC package. The RPCSRV MODULE provides the functionality of the child shown in Example 5–8 in a generic way.

5.7. Example Clients and Servers Using Remote MODULEs

Example 5–9 shows the same remote MODULE as in Example 5–6 except that it declares its optional protocol version numbers.

Example 5–9. Example Remote MODULE
BEGIN "foo"

RESTOREFROM "rpcHdr";

INITIAL PROCEDURE;
BEGIN
newestVersion := 5L;     # optional (see below)
oldestVersion := 3L;     # optional (see below)
END;

MODULE($remoteModuleClsfoo (
    
STRING PROCEDURE hello (STRING s);
);

STRING PROCEDURE hello (STRING s);
RETURN("How do you do, " & s);

$remoteModuleDefaults

END "foo"

Examples 5–10 and 5–11 are examples of a server program that provides FOO, and a client program that executes FOO remotely.

Example 5–10. Example Client of the FOO Remote MODULE Service
BEGIN "client"

RESTOREFROM "rpcHdr";

CLASS($remoteModuleClsfooCls (
    
STRING PROCEDURE hello (STRING s);
);

INITIAL PROCEDURE;
BEGIN
STRING s;
POINTER(fooClsm;

$initializeStreams;

Ask for version 4L of foo
m := $newRemoteModule("service>foo","foo",4L);
s := m.hello("MrSmith");
...
dispose(m);
END;

END "client"

Example 5–11. Example Server Providing the FOO Remote MODULE
BEGIN "server"

RESTOREFROM "rpcHdr";

CLASS($remoteModuleClsfooCls (
    
STRING PROCEDURE hello (STRING s);
);

INITIAL PROCEDURE;
BEGIN
STRING ARRAY(1 TO 1) modNames;

new(modNames);
INIT modNames ("foo");

$initializeStreams;

$becomeServer("service>foo",modNames);
END;

END "server"

The client is written like the parent in the previous section, except that it asks to talk to the server named foo instead of creating a child process. The server process is written like the child in the previous section, except that it becomes a server to service>foo instead of to its parent.

The server shown in Example 5–11 keeps accepting and servicing new clients indefinitely.

5.8. The Fields of $remoteModuleCls

Figure 5–12. User-Visible Fields of $remoteModuleCls
CLASS $remoteModuleCls (
    ...
    
LONG INTEGER version,oldestVersion,newestVersion;
    
LONG INTEGER $nto;
    
LONG INTEGER $maxExec;
    ...
    # 
Additional fields declared (you don't need to know
    # 
anything about thesebut you must not have an
    # 
outer declaration of the same identifieror you
    # 
will get a compiler error):
    #
    # 
serviceClient
    # 
initStream
    # 
executeProc
    # 
rpcData
);

$remoteModuleCls provides several user-visible fields, as shown in Figure 5–12.

The version field is used to provide a user-defined version number for the service provided by the remote MODULE, as described in Section 5.9. oldestVersion and newestVersion specify the oldest and newest semantics the server is willing to provide to the client; see Example 5–13.

The two fields $maxExec and $nto specify timeouts for the remote MODULE.

$maxExec is the longest expected execution time for the remote PROCEDURE. If the remote PROCEDURE executes for longer than $maxExec, the client will time out and abort execution of the remote PROCEDURE. The default value for $maxExec is quite long, since it is difficult to predict how long a remote PROCEDURE may require in order to finish.

$nto is a short timeout used when setting up a remote PROCEDURE call. Programmers will not usually need to adjust $nto. However, where client-server communication is unexpectedly slow, it is possible that $nto may be unnecessarily exceeded and the remote PROCEDURE call aborted by a timeout. In this case, $nto should be increased to avoid the timeout.

To set the a timeout, set the appropriate field of $remoteModuleCls immediately after allocating a remote MODULE; e.g., for $maxExec:

m := $newRemoteModule(...);
m.$maxExec := <new timeout value>;

You must set the $maxExec field before making any calls to the remote MODULE; the effect of setting it after the first call is made is undefined.

5.9. Use of Version Numbers

When a remote MODULE is provided as part of a service, the author of the service must provide writers of client MODULEs with documentation that includes:

The declaration of the remote MODULE allows the client program to declare a POINTER to the remote MODULE's CLASS so that PROCEDURE calls can be made using MAINSAIL's p.f construct.

The description of the semantics of the PROCEDUREs allows the client to make use of the remote MODULE to do useful work.

The service version number allows the client to identify the semantics it is expecting to the server. If a new version of the server is written that accepts new or additional semantics in addition to the old semantics, the server may be written so that it can also service older clients.

When a remote MODULE is written for use in a server, it may set two version parameters in its INITIAL PROCEDURE, as shown in Example 5–13.

Example 5–13. A Remote MODULE That Sets Its Version Numbers
BEGIN "foo"

...

INITIAL PROCEDURE;
BEGIN
oldestVersion := 3L;
newestVersion := 5L;
...
END;

END "foo"

These numbers describe the oldest and newest semantics the server is willing to provide to the client, thus allowing servers to be updated with new semantics while still providing the older semantics to older clients.

newestVersion is the current version of the remote MODULE semantics. oldestVersion is the oldest backward-compatible version that the server provides.

If a server provides no version numbers, the default for both version numbers is 0L.

If the code in the remote MODULE wishes to adapt to the client's actual version, it may access the LONG INTEGER interface variable version. This interface variable is declared in $remoteModuleCls, as shown in Figure 5–12. It is set to the value of the cliVersion parameter in the call to $newRemoteModule.

5.10. Writing Adaptable Clients

Version numbers allow new servers to be installed that support both new and old clients, and for new clients to make use of older servers. By giving an explicit remote MODULE version number (or range of version numbers) in the $newRemoteModule call, the client can indicate which MODULE version (or range of MODULE versions) it is written to use.

When the server and client establish a connection at the time of the call to $newRemoteModule, the underlying communication code determines if there is an overlap between the versions provided by the server and the versions acceptable to the client, and if so, uses the highest common version number. This highest number is then made available to the “true” remote MODULE in the LONG INTEGER MODULE interface variable version.

A client may ask for a range of service version numbers when it calls $newRemoteModule. It may then obtain the actual remote MODULE version provided by the server and adapt to the published semantic and syntactic specifications of the returned version.

If different versions provide different MODULE interfaces, the client must not call any PROCEDUREs of which the interface has changed (this requirement is enforced by runtime checking).

5.11. The Server Log File

When a server is started with the first form of $becomeServer, a log of the server's activities is kept in the file server.log. By examining that file (in the directory on which the server is running), a user may determine what the server has been doing.

Entries are made automatically into server.log when the following activities occur in a network server (i.e., a server that is available to processes other than its parent):

If multiple servers are run from the same directory, a MAINEX ENTER subcommand can be used in each server to cause the log files to have different names, if desired.

A sample server.log file:

29-Feb-88 14:21:
    
Server service>srv StartedRPC 1 MAINSAIL 11.20
29-
Feb-88 14:21:
    
Clientbob@Poseidon.1 foo  5 3  29-Feb-88 14:21
29-
Feb-88 14:32:
    
Finishbob@Poseidon.1 foo  5 3  29-Feb-88 14:21

5.12. Terminating a Global Server: $killServerExcpt

By convention, the server process can be killed by raising the exception $killServerExcpt in one of the remote MODULEs. Any PROCEDURE that calls $becomeServer for MODULEs that might raise $killServerExcpt should handle this exception. The code might look something like:

$HANDLE $becomeServer(...)
$WITH
    
IF $exceptionName = $killServerExcpt THENB
        
... clean up...
        
exit END;

The generic RPC server RPCSRV obeys this convention (see Section 5.13).

5.13. Generic RPC Server: the RPCSRV MODULE

The RPCSRV MODULE is provided by the RPC package to facilitate the creation of child and server processes for remote MODULEs. RPCSRV is a generic MODULE that provides remote MODULEs either to its parent or as a global server.

RPCSRV accepts a command line of the form:

rpcsrv servicePortName rm1 rm2 rm3 ...

to start a global server on the port named servicePortName, or:

rpcsrv - rm1 rm2 rm3 ...

to start a parent server on $parent. In both cases, the names rm1, rm2, etc., are names of remote MODULEs to supply to the clients or parent.

Using the RPCSRV MODULE, a server named foo that provides the RPC MODULEs foo and infMod can be started using the UNIX shell command:

/mslDirectory/mainsa rpcsrv service\>foo foo infMod<eol>

where mslDirectory is the MAINSAIL directory. Note that under the UNIX shell, you must quote the > used in the service port name with the shell escape character \.

Using the RPCSRV MODULE, a child process that provides the RPC MODULE FOO can be started using the $openStream PROCEDURE in a MAINSAIL program:

$openStream(chTty,chCtl,"process>" & $executableBootName &
            " 
rpcsrv - foo");

5.14. RPC Implementation Restrictions

Only one
$newRemoteModule (or, in C RPC, one _init function; see Section 5.18) may be active on a given stream, in the case that the second form of $newRemoteModule is called. This restriction may be lifted some day.

A server process must do a $becomeServer before a client process can call $newRemoteModule to talk to that server. This restriction is not severe because server processes are often started automatically when the node on which they run comes up (see Section A.4).

5.15. RPC Efficiency Considerations

Because many network protocols are often slow, remote MODULEs should be designed to handle relatively large subtasks rather than trivial ones. When possible, all the necessary USES and PRODUCES data should be passed in a single call rather than multiple calls.

For example, in a typical configuration, the wall clock time to bind a remote MODULE may vary from a fraction of a second to seconds, depending on whether the server process is paged out. Once the remote MODULE is bound, the wall clock time to complete a trivial remote PROCEDURE call is typically about 0.2 seconds. This time is almost entirely due to TCP communications overhead.

We believe that the overhead per PROCEDURE call could be improved with a faster UNIX network protocol such as UDP. In addition, on operating systems that support faster mechanisms, the overhead may be considerably less. However, if the remote PROCEDURE does a nontrivial amount of computation (e.g., several seconds' worth), even the communication overhead of TCP is not a significant factor.

5.16. Parallel Processing Using RPC Calls

Section 8.9.2 describes how RPC calls can be performed in parallel using multitasking.

5.17. Remote Exceptions

RPC can propagate exceptions from a remote MODULE to the calling client. It can also examine the server's PROCEDURE call stack and debug the server.

The model for exception handling is that the remote PROCEDURE execution is taking place in the client, so exceptions go up to the PROCEDURE invocation in the server stub, then are raised in the client stub. Certain exceptions are considered local to the server (mainly $abortProcedureExcpt). These are not raised in the client.

$exceptionPointerArg is always set to NULLPOINTER in the client, since it would be undesirable to pass a large structure when an exception occurred.

$systemExcpt is handled specially. If a remote PROCEDURE call causes a $systemExcpt, errMsg is called in the client with some extra registered exceptions: remote calls to see the remote calls stack, remote PRNTCO to see the output of PRNTCO run in the server, and remote debug to debug the server. $exceptionStringArg1 is prefixed with RPC: when $systemExcpt is raised, so that RPC: appears in any error message displayed to the user.

Debugging the server currently means that a call to $debugExec is made in the server. If the server is running on a terminal or terminal emulator, it can be debugged.

5.18. C RPC

Some features of the C RPC facility are subject to change. In particular, a new form of the C RPC mechanism (which handles POINTER and dynamic ARRAY parameters differently, among other changes) is likely to become available in a future version of MAINSAIL. The current C RPC mechanism described here will still be available, at least for a while after the introduction of the new mechanism, by using a special compiler subcommand.

RPC clients written in C are similar to those written in MAINSAIL except:

The correspondence between C client arguments and MAINSAIL remote MODULE parameters is shown in Table 5–14. The data types for return values are the same as those for return parameters, except that the caller is responsible for freeing returned pointers (as if they were PRODUCES parameters).

Table 5–14. C Client Arguments and MAINSAIL Server Parameters
MAINSAIL C Notes
USES BOOLEAN short 0 is FALSE, else TRUE
MODIFIES BOOLEAN short *
PRODUCES BOOLEAN short *
USES INTEGER short  
MODIFIES INTEGER short *
PRODUCES INTEGER short *
USES LONG INTEGER long
MODIFIES LONG INTEGER long *
PRODUCES LONG INTEGER long *
USES REAL float
MODIFIES REAL float *
PRODUCES REAL float *
USES LONG REAL double
MODIFIES LONG REAL double *
PRODUCES LONG REAL double *
USES BITS short Same as INTEGER
MODIFIES BITS short *
PRODUCES BITS short *
USES LONG BITS long Same as LONG INTEGER
MODIFIES LONG BITS long *
PRODUCES LONG BITS long *
USES STRING char *  
MODIFIES STRING char ** Client stub does not free input string; caller must cfree result
PRODUCES STRING char ** Caller must cfree result
USES ARRAY ARRAY * allocated by caller
MODIFIES ARRAY ARRAY * return allocated by RPC
PRODUCES ARRAY ARRAY * allocated by RPC
  POINTER forbidden  
  $PROCVAR forbidden
USES buffer: (ADDRESS, LONG INTEGER)
or (CHARADR, LONG INTEGER)
(char *, long)
MODIFIES buffer: (ADDRESS, LONG INTEGER, MODIFIES LONG INTEGER)
or (CHARADR, LONG INTEGER, MODIFIES LONG INTEGER}
(char *, long, long *)
PRODUCES buffer: (ADDRESS, LONG INTEGER, PRODUCES LONG INTEGER)
or (CHARADR, LONG INTEGER, PRODUCES LONG INTEGER)
(char *, long, long *)

5.18.1. Data Type Rules

String arguments must be NUL-terminated. Strings containing the character code 0 cannot be sent to or received from the remote PROCEDURE. String lengths must not exceed 32767. MODIFIES and USES strings returned by remote PROCEDUREs must be passed to cfree to reclaim the space they occupy. If the string or array passed to the remote PROCEDURE was allocated with malloc, then the client is also responsible for deallocating it by passing it to cfree; it is not automatically deallocated by RPC after transmission.

Arrays are passed as pointers to a C struct ARRAY typedefed in <mrpc.h> as:

typedef struct mainsail_array_descr {
    
char    *
ary_first_elem;
    
long    ary_lb1,ary_ub1,
            
ary_lb2,ary_ub2,
            
ary_lb3,ary_ub3;
    
short   ary_dimsary_type;
    
long    ary_alloc_status;
    } 
ARRAY;

When a C function passes an array to a remote PROCEDURE, it must first set all the relevant fields of the ARRAY struct. The meanings of the fields are shown in Table 5–15. Since MAINSAIL ARRAYs, unlike C arrays, are not necessarily zero-origin, lower bounds as well as upper bounds of each dimension must be specified.

Table 5–15. ARRAY struct Fields and Meanings
Field Meaning
ary_first_elem Pointer to first element
ary_dims Number of dimensions (1, 2, or 3)
ary_type MAINSAIL data type
ary_lb1 Lower bound of first dimension
ary_ub1 Upper bound of first dimension
ary_lb2 Lower bound of second dimension (only if ary_dims >= 2)
ary_ub2 Upper bound of second dimension (only if ary_dims >= 2)
ary_lb3 Lower bound of third dimension (only if ary_dims == 3)
ary_ub3 Upper bound of third dimension (only if ary_dims == 3)
ary_alloc_status For use with dispose_array

The ary_type field is the data type of the MAINSAIL ARRAY. It should be set to one of the following constants defined in <mrpc.h>:

MAINSAIL Data Type C Value for ary_type
BOOLEAN MS_BOOLEANCODE
INTEGER MS_INTEGERCODE
LONG INTEGER MS_LONGINTEGERCODE
REAL MS_REALCODE
LONG REAL MS_LONGREALCODE
BITS MS_BITSCODE
LONG BITS MS_LONGBITSCODE
STRING MS_STRINGCODE
ADDRESS MS_ADDRESSCODE
CHARADR MS_CHARADRCODE

ARRAYs of POINTERs or $PROCVARs are not allowed.

The elements of the array should be stored as the C data type corresponding to the MAINSAIL data type of the ARRAY; i.e., they do not have to be stored as MAINSAIL or PDF data types.

A NULLARRAY is represented by an ARRAY with ary_dims or ary_first_elem equal to zero. A USES NULLARRAY can also be passed as null C pointer.

The array elements returned in MODIFIES and PRODUCES ARRAY parameters are automatically allocated with malloc by RPC. The C client must deallocate the elements by calling dispose_array as described in Section 5.18.5.

5.18.2. connectServer and the _init Function

The C function connectServer, provided in mrpc.o, has the following interface:

int connectServer (serviceName)
char *serviceName;

connectServer accepts a null-terminated string of the form hostName:serviceName or serviceName. If you omit the hostName (which may be a raw internet address for services accessed through TCP/IP; e.g., 192.9.200.5), connectServer looks in the services table for the named service. If the service is supported on a unique host, it connects to the named service on that host.

connectServer returns a descriptor for the service. If the descriptor is nonnegative, it must be passed to the appropriate _init function to initialize a connection with the specified service; if negative, an error occurred and no connection is established.

The _init function for a remote MODULE has the name of the remote MODULE (in lower case) with _init added to it, e.g., foo_init for a remote MODULE FOO. The interface of an _init function is as follows:

MRPCMOD *foo_init(descr,cliver,oldestsrvver,msg)
int descr;
long cliveroldestsrvver;
char *msg;

descr is the descriptor returned from a successful connserver call. If the _init function is successful, it returns a nonzero value (the opaque handle MRPCMOD *) which must be specified as the first parameter to all remote PROCEDURE calls to FOO. If the _init function fails, it returns a zero value. msg is set to an error message if a failure occurs. cliver and oldestsrvver correspond to the cliVersion and oldestSrvVersion parameters to $newRemoteModule; see Section 5.2.

5.18.2.1. connserver
The obsolescent C function connserver, provided in mrpc.o, has the following interface:

int connserver(hostname,servername)
char *hostname,*servername;

The parameters hostname and servername are null-terminated strings that are the name of a host and the name of a service available on that host. connserver("a","b") acts like connectServer("a:b").

New programs should call connectServer instead.

5.18.3. Calling Remote PROCEDUREs

The name of a remote PROCEDURE in C is the MODULE name of the remote MODULE followed by an underscore followed by the MAINSAIL name of the remote PROCEDURE, all converted to lower case; e.g., to call myProc in FOO, the C function name would be foo_myproc. Remote PROCEDUREs return the return value of the remote PROCEDURE; rules for C data types of return values are the same as for PRODUCES parameters. The first argument to the remote PROCEDURE must be the value returned by the remote MODULE's _init function; the subsequent arguments correspond to the MAINSAIL PROCEDURE's arguments.

5.18.4. The _final Function and disconnserver

To close the connection to the remote MODULE, the C program calls a function with the name of the remote MODULE (in lower case) with _final added to it, e.g., foo_final for a remote MODULE FOO. The argument of the _final function is the value returned from the _init function.

After the _final function is called, the function disconnserver must be called for the descriptor returned from the connserver call. It takes a single int parameter:

int disconnserver(descr)
int descr;

It returns 0 on success, nonzero on error.

After disconnserver is called, the program should free the MRPCMOD struct allocated by the _init function.

5.18.5. dispose_array

The function dispose_array can be used to deallocate an array (by calling cfree) if the appropriate bits are set in the ARRAY struct's ary_alloc_status field. The bits and their meanings are:

Bit Meaning
ARY_DSCR_DYN Free the ARRAY struct itself
ARY_DATA_DYN Free the array elements at ary_first_elem
ARY_STRING_DYN If the array type is string, free the text of the strings

dispose_array is declared as follows:

dispose_array(a)
ARRAY *a;

5.18.6. Handling Exceptions in the Remote MODULE

The C setjmp/longjmp mechanism is used to allow a C client to handle MAINSAIL exceptions in the remote MODULE. A jmp_buf value, as returned from setjmp, is passed to rpc_register_jump along with the text of the exception that is to be handled. rpc_register_jump is declared as:

void rpc_register_jump(s,env)
char *s;
jmp_buf env;

A longjmp is made to env when the exception occurs. Before execution falls out of the if part of a C RPC exception handler normally (i.e., not due to an exception that causes a less nested handler to be invoked), the program must call rpc_clear_all(env), where env is the handler's environment argument passed to setjmp:

void rpc_clear_all(env)
jmp_buf env;

This clears all registered exceptions for the handler's environment env and removes the handler from the stack of active handlers. In particular, if the if part contains any conditional logic or statements that transfer control out of the if part, the programmer must be sure that rpc_clear_all is called for all such paths.

More than one exception may be handled by the same environment (i.e., the same jmp_buf may be passed to rpc_register_jump several times to handle several different exceptions in the same place).

The current exception is given by the char pointer function rpc_exception_name; it tells which exception name caused the else part of a handler to be entered. The function takes as an argument the jmp_buf variable env passed to setjmp:

char *rpc_exception_name(env)
jmp_buf env;

rpc_exception_name is guaranteed not to return NULL. If env's else part was not entered or env is invalid, it returns "", which is never a valid exception name.

Here is an example of a C RPC exception handler that catches two exceptions, E1 and E2:

#include <setjmp.h>
char *excptName;
jmp_buf env;

if (! setjmp(env)) {        /* equivalent to MAINSAIL's "$HANDLE" */
    
rpc_register_jump(E1,env);  /* catch exception E1 */
    
rpc_register_jump(E2,env);  /* catch exception E2 */

    
... code normally executed...

    
rpc_clear_all(env);         /* stop catching exceptions */
}
else {                      /* equivalent to MAINSAIL's "$WITH" */
    /* 
When execution gets hereall exceptions registered for this
     * 
handler have been cleared and rpc_exception_name(env)
     * 
contains a copy of the exception that caused this handler to
     * 
be entered.
     */

    
excptName = rpc_exception_name(env);
    
if (! strcmp(excptName,E1)) {

        
... code to handle the exception E1...

    }
    
else if (! strcmp(excptName,E2)) {

        
... code to handle the exception E2...

    }
}

5.18.7. Obsolete Mechanism for Handling Exceptions in the Remote MODULE

The function rpc_clear_jump and the variable ms_exception are obsolete. Do not use them in new code. You should convert existing code to use the newer features of Section 5.18.6; this section is present only to help you understand and convert existing code.

In previous releases, a function rpc_clear_jump was used instead of rpc_clear_all, and a variable ms_exception was used instead of the function rpc_exception_name. These identifiers still exist, but their use will produce incorrect results under some circumstances, so it is best to change any code that uses them immediately. These identifiers may be removed in some future release.

rpc_clear_jump took an exception name instead of a jmp_buf:

rpc_clear_jump(s)
char *s;

ms_exception was a char * variable, not a function like rpc_exception_name;

char *ms_exception;

5.18.8. Sample C RPC Session

The C program of
Example 5–17 is a client that calls the interface PROCEDURE provided by the remote MODULE Example 5–6; it is the C equivalent of the MODULE of Example 5–10. The C program is compiled with the command shown in Example 5–18 on a typical UNIX system. The files <mrpc.h> and mrpc.o reside on the MAINSAIL directory, for which the environment variable $MS has been defined. The files foocli.c and foocli.h were produced by the C RPC compilation shown in Example 5–16, in which foo.msl contains the remote MODULE FOO of Example 5–6.

Appendix D shows a C client that calls remote PROCEDUREs with a greater range of parameter types to demonstrate the correspondence between C client arguments and MAINSAIL server parameters.

Example 5–16. Compilation of Remote MODULE with RPC C Compiler Subcommand

MAINSAIL (R) Compiler
Copyright (c) 1984-1998 by XIDAK, Inc., Point Arena,
 California, USA.


compile (? for help): foo.msl,<eol>
rpc c<eol>
<eol>
Opening intmod for $SYS...

foo.msl
Opening intmod for RPCHDR...
...
Opening intmod for $SYS...
Client for FOO stored on foocli.c
Header for FOO stored on foocli.h
Intmod for FOO not stored

compile (? for help):

Example 5–17. C RPC Client in foo.c
#include <mrpc.h>
#
include "foocli.h"

MRPCMOD *rem;

main (argc,argv)
int argc;
char *argv[];
{
int descr;
char hostAndServ[100],emsg[100];
char *hostAndServSchar *s;

if (argc <= 1) {
    
printf("Host:server: "); gets(hostAndServ);
    
hostAndServS = hostAndServ; }
else { hostAndServS = argv[1]; }

if ((descr = connectServer(hostAndServS)) < 0) exit(1);

if (! (rem = foo_init(descr,4,0,emsg))) {
    
printf("Could not init: %s\n",emsg);
    
exit(1);
    }

s = foo_hello(rem,"xxx");
...
cfree(s);

foo_final(rem); disconnserver(descr); free(rem);
exit(0);
}

Example 5–18. Compiling the C Client on a Typical UNIX System
cc -I$MS -o foo foo.c foocli.c $MS/mrpc.o<eol>

5.18.9. Building a Sharable Object for mrpc.o

Many UNIX platforms now allow C programs to be compiled into “shared objects”, which can be loaded at runtime and shared among many different processes.

The default version of the C MPRC file mrpc.o is not a shared object. However, if you wish to build a shared object to use in place of mrpc.o, the C source files mrpcpi.c and mrpcgd.c, which were compiled to produce mrpc.o, reside on the MAINSAIL directory. The file mrpcpi.c contains no initialized global data, and is therefore suitable for compilation to produce position-independent code on systems where the C compiler requires that C files to be compiled into position-independent code contain no initialized global data. mrpcgd.c contains the initialized global data used by mrpcpi.c.

The names of the files and their locations may change in the future. XIDAK may, in some future release, start shipping a version of mrpc.o already compiled as a shared object, in which case source files will no longer be available.


previous   next   top   contents   index   framed top   this page unframed

MAINSAIL STREAMS User's Guide, Chapter 5