2 A C++ interface to SWI-Prolog (Version 2)
AllApplicationManualNameSummaryHelp

  • Documentation
    • Reference manual
    • Packages
      • A C++ interface to SWI-Prolog
        • A C++ interface to SWI-Prolog (Version 2)
          • Summary of changes between Versions 1 and 2
          • Introduction (version 2)
          • The life of a PREDICATE (version 2)
          • Overview (version 2)
          • Examples (version 2)
          • Rational for changes from version 1 (version 2)
          • Porting from version 1 to version 2
          • The class PlFail (version 2)
          • The class PlTerm (version 2)
          • The class PlTermv (version 2)
          • The class PlAtom - Supporting Prolog constants (version 2)
          • Unification and foreign frames (version 2)
          • The class PlRegister (version 2)
          • The class PlQuery (version 2)
          • The PREDICATE and PREDICATE_NONDET macros (version 2)
          • Exceptions (version 2)
          • Embedded applications (version 2)
          • Considerations (version 2)
            • The C++ versus the C interface (version 2)
            • Notes on exceptions
            • Static linking and embedding (version 2)
            • Status and compiler versions (version 2)
          • Conclusions (version 2)

2.18 Considerations (version 2)

2.18.1 The C++ versus the C interface (version 2)

Not all functionality of the C-interface is provided, but as PlTerm and term_t are essentially the same thing with type-conversion between the two (using the C_ field), this interface can be freely mixed with the functions defined for plain C. For checking return codes from C functions, it is recommended to use PlCheck().

Using this interface rather than the plain C-interface requires a little more resources. More term-references are wasted (but reclaimed on return to Prolog or using PlFrame). Use of some intermediate types (functor_t etc.) is not supported in the current interface, causing more hash-table lookups. This could be fixed, at the price of slighly complicating the interface.

2.18.2 Notes on exceptions

Exceptions are normal Prolog terms that are handled specially by the PREDICATE macro when they are used by a C++ throw, and converted into Prolog exceptions. The exception term may not be unbound; that is, throw(_) must raise an error. The C++ code and underlying C code do not explicitly check for the term being a variable, and behaviour of raising an exception that is an unbound term is undefined, including the possibility of causing a crash or corrupting data.

The Prolog exception term error(Formal, _) is special. If the 2nd argument of error/2 is undefined, and the term is thrown, the system finds the catcher (if any), and calls the hooks in library(prolog_stack) to add the context and stack trace information when appropriate. That is, throw PlDomainError(Domain,Culprit) ends up doing the same thing as calling PL_domain_error(Domain,Culprit) which internally calls PL_raise_exception() and returns control back to Prolog.

The VM handling of calling to C finds the FALSE return code, checks for the pending exception and propagates the exception into the Prolog environment. As the term references (term_t) used to create the exception are lost while returning from the foreign function we need some way to protect them. That is done using a global term_t handle that is allocated at the epoch of Prolog. PL_raise_exception() sets this to the term using PL_put_term(). PL_exception(0) returns the global exception term_t if it is bound and 0 otherwise.

Special care needs to be taken with data backtracking using PL_discard_foreign_frame() or PL_close_query() because that will invalidate the exception term. So, between raising the exception and returning control back to Prolog we must make sure not to do anything that invalidates the exception term. If you suspect something like that to happen, use the debugger with a breakpoint on __do_undo__LD() defined in pl-wam.c.

In order to always preserve Prolog exceptions and return as quickly as possible to Prolog on an exception, some of the C++ classes can throw an exception in their destructor. This is theoretically a dangerous thing to do, and can lead to a crash or program termination if the destructor is envoked as part of handling another exception.

2.18.3 Static linking and embedding (version 2)

The mechanisms outlined in this document can be used for static linking with the SWI-Prolog kernel using swipl-ld(1). In general the C++ linker should be used to deal with the C++ runtime libraries and global constructors.

2.18.4 Status and compiler versions (version 2)

The current interface is entirely defined in the .h file using inlined code. This approach has a few advantages: as no C++ code is in the Prolog kernel, different C++ compilers with different name-mangling schemas can cooperate smoothly.

Also, changes to the header file have no consequences to binary compatibility with the SWI-Prolog kernel. This makes it possible to have different versions of the header file with few compatibility consequences.

As of 2022-11, some details remain to be decided, mostly to do with encodings. A few methods have a PlEncoding optional parameter (e.g., PlTerm::as_string()), but this hasn't yet been extended to all methods that take or return a string. Also, the details of how the default encoding is set have not yet been decided.

As of 2022-11, the various error convenience classes do not fully match what the equivalent C functions do. That is, throw PlInstantiationError(A1) does not result in the same context and traceback information that calling that would happen from PL_instantiation_error(A1.C_); throw PlFail(). See section 2.18.2.