ACLWin 3.x FFI Compatibility Support

$Revision: 1.1 $

This document was provided to users of Allegro CL release 5.0/5.0.1 to assist in porting Allegro CL 3.0.1 for Windows applications to the new release. We provide it, essentially unedited, for any users of release 6.0 who might need it.

This document is for users porting code written for Allegro CL for Windows 3.0.x to Allegro CL 5.0.

The ACL 5.0 Foreign Function Interface differs from the ACLWin 3.x Foreign Function Interface. ACL 5.0 includes ACLWin 3.x FFI compatibility support to minimize the conversion effort developers with existing ACLWin 3.x applications must face. The new foreign function interface is described in foreign-functions.htm.

The document introduction.htm provides an overview of the Allegro CL documentation with links to all major documents. The document index.htm is an index with pointers to every documented object (operators, variables, etc.) The revision number of this document is below the title. These documents may be revised from time to time between releases.


Table of Contents

Accessing ACLWin 3.x FFI Compatibility Support
Areas Where You May Have to Change Your Code

Converting a C string into a Lisp string
ccallocate
cref: cpointers and handles
cref: cstructures
cref: floating point values
defun-callback
defun-dll
far-peek and far-poke
list-dll-libraries
common-lisp:null
rename-dll-libraries
unlink-dll
unlink-dll-functions

Improving performance
Table of functional equivalents


Accessing ACLWin 3.x FFI Compatibility Support

The :aclwffi module provides ACLWin 3.x FFI compatibility. To use this functionality, include a

(require :aclwffi)

form before compiling code containing ACLWin 3.x FFI calls. Remember that this module is required when generating a standalone application.

You should also evaluate the form

(setf ct::*default-allocation-type* nil)

before compiling code containing ACLWin 3.x FFI cref calls. See  improving performance for further discussion.


Areas Where You May Have to Change Your Code

Converting a C string into a Lisp string

From ACLWin documentation:

  1. (setq foo (ccallocate (char 256)))
  2. pass foo to a C function that fills the string
  3. (subseq foo 0 (strlen foo)) returns the lisp string

Step c. does not work in ACL 5.  Instead, use:

(ff:char*-to-string (cref (char 256) foo (0 &))) ; ACLWin syntax

or

(ff:char*-to-string
 (ff::fslot-address-typed '(:array :char 1) :foreign-static-gc foo 0)) ; ACL syntax

Currently, the FFI compatibility strlen function generates a warning, as in:

> (strlen foo)
Warning: See ACLWin FFI Compatibility notes for important information about handling strings
16
> (ff:char*-to-string (ff::fslot-address-typed '(:array :char 1) :foreign-static-gc foo 0))
"this is a string"
> 

There is an optional no warn argument to strlen, for situations where you have verified desired behavior and wish to suppress the warning:

> (strlen foo t)
16

ccallocate

The ccallocate macro allocates an object each time it is called. This is different than in ACLWin, where a single allocation occurs during compile time.

Here is a code example that shows a problem that results from this difference:

> (defun ctest (obj)
(let ((testobj (ccallocate (:long 10)))) (values testobj (eq obj testobj))))
ctest
> (ctest (ctest t))
#(#(30272048 t 901 nil nil 40 nil) 34773 67541
   1409474568 20 1931501856 1852404340
   220799591 10 0 ...)
nil

In ACLWin, a t result would have occurred.

Here is a suggested way to recode the above example to generate a t result:

> (let ((testobj (ccallocate (:long 10))))
     (defun ctest (obj)
         (values testobj (eq obj testobj))))
ctest
> (ctest (ctest t))
#(#(32577661 t 901 nil nil 40 nil) 178 55 993 57 0 0 0 0 0 ...)
t
>
cref: cpointers and handles

In ACLWin, C pointers are represented by cpointer objects. In ACL 5, they are represented by integers. The FFI compatibility package handles this difference transparently. However, if you have existing CLOS methods that specialize on cpointer objects or code that assumes that C pointers are cpointer objects, you must rewrite the code.

Here is an example:

ACLWin code:

(defun null-cpointer-p (object)
   (and object
          (typep object 'acl::cpointer)
          (= 0 (cpointer-value object))))

On ACL 5:

(defun null-cpointer-p (object)
    (and object (typep object 'integer) (= 0 object)))

The same issues apply to handles. In ACL 5, handles are integers.

cref: cstructures

In ACLWin, dereferencing a C pointer that contains a structure address results in the allocation of a cstructure on the Lisp heap, followed by a transparent copy. In ACL 5, dereferencing a pointer containing a structure address returns the address. The FFI compatibility package handles this difference transparently. However, you must rewrite code that depends on the ACLWin cstructure creation and copy.

Here is an example:

On ACLWin:

(defcstruct my-coord ((x :long) (y :long)))
(defcstruct my-line ((start (my-coord *)) (end (my-coord *))))

(setf foo (cref (my-curve *) cobj (* :start *))) ;; cobj is a C pointer to a my-curve
(do-a-c-free cobj) ;; calls a C function to free all memory
(cref my-coord foo :x)

On ACL 5:

(defcstruct my-coord ((x :long) (y :long)))
(defcstruct my-line ((start (my-coord *)) (end (my-coord *))))

(setf foo (cref (my-curve *) cobj (* :start *))) ;; cobj is a C pointer to a my-curve
(cref my-coord foo :x)
(do-a-c-free cobj) ;; don't free memory until the needed data is in Lisp

cref: floating point values

ACLWin has essentially only one internal floating point format - double float. ACL 5 has single float and double float. Currently you must insure that your code uses the correct format.

Examples:

On ACLWin:

(defcstruct my-coord ((x :double) (y :double)))
(setf (cref my-coord cobj :x) 1.23)

On ACL 5:

(defcstruct my-coord ((x :double) (y :double)))
(setf (cref my-coord cobj :x) 1.23d0) ;; the constant has to be a double float

If your code has a lot of constants, and all your C structures use double floats, you can avoid massive code changes by changing the default floating point read format:

(setf *read-default-float-format* 'double-float)

Here's another problem area example:

On ACLWin:

(setf (cref my-coord cobj :x) (float foo)) ;; foo is an integer

On ACL 5:

(setf (cref my-coord cobj :x) (float foo 1.d0)) ;; have to force conversion to double float

defun-callback

The ACLWin callback example's C code was incorrect. The correct code is:

int WINAPI
call_callback(int (WINAPI *pcb) (int, int, int), int a, int b, int c)
{
   return (*pcb) (a, b, c);
}

(The difference is the 'WINAPI' included in the function pointer declaration.)

In ACLWin, under some circumstances, C code with the incorrect declaration style would still work - the example did work, even though it was incorrect. In other circumstances, incorrect results would occur or a segmentation violation would occur. In ACL 5, incorrectly specified C code that invokes a callback will always fail.

Changing the C code is the recommended way to correct this situation. If you cannot do that, you can change your lisp code in the following manner:

Replace

(defun-callback cb1 ((a :long) (b :long) (c :long))
   (push (list 'cb1 a b c) *cb-stack*)
   (+ a b c))

with

(ff:register-function 
  (ff:defun-foreign-callable (cb1 :c)
     ((a :long) (b :long) (c :long))
      :unsigned-long (push (list 'cb1 a b c) *cb-stack*)
    (+ a b c))
:reuse t)

defun-dll

The :return-mode keyword is not supported. Code that depends on this keyword must be changed.

In ACLWin, the DLL specified in the defun-dll macro call is loaded the first time the function is called. In ACL 5, the DLL is loaded the first time the defun-dll macro invocation is evaluated, compiled, or the resulting compiled code is loaded.

far-peek and far-poke

These aren't implemented.

list-dll-libraries

In ACLWin, this function returns a list of keyword package symbols. In ACL 5, it returns a list of strings. In ACL 5, the 'all argument does not affect the result, since DLL's are loaded at defun-dll eval, compile, or resulting compiled code load time.

common-lisp:null

This constant is not available in ACL 5. Use ct:hnull instead.

rename-dll-libraries

Because foreign loading occurs when defun-dll forms are processed, this function is not available in the compatibility package.

unlink-dll

Because foreign loading occurs when defun-dll forms are processed, this function is not available in the compatibility package.

unlink-dll-functions

Because foreign loading occurs when defun-dll forms are processed, this function is not available in the compatibility package.


Improving performance

Evaluating the form

(setf ct::*default-allocation-type* nil)

before compiling code containing cref calls guarantees that memory accesses and sets will use the proper memory allocation model. The nil specifies that the memory allocation model will be determined at run time. This means that a lisp object will be examined each time the code containing the cref call is executed.

If you are certain that you know the proper allocation model, you can improve performance by setting the *default-allocation-type* variable at compile time. The relevant values are:

nil       determine the allocation model at runtime
:foreign  a callocate or ccallocate call generated the cref object argument
:c        the cref object argument was allocated in foreign code and returned to ACL

Here is an example:

(eval-when (compile)
    (setf ct::*default-allocation-type* :foreign))

(defun allocate-int-pointer (initial-value)
    (let ((new (callocate (:long *))))
        (setf (cref (:long *) new *) initial-value)
        new))

(eval-when (compile)
    (setf ct::*default-allocation-type* nil))

For *default-allocation-type* values other than nil, a runtime error occurs when the run time allocation model doesn't match the model specified at compile time.

Note that the performance improvement only occurs when the cref access arguments can be determined at compile time. For example:

(cref (:long *) foo (integer i))

is not a candidate for this performance improvement because the offset value i is determined at run time.

Table of functional equivalents

ACLWIN 3.0.x source code can be changed piece by piece to the new interface. The following table shows the correspondences between the old and new functions. For functions that have no equivalent, the entry is (same) and the symbol exists in the ct package.

PC symbols (exported from CT)

ACLWin symbol ACL symbol
*export-c-names* variable not applicable
callocate macro allocate-fobject
defctype macro def-foreign-type
defun-callback macro defun-foreign-callable
defun-dll macro def-foreign-call
get-callback-procinst register-foreign-callable
ccallocate macro allocate-fobject
cpointer-value macro (same)
cref macro (same)
cset macro (same)
csets macro (same)
default-callback-style variable not applicable
defcstruct macro def-foreign-type
deflhandle macro def-foreign-type
defshandle macro def-foreign-type
defun-c-callback defun-foreign-callable
defun-pascal-callback defun-foreign-callable
dll-handle (same)
far-peek not applicable
far-poke not applicable
handle-value macro (same)
handle= macro (same)
list-dll-libraries list-all-foreign-libraries
null constant ct:hnull
null-cpointer-p macro (same)
null-handle macro allocate-fobject
null-handle-p macro (same)
rename-dll-libraries not applicable
sizeof macro    sizeof-fobject
strlen (same)
unlink-dll unload-foreign-library
unlink-dll-functions (same)

Copyright (C) 1998-2000, Franz Inc., Berkeley, CA. All Rights Reserved.