1 ;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
3 ;;; cffi-ecl.lisp --- ECL backend for CFFI.
5 ;;; Copyright (C) 2005-2006, James Bielman <jamesjb@jamesjb.com>
7 ;;; Permission is hereby granted, free of charge, to any person
8 ;;; obtaining a copy of this software and associated documentation
9 ;;; files (the "Software"), to deal in the Software without
10 ;;; restriction, including without limitation the rights to use, copy,
11 ;;; modify, merge, publish, distribute, sublicense, and/or sell copies
12 ;;; of the Software, and to permit persons to whom the Software is
13 ;;; furnished to do so, subject to the following conditions:
15 ;;; The above copyright notice and this permission notice shall be
16 ;;; included in all copies or substantial portions of the Software.
18 ;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 ;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 ;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 ;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
22 ;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
23 ;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 ;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25 ;;; DEALINGS IN THE SOFTWARE.
30 (defpackage #:cffi-sys
31 (:use
#:common-lisp
#:alexandria
)
32 (:import-from
#:si
#:null-pointer-p
)
35 #:canonicalize-symbol-name-case
41 #:with-foreign-pointer
50 #:%foreign-funcall-pointer
51 #:%foreign-funcall-varargs
52 #:%foreign-funcall-pointer-varargs
53 #:%foreign-type-alignment
55 #:%load-foreign-library
56 #:%close-foreign-library
58 #:make-shareable-byte-vector
59 #:with-pointer-to-vector-data
62 #:%foreign-symbol-pointer
))
64 (in-package #:cffi-sys
)
67 ;;; ECL allows many ways of calling a foreign function, and also many
68 ;;; ways of finding the pointer associated to a function name. They
69 ;;; depend on whether the FFI relies on libffi or on the C/C++ compiler,
70 ;;; and whether they use the shared library loader to locate symbols
71 ;;; or they are linked by the linker.
75 ;;; ECL uses libffi to call foreign functions. The only way to find out
76 ;;; foreign symbols is by loading shared libraries and using dlopen()
81 ;;; ECL compiles FFI code as C/C++ statements. The names are resolved
82 ;;; at run time by the shared library loader every time the function
87 ;;; ECL compiles FFI code as C/C++ statements, but the name resolution
88 ;;; happens at link time. In this case you have to tell the ECL
89 ;;; compiler which are the right ld-flags (c:*ld-flags*) to link in
92 (defvar *cffi-ecl-method
*
94 #+(and dlopen
(not dffi
)) :dlopen
95 #-
(or dffi dlopen
) :c
/c
++
96 "The type of code that CFFI generates for ECL: :DFFI when using the
97 dynamical foreign function interface; :DLOPEN when using C code and
98 dynamical references to symbols; :C/C++ for C/C++ code with static
99 references to symbols.")
104 (pushnew 'no-long-long
*features
*)
105 (pushnew 'flat-namespace
*features
*)
109 (defun canonicalize-symbol-name-case (name)
110 (declare (string name
))
111 (string-upcase name
))
115 (defun %foreign-alloc
(size)
116 "Allocate SIZE bytes of foreign-addressable memory."
117 (si:allocate-foreign-data
:void size
))
119 (defun foreign-free (ptr)
120 "Free a pointer PTR allocated by FOREIGN-ALLOC."
121 (si:free-foreign-data ptr
))
123 (defmacro with-foreign-pointer
((var size
&optional size-var
) &body body
)
124 "Bind VAR to SIZE bytes of foreign memory during BODY. The
125 pointer in VAR is invalid beyond the dynamic extent of BODY, and
126 may be stack-allocated if supported by the implementation. If
127 SIZE-VAR is supplied, it will be bound to SIZE during BODY."
129 (setf size-var
(gensym "SIZE")))
130 `(let* ((,size-var
,size
)
131 (,var
(%foreign-alloc
,size-var
)))
134 (foreign-free ,var
))))
136 ;;;# Misc. Pointer Operations
138 (deftype foreign-pointer
()
141 (defun null-pointer ()
142 "Construct and return a null pointer."
143 (si:allocate-foreign-data
:void
0))
145 (defun inc-pointer (ptr offset
)
146 "Return a pointer OFFSET bytes past PTR."
147 (ffi:make-pointer
(+ (ffi:pointer-address ptr
) offset
) :void
))
149 (defun pointerp (ptr)
150 "Return true if PTR is a foreign pointer."
151 (typep ptr
'si
:foreign-data
))
153 (defun pointer-eq (ptr1 ptr2
)
154 "Return true if PTR1 and PTR2 point to the same address."
155 (= (ffi:pointer-address ptr1
) (ffi:pointer-address ptr2
)))
157 (defun make-pointer (address)
158 "Return a pointer pointing to ADDRESS."
159 (ffi:make-pointer address
:void
))
161 (defun pointer-address (ptr)
162 "Return the address pointed to by PTR."
163 (ffi:pointer-address ptr
))
165 ;;;# Shareable Vectors
167 ;;; This interface is very experimental. WITH-POINTER-TO-VECTOR-DATA
168 ;;; should be defined to perform a copy-in/copy-out if the Lisp
169 ;;; implementation can't do this.
171 (defun make-shareable-byte-vector (size)
172 "Create a Lisp vector of SIZE bytes that can passed to
173 WITH-POINTER-TO-VECTOR-DATA."
174 (make-array size
:element-type
'(unsigned-byte 8)))
176 (defmacro with-pointer-to-vector-data
((ptr-var vector
) &body body
)
177 "Bind PTR-VAR to a foreign pointer to the data in VECTOR."
178 `(let ((,ptr-var
(si:make-foreign-data-from-array
,vector
)))
183 (defconstant +translation-table
+
184 '((:char
:byte
"char")
185 (:unsigned-char
:unsigned-byte
"unsigned char")
186 (:short
:short
"short")
187 (:unsigned-short
:unsigned-short
"unsigned short")
189 (:unsigned-int
:unsigned-int
"unsigned int")
191 (:unsigned-long
:unsigned-long
"unsigned long")
193 (:long-long
:long-long
"long long")
195 (:unsigned-long-long
:unsigned-long-long
"unsigned long long")
196 (:float
:float
"float")
197 (:double
:double
"double")
198 (:pointer
:pointer-void
"void*")
199 (:void
:void
"void")))
201 (defun cffi-type->ecl-type
(type-keyword)
202 "Convert a CFFI type keyword to an ECL type keyword."
203 (or (second (find type-keyword
+translation-table
+ :key
#'first
))
204 (error "~S is not a valid CFFI type" type-keyword
)))
206 (defun ecl-type->c-type
(type-keyword)
207 "Convert a CFFI type keyword to an valid C type keyword."
208 (or (third (find type-keyword
+translation-table
+ :key
#'second
))
209 (error "~S is not a valid CFFI type" type-keyword
)))
211 (defun %foreign-type-size
(type-keyword)
212 "Return the size in bytes of a foreign type."
213 (nth-value 0 (ffi:size-of-foreign-type
214 (cffi-type->ecl-type type-keyword
))))
216 (defun %foreign-type-alignment
(type-keyword)
217 "Return the alignment in bytes of a foreign type."
218 (nth-value 1 (ffi:size-of-foreign-type
219 (cffi-type->ecl-type type-keyword
))))
223 (defun %mem-ref
(ptr type
&optional
(offset 0))
224 "Dereference an object of TYPE at OFFSET bytes from PTR."
225 (let* ((type (cffi-type->ecl-type type
))
226 (type-size (ffi:size-of-foreign-type type
)))
227 (si:foreign-data-ref-elt
228 (si:foreign-data-recast ptr
(+ offset type-size
) :void
) offset type
)))
230 (defun %mem-set
(value ptr type
&optional
(offset 0))
231 "Set an object of TYPE at OFFSET bytes from PTR."
232 (let* ((type (cffi-type->ecl-type type
))
233 (type-size (ffi:size-of-foreign-type type
)))
234 (si:foreign-data-set-elt
235 (si:foreign-data-recast ptr
(+ offset type-size
) :void
)
238 ;;; Inline versions that use C expressions instead of function calls.
240 (defparameter +mem-ref-strings
+
241 (loop for
(cffi-type ecl-type c-string
) in
+translation-table
+
242 for string
= (format nil
"*((~A *)(((char*)#0)+#1))" c-string
)
243 collect
(list cffi-type ecl-type string
)))
245 (defparameter +mem-set-strings
+
246 (loop for
(cffi-type ecl-type c-string
) in
+translation-table
+
247 for string
= (format nil
"*((~A *)(((char*)#0)+#1))=#2" c-string
)
248 collect
(list cffi-type ecl-type string
)))
250 (define-compiler-macro %mem-ref
(&whole whole ptr type
&optional
(offset 0))
251 (if (and (constantp type
) (constantp offset
))
252 (let ((record (assoc (eval type
) +mem-ref-strings
+)))
253 `(ffi:c-inline
(,ptr
,offset
)
254 (:pointer-void
:cl-index
) ; argument types
255 ,(second record
) ; return type
256 ,(third record
) ; the precomputed expansion
260 (define-compiler-macro %mem-set
(&whole whole value ptr type
&optional
(offset 0))
261 (if (and (constantp type
) (constantp offset
))
262 (let ((record (assoc (eval type
) +mem-set-strings
+)))
263 `(ffi:c-inline
(,ptr
,offset
,value
) ; arguments with type translated
264 (:pointer-void
:cl-index
,(second record
))
265 :void
; does not return anything
266 ,(third record
) ; precomputed expansion
270 ;;;# Calling Foreign Functions
272 (defconstant +ecl-inline-codes
+ "#0,#1,#2,#3,#4,#5,#6,#7,#8,#9,#a,#b,#c,#d,#e,#f,#g,#h,#i,#j,#k,#l,#m,#n,#o,#p,#q,#r,#s,#t,#u,#v,#w,#x,#y,#z")
274 (defun c-inline-function-call (thing fixed-types types values return-type dynamic-call variadic
)
276 (when (stringp thing
)
277 (setf thing
`(%foreign-symbol-pointer
,thing nil
)))
279 (push :pointer-void types
))
281 (format nil
"~{~A~^, ~}~A"
282 (mapcar #'ecl-type-
>c-type fixed-types
) (if (null variadic
) "" ", ...")))
285 ;; #0 is already used in a cast (it is a function pointer)
286 (subseq +ecl-inline-codes
+ 3 (max 3 (1- (* (length values
) 3))))
287 ;; #0 is not used, so we start from the beginning
288 (subseq +ecl-inline-codes
+ 0 (max 0 (1- (* (length values
) 3))))))
292 (format nil
"extern ~A ~A(~A);"
293 (ecl-type->c-type return-type
) thing decl-args
)))
296 (format nil
"((~A (*)(~A))(#0))(~A)"
297 (ecl-type->c-type return-type
) decl-args call-args
)
298 (format nil
"~A(~A)" thing call-args
))))
300 (ffi:clines
,@(ensure-list clines
))
301 (ffi:c-inline
,values
,types
,return-type
,call-code
:one-liner t
:side-effects t
))))
303 (defun dffi-function-pointer-call (pointer types values return-type
)
304 (when (stringp pointer
)
305 (setf pointer
`(%foreign-symbol-pointer
,pointer nil
)))
307 `(error "In interpreted code, attempted to call a foreign function~% ~A~%~
308 but ECL was built without support for that." ,pointer
)
310 `(si::call-cfun
,pointer
,return-type
(list ,@types
) (list ,@values
)))
312 (defun foreign-funcall-parse-args (args)
313 "Return three values, lists of arg types, values, and result type."
314 (let ((return-type :void
))
315 (loop for
(type arg
) on args by
#'cddr
316 if arg collect
(cffi-type->ecl-type type
) into types
317 and collect arg into values
318 else do
(setf return-type
(cffi-type->ecl-type type
))
319 finally
(return (values types values return-type
)))))
321 (defmacro %foreign-funcall
(name args
&key library convention
)
322 "Call a foreign function."
323 (declare (ignore library convention
))
324 (multiple-value-bind (types values return-type
)
325 (foreign-funcall-parse-args args
)
328 ,(dffi-function-pointer-call name types values return-type
)
330 ,(ecase *cffi-ecl-method
*
331 (:dffi
(dffi-function-pointer-call name types values return-type
))
332 (:dlopen
(c-inline-function-call name types types values return-type t nil
))
333 (:c
/c
++ (c-inline-function-call name types types values return-type nil nil
))))))
335 (defmacro %foreign-funcall-pointer
(pointer args
&key convention
)
336 "Funcall a pointer to a foreign function."
337 (declare (ignore convention
))
338 (multiple-value-bind (types values return-type
)
339 (foreign-funcall-parse-args args
)
342 ,(dffi-function-pointer-call pointer types values return-type
)
344 ,(if (eq *cffi-ecl-method
* :dffi
)
345 (dffi-function-pointer-call pointer types values return-type
)
346 (c-inline-function-call pointer types types values return-type t nil
)))))
348 (defmacro %foreign-funcall-varargs
(name args varargs
&key library convention
)
349 (declare (ignore library convention
))
350 (multiple-value-bind (fixed-types fixed-values
)
351 (foreign-funcall-parse-args args
)
352 (multiple-value-bind (varargs-types varargs-values return-type
)
353 (foreign-funcall-parse-args varargs
)
354 (let ((all-types (append fixed-types varargs-types
))
355 (values (append fixed-values varargs-values
)))
358 ,(dffi-function-pointer-call name all-types values return-type
)
360 ,(ecase *cffi-ecl-method
*
361 (:dffi
(dffi-function-pointer-call name all-types values return-type
))
362 (:dlopen
(c-inline-function-call name fixed-types all-types values return-type t t
))
363 (:c
/c
++ (c-inline-function-call name fixed-types all-types values return-type nil t
))))))))
365 (defmacro %foreign-funcall-pointer-varargs
(pointer args varargs
&key convention
)
366 (declare (ignore convention
))
367 (multiple-value-bind (fixed-types fixed-values
)
368 (foreign-funcall-parse-args args
)
369 (multiple-value-bind (varargs-types varargs-values return-type
)
370 (foreign-funcall-parse-args varargs
)
371 (let ((all-types (append fixed-types varargs-types
))
372 (values (append fixed-values varargs-values
)))
375 ,(dffi-function-pointer-call pointer all-types values return-type
)
377 ,(if (eq *cffi-ecl-method
* :dffi
)
378 (dffi-function-pointer-call pointer all-types values return-type
)
379 (c-inline-function-call pointer fixed-types all-types values return-type t t
)))))))
381 ;;;# Foreign Libraries
383 (defun %load-foreign-library
(name path
)
384 "Load a foreign library."
385 (declare (ignore name
))
386 #-dffi
(error "LOAD-FOREIGN-LIBRARY requires ECL's DFFI support. Use ~
387 FFI:LOAD-FOREIGN-LIBRARY with a constant argument instead.")
389 (handler-case (si:load-foreign-module path
)
391 (error "file error while trying to load `~A'" path
))))
393 (defun %close-foreign-library
(handle)
394 "Close a foreign library."
395 (handler-case (si::unload-foreign-module handle
)
396 (undefined-function ()
397 (restart-case (error "Detected ECL prior to version 15.2.21. ~
398 Function CFFI:CLOSE-FOREIGN-LIBRARY isn't implemented yet.")
399 (ignore () :report
"Continue anyway (foreign library will remain opened).")))))
401 (defun native-namestring (pathname)
402 (namestring pathname
))
406 ;;; Create a package to contain the symbols for callback functions.
407 ;;; We want to redefine callbacks with the same symbol so the internal
408 ;;; data structures are reused.
409 (defpackage #:cffi-callbacks
412 (defvar *callbacks
* (make-hash-table))
414 ;;; Intern a symbol in the CFFI-CALLBACKS package used to name the
415 ;;; internal callback for NAME.
416 (eval-when (:compile-toplevel
:load-toplevel
:execute
)
417 (defun intern-callback (name)
418 (intern (format nil
"~A::~A"
419 (if-let (package (symbol-package name
))
420 (package-name package
)
425 (defmacro %defcallback
(name rettype arg-names arg-types body
427 (declare (ignore convention
))
428 (let ((cb-name (intern-callback name
))
429 (cb-type #.
(if (> ext
:+ecl-version-number
+ 160102)
432 (ffi:defcallback
(,cb-name
,cb-type
)
433 ,(cffi-type->ecl-type rettype
)
434 ,(mapcar #'list arg-names
435 (mapcar #'cffi-type-
>ecl-type arg-types
))
437 (setf (gethash ',name
*callbacks
*) ',cb-name
))))
439 (defun %callback
(name)
440 (multiple-value-bind (symbol winp
)
441 (gethash name
*callbacks
*)
443 (error "Undefined callback: ~S" name
))
444 (ffi:callback symbol
)))
448 (defun %foreign-symbol-pointer
(name library
)
449 "Returns a pointer to a foreign symbol NAME."
450 (declare (ignore library
))
452 (si:find-foreign-symbol
(coerce name
'base-string
)
453 :default
:pointer-void
0)