View source with raw comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        jan@swi-prolog.org
    5    WWW:           https://www.swi-prolog.org
    6    Copyright (c)  1995-2026, University of Amsterdam
    7                              VU University Amsterdam
    8                              CWI, Amsterdam
    9                              SWI-Prolog Solutions b.v.
   10    All rights reserved.
   11
   12    Redistribution and use in source and binary forms, with or without
   13    modification, are permitted provided that the following conditions
   14    are met:
   15
   16    1. Redistributions of source code must retain the above copyright
   17       notice, this list of conditions and the following disclaimer.
   18
   19    2. Redistributions in binary form must reproduce the above copyright
   20       notice, this list of conditions and the following disclaimer in
   21       the documentation and/or other materials provided with the
   22       distribution.
   23
   24    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   25    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   26    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   27    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   28    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   29    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   30    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   31    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   32    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   33    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   34    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   35    POSSIBILITY OF SUCH DAMAGE.
   36*/
   37
   38:- module(shlib,
   39          [ load_foreign_library/1,     % :LibFile
   40            load_foreign_library/2,     % :LibFile, +Options
   41            unload_foreign_library/1,   % +LibFile
   42            unload_foreign_library/2,   % +LibFile, +UninstallFunc
   43            current_foreign_library/2,  % ?LibFile, ?Public
   44            foreign_library_property/2, % ?File:atom, ?Property
   45            reload_foreign_libraries/0,
   46                                        % Directives
   47            use_foreign_library/1,      % :LibFile
   48            use_foreign_library/2       % :LibFile, +Options
   49          ]).   50:- if(current_predicate(win_add_dll_directory/2)).   51:- export(win_add_dll_directory/1).   52:- endif.   53
   54:- autoload(library(error),[existence_error/2]).   55:- autoload(library(lists),[member/2,reverse/2]).   56
   57%:- set_prolog_flag(generate_debug_info, false).

Utility library for loading foreign objects (DLLs, shared objects)

This section discusses the functionality of the (autoload) library(shlib), providing an interface to manage shared libraries. We describe the procedure for using a foreign resource (DLL in Windows and shared object in Unix) called mylib.

First, one must assemble the resource and make it compatible to SWI-Prolog. The details for this vary between platforms. The swipl-ld(1) utility can be used to deal with this in a portable manner. The typical commandline is:

swipl-ld -shared -o mylib file.{c,o,cc,C} ...

Make sure that one of the files provides a global function install_mylib() that initialises the module using calls to PL_register_foreign(). Below is a simple example file mylib.c, which prints a "hello" message. Note that we use SWI-Prolog's Sprintf() rather than C standard printf() to print the outout through Prolog's current_output stream, making the example work in a windowed environment. The standard C printf() works in a console environment, but this bypasses Prolog's output redirection. Also note the use of the standard C bool type, which is supported in 9.2.x and more actively promoted in the 9.3.x development series.

#include <SWI-Prolog.h>
#include <SWI-Stream.h>
#include <stdbool.h>

static foreign_t
pl_say_hello(term_t to)
{ char *s;

  if ( PL_get_chars(to, &s, CVT_ALL|REP_UTF8) )
  { Sprintf("hello %Us", s);

    return true;
  }

  return false;
}

install_t
install_mylib(void)
{ PL_register_foreign("say_hello", 1, pl_say_hello, 0);
}

Now write a file mylib.pl:

:- module(mylib, [ say_hello/1 ]).
:- use_foreign_library(foreign(mylib)).

The file mylib.pl can be loaded as a normal Prolog file and provides the predicate defined in C. The generated mylib.so (or .dll, etc.) must be placed in a directory searched for using the Prolog search path foreign (see absolute_file_name/3). To load this from the current directory, we can use the -p alias=dir option:

swipl -p foreign=. mylib.pl
?- say_hello(world).
hello world
true.

*/

  131:- meta_predicate
  132    load_foreign_library(:),
  133    load_foreign_library(:, +).  134
  135:- dynamic
  136    loading/1,                      % Lib
  137    error/2,                        % File, Error
  138    foreign_predicate/2,            % Lib, Pred
  139    current_library/5.              % Lib, Entry, Path, Module, Handle
  140
  141:- volatile                             % Do not store in state
  142    loading/1,
  143    error/2,
  144    foreign_predicate/2,
  145    current_library/5.  146
  147:- '$notransact'((loading/1,
  148                  error/2,
  149                  foreign_predicate/2,
  150                  current_library/5)).  151
  152:- (   current_prolog_flag(open_shared_object, true)
  153   ->  true
  154   ;   print_message(warning, shlib(not_supported)) % error?
  155   ).  156
  157% The flag `res_keep_foreign` prevents deleting  temporary files created
  158% to load shared objects when set  to   `true`.  This  may be needed for
  159% debugging purposes.
  160
  161:- create_prolog_flag(res_keep_foreign, false,
  162                      [ keep(true) ]).
 use_foreign_library(+FileSpec) is det
 use_foreign_library(+FileSpec, +Options:list) is det
Load and install a foreign library as load_foreign_library/1,2 and register the installation using initialization/2 with the option now. This is similar to using:
:- initialization(load_foreign_library(foreign(mylib))).

but using the initialization/1 wrapper causes the library to be loaded after loading of the file in which it appears is completed, while use_foreign_library/1 loads the library immediately. I.e. the difference is only relevant if the remainder of the file uses functionality of the C-library.

As of SWI-Prolog 8.1.22, use_foreign_library/1,2 is in provided as a built-in predicate that, if necessary, loads library(shlib). This implies that these directives can be used without explicitly loading library(shlib) or relying on demand loading.

  188                 /*******************************
  189                 *           DISPATCHING        *
  190                 *******************************/
 find_library(+LibSpec, -Lib, -Delete) is det
Find a foreign library from LibSpec. If LibSpec is available as a resource, the content of the resource is copied to a temporary file and Delete is unified with true.
  198find_library(Spec, TmpFile, true) :-
  199    '$rc_handle'(Zipper),
  200    term_to_atom(Spec, Name),
  201    setup_call_cleanup(
  202        zip_lock(Zipper),
  203        setup_call_cleanup(
  204            open_foreign_in_resources(Zipper, Name, In),
  205            setup_call_cleanup(
  206                tmp_file_stream(binary, TmpFile, Out),
  207                copy_stream_data(In, Out),
  208                close(Out)),
  209            close(In)),
  210        zip_unlock(Zipper)),
  211    !.
  212find_library(Spec, Lib, Copy) :-
  213    absolute_file_name(Spec, Lib0,
  214                       [ file_type(executable),
  215                         access(read),
  216                         file_errors(fail)
  217                       ]),
  218    !,
  219    lib_to_file(Lib0, Lib, Copy).
  220find_library(Spec, Spec, false) :-
  221    atom(Spec),
  222    !.                  % use machines finding schema
  223find_library(foreign(Spec), Spec, false) :-
  224    atom(Spec),
  225    !.                  % use machines finding schema
  226find_library(Spec, _, _) :-
  227    throw(error(existence_error(source_sink, Spec), _)).
 lib_to_file(+Lib0, -Lib, -Copy) is det
If Lib0 is not a regular file we need to copy it to a temporary regular file because dlopen() and Windows LoadLibrary() expect a file name. On some systems this can be avoided. Roughly using two approaches (after discussion with Peter Ludemann):
See also
- https://github.com/fancycode/MemoryModule for Windows
  247lib_to_file(Res, TmpFile, true) :-
  248    sub_atom(Res, 0, _, _, 'res://'),
  249    !,
  250    setup_call_cleanup(
  251        open(Res, read, In, [type(binary)]),
  252        setup_call_cleanup(
  253            tmp_file_stream(binary, TmpFile, Out),
  254            copy_stream_data(In, Out),
  255            close(Out)),
  256        close(In)).
  257lib_to_file(Lib, Lib, false).
  258
  259
  260open_foreign_in_resources(Zipper, ForeignSpecAtom, Stream) :-
  261    term_to_atom(foreign(Name), ForeignSpecAtom),
  262    zipper_members_(Zipper, Entries),
  263    entries_for_name(Entries, Name, Entries1),
  264    compatible_architecture_lib(Entries1, Name, CompatibleLib),
  265    zipper_goto(Zipper, file(CompatibleLib)),
  266    zipper_open_current(Zipper, Stream,
  267                        [ type(binary),
  268                          release(true)
  269                        ]).
 zipper_members_(+Zipper, -Members) is det
Simplified version of zipper_members/2 from library(zip). We already have a lock on the zipper and by moving this here we avoid dependency on another library.
To be done
- : should we cache this?
  279zipper_members_(Zipper, Members) :-
  280    zipper_goto(Zipper, first),
  281    zip_members__(Zipper, Members).
  282
  283zip_members__(Zipper, [Name|T]) :-
  284    zip_file_info_(Zipper, Name, _Attrs),
  285    (   zipper_goto(Zipper, next)
  286    ->  zip_members__(Zipper, T)
  287    ;   T = []
  288    ).
 compatible_architecture_lib(+Entries, +Name, -CompatibleLib) is det
Entries is a list of entries in the zip file, which are already filtered to match the shared library identified by Name. The filtering is done by entries_for_name/3.

CompatibleLib is the name of the entry in the zip file which is compatible with the current architecture. The compatibility is determined according to the description in qsave_program/2 using the qsave:compat_arch/2 hook.

The entries are of the form 'shlib(Arch, Name)'

  304compatible_architecture_lib([], _, _) :- !, fail.
  305compatible_architecture_lib(Entries, Name, CompatibleLib) :-
  306    current_prolog_flag(arch, HostArch),
  307    (   member(shlib(EntryArch, Name), Entries),
  308        qsave_compat_arch1(HostArch, EntryArch)
  309    ->  term_to_atom(shlib(EntryArch, Name), CompatibleLib)
  310    ;   existence_error(arch_compatible_with(Name), HostArch)
  311    ).
  312
  313qsave_compat_arch1(Arch1, Arch2) :-
  314    qsave:compat_arch(Arch1, Arch2), !.
  315qsave_compat_arch1(Arch1, Arch2) :-
  316    qsave:compat_arch(Arch2, Arch1), !.
 qsave:compat_arch(Arch1, Arch2) is semidet
User definable hook to establish if Arch1 is compatible with Arch2 when running a shared object. It is used in saved states produced by qsave_program/2 to determine which shared object to load at runtime.
See also
- foreign option in qsave_program/2 for more information.
  326:- multifile qsave:compat_arch/2.  327
  328qsave:compat_arch(A,A).
  329
  330entries_for_name([], _, []).
  331entries_for_name([H0|T0], Name, [H|T]) :-
  332    shlib_atom_to_term(H0, H),
  333    match_filespec(Name, H),
  334    !,
  335    entries_for_name(T0, Name, T).
  336entries_for_name([_|T0], Name, T) :-
  337    entries_for_name(T0, Name, T).
  338
  339shlib_atom_to_term(Atom, shlib(Arch, Name)) :-
  340    sub_atom(Atom, 0, _, _, 'shlib('),
  341    !,
  342    term_to_atom(shlib(Arch,Name), Atom).
  343shlib_atom_to_term(Atom, Atom).
  344
  345match_filespec(Name, shlib(_,Name)).
  346
  347base(Path, Base) :-
  348    atomic(Path),
  349    !,
  350    file_base_name(Path, File),
  351    file_name_extension(Base, _Ext, File).
  352base(_/Path, Base) :-
  353    !,
  354    base(Path, Base).
  355base(Path, Base) :-
  356    Path =.. [_,Arg],
  357    base(Arg, Base).
  358
  359entry(_, Function, Function) :-
  360    Function \= default(_),
  361    !.
  362entry(Spec, default(FuncBase), Function) :-
  363    base(Spec, Base),
  364    atomic_list_concat([FuncBase, Base], '_', Function).
  365entry(_, default(Function), Function).
  366
  367                 /*******************************
  368                 *          (UN)LOADING         *
  369                 *******************************/
 load_foreign_library(:FileSpec) is det
 load_foreign_library(:FileSpec, +Options:list) is det
Load a shared object or DLL. After loading the Entry function is called without arguments. The default entry function is composed from =install_=, followed by the file base-name. E.g., the load-call below calls the function install_mylib(). If the platform prefixes extern functions with =_=, this prefix is added before calling. Options provided are below. Other options are passed to open_shared_object/3.
install(+Function)
Installation function to use. Default is default(install), which derives the function from FileSpec.
    ...
    load_foreign_library(foreign(mylib)),
    ...
Arguments:
FileSpec- is a specification for absolute_file_name/3. If searching the file fails, the plain name is passed to the OS to try the default method of the OS for locating foreign objects. The default definition of file_search_path/2 searches <prolog home>/lib/<arch> on Unix and <prolog home>/bin on Windows.
See also
- use_foreign_library/1,2 are intended for use in directives.
  400load_foreign_library(Library) :-
  401    load_foreign_library(Library, []).
  402
  403load_foreign_library(Module:LibFile, InstallOrOptions) :-
  404    (   is_list(InstallOrOptions)
  405    ->  Options = InstallOrOptions
  406    ;   Options = [install(InstallOrOptions)]
  407    ),
  408    with_mutex('$foreign',
  409               load_foreign_library(LibFile, Module, Options)).
  410
  411load_foreign_library(LibFile, _Module, _) :-
  412    current_library(LibFile, _, _, _, _),
  413    !.
  414load_foreign_library(LibFile, Module, Options) :-
  415    retractall(error(_, _)),
  416    find_library(LibFile, Path, Delete),
  417    asserta(loading(LibFile)),
  418    retractall(foreign_predicate(LibFile, _)),
  419    catch(Module:open_shared_object(Path, Handle, Options), E, true),
  420    (   nonvar(E)
  421    ->  delete_foreign_lib(Delete, Path),
  422        assert(error(Path, E)),
  423        fail
  424    ;   delete_foreign_lib(Delete, Path)
  425    ),
  426    !,
  427    '$option'(install(DefEntry), Options, default(install)),
  428    (   entry(LibFile, DefEntry, Entry),
  429        Module:call_shared_object_function(Handle, Entry)
  430    ->  retractall(loading(LibFile)),
  431        assert_shlib(LibFile, Entry, Path, Module, Handle)
  432    ;   foreign_predicate(LibFile, _)
  433    ->  retractall(loading(LibFile)),    % C++ object installed predicates
  434        assert_shlib(LibFile, 'C++', Path, Module, Handle)
  435    ;   retractall(loading(LibFile)),
  436        retractall(foreign_predicate(LibFile, _)),
  437        close_shared_object(Handle),
  438        findall(Entry, entry(LibFile, DefEntry, Entry), Entries),
  439        throw(error(existence_error(foreign_install_function,
  440                                    install(Path, Entries)),
  441                    _))
  442    ).
  443load_foreign_library(LibFile, _, _) :-
  444    retractall(loading(LibFile)),
  445    (   error(_Path, E)
  446    ->  retractall(error(_, _)),
  447        throw(E)
  448    ;   throw(error(existence_error(foreign_library, LibFile), _))
  449    ).
  450
  451delete_foreign_lib(true, Path) :-
  452    \+ current_prolog_flag(res_keep_foreign, true),
  453    !,
  454    catch(delete_file(Path), _, true).
  455delete_foreign_lib(_, _).
 unload_foreign_library(+FileSpec) is det
 unload_foreign_library(+FileSpec, +Exit:atom) is det
Unload a shared object or DLL. After calling the Exit function, the shared object is removed from the process. The default exit function is composed from uninstall_, followed by the file base-name.
  466unload_foreign_library(LibFile) :-
  467    unload_foreign_library(LibFile, default(uninstall)).
  468
  469unload_foreign_library(LibFile, DefUninstall) :-
  470    with_mutex('$foreign', do_unload(LibFile, DefUninstall)).
  471
  472do_unload(LibFile, DefUninstall) :-
  473    current_library(LibFile, _, _, Module, Handle),
  474    retractall(current_library(LibFile, _, _, _, _)),
  475    (   entry(LibFile, DefUninstall, Uninstall),
  476        Module:call_shared_object_function(Handle, Uninstall)
  477    ->  true
  478    ;   true
  479    ),
  480    abolish_foreign(LibFile),
  481    close_shared_object(Handle).
  482
  483abolish_foreign(LibFile) :-
  484    (   retract(foreign_predicate(LibFile, Module:Head)),
  485        functor(Head, Name, Arity),
  486        abolish(Module:Name, Arity),
  487        fail
  488    ;   true
  489    ).
  490
  491system:'$foreign_registered'(M, H) :-
  492    (   loading(Lib)
  493    ->  true
  494    ;   Lib = '<spontaneous>'
  495    ),
  496    assert(foreign_predicate(Lib, M:H)).
  497
  498assert_shlib(File, Entry, Path, Module, Handle) :-
  499    retractall(current_library(File, _, _, _, _)),
  500    asserta(current_library(File, Entry, Path, Module, Handle)).
  501
  502
  503                 /*******************************
  504                 *       ADMINISTRATION         *
  505                 *******************************/
 current_foreign_library(?File, ?Public)
Query currently loaded shared libraries.
  511current_foreign_library(File, Public) :-
  512    current_library(File, _Entry, _Path, _Module, _Handle),
  513    findall(Pred, foreign_predicate(File, Pred), Public).
 foreign_library_property(?File, ?Property) is nondet
True when Property is a property of the foreign library File. Currently defined properties are:
module(Module)
Module context into which the library was loaded
entry(Entry)
Name of the install function used.
absolute_file_name(Path)
Absolute file name for File when known.
predicate(Pred)
Predicate registered by calling the entry function.
  529foreign_library_property(File, module(Module)) :-
  530    current_library(File, _Entry, _Path, Module, _Handle).
  531foreign_library_property(File, entry(Entry)) :-
  532    current_library(File, Entry, _Path, _Module, _Handle).
  533foreign_library_property(File, absolute_file_name(Path)) :-
  534    current_library(File, _Entry, Path, _Module, _Handle).
  535foreign_library_property(File, predicate(Pred)) :-
  536    current_library(File, _Entry, _Path, _Module, _Handle),
  537    foreign_predicate(File, Pred).
  538
  539
  540                 /*******************************
  541                 *            RELOAD            *
  542                 *******************************/
 reload_foreign_libraries
Reload all foreign libraries loaded (after restore of a state created using qsave_program/2.
  549reload_foreign_libraries :-
  550    findall(lib(File, Entry, Module),
  551            (   retract(current_library(File, Entry, _, Module, _)),
  552                File \== -
  553            ),
  554            Libs),
  555    reverse(Libs, Reversed),
  556    reload_libraries(Reversed).
  557
  558reload_libraries([]).
  559reload_libraries([lib(File, Entry, Module)|T]) :-
  560    (   load_foreign_library(File, Module, [install(Entry)])
  561    ->  true
  562    ;   print_message(error, shlib(File, load_failed))
  563    ),
  564    reload_libraries(T).
  565
  566
  567                 /*******************************
  568                 *     CLEANUP (WINDOWS ...)    *
  569                 *******************************/
  570
  571/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  572Called from Halt() in pl-os.c (if it  is defined), *after* all at_halt/1
  573hooks have been executed, and after   dieIO(),  closing and flushing all
  574files has been called.
  575
  576On Unix, this is not very useful, and can only lead to conflicts.
  577- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  578
  579unload_all_foreign_libraries :-
  580    current_prolog_flag(unload_foreign_libraries, true),
  581    !,
  582    forall(current_library(File, _, _, _, _),
  583           unload_foreign(File)).
  584unload_all_foreign_libraries.
 unload_foreign(+File)
Unload the given foreign file and all `spontaneous' foreign predicates created afterwards. Handling these spontaneous predicates is a bit hard, as we do not know who created them and on which library they depend.
  593unload_foreign(File) :-
  594    unload_foreign_library(File),
  595    (   clause(foreign_predicate(Lib, M:H), true, Ref),
  596        (   Lib == '<spontaneous>'
  597        ->  functor(H, Name, Arity),
  598            abolish(M:Name, Arity),
  599            erase(Ref),
  600            fail
  601        ;   !
  602        )
  603    ->  true
  604    ;   true
  605    ).
  606
  607
  608:- if(current_predicate(win_add_dll_directory/2)).
 win_add_dll_directory(+AbsDir) is det
Add AbsDir to the directories where dependent DLLs are searched on Windows systems. This call uses the AddDllDirectory() API when provided. On older Windows systems it extends %PATH%.
Errors
- existence_error(directory, AbsDir) if the target directory does not exist.
- domain_error(absolute_file_name, AbsDir) if AbsDir is not an absolute file name.
  621win_add_dll_directory(Dir) :-
  622    win_add_dll_directory(Dir, _),
  623    !.
  624win_add_dll_directory(Dir) :-
  625    prolog_to_os_filename(Dir, OSDir),
  626    getenv('PATH', Path0),
  627    atomic_list_concat([Path0, OSDir], ';', Path),
  628    setenv('PATH', Path).
  629
  630% Environments such as MSYS2 and  CONDA   install  DLLs in some separate
  631% directory. We add these directories to   the  search path for indirect
  632% dependencies from ours foreign plugins.
  633
  634add_dll_directories :-
  635    current_prolog_flag(msys2, true),
  636    !,
  637    env_add_dll_dir('MINGW_PREFIX', '/bin').
  638add_dll_directories :-
  639    current_prolog_flag(conda, true),
  640    !,
  641    env_add_dll_dir('CONDA_PREFIX', '/Library/bin'),
  642    ignore(env_add_dll_dir('PREFIX', '/Library/bin')).
  643add_dll_directories.
  644
  645env_add_dll_dir(Var, Postfix) :-
  646    getenv(Var, Prefix),
  647    atom_concat(Prefix, Postfix, Dir),
  648    win_add_dll_directory(Dir).
  649
  650:- initialization
  651    add_dll_directories.  652
  653:- endif.  654
  655		 /*******************************
  656		 *          SEARCH PATH		*
  657		 *******************************/
  658
  659:- dynamic
  660    user:file_search_path/2.  661:- multifile
  662    user:file_search_path/2.  663
  664:- if((current_prolog_flag(apple, true),
  665       current_prolog_flag(bundle, true))).  666user:file_search_path(foreign, swi('../../PlugIns/swipl')).
  667:- elif(current_prolog_flag(apple_universal_binary, true)).  668user:file_search_path(foreign, swi('lib/fat-darwin')).
  669:- elif((current_prolog_flag(windows, true),
  670	 current_prolog_flag(bundle, true))).  671user:file_search_path(foreign, swi(bin)).
  672:- else.  673user:file_search_path(foreign, swi(ArchLib)) :-
  674    current_prolog_flag(arch, Arch),
  675    atom_concat('lib/', Arch, ArchLib).
  676:- endif.  677
  678                 /*******************************
  679                 *            MESSAGES          *
  680                 *******************************/
  681
  682:- multifile
  683    prolog:message//1,
  684    prolog:error_message//1.  685
  686prolog:message(shlib(LibFile, load_failed)) -->
  687    [ '~w: Failed to load file'-[LibFile] ].
  688prolog:message(shlib(not_supported)) -->
  689    [ 'Emulator does not support foreign libraries' ].
  690
  691prolog:error_message(existence_error(foreign_install_function,
  692                                     install(Lib, List))) -->
  693    [ 'No install function in ~q'-[Lib], nl,
  694      '\tTried: ~q'-[List]
  695    ]