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
            • PL_raise_exception()
            • PL_throw()
            • PL_exception()
            • PL_clear_exception()
          • 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
    • Packages

12.4.15 Prolog exceptions in foreign code

This section discusses PL_exception() and PL_raise_exception(), the interface functions to detect and generate Prolog exceptions from C code. PL_raise_exception() from the C interface registers the exception term and returns FALSE. If a foreign predicate returns FALSE, while an exception term is registered, a Prolog exception will be raised by the virtual machine. This implies for a foreign function that implements a predicate and wishes to raise an exception, the function shall call PL_raise_exception(), perform any necessary cleanup and return the return code of PL_raise_exception() or explicitly FALSE. Calling PL_raise_exception() outside the context of a function implementing a foreign predicate results in undefined behaviour.

Note that many of the C API functions may call PL_raise_exception() and return FALSE. The user shall test for this, cleanup and make the foreign function return FALSE.

PL_exception() may be used to inspect the currently registered exception. It is normally called after a call to PL_next_solution() returns FALSE, and returns a term reference to an exception term if an exception is pending, and (term_t)0 otherwise. It may also be called after, e.g., PL_unify() to distinguish a normal failing unification from a unification that raised an resource error exception. PL_exception() must only be called after a function such as PL_next_solution() or PL_unify() returns failure; if called elsewhere, the return value is undefined.

If a C function implementing a predicate that calls Prolog should use PL_open_query() with the flag PL_Q_PASS_EXCEPTION and make the function return FALSE if PL_next_solution() returns FALSE and PL_exception() indicates an exception is pending.

Both for C functions implementing a predicate and when Prolog is called while the main control of the process is in C, user code should always check for exceptions. As explained above, C functions implementing a predicate should normally cleanup and return with FALSE. If the C function whishes to continue it may call PL_clear_exception(). Note that this may cause any exception to be ignored, including time outs and abort. Typically the user should check the exception details before ignoring an exception (using PL_exception(0) or PL_exception(qid) as appropriate). If the C code does not implement a predicate it normally prints the exception and calls PL_clear_exception() to discard it. Exceptions may be printed by calling print_message/2 through the C interface.

int PL_raise_exception(term_t exception)
Generate an exception (as throw/1) and return FALSE. If there is already a pending exception, the most urgent exception is kept; and if both are of the same urgency, the new exception is kept. Urgency of exceptions is defined as

  1. abort ('$aborted').
  2. time_limit_exceeded (see call_with_time_limit/2).
  3. resource_error exceptions.
  4. Other error(Formal, ImplDef) exceptions.
  5. Other exceptions.

This function is rarely used directly. Instead, errors are typically raised using the functions in section 12.4.6 or the C api functions that end in _ex such as PL_get_atom_ex(). Below we give an example returning an exception from a foreign predicate the verbose way. Note that the exception is raised in a sequence of actions connected using &&. This ensures that a proper exception is raised should any of the calls used to build or raise the exception themselves raise an exception. In this simple case PL_new_term_ref() is guaranteed to succeed because the system guarantees at least 10 available term references before entering the foreign predicate. PL_unify_term() however may raise a resource exception for the global stack.

foreign_t
pl_hello(term_t to)
{ char *s;

  if ( PL_get_atom_chars(to, &s) )
  { Sprintf("Hello \"%s\"\n", s);

    return TRUE;
  } else
  { term_t except;

    return  ( (except=PL_new_term_ref()) &&
              PL_unify_term(except,
                            PL_FUNCTOR_CHARS, "type_error", 2,
                              PL_CHARS, "atom",
                              PL_TERM, to) &&
              PL_raise_exception(except) );
  }
}

For reference, the preferred implementation of the above is below. The CVT_EXCEPTION tells the system to generate an exception if the conversion fails. The other CVT_ flags define the admissible types and REP_MB requests the string to be provided in the current locale representation. This implies that Unicode text is printed correctly if the current environment can represent it. If not, a representation_error is raised.

foreign_t
pl_hello(term_t to)
{ char *s;

  if ( PL_get_chars(to, &s, CVT_ATOM|CVT_STRING|CVT_EXCEPTION|REP_MB) )
  { Sprintf("Hello \"%s\"\n", s);

    return TRUE;
  }

  return FALSE;
}
int PL_throw(term_t exception)
Similar to PL_raise_exception(), but returns using the C longjmp() function to the innermost PL_next_solution(). This function is deprecated as it does not provide the opportunity to cleanup.
term_t PL_exception(qid_t qid)
Return the pending exception. Exceptions may be raised by most of the API calls described in this chapter, a common possibility being resource_error exceptions. Some return type_error or domain_error exceptions. A call to Prolog using PL_next_solution() may return any exception, including those thrown by explicit calls to throw/1. If no exception is pending this function returns (term_t)0.

Normally qid should be 0. An explicit qid must be used after a call to PL_next_solution() that returns FALSE when the query was created using the PL_Q_PASS_EXCEPTION flag (see PL_open_query()).

Note that an API may only raise an exception when it fails; if the API call succeeds, the result of PL_exception(0) will be 0.225Provided no exception was pending before calling the API function. As clients must deal with exceptions immediately after an API call raises one, this can not happen in a well behaved client. The implementation of a foreign predicate should normally cleanup and return FALSE after an exception is raised (and typically also after an API call failed for logical reasons; see PL_unify() for an elaboration on this topic). If the call to Prolog is not the implementation of a foreign predicate, e.g., when the overall process control is in some other language, exceptions may be printed by calling print_message/2 and should be discarded by calling PL_clear_exception().

void PL_clear_exception(void)
Tells Prolog that the encountered exception must be ignored. This function must be called if control remains in C after a previous API call fails with an exception.226This feature is non-portable. Other Prolog systems (e.g., YAP) have no facilities to ignore raised exceptions, and the design of YAP's exception handling does not support such a facility. If there is no pending exception, PL_clear_exception() does nothing.