12.4 The Foreign Include File
AllApplicationManualNameSummaryHelp

  • Documentation
    • Reference manual
      • Foreign Language Interface
        • The Foreign Include File
          • Argument Passing and Control
          • Atoms and functors
          • Analysing Terms via the Foreign Interface
          • Constructing Terms
          • Unifying data
          • Convenient functions to generate Prolog exceptions
          • Foreign language wrapper support functions
          • Serializing and deserializing Prolog terms
          • BLOBS: Using atoms to store arbitrary binary data
          • Exchanging GMP numbers
          • Calling Prolog from C
          • Discarding Data
          • String buffering
          • Foreign Code and Modules
          • Prolog exceptions in foreign code
          • Catching Signals (Software Interrupts)
          • Miscellaneous
          • Errors and warnings
          • Environment Control from Foreign Code
          • Querying Prolog
          • Registering Foreign Predicates
          • Foreign Code Hooks
          • Storing foreign data
          • Embedding SWI-Prolog in other applications
            • PL_initialise()
            • PL_winitialise()
            • PL_is_initialised()
            • PL_set_resource_db_mem()
            • PL_toplevel()
            • PL_cleanup()
            • PL_cleanup_fork()
            • PL_halt()
            • Threading, Signals and embedded Prolog
    • Packages

12.4.24 Embedding SWI-Prolog in other applications

With embedded Prolog we refer to the situation where theā€˜main' program is not the Prolog application. Prolog is sometimes embedded in C, C++, Java or other languages to provide logic based services in a larger application. Embedding loads the Prolog engine as a library to the external language. Prolog itself only provides for embedding in the C language (compatible with C++). Embedding in Java is achieved using JPL using a C-glue between the Java and Prolog C interfaces.

The most simple embedded program is below. The interface function PL_initialise() must be called before any of the other SWI-Prolog foreign language functions described in this chapter, except for PL_initialise_hook(), PL_new_atom(), PL_new_functor() and PL_register_foreign(). PL_initialise() interprets all the command line arguments, except for the -t toplevel flag that is interpreted by PL_toplevel().

int
main(int argc, char **argv)
{ if ( !PL_initialise(argc, argv) )
    PL_halt(1);

  PL_halt(PL_toplevel() ? 0 : 1);
}
int PL_initialise(int argc, char **argv)
Initialises the SWI-Prolog heap and stacks, restores the Prolog state, loads the system and personal initialisation files, runs the initialization/1 hooks and finally runs the initialization goals registered using -g goal.

Special consideration is required for argv[0]. On Unix, this argument passes the part of the command line that is used to locate the executable. Prolog uses this to find the file holding the running executable. The Windows version uses this to find a module of the running executable. If the specified module cannot be found, it tries the module libswipl.dll, containing the Prolog runtime kernel. In all these cases, the resulting file is used for two purposes:

  • See whether a Prolog saved state is appended to the file. If this is the case, this state will be loaded instead of the default boot.prc file from the SWI-Prolog home directory. See also qsave_program/[1,2] and section 12.5.
  • Find the Prolog home directory. This process is described in detail in section 12.6.

PL_initialise() returns 1 if all initialisation succeeded and 0 otherwise.bugVarious fatal errors may cause PL_initialise() to call PL_halt(1), preventing it from returning at all.

In most cases, argc and argv will be passed from the main program. It is allowed to create your own argument vector, provided argv[0] is constructed according to the rules above. For example:

int
main(int argc, char **argv)
{ char *av[10];
  int ac = 0;

  av[ac++] = argv[0];
  av[ac++] = "-x";
  av[ac++] = "mystate";
  av[ac]   = NULL;

  if ( !PL_initialise(ac, av) )
    PL_halt(1);
  ...
}

Please note that the passed argument vector may be referred from Prolog at any time and should therefore be valid as long as the Prolog engine is used.

A good setup in Windows is to add SWI-Prolog's bin directory to your PATH and either pass a module holding a saved state, or "libswipl.dll" as argv[0]. If the Prolog state is attached to a DLL (see the -dll option of swipl-ld), pass the name of this DLL.

int PL_winitialise(int argc, wchar_t **argv)
Wide character version of PL_initialise(). Can be used in Windows combined with the wmain() entry point.
int PL_is_initialised(int *argc, char ***argv)
Test whether the Prolog engine is already initialised. Returns FALSE if Prolog is not initialised and TRUE otherwise. If the engine is initialised and argc is not NULL, the argument count used with PL_initialise() is stored in argc. Same for the argument vector argv.
int PL_set_resource_db_mem(const unsigned char *data, size_t size)
This function must be called at most once and before calling PL_initialise(). The memory area designated by data and size must contain the resource data and be in the format as produced by qsave_program/2. The memory area is accessed by PL_initialise() as well as calls to open_resource/3.223This implies that the data must remain accessible during the lifetime of the process if open_resource/3 is used. Future versions may provide a function to detach the resource database and cause open_resource/3 to raise an exception.

For example, we can include the bootstrap data into an embedded executable using the steps below. The advantage of this approach is that it is fully supported by any OS and you obtain a single file executable.

  1. Create a saved state using qsave_program/2 or
    % swipl -o state -c file.pl ...

  2. Create a C source file from the state using e.g., the Unix utility xxd(1):
    % xxd -i state > state.h

  3. Embed Prolog as in the example below. Instead of calling the toplevel you probably want to call your application code.
    #include <SWI-Prolog.h>
    #include "state.h"
    
    int
    main(int argc, char **argv)
    { if ( !PL_set_resource_db_mem(state, state_len) ||
           !PL_initialise(argc, argv) )
        PL_halt(1);
    
      return PL_toplevel();
    }

Alternative to xxd, it is possible to use inline assembler, e.g. the gcc incbin instruction. Code for gcc was provided by Roberto Bagnara on the SWI-Prolog mailinglist. Given the state in a file state, create the following assembler program:

        .globl _state
        .globl _state_end
_state:
        .incbin "state"
_state_end:

Now include this as follows:

#include <SWI-Prolog.h>

#if __linux
#define STATE _state
#define STATE_END _state_end
#else
#define STATE state
#define STATE_END state_end
#endif

extern unsigned char STATE[];
extern unsigned char STATE_END[];

int
main(int argc, char **argv)
{ if ( !PL_set_resource_db_mem(STATE, STATE_END - STATE) ||
       !PL_initialise(argc, argv) )
    PL_halt(1);
  return PL_toplevel();
}

As Jose Morales pointed at https://github.com/graphitemaster/incbin, which contains a portability layer on top of the above idea.

int PL_toplevel()
Runs the goal of the -t toplevel switch (default prolog/0) and returns 1 if successful, 0 otherwise.
int PL_cleanup(int status_and_flags)
This function may be called instead of PL_halt() to cleanup Prolog without exitting the process. It performs the reverse of PL_initialise(). It runs the PL_on_halt() and at_halt/1 handlers, closes all streams (except for the standard I/O streams, which are flushed only), restores all signal handlers and reclaims all memory unless asked not to. status_and_flags accepts the following flags:
PL_CLEANUP_NO_RECLAIM_MEMORY
Do not reclaim memory. This is the default when called from PL_halt() for the release versions because the OS will do so anyway.
PL_CLEANUP_NO_CANCEL
Do not allow Prolog and foreign halt hooks to cancel the cleanup.

The return value of PL_cleanup() is one of the following:

PL_CLEANUP_CANCELED
A Prolog or foreign halt hook cancelled the cleanup. Note that some of the halt hooks may have been executed.
PL_CLEANUP_SUCCESS
Cleanup completed successfully. Unless PL_CLEANUP_NO_RECLAIM_MEMORY was specified this implies most of the memory was reclaimed and Prolog may be reinitialized in the same process using PL_initialise().
PL_CLEANUP_FAILED
Cleanup failed. This happens if the user requested to reclaim all memory but this failed because the system was not able to join all Prolog threads and could therefore not reclaim the memory.
PL_CLEANUP_RECURSIVE
PL_cleanup() was called from a hook called by the cleanup process.

PL_cleanup() allows deleting and restarting the Prolog system in the same process. In versions older than 8.5.9 this did not work. As of version 8.5.9, it works for the basic Prolog engine. Many of the plugins that contain foreign code do not implement a suitable uninstall handler and will leak memory and possibly other resources. Note that shutting Prolog down and renitializing it is slow. For almost all scenarios there are faster alternatives such as reloading modified code, using temporary modules, using threads, etc.

void PL_cleanup_fork()
Stop intervaltimer that may be running on behalf of profile/1. The call is intended to be used in combination with fork():
    if ( (pid=fork()) == 0 )
    { PL_cleanup_fork();
      <some exec variation>
    }

The call behaves the same on Windows, though there is probably no meaningful application.

int PL_halt(int status)
Clean up the Prolog environment using PL_cleanup() and if successful call exit() with the status argument. Returns FALSE if exit was cancelled by PL_cleanup().

12.4.24.1 Threading, Signals and embedded Prolog

This section applies to Unix-based environments that have signals or multithreading. The Windows version is compiled for multithreading, and Windows lacks proper signals.

We can distinguish two classes of embedded executables. There are small C/C++ programs that act as an interfacing layer around Prolog. Most of these programs can be replaced using the normal Prolog executable extended with a dynamically loaded foreign extension and in most cases this is the preferred route. In other cases, Prolog is embedded in a complex application that---like Prolog---wants to control the process environment. A good example is Java. Embedding Prolog is generally the only way to get these environments together in one process image. Java VMs, however, are by nature multithreaded and appear to do signal handling (software interrupts).

On Unix systems, SWI-Prolog installs handlers for the following signals:

SIGUSR2
has an empty signal handler. This signal is sent to a thread after sending a thread-signal (see thread_signal/2). It causes blocking system calls to return with EINTR, which gives them the opportunity to react to thread-signals.

In some cases the embedded system and SWI-Prolog may both use SIGUSR2 without conflict. If the embedded system redefines SIGUSR2 with a handler that runs quickly and no harm is done in the embedded system due to spurious wakeup when initiated from Prolog, there is no problem. If SWI-Prolog is initialised after the embedded system it will call the handler set by the embedded system and the same conditions as above apply. SWI-Prolog's handler is a simple function only chaining a possibly previously registered handler. SWI-Prolog can handle spurious SIGUSR2 signals.

SIGINT
is used by the top level to activate the tracer (typically bound to control-C). The first control-C posts a request for starting the tracer in a safe, synchronous fashion. If control-C is hit again before the safe route is executed, it prompts the user whether or not a forced interrupt is desired.
SIGTERM, SIGABRT and SIGQUIT
are caught to cleanup before killing the process again using the same signal.
SIGSEGV, SIGILL, SIGBUS, SIGFPE and SIGSYS
are caught by to print a backtrace before killing the process again using the same signal.
SIGHUP
is caught and causes the process to exit with status 2 after cleanup.

The --no-signals option can be used to inhibit all signal processing except for SIGUSR2. The handling of SIGUSR2 is vital for dealing with blocking system call in threads. The used signal may be changed using the --sigalert=NUM option or disabled using --sigalert=0.