| Allegro CL version 9.0 Unrevised from 8.2 to 9.0. 8.2 version |
This document contains the following sections:
1.0 Using An Example Allegro CL Based DLL: fact.dllAllegro 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.htm 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).
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 8. 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:
int initialize_factorial()
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
Here is the examples/dll/fact.c file containing the source code for the fact DLL. The functions InitializeLisp and RemoteCommand are documented in Section 6.0 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 Section 6.0 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.
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.
Your C/C++ code and Lisp code should perform or do the following:
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.
After calling InitializeLisp(), but before calling any of your other Lisp code, you must call a custom initialization Lisp function using RemoteCommand() (again, see Section 6.0 LNKACL DLL Exported Routines). The custom initialization function is described in steps 6 and 7 below.
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.
The factorial() function in the above example illustrates how to do this.
Remember to name the image so it matches your InitializeLisp() argument.
Here is example code (it is similar to the factorial example, above, and like it uses start-customs):
(mp:start-customs "Factorial")
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.
*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.
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:
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.)
In addition, you may have to provide any DLL's required by your C/C++ compiler.
In this section, we describe the functions InitializeLisp(), RemoteCommand(), TerminateLisp(), and GetLispThread().
int InitializeLisp( char *image_file_name, char *sys_dir, int make_console )
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.
int RemoteCommand( char *command_buf, int wait_for_message )
command_buf
:
string to be processed by eval in Lisp thread
wait_for_message
:
0, return asynchronously
void TerminateLisp()
unsigned int GetLispThread()
Copyright (c) 1998-2019, Franz Inc. Oakland, CA., USA. All rights reserved.
This page was not revised from the 8.2 page.
Created 2012.5.30.
| Allegro CL version 9.0 Unrevised from 8.2 to 9.0. 8.2 version |