Quintus Prolog Manual
Quintus recommends that if you are just starting out, do NOT use this package. The TCP package is much faster and more powerful. In releases prior to Quintus Prolog Release 2.5, this package was simply know as IPC. It is now called IPC/RPC to distinguish it from another interprocess communication package which is called IPC/TCP. That package is more general than this one since its facilities can be used to implement the functionality of this package. This package has some interesting facilities for calling a prolog servant from C. Before Release 3.0, this was the only way to call prolog from C. Now prolog can be fully embedded in a C application. This Interprocess Communication (IPC) package provides tools for allowing programs written in Prolog or C to remotely call predicates in a Prolog program which is running as a separate process, possibly on a different machine. The communication between the processes is implemented using sockets or pipes to send goals to the remote process and to retrieve answers back. We refer to the Prolog process that is being invoked by some other program as a servant, because it provides a goal evaluation service at the request of another program: it is given a goal to invoke, invokes it, and then returns the answers to the caller. It is called a servant, as opposed to a server, because it serves a single master. We refer to the program that is calling the servant as the master. The interface described here permits a program (the master) to call one servant and use it to evaluate many subgoals. The characteristics of the interface vary somewhat with the programming language of the master. If the master is itself a Prolog program, then the interface can be much more flexible than when the calling program is written in a procedural language, such as C. We divide the description of the interface into two parts: (1) when the master program is written in Prolog, and (2) when the master program is written in C. Although only C calling Prolog is documented, other languages can also call Prolog if they adhere to the specified protocol. NOTE: On System V versions of UNIX or VMS, the master and server processes are currently required to run on the same machine, and the communication is via pipes rather than sockets. On UNIX systems based on BSD, such as SunOS, the user can choose to use either pipes or sockets, provided that the two processes are on the same machine. Sockets must be used when the processes are on separate machines. When sockets are used, there needs to be an entry in the '/etc/hosts' file(s) for each machine that is used.
When the master is a Prolog program, a very flexible interface is supported because the nondeterminism of the two Prolog programs can be combined. Also, general Prolog data structures can be passed between the programs easily, since both programs support the same data types. Using this interface, a complex Prolog system can achieve significant parallel evaluation, by using a servant on another processor and communicating over a network. The routines described below allow a master to have only a single servant process. (They could be extended without much difficulty to support multiple servants and servants being masters of other servants, if that proves important.) There are two sides to any interface: here we have the calling Prolog program (the master), and the called Prolog program (the servant). Each must perform certain functions which allow them to cooperate. For a master to use a servant, the master must first create it. This is done by starting a Prolog process that will be the servant. The system creates that process by running a saved state previously created by the programmer. After the servant has been created and is running, the master may send it goals to evaluate using call_servant/1 and bag_of_all_servant/3. All goals sent to the servant are evaluated in the database of the servant, which is disjoint from the database of the master. This means that all programs that the servant will execute must either already be in the saved state that was initially loaded, or a goal must be sent to the servant telling it to compile (or consult) the appropriate files. One could also use remote call to have the servant evaluate an assert/1. For an example of using a Prolog servant from Prolog, see the RPC demo in the IPC subdirectory of the demo directory:
quintus('generic/q3.3/demo')
All the following predicates are defined in the module 'qpcallqp'. To be able to use them, the master program must first load them by entering the directive:
:- use_module(library(qpcallqp)).
To be able to call a servant, you must first create (using save_servant/1) a saved state that is to be run as the servant. Run Prolog on the machine on which the servant is to be run, and load (that is, compile, consult, or assert) everything that the servant will need. Then call save_servant(SavedState), where SavedState is the name of the file in which to save the state. (This saved state should not normally be started directly from a terminal by a user; when started it will automatically try to open and read a socket.)
Before a master can use a servant, the servant must first be started up and connections to it must be made. This is done by a call to create_servant/[2,3]. Machine is the name of the machine on which to run the servant. If Machine is omitted, or set to the null atom '', the servant is run on the same machine, and communication is via pipes. If Machine is the atom local, the servant is run on the same machine but communication is via sockets. If Machine names another machine, communication will be via sockets. You need to be able to use 'rsh' on that machine. SavedState is the name of the file that contains the Prolog saved state on that machine. It must have been previously created with save_servant/1. If SavedState is not an absolute file name, it will be sought, on the specified machine, in the sequence of directories specified by your PATH environment variable. If this search fails, the current working directory will be tried, if it exis on that machine. OutFile is the name of the file to which output from the servant will be written. This file is on the local machine and it will be created if it does not already exist. This file should be examined if there are problems with the communication to the servant. Tracing information (if any, see {manual(j-2-3)}) will also be written to this file. If OutFile is the atom 'user', then all output will be sent to the standard output stream of the master.
Once a servant has been created (by create_servant/2), goals can be sent to it for evaluation, by using call_servant(Goal). This sends the goal Goal to the servant, which evaluates it (with respect to its own database) and sends all the answers back. The answers are returned as solutions of call_servant/1. The answers bind the variables in Goal. Answers after the first are obtained by backtracking into call_servant/1. Note that the servant computes and sends all answers back to the master, even if the caller uses a cut to throw away all but the first.
If the servant is running on a different physical processor than the master, then it is desirable to be able to achieve some degree of parallelism, to have both machines doing useful work at the same time. This is not the case with call_servant/1, since the master Prolog process is waiting the entire time that the servant process is computing. The predicate bag_of_all_servant/3 is provided to allow a sophisticated user to write some truly parallel applications. (See the demo program 'queensdemo' for an example of using parallelism in a search problem.) Semantically bag_of_all_servant/3 is very similar to bagof/3. The reader should be familiar with the operation of bagof/3 before reading further. The differences are:
The exact operation of bag_of_all_servant/3 depends on the form of Goal. If Goal is a conjunction of the form (Goal1, Goal2) or a disjunction of the form (Goal1; Goal2), then the first subgoal (Goal1) will be executed by the servant, and the second subgoal (Goal2) will be executed by the current process. The system will try to overlap local and remote evaluation as much as possible. If Goal is neither a conjunction nor a disjunction, then the entire goal will be sent to the servant to be executed. There are several restrictions on how bag_of_all_servant/3 can be used.
This is just like bag_of_all_servant except that duplicates are removed in th returned list.
There are occasions in which the communications between the master and the servant can get out of sync, in particular if the user generates an interrupt and then aborts a process in the middle of a remote goal service. In this case, the goal reset_servant attempts to return the servant to the top level and to flush the socket .
To close down a servant when it is no longer needed (or to reinitialize it or to connect to another servant with a different database), use shutdown_servant/0. This terminates the servant process.
The support for calling a Prolog goal from a C program consists of Prolog predicates and C functions. The Prolog predicates allow you to create a saved state which will be the servant. The C functions are used by your C program to create the Prolog process and communicate with it. Before a C program can call a Prolog program, you must first create a Prolog saved state in which all the predicates to be called are defined. The saved state must also define the characteristics of the interface to each of the predicates to be called. Once this saved state is created, a C program can call the Prolog predicates by using C functions supplied by Quintus. We first describe how to create the Prolog saved state. After that we describe how the C program calls a Prolog predicate.
:- use_module(library(ccallqp)).
The interface for calling a Prolog program from a C program is strictly typed. In the Prolog servant program, the user must declare which Prolog procedures can be called from the C program, the types of the data elements to be passed between them, and the direction the elements are to be sent. This is done in Prolog by defining external/3 facts to provide this information. These facts are very similar to those for foreign/3 and have the following form:
external(CommandName, Protocol, PredicateSpecification).
CommandName is the name by which the C program invokes this predicate. Protocol is the protocol to be used, which currently must be 'xdr'. PredicateSpecification is a term that describes the Prolog predicate and the interface, and is of the form:
PredicateName(ArgSpec1, ArgSpec2, ...)
PredicateName is the name of the Prolog predicate (an atom). There is an ArgSpec for each argument of the predicate, and ArgSpec is one of:
+integer +float +atom +string -integer -float -atom -string
Examples:
external(add, xdr, addtwoints(+integer,+integer,-integer)). external(ancestor, xdr, ancestor(+string,-string)). /* Define addtwoints/3 for use by C caller. */ addtwoints(X, Y, Z) :- Z is X+Y. /* Define ancestor/2 for use by C caller */ ancestor(X, Y) :- parent(X, Y). ancestor(X, Y) :- parent(X, Z), ancestor(Z, Y).
The interface allows the simple Prolog data types (atoms, integers, and floating-point numbers) to be passed to and from a calling C program. The '+' annotation on an argument specification means that the corresponding value will be passed from the calling C program to the called Prolog predicate. A '-' annotation means that the value will be passed from the Prolog predicate back to the calling C program. The '+' and '-' annotations are always from the point of view of the master (or caller). In this case the C program is the master. The argument specifications have the same meanings as they do in foreign/3 facts, but note the directions implied by '+' and '-'. Also note that the [...] specifications are not allowed. The limitations on the sizes of integers, floats, and strings in Prolog are the same as for the interface to foreign routines. The values passed as 'atom' arguments will be treated as long unsigned integers in the C program. Their uses must be restricted to the same invocation of the Prolog servant. These long integers can be converted to and from the associated strings by using the C functions QP_ipc_atom_from_string and QP_ipc_string_from_atom below.
To be able to call a servant from C, you must first have created a saved state that will run as the servant. This is done using save_ipc_servant/1. Run Prolog on the machine on which the servant is to be run, and load (that is, compile, consult, or assert) everything that the servant will need. This includes all the external/3 facts that define the interface, as well as the predicates that the C program will call. Then call save_ipc_servant(SavedState), where SavedState is the name of the file in which to save the state.
After the saved state containing the Prolog predicates and the interface declarations has been created, a C program can access those predicates by using the C functions described in the following sections. Your C program that uses these functions should #include the file to include the extern definitions. The final "ld" step that creates the main ex This file contains the C object code that implements the C functions you will be using, and was create Recall that RPC is a subdirectory of
'qplib/IPC'
See the RPC demo for an example.
int QP_ipc_create_servant(host, QP_save_state, QP_outfile)
char *host, *QP_save_state, *QP_outfile;
host is the name of the machine on which the Prolog servant is to run. If host is the string "local", then the servant is run on the same machine as the master. If host is the empty string "", the servant is run on the local machine but pipes are used for the interprocess communication rather than sockets whi are used otherwise. QP_save_state is the name of a file containing a Prolog saved state that was created by save_ipc_servant/1. This saved state must contain the definitions of the predicates to be called and the external/3 facts that specify the interface. If QP_save_state is not an absolute file name, it will be sought, on the specified machine, in the sequence of directories specified by your PATH environment variable. If this search fails, the current working directory will be tried, if it exis on that machine. QP_outfile is the name of the file to which to route the Prolog servant's output (stdout and stderr). If QP_outfile is the string "user", then the servant's output is routed to the C program's stdout. If it is the empty string "", the servant's stdout stream is discarded, and its stderr stream is routed to the master's stderr. This routine returns the file descriptor of the connecting socket if the connection was made successfully and -1 if not. This routine starts the Prol servant and may take several seconds to complete.
int QP_ipc_lookup(name)
char *name;
This routine finds and returns the command number associated with the external routine name. The command number is a nonnegative integer. The associated command must have appeared as the first field in an 'external' declaration in the saved state started by the previous QP_ipc_create_servant call. If the command is not found, -1 is returned.
int QP_ipc_prepare(command, arg1, ..., argn)
int command;
va_dcl
This function sends a request to the Prolog process to evaluate a goal. The command is identified by command, which must have been obtained by an earlier call to QP_ipc_lookup. The arguments are the values to be sent to the command, that is those in the interface specification with a '+' annotation. They must be in left-to-right order as they appear in the specification. They must be of the corresponding types as indicated in the specification:
+integer: int
+float: float
+atom: QP_atom
+string: char *
See the example below in {manual(j-2-2-12)}. On successful completion, this routine returns 0. If an error has occurred, it returns -1.
int QP_ipc_next(command, arg1, ..., argm)
int command;
va_dcl
The C routine QP_ipc_next retrieves an answer for a goal (command) that was initiated by a previous call to QP_ipc_prepare. The command is identified by command. It must be the same as the command given to QP_ipc_prepare. The remaining arguments are variables that will be set by QP_ipc_next to the values returned as the next answer to the goal. There is one argument for each field in the interface specification that was annotated with '-'. They must be in left-to-right order. The types of the arguments must correspond to the types declared in the external/3 specification as follows:
-integer: int *
-float: float *
-atom: QP_atom *
-string: char **
For a returned value of type string, space must be provided by the calling routine to hold the value returned. The characters of the returned string will be copied over the string passed in. If an(other) answer to the query has been obtained and the argument parameters have been set accordingly, QP_ipc_next returns 0. If there are no more answers, it returns -1. If there is an error, it returns -2. See the example below in {manual(j-2-2-12)}.
int QP_ipc_close()
The QP_ipc_close routine closes a query that was opened by QP_ipc_prepare but did not have all of its answers retrieved by calls to QP_ipc_next. (When QP_ipc_next returns a -1, indicating no more answers, the query is automatically closed, and a subsequent call to QP_ipc_close is an error.) QP_ipc_close returns 0 if it has closed the query successfully, and -1 if there is an error.
int QP_ipc_shutdown_servant()
The routine QP_ipc_shutdown_servant shuts down the servant. It sends a message to the servant that causes it to terminate. It returns 0 if the shutdown is successful, and -1 if there is some problem.
QP_atom QP_ipc_atom_from_string(str)
char *str;
The routine QP_ipc_atom_from_string returns the unsigned integer that is the Prolog representation of the atom with the name str. This representation is valid for the lifetime of the servant. It must not be saved and used with a different invocation of the servant.
void QP_ipc_string_from_atom(atom, str)
QP_atom atom;
char *str;
The routine QP_ipc_string_from_atom can be used to get the name corresponding to an atom, given the Prolog internal unsigned integer representation of the atom. The string str will be overwritten with a null-terminated string that is the name of the atom. The caller must provide enough space to contain this string.
The first example shows how you can package a call to a Prolog goal that is known to be deterministic. Here, the C function 'fred' hides the call to Prolog. However, the servant must be initiated by a call to QP_ipc_create_servant before it can be called.
Example 1.
Prolog Specification:
external(fred, xdr, fred(+integer,-integer,+integer)).
fred(X, Y, Z) :- ....
C routine:
int fred(i, j)
int i, j;
{
static int fredp = -1;
int k;
if (fredp < 0) { /* initialize */
fredp = QP_ipc_lookup("fred");
if (fredp < 0)
DieBecause("couldn't find fred");
}
/* send the request */
QP_ipc_prepare(fredp, i, j);
/* get the answer back */
if (QP_ipc_next(fredp, &k))
DieBecause("fred failed");
/* known deterministic, so close request */
QP_ipc_close();
/* return the answer */
return k;
}
The second example shows an entire program and how all types of arguments are be passed. It also shows how QP_ipc_atom_from_string and QP_ipc_string_from_atom can be used. In terms of functionality, this is not a very interesting program, and the conversion between atoms and strings is just to give an example.
Example 2.
Prolog Specification:
external(dupl, xdr, duplicate(-integer,+integer,-string,+string,
-float,+float,-atom,+atom)).
duplicate(A, A, B, B, C, C, D, D).
C program:
main()
{
int pdupl;
char host[20], savestate[50];
int iint, oint;
char istr1[20], istr2[20], ostr1[20], ostr2[20];
float iflt, oflt;
QP_atom iatom, oatom;
printf("Enter host and savestate: ");
scanf("%s%s", host, savestate);
if (QP_ipc_create_servant(host,savestate,"servant_out"))
DieBecause("Error starting up servant");
pdupl = QP_ipc_lookup("dupl");
if (pdupl < 0) DieBecause("dupl not defined");
for (;;) { /* loop until break */
printf("Enter int, str, flt, str: ");
if (scanf("%d%s%f%s",&iint,istr1,&iflt,istr2) != 4)
break;
/* get atom for the string typed in */
iatom = QP_ipc_atom_from_string(istr2);
/* send the request */
if (QP_ipc_prepare(pdupl, iint, istr1, iflt, iatom))
DieBecause("dupl prepare error");
/* get answer back, and convert atom back to string */
QP_ipc_next(pdupl, &oint, ostr1, &oflt, &oatom);
QP_ipc_string_from_atom(oatom, ostr2);
/* close request because we want only one answer */
if (QP_ipc_close()) printf("ERROR closing\n");
printf("Answer is: %d %s %G %s(%d)\n",
oint, ostr1, oflt, ostr2, oatom);
}
if (QP_ipc_shutdown_servant())
DieBecause("Error shutting down servant");
}
The third example shows how to retrieve multiple answers:
Example 3.
Prolog Specification:
external(table, xdr, table(-string,-integer)).
table(samuel, 34).
table(sarah, 54).
....
C program:
main()
{
char host[20], savestate[50];
int ptable, ret;
char strval[40];
int intval;
printf("Enter host and savestate: ");
scanf("%s%s", host, savestate);
if (QP_ipc_create_servant(host,savestate,"servant_out"))
DieBecause("Error starting up servant");
ptable = QP_ipc_lookup("table");
if (ptable < 0) {
printf("table not defined\n");
return;
}
/* send the request */
QP_ipc_prepare(ptable);
/* retrieve and print ALL answers */
while (!(ret = QP_ipc_next(ptable, strval, &intval)))
printf("String: %s, Integer: %d\n", strval,intval);
/* note no close, since we retrieved all the answers! */
if (ret == -1) printf("All answers retrieved\n");
else printf("Error retrieving answers\n");
if (QP_ipc_shutdown_servant())
DieBecause("Error shutting down servant");
}
The final example shows how one could write a C function to turn Prolog's message tracing (see {manual(j-2-3)}) on and off.
Example 4.
Prolog Specification:
external(settrace, xdr, settrace(+string)).
settrace(X) :- msg_trace(_,X).
C routine:
void settrace(OnOff)
char *OnOff;
{
static int psettrace = -1;
int k;
if (psettrace < 0) {
psettrace = QP_ipc_lookup("settrace");
if (psettrace < 0)
DieBecause("couldn't find settrace");
}
QP_ipc_prepare(psettrace, OnOff);
if (QP_ipc_next(psettrace))
DieBecause("settrace failed");
QP_ipc_close();
}
A simple tracing facility is available for determining what messages are received by and sent from the Prolog servant. When message tracing is on, messages sent or received cause a trace message to be written to the current output stream. It will normally be redirected to a file by create_servant/3 or QP_ipc_create_servant. The UNIX command 'tail -f' may be helpful in looking at the trace messages. Each trace message indicates what the corresponding interprocess message was. The precise form of the trace information depends on whether the Prolog servant is servin a C program or another Prolog program. Message tracing can be turned on and off by having the servant process call msg_trace/2 which is described below. A master that is a Prolog process can use call_servant/1 to cause the servant to call msg_trace/2. It can also call msg_trace/2 directly to control tracing of its own messages. To make a servant that serves a C master trace its message, it must either have had tracing turned on before its saved state was created, or it must provide an 'external' routine that can be invoked by the C master to turn on tracing (see Example 4 in {manual(j-2-2-12)}).
The predicate msg_trace/2 returns the current value of the message-trace flag ('on' or 'off') and resets its value. The message-trace flag has one of the values 'on' or 'off'. OldValue is bound to the previous value of the flag, and the flag is reset to the value of OnOrOff, which must be either 'on' or 'off'. The call msg_trace(X,X) returns the current value without changing it. When the message-trace flag is on, messages to and from the servant are traced. By executing call_servant(msg_trace(_,on)), tracing can be turned on in the servant.
If the Prolog master process is interrupted while it is waiting for an answer from the servant process, the master process may crash.