Allegro CL support in SWIG

SWIG is a tool for automatically generating interfaces to C/C++ libraries for use with non-C/C++ programming languages. We have added a module to SWIG to support generation of interface code suitable for Allegro Common Lisp. (SWIG stands for Simplified Wrapper and Interface Generator.)

The necessary files can be downloaded from

http://franz.com/ftp/pub/swig/swig-1.3.22-fi.tar.gz

The file can also be downloaded from ftp://ftp.franz.com/pub/swig/swig-1.3.22-fi.tar.gz. Once that file is downloaded, it should be unzipped and untarred, and then, in the directory created, run the configure script:

./configure --prefix=/usr/local/swig
make
make install

The standard SWIG documentation is included in the image.

Why SWIG is useful

Without SWIG, def-foreign-call is used to create a link between a non-Lisp function residing in a library and Lisp. But if the library you are interfacing to has many functions, it can become very tedious to write all the suitable def-foreign-call forms. The Allegro CL module for SWIG allows you to automate this process if you have access to the C/C++ header files for the library.

You will need to be familiar with foreign function calls to properly use generated interfaces. See foreign-functions.htm.

To use SWIG you need to start with an interface file. Here is an example. The interface file will be called test.i.

The first thing in the interface file should be a %module directive. The %module directive is used to define the package in which the foreign calls and constants will be defined. It is also used to determine the name of the generated .cl file.

/* Example file: test.i */

%module mypackage

The remainder of the interface file should be populated with the C function prototypes, typedefs, structure definitions, and preprocessor directives which define the API.

/* Example file: test.i */

%module mypackage

typedef double compute_arg_t;
typedef double compute_res_t;

#define COMPUTE_STYLE_SQUARE 1
#define COMPUTE_STYLE_SQRT 2

compute_res_t compute(compute_arg_t value, int style);

typedef void *opaque_handle;

#define DEVICE_OP_CLOSE 1
#define DEVICE_OP_CRYPT_MODE 2

#define DEV_ID_PRINTER 0x9943
#define DEV_ID_SERIAL 0x9944

opaque_handle open_device(int dev_id); 
int operate_device(opaque_handle h, int op);
void write_device_number(int *x);

If you run this interface file through swig, like so:

 % swig -allegrocl test.i

you will end up with a file called mypackage.cl. Note: unless told to do otherwise, swig generates the output file in the same directory as the input file (rather than in the current working directory if it is different). This can be quite surprising sometimes.

In mypackage.cl you will find generated code that will not be pretty to look at. It is not intended for human consumption, but you will see some constructs that you will recognize: a defpackage form for :mypackage, defconstants for COMPUTE_STYLE_SQUARE and COMPUTE_STYLE_SQRT, def-foreign-calls for compute, open_device, operate_device, and write_device_number.

You will also notice that SWIG has broken down the typedefs back into their lowest level types. E.g., the argument type and return type in the def-foreign-call for compute are specified as double.

You may notice that all pointers have been translated to :foreign-address, regardless of what they point to. Make sure you are just as careful with pointers as you would be in C. Misuse of pointers can lead to very confusing garbage collection errors.

To use this fictional test API, start up Lisp, load the shared library object that contains the code that implements the API, and then load in the generated mypackage.cl file. You could also load the mypackage.cl file first. The order doesn't matter as long as the shared library is loaded before you attempt to call one of the API functions.

:ld ./test.so
:cl mypackage.cl

Now you can call the API functions.

(format t "result is ~g~%" (compute 123d0 COMPUTE_STYLE_SQUARE))

(let ((handle (open_device DEV_ID_SERIAL))
      (numbuffer (make-array 1 :element-type '(signed-byte 32))))
   (if (/= 1 (operate_device handle DEVICE_OP_CRYPT_MODE))
       (error "operate_device call failed"))
   (setf (aref numbuffer 0) 100000)
   (write_device_number handle numbuffer) 
   (operate_device handle DEVICE_OP_CLOSE))

Notice in the sample code that we don't just pass the number 100000 to write_device_number. That is because write_device_number is expecting a pointer to an integer. If we just passed 100000, then write_device_number would try to access address 100000, which would be incorrect. Instead, we create an array of one element and store the number 100000 there and we pass the array in. When you pass an array to a foreign function, the address of the first element is what is really passed.

If you have the C header files for the API that you are interfacing to, you can save a lot of time by telling SWIG that you want it to parse the header file to extract the function prototypes, constants, etc. You do this using the %include directive.

/* Example file: test.i using %include */

%module mypackage

%include "/usr/include/testapi.h"

Important note: SWIG does not follow #include directives by default. This is so that it won't generate a bunch of code for stuff that really isn't part of the API you're working with. For example, if testapi.h had "#include <stdio.h>" at the top of it, you wouldn't want all the stdio.h stuff to be made part of your mypackage.cl file. This means that if there are relevant #include files in testapi.h, you will need to add %include directives to the .i file so SWIG will look at them. (If you know what you are doing, you can pass the -includeall command line option to SWIG to make it follow all #include directives.)

You can also cause your own code to be inserted into the .cl file. To do so, use the %wrapper %{ %} syntax.

/* Example: test.i using %wrapper% */

%module mypackage 

%wrapper %{
  (defun extra-function (...) 
      ...)
%}

%include "/usr/include/testapi.h"

Limitations in the current SWIG/Allegro CL module

  • C++ classes are not supported.
  • Unions are not supported.
  • Nested structures are not supported.
  • Bitfields within structures are not supported.
  • Functions that take string arguments will need to be handled by manually.
  • Some automatically-generated defconstants may be incorrect (if extracted from a complex #define preprocessor directive)
  • Global variables are not supported.
  • Enumeration types are not supported.

Some of these limitations may be removed in the future. Some are due to deficiencies in SWIG, others are due to incomplete implementation of the Allegro CL SWIG module.

Copyright © 2023 Franz Inc., All Rights Reserved | Privacy Statement Twitter