• Places
    • Home
    • Graphs
    • Prefixes
  • Admin
    • Users
    • Settings
    • Plugins
    • Statistics
  • CPACK
    • Home
    • List packs
    • Submit pack
  • Repository
    • Load local file
    • Load from HTTP
    • Load from library
    • Remove triples
    • Clear repository
  • Query
    • YASGUI SPARQL Editor
    • Simple Form
    • SWISH Prolog shell
  • Help
    • Documentation
    • Tutorial
    • Roadmap
    • HTTP Services
  • Login

1 A C++ interface to SWI-Prolog
All Application Manual Name SummaryHelp

  • Documentation
    • Reference manual
    • Packages
      • A C++ interface to SWI-Prolog
        • A C++ interface to SWI-Prolog
          • Summary of changes between Versions 1 and 2
          • A simple example
          • Sample code
          • Introduction
          • The life of a PREDICATE
          • Overview
          • Examples
          • Rationale for changes from version 1
          • Porting from version 1 to version 2
          • The class PlFail
          • Overview of accessing and changing values
          • The class PlRegister
          • The class PlQuery
          • The PREDICATE and PREDICATE_NONDET macros
          • Exceptions
          • Embedded applications
          • Considerations
            • The C++ versus the C interface
            • Notes on exceptions
            • Global terms, atoms, and functors
            • Atom map utilities
            • Static linking and embedding
            • Status and compiler versions
          • Conclusions

1.17 Considerations

1.17.1 The C++ versus the C interface

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 unwrap() method), 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 PlCheckFail() or PlCheck_PL().

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.

Global terms and atoms need to be handled slightly differently in C++ than in C - see section 1.17.3

1.17.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 invoked as part of handling another exception.

1.17.3 Global terms, atoms, and functors

Sometimes it is convenient to put constant terms and atoms as global variables in a file (with a static qualifier), so that they are only created (and looked up) cone. This is fine for atoms and functors, which can be created by something like this:

static PlAtom ATOM_foo("foo");
static PlFunctor FUNCTOR_ff_2("ff", 2);

C++ makes no guarantees about the order of creating global variables across “translation units” (that is, individual C++ files), but the Prolog runtime ensures that the necessary initialization has been done to allow PlAtom and PlFunctor objects to be created. However, to be safe, it is best to put such global variables inside functions - C++ will initialize them on their firstuse.

Global Terms need a bit of care. For one thing, terms are ephemeral, so it is wrong to have a PlTerm static variable - instead, a PlRecord must be used, which will provide a fresh copy of the term using PlRecord::term(). There is no guarantee that the Prolog runtime has initialized everything needed for creating entries in the recorded database (see Recorded database). Therefore, global recorded terms must be wrapped inside a function. C++ will call the constructor upon first use. For example:

static PlTerm
term_foo_bar()
{ static PlRecord r(PlCompound("foo", PlTermv(PlTerm_atom("bar"))).record());
  return r.term();
}

1.17.4 Atom map utilities

The include file SWI-cpp2-atommap.h contains a templated class AtomMap for mapping atoms to atoms or terms. The typical use case is for when it is desired to open a database or stream and, instead of passing around the blob, an atom can be used to identify the blob.

The keys in the map must be standard Prolog atoms and not blobs - the code depends on the fact that an atom has a unique ID.

The AtomMap is thread-safe (it contains a mutex). It also takes care of reference counts for both the key and the value. Here is a typical use case:

static AtomMap<PlAtom, PlAtom> map_atom_my_blob("alias", "my_blob");

// look up an entry:
   auto value = map_atom_my_blob(A1.as_atom());
   PlCheckFail(value.not_null());

// insert an entry:
   map_atom_my_blob.insert(A1.as_atom(), A2.as_atom());

// remove an entry:
   map_atom_my_blob.erase(A1.as_atom());

The constructor and methods are as follows:

    template<ValueType, StoredValueType> AtomMap::AtomMap(const std::string& insert_op)
    const std::string& insert_type Construct an AtomMap. The ValueType and StoredValueType specify what type you wish for the value. Currently, two value types are supported:
    • PlAtom - the StoredValueType should be PlAtom.
    • PlTerm - the StoredValueType shoud be PlRecord (because the term needs to be put on the global stack).
    The insert_op and insert_type values are used in constructing error terms - these correspond to the operation and type arguments to Pl_permission_error().
    insert PlAtom key, ValueType value(I)
    nserts a new value; raises a permission_error if the value is already in the map, unless the value is identical to the value in the map. The insert() method converts the value to the StoredValueType. The insertion code takes care of atom reference counts.
    ValueType find(PlAtom key)
    Look up an entry. Success/failure can be determined by using ValueType::is_null() or ValueType::not_null(). The stored value is converted from StoredValueType to ValueType.
    erase PlAtom(r)
    emoves the entry from the map. If there was no entry in the map with that key, this is a no-op. The erasure code takes care of atom reference counts.

1.17.5 Static linking and embedding

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.

1.17.6 Status and compiler versions

The current interface can be 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. However, inlining everything can lead to code bloat, so the larger functions and methods have been put into a .cpp file that can be either compiled separately (by the same compiler as used by the foreign predicate) or inlined as if it were part of the .h file.

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 2023-04, 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 2023-04, 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 would happen from Plx_instantiation_error(A1. unwrap()); throw PlFail(). See section 1.17.2.

The Plx_*() wrappers may require small adjustments in whether their return values require [[nodiscard]] or whether their return values should be treated as an error.

The implementation of PlException is likely to change somewhat in the future. Currently, to ensure that the exception term has a sufficient lifetime, it is serialized using PL_record_external(). In future, if this proves unnecessary, the term will be stored as-is. The API will not change if this implementation detail changes.

ClioPatria (version V3.1.1-51-ga0b30a5)