previous next top contents index framed top this page unframed
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.
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. Figure 5–2. Remote MODULE PROCEDUREs
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($stream) st;
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($stream) st;
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($remoteModuleCls) xxx ( # 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); ); |
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
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.
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($rpcBufferMgrCls) bufferMgrModule; # 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($rpcBufferMgrCls) p);
BOOLEAN PROCEDURE rpcNewBuffer (
PRODUCES CHARADR buffer;
LONG INTEGER bufSize;
OPTIONAL POINTER($rpcBufferMgrCls) p);
PROCEDURE rpcDisposeBuffer (
MODIFIES CHARADR buffer;
LONG INTEGER bufSize;
OPTIONAL POINTER($rpcBufferMgrCls) p);
PROCEDURE rpcDisposeBuffer (
MODIFIES ADDRESS buffer;
LONG INTEGER bufSize;
OPTIONAL POINTER($rpcBufferMgrCls) p);
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($remoteModuleCls) foo ( 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($remoteModuleCls) fooCls ( STRING PROCEDURE hello (STRING s); ); POINTER($stream) chTty; POINTER($coroutine) rc, wc; PROCEDURE reader; BEGIN STRING s; WHILE $success($readStream(chTty,s,errorOK)) DO $writeStream($tty,s,$line); IF wc AND NOT $killedCoroutine(wc) THEN $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(rc) THEN $killCoroutine(rc); $reschedule(delete); END; INITIAL PROCEDURE; BEGIN STRING s; POINTER(fooCls) m; POINTER($stream) chCtl; $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($remoteModuleCls) fooCls ( 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($remoteModuleCls) foo ( 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($remoteModuleCls) fooCls ( STRING PROCEDURE hello (STRING s); ); INITIAL PROCEDURE; BEGIN STRING s; POINTER(fooCls) m; $initializeStreams; # Ask for version 4L of foo m := $newRemoteModule("service>foo","foo",4L); s := m.hello("Mr. Smith"); ... dispose(m); END; END "client" |
Example 5–11. Example Server Providing the FOO Remote MODULE
| BEGIN "server" RESTOREFROM "rpcHdr"; CLASS($remoteModuleCls) fooCls ( 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. Figure 5–12. User-Visible Fields of $remoteModuleCls
5.8. The 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 these, but you must not have an
# outer declaration of the same identifier, or 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 Started, RPC 1 MAINSAIL 11.20
29-Feb-88 14:21:
Client: bob@Poseidon.1 foo 5 3 29-Feb-88 14:21
29-Feb-88 14:32:
Finish: bob@Poseidon.1 foo 5 3 29-Feb-88 14:21
$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");
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.
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
Arrays are passed as pointers to
a C struct ARRAY typedefed in <mrpc.h> as:
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.
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.
typedef struct mainsail_array_descr {
char *ary_first_elem;
long ary_lb1,ary_ub1,
ary_lb2,ary_ub2,
ary_lb3,ary_ub3;
short ary_dims, ary_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 cliver, oldestsrvver;
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;
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 here, all 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...
}
}
| 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;
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 *hostAndServS; char *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> |
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.
MAINSAIL STREAMS User's Guide, Chapter 5