Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

433 lines (376 sloc) 15.949 kB
;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
;;;
;;; cffi-clisp.lisp --- CFFI-SYS implementation for CLISP.
;;;
;;; Copyright (C) 2005-2006, James Bielman <jamesjb@jamesjb.com>
;;; Copyright (C) 2005-2006, Joerg Hoehle <hoehle@users.sourceforge.net>
;;;
;;; Permission is hereby granted, free of charge, to any person
;;; obtaining a copy of this software and associated documentation
;;; files (the "Software"), to deal in the Software without
;;; restriction, including without limitation the rights to use, copy,
;;; modify, merge, publish, distribute, sublicense, and/or sell copies
;;; of the Software, and to permit persons to whom the Software is
;;; furnished to do so, subject to the following conditions:
;;;
;;; The above copyright notice and this permission notice shall be
;;; included in all copies or substantial portions of the Software.
;;;
;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
;;; DEALINGS IN THE SOFTWARE.
;;;
;;;# Administrivia
(defpackage #:cffi-sys
(:use #:common-lisp #:alexandria)
(:export
#:canonicalize-symbol-name-case
#:foreign-pointer
#:pointerp
#:pointer-eq
#:null-pointer
#:null-pointer-p
#:inc-pointer
#:make-pointer
#:pointer-address
#:%foreign-alloc
#:foreign-free
#:with-foreign-pointer
#:%foreign-funcall
#:%foreign-funcall-pointer
#:%foreign-type-alignment
#:%foreign-type-size
#:%load-foreign-library
#:%close-foreign-library
#:native-namestring
#:%mem-ref
#:%mem-set
#:make-shareable-byte-vector
#:with-pointer-to-vector-data
#:%foreign-symbol-pointer
#:%defcallback
#:%callback))
(in-package #:cffi-sys)
(eval-when (:compile-toplevel :load-toplevel :execute)
(unless (find-package :ffi)
(error "CFFI requires CLISP compiled with dynamic FFI support.")))
;;;# Symbol Case
(defun canonicalize-symbol-name-case (name)
(declare (string name))
(string-upcase name))
;;;# Built-In Foreign Types
(defun convert-foreign-type (type)
"Convert a CFFI built-in type keyword to a CLisp FFI type."
(ecase type
(:char 'ffi:char)
(:unsigned-char 'ffi:uchar)
(:short 'ffi:short)
(:unsigned-short 'ffi:ushort)
(:int 'ffi:int)
(:unsigned-int 'ffi:uint)
(:long 'ffi:long)
(:unsigned-long 'ffi:ulong)
(:long-long 'ffi:sint64)
(:unsigned-long-long 'ffi:uint64)
(:float 'ffi:single-float)
(:double 'ffi:double-float)
;; Clisp's FFI:C-POINTER converts NULL to NIL. For now
;; we have a workaround in the pointer operations...
(:pointer 'ffi:c-pointer)
(:void nil)))
(defun %foreign-type-size (type)
"Return the size in bytes of objects having foreign type TYPE."
(nth-value 0 (ffi:sizeof (convert-foreign-type type))))
;; Remind me to buy a beer for whoever made getting the alignment
;; of foreign types part of the public interface in CLisp. :-)
(defun %foreign-type-alignment (type)
"Return the structure alignment in bytes of foreign TYPE."
#+(and darwin ppc)
(case type
((:double :long-long :unsigned-long-long)
(return-from %foreign-type-alignment 8)))
;; Override not necessary for the remaining types...
(nth-value 1 (ffi:sizeof (convert-foreign-type type))))
;;;# Basic Pointer Operations
(deftype foreign-pointer ()
'ffi:foreign-address)
(defun pointerp (ptr)
"Return true if PTR is a foreign pointer."
(typep ptr 'ffi:foreign-address))
(defun pointer-eq (ptr1 ptr2)
"Return true if PTR1 and PTR2 point to the same address."
(eql (ffi:foreign-address-unsigned ptr1)
(ffi:foreign-address-unsigned ptr2)))
(defun null-pointer ()
"Return a null foreign pointer."
(ffi:unsigned-foreign-address 0))
(defun null-pointer-p (ptr)
"Return true if PTR is a null foreign pointer."
(zerop (ffi:foreign-address-unsigned ptr)))
(defun inc-pointer (ptr offset)
"Return a pointer pointing OFFSET bytes past PTR."
(ffi:unsigned-foreign-address
(+ offset (ffi:foreign-address-unsigned ptr))))
(defun make-pointer (address)
"Return a pointer pointing to ADDRESS."
(ffi:unsigned-foreign-address address))
(defun pointer-address (ptr)
"Return the address pointed to by PTR."
(ffi:foreign-address-unsigned ptr))
;;;# Foreign Memory Allocation
(defun %foreign-alloc (size)
"Allocate SIZE bytes of foreign-addressable memory and return a
pointer to the allocated block. An implementation-specific error
is signalled if the memory cannot be allocated."
(ffi:foreign-address
(ffi:allocate-shallow 'ffi:uint8 :count (if (zerop size) 1 size))))
(defun foreign-free (ptr)
"Free a pointer PTR allocated by FOREIGN-ALLOC. The results
are undefined if PTR is used after being freed."
(ffi:foreign-free ptr))
(defmacro with-foreign-pointer ((var size &optional size-var) &body body)
"Bind VAR to a pointer to SIZE bytes of foreign-addressable
memory during BODY. Both PTR and the memory block pointed to
have dynamic extent and may be stack allocated if supported by
the implementation. If SIZE-VAR is supplied, it will be bound to
SIZE during BODY."
(unless size-var
(setf size-var (gensym "SIZE")))
(let ((obj-var (gensym)))
`(let ((,size-var ,size))
(ffi:with-foreign-object
(,obj-var `(ffi:c-array ffi:uint8 ,,size-var))
(let ((,var (ffi:foreign-address ,obj-var)))
,@body)))))
;;;# Memory Access
;;; %MEM-REF and its compiler macro work around CLISP's FFI:C-POINTER
;;; type and convert NILs back to null pointers.
(defun %mem-ref (ptr type &optional (offset 0))
"Dereference a pointer OFFSET bytes from PTR to an object of
built-in foreign TYPE. Returns the object as a foreign pointer
or Lisp number."
(let ((value (ffi:memory-as ptr (convert-foreign-type type) offset)))
(if (eq type :pointer)
(or value (null-pointer))
value)))
(define-compiler-macro %mem-ref (&whole form ptr type &optional (offset 0))
"Compiler macro to open-code when TYPE is constant."
(if (constantp type)
(let* ((ftype (convert-foreign-type (eval type)))
(form `(ffi:memory-as ,ptr ',ftype ,offset)))
(if (eq type :pointer)
`(or ,form (null-pointer))
form))
form))
(defun %mem-set (value ptr type &optional (offset 0))
"Set a pointer OFFSET bytes from PTR to an object of built-in
foreign TYPE to VALUE."
(setf (ffi:memory-as ptr (convert-foreign-type type) offset) value))
(define-compiler-macro %mem-set
(&whole form value ptr type &optional (offset 0))
(if (constantp type)
;; (setf (ffi:memory-as) value) is exported, but not so nice
;; w.r.t. the left to right evaluation rule
`(ffi::write-memory-as
,value ,ptr ',(convert-foreign-type (eval type)) ,offset)
form))
;;;# Shareable Vectors
;;;
;;; This interface is very experimental. WITH-POINTER-TO-VECTOR-DATA
;;; should be defined to perform a copy-in/copy-out if the Lisp
;;; implementation can't do this.
(declaim (inline make-shareable-byte-vector))
(defun make-shareable-byte-vector (size)
"Create a Lisp vector of SIZE bytes can passed to
WITH-POINTER-TO-VECTOR-DATA."
(make-array size :element-type '(unsigned-byte 8)))
(deftype shareable-byte-vector ()
`(vector (unsigned-byte 8)))
(defmacro with-pointer-to-vector-data ((ptr-var vector) &body body)
"Bind PTR-VAR to a foreign pointer to the data in VECTOR."
(with-unique-names (vector-var size-var)
`(let ((,vector-var ,vector))
(check-type ,vector-var shareable-byte-vector)
(with-foreign-pointer (,ptr-var (length ,vector-var) ,size-var)
;; copy-in
(loop for i below ,size-var do
(%mem-set (aref ,vector-var i) ,ptr-var :unsigned-char i))
(unwind-protect (progn ,@body)
;; copy-out
(loop for i below ,size-var do
(setf (aref ,vector-var i)
(%mem-ref ,ptr-var :unsigned-char i))))))))
;;;# Foreign Function Calling
(defun parse-foreign-funcall-args (args)
"Return three values, a list of CLISP FFI types, a list of
values to pass to the function, and the CLISP FFI return type."
(let ((return-type nil))
(loop for (type arg) on args by #'cddr
if arg collect (list (gensym) (convert-foreign-type type)) into types
and collect arg into fargs
else do (setf return-type (convert-foreign-type type))
finally (return (values types fargs return-type)))))
(defun convert-calling-convention (convention)
(ecase convention
(:stdcall :stdc-stdcall)
(:cdecl :stdc)))
(defun c-function-type (arg-types rettype convention)
"Generate the apropriate CLISP foreign type specification. Also
takes care of converting the calling convention names."
`(ffi:c-function (:arguments ,@arg-types)
(:return-type ,rettype)
(:language ,(convert-calling-convention convention))))
;;; Quick hack around the fact that the CFFI package is not yet
;;; defined when this file is loaded. I suppose we could arrange for
;;; the CFFI package to be defined a bit earlier, though.
(defun library-handle-form (name)
(flet ((find-cffi-symbol (symbol)
(find-symbol (symbol-name symbol) '#:cffi)))
`(,(find-cffi-symbol '#:foreign-library-handle)
(,(find-cffi-symbol '#:get-foreign-library) ',name))))
(eval-when (:compile-toplevel :load-toplevel :execute)
;; version 2.40 (CVS 2006-09-03, to be more precise) added a
;; PROPERTIES argument to FFI::FOREIGN-LIBRARY-FUNCTION.
(defun post-2.40-ffi-interface-p ()
(let ((f-l-f (find-symbol (string '#:foreign-library-function) '#:ffi)))
(if (and f-l-f (= (length (ext:arglist f-l-f)) 5))
'(:and)
'(:or))))
;; FFI::FOREIGN-LIBRARY-FUNCTION and FFI::FOREIGN-LIBRARY-VARIABLE
;; were deprecated in 2.41 and removed in 2.45.
(defun post-2.45-ffi-interface-p ()
(if (find-symbol (string '#:foreign-library-function) '#:ffi)
'(:or)
'(:and))))
#+#.(cffi-sys::post-2.45-ffi-interface-p)
(defun %foreign-funcall-aux (name type library)
`(ffi::find-foreign-function ,name ,type nil ,library nil nil))
#-#.(cffi-sys::post-2.45-ffi-interface-p)
(defun %foreign-funcall-aux (name type library)
`(ffi::foreign-library-function
,name ,library nil
#+#.(cffi-sys::post-2.40-ffi-interface-p)
nil
,type))
(defmacro %foreign-funcall (name args &key library convention)
"Invoke a foreign function called NAME, taking pairs of
foreign-type/value pairs from ARGS. If a single element is left
over at the end of ARGS, it specifies the foreign return type of
the function call."
(multiple-value-bind (types fargs rettype)
(parse-foreign-funcall-args args)
(let* ((fn (%foreign-funcall-aux
name
`(ffi:parse-c-type
',(c-function-type types rettype convention))
(if (eq library :default)
:default
(library-handle-form library))))
(form `(funcall
(load-time-value
(handler-case ,fn
(error (err)
(warn "~A" err))))
,@fargs)))
(if (eq rettype 'ffi:c-pointer)
`(or ,form (null-pointer))
form))))
(defmacro %foreign-funcall-pointer (ptr args &key convention)
"Similar to %foreign-funcall but takes a pointer instead of a string."
(multiple-value-bind (types fargs rettype)
(parse-foreign-funcall-args args)
`(funcall (ffi:foreign-function
,ptr (load-time-value
(ffi:parse-c-type ',(c-function-type
types rettype convention))))
,@fargs)))
;;;# Callbacks
;;; *CALLBACKS* contains the callbacks defined by the CFFI DEFCALLBACK
;;; macro. The symbol naming the callback is the key, and the value
;;; is a list containing a Lisp function, the parsed CLISP FFI type of
;;; the callback, and a saved pointer that should not persist across
;;; saved images.
(defvar *callbacks* (make-hash-table))
;;; Return a CLISP FFI function type for a CFFI callback function
;;; given a return type and list of argument names and types.
(eval-when (:compile-toplevel :load-toplevel :execute)
(defun callback-type (rettype arg-names arg-types convention)
(ffi:parse-c-type
`(ffi:c-function
(:arguments ,@(mapcar (lambda (sym type)
(list sym (convert-foreign-type type)))
arg-names arg-types))
(:return-type ,(convert-foreign-type rettype))
(:language ,(convert-calling-convention convention))))))
;;; Register and create a callback function.
(defun register-callback (name function parsed-type)
(setf (gethash name *callbacks*)
(list function parsed-type
(ffi:with-foreign-object (ptr 'ffi:c-pointer)
;; Create callback by converting Lisp function to foreign
(setf (ffi:memory-as ptr parsed-type) function)
(ffi:foreign-value ptr)))))
;;; Restore all saved callback pointers when restarting the Lisp
;;; image. This is pushed onto CUSTOM:*INIT-HOOKS*.
;;; Needs clisp > 2.35, bugfix 2005-09-29
(defun restore-callback-pointers ()
(maphash
(lambda (name list)
(register-callback name (first list) (second list)))
*callbacks*))
;;; Add RESTORE-CALLBACK-POINTERS to the lists of functions to run
;;; when an image is restarted.
(eval-when (:load-toplevel :execute)
(pushnew 'restore-callback-pointers custom:*init-hooks*))
;;; Define a callback function NAME to run BODY with arguments
;;; ARG-NAMES translated according to ARG-TYPES and the return type
;;; translated according to RETTYPE. Obtain a pointer that can be
;;; passed to C code for this callback by calling %CALLBACK.
(defmacro %defcallback (name rettype arg-names arg-types body
&key convention)
`(register-callback
',name
(lambda ,arg-names
;; Work around CLISP's FFI:C-POINTER type and convert NIL values
;; back into a null pointers.
(let (,@(loop for name in arg-names
and type in arg-types
when (eq type :pointer)
collect `(,name (or ,name (null-pointer)))))
,body))
,(callback-type rettype arg-names arg-types convention)))
;;; Look up the name of a callback and return a pointer that can be
;;; passed to a C function. Signals an error if no callback is
;;; defined called NAME.
(defun %callback (name)
(multiple-value-bind (list winp) (gethash name *callbacks*)
(unless winp
(error "Undefined callback: ~S" name))
(third list)))
;;;# Loading and Closing Foreign Libraries
(defun %load-foreign-library (name path)
"Load a foreign library from PATH."
(declare (ignore name))
#+#.(cffi-sys::post-2.45-ffi-interface-p)
(ffi:open-foreign-library path)
#-#.(cffi-sys::post-2.45-ffi-interface-p)
(ffi::foreign-library path))
(defun %close-foreign-library (handle)
"Close a foreign library."
(ffi:close-foreign-library handle))
(defun native-namestring (pathname)
(namestring pathname))
;;;# Foreign Globals
(defun %foreign-symbol-pointer (name library)
"Returns a pointer to a foreign symbol NAME."
(prog1 (ignore-errors
(ffi:foreign-address
#+#.(cffi-sys::post-2.45-ffi-interface-p)
(ffi::find-foreign-variable name nil library nil nil)
#-#.(cffi-sys::post-2.45-ffi-interface-p)
(ffi::foreign-library-variable name library nil nil)))))
Jump to Line
Something went wrong with that request. Please try again.