All Manuals > Delivery User Guide > 4 Delivering your Application

4.4 Delivering a dynamic library

Depending on how your application needs to interoperate with other software, you may want to build it as a DLL (also referred to as a dynamic library) rather than an executable.

4.4.1 Simple delivery of a dynamic library

Supply the names of your library's exports in a list value for the deliver keyword :dll-exports. Each name in :dll-exports should be a string naming a Lisp function defined by fli:define-foreign-callable.

The deliver function argument should be nil, because a dynamic library does not have a startup function.

Supply the file type of the delivered image in the deliver file argument if necessary.

As when delivering a LispWorks executable, start at deliver level 0. Increase the delivery level, if desired, after you have debugged your library. Whenever possible, debug your code running in the LispWorks development image. If the problem only occurs when your code runs inside a dynamic library, you may be able to debug it on your development machine in a dynamic library created by save-image rather than deliver.

4.4.2 Using the dynamic library

A Microsoft Windows application should use LoadLibrary to load the DLL and GetProcAddress to find the address of the exported names. On other platforms the application should use dlopen and dlsym.

On some platforms there are special requirements for a program that loads a LispWorks dynamic library, as follows:

Linux
The program should be linked with libpthread.so.
FreeBSD
The program should be linked with libpthread.so.
x86/x64 Solaris
The program should be compiled and linked multi-threaded, for example using the -mt option to Oracle's cc.
macOS
No special requirements.

A dynamic library can be loaded into LispWorks using fli:register-module, and this is a convenient way of testing it. See 4.4.5 Further example for an example.

For more information about the behavior of LispWorks dynamic libraries see the chapter "LispWorks as a dynamic library" in the LispWorks® User Guide and Reference Manual.

4.4.3 Simple Windows example

The script below creates hello.dll.

-------------------- hello.lisp -------------------------
(in-package "CL-USER")
(load-all-patches)
;; The signature of this function is suitable for use with
;; rundll32.exe.
(fli:define-foreign-callable ("Hello"
                              :calling-convention :stdcall)
    ((hwnd w:hwnd)
     (hinst w:hinstance)
     (string :pointer)
     (cmd-show :int))
  (capi:display-message "Hello world")
  ;; quit when library's job is done
  (dll-quit))
 
(deliver nil "hello" 0 :dll-exports '("Hello") :interface :capi)
---------------------------------------------------------

You can build the DLL with this command line:

MS-DOS> lispworks-8-0-0-x86-win32.exe -build hello.lisp

and you can test it with this command line:

rundll32 hello.dll,Hello
4.4.3.1 Using the Application Builder

The Application Builder tool provides another way to build and test hello.dll:

  1. In the LispWorks for Windows IDE do Works > Tools > Application Builder.
  2. Set the Build script to be your file hello.lisp and do Works > Build > Build to build the DLL.
  3. Do Works > Build > Run With Arguments. Enter rundll32 in the Execute pane, enter hello.dll,Hello in the Arguments pane, and press OK to test the library.

4.4.4 Simple non-Windows example

See the example in the LispWorks library at:

examples/delivery/dynamic-library/

This example creates a LispWorks dynamic library and also a test program for loading it on non-Windows platforms.

To build and run the example, follow the instructions in README.txt.

4.4.5 Further example

This example builds a dynamic library which in principle could be loaded by any application and called to calculate square numbers.

For illustrative purposes, we show how to load the dynamic library into the LispWorks development image. This illustrates some platform-specific initialization. Then we use the library, ensure it exits cleanly, and finally delete the dynamic library file.

Note that on non-Windows platforms, to deliver a dynamic library, the build machine must have a C compiler installed.

For convenience the code is presented without external files. To run it, copy each form in turn and enter it at the Listener prompt.

  1. Define a path for the dynamic library:
    (defvar *dynamic-library-path*
      (merge-pathnames (make-pathname :name "CalculateSquareExample"
                                      :type scm::*object-file-suffix*)
                       (get-temp-directory)))
    
  2. Define a function to create the dynamic library:
    (defun save-dynamic-library ()
      (let* ((file (open-temp-file :file-type "lisp"))
             (ns (namestring file)))
        (format file
          "
            (fli:define-foreign-callable (calculate-square :result-type :int)
                ((arg :int))
              (* arg arg))
        (deliver nil ~s 5 :dll-exports '(\"calculate_square\"))"
          (namestring *dynamic-library-path*))
        (close file)
        (sys:call-system-showing-output (list (lisp-image-name)
                                              "-build"
                                             ns ))
        (delete-file file nil)))
    
  3. Create the dynamic library:
    (save-dynamic-library)
    
  4. Define functions to use the dynamic library:
    (fli:define-foreign-function (my-quit-lispworks "QuitLispWorks")
        ((force :int)
         (milli-timeout :int))
      :result-type :int
      ;; specifying :module ensures the foreign function finds
      ;;  the function in our module
      :module 'my-dynamic-library)
    (fli:define-foreign-function (my-init-lispworks "InitLispWorks")
        ((milli-timeout :int)
         (base-address (:pointer-integer :int))
         (reserve-size (:pointer-integer :int)) ; really size_t
         )
      :result-type :int
      :module 'my-dynamic-library)
    (fli:define-foreign-function calculate-square
        ((arg :int))
      :result-type :int
      :module 'my-dynamic-library)
    
  5. Define a function to load the dynamic library, use it, and then unload it:
    (defun run-the-dynamic-library ()
      (fli:register-module 'my-dynamic-library
                           :connection-style :immediate
                           :file-name *dynamic-library-path*)
      ;; Windows and macOS can detect and resolve memory clashes.
      ;; On other platforms, tell the library to load at different
      ;;  address (that is, relocate) because otherwise it will use
      ;;  the same address as the running LispWorks development image.
      ;; Relocation may be needed when loading a LispWorks dynamic 
      ;; library in other applications.
      #-(or mswindows darwin)
      (my-init-lispworks 0
                         #+lispworks-64bit #x5000000000
                         #+lispworks-32bit #x50000000
                         0)
      (dotimes (x 4)
        (format t "square of ~d = ~d~%" x
                (calculate-square x)))
      (my-quit-lispworks 0 1000)
      (fli:disconnect-module 'my-dynamic-library))
    
  6. Use the dynamic library:
    (run-the-dynamic-library)
    

    Check the output to see that it computed square numbers.

  7. (optional) Delete the dynamic library file:
    (delete-file *dynamic-library-path* nil)
    

4.4.6 More about building dynamic libraries

On non-Windows platforms, you can supply files to be included in the library via the deliver keyword argument :dll-added-files. This is useful if you need to write wrappers around calls into the library.

You can specify whether your LispWorks dynamic library initializes itself automatically on loading with the deliver keyword argument :automatic-init. For more information see "Initialization of the dynamic library" in the LispWorks® User Guide and Reference Manual.


Delivery User Guide - 01 Dec 2021 19:35:04