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 (defmacro allocate-foreign-object
(type &optional
(size 1))
270 "Allocate one or more instance of a foreign type."
271 `(cffi:foreign-alloc
,(if (constantp type
)
272 `',(convert-uffi-type (eval type
))
273 `(convert-uffi-type ,type
))
276 (defmacro free-foreign-object
(ptr)
277 "Free a foreign object allocated by ALLOCATE-FOREIGN-OBJECT."
278 `(cffi:foreign-free
,ptr
))
280 (defmacro with-foreign-object
((var type
) &body body
)
281 "Wrap the allocation of a foreign object around BODY."
282 `(cffi:with-foreign-object
(,var
(convert-uffi-type ,type
))
285 ;; Taken from UFFI's src/objects.lisp
286 (defmacro with-foreign-objects
(bindings &rest body
)
288 `(with-foreign-object ,(car bindings
)
289 (with-foreign-objects ,(cdr bindings
)
293 (defmacro size-of-foreign-type
(type)
294 "Return the size in bytes of a foreign type."
295 `(cffi:foreign-type-size
(convert-uffi-type ,type
)))
297 (defmacro pointer-address
(ptr)
298 "Return the address of a pointer."
299 `(cffi:pointer-address
,ptr
))
301 (defmacro deref-pointer
(ptr type
)
302 "Dereference a pointer."
303 `(cffi:mem-ref
,ptr
(convert-uffi-type ,type
)))
305 (defsetf deref-pointer
(ptr type
) (value)
306 `(setf (cffi:mem-ref
,ptr
(convert-uffi-type ,type
)) ,value
))
308 (defmacro ensure-char-character
(obj &environment env
)
309 "Convert OBJ to a character if it is an integer."
310 (if (constantp obj env
)
311 (if (characterp obj
) obj
(code-char obj
))
312 (let ((obj-var (gensym)))
313 `(let ((,obj-var
,obj
))
314 (if (characterp ,obj-var
)
316 (code-char ,obj-var
))))))
318 (defmacro ensure-char-integer
(obj &environment env
)
319 "Convert OBJ to an integer if it is a character."
320 (if (constantp obj env
)
321 (let ((the-obj (eval obj
)))
322 (if (characterp the-obj
) (char-code the-obj
) the-obj
))
323 (let ((obj-var (gensym)))
324 `(let ((,obj-var
,obj
))
325 (if (characterp ,obj-var
)
329 (defmacro ensure-char-storable
(obj)
330 "Ensure OBJ is storable as a character."
331 `(ensure-char-integer ,obj
))
333 (defmacro make-null-pointer
(type)
334 "Create a NULL pointer."
335 (declare (ignore type
))
336 `(cffi:null-pointer
))
338 (defmacro make-pointer
(address type
)
339 "Create a pointer to ADDRESS."
340 (declare (ignore type
))
341 `(cffi:make-pointer
,address
))
343 (defmacro null-pointer-p
(ptr)
344 "Return true if PTR is a null pointer."
345 `(cffi:null-pointer-p
,ptr
))
347 (defparameter +null-cstring-pointer
+ (cffi:null-pointer
)
348 "A constant NULL string pointer.")
350 (defmacro char-array-to-pointer
(obj)
353 (defmacro with-cast-pointer
((var ptr type
) &body body
)
354 "Cast a pointer, does nothing in CFFI."
355 (declare (ignore type
))
359 (defmacro def-foreign-var
(name type module
)
360 "Define a symbol macro to access a foreign variable."
361 (declare (ignore module
))
362 (flet ((lisp-name (name)
363 (intern (cffi-sys:canonicalize-symbol-name-case
364 (substitute #\-
#\_ name
)))))
365 `(cffi:defcvar
,(if (listp name
)
367 (list name
(lisp-name name
)))
368 ,(convert-uffi-type type
))))
370 (defmacro def-pointer-var
(name value
&optional doc
)
371 #-openmcl
`(defvar ,name
,value
,@(if doc
(list doc
)))
372 #+openmcl
`(ccl::defloadvar
,name
,value
,doc
))
374 (defmacro convert-from-cstring
(s)
375 "Convert a cstring to a Lisp string."
376 (let ((ret (gensym)))
377 `(let ((,ret
(cffi:foreign-string-to-lisp
,s
)))
382 (defmacro convert-to-cstring
(obj)
383 "Convert a Lisp string to a cstring."
384 (let ((str (gensym)))
388 (cffi:foreign-string-alloc
,str
)))))
390 (defmacro free-cstring
(ptr)
392 `(cffi:foreign-string-free
,ptr
))
394 (defmacro with-cstring
((foreign-string lisp-string
) &body body
)
395 "Binds a newly creating string."
396 (let ((str (gensym)) (body-proc (gensym)))
397 `(flet ((,body-proc
(,foreign-string
) ,@body
))
398 (let ((,str
,lisp-string
))
400 (,body-proc
(cffi:null-pointer
))
401 (cffi:with-foreign-string
(,foreign-string
,str
)
402 (,body-proc
,foreign-string
)))))))
404 ;; Taken from UFFI's src/strings.lisp
405 (defmacro with-cstrings
(bindings &rest body
)
407 `(with-cstring ,(car bindings
)
408 (with-cstrings ,(cdr bindings
)
412 (defmacro def-function
(name args
&key module
(returning :void
))
413 "Define a foreign function."
414 (declare (ignore module
))
415 `(cffi:defcfun
,name
,(convert-uffi-type returning
)
416 ,@(loop for
(name type
) in args
417 collect
`(,name
,(convert-uffi-type type
)))))
419 ;;; Taken from UFFI's src/libraries.lisp
421 (defvar *loaded-libraries
* nil
422 "List of foreign libraries loaded. Used to prevent reloading a library")
424 (defun default-foreign-library-type ()
425 "Returns string naming default library type for platform"
426 #+(or win32 cygwin mswindows
) "dll"
427 #+(or macos macosx darwin ccl-5.0
) "dylib"
428 #-
(or win32 cygwin mswindows macos macosx darwin ccl-5.0
) "so")
430 (defun foreign-library-types ()
431 "Returns list of string naming possible library types for platform,
432 sorted by preference"
433 #+(or win32 cygwin mswindows
) '("dll" "lib" "so")
434 #+(or macos macosx darwin ccl-5.0
) '("dylib" "bundle")
435 #-
(or win32 cygwin mswindows macos macosx darwin ccl-5.0
) '("so" "a" "o"))
437 (defun find-foreign-library (names directories
&key types drive-letters
)
438 "Looks for a foreign library. directories can be a single
439 string or a list of strings of candidate directories. Use default
440 library type if type is not specified."
442 (setq types
(foreign-library-types)))
443 (unless (listp types
)
444 (setq types
(list types
)))
445 (unless (listp names
)
446 (setq names
(list names
)))
447 (unless (listp directories
)
448 (setq directories
(list directories
)))
449 #+(or win32 mswindows
)
450 (unless (listp drive-letters
)
451 (setq drive-letters
(list drive-letters
)))
452 #-
(or win32 mswindows
)
453 (setq drive-letters
'(nil))
454 (dolist (drive-letter drive-letters
)
456 (dolist (dir directories
)
458 (let ((path (make-pathname
460 #+lispworks
(when drive-letter drive-letter
)
462 #-lispworks
(when drive-letter drive-letter
)
468 (pathname-directory dir
))
473 (parse-namestring dir
)))))))
474 (when (probe-file path
)
475 (return-from find-foreign-library path
)))))))
478 (defun convert-supporting-libraries-to-string (libs)
481 (push (format nil
"-l~A" lib
) lib-load-list
))
482 (nreverse lib-load-list
)))
484 (defun load-foreign-library (filename &key module supporting-libraries
486 #+(or allegro mcl sbcl clisp
) (declare (ignore module supporting-libraries
))
487 #+(or cmucl scl sbcl
) (declare (ignore module
))
489 (when (and filename
(or (null (pathname-directory filename
))
490 (probe-file filename
)))
491 (if (pathnamep filename
) ;; ensure filename is a string to check if
492 (setq filename
(namestring filename
))) ; already loaded
494 (if (and (not force-load
)
495 (find filename
*loaded-libraries
* :test
#'string-equal
))
496 t
;; return T, but don't reload library
498 ;; FIXME: Hmm, what are these two for?
500 (let ((type (pathname-type (parse-namestring filename
))))
501 (if (string-equal type
"so")
502 (sys::load-object-file filename
)
503 (alien:load-foreign filename
505 (convert-supporting-libraries-to-string
506 supporting-libraries
))))
508 (let ((type (pathname-type (parse-namestring filename
))))
509 (if (string-equal type
"so")
510 (sys::load-dynamic-object filename
)
511 (alien:load-foreign filename
513 (convert-supporting-libraries-to-string
514 supporting-libraries
))))
517 (cffi:load-foreign-library filename
)
518 (push filename
*loaded-libraries
*)
521 ;; Taken from UFFI's src/os.lisp
523 "Return the value of the environment variable."
524 #+allegro
(sys::getenv
(string var
))
525 #+clisp
(sys::getenv
(string var
))
526 #+(or cmucl scl
) (cdr (assoc (string var
) ext
:*environment-list
* :test
#'equalp
528 #+(or ecl gcl
) (si:getenv
(string var
))
529 #+lispworks
(lw:environment-variable
(string var
))
530 #+lucid
(lcl:environment-variable
(string var
))
531 #+(or mcl ccl
) (ccl::getenv var
)
532 #+sbcl
(sb-ext:posix-getenv var
)
533 #-
(or allegro clisp cmucl ecl scl gcl lispworks lucid mcl ccl sbcl
)
534 (error 'not-implemented
:proc
(list 'getenv var
)))
536 ;; Taken from UFFI's src/os.lisp
537 ;; modified from function ASDF -- Copyright Dan Barlow and Contributors
538 (defun run-shell-command (control-string &rest args
)
539 "Interpolate ARGS into CONTROL-STRING as if by FORMAT, and
540 synchronously execute the result using a Bourne-compatible shell, with
541 output to *trace-output*. Returns the shell's exit code."
542 (let ((command (apply #'format nil control-string args
))
543 (output *trace-output
*))
545 (sb-impl::process-exit-code
549 :input nil
:output output
))
552 (ext:process-exit-code
556 :input nil
:output output
))
559 (excl:run-shell-command command
:input nil
:output output
)
562 (system:call-system-showing-output
564 :shell-type
"/bin/sh"
565 :output-stream output
)
567 #+clisp
;XXX not exactly *trace-output*, I know
568 (ext:run-shell-command command
:output
:terminal
:wait t
)
572 (ccl:external-process-status
573 (ccl:run-program
"/bin/sh" (list "-c" command
)
574 :input nil
:output output
580 "/bin/sh" (list "-c" command
)
581 :input nil
:output output
:error nil
:wait t
))
583 #-
(or openmcl ecl clisp lispworks allegro scl cmucl sbcl
)
584 (error "RUN-SHELL-PROGRAM not implemented for this Lisp")
587 ;;; Some undocumented UFFI operators...
589 (defmacro convert-from-foreign-string
590 (obj &key length
(locale :default
)
591 (encoding 'cffi
:*default-foreign-encoding
*)
592 (null-terminated-p t
))
593 ;; in effect, (eq NULL-TERMINATED-P (null LENGTH)). Hopefully,
594 ;; that's compatible with the intended semantics, which are
595 ;; undocumented. If that's not the case, we can implement
596 ;; NULL-TERMINATED-P in CFFI:FOREIGN-STRING-TO-LISP.
597 (declare (ignore locale null-terminated-p
))
598 (let ((ret (gensym)))
599 `(let ((,ret
(cffi:foreign-string-to-lisp
,obj
601 :encoding
,encoding
)))
606 ;; What's the difference between this and convert-to-cstring?
607 (defmacro convert-to-foreign-string
608 (obj &optional
(encoding 'cffi
:*default-foreign-encoding
*))
609 (let ((str (gensym)))
613 (cffi:foreign-string-alloc
,str
:encoding
,encoding
)))))
615 (defmacro allocate-foreign-string
(size &key unsigned
)
616 (declare (ignore unsigned
))
617 `(cffi:foreign-alloc
:char
:count
,size
))
620 (defmacro with-foreign-string
((foreign-string lisp-string
) &body body
)
621 (let ((str (gensym)))
622 `(let ((,str
,lisp-string
))
624 (let ((,foreign-string
(cffi:null-pointer
)))
626 (cffi:with-foreign-string
(,foreign-string
,str
)
629 (defmacro with-foreign-strings
(bindings &body body
)
630 `(with-foreign-string ,(car bindings
)
632 `((with-foreign-strings ,(cdr bindings
) ,@body
))
635 ;; This function returns a form? Where is this used in user-code?
636 (defun foreign-string-length (foreign-string)
637 (declare (ignore foreign-string
))
638 (error "FOREIGN-STRING-LENGTH not implemented."))
640 ;; This should be optimized.
641 (defun convert-from-foreign-usb8 (s len
)
642 (let ((a (make-array len
:element-type
'(unsigned-byte 8))))
644 (setf (aref a i
) (cffi:mem-ref s
:unsigned-char i
)))))
646 ;;;; String Encodings
648 (defmacro string-to-octets
(str &key encoding null-terminate
)
649 `(babel:concatenate-strings-to-octets
650 (or ,encoding cffi
:*default-foreign-encoding
*)
656 (defmacro octets-to-string
(octets &key encoding
)
657 `(babel:octets-to-string
,octets
658 :encoding
(or ,encoding
659 cffi
:*default-foreign-encoding
*)))
661 (defun foreign-encoded-octet-count (str &key encoding
)
662 (babel:string-size-in-octets str
663 :encoding
(or encoding
664 cffi
:*default-foreign-encoding
*)))