Previous Up Next

10.3  Calling C from Prolog

10.3.1  Introduction

This interface can then be used to write both simple and complex C routines. A simple routine uses either input or output arguments which type is simple. In that case the user does not need any knowledge of Prolog data structures since all Prolog ↔ C data conversions are implicitly achieved. To manipulate complex terms (lists, structures) a set of functions is provided. Finally it is also possible to write non-deterministic C code.

10.3.2  foreign/2 directive

foreign/2 directive (section 7.1.15) declares a C function interface. The general form is foreign(Template, Options) which defines an interface predicate whose prototype is Template according to the options given by Options. Template is a callable term specifying the type/mode of each argument of the associated Prolog predicate.

Foreign options: Options is a list of foreign options. If this list contains contradictory options, the rightmost option is the one which applies. Possible options are:

foreign(Template) is equivalent to foreign(Template, []).

Foreign modes and types: each argument of Template specifies the foreign mode and type of the corresponding argument. This information is used to check the type of effective arguments at run-time and to perform Prolog ↔ C data conversions. Each argument of Template is formed with a mode symbol followed by a type name. Possible foreign modes are:

Possible foreign types are:

Foreign typeProlog typeC typeDescription of the C type
integerintegerPlLongvalue of the integer
positivepositive integerPlLongvalue of the integer
floatfloating point numberdoublevalue of the floating point number
numbernumberdoublevalue of the number
atomatomPlLonginternal key of the atom
booleanbooleanPlLongvalue of the boolean (0=false, 1=true)
charcharacterPlLongvalue of (the code of) the character
codecharacter codePlLongvalue of the character-code
bytebytePlLongvalue of the byte
in_charin-characterPlLongvalue of the character or -1 for end-of-file
in_codein-character codePlLongvalue of the character-code or -1 for end-of-file
in_bytein-bytePlLongvalue of the byte or -1 for the end-of-file
stringatomchar *C string containing the name of the atom
charscharacter listchar *C string containing the characters of the list
codescharacter-code listchar *C string containing the characters of the list
termProlog termPlTermgeneric Prolog term

Simple foreign type: a simple type is any foreign type listed in the above tabled except term. A simple foreign type is an atomic term (character and character-code lists are in fact lists of constants). Each simple foreign type is converted to/from a C type to simplify the writing of the C function.

Complex foreign type: type foreign type term refers to any Prolog term (e.g. lists, structures…). When such an type is specified the argument is passed to the C function as a PlTerm (GNU Prolog C type equivalent to a PlLong). Several functions are provided to manipulate PlTerm variables (section 10.4). Since the original term is passed to the function it is possible to read its value or to unify it. So the meaning of the mode symbol is less significant. For this reason it is possible to omit the mode symbol. In that case term is equivalent to +term.

10.3.3  The C function

The type returned by the C function depends on the value of the return foreign option (section 10.3.2). If it is boolean then the C function is of type PlBool and shall return PL_TRUE in case of success and PL_FALSE otherwise. If the return option is none the C function is of type void. Finally if it is jump, the function shall return the address of a Prolog predicate and, at the exit of the function, the control is given to that predicate.

The type of the arguments of the C function depends on the mode and type declaration specified in Template for the corresponding argument as explained in the following sections.

10.3.4  Input arguments

An input argument is tested at run-time to check if its type conforms to the foreign type and then it is passed to the C function. The type of the associated C argument is given by the above table (section 10.3.2). For instance, the effective argument Arg associated with +positive foreign declaration is submitted to the following process:

When +string is specified the string passed to the function is the internal string of the corresponding atom and should not be modified.

When +term is specified the term passed to the function is the original Prolog term. It can be read and/or unified. It is also the case when term is specified without any mode symbol.

10.3.5  Output arguments

An output argument is tested at run-time to check if its type conforms to the foreign type and it is unified with the value set by the C function. The type of the associated C argument is a pointer to the type given by the above table (section 10.3.2). For instance, the effective argument Arg associated with -positive foreign declaration is handled as follows:

When -term is specified, the function must construct a term into the its corresponding argument (which is of type PlTerm *). At the exit of the function this term will be unified with the actual predicate argument.

10.3.6  Input/output arguments

Basically an input/output argument is treated as in input argument if it is not a variable, as an output argument otherwise. The type of the associated C argument is a pointer to a PlFIOArg (GNU Prolog C type) defined as follows:

typedef struct
    {
     PlBool is_var;
     PlBool unify;
     union
        {
         PlLong l;
         char  *s;
         double d;
        }value;
    }PlFIOArg;

The field is_var is set to PL_TRUE if the argument is a variable and PL_FALSE otherwise. This value can be tested by the C function to determine which treatment to perform. The field unify controls whether the effective argument must be unified at the exit of the C function. Initially unify is set to the same value as is_var (i.e. a variable argument will be unified while a non-variable argument will not) but it can be modified by the C function. The field value stores the value of the argument. It is declared as a C union since there are several kinds of value types. The field s is used for C strings, d for C doubles and l otherwise (int, PlLong, PlTerm). if is_var is PL_FALSE then value contains the input value of the argument with the same conventions as for input arguments (section 10.3.4). At the exit of the function, if unify is PL_TRUE value must contain the value to unify with the same conventions as for output arguments (section 10.3.5).

For instance, the effective argument Arg associated with ?positive foreign declaration is handled as follows:

10.3.7  Writing non-deterministic C code

The interface allows the user to write non-deterministic C code. When a C function is non-deterministic, a choice-point is created for this function. When a failure occurs, if all more recent non-deterministic code are finished, the function is re-invoked. It is then important to inform Prolog when there is no more solution (i.e. no more choice) for a non-deterministic code. So, when no more choices remains the function must remove the choice-point. The interface increments a counter each time the function is re-invoked. At the first call this counter is equal to 0. This information allows the function to detect its first call. When writing non-deterministic code, it is often useful to record data between consecutive re-invocations of the function. The interface maintains a buffer to record such an information. The size of this buffer is given by choice_size(N) when using foreign/2 (section 10.3.2). This size is the number of (consecutive) PlLongs needed by the C function. Inside the function it is possible to call the following functions/macros:

int  Pl_Get_Choice_Counter(void)
TYPE Pl_Get_Choice_Buffer (TYPE)
void Pl_No_More_Choice    (void)

The macro Pl_Get_Choice_Counter() returns the value of the invocation counter (0 at the first call).

The macro Pl_Get_Choice_Buffer(TYPE) returns a pointer to the buffer (casted to TYPE).

The function Pl_No_More_Choice() deletes the choice point associated with the function.

10.3.8  Example: input and output arguments

All examples presented here can be found in the ExamplesC sub-directory of the distribution, in the files examp.pl (Prolog part) and examp_c.c (C part).

Let us define a predicate first_occurrence(A, C, P) which unifies P with the position (from 0) of the first occurrence of the character C in the atom A. The predicate must fail if C does not appear in A.

In the prolog file examp.pl:

:- foreign(first_occurrence(+string, +char, -positive)).

In the C file examp_c.c:

#include <string.h>
#include <gprolog.h>

PlBool
first_occurrence(char *str, PlLong c, PlLong *pos)
{
  char *p;

  p = strchr(str, c);
  if (p == NULL)                    /* C does not appear in A */
    return PL_FALSE;                /* fail */

  *pos = p - str;                   /* set the output argument */
  return PL_TRUE;                   /* succeed */
}

The compilation produces an executable called examp:

% gplc examp.pl examp_c.c

Examples of use:

| ?- first_occurrence(prolog, p, X).

X = 0

| ?- first_occurrence(prolog, k, X).

no

| ?- first_occurrence(prolog, A, X).
{exception: error(instantiation_error,first_occurrence/3)}

| ?- first_occurrence(prolog, 1 ,X).
{exception: error(type_error(character,1),first_occurrence/3)}

10.3.9  Example: non-deterministic code

We here define a predicate occurrence(A, C, P) which unifies P with the position (from 0) of one occurrence of the character C in the atom A. The predicate will fail if C does not appear in A. The predicate is re-executable on backtracking. The information that must be recorded between two invocations of the function is the next starting position in A to search for C.

In the prolog file examp.pl:

:- foreign(occurrence(+string, +char, -positive), [choice_size(1)]).

In the C file examp_c.c:

#include <string.h>
#include <gprolog.h>

PlBool
occurrence(char *str, PlLong c, PlLong *pos)
{
  char **info_pos;
  char *p;

  info_pos = Pl_Get_Choice_Buffer(char **); /* recover the buffer */

  if (Pl_Get_Choice_Counter() == 0) /* first invocation ? */
    *info_pos = str;

  p = strchr(*info_pos, c);
  if (p == NULL)                    /* c does not appear */
    {
      Pl_No_More_Choice();          /* remove choice-point */
      return PL_FALSE;              /* fail */
    }

  *pos = p - str;                   /* set the output argument */
  *info_pos = p + 1;                /* update next starting pos */
  return PL_TRUE;                   /* succeed */
}

The compilation produces an executable called examp:

% gplc examp.pl examp_c.c

Examples of use:

| ?- occurrence(prolog, o, X).
 
X = 2 ?  (here the user presses ; to compute another solution)
 
X = 4 ?  (here the user presses ; to compute another solution)
 
no  (no more solution)
 
| ?- occurrence(prolog, k, X).
 
no

In the first example when the second (the last) occurrence is found (X=4) the choice-point remains and the failure is detected only when another solution is requested (by pressing ;). It is possible to improve this behavior by deleting the choice-point when there is no more occurrence. To do this it is necessary to do one search ahead. The information stored is the position of the next occurrence. Let us define such a behavior for the predicate occurrence2/3.

In the prolog file examp.pl:

:- foreign(occurrence2(+string, +char, -positive), [choice_size(1)]).

In the C file examp_c.c:

#include <string.h>
#include <gprolog.h>

PlBool
occurrence2(char *str, PlLong c, PlLong *pos)
{
  char **info_pos;
  char *p;

  info_pos = Pl_Get_Choice_Buffer(char **); /* recover the buffer */

  if (Pl_Get_Choice_Counter() == 0) /* first invocation ? */
    {
      p = strchr(str, c);
      if (p == NULL)                /* C does not appear at all */
        {
          Pl_No_More_Choice();      /* remove choice-point */
          return PL_FALSE;          /* fail */
        }

      *info_pos = p;
    }
                                    /* info_pos = an occurrence */
  *pos = *info_pos - str;           /* set the output argument */

  p = strchr(*info_pos + 1, c);
  if (p == NULL)                    /* no more occurrence */
    Pl_No_More_Choice();            /* remove choice-point */
  else
    *info_pos = p;                  /* else update next solution */

  return PL_TRUE;                   /* succeed */
}

Examples of use:

| ?- occurrence2(prolog, l, X).
 
X = 3  (here the user is not prompted since there is no more alternative)
 
| ?- occurrence2(prolog, o, X).
 
X = 2 ?  (here the user presses ; to compute another solution)
 
X = 4  (here the user is not prompted since there is no more alternative)

10.3.10  Example: input/output arguments

We here define a predicate char_ascii(Char, Code) which converts in both directions the character Char and its character-code Code. This predicate is then similar to char_code/2 (section 8.19.4).

In the prolog file examp.pl:

:- foreign(char_ascii(?char, ?code)).

In the C file examp_c.c:

#include <gprolog.h>

PlBool
char_ascii(PlFIOArg *c, PlFIOArg *ascii)
{
  if (!c->is_var)                  /* Char is not a variable */
    {
      ascii->unify = PL_TRUE;      /* enforce unif. of Code */
      ascii->value.l = c->value.l; /* set Code */
      return PL_TRUE;              /* succeed */
    }

  if (ascii->is_var)               /* Code is also a variable */
    Pl_Err_Instantiation();        /* emit instantiation_error */

  c->value.l = ascii->value.l;     /* set Char */
  return PL_TRUE;                  /* succeed */
}

If Char is instantiated it is necessary to enforce the unification of Code since it could be instantiated. Recall that by default if an input/output argument is instantiated it will not be unified at the exit of the function (section 10.3.6). If both Char and Code are variables the function raises an instantiation_error. The way to raise Prolog errors is described later (section 10.5).

The compilation produces an executable called examp:

% gplc examp.pl examp_c.c

Examples of use:

| ?- char_ascii(a, X).

X = 97

| ?- char_ascii(X, 65).

X = 'A'

| ?- char_ascii(a, 12).

no

| ?- char_ascii(X, X).
{exception: error(instantiation_error,char_ascii/2)}

| ?- char_ascii(1, 12).
{exception: error(type_error(character,1),char_ascii/2)}

Copyright (C) 1999-2021 Daniel Diaz Verbatim copying and distribution of this entire article is permitted in any medium, provided this notice is preserved. More about the copyright
Previous Up Next