Building and Using an Allegro Common Lisp Based DLL
Allegro Common Lisp developers can build DLL's that they and other C/C++ programmers may use in the same manner as they use any other Windows function library. Below is an example that illustrates the steps necessary to construct and deploy an ACL based DLL.
Sections:
Using An Example ACL Based DLL: fact.dll
Building the Example DLL: fact.dll
Building the Companion DXL File: fact.dxl
Building Your Own Lisp Based DLL
Deploying Your Lisp Based DLL
LNKACL DLL Exported Routines
Using An Example ACL Based DLL: fact.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()
Parameters
none
Return Value
1
initialization successful
0
initialization failure
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 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];
i f (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 ftest.c -Foftest.obj
link -nologo -debug -out:ftest.exe ftest.obj fact.lib
ftest 123
12146304367025329675766243241881295855454217088483382315328918161829235892362167
66883115696061264020217073583522129404778259109157041165147218602951990626164673
0733907419814952960000000000000000000000000000
Building the Example DLL: fact.dll
Here is the fact.c file containing the source code for the fact DLL:
#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';
}
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)
These functions are documented in the LNKACL DLL Exported Routines
section.
Here are the compilation and linking commands:
cl -nologo -Od -c -Zi -I -W3 -G3 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.
Building the Companion DXL File: fact.dxl
Here is the fact.cl file containing the custom fact.dxl code:
(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-long) (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:process-run-function
"Factorial"
#'(lambda ()
(let ((ct 0))
(loop
(mp:process-wait
"Wait for immigrant"
#'(lambda ()
(> (mp::thread-test :waiting-threads nil)
0)))
(incf ct)
(mp:make-immigrant-process
(format nil "CB-~d" ct))))))
(set_factorial_callback (ff:register-function #'factorial-callback nil
t)))
These Lisp forms are used to create the fact.dxl file:
(load "lnk.fasl") ;; provided by Franz
(compile-file "fact.cl")
(load "fact.fasl")
(sys::resize-areas :old 512000 :verbose t :global-gc t :pack-areas nil)
(dumplisp :name "fact.dxl")
The above commands are entered into a non-IDE Allegro Common Lisp session.
Building Your Own Lisp Based DLL
Your C/C++ code and Lisp code should include the following:
1. Your C/C++ code must initialize Lisp by calling InitializeLisp().
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(). The custom initialization function is described in steps 6 and 7.
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 lnkacl.fasl file provided by Franz into your custom Lisp image.
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):
(mp:process-run-function
"put a useful name here"
#'(lambda ()
(let ((ct 0))
(loop
(mp:process-wait
"Wait for immigrant"
#'(lambda ()
(> (mp::thread-test :waiting-threads nil)
0)))
(incf ct)
(mp:make-immigrant-process
(format nil "CB-~d" ct))))))
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, 'ff:def-foreign-call to make the exported function available, ff:defun-c-callable to define your exportable Lisp functionality, and 'ff:register-function to generate an address to provide to the function defined in step 3. If your C/C++ code process return values, you will most likely want to use the 'ff:register-function 'convert optional argument.
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, and your custom Lisp image file.
These files can reside in any of the following directories:
The directory from which the calling application loaded
The current directory
The windows system directory
The windows directory
One of the PATH environmental variable directories
For Windows 95 usage, you must provide the aclxxx.vxd file (provided by Franz)
This file should be placed in the windows\system directory.
In addition, you may have to provide any DLL's required by your C/C++ compiler.
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
1, open debugging console
0, don't open debugging console
Return Value
1
found image file
0
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:
The directory from which the calling application loaded
The current directory
The windows system directory
The windows directory
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.
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.
void TerminateLisp()
Remarks
Terminates Lisp environment and closes debugging console if necessary.
InitializeLisp() may be called to restart the Lisp environment
after this routine has been called.
unsigned int GetLispThread()
Return Value
The Lisp thread handle is returned.