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).
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) ]).
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 *******************************/
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), _)).
dlopen() and Windows LoadLibrary() expect a
file name. On some systems this can be avoided. Roughly using two
approaches (after discussion with Peter Ludemann):
shm_open() to create an anonymous file in
memory and than fdlopen() to link this.open(), etc. to
make dlopen() work on non-files. This is highly non-portably
though.fuse-zip on Linux.
This however fails if we include the resources as a string in
the executable.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 ]).
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 ).
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), !.
326:- multifile qsave:compat_arch/2. 327 328qsavecompat_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 *******************************/
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.
default(install),
which derives the function from FileSpec.
...
load_foreign_library(foreign(mylib)),
...
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(_, _).
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 *******************************/
511current_foreign_library(File, Public) :-
512 current_library(File, _Entry, _Path, _Module, _Handle),
513 findall(Pred, foreign_predicate(File, Pred), Public).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 *******************************/
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.
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)).
%PATH%.
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 686prologmessage(shlib(LibFile, load_failed)) --> 687 [ '~w: Failed to load file'-[LibFile] ]. 688prologmessage(shlib(not_supported)) --> 689 [ 'Emulator does not support foreign libraries' ]. 690 691prologerror_message(existence_error(foreign_install_function, 692 install(Lib, List))) --> 693 [ 'No install function in ~q'-[Lib], nl, 694 '\tTried: ~q'-[List] 695 ]
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 filemylib.c, which prints a "hello" message. Note that we use SWI-Prolog's Sprintf() rather than C standardprintf()to print the outout through Prolog'scurrent_outputstream, making the example work in a windowed environment. The standard Cprintf()works in a console environment, but this bypasses Prolog's output redirection. Also note the use of the standard Cbooltype, 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:The file
mylib.plcan be loaded as a normal Prolog file and provides the predicate defined in C. The generatedmylib.so(or.dll, etc.) must be placed in a directory searched for using the Prolog search pathforeign(see absolute_file_name/3). To load this from the current directory, we can use the-p alias=diroption:*/