Search code examples
cswi-prolog

How to create a .dll file with gcc including swi-prolog.h?


I'm trying to create a .dll file with gcc on Windows 10 and I'm getting an error. The mylib.c file:

#include <windows.h>
#include "C:/Programme/swipl/include/SWI-Prolog.h"

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

  if ( PL_get_atom_chars(to, &a) )
  { MessageBox(NULL, a, "DLL test", MB_OK|MB_TASKMODAL);

    PL_succeed;
  }

  PL_fail;
}

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

I included the the swi-prolog.h file because the compiler complained that it can't find the header file like here.

So compiling doesn't make any problems via:

> gcc -c mydll.c

But if I want to create the .dll file via:

> gcc -shared -o mydll.dll mydll.o

I get the following errors:

C:\Users\Julian\AppData\Local\Temp\ccAblUMK.o:mylib.c:(.text+0x14): undefined reference to `PL_get_atom_chars'
C:\Users\Julian\AppData\Local\Temp\ccAblUMK.o:mylib.c:(.text+0x76): undefined reference to `PL_register_foreign'
collect2.exe: error: ld returned 1 exit status

Does anyone know this problem?

Update: I also tried it with swipl-ld, but it doesn't work either. (Just changed the file a little bit, but the issue stays same)

enter image description here


Solution

  • I do not have a ready.made answer as I have never tried this but this question:

    Building a shared library using gcc on Linux and MinGW on Windows

    seems to touch on the problem:

    I'm having trouble with generating a build setup that allows shared libraries to be built in both Linux and Windows using gcc and MinGW, respectively. In Linux, a shared library doesn't have to resolve all dependencies at compile time; whereas, this appears to the case in Windows.

    That being said, the manual says at Linking Foreign Modules that there is a special linking tool:

    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 -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(). Here is a simple example file mylib.c, which creates a Windows MessageBox: ...

    However, it seems that you need to add the option -shared which is not given above!

    The manual for swipl-ld is here

    but is given in the context of building a standalone executable based on swipl libraries and additional C-code, which is is different.

    swipl-ld is indeed in the distribution, in the bin directory.

    Note that you need a "module stub" to introduce the foreign functions to the swipl runtime. Like so:

    calling binaries compiled from C from SWI-Prolog

    Testing whether it works

    On Linux

    Just a short test, but on Linux (I will have to reactive my Windows machine a bit later)

    In directory /home/rost/compilec:

    C-code file mylib.c

    #include <SWI-Prolog.h>
    #include <stdio.h>
    
    static foreign_t
    pl_say_hello(term_t to)
    { char *a;
      if ( PL_get_atom_chars(to, &a)) {
        printf("Hello, %s\n",a);
        PL_succeed;
      }
      else {
        PL_fail;
      }
    }
    
    install_t
    install_mylib()
    { PL_register_foreign("say_hello", 1, pl_say_hello, 0);
    }
    

    Prolog file mylib.pl declaring a "stub module":

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

    After the above have been created, build a shared library using swipl-ld by compiling mylib.c:

    $ swipl-ld -v -shared -o mylib mylib.c 
    

    Output is:

    eval `swipl --dump-runtime-variables`
             PLBASE="/usr/local/logic/swipl/lib/swipl"
             PLARCH="x86_64-linux"
             PLSOEXT="so"
             PLLIBDIR="/usr/local/logic/swipl/lib/swipl/lib/x86_64-linux"
             PLLIB="-lswipl"
             PLTHREADS="yes"
    /usr/bin/cc -c -fPIC -D_REENTRANT -D__SWI_PROLOG__ -I/usr/local/logic/swipl/lib/swipl/include -o mylib.o mylib.c
    /usr/bin/cc -o mylib.so -shared -Wl,-rpath=/usr/local/logic/swipl/lib/swipl/lib/x86_64-linux mylib.o -L/usr/local/logic/swipl/lib/swipl/lib/x86_64-linux -lswipl
    rm mylib.o
    

    cc happens to be gcc version 10.2.1 (run cc -v to see that)

    Now start swipl and call the provided predicate.

    On the Prolog Toplevel (i.e. the Prolog command line), first extend the search path for foreign libraries:

    ?- assertz(file_search_path(foreign,'/home/rost/compilec')).
    true.
    

    Consult the "stub module":

    ?- [mylib].
    true.
    

    Then call the predicate in question:

    ?- say_hello('World').
    Hello, World
    true.
    

    On Windows

    (More precisely, on Windows 10. Awkward to work with.)

    Get SWI-Prolog

    I have installed SWI-Prolog using the installer available at Download SWI-Prolog stable versions, namely SWI-Prolog 8.2.4-1 for Microsoft Windows (64 bit).

    After installation has been performed, the SWI-Prolog file tree can be found in C:\Program Files\swipl and the bin subdirectory indeed contains all we all.

    Get MinGW

    Now get MinGW. This being 64-bit Windows, so we will get the 64-bit version (which supplants the 32-bit version, and is also being maintained by another team).

    Downloads can be found at this page.

    Download the MinGW64-builds.

    Sidenote

    I also tried Win-builds but no luck, the win-builds installer fails trying to download packages because the SHA-3 checksum do not match. Maybe it's too old?

    End of sidenote

    Clicking on MinGW64-builds. will redirect us to Sourceforge (ah, nsotalgia!) and will dump an installer into the Downloads directory. It passes the antivirus, ESet Antivirus says that it has good confidence in this executable, and computing the SHA-256 checksum then googling it brings good results. Excellent.

    So run the installer. I filled in the mysterious settings that it requests as follows:

    Version 8.1.0     - Latest MingW version
    Architecture      - Proposes "i686" (why), switch to "x86_64"
    Threads           - "posix" or "win32"? Let's take "posix"
    Exception         - "seh", why not, this seems to be the modern exception format
    Build revision 0  - Ok I guess
    

    (For SEH vs SLJ exception format, see this question)

    After then download of packages & installation finishes, you end up with a lot of tools in

    C:\Program Files\mingw-w64\x86_64-8.1.0-posix-seh-rt_v6-rev0\mingw64\bin
    

    Now we can build our DLL. Open the "Command Prompt" window:

    > set PATH=C:\Program Files\swipl\bin;C:\Program Files\mingw-w64\x86_64-8.1.0-posix-seh-rt_v6-rev0\mingw64\bin;%PATH%
    
    > cd (to where the mylib.c and mylib.pl files reside)
    
    > swipl-ld -v -shared -o mylib mylib.c
    

    We see:

    eval `swipl.exe --dump-runtime-variables`
         PLBASE="c:/program files/swipl"
         PLARCH="x64-win64"
         PLSOEXT="dll"
         PLLIBDIR="c:/program files/swipl/bin"
         PLLIB="-lswipl"
         PLTHREADS="yes"
     gcc.exe -c -D_REENTRANT -D__WINDOWS__ -D_WINDOWS -D__SWI_PROLOG__ -I"c:/program files/swipl/include" -o mylib.obj mylib.c
     gcc.exe -o mylib.dll -shared mylib.obj -L"c:/program files/swipl/bin" -lswipl
     rm mylib.obj
    

    and the file mylib.dll has been successfully created.

    Sidenote:

    If you check it out on Linux, file says that this is a PE/COFF file:

    $ file mylib.dll
    mylib.dll: PE32+ executable (DLL) (console) x86-64, for MS Windows
    

    nm(1) works on the DLL (sweet!) and lists the exported procedure:

    $ nm mylib.dll  | grep say
    000000006e5813b0 t pl_say_hello
    

    Well, that DLL file is just a serialized structure like any other,

    End of sidenote

    Can swipl use it the DLL? Yes it can!

    Start swipl. Suppose we have the mylib.dll in directory C:\Users\francisbacon\Documents\Compiling:

    1 ?-
    assertz(file_search_path(foreign,'C:/Users/francisbacon/Documents/Compiling')).
    true.
    
    2 ?-
    [mylib].
    true.
    
    3 ?-
    say_hello('World').
    Hello, World
    true.