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. If the blob's type does not have the
PL_BLOB_UNIQUE
feature multiple instances of this blob type
may contain the same data. The blob handle (atom_t
)
is reclaimed only by the atom garbage collector. The blob's
content (data) is normally reclaimed when the garbage collector
reclaims the blob. If the blob's type defines the
release()
function, this function is called. This hook may deal with side effects
and is responsible of releasing the data if the blob's type has the PL_BLOB_NOCOPY
flag. The content of a
PL_BLOB_NOCOPY
blob may be released before the blob itself
can be garbage collected using PL_free_blob().
This immediately triggers the release()
function. After PL_free_blob()
has reclaimed the content, this function will not be called when the atom_t
handle is reclaimed. An atom_t
handle may be reused for a
new atom or blob after it has been garbage collected.
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.
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 and
must not be moved because the address of the structure determines the
blob's "type". Its first field must be initialised to PL_BLOB_MAGIC
.
If a blob type is registered from a loadable object (shared object or
DLL) the blob type must be deregistered using PL_unregister_blob_type()
before the object may be released.
The flags is a bitwise or of the following constants:
PL_BLOB_NOCOPY
is also specified, in which case the
pointers are compared. Note that the lookup does not use the
blob's compare function when testing for equality, but only tests the
bytes; this means that terms from the recorded database or C++-style
strings will typically not compare as equal when doing blob lookup.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_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:
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.FALSE
, the atom garbage collector will
not reclaim the atom. 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.
The release()
callback is called in the context of the thread executing the atom
garbage collect, the thread executing
PL_free_blob()
or the thread initiating the shutdown. Normally the thread gc
runs all atom and clause garbage collections. The
release()
function may not call any of the PL_*() functions except for
PL_blob_data()
or PL_unregister_atom()
to unregister other atoms that are part data associated to the blob.
Calling any of the other PL_* functions may result in deadlocks or
crashes. The release()
function should not call any potentially slow or blocking functions as
this may cause serious slowdowns in the rest of the system.
Blobs that require cleanup that is slow, blocking or requires calling
Prolog must pass the data to be cleaned to another thread. Be aware that
if the blob uses PL_BLOB_NOCOPY
the user is responsible for
discarding the data, otherwise the atom garbage collector will free the
data.
As SWI-Prolog atom garbage collector is conservative, there is no guarantee that the release() function will ever be called. If it is important to clean up some resource, there should be an explicit predicate for doing that, and calling that predicate should be guaranteed by using setup_call_cleanup/3 or some a process finalization hook such as at_halt/1.
Normally, Prolog does not clean memory during shutdown. It does so on
an explicit call to PL_cleanup().224Or
if the system is compiled with the cmake build type Debug
.
In such a situation, there is no guarantee of the order in which atoms
are released; if a blob contains an atom (or another blob), those atoms
(or blobs) may have already been released. See also
PL_blob_data().
PL_BLOB_UNIQUE
is set and the blob follows the requirement
that its contents do not change, although it might give an unexpected
ordering, and the ordering may change if the blob is saved and restored
using save_program/2.
If the compare() function is defined, the sort/2 predicate uses that to determine if two blobs are equal and only keeps one of them. This can cause unexpected results with blobs that are actually different; 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). This function should not call any PL_*() functions other than PL_blob_data().
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; }
TRUE
or FALSE
and does not follow the Unix convention
of the number of bytes (where zero is possible) and negative for errors.
Any I/O operations to
s are in the context of a PL_acquire_stream();
upon return, the PL_release_stream()
handles any errors, so it is safe to not check return codes from Sfprintf(),
etc.
In general, the output from the write() callback should be minimal. If you wish to output more debug information, it is suggested that you either add a debug option to your "open" predicate to output more information, or provide a "properties" predicate. A typical implementation is:
static int write_my_blob(IOSTREAM *s, atom_t symbol, int flags) { (void)flags; /* unused */ Sfprintf(s, "<my_blob>(%p)", PL_blob_data(symbol, NULL, NULL)); return TRUE; }
The flags are a bitwise or of zero or more of the
PL_WRT_*
flags that were passed in to the calling
PL_write_term()
that called write(),
and are defined in
SWI-Prolog.h
. The flags do not have the
PL_WRT_NEWLINE
bit set, so it is safe to call PL_write_term()
and there is no need for writing a trailing newline. 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().
Most blobs reference some external data identified by a pointer and
the
write() function
writes
<
type>(
address)
.
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.
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
.225Details
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.
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().
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.
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.
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.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.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() function, it can be desirable to reclaim the critical resources after the failing PL_unify_blob() call.
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().TRUE
. Otherwise return FALSE
. Each result
pointer may be NULL
, in which case the requested
information is ignored.NULL
. During normal
execution it may return the content of a newly allocated blob that
reuses the released handle.PL_BLOB_NOCOPY
flag set and the blob type implements the
release()
callback. It causes the release()
callback to be called, after which the data and size are set to 0 if the release()
returns TRUE
. After this sequence, the release()
for this blob is never called again. The related atom_t
handle remains valid until it is no longer referenced and reclaimed by
the atom garbage collector. If the blob data is accessed using e.g., PL_get_blob()
it returns NULL
for the data and 0 for the size.227This
means that any predicates or callbacks that use the blob must check the
result of PL_blob_data().
If the release()
function is not called, or if it returns FALSE
, FALSE
is returned.
PL_free_blob()
may be called multiple times on the same
atom_t
, provided the handle is still valid. Subsequent
calls after a successful call have no effect and return FALSE
.
The blob API assumes that Prolog will take care of memory management, using the release(c)allback to handle any cleanup.
Other programming languages have their own memory management, which might not fit nicely with the Prolog memory management. For more details on blobs written with C++, see C++ interface to SWI-Prolog (Version 2).