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.
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 Type Reference.
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.
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;
} POINT;
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.
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.
The names of the FLI types are designed to best match the types that are defined by Clang, which is used on Mac OS X, 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)
single-float
double-float
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.
vector-char2
vector-char3
vector-char4
vector-char8
vector-char16
vector-char32
vector-uchar2
vector-uchar3
vector-uchar4
vector-uchar8
vector-uchar16
vector-uchar32
vector-short2
vector-short3
vector-short4
vector-short8
vector-short16
vector-short32
vector-ushort2
vector-ushort3
vector-ushort4
vector-ushort8
vector-ushort16
vector-ushort32
vector-int2
vector-int3
vector-int4
vector-int8
vector-int16
vector-uint2
vector-uint3
vector-uint4
vector-uint8
vector-uint16
vector-long2
vector-long3
vector-long4
vector-long8
vector-ulong2
vector-ulong3
vector-ulong4
vector-ulong8
vector-float2
vector-float3
vector-float4
vector-float8
vector-float16
vector-double2
vector-double3
vector-double4
vector-double8
In addition, vector-long1
and vector-ulong1
are defined as immediate 64-bit signed and unsigned integers, because Clang defines them like that.
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.
vector-double<count>
and vector-float<count>
, the Lisp vector must either have element type double-float
or single-float
, or have element type t
and contain elements of type float
.t
and contain elements that fit into the FLI vector.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.
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)
C compilers other than Clang can also define vector types in various ways:
vector_size
attribute, for example, vector_double4
would be defined by:float32x4_t
matches vector-float4
.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 Mac OS X, 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 Mac OS X 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 Mac OS X, 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 Mac OS X 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.
LispWorks Foreign Language Interface User Guide and Reference Manual - 29 Sep 2017