1 ;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
3 ;;; uffi-compat.lisp --- UFFI compatibility layer for CFFI.
5 ;;; Copyright (C) 2005-2006, James Bielman <jamesjb@jamesjb.com>
6 ;;; Copyright (C) 2005-2007, Luis Oliveira <loliveira@common-lisp.net>
8 ;;; Permission is hereby granted, free of charge, to any person
9 ;;; obtaining a copy of this software and associated documentation
10 ;;; files (the "Software"), to deal in the Software without
11 ;;; restriction, including without limitation the rights to use, copy,
12 ;;; modify, merge, publish, distribute, sublicense, and/or sell copies
13 ;;; of the Software, and to permit persons to whom the Software is
14 ;;; furnished to do so, subject to the following conditions:
16 ;;; The above copyright notice and this permission notice shall be
17 ;;; included in all copies or substantial portions of the Software.
19 ;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20 ;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21 ;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22 ;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
23 ;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
24 ;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 ;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26 ;;; DEALINGS IN THE SOFTWARE.
29 ;;; Code borrowed from UFFI is Copyright (c) Kevin M. Rosenberg.
31 (defpackage #:cffi-uffi-compat
32 (:nicknames
#:uffi
) ;; is this a good idea?
52 #:allocate-foreign-object
55 #:with-foreign-objects
56 #:size-of-foreign-type
59 #:ensure-char-character
61 #:ensure-char-storable
65 #:+null-cstring-pointer
+
66 #:char-array-to-pointer
69 #:convert-from-foreign-usb8
73 #:convert-from-cstring
78 #:convert-from-foreign-string
79 #:convert-to-foreign-string
80 #:allocate-foreign-string
82 #:with-foreign-strings
83 #:foreign-string-length
; not implemented
86 #:foreign-encoded-octet-count
92 #:find-foreign-library
93 #:load-foreign-library
94 #:default-foreign-library-type
95 #:foreign-library-types
102 (in-package #:cffi-uffi-compat
)
105 (eval-when (:compile-toplevel
:load-toplevel
:execute
)
106 (when (equal (machine-type) "POWER MACINTOSH")
107 (pushnew :ppc
*features
*)))
109 (defun convert-uffi-type (uffi-type)
110 "Convert a UFFI primitive type to a CFFI type."
111 ;; Many CFFI types are the same as UFFI. This list handles the
115 (:pointer-void
:pointer
)
116 (:pointer-self
:pointer
)
117 ;; Although UFFI's documentation claims dereferencing :CHAR and
118 ;; :UNSIGNED-CHAR returns characters, it actually returns
121 (:unsigned-char
:unsigned-char
)
123 (:unsigned-byte
:unsigned-char
)
125 (if (listp uffi-type
)
126 (case (car uffi-type
)
127 ;; this is imho gross but it is what uffi does
128 (quote (convert-uffi-type (second uffi-type
)))
130 (:array
`(uffi-array ,(convert-uffi-type (second uffi-type
))
132 (:union
(second uffi-type
))
133 (:struct
(convert-uffi-type (second uffi-type
)))
134 (:struct-pointer
:pointer
))
137 (cffi:define-foreign-type uffi-array-type
()
138 ;; ELEMENT-TYPE should be /unparsed/, suitable for passing to mem-aref.
139 ((element-type :initform
(error "An element-type is required.")
140 :accessor element-type
:initarg
:element-type
)
141 (nelems :initform
(error "nelems is required.")
142 :accessor nelems
:initarg
:nelems
))
143 (:actual-type
:pointer
)
144 (:documentation
"UFFI's :array type."))
146 (cffi:define-parse-method uffi-array
(element-type count
)
147 (make-instance 'uffi-array-type
:element-type element-type
148 :nelems
(or count
1)))
150 (defmethod cffi:foreign-type-size
((type uffi-array-type
))
151 (* (cffi:foreign-type-size
(element-type type
)) (nelems type
)))
153 (defmethod cffi::aggregatep
((type uffi-array-type
))
156 ;; UFFI's :(unsigned-)char
158 (cffi:define-foreign-type uffi-char
()
162 (cffi:define-parse-method uffi-char
(base-type)
163 (make-instance 'uffi-char
:actual-type base-type
))
166 (defmethod cffi:translate-to-foreign
((value character
) (type uffi-char
))
170 (defmethod cffi:translate-from-foreign
(obj (type uffi-char
))
173 (defmacro def-type
(name type
)
174 "Define a Common Lisp type NAME for UFFI type TYPE."
175 (declare (ignore type
))
176 `(deftype ,name
() t
))
178 (defmacro def-foreign-type
(name type
)
179 "Define a new foreign type."
180 `(cffi:defctype
,name
,(convert-uffi-type type
)))
182 (defmacro def-constant
(name value
&key export
)
183 "Define a constant and conditionally export it."
184 `(eval-when (:compile-toplevel
:load-toplevel
:execute
)
185 (defconstant ,name
,value
)
186 ,@(when export
`((export ',name
)))
189 (defmacro null-char-p
(val)
190 "Return true if character is null."
191 `(zerop (char-code ,val
)))
193 (defmacro def-enum
(enum-name args
&key
(separator-string "#"))
194 "Creates a constants for a C type enum list, symbols are
195 created in the created in the current package. The symbol is the
196 concatenation of the enum-name name, separator-string, and
201 (declare (fixnum counter
))
203 (let ((name (if (listp arg
) (car arg
) arg
))
204 (value (if (listp arg
)
206 (setq counter
(cadr arg
))
211 (setq name
(intern (concatenate 'string
212 (symbol-name enum-name
)
214 (symbol-name name
))))
215 (push `(def-constant ,name
,value
) constants
)))
216 (setf cmds
(append '(progn) `((cffi:defctype
,enum-name
:int
))
217 (nreverse constants
)))
220 (defmacro def-struct
(name &body fields
)
221 "Define a C structure."
222 `(cffi:defcstruct
,name
223 ,@(loop for
(name uffi-type
) in fields
224 for cffi-type
= (convert-uffi-type uffi-type
)
225 collect
(list name cffi-type
))))
227 ;; TODO: figure out why the compiler macro is kicking in before
228 ;; the setf expander.
229 (defun %foreign-slot-value
(obj type field
)
230 (cffi:foreign-slot-value obj
`(:struct
,type
) field
))
232 (defun (setf %foreign-slot-value
) (value obj type field
)
233 (setf (cffi:foreign-slot-value obj
`(:struct
,type
) field
) value
))
235 (defmacro get-slot-value
(obj type field
)
236 "Access a slot value from a structure."
237 `(%foreign-slot-value
,obj
,type
,field
))
239 ;; UFFI uses a different function when accessing a slot whose
240 ;; type is a pointer. We don't need that in CFFI so we use
241 ;; foreign-slot-value too.
242 (defmacro get-slot-pointer
(obj type field
)
243 "Access a pointer slot value from a structure."
244 `(cffi:foreign-slot-value
,obj
,type
,field
))
246 (defmacro def-array-pointer
(name type
)
247 "Define a foreign array type."
248 `(cffi:defctype
,name
(uffi-array ,(convert-uffi-type type
) 1)))
250 (defmacro deref-array
(array type position
)
251 "Dereference an array."
252 `(cffi:mem-aref
,array
253 ,(if (constantp type
)
254 `',(element-type (cffi::parse-type
255 (convert-uffi-type (eval type
))))
256 `(element-type (cffi::parse-type
257 (convert-uffi-type ,type
))))
260 ;; UFFI's documentation on DEF-UNION is a bit scarce, I'm not sure
261 ;; if DEFCUNION and DEF-UNION are strictly compatible.
262 (defmacro def-union
(name &body fields
)
263 "Define a foreign union type."
264 `(cffi:defcunion
,name
265 ,@(loop for
(name uffi-type
) in fields
266 for cffi-type
= (convert-uffi-type uffi-type
)
267 collect
(list name cffi-type
))))
269 (defun convert-uffi-type-form (type-form)
270 (if (constantp type-form
)
271 `',(convert-uffi-type (eval type-form
))
272 `(convert-uffi-type ,type-form
)))
274 (defmacro allocate-foreign-object
(type &optional
(size 1))
275 "Allocate one or more instance of a foreign type."
276 `(cffi:foreign-alloc
,(convert-uffi-type-form type
)
279 (defmacro free-foreign-object
(ptr)
280 "Free a foreign object allocated by ALLOCATE-FOREIGN-OBJECT."
281 `(cffi:foreign-free
,ptr
))
283 (defmacro with-foreign-object
((var type
) &body body
)
284 "Wrap the allocation of a foreign object around BODY."
285 `(cffi:with-foreign-object
(,var
,(convert-uffi-type-form type
))
288 ;; Taken from UFFI's src/objects.lisp
289 (defmacro with-foreign-objects
(bindings &rest body
)
291 `(with-foreign-object ,(car bindings
)
292 (with-foreign-objects ,(cdr bindings
)
296 (defmacro size-of-foreign-type
(type)
297 "Return the size in bytes of a foreign type."
298 `(cffi:foreign-type-size
,(convert-uffi-type-form type
)))
300 (defmacro pointer-address
(ptr)
301 "Return the address of a pointer."
302 `(cffi:pointer-address
,ptr
))
304 (defmacro deref-pointer
(ptr type
)
305 "Dereference a pointer."
306 `(cffi:mem-ref
,ptr
,(convert-uffi-type-form type
)))
308 (defsetf deref-pointer
(ptr type
) (value)
309 `(setf (cffi:mem-ref
,ptr
,(convert-uffi-type-form type
)) ,value
))
311 (defmacro ensure-char-character
(obj &environment env
)
312 "Convert OBJ to a character if it is an integer."
313 (if (constantp obj env
)
314 (if (characterp obj
) obj
(code-char obj
))
315 (let ((obj-var (gensym)))
316 `(let ((,obj-var
,obj
))
317 (if (characterp ,obj-var
)
319 (code-char ,obj-var
))))))
321 (defmacro ensure-char-integer
(obj &environment env
)
322 "Convert OBJ to an integer if it is a character."
323 (if (constantp obj env
)
324 (let ((the-obj (eval obj
)))
325 (if (characterp the-obj
) (char-code the-obj
) the-obj
))
326 (let ((obj-var (gensym)))
327 `(let ((,obj-var
,obj
))
328 (if (characterp ,obj-var
)
332 (defmacro ensure-char-storable
(obj)
333 "Ensure OBJ is storable as a character."
334 `(ensure-char-integer ,obj
))
336 (defmacro make-null-pointer
(type)
337 "Create a NULL pointer."
338 (declare (ignore type
))
339 `(cffi:null-pointer
))
341 (defmacro make-pointer
(address type
)
342 "Create a pointer to ADDRESS."
343 (declare (ignore type
))
344 `(cffi:make-pointer
,address
))
346 (defmacro null-pointer-p
(ptr)
347 "Return true if PTR is a null pointer."
348 `(cffi:null-pointer-p
,ptr
))
350 (defparameter +null-cstring-pointer
+ (cffi:null-pointer
)
351 "A constant NULL string pointer.")
353 (defmacro char-array-to-pointer
(obj)
356 (defmacro with-cast-pointer
((var ptr type
) &body body
)
357 "Cast a pointer, does nothing in CFFI."
358 (declare (ignore type
))
362 (defmacro def-foreign-var
(name type module
)
363 "Define a symbol macro to access a foreign variable."
364 (declare (ignore module
))
365 (flet ((lisp-name (name)
366 (intern (cffi-sys:canonicalize-symbol-name-case
367 (substitute #\-
#\_ name
)))))
368 `(cffi:defcvar
,(if (listp name
)
370 (list name
(lisp-name name
)))
371 ,(convert-uffi-type type
))))
373 (defmacro def-pointer-var
(name value
&optional doc
)
374 #-openmcl
`(defvar ,name
,value
,@(if doc
(list doc
)))
375 #+openmcl
`(ccl::defloadvar
,name
,value
,doc
))
377 (defmacro convert-from-cstring
(s)
378 "Convert a cstring to a Lisp string."
379 (let ((ret (gensym)))
380 `(let ((,ret
(cffi:foreign-string-to-lisp
,s
)))
385 (defmacro convert-to-cstring
(obj)
386 "Convert a Lisp string to a cstring."
387 (let ((str (gensym)))
391 (cffi:foreign-string-alloc
,str
)))))
393 (defmacro free-cstring
(ptr)
395 `(cffi:foreign-string-free
,ptr
))
397 (defmacro with-cstring
((foreign-string lisp-string
) &body body
)
398 "Binds a newly creating string."
399 (let ((str (gensym)) (body-proc (gensym)))
400 `(flet ((,body-proc
(,foreign-string
) ,@body
))
401 (let ((,str
,lisp-string
))
403 (,body-proc
(cffi:null-pointer
))
404 (cffi:with-foreign-string
(,foreign-string
,str
)
405 (,body-proc
,foreign-string
)))))))
407 ;; Taken from UFFI's src/strings.lisp
408 (defmacro with-cstrings
(bindings &rest body
)
410 `(with-cstring ,(car bindings
)
411 (with-cstrings ,(cdr bindings
)
415 (defmacro def-function
(name args
&key module
(returning :void
))
416 "Define a foreign function."
417 (declare (ignore module
))
418 `(cffi:defcfun
,name
,(convert-uffi-type returning
)
419 ,@(loop for
(name type
) in args
420 collect
`(,name
,(convert-uffi-type type
)))))
422 ;;; Taken from UFFI's src/libraries.lisp
424 (defvar *loaded-libraries
* nil
425 "List of foreign libraries loaded. Used to prevent reloading a library")
427 (defun default-foreign-library-type ()
428 "Returns string naming default library type for platform"
429 #+(or win32 cygwin mswindows
) "dll"
430 #+(or macos macosx darwin ccl-5.0
) "dylib"
431 #-
(or win32 cygwin mswindows macos macosx darwin ccl-5.0
) "so")
433 (defun foreign-library-types ()
434 "Returns list of string naming possible library types for platform,
435 sorted by preference"
436 #+(or win32 cygwin mswindows
) '("dll" "lib" "so")
437 #+(or macos macosx darwin ccl-5.0
) '("dylib" "bundle")
438 #-
(or win32 cygwin mswindows macos macosx darwin ccl-5.0
) '("so" "a" "o"))
440 (defun find-foreign-library (names directories
&key types drive-letters
)
441 "Looks for a foreign library. directories can be a single
442 string or a list of strings of candidate directories. Use default
443 library type if type is not specified."
445 (setq types
(foreign-library-types)))
446 (unless (listp types
)
447 (setq types
(list types
)))
448 (unless (listp names
)
449 (setq names
(list names
)))
450 (unless (listp directories
)
451 (setq directories
(list directories
)))
452 #+(or win32 mswindows
)
453 (unless (listp drive-letters
)
454 (setq drive-letters
(list drive-letters
)))
455 #-
(or win32 mswindows
)
456 (setq drive-letters
'(nil))
457 (dolist (drive-letter drive-letters
)
459 (dolist (dir directories
)
461 (let ((path (make-pathname
463 #+lispworks
(when drive-letter drive-letter
)
465 #-lispworks
(when drive-letter drive-letter
)
471 (pathname-directory dir
))
476 (parse-namestring dir
)))))))
477 (when (probe-file path
)
478 (return-from find-foreign-library path
)))))))
481 (defun convert-supporting-libraries-to-string (libs)
484 (push (format nil
"-l~A" lib
) lib-load-list
))
485 (nreverse lib-load-list
)))
487 (defun load-foreign-library (filename &key module supporting-libraries
489 #+(or allegro mcl sbcl clisp
) (declare (ignore module supporting-libraries
))
490 #+(or cmucl scl sbcl
) (declare (ignore module
))
492 (when (and filename
(or (null (pathname-directory filename
))
493 (probe-file filename
)))
494 (if (pathnamep filename
) ;; ensure filename is a string to check if
495 (setq filename
(namestring filename
))) ; already loaded
497 (if (and (not force-load
)
498 (find filename
*loaded-libraries
* :test
#'string-equal
))
499 t
;; return T, but don't reload library
501 ;; FIXME: Hmm, what are these two for?
503 (let ((type (pathname-type (parse-namestring filename
))))
504 (if (string-equal type
"so")
505 (sys::load-object-file filename
)
506 (alien:load-foreign filename
508 (convert-supporting-libraries-to-string
509 supporting-libraries
))))
511 (let ((type (pathname-type (parse-namestring filename
))))
512 (if (string-equal type
"so")
513 (sys::load-dynamic-object filename
)
514 (alien:load-foreign filename
516 (convert-supporting-libraries-to-string
517 supporting-libraries
))))
520 (cffi:load-foreign-library filename
)
521 (push filename
*loaded-libraries
*)
524 ;; Taken from UFFI's src/os.lisp
526 "Return the value of the environment variable."
527 #+allegro
(sys::getenv
(string var
))
528 #+clisp
(sys::getenv
(string var
))
529 #+(or cmucl scl
) (cdr (assoc (string var
) ext
:*environment-list
* :test
#'equalp
531 #+(or ecl gcl
) (si:getenv
(string var
))
532 #+lispworks
(lw:environment-variable
(string var
))
533 #+lucid
(lcl:environment-variable
(string var
))
534 #+(or mcl ccl
) (ccl::getenv var
)
535 #+sbcl
(sb-ext:posix-getenv var
)
536 #-
(or allegro clisp cmucl ecl scl gcl lispworks lucid mcl ccl sbcl
)
537 (error 'not-implemented
:proc
(list 'getenv var
)))
539 ;; Taken from UFFI's src/os.lisp
540 ;; modified from function ASDF -- Copyright Dan Barlow and Contributors
541 (defun run-shell-command (control-string &rest args
)
542 "Interpolate ARGS into CONTROL-STRING as if by FORMAT, and
543 synchronously execute the result using a Bourne-compatible shell, with
544 output to *trace-output*. Returns the shell's exit code."
545 (let ((command (apply #'format nil control-string args
))
546 (output *trace-output
*))
548 (sb-impl::process-exit-code
552 :input nil
:output output
))
555 (ext:process-exit-code
559 :input nil
:output output
))
562 (excl:run-shell-command command
:input nil
:output output
)
565 (system:call-system-showing-output
567 :shell-type
"/bin/sh"
568 :output-stream output
)
570 #+clisp
;XXX not exactly *trace-output*, I know
571 (ext:run-shell-command command
:output
:terminal
:wait t
)
575 (ccl:external-process-status
576 (ccl:run-program
"/bin/sh" (list "-c" command
)
577 :input nil
:output output
583 "/bin/sh" (list "-c" command
)
584 :input nil
:output output
:error nil
:wait t
))
586 #-
(or openmcl ecl clisp lispworks allegro scl cmucl sbcl
)
587 (error "RUN-SHELL-PROGRAM not implemented for this Lisp")
590 ;;; Some undocumented UFFI operators...
592 (defmacro convert-from-foreign-string
593 (obj &key length
(locale :default
)
594 (encoding 'cffi
:*default-foreign-encoding
*)
595 (null-terminated-p t
))
596 ;; in effect, (eq NULL-TERMINATED-P (null LENGTH)). Hopefully,
597 ;; that's compatible with the intended semantics, which are
598 ;; undocumented. If that's not the case, we can implement
599 ;; NULL-TERMINATED-P in CFFI:FOREIGN-STRING-TO-LISP.
600 (declare (ignore locale null-terminated-p
))
601 (let ((ret (gensym)))
602 `(let ((,ret
(cffi:foreign-string-to-lisp
,obj
604 :encoding
,encoding
)))
609 ;; What's the difference between this and convert-to-cstring?
610 (defmacro convert-to-foreign-string
611 (obj &optional
(encoding 'cffi
:*default-foreign-encoding
*))
612 (let ((str (gensym)))
616 (cffi:foreign-string-alloc
,str
:encoding
,encoding
)))))
618 (defmacro allocate-foreign-string
(size &key unsigned
)
619 (declare (ignore unsigned
))
620 `(cffi:foreign-alloc
:char
:count
,size
))
623 (defmacro with-foreign-string
((foreign-string lisp-string
) &body body
)
624 (let ((str (gensym)))
625 `(let ((,str
,lisp-string
))
627 (let ((,foreign-string
(cffi:null-pointer
)))
629 (cffi:with-foreign-string
(,foreign-string
,str
)
632 (defmacro with-foreign-strings
(bindings &body body
)
633 `(with-foreign-string ,(car bindings
)
635 `((with-foreign-strings ,(cdr bindings
) ,@body
))
638 ;; This function returns a form? Where is this used in user-code?
639 (defun foreign-string-length (foreign-string)
640 (declare (ignore foreign-string
))
641 (error "FOREIGN-STRING-LENGTH not implemented."))
643 ;; This should be optimized.
644 (defun convert-from-foreign-usb8 (s len
)
645 (let ((a (make-array len
:element-type
'(unsigned-byte 8))))
647 (setf (aref a i
) (cffi:mem-ref s
:unsigned-char i
)))))
649 ;;;; String Encodings
651 (defmacro string-to-octets
(str &key encoding null-terminate
)
652 `(babel:concatenate-strings-to-octets
653 (or ,encoding cffi
:*default-foreign-encoding
*)
659 (defmacro octets-to-string
(octets &key encoding
)
660 `(babel:octets-to-string
,octets
661 :encoding
(or ,encoding
662 cffi
:*default-foreign-encoding
*)))
664 (defun foreign-encoded-octet-count (str &key encoding
)
665 (babel:string-size-in-octets str
666 :encoding
(or encoding
667 cffi
:*default-foreign-encoding
*)))