All Manuals > Foreign Language Interface User Guide and Reference Manual > 2 FLI Types

2.2 Aggregate types

Aggregate types are types such as arrays, strings and structures. The internal structure of an aggregate type is not transparent in the way that immediate types are. For example, two structures may have the same size of 8 bytes, but one might partition its bytes into two integers, whereas the other might be partitioned into a byte, an integer, and another byte. The FLI provides a number of functions to manipulate aggregate types. A feature of aggregate types is that they are usually accessed through the use of pointers, rather than directly.

2.2.1 Arrays

The FLI has two predefined array types: the :c-array type, which corresponds to C arrays, and the :foreign-array type. The two types are the same in all aspects but one: if you attempt to pass a :c-array by value through a foreign function, the starting address of the array is what is actually passed, whereas if you attempt to pass a :foreign-array in this manner, an error is raised.

For examples on the use of FLI arrays refer to :c-array and :foreign-array in 8 Type Reference.

2.2.2 Strings

The FLI provides two foreign types to interface Lisp and C strings, :ef-wc-string and :ef-mb-string.

The :ef-mb-string converts between a Lisp string and an external format C multi-byte string. A maximum number of bytes must be given as a limit for the string size.

The :ef-wc-string converts between a Lisp string and an external format C wide character string. A maximum number of characters must be given as a limit for the string size.

For more information on converting Lisp strings to foreign language strings see the string types :ef-mb-string, :ef-wc-string, and the string functions convert-from-foreign-string, convert-to-foreign-string, and with-foreign-string.

2.2.3 Structures and unions

The FLI provides the :struct and :union types to interface Lisp objects with the C struct and union types.

To define types to interface with C structures, the FLI macro define-c-struct is provided. In the next example it is used to define a FLI structure, tagpoint:

(fli:define-c-struct tagpoint 
  (x :long) 
  (y :long)
  (visible (:boolean :byte))

This structure would interface with the following C structure:

typedef struct tagPOINT {
    LONG x; 
    LONG y; 
    BYTE visible;

The various elements of a structure are known as slots, and can be accessed using the FLI foreign slot functions foreign-slot-names, foreign-slot-type and foreign-slot-value, and the macro with-foreign-slots. For example, the next commands set point equal to an instance of tagPOINT, and set the Lisp variable names equal to a list of the names of the slots of tagPOINT.

(setq point (fli:allocate-foreign-object :type 'tagpoint))
(setq names (fli:foreign-slot-names point))

The next command finds the type of the first element in the list names, and sets the variable name-type equal to it.

(setq name-type (fli:foreign-slot-type point (car names)))

Finally, the following command sets point-to equal to a pointer to the first element of point, with the correct type.

(setq point-to (fli:foreign-slot-pointer point (car names) 
                                        :type name-type))

The above example demonstrates some of the functions used to manipulate FLI structures. The FLI :union type is similar to the :struct type, in that the FLI slot functions can be used to access instances of a union. The convenience FLI function define-c-union is also provided for the definition of specific union types.

2.2.4 Vector types

Vector types are types that correspond to C vector types. These are handled by the C compiler in a special way, and therefore when you pass or return them to/from foreign code by value you must declare them correctly. Vector type names

The names of the FLI types are designed to best match the types that are defined by Clang, which is used on macOS, iOS and FreeBSD and is optionally available on other operating systems. For every C/Objective-C type of the form vector_<type><count>, there is an FLI type of the form fli:vector-<scalar fli type><count>. For example, the C/Objective-C type vector_double8 is matched by the FLI type fli:vector-double8.

The scalar fli types and their matching Common Lisp types are:

(signed-byte 8)
(unsigned-byte 8)
(signed-byte 16)
(unsigned-byte 16)
(signed-byte 32)
(unsigned-byte 32)
(signed-byte 64)
(unsigned-byte 64)

The count can be 2, 3, 4, 8, 16 (for elements of 32 bits or less) or 32 (for elements of 16 bits or less). The restrictions mean that the maximum size of a vector is 64 bytes and the maximum count is 32.

Note that long and ulong are always 64 bits in this context, even on 32-bit where the C type long is 32 bits.

The full list of types:




















































In addition, vector-long1 and vector-ulong1 are defined as immediate 64-bit signed and unsigned integers, because Clang defines them like that. Vector type values

When passing an argument that is declared as any of the FLI vector types, the value needs to be a Lisp vector of the correct length or a foreign pointer to the FLI vector type.

When a FLI vector type is passed into Lisp, either because it is a returned value from a foreign function or an argument to a foreign callable, it is automatically converted to a Lisp vector of the correct length and element type. This also occurs when accessing a value using foreign-slot-value, foreign-aref and dereference. Using a foreign pointer to a vector type

When you have a foreign pointer to a vector type, you can access individual elements using foreign-aref, or convert the vector into a Lisp vector using dereference. The reverse operations can be performed using the setf form or foreign-aref and dereference. For example:

(let ((d4-poi (fli:allocate-foreign-object 
               :type 'fli:vector-double4)))
  (setf (fli:dereference d4-poi) #(0d0 1d0 2d0 3d0))
  (format t "Collected values: ~s~%"
             (loop for x below 4 
               collect (fli:foreign-aref d4-poi x)))
  (setf (fli:foreign-aref d4-poi 3) -3d0)
  (format t "Dereference after setf: ~s~%" 
           (fli:dereference d4-poi)))
Collected values: (0.0D0 1.0D0 2.0D0 3.0D0)
Dereference after setf: #(0.0D0 1.0D0 2.0D0 -3.0D0)

Normally there is no reason to allocate a foreign object for a vector type as in the example above. You would, however, encounter such a pointer if you have foreign code that calls into Lisp passing it an argument that is a pointer to a vector type, and your Lisp code needs to set the values in it. In this case, you will need to declare the argument type as (:pointer vector-double4) and then set it like this:

(fli:define-foreign-callable my-callable
   ((d4-poi (:pointer fli:vector-double4)))
  (let ((lisp-v4 (my-compute-d4-values)))
     (setf (fli:dereference d4-poi) lisp-v4)))
(defun my-compute-d4-values () 
   (vector 3.5d0 7d0 9d23 0.1d0)))

Note that if you call a function that takes a pointer to a vector type, you can use the FLI types :reference, :reference-pass and :reference-return to pass and return values without having to explicitly allocate a foreign pointer. For example, if the C function my_function takes a pointer to vector_double2 and fills it like this:

void my_function (vector_double2* d2_poi) {
   (*d2_poi)[0] = 3.0;
   (*d2_poi)[1] = 4.0;

then in Lisp you can call it by:

(fli:define-foreign-function my-function 
   ((d2-po1 (:reference-return fli:vector-double2))))
(my-function)  ; returns #(3D0 4D0) Notes on foreign vector types

C compilers other than Clang can also define vector types in various ways:

On 32-bit x86, vector types can be passed either with or without using SSE2. The Lisp FLI definitions must pass/receive arguments in the same way as the C compiler that was used to compile the foreign code. On macOS, this is always with SSE2, so this is not an issue, but on other platforms (Linux, FreeBSD, Solaris) the situation is not clear. What the Lisp definitions do is controlled by *use-sse2-for-ext-vector-type*.

When using vector-char2 and vector-uchar2 on x86_64 platforms and the C compiler is Clang or a derivative, you need to check that you have the latest version of the C compiler, because earlier versions of Clang compiled these types differently from later versions. This affects macOS too because the Xcode C compiler is based on Clang. You can check the version of the C compiler by executing cc -v in a shell. On macOS, you need to check that you have LLVM 8.0 or later. If you have Clang, you need to check that you have version 3.9 or later.

On macOS x86_64, the treatment of vector_char2 and vector_uchar2 changed between LLVM 6.0 and 8.0. LispWorks is compatible with LLVM 8.0. You can check which version of LLVM you have by executing cc -v in a shell.

When a structure is passed by value and it contains one of more fields whose types are vector types, it is also important to declare the type correctly in Lisp, otherwise the wrong data may be passed. That is because the machine registers that are used to pass such structures may be different from the registers that are used to pass seemingly equivalent structures that are defined without vector types. Such structures are commonly used to represent matrices.

Foreign Language Interface User Guide and Reference Manual - 01 Dec 2021 19:34:56