[modified 8/16/2007]

Updated 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 recently updated the SWIG module that supports generation of interface code suitable for Allegro Common Lisp. (SWIG stands for Simplified Wrapper and Interface Generator.)

The update is available at www.swig.org. It is part of the current SWIG release and is undergoing continual updates as new releases are made available. You can download it by visiting http://www.swig.org/survey.html, filling out the form and continuing on to the download page. You can also check out the bleeding edge repository via subversion at http://www.swig.org/svn.html.

New features in the updated interface

The new interface provides support for:

  • Enhanced C++ support to the Allegro CL module. C wrappers needed to interface with C++ are automatically generated.
  • Improved generation of foreign types based on the C/C++ types for use in defining the Foreign Function Interface on the Lisp side. The earlier interface passed everything as a (* :void). The new interface uses the actual types.
  • Using typemaps for better control of type conversions and code generation in the generated Lisp and C++ wrapper code.
  • CLOS wrapping of pointers returned from foreign space. This makes it easier to differentiate pointers in user code. The wrapping objects can be passed directly to Foreign Function calls.
  • Defun wrapping of Foreign Function calls, allowing for a more lispy interface. Conversion, of Lisp objects to foreign objects can be done in the wrapping defun via the use of typemaps. Memory management of foreign allocated code can be handled inside the wrapped defuns and so need not clutter up your application code.
  • Overload dispatching implemented on the Lisp side using generic functions. A single function interface for calling overloaded functions, or functions with defaulted arguments. In the case of ambiguous overloads, the user can give preference to one overload over another.
  • Templates and synonymous types.
  • Providing more information to identifier-converter functions. Users can use these functions to create the symbols on the Lisp side of their interface.

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. When interfacing to C++ code, the situation is even worse. Due to name mangling, and various C++ language features, it is very difficult to call C++ directly. In addition to writing the foreign-function definitions for the interface, it is also necessary to write C wrapper functions that then call the appropriate C++ routines. The new Allegro CL module for SWIG allows you to automate this process if you have access to the C/C++ header files for the library.

To use SWIG effectively you need to be familiar with foreign function calls to properly use generated interfaces. See foreign-functions.htm.

First, 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.

A C Example

We start with a file test.i with a %module directive. Thus the file starts with:

/* 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(opaque_handle h, int *x);

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

% swig -allegrocl test.i

you will end up with a file called test.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 may be unexpected and the output file may be hard to find unless this feature is kept in mind.

In test.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 :test, 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 calls to def-foreign-type for compute_arg_t, compute_res_t, and opaque_handle.

One way in which the updated interface differs from the old is that the interface attempts to use the same types as specified by the user. Instead of generic references to primitive double, function parameters and return types in the generated code are specified using forms like:

#.(swig-insert-id "compute_arg_t" ())

When the generated .cl file is loaded, these will be expanded into the symbol that identifies the appropriate foreign (or, in some cases, CLOS) type.

Likewise, pointers passed through the interface are not all translated to :foreign-address. Instead, a suitable foreign-type definition will be used in the def-foreign-call. Pointers that are returned to lisp from foreign-code are packed into a CLOS ff:foreign-pointer wrapper. In C++ mode, non-primitive pointer types will be wrapped in a CLOS wrapper that inherits from ff:foreign-pointer (more on this in the C++ example). While this addition to the interface makes it easier to distinguish pointers from integer values -- and in some cases, pointers from other pointers -- it is always important to be very careful when using them. Misuse of pointers in Lisp is just as dangerous as in C/C++ and 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 do not 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.

A C++ Example

Generating a C++ interface is almost the same as doing so for C, but SWIG must be told that it will be parsing C++ constructs.

/* sample c++ interface file */

%module tcpp


class OBJ {
};

namespace polygons {
  class RECT : public OBJ {
   public: 
    float height;
    float width;
  };

  class CIRCLE : public OBJ {
   public:
    float radius;
  };

  float area(RECT *r);
  float area(CIRCLE *c);

}

To run SWIG on this interface file, you would enter the following command:

% swig -allegrocl -c++ tcpp.i

This will create the file tcpp.cl (based on the %module name) containing the Allegro CL FFI interface code, and tcpp_wrap.cxx, which contains the automatically generated C wrappers to the C++ code.

A number of new constructs are present in C++ generated interfaces.

Like with a C-generated interface, a package is defined based on the module name. A package is also defined for each namespace that appears in the interface. These namespace packages are named using the Allegro CL dot notation for packages. In the example above, a package named tcpp is created for the module itself. The symbol naming the class OBJ will be interned in this package. A second package tcpp.polygons is also created, containing all definitions from this namespace: the class objects for RECT and CIRCLE, along with the wrappers for the overloaded 'area' function.

Wrappers for any public class member and functions are generated. This includes any overloaded operators. By default, SWIG will also include wrappers for constructor/destructors.

Where pointers to c++ classes are being returned to lisp, they are automatically wrapped in a CLOS instance. For example, the swig-defun generated for the CIRCLE constructor looks as follows:

(swig-defun ("CIRCLE" "ACL_polygons__new_CIRCLE__SWIG_0" :type :constructor)
  (:void)
  (:returning ((* #.(swig-insert-id "CIRCLE" ("polygons"))) )
   :strings-convert t)
  (make-instance '#.(swig-insert-id "CIRCLE" ("polygons") :type :class)
                 :foreign-address (swig-ff-call)))

The call to (swig-ff-call) is replaced by the call to the foreign wrapper in tcpp_wrap.cxx called ACL_polygons__new_CIRCLE__SWIG_0. The pointer is wrapped in the tcpp.polygons::CIRCLE CLOS object before being returned.

The example function area is wrapped in a swig-defmethod and swig-dispatcher form rather than a swig-defun. By this method, the interface provides a single entry point for calls to overloaded (or defaulted argument) functions. The dispatcher function is a simple wrapping defun, invokes a generic-function with the appropriate number of arguments passed. In this example, there is only the 1 argument generic-function. The swig-defmethod forms define the two methods, one accepting a CLOS class of type CIRCLE, the other of type RECT.

More information on the types of wrapping and ways the interface can be user-configured is available in the Allegro CL section of the SWIG documentation.

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