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
            • Defining a BLOB type
            • Accessing blobs
          • 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
    • Packages

12.4.9 BLOBS: Using atoms to store arbitrary binary data

SWI-Prolog atoms as well as strings can represent arbitrary binary data of arbitrary length. This facility is attractive for storing foreign data such as images in an atom. An atom is a unique handle to this data and the atom garbage collector is able to destroy atoms that are no longer referenced by the Prolog engine. This property of atoms makes them attractive as a handle to foreign resources, such as Java atoms, Microsoft's COM objects, etc., providing safe combined garbage collection.

To exploit these features safely and in an organised manner, the SWI-Prolog foreign interface allows creating‘atoms' with additional type information. The type is represented by a structure holding C function pointers that tell Prolog how to handle releasing the atom, writing it, sorting it, etc. Two atoms created with different types can represent the same sequence of bytes. Atoms are first ordered on the rank number of the type and then on the result of the compare() function. Rank numbers are assigned when the type is registered. This implies that the results of inequality comparisons between blobs of different types is undefined and can change if the program is run twice (the ordering within a blob type will not change, of course).

While the blob is alive, neither its handle nor the location of the contents (see PL_blob_data()) change. If the blob's type has the PL_BLOB_UNIQUE feature, the content of the blob must remain unmodified. Blobs are only reclaimed by the atom garbage collector. In this case the atom garbage collector calls the release(f)unction associated with the blob type and reclaims the memory allocated for the content unless this is owned by the creator of the blob indicated by the PL_BLOB_NOCOPY flag. After an atom_t value is reclaimed by the atom garbage collector, the value may be reused for allocating a new blob or atom.

If foreign code stores the atom_t handle in some permanent location it must make sure the handle is registered to prevent it from being garbage collected. If the handle is obtained from a term_t object it is not registered because it is protected by the term_t object. This applies to e.g., PL_get_atom(). Functions that create a handle from data such as PL_new_atom() return a registered handle to prevent the asynchronous atom garbage collector from reclaiming it immediately. Note that many of the API functions create an atom or blob handle and use this to fill a term_t object, e.g., PL_unify_blob(), PL_unify_chars(), etc. In this scenario the handle is protected by the term_t object. Registering and unregistering atom_t handles is done by PL_register_atom() and PL_unregister_atom().

Note that during program shutdown using PL_cleanup(), all atoms and blobs are reclaimed as described above. These objects are reclaimed regardless of their registration count. The order in which the atoms or blobs are reclaimed under PL_cleanup() is undefined. However, when these objects are reclaimed using garbage_collect_atoms/0, registration counts are taken into account.

12.4.9.1 Defining a BLOB type

The type PL_blob_t represents a structure with the layout displayed below. The structure contains additional fields at the ... for internal bookkeeping as well as future extensions.

typedef struct PL_blob_t
{ uintptr_t     magic;          /* PL_BLOB_MAGIC */
  uintptr_t     flags;          /* Bitwise or of PL_BLOB_* */
  const char *  name;           /* name of the type */
  int           (*release)(atom_t a);
  int           (*compare)(atom_t a, atom_t b);
  int           (*write)(IOSTREAM *s, atom_t a, int flags);
  void          (*acquire)(atom_t a);
  int           (*save)(atom_t a, IOSTREAM *s);
  atom_t        (*load)(IOSTREAM *s);
  ...
} PL_blob_t;

For each type, exactly one such structure should be allocated. Its first field must be initialised to PL_BLOB_MAGIC. The flags is a bitwise or of the following constants:

PL_BLOB_TEXT
If specified, the blob is assumed to contain text and is considered a normal Prolog atom. The (currently) two predefined blob types that represent atoms have this flag set. User-defined blobs may not specify this, even if they contain only text. Applications should not use the blob API to create normal text atoms or get access to the text represented by normal text atoms. Most applications should use PL_get_nchars() and PL_unify_chars() to get text from Prolog terms or create Prolog terms that represent text.
PL_BLOB_UNIQUE
If specified the system ensures that the blob-handle is a unique reference for a blob with the given type, length and content. If this flag is not specified, each lookup creates a new blob. Uniqueness is determined by comparing the bytes in the blobs unless PL_BLOB_NOCOPY is also specified, in which case the pointers are compared.
PL_BLOB_NOCOPY
By default the content of the blob is copied. Using this flag the blob references the external data directly. The user must ensure the provided pointer is valid as long as the atom lives. If PL_BLOB_UNIQUE is also specified, uniqueness is determined by comparing the pointer rather than the data pointed at. Using PL_BLOB_UNIQUE|PL_BLOB_NOCOPY can be used to make a blob reference an arbitrary pointer where the pointer data may be reclaimed in the release() handler.
PL_BLOB_WCHAR
If PL_BLOB_TEXT is also set, then the text is made up of pl_wchar_t items and the blob's lenght is the number of bytes (that is, the number of characters times sizeof(pl_wchar_t)). As PL_BLOB_TEXT, this flag should not be set in user-defined blobs.

The name field represents the type name as available to Prolog. See also current_blob/2. The other fields are function pointers that must be initialised to proper functions or NULL to get the default behaviour of built-in atoms. Below are the defined member functions:

void acquire(atom_t a)
Called if a new blob of this type is created through PL_put_blob() or PL_unify_blob(). Note this this call is done as part of creating the blob. The call to PL_unify_blob() may fail if the unification fails or cannot be completed due to a resource error. PL_put_blob() has no such error conditions. This callback is typically used to store the atom_t handle into the content of the blob. Given a pointer to the content, we can now use PL_unify_atom() to bind a Prolog term with a reference to the pointed to object. If the content of the blob can be modified (PL_BLOB_UNIQUE is not present) this is the only way to get access to the atom_t handle that belongs to this blob. If PL_BLOB_UNIQUE is provided and respected, PL_unify_blob() given the same pointer and length will produce the same atom_t handle.
int release(atom_t a)
The blob (atom) a is about to be released. This function can retrieve the data of the blob using PL_blob_data(). If it returns FALSE, the atom garbage collector will not reclaim the atom. The release(f)unction is called when the atom is reclaimed by the atom garbage collector. For critical resources such as file handles or significant memory resources it may be desirable to have an explicit call to dispose (most of) the resources. For example, close/1 reclaims the file handle and most of the resources associated with a stream, leaving only a tiny bit of content to the garbage collector. See also setup_call_cleanup/3.
int compare(atom_t a, atom_t b)
Compare the blobs a and b, both of which are of the type associated to this blob type. Return values are as memcmp(): < 0 if a is less than b, = 0 if both are equal, and > 0 otherwise. The default implementation is a bitwise comparison of the blobs' contents. If you cannot guarantee that the blobs all have unique contents, then you should incorporate the blob address (the system guarantees that blobs are not shifted in memory after they are allocated). The following minimal compare function gives a stable total ordering:
static int
compare_my_blob(atom_t a, atom_t b)
{ const struct my_blob_data *blob_a = PL_blob_data(a, NULL, NULL);
  const struct my_blob_data *blob_b = PL_blob_data(b, NULL, NULL);
  return (blob_a > blob_b) ?  1 :  (blob_a < blob_b) ? -1 : 0;
}
int write(IOSTREAM *s, atom_t a, int flags)
Write the content of the blob a to the stream s respecting the flags. The flags are a bitwise or of zero or more of the PL_WRT_* flags defined in SWI-Prolog.h. This prototype is available if the SWI-Stream.h is included before SWI-Prolog.h. This function can retrieve the data of the blob using PL_blob_data().

If this function is not provided, write/1 emits the content of the blob for blobs of type PL_BLOB_TEXT or a string of the format <#hex data> for binary blobs.

If a blob type is registered from a loadable object (shared object or DLL) the blob type must be deregistered before the object may be released.

int save(atom_t a, IOSTREAM *s)
Write the blob to stream s, in an opaque form that is known only to the blob. If a “save'' function is not provided (that is, the field is NULL), the default implementation saves and restores the blob as if it is an array of bytes which may contain null (’
0'
) bytes.

SWI-Stream.h defines a number of PL_qlf_put_*() functions that write data in a machine-independent form that can be read by the corresponding PL_qlf_get_*() functions.

If the “save'' function encounters an error, it should call PL_warning(), raise an exception (see PL_raise_exception()), and return FALSE.221Details are subject to change. Note that failure to save/restore a blob makes it impossible to compile a file that contains such a blob using qcompile/2 as well as creating a saved state from a program that contains such a blob impossible. Here, contains means that the blob appears in a clause or directive.

atom_t load(IOSTREAM *s)
Read the blob from its saved form as written by the “save'' function of the same blob type. If this cannot be done (e.g., a stream read failure or a corrupted external form), the “load'' function should call PL_warning(), then PL_fatal_error(), and return constFALSE.222Details are subject to change; see the “save'' function. If a “load'' function is not provided (that is, the field is NULL, the default implementation assumes that the blob was written by the default “save'' - that is, as an array of bytes

SWI-Stream.h defines a number of PL_qlf_get_*() functions that read data in a machine-independent form, as written by the by the corresponding PL_qlf_put_*() functions.

The atom that the “load'' function returns can be created using PL_new_blob().

int PL_unregister_blob_type(PL_blob_t *type)
Unlink the blob type from the registered type and transform the type of possible living blobs to unregistered, avoiding further reference to the type structure, functions referred by it, as well as the data. This function returns TRUE if no blobs of this type existed and FALSE otherwise. PL_unregister_blob_type() is intended for the uninstall() hook of foreign modules, avoiding further references to the module.

12.4.9.2 Accessing blobs

The blob access functions are similar to the atom accessing functions. Blobs being atoms, the atom functions operate on blobs and vice versa. For clarity and possible future compatibility issues, however, it is not advised to rely on this.

int PL_is_blob(term_t t, PL_blob_t **type)
Succeeds if t refers to a blob, in which case type is filled with the type of the blob.
int PL_unify_blob(term_t t, void *blob, size_t len, PL_blob_t *type)
Unify t to a blob constructed from the given data and associated with the given type. This performs the following steps:

  1. If the type has PL_BLOB_UNIQUE set, search the blob database for a blob of the same type with the same content. If found, unify t with the existing handle.
  2. If not found or PL_BLOB_UNIQUE is not set, create a new blob handle. If PL_BLOB_NOCOPY is set, associate it to the given memory; else, copy the memory to a new area owned by the blob. Call the acquire() function of the type.
  3. Unify t with the existing or new handle. This succeeds if t is already bound to the existing blob handle. If t is a variable, it succeeds if sufficient resources are available to perform the unification; if t is bound to something else, this fails.

It is possible that a blob referencing critial resources is created after which the unification fails. Typically these resources are eventually reclaimed because the new blob is not referenced and reclaimed by the atom garbage collector. As described with the release(f)unction, it can be desirable to reclaim the critical resources after the failing PL_unify_blob() call.

int PL_put_blob(term_t t, void *blob, size_t len, PL_blob_t *type)
Store the described blob in t. The return value indicates whether a new blob was allocated (FALSE) or the blob is a reference to an existing blob (TRUE). Reporting new/existing can be used to deal with external objects having their own reference counts. If the return is TRUE this reference count must be incremented, and it must be decremented on blob destruction callback. See also PL_put_atom_nchars().
atom_t PL_new_blob(void *blob, size_t len, PL_blob_t *type)
Create a blob from its internal opaque form. This function is intended for the “load'' function of a blob.
int PL_get_blob(term_t t, void **blob, size_t *len, PL_blob_t **type)
If t holds a blob or atom, get the data and type and return TRUE. Otherwise return FALSE. Each result pointer may be NULL, in which case the requested information is ignored.
void * PL_blob_data(atom_t a, size_t *len, PL_blob_t **type)

Get the data and type associated to a blob. This function is mainly used from the callback functions described in section 12.4.9.1. Note that if the release() hook is called from PL_cleanup(), blobs are releases regardless of whether or not they are referenced and the order in which blobs are released is undefined (the order depends on the ordering in the atom hash table). PL_blob_data() may be called safely on a blob that has already been released. If this happens during PL_cleanup() the return value is guaranteed to be NULL. During normal execution it may return the content of a newly allocated blob that reused the released handle.