ToC DocOverview CGDoc RelNotes FAQ Index PermutedIndex
Allegro CL version 11.0

Building and Using an Allegro Common Lisp Based DLL

Allegro CL supports building shared libraries that can be used by C/C++ programs in the same manner as they use any other function libraries. On Windows, the library files are DLL files. This document describes how to create such DLL's. See unix-shared-library.html for information on creating similar files on UNIX machines.

Below is an example that illustrates the steps necessary to construct and deploy an Allegro CL based DLL. The files associated with this example are in the examples/dll/ subdirectory of the Allegro directory.

You must have at least an Entrerprise Allegro CL license, because the example uses generate-application (and only Enterprise and above customers have access to that function).


1.0 Using An Example Allegro CL Based DLL: fact.dll

The basic instructions for this example are as follows:

  1. Update your PATH environment variable to include [Allegro directory]/examples/dll/

  2. Bring up a DOS Window/Command Prompt

  3. Change to the directory [Allegro directory]/examples/dll/

  4. Clean out the files from any previous build attempts by executing

    del *.fasl
    del *.ilk
    del *.exe
    del *.pdb
    del *.obj
    del fact.dll
    rd fact /s /q
  5. Compile the file ftest.c by executing

    cl -nologo -Od -c -Zi -I -W3 -G3 -MT ftest.c -Foftest.obj
  6. Compile the file fact.c by executing

    cl -nologo -Od -c -Zi -I -W3 -G3 -MT fact.c -Fofact.obj
  7. Copy linkacl.lib from the Allegro directory to example/dll/

  8. Create fact.dll by executing

    link -nologo -dll -debug -out:fact.dll fact.obj lnkacl.lib
  9. Create the ftest executable by executing

    link -nologo -debug -out:ftest.exe ftest.obj fact.lib
  10. Compile and build the ACL components by executing ([runacl] means the command to run Allegro CL in a DOS box, something like [Allegro directory]\mlisp):

    [runacl] -L dist.cl -kill

    If instead you start Allegro CL from the Start menu, change the current directory to examples/dll/ by evaluating

    ;; Assuming the current directory is presently the Allegro directory
    (chdir "examples/dll/")

The fact.dll example provides a function that computes a factorial (1 x 2 x 3 x ... x (n-1) x n). Since Lisp supports bignum arithmetic, it is easy to write such a factorial function in Lisp. Without Lisp, a C/C++ programmer must address integer overflow for larger input arguments.

The example fact library exports two functions:

  1. int initialize_factorial()

  2. int factorial ( unsigned int x, char *result, int string_size )

Here is a file called examples/dll/ftest.c that uses the fact library. Note that it is a console application; you could use the same functionality in a Windows application:

#include <stdio.h>

void usage();

main( int argc, char *argv[] )
{
   int i, n, ret;
   char result_string[4096];

   if (argc != 2)
   {
      usage();
      exit( 1 );
   }
   if (strlen( argv[1] ) > 3 )
   {
      usage();
      exit( 1 );
   }
   for (i = 0; *(argv[1] + i); i++)
   {
      if (!isdigit( *(argv[1] + i) ))
      {
         usage();
         exit( 1 );
      }
   }
   sscanf( argv[1], "%d", &n );
   if (initialize_factorial() != 1)
   {
      fprintf(stderr, "factorial startup error" );
      exit( 1 );
   }
   if ((ret = factorial( n, result_string, 4096 )) != 1)
   {
      fprintf(stderr, "factorial internal error" );
      exit( 1 );
   }
   printf( "%s", result_string );
   exit( 0 );
}

void
usage()
{
   fprintf( stderr, "usage: ftest n, where n is an integer between 0 and 999" );
}

Here is the compilation, linking, and a sample run:

cl -nologo -Od -c -Zi -I -W3 -G3 -MT ftest.c -Foftest.obj
link -nologo -debug -out:ftest.exe ftest.obj fact.lib

ftest 123
12146304367025329675766243241881295855454217088483382315328918161829235892362167
66883115696061264020217073583522129404778259109157041165147218602951990626164673
0733907419814952960000000000000000000000000000

2.0 Building the Example DLL: fact.dll

Here is the examples/dll/fact.c file containing the source code for the fact DLL. The functions InitializeLisp and RemoteCommand are documented in LNKACL DLL Exported Routines.

#include <windows.h>
#include <string.h>

#define Dllexport _declspec(dllexport)

int lisp_initialized = 0;

int (*lisp_factorial)(int, char *, int) = 0;

/* returns 0 if lisp previously initialized
returns 1 if initialization successful
returns -1 if initialization failed
*/
Dllexport int initialize_factorial()
{
   if (!lisp_initialized) 
   {
      if (InitializeLisp( "fact.dxl", 0, 0 ) == 0) 
      {
         return -1;
      }

/* don't return until lisp can process commands */

      if (RemoteCommand( "(initialize-factorial)", 1 ) != 1)
         return -1;
      lisp_initialized = 1;
      return 1;
   }  else 
   {
      return 0;
   }
}

/* Lisp calls this to set factorial callback address */
Dllexport void set_factorial_callback( int (*fcb) (int, char *, int) )
{
   lisp_factorial = fcb;
}

/* users of the factorial library call this */
/* returns 1 if successful; 0 otherwise */
Dllexport int factorial ( unsigned int x, char *result, int string_size )
{
   if (!lisp_initialized || !lisp_factorial) {
      return -1;
   }
   switch ( (*lisp_factorial) ( x, result, string_size ))
   {
      case 1:
         return 1;
      case 0:
      default:
         *result = '\0';
         return 0;
   }
}

/* Lisp calls this to fill result string */
int Dllexport
copy_factorial_result( char *str1, char *str2, int n )
{
   strncpy( str1, str2, n - 1 );
   *(str2 + n) = '\0';
}

To repeat the note above, the above code uses two functions exported by the lnkacl library provided by Franz for building Lisp base DLL's. The relevant source code lines are:

if (InitializeLisp( "fact.dxl", 0, 0 ) == 0) 

and

if (RemoteCommand( "(initialize-factorial)", 1 ) != 1)

InitializeLisp and RemoteCommand are documented in LNKACL DLL Exported Routines.

Here are the compilation and linking commands:

cl -nologo -Od -c -Zi -I -W3 -G3 -MT fact.c -Fofact.obj
link -nologo -dll -debug -out:fact.dll fact.obj ..\..\lnkacl.lib

Note that lnkacl.lib file (provided by Franz) must be either in the current directory or in a directory named in the "lib" environmental variable. It is easiest to copy it to examples/dll/ from the Allegro directory.


3.0 Building the Companion DXL File: fact.dxl

You should update your PATH environment variable to include [Allegro directory]/examples/dll/. You should also either run Allegro CL from a DOS box/Command Prompt where the current directory is [Allegro directory]/examples/dll/ or you should change the current directory in the running Lisp to [Allegro directory]/examples/dll/ before loading examples/dll/dist.cl. You change the directory by evaluating:

;; Assuming the current directory is presently the Allegro directory
(chdir "examples/dll/")

A dxl file is an Allegro CL image file. It is created by generate-application from Lisp source files. The call to generate-application is in the file examples/dll/dist.cl and will be described below. Here is the examples/dll/fact.cl Lisp source file containing code needed for fact.dxl:

(in-package :user)

(defun factorial (x)            ; assumes caller will trap errors
   (cond
     ((= x 0) 1)
     (t (* x (factorial (1- x))))))

(eval-when (load eval)
   (load "fact.dll"))

(ff:def-foreign-call set_factorial_callback (address))

(ff:def-foreign-call copy_factorial_result 
   ((str1 :unsigned-nat) (str2 (* :char)) (n :int)))

(ff:defun-c-callable (factorial-callback :c)
     ((arg :unsigned-long) (string :unsigned-long) (length :unsigned-long))
   :long
   (handler-case
       (progn
           (copy_factorial_result string
               (format nil "~s" (factorial arg))
               length)
           1) ;; return 1 if successful
       (serious-condition (condition)
           0)) ;; return 0 if unsuccessful
   )

(defun initialize-factorial ()
   (mp:start-customs "Factorial")
   (set_factorial_callback (ff:register-function #'factorial-callback nil t)))

The examples/dll/dist.cl file can be loaded into Allegro CL to generate a distribution directory examples/dll/fact/ (under the Allegro CL installation directory) containing the fact.dxl and associated files. Before loading examples/dll/dist.cl, remove examples/dll/fact/, if it exists. See examples/dll/dist.cl contents for more information. This file assumes you have the generate-application functionality enabled in your Allegro CL. Also, as noted in the file, you must first generate the fact.dll and ftest.exe modules as described below.


4.0 Building Your Own Lisp Based DLL

Your C/C++ code and Lisp code should perform or do the following:

  1. Your C/C++ code must initialize Lisp by calling InitializeLisp() (see LNKACL DLL Exported Routines).

    You can do this with an exported initialization function as in the above example, or you can transparently wrap the initialization steps within your DLL's exported functions.

  2. Your C/C++ code must call a custom Lisp initialization function.

    After calling InitializeLisp(), but before calling any of your other Lisp code, you must call a custom initialization Lisp function using RemoteCommand() (again, see LNKACL DLL Exported Routines). The custom initialization function is described in steps 6 and 7 below.

  3. Your C/C++ code must provide at least one exported function to setup entry points.

    Your Lisp initialization function calls this routine to fill in addresses at runtime that your C/C++ code uses to invoke Lisp based functionality. In the example above, the set_factorial_callback() function provided this functionality.

  4. Provide C/C++ wrappers for the Lisp functionality you wish to export.

    The factorial() function in the above example illustrates how to do this.

  5. You must load the lnk.fasl file into your custom Lisp image. Ink.fasl comes from compiling the provided file examples/dll/Ink.cl.

    Remember to name the image so it matches your InitializeLisp() argument.

  6. Your custom Lisp initialization code must initiate a Lisp process that waits for remote callback invocation.

    Here is example code (it is similar to the factorial example, above, and like it uses start-customs):

    (mp:start-customs "Factorial")
  7. Your custom Lisp initialization code must call the exported function described in step 3 to setup callback entry points.

    You will use load to load your DLL, def-foreign-call to make the exported function available, defun-c-callable to define your exportable Lisp functionality, and register-function to generate an address to provide to the function defined in step 3.

    If your C/C++ code process returns values, you will most likely want to use the register-function convert optional argument.

  8. The Lisp functions defined by defun-c-callable and called from C programs should handle all Lisp errors and throws and return some success/failure indication to the C caller (see the factorial function for an example). Any Lisp throw past the point of call from C is likely to produce unpredictable results.

  9. Output to the stream bound to *initial-terminal-io* goes to the console window. Even if InitializeLisp() is called with make_console=0 then the console window will pop up when output is written.

    Input from the stream bound to *initial-terminal-io* will never return to the caller. All input to this stream is sent to the initial Lisp listener in the console window.


5.0 Deploying Your Lisp Based DLL

To allow building an application that uses a custom Lisp based DLL, you must provide the .lib file generated when you built your DLL.

The file must reside in a directory included in the LIB environmental variable, or it must reside in the directory where the build occurs. Remember that you must export any functions you wish to make available to applications using your DLL.

To allow a generated application to run, you must provide lnkacl.dll (provided by Franz), your custom DLL, your custom Lisp image file, and the Allegro CL license file. These files can reside in any of the following directories:

  1. The sys: directory as determined by the application (that is, the result of (translate-logical-pathname "sys:")). This will be the sys_dir argument to InitializeLisp, or if 0, the application's working directory. (Earlier versions of this document incorrectly said "the directory from which the calling application loaded". That directory may not be known and will not be searched unless it is also the directory described in the corrected text.)

  2. The current directory

  3. The windows system directory

  4. The windows directory

  5. One of the PATH environmental variable directories

In addition, you may have to provide any DLL's required by your C/C++ compiler.


6.0 LNKACL DLL Exported Routines

In this section, we describe the functions InitializeLisp(), RemoteCommand(), TerminateLisp(), and GetLispThread().

InitializeLisp

int InitializeLisp( char *image_file_name, char *sys_dir, int make_console )

RemoteCommand

int RemoteCommand( char *command_buf, int wait_for_message )

TerminateLisp()

void TerminateLisp()

GetLispThread()

unsigned int GetLispThread()


Copyright (c) Franz Inc. Lafayette, CA., USA. All rights reserved.

ToC DocOverview CGDoc RelNotes FAQ Index PermutedIndex
Allegro CL version 11.0