ToC | DocOverview | CGDoc | RelNotes | FAQ | Index | PermutedIndex
| Allegro CL version 11.0

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()`

   - Parameters: none
   - Return Value: 1 if initialization is successful, 0 if initialization fails

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

   - Parameters:

     - x: the factorial argument
     - result: a string buffer large enough to hold the result
     - string_size: the string buffer size

     Remarks: The factorial() function returns the answer as a string to avoid C integer overflow problems.

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 )`

- Parameters:

  - `image_file_name`: Lisp image filename
  - `sys_dir`: directory Lisp will use as sys: for logical pathnames (if 0, the application's working directory will be used)
  - `make_console`: if 1, open debugging console, if 0, don't open debugging console. NOTE: this flag is only advisory. If the application writes to the stream bound to initial-terminal-io then the console window will pop up whether make_console was 0 or 1.

- Return Value: 1 if found image file, 0 if couldn't find image file

- See Also: TerminateLisp()

- Remarks:

  - The image file can be a complete path or just a file name. If just a file name, the following directories are searched, in order:

    1. The directory from which the calling application loaded
    2. The current directory
    3. The windows system directory
    4. The windows directory
    5. The PATH environmental variable directories

  - The debugging console is useful during development - you can view initialization messages, enter Lisp expressions, and manage Lisp debugging activities.

    Note that this routine returns asynchronously. The Lisp initialization takes some time to complete. Use a subsequent synchronous RemoteCommand() call if you desire a synchronous startup procedure or if you wish to verify that the Lisp startup procedure has completed successfully.

### RemoteCommand

`int RemoteCommand( char *command_buf, int wait_for_message )`

- Parameters:

  - `command_buf`: string to be processed by eval in Lisp thread

  - `wait_for_message`: 0, return asynchronously, 1, wait for completion before returning

- Return Value: 1, wait_for_message = 0 or successful completion, 0, Lisp error occurred during command, -1, Lisp thread no longer alive

- Remarks: Note that Windows messages are processed while waiting for successful command completion.

### TerminateLisp()

`void TerminateLisp()`

- Remarks:

  - Terminates Lisp environment and closes debugging console if necessary.

  - Problem with restarting Lisp after calling TerminateLisp(): because of a design flaw, InitializeLisp() may not be called to restart the Lisp environment after TerminateLisp() has been called. The problem is that TerminateLisp() takes time to actually kill the Lisp thread, but (essentially) reports that it has succeeded too early, and the initialization routines rely on this false report and fail when the report differs from the reality.

### GetLispThread()

`unsigned int GetLispThread()`

- Return Value: The Lisp thread handle is returned.

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

ToC | DocOverview | CGDoc | RelNotes | FAQ | Index | PermutedIndex
| Allegro CL version 11.0