This commit is contained in:
Ian Keane 2020-02-18 14:21:14 -05:00
parent 276853ba84
commit 1cb167b597
361 changed files with 77302 additions and 4 deletions

View file

@ -0,0 +1,211 @@
;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
;;;
;;; asdf.lisp --- ASDF components for cffi/c2ffi.
;;;
;;; Copyright (C) 2015, Attila Lendvai <attila@lendvai.name>
;;;
;;; 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.
;;;
(in-package #:cffi/c2ffi)
(defclass c2ffi-file (cl-source-file)
((package :initarg :package
:initform nil
:accessor c2ffi-file/package)
(c2ffi-executable :initarg :c2ffi-executable
:accessor c2ffi-file/c2ffi-executable)
(trace-c2ffi :initarg :trace-c2ffi
:accessor c2ffi-file/trace-c2ffi)
(prelude :initform nil
:initarg :prelude
:accessor c2ffi-file/prelude)
(sys-include-paths :initarg :sys-include-paths
:initform nil
:accessor c2ffi-file/sys-include-paths)
(exclude-archs :initarg :exclude-archs
:initform nil
:accessor c2ffi-file/exclude-archs)
;; The following slots correspond to an arg of the same name for
;; the generator function. No accessors are needed, they just hold
;; the data until it gets delegated to the generator function using
;; SLOT-VALUE and a LOOP.
(ffi-name-transformer :initarg :ffi-name-transformer
:initform 'default-ffi-name-transformer)
(ffi-name-export-predicate :initarg :ffi-name-export-predicate
:initform 'default-ffi-name-export-predicate)
(ffi-type-transformer :initarg :ffi-type-transformer
:initform 'default-ffi-type-transformer)
(callback-factory :initarg :callback-factory
:initform 'default-callback-factory)
(foreign-library-name :initarg :foreign-library-name
:initform nil)
(foreign-library-spec :initarg :foreign-library-spec
:initform nil)
(emit-generated-name-mappings :initarg :emit-generated-name-mappings
:initform :t)
(include-sources :initarg :include-sources
:initform :all)
(exclude-sources :initarg :exclude-sources
:initform nil)
(include-definitions :initarg :include-definitions
:initform :all)
(exclude-definitions :initarg :exclude-definitions
:initform nil))
(:default-initargs
:type nil)
(:documentation
"The input of this ASDF component is a C header file and the configuration for
the binding generation process. This header file will define the initial scope of
the generation process, which can be further filtered by other configuration
parameters.
A clang/llvm based external program called 'c2ffi' is used to process this header
file and generate a json spec file for each supported architecture triplet. Normally
these .spec files are only (re)generated by the author of the lib and are checked into
the corresponding source repository. It needs to be done manually by invoking the
following command:
(cffi/c2ffi:generate-spec :your-system)
which is a shorthand for:
(asdf:operate 'cffi/c2ffi::generate-spec-op :your-system)
The generation of the underlying platform's json file must succeed, but the
generation for the other arch's is allowed to fail
\(see ENSURE-SPEC-FILE-IS-UP-TO-DATE for details).
During the normal build process the json file is used as the input to generate
a lisp file containing the CFFI definitions (see PROCESS-C2FFI-SPEC-FILE).
This file will be placed next to the .spec file, and will be compiled as any
other lisp file. This process requires loading the ASDF system called
\"cffi/c2ffi-generator\" that has more dependencies than CFFI itself. If you
want to avoid those extra dependencies in your project, then you can check in
these generated lisp files into your source repository, but keep in mind that
you'll need to manually force their regeneration if CFFI/C2FFI itself gets
updated (by e.g. deleting them from the filesystem) ."))
(defun input-file (operation component)
(let ((files (input-files operation component)))
(assert (length=n-p files 1))
(first files)))
(defclass generate-spec-op (downward-operation)
())
(defun generate-spec (system)
(asdf:operate 'generate-spec-op system))
(defmethod input-files ((op generate-spec-op) (c c2ffi-file))
(list (component-pathname c)))
(defmethod component-depends-on ((op generate-spec-op) (c c2ffi-file))
`((prepare-op ,c) ,@(call-next-method)))
(defmethod output-files ((op generate-spec-op) (c c2ffi-file))
(let* ((input-file (input-file op c))
(spec-file (spec-path input-file)))
(values
(list spec-file)
;; Tell ASDF not to apply output translation.
t)))
(defmethod perform ((op generate-spec-op) (c asdf:component))
(values))
(defmethod perform ((op generate-spec-op) (c c2ffi-file))
(let ((input-file (input-file op c))
(*c2ffi-executable* (if (slot-boundp c 'c2ffi-executable)
(c2ffi-file/c2ffi-executable c)
*c2ffi-executable*))
(*trace-c2ffi* (if (slot-boundp c 'trace-c2ffi)
(c2ffi-file/trace-c2ffi c)
*trace-c2ffi*)))
;; NOTE: we don't call OUTPUT-FILE here, which may be a violation
;; of the ASDF contract, that promises that OUTPUT-FILE can be
;; customized by users.
(ensure-spec-file-is-up-to-date
input-file
:exclude-archs (c2ffi-file/exclude-archs c)
:sys-include-paths (c2ffi-file/sys-include-paths c))))
(defclass generate-lisp-op (downward-operation)
())
(defmethod component-depends-on ((op generate-lisp-op) (c c2ffi-file))
`((load-op ,(find-system "cffi/c2ffi-generator"))
,@(call-next-method)))
(defmethod component-depends-on ((op compile-op) (c c2ffi-file))
`((generate-lisp-op ,c) ,@(call-next-method)))
(defmethod component-depends-on ((op load-source-op) (c c2ffi-file))
`((generate-lisp-op ,c) ,@(call-next-method)))
(defmethod input-files ((op generate-lisp-op) (c c2ffi-file))
(list (output-file 'generate-spec-op c)))
(defmethod input-files ((op compile-op) (c c2ffi-file))
(list (output-file 'generate-lisp-op c)))
(defmethod output-files ((op generate-lisp-op) (c c2ffi-file))
(let* ((spec-file (input-file op c))
(generated-lisp-file (make-pathname :type "lisp"
:defaults spec-file)))
(values
(list generated-lisp-file)
;; Tell ASDF not to apply output translation.
t)))
(defmethod perform ((op generate-lisp-op) (c c2ffi-file))
(let ((spec-file (input-file op c))
(generated-lisp-file (output-file op c)))
(with-staging-pathname (tmp-output generated-lisp-file)
(format *debug-io* "~&; CFFI/C2FFI is generating the file ~S~%" generated-lisp-file)
(apply 'process-c2ffi-spec-file
spec-file (c2ffi-file/package c)
:output tmp-output
:output-encoding (asdf:component-encoding c)
:prelude (let ((prelude (c2ffi-file/prelude c)))
(if (and (pathnamep prelude)
(not (absolute-pathname-p prelude)))
(merge-pathnames* prelude (component-pathname c))
prelude))
;; The following slots and keyword args have the same name in the ASDF
;; component and in PROCESS-C2FFI-SPEC-FILE, and this loop copies them.
(loop
:for arg :in '(ffi-name-transformer
ffi-name-export-predicate
ffi-type-transformer
callback-factory
foreign-library-name
foreign-library-spec
emit-generated-name-mappings
include-sources
exclude-sources
include-definitions
exclude-definitions)
:append (list (make-keyword arg)
(slot-value c arg)))))))
;; Allow for naked :cffi/c2ffi-file in asdf definitions.
(setf (find-class 'asdf::cffi/c2ffi-file) (find-class 'c2ffi-file))

View file

@ -0,0 +1,194 @@
;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
;;;
;;; c2ffi.lisp --- c2ffi related code
;;;
;;; Copyright (C) 2013, Ryan Pavlik <rpavlik@gmail.com>
;;; Copyright (C) 2015, Attila Lendvai <attila@lendvai.name>
;;;
;;; 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.
;;;
(in-package #:cffi/c2ffi)
;;; NOTE: Most of this has been taken over from cl-autowrap.
;;; Note this is rather untested and not very extensive at the moment;
;;; it should probably work on linux/win/osx though. Patches welcome.
(defun local-cpu ()
#+x86-64 "x86_64"
#+(and (not (or x86-64 freebsd)) x86) "i686"
#+(and (not x86-64) x86 freebsd) "i386"
#+arm "arm")
(defun local-vendor ()
#+(or linux windows) "-pc"
#+darwin "-apple"
#+(not (or linux windows darwin)) "-unknown")
(defun local-os ()
#+linux "-linux"
#+windows "-windows-msvc"
#+darwin "-darwin9"
#+freebsd "-freebsd")
(defun local-environment ()
#+linux "-gnu"
#-linux "")
(defun local-arch ()
(strcat (local-cpu) (local-vendor) (local-os) (local-environment)))
(defparameter *known-archs*
'("i686-pc-linux-gnu"
"x86_64-pc-linux-gnu"
"i686-pc-windows-msvc"
"x86_64-pc-windows-msvc"
"i686-apple-darwin9"
"x86_64-apple-darwin9"
"i386-unknown-freebsd"
"x86_64-unknown-freebsd"))
(defvar *c2ffi-executable* "c2ffi")
(defvar *trace-c2ffi* nil)
(defun c2ffi-executable-available? ()
;; This is a hack to determine if c2ffi exists; it assumes if it
;; doesn't exist, we will get a return code other than 0.
(zerop (nth-value 2 (uiop:run-program `(,*c2ffi-executable* "-h")
:ignore-error-status t))))
(defun run-program* (program args &key (output (if *trace-c2ffi* *standard-output* nil))
(error-output (if *trace-c2ffi* *error-output* nil))
ignore-error-status)
(when *trace-c2ffi*
(format *debug-io* "~&; Invoking: ~A~{ ~A~}~%" program args))
(zerop (nth-value 2 (uiop:run-program (list* program args) :output output
:error-output error-output
:ignore-error-status ignore-error-status))))
(defun generate-spec-with-c2ffi (input-header-file output-spec-path
&key arch sys-include-paths ignore-error-status)
"Run c2ffi on `INPUT-HEADER-FILE`, outputting to `OUTPUT-FILE` and
`MACRO-OUTPUT-FILE`, optionally specifying a target triple `ARCH`."
(uiop:with-temporary-file (:pathname tmp-macro-file
:keep *trace-c2ffi*)
nil ; workaround for an UIOP bug; delme eventually (attila, 2016-01-27).
:close-stream
(let* ((arch (when arch (list "--arch" arch)))
(sys-include-paths (loop
:for dir :in sys-include-paths
:append (list "--sys-include" dir))))
;; Invoke c2ffi to first emit C #define's into TMP-MACRO-FILE. We ask c2ffi
;; to first generate a file of C global variables that are assigned the
;; value of the corresponding #define's, so that in the second pass below
;; the C compiler evaluates for us their right hand side and thus we can
;; get hold of their value. This is a kludge and eventually we could/should
;; support generating cffi-grovel files, and in grovel mode not rely
;; on this kludge anymore.
(when (run-program* *c2ffi-executable* (list* (namestring input-header-file)
"--driver" "null"
"--macro-file" (namestring tmp-macro-file)
(append arch sys-include-paths))
:output *standard-output*
:ignore-error-status ignore-error-status)
;; Write a tmp header file that #include's the original input file and
;; the above generated macros file which will form the input for our
;; final, second pass.
(uiop:with-temporary-file (:stream tmp-include-file-stream
:pathname tmp-include-file
:keep *trace-c2ffi*)
(format tmp-include-file-stream "#include \"~A\"~%" input-header-file)
(format tmp-include-file-stream "#include \"~A\"~%" tmp-macro-file)
:close-stream
;; Invoke c2ffi again to generate the final output.
(run-program* *c2ffi-executable* (list* (namestring tmp-include-file)
"--output" (namestring output-spec-path)
(append arch sys-include-paths))
:output *standard-output*
:ignore-error-status ignore-error-status))))))
(defun spec-path (base-name &key version (arch (local-arch)))
(check-type base-name pathname)
(make-pathname :defaults base-name
:name (strcat (pathname-name base-name)
(if version
(strcat "-" version)
"")
"."
arch)
:type "spec"))
(defun find-local-spec (base-name &optional (errorp t))
(let* ((spec-path (spec-path base-name))
(probed (probe-file spec-path)))
(if probed
spec-path
(when errorp
(error "c2ffi spec file not found for base name ~S" base-name)))))
(defun ensure-spec-file-is-up-to-date (header-file-path
&key exclude-archs sys-include-paths version)
(let ((spec-path (find-local-spec header-file-path nil)))
(flet ((regenerate-spec-file ()
(let ((local-arch (local-arch)))
(unless (c2ffi-executable-available?)
(error "No spec found for ~S on arch '~A' and c2ffi not found"
header-file-path local-arch))
(generate-spec-with-c2ffi header-file-path
(spec-path header-file-path
:arch local-arch
:version version)
:arch local-arch
:sys-include-paths sys-include-paths)
;; Try to run c2ffi for other architectures, but tolerate failure
(dolist (arch *known-archs*)
(unless (or (string= local-arch arch)
(member arch exclude-archs :test #'string=))
(unless (generate-spec-with-c2ffi header-file-path
(spec-path header-file-path
:arch arch
:version version)
:arch arch
:sys-include-paths sys-include-paths
:ignore-error-status t)
(warn "Failed to generate spec for other arch: ~S" arch))))
(find-local-spec header-file-path))))
(if (and spec-path
(uiop:timestamp< (file-write-date header-file-path)
(file-write-date spec-path)))
spec-path ; it's up to date, just return it as is
(restart-case
(regenerate-spec-file)
(touch-old-copy ()
:report (lambda (stream)
(format stream "Update the modification time of the out-of-date copy ~S" spec-path))
;; Make it only be visible when the spec file exists (but it's out of date)
:test (lambda (condition)
(declare (ignore condition))
(not (null spec-path)))
;; Update the last modification time. Yes, it's convoluted and wasteful,
;; but I can't see any other way.
(with-staging-pathname (tmp-file spec-path)
(copy-file spec-path tmp-file))
;; The return value of RESTART-CASE
spec-path))))))

View file

@ -0,0 +1,838 @@
;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
;;;
;;; generator.lisp --- Generate CFFI bindings for a c2ffi output.
;;;
;;; Copyright (C) 2015, Attila Lendvai <attila@lendvai.name>
;;;
;;; 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.
;;;
(in-package #:cffi/c2ffi)
;;; Output generation happens in one phase, straight into the output
;;; stream. There's minimal look-ahead (for source-location and name)
;;; which is needed to apply user specified filters in time.
;;;
;;; Each CFFI form is also EVAL'd during generation because the CFFI
;;; type lookup/parsing mechanism is used while generating the output.
;;;
;;; Nomenclature:
;;;
;;; - variable names in this file are to be interpreted in the
;;; C,c2ffi,json context, and 'cffi' is added to names that denote
;;; the cffi name.
;;;
;;; Possible improvments:
;;;
;;; - generate an additional grovel file for C inline function
;;; declarations found in header files
;;;
;;; - generate struct-by-value DEFCFUN's into a separate file so that
;;; users can decide whether to depend on libffi, or they can make do
;;; without those definitions
(defvar *allow-pointer-type-simplification* t)
(defvar *allow-skipping-struct-fields* t)
(defvar *assume-struct-by-value-support* t)
;; Called on the json name and may return a symbol to be used, or a string.
(defvar *ffi-name-transformer* 'default-ffi-name-transformer)
;; Called on the already transformed name to decide whether to export it
(defvar *ffi-name-export-predicate* 'default-ffi-name-export-predicate)
;; Called on the CFFI type, e.g. to turn (:pointer :char) into a :string.
(defvar *ffi-type-transformer* 'default-ffi-type-transformer)
;; May return up to two closures using VALUES. The first one will be called
;; with each emitted form, and the second one once, at the end. They both may
;; return a list of forms that will be emitted using OUTPUT/CODE.
(defvar *callback-factory* 'default-callback-factory)
(define-constant +generated-file-header+
";;; -*- Mode: lisp -*-~%~
;;;~%~
;;; This file has been automatically generated by cffi/c2ffi. Editing it by hand is not wise.~%~
;;;~%~%"
:test 'equal)
(defvar *c2ffi-output-stream*)
(defun output/export (names package)
(let ((names (uiop:ensure-list names)))
;; Make sure we have something PRINT-READABLY as a package name,
;; i.e. not a SIMPLE-BASE-STRING on SBCL.
(output/code `(export ',names ',(make-symbol (package-name package))))))
(defun output/code (form)
(check-type form cons)
(format *c2ffi-output-stream* "~&")
(write form
:stream *c2ffi-output-stream*
:circle t
:pretty t
:escape t
:readably t)
(format *c2ffi-output-stream* "~%~%")
(unless (member (first form) '(cffi:defcfun alexandria:define-constant) :test 'eq)
(eval form)))
(defun output/string (message-control &rest message-arguments)
(apply 'format *c2ffi-output-stream* message-control message-arguments))
;; NOTE: as per c2ffi json output. A notable difference to
;; CFFI::*BUILT-IN-FOREIGN-TYPES* is the presence of :SIGNED-CHAR.
(define-constant +c-builtin-types+ '(":void" ":_Bool" ":char" ":signed-char" ":unsigned-char" ":short"
":unsigned-short" ":int" ":unsigned-int" ":long" ":unsigned-long"
":long-long" ":unsigned-long-long" ":float" ":double" ":long-double")
:test 'equal)
(define-condition unsupported-type (cffi::foreign-type-error)
((json-definition :initarg :json-definition
:accessor json-definition-of)))
(defun unsupported-type (json-entry)
(error 'unsupported-type :type-name nil :json-definition json-entry))
;;;;;;
;;; Utilities
(defun compile-rules (rules)
(case rules
(:all rules)
(t (mapcar (lambda (pattern)
(check-type pattern string "Patterns in the inclusion/exclusion rules must be strings.")
(let ((scanner (cl-ppcre:create-scanner pattern)))
(named-lambda cffi/c2ffi/cl-ppcre-rule-matcher
(string)
(funcall scanner string 0 (length string)))))
rules))))
(defun include-definition? (name source-location
include-definitions exclude-definitions
include-sources exclude-sources)
(labels
((covered-by-a-rule? (name rules)
(or (eq rules :all)
(not (null (some (rcurry #'funcall name) rules)))))
(weak? (rules)
(eq :all rules))
(strong? (name rules)
(and name
(not (weak? rules))
(covered-by-a-rule? name rules))))
(let* ((excl-def/weak (weak? exclude-definitions))
(excl-def/strong (strong? name exclude-definitions))
(incl-def/weak (weak? include-definitions))
(incl-def/strong (strong? name include-definitions))
(excl-src/weak (weak? exclude-sources))
(excl-src/strong (strong? source-location exclude-sources))
(incl-src/weak (weak? include-sources))
(incl-src/strong (strong? source-location include-sources))
(incl/strong (or incl-def/strong
incl-src/strong))
(excl/strong (or excl-def/strong
excl-src/strong))
(incl/weak (or incl-def/weak
incl-src/weak))
(excl/weak (or excl-def/weak
excl-src/weak)))
(or incl-def/strong
(and (not excl/strong)
(or incl/strong
(and incl/weak
;; we want src exclude rules to be stronger
(not excl-src/weak))
(not excl/weak)))))))
(defun coerce-to-byte-size (bit-size)
(let ((byte-size (/ bit-size 8)))
(unless (integerp byte-size)
(error "Non-byte size encountered where it wasn't expected (~A bits)" bit-size))
byte-size))
(defmacro assume (condition &optional format-control &rest format-arguments)
"Similar to ASSERT, but WARN's only."
`(unless ,condition
,(if format-control
`(warn ,format-control ,@format-arguments)
`(warn "ASSUME failed: ~S" ',condition))))
(defun canonicalize-transformer-hook (hook)
(etypecase hook
((and (or function symbol)
(not null))
hook)
(string
(the symbol (safe-read-from-string hook)))))
;;;;;;
;;; Json access
(defun json-value (alist key &key (otherwise nil otherwise?))
(check-type alist list)
(check-type key (and symbol (not null)))
(let* ((entry (assoc key alist))
(result (cond
(entry
(cdr entry))
(otherwise?
otherwise)
(t (error "Key ~S not found in json entry ~S." key alist)))))
(if (equal result "")
nil
result)))
(defmacro with-json-values ((json-entry &rest args) &body body)
(if (null args)
`(progn
,@body)
(once-only (json-entry)
`(let (,@(loop
:for entry :in args
:collect (let* ((args (ensure-list entry))
(name (pop args))
(key (or (pop args)
(make-keyword (symbol-name name)))))
(destructuring-bind
;; using &optional would trigger a warning (on SBCL)
(&key (otherwise nil otherwise?))
args
`(,name
(json-value ,json-entry ,key ,@(when otherwise?
`(:otherwise ,otherwise))))))))
,@body))))
(defun expected-json-keys (alist &rest keys)
(let* ((keys (list* :location keys))
(outliers (remove-if (lambda (el)
(member (car el) keys :test 'eq))
alist)))
(when outliers
(warn "Unexpected key(s) in json entry ~S: ~S" alist outliers))))
;;;;;;
;;; Namespaces, names and conversions
;; an alist of (name . hashtable)
(defvar *generated-names*)
(defvar *anon-name-counter*)
(defvar *anon-entities*)
(defun register-anon-entity (id name)
(check-type id integer)
(check-type name string)
(assert (not (zerop (length name))))
(setf (gethash id *anon-entities*) name)
name)
(defun lookup-anon-entity (id)
(or (gethash id *anon-entities*)
(error "Could not find anonymous entity with id ~S." id)))
(defun generate-anon-name (base-name)
(format nil "~A"
(strcat (symbol-name base-name)
(princ-to-string (incf *anon-name-counter*)))))
(defun valid-name-or-die (name)
;; checks for valid json names (*not* CFFI names)
(etypecase name
(string
(assert (not (zerop (length name)))))
(cons
(assert (= 2 (length name)))
(assert (member (first name) '(:struct :union :enum)))
(valid-name-or-die (second name)))))
(defun call-hook (hook &rest args)
(apply hook
;; indiscriminately add one keyword arg entry to warn
(append args '(just-a-warning "Make sure your transformer hook has &key &allow-other-keys for future extendability."))))
(defun find-cffi-type-or-die (type-name &optional (namespace :default))
(when (eq namespace :enum)
;; TODO FIXME this should be cleaned up in CFFI. more about namespace confusion at:
;; https://bugs.launchpad.net/cffi/+bug/1527947
(setf namespace :default))
(cffi::find-type-parser type-name namespace))
(define-constant +name-kinds+ '(:struct :union :function :variable :type
:constant :field :argument :enum :member)
:test 'equal)
(deftype ffi-name-kind ()
'#.(list* 'member +name-kinds+))
(defun json-name-to-cffi-name (name kind &optional anonymous)
(check-type name string)
(check-type kind ffi-name-kind)
(when *ffi-name-transformer*
(setf name (call-hook *ffi-name-transformer* name kind))
(unless (or (and (symbolp name)
(not (null name)))
(stringp name))
(error "The FFI-NAME-TRANSFORMER ~S returned with ~S which is not a valid name."
*ffi-name-transformer* name)))
(let ((cffi-name (if (symbolp name)
name
(intern name))))
(when (and (not anonymous)
(boundp '*generated-names*))
;; TODO FIXME this function also gets called for e.g. argument types of a function. and
;; if the function ends up *not* getting emitted, e.g. because of a missing type, then
;; we wrongly record here the missing type in the *generated-names* registry.
(setf (gethash name (cdr (assoc kind *generated-names*)))
cffi-name))
cffi-name))
(defun default-callback-factory (&key &allow-other-keys)
(values))
(defun default-ffi-name-transformer (name kind &key &allow-other-keys)
(check-type name string)
(case kind
#+nil
((:constant :member)
(assert (not (symbolp name)))
(format nil "+~A+" name))
(t name)))
(defun change-case-to-readtable-case (name &optional (reatable *readtable*))
(ecase (readtable-case reatable)
(:upcase (string-upcase name))
(:downcase (string-downcase name))
(:preserve name)
;; (:invert no, you don't)
))
(defun camelcased? (name)
(and (>= (length name) 3)
(let ((lower 0)
(upper 0))
(loop
:for char :across name
:do (cond
((upper-case-p char)
(incf upper))
((lower-case-p char)
(incf lower))))
(unless (or (zerop lower)
(zerop upper))
(let ((ratio (/ upper lower)))
(and (<= 0.05 ratio 0.5)))))))
(defun camelcase-to-dash-separated (name)
(coerce (loop
:for char :across name
:for index :from 0
:when (and (upper-case-p char)
(not (zerop index)))
:collect #\-
:collect (char-downcase char))
'string))
(defun maybe-camelcase-to-dash-separated (name)
(if (camelcased? name)
(camelcase-to-dash-separated name)
name))
(defun default-ffi-name-export-predicate (symbol &key &allow-other-keys)
(declare (ignore symbol))
nil)
(defun default-ffi-type-transformer (type context &key &allow-other-keys)
(declare (ignore context))
(cond
((and (consp type)
(eq :pointer (first type)))
(let ((pointed-to-type (second type)))
(if (eq pointed-to-type :char)
:string
type)))
(t
type)))
(defun function-pointer-type-name ()
(symbolicate '#:function-pointer))
(defmacro with-allowed-foreign-type-errors ((on-failure-form &key (enabled t)) &body body)
(with-unique-names (type-block)
`(block ,type-block
(handler-bind
((cffi::foreign-type-error
(lambda (_)
(declare (ignore _))
(when ,enabled
(return-from ,type-block ,on-failure-form)))))
,@body))))
(defun %json-type-to-cffi-type (json-entry)
(with-json-values (json-entry tag)
(let ((cffi-type
(cond
((switch (tag :test 'equal)
(":void" :void)
(":_Bool" :bool)
;; regarding :signed-char see https://stackoverflow.com/questions/436513/char-signed-char-char-unsigned-char
(":char" :char)
(":signed-char" :char)
(":unsigned-char" :unsigned-char)
(":short" :short)
(":unsigned-short" :unsigned-short)
(":int" :int)
(":unsigned-int" :unsigned-int)
(":long" :long)
(":unsigned-long" :unsigned-long)
(":long-long" :long-long)
(":unsigned-long-long" :unsigned-long-long)
(":float" :float)
(":double" :double)
;; TODO FIXME
;;(":long-double" :long-double)
)
;; return the result of the condition expression
)
((or (progn
(assert (not (member tag +c-builtin-types+ :test 'equal)) ()
"Not all C basic types are covered! The outlier is: ~S" tag)
nil)
(equal tag ":struct")
(equal tag ":union"))
;; ":struct" is a "struct foo-struct var" kind of reference
(expected-json-keys json-entry :name :tag :id)
(with-json-values (json-entry name id)
(let* ((kind (if (equal tag ":struct")
:struct
:union))
(cffi-name (if name
(json-name-to-cffi-name name kind)
(lookup-anon-entity id))))
(find-cffi-type-or-die cffi-name kind)
`(,kind ,cffi-name))))
((or (equal tag "struct")
(equal tag "union"))
;; "struct" denotes a "struct {} var", or "typedef struct {} my_type"
;; kind of inline anonymous declaration. Let's call PROCESS-C2FFI-ENTRY
;; to emit it for us, and return with the generated name (first value)
;; as if it was a standalone toplevel struct definition.
;; TODO is it a problem that we don't invoke the CALLBACK-FACTORY stuff here?
(let ((form (process-c2ffi-entry json-entry))
(kind (if (equal tag "struct")
:struct
:union)))
(assert (and (consp form)
(member (first form) '(cffi:defcstruct cffi:defcunion))))
`(,kind ,(first (ensure-list (second form))))))
((equal tag ":enum")
;; ":enum" is an "enum foo var" kind of reference
(expected-json-keys json-entry :name :tag :id)
(with-json-values (json-entry name id)
(let ((cffi-name (json-name-to-cffi-name (or name
(lookup-anon-entity id))
:enum)))
(find-cffi-type-or-die cffi-name :enum)
;; TODO FIXME this would be the proper one, but CFFI is broken: `(:enum ,cffi-name)
cffi-name)))
((equal tag "enum")
;; "enum" is an inline "typedef enum {m1, m2} var" kind of inline declaration
(expected-json-keys json-entry :name :tag :id)
;; TODO FIXME similarly to struct, but it would be nice to see an example
(error "not yet implemented"))
((equal tag ":array")
(expected-json-keys json-entry :tag :type :size)
(with-json-values (json-entry type size)
(check-type size integer)
`(:array ,(json-type-to-cffi-type type) ,size)))
((equal tag ":pointer")
(expected-json-keys json-entry :tag :type :id)
(with-json-values (json-entry type)
`(:pointer ,(with-allowed-foreign-type-errors
(:void :enabled *allow-pointer-type-simplification*)
(json-type-to-cffi-type type)))))
((equal tag ":function-pointer")
(expected-json-keys json-entry :tag)
(function-pointer-type-name))
((equal tag ":function")
(unsupported-type json-entry))
(t
(assert (not (starts-with #\: tag)))
(let ((cffi-name (json-name-to-cffi-name tag :type)))
;; TODO FIXME json-name-to-cffi-name collects the mentioned
;; types to later emit +TYPE-NAMES+, but if this next
;; find-cffi-type-or-die dies then the entire function is
;; skipped.
(find-cffi-type-or-die cffi-name)
cffi-name)))))
(assert cffi-type () "Failed to map ~S to a cffi type" json-entry)
cffi-type)))
(defun should-export-p (symbol)
(and symbol
(symbolp symbol)
(not (keywordp symbol))
*ffi-name-export-predicate*
(call-hook *ffi-name-export-predicate* symbol)))
(defun json-type-to-cffi-type (json-entry &optional (context nil context?))
(let ((cffi-type (%json-type-to-cffi-type json-entry)))
(if context?
(call-hook *ffi-type-transformer* cffi-type context)
cffi-type)))
;;;;;;
;;; Entry point, the "API"
(defun process-c2ffi-spec-file (c2ffi-spec-file package-name
&key
(allow-pointer-type-simplification *allow-pointer-type-simplification*)
(allow-skipping-struct-fields *allow-skipping-struct-fields*)
(assume-struct-by-value-support *assume-struct-by-value-support*)
;; either a pathname or a string (will be copied as is),
;; or a function that will be funcall'd with one argument
;; to emit a form (i.e. OUTPUT/CODE).
prelude
(output (make-pathname :name (strcat (pathname-name c2ffi-spec-file) ".cffi-tmp")
:type "lisp" :defaults c2ffi-spec-file))
(output-encoding asdf:*default-encoding*)
;; The args following this point are mirrored in the ASDF
;; component on the same name.
(ffi-name-transformer *ffi-name-transformer*)
(ffi-name-export-predicate *ffi-name-export-predicate*)
;; as per CFFI:DEFINE-FOREIGN-LIBRARY and CFFI:LOAD-FOREIGN-LIBRARY
(ffi-type-transformer *ffi-type-transformer*)
(callback-factory *callback-factory*)
foreign-library-name
foreign-library-spec
(emit-generated-name-mappings t)
(include-sources :all)
exclude-sources
(include-definitions :all)
exclude-definitions)
"Generates a lisp file with CFFI definitions from C2FFI-SPEC-FILE.
PACKAGE-NAME will be overwritten, it assumes full control over the
target package."
(check-type c2ffi-spec-file (or pathname string))
(macrolet ((@ (var)
`(setf ,var (compile-rules ,var))))
(@ include-sources)
(@ exclude-sources)
(@ include-definitions)
(@ exclude-definitions))
(with-standard-io-syntax
(with-input-from-file (in c2ffi-spec-file :external-format (asdf/driver:encoding-external-format :utf-8))
(with-output-to-file (*c2ffi-output-stream* output :if-exists :supersede
:external-format (asdf/driver:encoding-external-format output-encoding))
(let* ((*package* (or (find-package package-name)
(make-package package-name)))
;; Make sure we use an uninterned symbol, so that it's neutral to READTABLE-CASE.
(package-name (make-symbol (package-name *package*)))
;; Let's rebind a copy, so that when we are done with
;; the generation (which also EVAL's the forms) then
;; the CFFI type repository is also reverted back to
;; the previous state. This avoids redefinition warning
;; when the generated file gets compiled and loaded
;; later.
(cffi::*type-parsers* (copy-hash-table cffi::*type-parsers*))
(*anon-name-counter* 0)
(*anon-entities* (make-hash-table))
(*generated-names* (mapcar (lambda (key)
`(,key . ,(make-hash-table :test 'equal)))
+name-kinds+))
(*allow-pointer-type-simplification* allow-pointer-type-simplification)
(*allow-skipping-struct-fields* allow-skipping-struct-fields)
(*assume-struct-by-value-support* assume-struct-by-value-support)
(*ffi-name-transformer* (canonicalize-transformer-hook ffi-name-transformer))
(*ffi-name-export-predicate* (canonicalize-transformer-hook ffi-name-export-predicate))
(*ffi-type-transformer* (canonicalize-transformer-hook ffi-type-transformer))
(*callback-factory* (canonicalize-transformer-hook callback-factory))
(*read-default-float-format* 'double-float)
(json (json:decode-json in)))
(output/string +generated-file-header+)
;; some forms that are always emitted
(mapc 'output/code
;; Make sure the package exists. We don't even want to :use COMMON-LISP here,
;; to avoid any possible name clashes.
`((uiop:define-package ,package-name (:use))
(in-package ,package-name)
(cffi:defctype ,(function-pointer-type-name) :pointer)))
(when (and foreign-library-name
foreign-library-spec)
(when (stringp foreign-library-name)
(setf foreign-library-name (safe-read-from-string foreign-library-name)))
(output/code `(cffi:define-foreign-library ,foreign-library-name
,@foreign-library-spec))
;; TODO: Unconditionally emitting a USE-FOREIGN-LIBRARY may not be smart.
;; For details see: https://bugs.launchpad.net/cffi/+bug/1593635
(output/code `(cffi:use-foreign-library ,foreign-library-name)))
(etypecase prelude
(null)
(string
(output/string prelude))
(pathname
(with-input-from-file (prelude-stream prelude)
(alexandria:copy-stream prelude-stream *c2ffi-output-stream*
:element-type 'character)))
((or symbol function)
(funcall prelude 'output/code)))
;;
;; Let's enumerate the entries
(multiple-value-bind (form-callback epilogue-callback)
(funcall *callback-factory*)
(dolist (json-entry json)
(with-json-values (json-entry name location)
(let ((source-location-file (subseq location
0
(or (position #\: location)
0))))
(if (include-definition?
name source-location-file
include-definitions exclude-definitions
include-sources exclude-sources)
(progn
(output/string "~&~%;; ~S" location)
(let ((emitted-definition (process-c2ffi-entry json-entry)))
;;
;; Call the plugin to let the user emit a form after the given
;; definition
(when (and emitted-definition
form-callback)
(map nil 'output/code (call-hook form-callback emitted-definition)))))
(output/string "~&;; Skipped ~S due to filters" name)))))
;;
;; Call the plugin to let the user append multiple forms after the
;; emitted definitions
(when epilogue-callback
(map nil 'output/code (call-hook epilogue-callback))))
;;
;; emit optional exports
(maphash
(lambda (package-name symbols)
(output/export (sort (remove-if-not #'should-export-p symbols) #'string<)
package-name))
(get-all-names-by-package *generated-names*))
;;
;; emit optional mappings
(when emit-generated-name-mappings
(mapcar (lambda (entry)
(destructuring-bind (kind variable-name) entry
(output/code `(defparameter
,(intern (symbol-name variable-name))
',(hash-table-alist (cdr (assoc kind *generated-names*)))))))
`((:function #:+function-names+)
(:struct #:+struct-names+)
(:union #:+union-names+)
(:variable #:+variable-names+)
(:type #:+type-names+)
(:constant #:+constant-names+)
(:argument #:+argument-names+)
(:field #:+field-names+))))))))
output)
(defun get-all-names-by-package (name-collection)
(let ((tables (mapcar #'cdr name-collection))
all
(grouped (make-hash-table)))
(loop :for table :in tables :do
(loop :for s :being :the :hash-values :of table :do
(push s all)))
(remove-duplicates all :test #'eq)
(loop :for name :in all
:for package-name := (package-name (symbol-package name))
:do (setf (gethash package-name grouped)
(cons name (gethash package-name grouped))))
grouped))
;;;;;;
;;; Processors for various definitions
(defvar *c2ffi-entry-processors* (make-hash-table :test 'equal))
(defun process-c2ffi-entry (json-entry)
(let* ((kind (json-value json-entry :tag))
(processor (gethash kind *c2ffi-entry-processors*)))
(if processor
(let ((definition-form
(handler-bind
((unsupported-type
(lambda (e)
(warn "Skip definition because cannot map ~S to any CFFI type. The definition is ~S"
(json-definition-of e) json-entry)
(return-from process-c2ffi-entry (values))))
(cffi::undefined-foreign-type-error
(lambda (e)
(output/string "~&;; Skipping definition ~S because of missing type ~S"
json-entry (cffi::foreign-type-error/compound-name e))
(return-from process-c2ffi-entry (values)))))
(funcall processor json-entry))))
(when definition-form
(output/code definition-form)
definition-form))
(progn
(warn "No cffi/c2ffi processor defined for ~A" json-entry)
(values)))))
(defmacro define-processor (kind args &body body)
`(setf (gethash ,(string-downcase kind) *c2ffi-entry-processors*)
(named-lambda ,(symbolicate 'c2ffi-processor/ kind) (-json-entry-)
(with-json-values (-json-entry- ,@args)
,@body))))
(defun %process-struct-like (json-entry kind definer anon-base-name)
(expected-json-keys json-entry :tag :ns :name :id :bit-size :bit-alignment :fields)
(with-json-values (json-entry tag (struct-name :name) fields bit-size id)
(assert (member tag '(":struct" "struct" ":union" "union") :test 'equal))
(flet ((process-field (json-entry)
(with-json-values (json-entry (field-name :name) bit-offset type)
(let ((cffi-type (with-allowed-foreign-type-errors
('failed :enabled *allow-skipping-struct-fields*)
(json-type-to-cffi-type type `(,kind ,struct-name ,field-name)))))
(if (eq cffi-type 'failed)
(output/string "~&;; skipping field due to missing type ~S, full json entry: ~S" type json-entry)
`(,(json-name-to-cffi-name field-name :field)
,cffi-type
,@(unless (eq kind :union)
`(:offset ,(coerce-to-byte-size bit-offset)))))))))
`(,definer (,(json-name-to-cffi-name (or struct-name
(register-anon-entity
id
(generate-anon-name anon-base-name)))
kind
(null struct-name))
:size ,(coerce-to-byte-size bit-size))
,@(remove nil (mapcar #'process-field fields))))))
(define-processor struct ()
(%process-struct-like -json-entry- :struct 'cffi:defcstruct '#:anon-struct-))
(define-processor union ()
(%process-struct-like -json-entry- :union 'cffi:defcunion '#:anon-union-))
(define-processor typedef (name type)
(expected-json-keys -json-entry- :tag :name :ns :type)
`(cffi:defctype ,(json-name-to-cffi-name name :type)
,(json-type-to-cffi-type type `(:typedef ,name))))
(define-processor function (return-type (function-name :name) parameters inline variadic storage-class)
(declare (ignore storage-class))
;; TODO does storage-class matter for FFI accessibility?
#+nil
(assume (equal "extern" storage-class)
"Unexpected function STORAGE-CLASS: ~S for function ~S" storage-class function-name)
(expected-json-keys -json-entry- :tag :name :return-type :parameters :variadic :inline :storage-class :ns)
(let ((uses-struct-by-value? nil))
(flet ((process-arg (json-entry index)
(expected-json-keys json-entry :tag :name :type)
(with-json-values (json-entry tag (argument-name :name) type)
(assert (equal tag "parameter"))
(let* ((cffi-type (json-type-to-cffi-type type `(:function ,function-name ,argument-name)))
(canonicalized-type (cffi::canonicalize-foreign-type cffi-type)))
(when (and (consp canonicalized-type)
(member (first canonicalized-type) '(:struct :union)))
(setf uses-struct-by-value? t))
`(,(if argument-name
(json-name-to-cffi-name argument-name :argument)
(symbolicate '#:arg (princ-to-string index)))
,cffi-type)))))
(let ((cffi-args (loop
:for arg :in parameters
:for index :upfrom 1
:collect (process-arg arg index))))
(cond
((and uses-struct-by-value?
(not *assume-struct-by-value-support*))
(values))
(inline
;; TODO inline functions should go into a separate grovel file?
(output/string "~&;; Skipping inline function ~S" function-name)
(values))
(t `(cffi:defcfun (,function-name ,(json-name-to-cffi-name function-name :function))
,(json-type-to-cffi-type return-type `(:function ,function-name :return-type))
,@(append cffi-args
(when variadic
'(&rest))))))))))
(define-processor extern (name type)
(expected-json-keys -json-entry- :tag :name :type)
`(cffi:defcvar (,name ,(json-name-to-cffi-name name :variable))
,(json-type-to-cffi-type type `(:variable ,name))))
;; ((TAG . enum) (NS . 0) (NAME . ) (ID . 3) (LOCATION . /usr/include/bits/confname.h:24:1) (FIELDS ((TAG . field) (NAME . _PC_LINK_MAX) (VALUE . 0)) ((TAG . field) (NAME . _PC_MAX_CANON) (VALUE . 1)) ((TAG . field) (NAME . _PC_MAX_INPUT) (VALUE . 2)) ((TAG . field) (NAME . _PC_NAME_MAX) (VALUE . 3)) ((TAG . field) (NAME . _PC_PATH_MAX) (VALUE . 4)) ((TAG . field) (NAME . _PC_PIPE_BUF) (VALUE . 5)) ((TAG . field) (NAME . _PC_CHOWN_RESTRICTED) (VALUE . 6)) ((TAG . field) (NAME . _PC_NO_TRUNC) (VALUE . 7)) ((TAG . field) (NAME . _PC_VDISABLE) (VALUE . 8)) ((TAG . field) (NAME . _PC_SYNC_IO) (VALUE . 9)) ((TAG . field) (NAME . _PC_ASYNC_IO) (VALUE . 10)) ((TAG . field) (NAME . _PC_PRIO_IO) (VALUE . 11)) ((TAG . field) (NAME . _PC_SOCK_MAXBUF) (VALUE . 12)) ((TAG . field) (NAME . _PC_FILESIZEBITS) (VALUE . 13)) ((TAG . field) (NAME . _PC_REC_INCR_XFER_SIZE) (VALUE . 14)) ((TAG . field) (NAME . _PC_REC_MAX_XFER_SIZE) (VALUE . 15)) ((TAG . field) (NAME . _PC_REC_MIN_XFER_SIZE) (VALUE . 16)) ((TAG . field) (NAME . _PC_REC_XFER_ALIGN) (VALUE . 17)) ((TAG . field) (NAME . _PC_ALLOC_SIZE_MIN) (VALUE . 18)) ((TAG . field) (NAME . _PC_SYMLINK_MAX) (VALUE . 19)) ((TAG . field) (NAME . _PC_2_SYMLINKS) (VALUE . 20))))
(define-processor enum (name fields id)
(let ((bitmasks 0)
(non-bitmasks 0))
(labels
((for-bitmask-statistics (name value)
(declare (ignore name))
(if (cffi::single-bit-p value)
(incf bitmasks)
(incf non-bitmasks)))
(for-enum-body (name value)
`(,(json-name-to-cffi-name name :member)
,value))
(process-fields (visitor)
(loop
:for json-entry :in fields
:do (expected-json-keys json-entry :tag :name :value)
:collect
(with-json-values (json-entry tag name value)
(assert (equal tag "field"))
(check-type value integer)
(funcall visitor name value)))))
(process-fields #'for-bitmask-statistics)
`(,(if (> (/ bitmasks
(+ non-bitmasks bitmasks))
0.8)
'cffi:defbitfield
'cffi:defcenum)
,(json-name-to-cffi-name (or name
(register-anon-entity
id
(generate-anon-name '#:anon-enum-)))
:enum
(null name))
,@(process-fields #'for-enum-body)))))
(defun make-define-constant-form (name value)
(valid-name-or-die name)
(let ((test-fn (typecase value
(number)
(t 'equal))))
`(alexandria:define-constant ,(json-name-to-cffi-name name :constant)
,value ,@(when test-fn `(:test ',test-fn)))))
(define-processor const (name type (value :value :otherwise nil))
(expected-json-keys -json-entry- :tag :name :type :value :ns)
(let ((cffi-type (json-type-to-cffi-type type `(:contant ,name))))
(cond
((not value)
;; #define __FOO_H and friends... just ignore them.
(values))
((and (member cffi-type '(:int :unsigned-int
:long :unsigned-long
:long-long :unsigned-long-long))
(integerp value))
(make-define-constant-form name value))
((and (member cffi-type '(:float :double))
(floatp value))
(make-define-constant-form name value))
((member cffi-type '(:string (:pointer :char)) :test 'equal)
(make-define-constant-form name value))
(t
(warn "Don't know how to emit a constant of CFFI type ~S, with value ~S (json type is ~S)." cffi-type value type)
(values)))))

View file

@ -0,0 +1,54 @@
;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
;;;
;;; Copyright (C) 2015, Attila Lendvai <attila@lendvai.name>
;;;
;;; 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.
;;;
(uiop:define-package #:cffi/c2ffi
(:mix #:uiop
#:alexandria
#:common-lisp)
(:import-from :asdf
#:cl-source-file
#:find-system
#:output-file
#:output-files
#:input-files
#:perform
#:compile-op
#:load-op
#:load-source-op
#:prepare-op
#:component-pathname
#:component-depends-on
#:downward-operation
#:load-system
#:component-loaded-p)
(:export
#:c2ffi-file
#:camelcased?
#:camelcase-to-dash-separated
#:change-case-to-readtable-case
#:default-ffi-name-transformer
#:default-ffi-type-transformer
#:generate-spec
#:maybe-camelcase-to-dash-separated))

View file

@ -0,0 +1,664 @@
;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
;;;
;;; cffi-abcl.lisp --- CFFI-SYS implementation for ABCL/JNA.
;;;
;;; Copyright (C) 2009, Luis Oliveira <loliveira@common-lisp.net>
;;; Copyright (C) 2012, Mark Evenson <evenson.not.org@gmail.com>
;;;
;;; 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.
;;;
;;; This implementation requires the Java Native Access (JNA) library.
;;; <http://jna.dev.java.net/>
;;;
;;; JNA may be automatically loaded into the current JVM process from
;;; abcl-1.1.0-dev via the contrib mechanism.
(eval-when (:compile-toplevel :load-toplevel :execute)
(require :abcl-contrib)
(require :jna)
(require :jss))
;;; This is a preliminary version that will have to be cleaned up,
;;; optimized, etc. Nevertheless, it passes all of the relevant CFFI
;;; tests except MAKE-POINTER.HIGH. Shareable Vectors are not
;;; implemented yet.
;;;# Administrivia
(defpackage #:cffi-sys
(:use #:cl #:java)
(:import-from #:alexandria #:hash-table-values #:length= #:format-symbol)
(: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
#:with-pointer-to-vector-data
#:make-shareable-byte-vector))
(in-package #:cffi-sys)
;;;# Loading and Closing Foreign Libraries
(defparameter *loaded-libraries* (make-hash-table))
(defun %load-foreign-library (name path)
"Load a foreign library, signals a simple error on failure."
(flet ((load-and-register (name path)
(let ((lib (jstatic "getInstance" "com.sun.jna.NativeLibrary" path)))
(setf (gethash name *loaded-libraries*) lib)
lib))
(foreign-library-type-p (type)
(find type '("so" "dll" "dylib") :test #'string=))
(java-error (e)
(error (jcall (jmethod "java.lang.Exception" "getMessage")
(java-exception-cause e)))))
(handler-case
(load-and-register name path)
(java-exception (e)
;; From JNA http://jna.java.net/javadoc/com/sun/jna/NativeLibrary.html
;; ``[The name] can be short form (e.g. "c"), an explicit
;; version (e.g. "libc.so.6"), or the full path to the library
;; (e.g. "/lib/libc.so.6")''
;;
;; Try to deal with the occurance "libXXX" and "libXXX.so" as
;; "libXXX.so.6" and "XXX" should have succesfully loaded.
(let ((p (pathname path)))
(if (and (not (pathname-directory p))
(= (search "lib" (pathname-name p)) 0))
(let ((short-name (if (foreign-library-type-p (pathname-type p))
(subseq (pathname-name p) 3)
(pathname-name p))))
(handler-case
(load-and-register name short-name)
(java-exception (e) (java-error e))))
(java-error e)))))))
;;; FIXME. Should remove libraries from the hash table.
(defun %close-foreign-library (handle)
"Closes a foreign library."
#+#:ignore (setf *loaded-libraries* (remove handle *loaded-libraries*))
(jcall-raw (jmethod "com.sun.jna.NativeLibrary" "dispose") handle))
;;;
;;; FIXME! We should probably define a private-jfield-accessor that does the hard work once!
(let ((get-declared-fields-jmethod (jmethod "java.lang.Class" "getDeclaredFields")))
(defun private-jfield (class-name field-name instance)
(let ((field (find field-name
(jcall get-declared-fields-jmethod
(jclass class-name))
:key #'jfield-name
:test #'string=)))
(jcall (jmethod "java.lang.reflect.Field" "setAccessible" "boolean")
field +true+)
(jcall (jmethod "java.lang.reflect.Field" "get" "java.lang.Object")
field instance))))
;;; XXX: doesn't match jmethod-arguments.
(let ((get-declared-methods-jmethod (jmethod "java.lang.Class" "getDeclaredMethods")))
(defun private-jmethod (class-name method-name)
(let ((method (find method-name
(jcall get-declared-methods-jmethod
(jclass class-name))
:key #'jmethod-name
:test #'string=)))
(jcall (jmethod "java.lang.reflect.Method" "setAccessible" "boolean")
method +true+)
method)))
(let ((get-declared-constructors-jmethod (jmethod "java.lang.Class"
"getDeclaredConstructors"))
(set-accessible-jmethod (jmethod "java.lang.reflect.Constructor" "setAccessible" "boolean")))
(defun private-jconstructor (class-name &rest params)
(let* ((param-classes (mapcar #'jclass params))
(cons (find-if (lambda (x &aux (cons-params (jconstructor-params x)))
(and (length= param-classes cons-params)
(loop for param in param-classes
and param-x across cons-params
always (string= (jclass-name param)
(jclass-name param-x)))))
(jcall get-declared-constructors-jmethod (jclass class-name)))))
(jcall set-accessible-jmethod cons +true+)
cons)))
;;;# Symbol Case
(defun canonicalize-symbol-name-case (name)
(string-upcase name))
;;;# Pointers
(deftype foreign-pointer ()
'(satisfies pointerp))
(defun pointerp (ptr)
"Return true if PTR is a foreign pointer."
(let ((jclass (jclass-of ptr)))
(when jclass
(jclass-superclass-p (jclass "com.sun.jna.Pointer") jclass))))
(let ((jconstructor (private-jconstructor "com.sun.jna.Pointer" "long")))
(defun make-pointer (address)
"Return a pointer pointing to ADDRESS."
(jnew jconstructor address)))
(defun make-private-jfield-accessor (class-name field-name)
(let ((field (find field-name
(jcall (jmethod "java.lang.Class" "getDeclaredFields")
(jclass class-name))
:key #'jfield-name
:test #'string=)))
(jcall (jmethod "java.lang.reflect.Field" "setAccessible" "boolean")
field +true+)
(let ((get-jmethod (jmethod "java.lang.reflect.Field" "get" "java.lang.Object")))
(lambda (instance)
(jcall get-jmethod field instance)))))
(let ((accessor (make-private-jfield-accessor "com.sun.jna.Pointer" "peer")))
(defun %pointer-address (pointer)
(funcall accessor pointer)))
(defun pointer-address (pointer)
"Return the address pointed to by PTR."
(let ((peer (%pointer-address pointer)))
(if (< peer 0)
(+ #.(ash 1 64) peer)
peer)))
(defun pointer-eq (ptr1 ptr2)
"Return true if PTR1 and PTR2 point to the same address."
(= (%pointer-address ptr1) (%pointer-address ptr2)))
(defun null-pointer ()
"Construct and return a null pointer."
(make-pointer 0))
(defun null-pointer-p (ptr)
"Return true if PTR is a null pointer."
(zerop (%pointer-address ptr)))
(defun inc-pointer (ptr offset)
"Return a fresh pointer pointing OFFSET bytes past PTR."
(make-pointer (+ (%pointer-address ptr) offset)))
;;;# Allocation
(let ((malloc-jmethod (private-jmethod "com.sun.jna.Memory" "malloc")))
(defun %foreign-alloc (size)
"Allocate SIZE bytes on the heap and return a pointer."
(make-pointer
(jstatic-raw malloc-jmethod nil size))))
(let ((free-jmethod (private-jmethod "com.sun.jna.Memory" "free")))
(defun foreign-free (ptr)
"Free a PTR allocated by FOREIGN-ALLOC."
(jstatic-raw free-jmethod nil (%pointer-address ptr))
nil))
;;; TODO: stack allocation.
(defmacro with-foreign-pointer ((var size &optional size-var) &body body)
"Bind VAR to SIZE bytes of foreign memory during BODY. The pointer
in VAR is invalid beyond the dynamic extent of BODY, 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* ((,size-var ,size)
(,var (%foreign-alloc ,size-var)))
(unwind-protect
(progn ,@body)
(foreign-free ,var))))
;;;# 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.
(defun jna-setter (type)
(ecase type
((:char :unsigned-char) "setByte")
(:double "setDouble")
(:float "setFloat")
((:int :unsigned-int) "setInt")
((:long :unsigned-long) "setNativeLong")
((:long-long :unsigned-long-long) "setLong")
(:pointer "setPointer")
((:short :unsigned-short) "setShort")))
(defun jna-setter-arg-type (type)
(ecase type
((:char :unsigned-char) "byte")
(:double "double")
(:float "float")
((:int :unsigned-int) "int")
((:long :unsigned-long) "com.sun.jna.NativeLong")
((:long-long :unsigned-long-long) "long")
(:pointer "com.sun.jna.Pointer")
((:short :unsigned-short) "short")))
(defun jna-getter (type)
(ecase type
((:char :unsigned-char) "getByte")
(:double "getDouble")
(:float "getFloat")
((:int :unsigned-int) "getInt")
((:long :unsigned-long) "getNativeLong")
((:long-long :unsigned-long-long) "getLong")
(:pointer "getPointer")
((:short :unsigned-short) "getShort")))
(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)))
(let ((method (jmethod "com.sun.jna.Pointer"
(jna-setter :char) "long" (jna-setter-arg-type :char))))
(defun copy-to-foreign-vector (vector foreign-pointer)
(loop for i below (length vector)
do
(jcall-raw method
foreign-pointer i
(aref vector i)))))
;; hand-roll the jna-getter method instead of calling %mem-ref every time through
(let ((method (jmethod "com.sun.jna.Pointer" (jna-getter :char) "long")))
(defun copy-from-foreign-vector (vector foreign-pointer)
(loop for i below (length vector)
do (setf (aref vector i)
(java:jobject-lisp-value (jcall-raw method foreign-pointer i))))))
(defmacro with-pointer-to-vector-data ((ptr-var vector) &body body)
"Bind PTR-VAR to a foreign pointer to the data in VECTOR."
(let ((vector-sym (gensym "VECTOR")))
`(let ((,vector-sym ,vector))
(with-foreign-pointer (,ptr-var (length ,vector-sym))
(copy-to-foreign-vector ,vector-sym ,ptr-var)
(unwind-protect
(progn ,@body)
(copy-from-foreign-vector ,vector-sym ,ptr-var))))))
;;;# Dereferencing
(defun foreign-type-to-java-class (type)
(jclass
(ecase type
((:int :unsigned-int) "java.lang.Integer")
((:long :unsigned-long) "com.sun.jna.NativeLong")
((:long-long :unsigned-long-long) "java.lang.Long")
(:pointer "com.sun.jna.Pointer") ;; void * is pointer?
(:float "java.lang.Float")
(:double "java.lang.Double")
((:char :unsigned-char) "java.lang.Byte")
((:short :unsigned-short) "java.lang.Short"))))
(defun %foreign-type-size (type)
"Return the size in bytes of a foreign type."
(jstatic "getNativeSize" "com.sun.jna.Native"
(foreign-type-to-java-class type)))
;;; FIXME.
(defun %foreign-type-alignment (type)
"Return the alignment in bytes of a foreign type."
(%foreign-type-size type))
(defun unsigned-type-p (type)
(case type
((:unsigned-char
:unsigned-int
:unsigned-short
:unsigned-long
:unsigned-long-long) t)
(t nil)))
(defun lispify-value (value type)
(when (and (eq type :pointer) (or (null (java:jobject-lisp-value value))
(eq +null+ (java:jobject-lisp-value value))))
(return-from lispify-value (null-pointer)))
(when (or (eq type :long) (eq type :unsigned-long))
(setq value (jcall-raw (jmethod "com.sun.jna.NativeLong" "longValue")
(java:jobject-lisp-value value))))
(let ((bit-size (* 8 (%foreign-type-size type))))
(let ((lisp-value (java:jobject-lisp-value value)))
(if (and (unsigned-type-p type)
(logbitp (1- bit-size) lisp-value))
(lognot (logxor lisp-value (1- (expt 2 bit-size))))
lisp-value))))
(defun %mem-ref (ptr type &optional (offset 0))
(lispify-value
(jcall-raw (jmethod "com.sun.jna.Pointer" (jna-getter type) "long")
ptr offset)
type))
(defun %mem-set (value ptr type &optional (offset 0))
(let* ((bit-size (* 8 (%foreign-type-size type)))
(val (if (and (unsigned-type-p type) (logbitp (1- bit-size) value))
(lognot (logxor value (1- (expt 2 bit-size))))
value)))
(jcall-raw (jmethod "com.sun.jna.Pointer"
(jna-setter type) "long" (jna-setter-arg-type type))
ptr
offset
(if (or (eq type :long) (eq type :unsigned-long))
(jnew (jconstructor "com.sun.jna.NativeLong" "long") val)
val)))
value)
;;;# Foreign Globals
(let ((get-symbol-address-jmethod (private-jmethod "com.sun.jna.NativeLibrary" "getSymbolAddress")))
(defun %foreign-symbol-pointer (name library)
"Returns a pointer to a foreign symbol NAME."
(flet ((find-it (library)
(ignore-errors
(make-pointer
(jcall-raw get-symbol-address-jmethod library name)))))
(if (eq library :default)
(or (find-it
(jstatic "getProcess" "com.sun.jna.NativeLibrary"))
;; The above should find it, but I'm not exactly sure, so
;; let's still do it manually just in case.
(loop for lib being the hash-values of *loaded-libraries*
thereis (find-it lib)))
(find-it library)))))
;;;# Calling Foreign Functions
(defun find-foreign-function (name library)
(flet ((find-it (library)
(ignore-errors
(jcall-raw (jmethod "com.sun.jna.NativeLibrary" "getFunction"
"java.lang.String")
library name))))
(if (eq library :default)
(or (find-it
(jstatic "getProcess" "com.sun.jna.NativeLibrary"))
;; The above should find it, but I'm not exactly sure, so
;; let's still do it manually just in case.
(loop for lib being the hash-values of *loaded-libraries*
thereis (find-it lib)))
(find-it (gethash library *loaded-libraries*)))))
(defun convert-calling-convention (convention)
(ecase convention
(:stdcall "ALT_CONVENTION")
(:cdecl "C_CONVENTION")))
(defparameter *jna-string-encoding* "UTF-8"
"Encoding for conversion between Java and native strings that occurs within JNA.
Used with jna-4.0.0 or later.")
;;; c.f. <http://twall.github.io/jna/4.0/javadoc/com/sun/jna/Function.html#Function%28com.sun.jna.Pointer,%20int,%20java.lang.String%29>
(defvar *jna-4.0.0-or-later-p*
(ignore-errors (private-jconstructor "com.sun.jna.Function"
"com.sun.jna.Pointer" "int" "java.lang.String")))
(let ((jconstructor
(if *jna-4.0.0-or-later-p*
(private-jconstructor "com.sun.jna.Function"
"com.sun.jna.Pointer" "int" "java.lang.String")
(private-jconstructor "com.sun.jna.Function"
"com.sun.jna.Pointer" "int"))))
(defun make-function-pointer (pointer convention)
(apply
#'jnew jconstructor pointer
(jfield "com.sun.jna.Function" (convert-calling-convention convention))
(when *jna-4.0.0-or-later-p*
(list *jna-string-encoding*)))))
(defun lisp-value-to-java (value foreign-type)
(case foreign-type
(:pointer value)
(:void nil)
(t (jnew (ecase foreign-type
((:int :unsigned-int) (jconstructor "java.lang.Integer" "int"))
((:long-long :unsigned-long-long)
(jconstructor "java.lang.Long" "long"))
((:long :unsigned-long)
(jconstructor "com.sun.jna.NativeLong" "long"))
((:short :unsigned-short) (jconstructor "java.lang.Short" "short"))
((:char :unsigned-char) (jconstructor "java.lang.Byte" "byte"))
(:float (jconstructor "java.lang.Float" "float"))
(:double (jconstructor "java.lang.Double" "double")))
value))))
(defun %%foreign-funcall (function args arg-types return-type)
(let ((jargs (jnew-array "java.lang.Object" (length args))))
(loop for arg in args and type in arg-types and i from 0
do (setf (jarray-ref jargs i)
(lisp-value-to-java arg type)))
(if (eq return-type :void)
(progn
(jcall-raw (jmethod "com.sun.jna.Function" "invoke" "[Ljava.lang.Object;")
function jargs)
(values))
(lispify-value
(jcall-raw (jmethod "com.sun.jna.Function" "invoke"
"java.lang.Class" "[Ljava.lang.Object;")
function
(foreign-type-to-java-class return-type)
jargs)
return-type))))
(defun foreign-funcall-type-and-args (args)
(let ((return-type :void))
(loop for (type arg) on args by #'cddr
if arg collect type into types
and collect arg into fargs
else do (setf return-type type)
finally (return (values types fargs return-type)))))
(defmacro %foreign-funcall (name args &key library convention)
(declare (ignore convention))
(multiple-value-bind (types fargs rettype)
(foreign-funcall-type-and-args args)
`(%%foreign-funcall (find-foreign-function ',name ',library)
(list ,@fargs) ',types ',rettype)))
(defmacro %foreign-funcall-pointer (ptr args &key convention)
(multiple-value-bind (types fargs rettype)
(foreign-funcall-type-and-args args)
`(%%foreign-funcall (make-function-pointer ,ptr ',convention)
(list ,@fargs) ',types ',rettype)))
;;;# Callbacks
(defun foreign-to-callback-type (type)
(ecase type
((:int :unsigned-int)
:int)
((:long :unsigned-long)
(jvm::make-jvm-class-name "com.sun.jna.NativeLong"))
((:long-long :unsigned-long-long)
(jvm::make-jvm-class-name "java.lang.Long"))
(:pointer
(jvm::make-jvm-class-name "com.sun.jna.Pointer"))
(:float
:float)
(:double
:double)
((:char :unsigned-char)
:byte)
((:short :unsigned-short)
:short)
(:wchar_t
:int)
(:void
:void)))
(defvar *callbacks* (make-hash-table))
(defmacro convert-args-to-lisp-values (arg-names arg-types &body body)
(let ((gensym-args (loop for name in arg-names
collect (format-symbol t '#:callback-arg-~a- name))))
`(lambda (,@gensym-args)
(let ,(loop for arg in arg-names
for type in arg-types
for gensym-arg in gensym-args
collecting `(,arg (if (typep ,gensym-arg 'java:java-object)
(lispify-value ,gensym-arg ,type)
,gensym-arg)))
,@body))))
(defmacro %defcallback (name return-type arg-names arg-types body
&key convention)
(declare (ignore convention)) ;; I'm always up for ignoring convention, but this is probably wrong.
`(setf (gethash ',name *callbacks*)
(jinterface-implementation
(ensure-callback-interface ',return-type ',arg-types)
"callback"
(convert-args-to-lisp-values ,arg-names ,arg-types (lisp-value-to-java ,body ',return-type)))))
;; (lambda (,@arg-names) ,body))))
(jvm::define-class-name +callback-object+ "com.sun.jna.Callback")
(defconstant
+dynamic-callback-package+
"org/armedbear/jna/dynamic/callbacks"
"The slash-delimited Java package in which we create classes dynamically to specify callback interfaces.")
(defun ensure-callback-interface (returns args)
"Ensure that the jvm interface for the callback exists in the current JVM.
Returns the fully dot qualified name of the interface."
(let* ((jvm-returns (foreign-to-callback-type returns))
(jvm-args (mapcar #'foreign-to-callback-type args))
(interface-name (qualified-callback-interface-classname jvm-returns jvm-args)))
(handler-case
(jss:find-java-class interface-name)
(java-exception (e)
(when (jinstance-of-p (java:java-exception-cause e)
"java.lang.ClassNotFoundException")
(let ((interface-class-bytes (%define-jna-callback-interface jvm-returns jvm-args))
(simple-interface-name (callback-interface-classname jvm-returns jvm-args)))
(load-class interface-name interface-class-bytes)))))
interface-name))
(defun qualified-callback-interface-classname (returns args)
(format nil "~A.~A"
(substitute #\. #\/ +dynamic-callback-package+)
(callback-interface-classname returns args)))
(defun callback-interface-classname (returns args)
(flet ((stringify (thing)
(typecase thing
(jvm::jvm-class-name
(substitute #\_ #\/
(jvm::class-name-internal thing)))
(t (string thing)))))
(format nil "~A__~{~A~^__~}"
(stringify returns)
(mapcar #'stringify args))))
(defun %define-jna-callback-interface (returns args)
"Returns the Java byte[] array of a class representing a Java
interface descending form +CALLBACK-OBJECT+ which contains the
single function 'callback' which takes ARGS returning RETURNS.
The fully qualified dotted name of the generated class is returned as
the second value."
(let ((name (callback-interface-classname returns args)))
(values
(define-java-interface name +dynamic-callback-package+
`(("callback" ,returns ,args))
`(,+callback-object+))
(qualified-callback-interface-classname returns args))))
(defun define-java-interface (name package methods
&optional (superinterfaces nil))
"Returns the bytes of the Java class interface called NAME in PACKAGE with METHODS.
METHODS is a list of (NAME RETURN-TYPE (ARG-TYPES)) entries. NAME is
a string. The values of RETURN-TYPE and the list of ARG-TYPES for the
defined method follow the are either references to Java objects as
created by JVM::MAKE-JVM-CLASS-NAME, or keywords representing Java
primtive types as contained in JVM::MAP-PRIMITIVE-TYPE.
SUPERINTERFACES optionally contains a list of interfaces that this
interface extends specified as fully qualifed dotted Java names."
(let* ((class-name-string (format nil "~A/~A" package name))
(class-name (jvm::make-jvm-class-name class-name-string))
(class (jvm::make-class-interface-file class-name)))
(dolist (superinterface superinterfaces)
(jvm::class-add-superinterface
class
(if (typep superinterface 'jvm::jvm-class-name)
superinterface
(jvm::make-jvm-class-name superinterface))))
(dolist (method methods)
(let ((name (first method))
(returns (second method))
(args (third method)))
(jvm::class-add-method
class
(jvm::make-jvm-method name returns args
:flags '(:public :abstract)))))
(jvm::finalize-class-file class)
(let ((s (sys::%make-byte-array-output-stream)))
(jvm::write-class-file class s)
(sys::%get-output-stream-bytes s))))
(defun load-class (name bytes)
"Load the byte[] array BYTES as a Java class called NAME."
(#"loadClassFromByteArray" java::*classloader* name bytes))
;;; Test function: unused in CFFI
(defun write-class (class-bytes pathname)
"Write the Java byte[] array CLASS-BYTES to PATHNAME."
(with-open-file (stream pathname
:direction :output
:element-type '(signed-byte 8))
(dotimes (i (jarray-length class-bytes))
(write-byte (jarray-ref class-bytes i) stream))))
(defun %callback (name)
(or (#"getFunctionPointer" 'com.sun.jna.CallbackReference
(gethash name *callbacks*))
(error "Undefined callback: ~S" name)))
(defun native-namestring (pathname)
(namestring pathname))

View file

@ -0,0 +1,446 @@
;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
;;;
;;; cffi-allegro.lisp --- CFFI-SYS implementation for Allegro CL.
;;;
;;; Copyright (C) 2005-2009, Luis Oliveira <loliveira(@)common-lisp.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)
(:import-from #:alexandria #:if-let #:with-unique-names #:once-only)
(: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
#:defcfun-helper-forms
#:%defcallback
#:%callback))
(in-package #:cffi-sys)
;;;# Mis-features
#-64bit (pushnew 'no-long-long *features*)
(pushnew 'flat-namespace *features*)
;;;# Symbol Case
(defun canonicalize-symbol-name-case (name)
(declare (string name))
(if (eq excl:*current-case-mode* :case-sensitive-lower)
(string-downcase name)
(string-upcase name)))
;;;# Basic Pointer Operations
(deftype foreign-pointer ()
'ff:foreign-address)
(defun pointerp (ptr)
"Return true if PTR is a foreign pointer."
(ff:foreign-address-p ptr))
(defun pointer-eq (ptr1 ptr2)
"Return true if PTR1 and PTR2 point to the same address."
(eql ptr1 ptr2))
(defun null-pointer ()
"Return a null pointer."
0)
(defun null-pointer-p (ptr)
"Return true if PTR is a null pointer."
(zerop ptr))
(defun inc-pointer (ptr offset)
"Return a pointer pointing OFFSET bytes past PTR."
(+ ptr offset))
(defun make-pointer (address)
"Return a pointer pointing to ADDRESS."
(check-type address ff:foreign-address)
address)
(defun pointer-address (ptr)
"Return the address pointed to by PTR."
(check-type ptr ff:foreign-address)
ptr)
;;;# Allocation
;;;
;;; Functions and macros for allocating foreign memory on the stack
;;; and on the heap. The main CFFI package defines macros that wrap
;;; FOREIGN-ALLOC and FOREIGN-FREE in UNWIND-PROTECT for the common usage
;;; when the memory has dynamic extent.
(defun %foreign-alloc (size)
"Allocate SIZE bytes on the heap and return a pointer."
(ff:allocate-fobject :char :c size))
(defun foreign-free (ptr)
"Free a PTR allocated by FOREIGN-ALLOC."
(ff:free-fobject ptr))
(defmacro with-foreign-pointer ((var size &optional size-var) &body body)
"Bind VAR to SIZE bytes of foreign memory during BODY. The
pointer in VAR is invalid beyond the dynamic extent of BODY, 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")))
#+(version>= 8 1)
(when (and (constantp size) (<= (eval size) ff:*max-stack-fobject-bytes*))
(return-from with-foreign-pointer
`(let ((,size-var ,(eval size)))
(declare (ignorable ,size-var))
(ff:with-static-fobject (,var '(:array :char ,(eval size))
:allocation :foreign-static-gc)
;; (excl::stack-allocated-p var) => T
(let ((,var (ff:fslot-address ,var)))
,@body)))))
`(let* ((,size-var ,size)
(,var (ff:allocate-fobject :char :c ,size-var)))
(unwind-protect
(progn ,@body)
(ff:free-fobject ,var))))
;;;# 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.
(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)
:allocation :static-reclaimable))
(defmacro with-pointer-to-vector-data ((ptr-var vector) &body body)
"Bind PTR-VAR to a foreign pointer to the data in VECTOR."
;; An array allocated in static-reclamable is a non-simple array in
;; the normal Lisp allocation area, pointing to a simple array in
;; the static-reclaimable allocation area. Therefore we have to get
;; out the simple-array to find the pointer to the actual contents.
(with-unique-names (simple-vec)
`(excl:with-underlying-simple-vector (,vector ,simple-vec)
(let ((,ptr-var (ff:fslot-address-typed :unsigned-char :lisp
,simple-vec)))
,@body))))
;;;# Dereferencing
(defun convert-foreign-type (type-keyword)
"Convert a CFFI type keyword to an Allegro type."
(ecase type-keyword
(:char :char)
(:unsigned-char :unsigned-char)
(:short :short)
(:unsigned-short :unsigned-short)
(:int :int)
(:unsigned-int :unsigned-int)
(:long :long)
(:unsigned-long :unsigned-long)
(:long-long
#+64bit :nat
#-64bit (error "this platform does not support :long-long."))
(:unsigned-long-long
#+64bit :unsigned-nat
#-64bit (error "this platform does not support :unsigned-long-long"))
(:float :float)
(:double :double)
(:pointer :unsigned-nat)
(:void :void)))
(defun %mem-ref (ptr type &optional (offset 0))
"Dereference an object of TYPE at OFFSET bytes from PTR."
(unless (zerop offset)
(setf ptr (inc-pointer ptr offset)))
(ff:fslot-value-typed (convert-foreign-type type) :c ptr))
;;; Compiler macro to open-code the call to FSLOT-VALUE-TYPED when the
;;; CFFI type is constant. Allegro does its own transformation on the
;;; call that results in efficient code.
(define-compiler-macro %mem-ref (&whole form ptr type &optional (off 0))
(if (constantp type)
(let ((ptr-form (if (eql off 0) ptr `(+ ,ptr ,off))))
`(ff:fslot-value-typed ',(convert-foreign-type (eval type))
:c ,ptr-form))
form))
(defun %mem-set (value ptr type &optional (offset 0))
"Set the object of TYPE at OFFSET bytes from PTR."
(unless (zerop offset)
(setf ptr (inc-pointer ptr offset)))
(setf (ff:fslot-value-typed (convert-foreign-type type) :c ptr) value))
;;; Compiler macro to open-code the call to (SETF FSLOT-VALUE-TYPED)
;;; when the CFFI type is constant. Allegro does its own
;;; transformation on the call that results in efficient code.
(define-compiler-macro %mem-set (&whole form val ptr type &optional (off 0))
(if (constantp type)
(once-only (val)
(let ((ptr-form (if (eql off 0) ptr `(+ ,ptr ,off))))
`(setf (ff:fslot-value-typed ',(convert-foreign-type (eval type))
:c ,ptr-form) ,val)))
form))
;;;# Calling Foreign Functions
(defun %foreign-type-size (type-keyword)
"Return the size in bytes of a foreign type."
(ff:sizeof-fobject (convert-foreign-type type-keyword)))
(defun %foreign-type-alignment (type-keyword)
"Returns the alignment in bytes of a foreign type."
#+(and powerpc macosx32)
(when (eq type-keyword :double)
(return-from %foreign-type-alignment 8))
;; No override necessary for the remaining types....
(ff::sized-ftype-prim-align
(ff::iforeign-type-sftype
(ff:get-foreign-type
(convert-foreign-type type-keyword)))))
(defun foreign-funcall-type-and-args (args)
"Returns a list of types, list of args and return type."
(let ((return-type :void))
(loop for (type arg) on args by #'cddr
if arg collect type into types
and collect arg into fargs
else do (setf return-type type)
finally (return (values types fargs return-type)))))
(defun convert-to-lisp-type (type)
(ecase type
((:char :short :int :long :nat)
`(signed-byte ,(* 8 (ff:sizeof-fobject type))))
((:unsigned-char :unsigned-short :unsigned-int :unsigned-long :unsigned-nat)
`(unsigned-byte ,(* 8 (ff:sizeof-fobject type))))
(:float 'single-float)
(:double 'double-float)
(:void 'null)))
(defun allegro-type-pair (cffi-type)
;; the :FOREIGN-ADDRESS pseudo-type accepts both pointers and
;; arrays. We need the latter for shareable byte vector support.
(if (eq cffi-type :pointer)
(list :foreign-address)
(let ((ftype (convert-foreign-type cffi-type)))
(list ftype (convert-to-lisp-type ftype)))))
#+ignore
(defun note-named-foreign-function (symbol name types rettype)
"Give Allegro's compiler a hint to perform a direct call."
`(eval-when (:compile-toplevel :load-toplevel :execute)
(setf (get ',symbol 'system::direct-ff-call)
(list '(,name :language :c)
t ; callback
:c ; convention
;; return type '(:c-type lisp-type)
',(allegro-type-pair rettype)
;; arg types '({(:c-type lisp-type)}*)
'(,@(mapcar #'allegro-type-pair types))
nil ; arg-checking
ff::ep-flag-never-release))))
(defmacro %foreign-funcall (name args &key convention library)
(declare (ignore convention library))
(multiple-value-bind (types fargs rettype)
(foreign-funcall-type-and-args args)
`(system::ff-funcall
(load-time-value (excl::determine-foreign-address
'(,name :language :c)
#-(version>= 8 1) ff::ep-flag-never-release
#+(version>= 8 1) ff::ep-flag-always-release
nil ; method-index
))
;; arg types {'(:c-type lisp-type) argN}*
,@(mapcan (lambda (type arg)
`(',(allegro-type-pair type) ,arg))
types fargs)
;; return type '(:c-type lisp-type)
',(allegro-type-pair rettype))))
(defun defcfun-helper-forms (name lisp-name rettype args types options)
"Return 2 values for DEFCFUN. A prelude form and a caller form."
(declare (ignore options))
(let ((ff-name (intern (format nil "%cffi-foreign-function/~A" lisp-name))))
(values
`(ff:def-foreign-call (,ff-name ,name)
,(loop for type in types
collect (list* (gensym) (allegro-type-pair type)))
:returning ,(allegro-type-pair rettype)
;; Don't use call-direct when there are no arguments.
,@(unless (null args) '(:call-direct t))
:arg-checking nil
:strings-convert nil
#+(version>= 8 1) ,@'(:release-heap :when-ok
:release-heap-ignorable t)
#+smp ,@'(:release-heap-implies-allow-gc t))
`(,ff-name ,@args))))
;;; See doc/allegro-internals.txt for a clue about entry-vec.
(defmacro %foreign-funcall-pointer (ptr args &key convention)
(declare (ignore convention))
(multiple-value-bind (types fargs rettype)
(foreign-funcall-type-and-args args)
(with-unique-names (entry-vec)
`(let ((,entry-vec (excl::make-entry-vec-boa)))
(setf (aref ,entry-vec 1) ,ptr) ; set jump address
(system::ff-funcall
,entry-vec
;; arg types {'(:c-type lisp-type) argN}*
,@(mapcan (lambda (type arg)
`(',(allegro-type-pair type) ,arg))
types fargs)
;; return type '(:c-type lisp-type)
',(allegro-type-pair rettype))))))
;;;# Callbacks
;;; The *CALLBACKS* hash table contains information about a callback
;;; for the Allegro FFI. The key is the name of the CFFI callback,
;;; and the value is a cons, the car containing the symbol the
;;; callback was defined on in the CFFI-CALLBACKS package, the cdr
;;; being an Allegro FFI pointer (a fixnum) that can be passed to C
;;; functions.
;;;
;;; These pointers must be restored when a saved Lisp image is loaded.
;;; The RESTORE-CALLBACKS function is added to *RESTART-ACTIONS* to
;;; re-register the callbacks during Lisp startup.
(defvar *callbacks* (make-hash-table))
;;; Register a callback in the *CALLBACKS* hash table.
(defun register-callback (cffi-name callback-name)
(setf (gethash cffi-name *callbacks*)
(cons callback-name (ff:register-foreign-callable
callback-name :reuse t))))
;;; Restore the saved pointers in *CALLBACKS* when loading an image.
(defun restore-callbacks ()
(maphash (lambda (key value)
(register-callback key (car value)))
*callbacks*))
;;; Arrange for RESTORE-CALLBACKS to run when a saved image containing
;;; CFFI is restarted.
(eval-when (:load-toplevel :execute)
(pushnew 'restore-callbacks excl:*restart-actions*))
;;; Create a package to contain the symbols for callback functions.
(defpackage #:cffi-callbacks
(:use))
(defun intern-callback (name)
(intern (format nil "~A::~A"
(if-let (package (symbol-package name))
(package-name package)
"#")
(symbol-name name))
'#:cffi-callbacks))
(defun convert-calling-convention (convention)
(ecase convention
(:cdecl :c)
(:stdcall :stdcall)))
(defmacro %defcallback (name rettype arg-names arg-types body
&key convention)
(declare (ignore rettype))
(let ((cb-name (intern-callback name)))
`(progn
(ff:defun-foreign-callable ,cb-name
,(mapcar (lambda (sym type) (list sym (convert-foreign-type type)))
arg-names arg-types)
(declare (:convention ,(convert-calling-convention convention)))
,body)
(register-callback ',name ',cb-name))))
;;; Return the saved Lisp callback pointer from *CALLBACKS* for the
;;; CFFI callback named NAME.
(defun %callback (name)
(or (cdr (gethash name *callbacks*))
(error "Undefined callback: ~S" name)))
;;;# Loading and Closing Foreign Libraries
(defun %load-foreign-library (name path)
"Load a foreign library."
;; ACL 8.0 honors the :FOREIGN option and always tries to foreign load
;; the argument. However, previous versions do not and will only
;; foreign load the argument if its type is a member of the
;; EXCL::*LOAD-FOREIGN-TYPES* list. Therefore, we bind that special
;; to a list containing whatever type NAME has.
(declare (ignore name))
(let ((excl::*load-foreign-types*
(list (pathname-type (parse-namestring path)))))
(handler-case
(progn
#+(version>= 7) (load path :foreign t)
#-(version>= 7) (load path))
(file-error (fe)
(error (change-class fe 'simple-error))))
path))
(defun %close-foreign-library (name)
"Close the foreign library NAME."
(ff:unload-foreign-library name))
(defun native-namestring (pathname)
(namestring pathname))
;;;# Foreign Globals
(defun convert-external-name (name)
"Add an underscore to NAME if necessary for the ABI."
#+macosx (concatenate 'string "_" name)
#-macosx name)
(defun %foreign-symbol-pointer (name library)
"Returns a pointer to a foreign symbol NAME."
(declare (ignore library))
(prog1 (ff:get-entry-point (convert-external-name name))))

View file

@ -0,0 +1,201 @@
;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
;;;
;;; cffi-clasp.lisp --- CFFI-SYS implementation for Clasp.
;;;
;;; Copyright (C) 2017 Frank Goenninger <frank.goenninger@goenninger.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
#:%foreign-alloc
#:foreign-free
#:with-foreign-pointer
#:null-pointer
#:null-pointer-p
#:inc-pointer
#:make-pointer
#:pointer-address
#:%mem-ref
#:%mem-set
#:%foreign-funcall
#:%foreign-funcall-pointer
#:%foreign-type-alignment
#:%foreign-type-size
#:%load-foreign-library
#:%close-foreign-library
#:native-namestring
#:make-shareable-byte-vector
#:with-pointer-to-vector-data
#:%defcallback
#:%callback
#:%foreign-symbol-pointer))
(in-package #:cffi-sys)
;;;# Mis-features
(pushnew 'flat-namespace cl:*features*)
;;;# Symbol Case
(defun canonicalize-symbol-name-case (name)
(declare (string name))
(string-upcase name))
;;;# Allocation
(defun %foreign-alloc (size)
"Allocate SIZE bytes of foreign-addressable memory."
(clasp-ffi:%foreign-alloc size))
(defun foreign-free (ptr)
"Free a pointer PTR allocated by FOREIGN-ALLOC."
(clasp-ffi:%foreign-free ptr))
(defmacro with-foreign-pointer ((var size &optional size-var) &body body)
"Bind VAR to SIZE bytes of foreign memory during BODY. The
pointer in VAR is invalid beyond the dynamic extent of BODY, 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* ((,size-var ,size)
(,var (%foreign-alloc ,size-var)))
(unwind-protect
(progn ,@body)
(foreign-free ,var))))
;;;# Misc. Pointer Operations
(deftype foreign-pointer ()
'clasp-ffi:foreign-data)
(defun null-pointer-p (ptr)
"Test if PTR is a null pointer."
(clasp-ffi:%null-pointer-p ptr))
(defun null-pointer ()
"Construct and return a null pointer."
(clasp-ffi:%make-nullpointer))
(defun make-pointer (address)
"Return a pointer pointing to ADDRESS."
(clasp-ffi:%make-pointer address))
(defun inc-pointer (ptr offset)
"Return a pointer OFFSET bytes past PTR."
(clasp-ffi:%inc-pointer ptr offset))
(defun pointer-address (ptr)
"Return the address pointed to by PTR."
(clasp-ffi:%foreign-data-address ptr))
(defun pointerp (ptr)
"Return true if PTR is a foreign pointer."
(typep ptr 'clasp-ffi:foreign-data))
(defun pointer-eq (ptr1 ptr2)
"Return true if PTR1 and PTR2 point to the same address."
(check-type ptr1 clasp-ffi:foreign-data)
(check-type ptr2 clasp-ffi:foreign-data)
(eql (pointer-address ptr1) (pointer-address ptr2)))
;;;# 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.
(defun make-shareable-byte-vector (size)
"Create a Lisp vector of SIZE bytes that can passed to
WITH-POINTER-TO-VECTOR-DATA."
(make-array size :element-type '(unsigned-byte 8)))
;; frgo, 2016-07-02: TODO: Implemenent!
;; (defmacro with-pointer-to-vector-data ((ptr-var vector) &body body)
;; "Bind PTR-VAR to a foreign pointer to the data in VECTOR."
;; `(let ((,ptr-var (si:make-foreign-data-from-array ,vector)))
;; ,@body))
(defun %foreign-type-size (type-keyword)
"Return the size in bytes of a foreign type."
(clasp-ffi:%foreign-type-size type-keyword))
(defun %foreign-type-alignment (type-keyword)
"Return the alignment in bytes of a foreign type."
(clasp-ffi:%foreign-type-alignment type-keyword))
;;;# Dereferencing
(defun %mem-ref (ptr type &optional (offset 0))
"Dereference an object of TYPE at OFFSET bytes from PTR."
(clasp-ffi:%mem-ref ptr type offset))
(defun %mem-set (value ptr type &optional (offset 0))
"Set an object of TYPE at OFFSET bytes from PTR."
(clasp-ffi:%mem-set ptr type value offset))
(defmacro %foreign-funcall (name args &key library convention)
"Call a foreign function."
(declare (ignore library convention))
`(clasp-ffi:%foreign-funcall ,name ,@args))
(defmacro %foreign-funcall-pointer (ptr args &key convention)
"Funcall a pointer to a foreign function."
(declare (ignore convention))
`(clasp-ffi:%foreign-funcall-pointer ,ptr ,@args))
;;;# Foreign Libraries
(defun %load-foreign-library (name path)
"Load a foreign library."
(clasp-ffi:%load-foreign-library name path))
(defun %close-foreign-library (handle)
"Close a foreign library."
(clasp-ffi:%close-foreign-library handle))
(defun %foreign-symbol-pointer (name library)
"Returns a pointer to a foreign symbol NAME."
(clasp-ffi:%foreign-symbol-pointer name library))
(defun native-namestring (pathname)
(namestring pathname))
;;;# Callbacks
(defmacro %defcallback (name rettype arg-names arg-types body
&key convention)
`(clasp-ffi:%defcallback (,name ,@(when convention `(:convention ,convention)))
,rettype ,arg-names ,arg-types ,body))
(defun %callback (name)
(clasp-ffi:%get-callback name))

View file

@ -0,0 +1,432 @@
;;;; -*- 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)))))

View file

@ -0,0 +1,384 @@
;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
;;;
;;; cffi-cmucl.lisp --- CFFI-SYS implementation for CMU CL.
;;;
;;; Copyright (C) 2005-2006, James Bielman <jamesjb@jamesjb.com>
;;;
;;; 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 #:alien #:c-call)
(:import-from #:alexandria #:once-only #:with-unique-names #:if-let)
(: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)
;;;# Misfeatures
(pushnew 'flat-namespace *features*)
;;;# Symbol Case
(defun canonicalize-symbol-name-case (name)
(declare (string name))
(string-upcase name))
;;;# Basic Pointer Operations
(deftype foreign-pointer ()
'sys:system-area-pointer)
(declaim (inline pointerp))
(defun pointerp (ptr)
"Return true if PTR is a foreign pointer."
(sys:system-area-pointer-p ptr))
(declaim (inline pointer-eq))
(defun pointer-eq (ptr1 ptr2)
"Return true if PTR1 and PTR2 point to the same address."
(sys:sap= ptr1 ptr2))
(declaim (inline null-pointer))
(defun null-pointer ()
"Construct and return a null pointer."
(sys:int-sap 0))
(declaim (inline null-pointer-p))
(defun null-pointer-p (ptr)
"Return true if PTR is a null pointer."
(zerop (sys:sap-int ptr)))
(declaim (inline inc-pointer))
(defun inc-pointer (ptr offset)
"Return a pointer pointing OFFSET bytes past PTR."
(sys:sap+ ptr offset))
(declaim (inline make-pointer))
(defun make-pointer (address)
"Return a pointer pointing to ADDRESS."
(sys:int-sap address))
(declaim (inline pointer-address))
(defun pointer-address (ptr)
"Return the address pointed to by PTR."
(sys:sap-int ptr))
(defmacro with-foreign-pointer ((var size &optional size-var) &body body)
"Bind VAR to SIZE bytes of foreign memory during BODY. The
pointer in VAR is invalid beyond the dynamic extent of BODY, 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")))
;; If the size is constant we can stack-allocate.
(if (constantp size)
(let ((alien-var (gensym "ALIEN")))
`(with-alien ((,alien-var (array (unsigned 8) ,(eval size))))
(let ((,size-var ,(eval size))
(,var (alien-sap ,alien-var)))
(declare (ignorable ,size-var))
,@body)))
`(let* ((,size-var ,size)
(,var (%foreign-alloc ,size-var)))
(unwind-protect
(progn ,@body)
(foreign-free ,var)))))
;;;# Allocation
;;;
;;; Functions and macros for allocating foreign memory on the stack
;;; and on the heap. The main CFFI package defines macros that wrap
;;; FOREIGN-ALLOC and FOREIGN-FREE in UNWIND-PROTECT for the common usage
;;; when the memory has dynamic extent.
(defun %foreign-alloc (size)
"Allocate SIZE bytes on the heap and return a pointer."
(declare (type (unsigned-byte 32) size))
(alien-funcall
(extern-alien
"malloc"
(function system-area-pointer unsigned))
size))
(defun foreign-free (ptr)
"Free a PTR allocated by FOREIGN-ALLOC."
(declare (type system-area-pointer ptr))
(alien-funcall
(extern-alien
"free"
(function (values) system-area-pointer))
ptr))
;;;# 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.
(defun make-shareable-byte-vector (size)
"Create a Lisp vector of SIZE bytes that can passed to
WITH-POINTER-TO-VECTOR-DATA."
(make-array size :element-type '(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."
`(sys:without-gcing
(let ((,ptr-var (sys:vector-sap ,vector)))
,@body)))
;;;# Dereferencing
;;; Define the %MEM-REF and %MEM-SET functions, as well as compiler
;;; macros that optimize the case where the type keyword is constant
;;; at compile-time.
(defmacro define-mem-accessors (&body pairs)
`(progn
(defun %mem-ref (ptr type &optional (offset 0))
(ecase type
,@(loop for (keyword fn) in pairs
collect `(,keyword (,fn ptr offset)))))
(defun %mem-set (value ptr type &optional (offset 0))
(ecase type
,@(loop for (keyword fn) in pairs
collect `(,keyword (setf (,fn ptr offset) value)))))
(define-compiler-macro %mem-ref
(&whole form ptr type &optional (offset 0))
(if (constantp type)
(ecase (eval type)
,@(loop for (keyword fn) in pairs
collect `(,keyword `(,',fn ,ptr ,offset))))
form))
(define-compiler-macro %mem-set
(&whole form value ptr type &optional (offset 0))
(if (constantp type)
(once-only (value)
(ecase (eval type)
,@(loop for (keyword fn) in pairs
collect `(,keyword `(setf (,',fn ,ptr ,offset)
,value)))))
form))))
(define-mem-accessors
(:char sys:signed-sap-ref-8)
(:unsigned-char sys:sap-ref-8)
(:short sys:signed-sap-ref-16)
(:unsigned-short sys:sap-ref-16)
(:int sys:signed-sap-ref-32)
(:unsigned-int sys:sap-ref-32)
(:long sys:signed-sap-ref-32)
(:unsigned-long sys:sap-ref-32)
(:long-long sys:signed-sap-ref-64)
(:unsigned-long-long sys:sap-ref-64)
(:float sys:sap-ref-single)
(:double sys:sap-ref-double)
(:pointer sys:sap-ref-sap))
;;;# Calling Foreign Functions
(defun convert-foreign-type (type-keyword)
"Convert a CFFI type keyword to an ALIEN type."
(ecase type-keyword
(:char 'char)
(:unsigned-char 'unsigned-char)
(:short 'short)
(:unsigned-short 'unsigned-short)
(:int 'int)
(:unsigned-int 'unsigned-int)
(:long 'long)
(:unsigned-long 'unsigned-long)
(:long-long '(signed 64))
(:unsigned-long-long '(unsigned 64))
(:float 'single-float)
(:double 'double-float)
(:pointer 'system-area-pointer)
(:void 'void)))
(defun %foreign-type-size (type-keyword)
"Return the size in bytes of a foreign type."
(/ (alien-internals:alien-type-bits
(alien-internals:parse-alien-type
(convert-foreign-type type-keyword))) 8))
(defun %foreign-type-alignment (type-keyword)
"Return the alignment in bytes of a foreign type."
(/ (alien-internals:alien-type-alignment
(alien-internals:parse-alien-type
(convert-foreign-type type-keyword))) 8))
(defun foreign-funcall-type-and-args (args)
"Return an ALIEN function type for ARGS."
(let ((return-type nil))
(loop for (type arg) on args by #'cddr
if arg collect (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)))))
(defmacro %%foreign-funcall (name types fargs rettype)
"Internal guts of %FOREIGN-FUNCALL."
`(alien-funcall
(extern-alien ,name (function ,rettype ,@types))
,@fargs))
(defmacro %foreign-funcall (name args &key library convention)
"Perform a foreign function call, document it more later."
(declare (ignore library convention))
(multiple-value-bind (types fargs rettype)
(foreign-funcall-type-and-args args)
`(%%foreign-funcall ,name ,types ,fargs ,rettype)))
(defmacro %foreign-funcall-pointer (ptr args &key convention)
"Funcall a pointer to a foreign function."
(declare (ignore convention))
(multiple-value-bind (types fargs rettype)
(foreign-funcall-type-and-args args)
(with-unique-names (function)
`(with-alien ((,function (* (function ,rettype ,@types)) ,ptr))
(alien-funcall ,function ,@fargs)))))
;;;# Callbacks
(defvar *callbacks* (make-hash-table))
;;; Create a package to contain the symbols for callback functions. We
;;; want to redefine callbacks with the same symbol so the internal data
;;; structures are reused.
(defpackage #:cffi-callbacks
(:use))
;;; Intern a symbol in the CFFI-CALLBACKS package used to name the internal
;;; callback for NAME.
(eval-when (:compile-toplevel :load-toplevel :execute)
(defun intern-callback (name)
(intern (format nil "~A::~A"
(if-let (package (symbol-package name))
(package-name package)
name)
(symbol-name name))
'#:cffi-callbacks)))
(defmacro %defcallback (name rettype arg-names arg-types body
&key convention)
(declare (ignore convention))
(let ((cb-name (intern-callback name)))
`(progn
(def-callback ,cb-name
(,(convert-foreign-type rettype)
,@(mapcar (lambda (sym type)
(list sym (convert-foreign-type type)))
arg-names arg-types))
,body)
(setf (gethash ',name *callbacks*) (callback ,cb-name)))))
(defun %callback (name)
(multiple-value-bind (pointer winp)
(gethash name *callbacks*)
(unless winp
(error "Undefined callback: ~S" name))
pointer))
;;; CMUCL makes new callback trampolines when it reloads, so we need
;;; to update CFFI's copies.
(defun reset-callbacks ()
(loop for k being the hash-keys of *callbacks*
do (setf (gethash k *callbacks*)
(alien::symbol-trampoline (intern-callback k)))))
;; Needs to be after cmucl's restore-callbacks, so put at the end...
(unless (member 'reset-callbacks ext:*after-save-initializations*)
(setf ext:*after-save-initializations*
(append ext:*after-save-initializations* (list 'reset-callbacks))))
;;;# Loading and Closing Foreign Libraries
;;; Work-around for compiling ffi code without loading the
;;; respective library at compile-time.
(setf c::top-level-lambda-max 0)
(defun %load-foreign-library (name path)
"Load the foreign library NAME."
;; On some platforms SYS::LOAD-OBJECT-FILE signals an error when
;; loading fails, but on others (Linux for instance) it returns
;; two values: NIL and an error string.
(declare (ignore name))
(multiple-value-bind (ret message)
(sys::load-object-file path)
(cond
;; Loading failed.
((stringp message) (error "~A" message))
;; The library was already loaded.
((null ret) (cdr (rassoc path sys::*global-table* :test #'string=)))
;; The library has been loaded, but since SYS::LOAD-OBJECT-FILE
;; returns an alist of *all* loaded libraries along with their addresses
;; we return only the handler associated with the library just loaded.
(t (cdr (rassoc path ret :test #'string=))))))
;;; XXX: doesn't work on Darwin; does not check for errors. I suppose we'd
;;; want something like SBCL's dlclose-or-lose in foreign-load.lisp:66
(defun %close-foreign-library (handler)
"Closes a foreign library."
(let ((lib (rassoc (ext:unix-namestring handler) sys::*global-table*
:test #'string=)))
(sys::dlclose (car lib))
(setf (car lib) (sys:int-sap 0))))
(defun native-namestring (pathname)
(ext:unix-namestring pathname nil))
;;;# Foreign Globals
(defun %foreign-symbol-pointer (name library)
"Returns a pointer to a foreign symbol NAME."
(declare (ignore library))
(let ((address (sys:alternate-get-global-address
(vm:extern-alien-name name))))
(if (zerop address)
nil
(sys:int-sap address))))

View file

@ -0,0 +1,331 @@
;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
;;;
;;; cffi-corman.lisp --- CFFI-SYS implementation for Corman Lisp.
;;;
;;; Copyright (C) 2005-2008, Luis Oliveira <loliveira(@)common-lisp.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.
;;;
;;; This port is suffering from bitrot as of 2007-03-29. Corman Lisp
;;; is too funky with ASDF, crashes easily, makes it very painful to
;;; do any testing. -- luis
;;;# Administrivia
(defpackage #:cffi-sys
(:use #:common-lisp #:c-types)
(:import-from #:alexandria #:with-unique-names)
(: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-type-alignment
#:%foreign-type-size
#:%load-foreign-library
#:native-namestring
#:%mem-ref
#:%mem-set
;#:make-shareable-byte-vector
;#:with-pointer-to-vector-data
#:foreign-symbol-pointer
#:defcfun-helper-forms
#:%defcallback
#:%callback))
(in-package #:cffi-sys)
;;;# Misfeatures
(pushnew 'no-long-long *features*)
(pushnew 'no-foreign-funcall *features*)
;;;$ Symbol Case
(defun canonicalize-symbol-name-case (name)
(declare (string name))
(string-upcase name))
;;;# Basic Pointer Operations
(deftype foreign-pointer ()
'cl::foreign)
(defun pointerp (ptr)
"Return true if PTR is a foreign pointer."
(cpointerp ptr))
(defun pointer-eq (ptr1 ptr2)
"Return true if PTR1 and PTR2 point to the same address."
(cpointer= ptr1 ptr2))
(defun null-pointer ()
"Return a null pointer."
(create-foreign-ptr))
(defun null-pointer-p (ptr)
"Return true if PTR is a null pointer."
(cpointer-null ptr))
(defun inc-pointer (ptr offset)
"Return a pointer pointing OFFSET bytes past PTR."
(let ((new-ptr (create-foreign-ptr)))
(setf (cpointer-value new-ptr)
(+ (cpointer-value ptr) offset))
new-ptr))
(defun make-pointer (address)
"Return a pointer pointing to ADDRESS."
(int-to-foreign-ptr address))
(defun pointer-address (ptr)
"Return the address pointed to by PTR."
(foreign-ptr-to-int ptr))
;;;# Allocation
;;;
;;; Functions and macros for allocating foreign memory on the stack
;;; and on the heap. The main CFFI package defines macros that wrap
;;; FOREIGN-ALLOC and FOREIGN-FREE in UNWIND-PROTECT for the common usage
;;; when the memory has dynamic extent.
(defun %foreign-alloc (size)
"Allocate SIZE bytes on the heap and return a pointer."
(malloc size))
(defun foreign-free (ptr)
"Free a PTR allocated by FOREIGN-ALLOC."
(free ptr))
(defmacro with-foreign-pointer ((var size &optional size-var) &body body)
"Bind VAR to SIZE bytes of foreign memory during BODY. The
pointer in VAR is invalid beyond the dynamic extent of BODY, 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* ((,size-var ,size)
(,var (malloc ,size-var)))
(unwind-protect
(progn ,@body)
(free ,var))))
;;;# 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.
;(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)))
;
;(defmacro with-pointer-to-vector-data ((ptr-var vector) &body body)
; "Bind PTR-VAR to a foreign pointer to the data in VECTOR."
; `(sb-sys:without-gcing
; (let ((,ptr-var (sb-sys:vector-sap ,vector)))
; ,@body)))
;;;# Dereferencing
;;; According to the docs, Corman's C Function Definition Parser
;;; converts int to long, so we'll assume that.
(defun convert-foreign-type (type-keyword)
"Convert a CFFI type keyword to a CormanCL type."
(ecase type-keyword
(:char :char)
(:unsigned-char :unsigned-char)
(:short :short)
(:unsigned-short :unsigned-short)
(:int :long)
(:unsigned-int :unsigned-long)
(:long :long)
(:unsigned-long :unsigned-long)
(:float :single-float)
(:double :double-float)
(:pointer :handle)
(:void :void)))
(defun %mem-ref (ptr type &optional (offset 0))
"Dereference an object of TYPE at OFFSET bytes from PTR."
(unless (eql offset 0)
(setq ptr (inc-pointer ptr offset)))
(ecase type
(:char (cref (:char *) ptr 0))
(:unsigned-char (cref (:unsigned-char *) ptr 0))
(:short (cref (:short *) ptr 0))
(:unsigned-short (cref (:unsigned-short *) ptr 0))
(:int (cref (:long *) ptr 0))
(:unsigned-int (cref (:unsigned-long *) ptr 0))
(:long (cref (:long *) ptr 0))
(:unsigned-long (cref (:unsigned-long *) ptr 0))
(:float (cref (:single-float *) ptr 0))
(:double (cref (:double-float *) ptr 0))
(:pointer (cref (:handle *) ptr 0))))
;(define-compiler-macro %mem-ref (&whole form ptr type &optional (offset 0))
; (if (constantp type)
; `(cref (,(convert-foreign-type type) *) ,ptr ,offset)
; form))
(defun %mem-set (value ptr type &optional (offset 0))
"Set the object of TYPE at OFFSET bytes from PTR."
(unless (eql offset 0)
(setq ptr (inc-pointer ptr offset)))
(ecase type
(:char (setf (cref (:char *) ptr 0) value))
(:unsigned-char (setf (cref (:unsigned-char *) ptr 0) value))
(:short (setf (cref (:short *) ptr 0) value))
(:unsigned-short (setf (cref (:unsigned-short *) ptr 0) value))
(:int (setf (cref (:long *) ptr 0) value))
(:unsigned-int (setf (cref (:unsigned-long *) ptr 0) value))
(:long (setf (cref (:long *) ptr 0) value))
(:unsigned-long (setf (cref (:unsigned-long *) ptr 0) value))
(:float (setf (cref (:single-float *) ptr 0) value))
(:double (setf (cref (:double-float *) ptr 0) value))
(:pointer (setf (cref (:handle *) ptr 0) value))))
;;;# Calling Foreign Functions
(defun %foreign-type-size (type-keyword)
"Return the size in bytes of a foreign type."
(sizeof (convert-foreign-type type-keyword)))
;;; Couldn't find anything in sys/ffi.lisp and the C declaration parser
;;; doesn't seem to care about alignment so we'll assume that it's the
;;; same as its size.
(defun %foreign-type-alignment (type-keyword)
(sizeof (convert-foreign-type type-keyword)))
(defun find-dll-containing-function (name)
"Searches for NAME in the loaded DLLs. If found, returns
the DLL's name (a string), else returns NIL."
(dolist (dll ct::*dlls-loaded*)
(when (ignore-errors
(ct::get-dll-proc-address name (ct::dll-record-handle dll)))
(return (ct::dll-record-name dll)))))
;;; This won't work at all...
#||
(defmacro %foreign-funcall (name &rest args)
(let ((sym (gensym)))
`(let (,sym)
(ct::install-dll-function ,(find-dll-containing-function name)
,name ,sym)
(funcall ,sym ,@(loop for (type arg) on args by #'cddr
if arg collect arg)))))
||#
;;; It *might* be possible to implement by copying most of the code
;;; from Corman's DEFUN-DLL. Alternatively, it could implemented the
;;; same way as Lispworks' foreign-funcall. In practice, nobody uses
;;; Corman with CFFI, apparently. :)
(defmacro %foreign-funcall (name &rest args)
"Call a foreign function NAME passing arguments ARGS."
`(format t "~&;; Calling ~A with args ~S.~%" ,name ',args))
(defun defcfun-helper-forms (name lisp-name rettype args types)
"Return 2 values for DEFCFUN. A prelude form and a caller form."
(let ((ff-name (intern (format nil "%cffi-foreign-function/~A" lisp-name)))
;; XXX This will only work if the dll is already loaded, fix this.
(dll (find-dll-containing-function name)))
(values
`(defun-dll ,ff-name
,(mapcar (lambda (type)
(list (gensym) (convert-foreign-type type)))
types)
:return-type ,(convert-foreign-type rettype)
:library-name ,dll
:entry-name ,name
;; we want also :pascal linkage type to access
;; the win32 api for instance..
:linkage-type :c)
`(,ff-name ,@args))))
;;;# Callbacks
;;; defun-c-callback vs. defun-direct-c-callback?
;;; same issue as Allegro, no return type declaration, should we coerce?
(defmacro %defcallback (name rettype arg-names arg-types body-form)
(declare (ignore rettype))
(with-unique-names (cb-sym)
`(progn
(defun-c-callback ,cb-sym
,(mapcar (lambda (sym type) (list sym (convert-foreign-type type)))
arg-names arg-types)
,body-form)
(setf (get ',name 'callback-ptr)
(get-callback-procinst ',cb-sym)))))
;;; Just continue to use the plist for now even though this really
;;; should use a *CALLBACKS* hash table and not define the callbacks
;;; as gensyms. Someone with access to Corman should update this.
(defun %callback (name)
(get name 'callback-ptr))
;;;# Loading Foreign Libraries
(defun %load-foreign-library (name)
"Load the foreign library NAME."
(ct::get-dll-record name))
(defun %close-foreign-library (name)
"Close the foreign library NAME."
(error "Not implemented."))
(defun native-namestring (pathname)
(namestring pathname)) ; TODO: confirm
;;;# Foreign Globals
;;; FFI to GetProcAddress from the Win32 API.
;;; "The GetProcAddress function retrieves the address of an exported
;;; function or variable from the specified dynamic-link library (DLL)."
(defun-dll get-proc-address
((module HMODULE)
(name LPCSTR))
:return-type FARPROC
:library-name "Kernel32.dll"
:entry-name "GetProcAddress"
:linkage-type :pascal)
(defun foreign-symbol-pointer (name)
"Returns a pointer to a foreign symbol NAME."
(let ((str (lisp-string-to-c-string name)))
(unwind-protect
(dolist (dll ct::*dlls-loaded*)
(let ((ptr (get-proc-address
(int-to-foreign-ptr (ct::dll-record-handle dll))
str)))
(when (not (cpointer-null ptr))
(return ptr))))
(free str))))

View file

@ -0,0 +1,454 @@
;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
;;;
;;; cffi-ecl.lisp --- ECL backend for CFFI.
;;;
;;; Copyright (C) 2005-2006, James Bielman <jamesjb@jamesjb.com>
;;;
;;; 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)
(:import-from #:si #:null-pointer-p)
(:export
#:*cffi-ecl-method*
#:canonicalize-symbol-name-case
#:foreign-pointer
#:pointerp
#:pointer-eq
#:%foreign-alloc
#:foreign-free
#:with-foreign-pointer
#:null-pointer
#:null-pointer-p
#:inc-pointer
#:make-pointer
#:pointer-address
#:%mem-ref
#:%mem-set
#:%foreign-funcall
#:%foreign-funcall-pointer
#:%foreign-funcall-varargs
#:%foreign-funcall-pointer-varargs
#:%foreign-type-alignment
#:%foreign-type-size
#:%load-foreign-library
#:%close-foreign-library
#:native-namestring
#:make-shareable-byte-vector
#:with-pointer-to-vector-data
#:%defcallback
#:%callback
#:%foreign-symbol-pointer))
(in-package #:cffi-sys)
;;;
;;; ECL allows many ways of calling a foreign function, and also many
;;; ways of finding the pointer associated to a function name. They
;;; depend on whether the FFI relies on libffi or on the C/C++ compiler,
;;; and whether they use the shared library loader to locate symbols
;;; or they are linked by the linker.
;;;
;;; :DFFI
;;;
;;; ECL uses libffi to call foreign functions. The only way to find out
;;; foreign symbols is by loading shared libraries and using dlopen()
;;; or similar.
;;;
;;; :DLOPEN
;;;
;;; ECL compiles FFI code as C/C++ statements. The names are resolved
;;; at run time by the shared library loader every time the function
;;; is called
;;;
;;; :C/C++
;;;
;;; ECL compiles FFI code as C/C++ statements, but the name resolution
;;; happens at link time. In this case you have to tell the ECL
;;; compiler which are the right ld-flags (c:*ld-flags*) to link in
;;; the library.
;;;
(defvar *cffi-ecl-method*
#+dffi :dffi
#+(and dlopen (not dffi)) :dlopen
#-(or dffi dlopen) :c/c++
"The type of code that CFFI generates for ECL: :DFFI when using the
dynamical foreign function interface; :DLOPEN when using C code and
dynamical references to symbols; :C/C++ for C/C++ code with static
references to symbols.")
;;;# Mis-features
#-long-long
(pushnew 'no-long-long *features*)
(pushnew 'flat-namespace *features*)
;;;# Symbol Case
(defun canonicalize-symbol-name-case (name)
(declare (string name))
(string-upcase name))
;;;# Allocation
(defun %foreign-alloc (size)
"Allocate SIZE bytes of foreign-addressable memory."
(si:allocate-foreign-data :void size))
(defun foreign-free (ptr)
"Free a pointer PTR allocated by FOREIGN-ALLOC."
(si:free-foreign-data ptr))
(defmacro with-foreign-pointer ((var size &optional size-var) &body body)
"Bind VAR to SIZE bytes of foreign memory during BODY. The
pointer in VAR is invalid beyond the dynamic extent of BODY, 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* ((,size-var ,size)
(,var (%foreign-alloc ,size-var)))
(unwind-protect
(progn ,@body)
(foreign-free ,var))))
;;;# Misc. Pointer Operations
(deftype foreign-pointer ()
'si:foreign-data)
(defun null-pointer ()
"Construct and return a null pointer."
(si:allocate-foreign-data :void 0))
(defun inc-pointer (ptr offset)
"Return a pointer OFFSET bytes past PTR."
(ffi:make-pointer (+ (ffi:pointer-address ptr) offset) :void))
(defun pointerp (ptr)
"Return true if PTR is a foreign pointer."
(typep ptr 'si:foreign-data))
(defun pointer-eq (ptr1 ptr2)
"Return true if PTR1 and PTR2 point to the same address."
(= (ffi:pointer-address ptr1) (ffi:pointer-address ptr2)))
(defun make-pointer (address)
"Return a pointer pointing to ADDRESS."
(ffi:make-pointer address :void))
(defun pointer-address (ptr)
"Return the address pointed to by PTR."
(ffi:pointer-address ptr))
;;;# 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.
(defun make-shareable-byte-vector (size)
"Create a Lisp vector of SIZE bytes that can passed to
WITH-POINTER-TO-VECTOR-DATA."
(make-array size :element-type '(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."
`(let ((,ptr-var (si:make-foreign-data-from-array ,vector)))
,@body))
;;;# Type Operations
(defconstant +translation-table+
'((:char :byte "char")
(:unsigned-char :unsigned-byte "unsigned char")
(:short :short "short")
(:unsigned-short :unsigned-short "unsigned short")
(:int :int "int")
(:unsigned-int :unsigned-int "unsigned int")
(:long :long "long")
(:unsigned-long :unsigned-long "unsigned long")
#+long-long
(:long-long :long-long "long long")
#+long-long
(:unsigned-long-long :unsigned-long-long "unsigned long long")
(:float :float "float")
(:double :double "double")
(:pointer :pointer-void "void*")
(:void :void "void")))
(defun cffi-type->ecl-type (type-keyword)
"Convert a CFFI type keyword to an ECL type keyword."
(or (second (find type-keyword +translation-table+ :key #'first))
(error "~S is not a valid CFFI type" type-keyword)))
(defun ecl-type->c-type (type-keyword)
"Convert a CFFI type keyword to an valid C type keyword."
(or (third (find type-keyword +translation-table+ :key #'second))
(error "~S is not a valid CFFI type" type-keyword)))
(defun %foreign-type-size (type-keyword)
"Return the size in bytes of a foreign type."
(nth-value 0 (ffi:size-of-foreign-type
(cffi-type->ecl-type type-keyword))))
(defun %foreign-type-alignment (type-keyword)
"Return the alignment in bytes of a foreign type."
(nth-value 1 (ffi:size-of-foreign-type
(cffi-type->ecl-type type-keyword))))
;;;# Dereferencing
(defun %mem-ref (ptr type &optional (offset 0))
"Dereference an object of TYPE at OFFSET bytes from PTR."
(let* ((type (cffi-type->ecl-type type))
(type-size (ffi:size-of-foreign-type type)))
(si:foreign-data-ref-elt
(si:foreign-data-recast ptr (+ offset type-size) :void) offset type)))
(defun %mem-set (value ptr type &optional (offset 0))
"Set an object of TYPE at OFFSET bytes from PTR."
(let* ((type (cffi-type->ecl-type type))
(type-size (ffi:size-of-foreign-type type)))
(si:foreign-data-set-elt
(si:foreign-data-recast ptr (+ offset type-size) :void)
offset type value)))
;;; Inline versions that use C expressions instead of function calls.
(defparameter +mem-ref-strings+
(loop for (cffi-type ecl-type c-string) in +translation-table+
for string = (format nil "*((~A *)(((char*)#0)+#1))" c-string)
collect (list cffi-type ecl-type string)))
(defparameter +mem-set-strings+
(loop for (cffi-type ecl-type c-string) in +translation-table+
for string = (format nil "*((~A *)(((char*)#0)+#1))=#2" c-string)
collect (list cffi-type ecl-type string)))
(define-compiler-macro %mem-ref (&whole whole ptr type &optional (offset 0))
(if (and (constantp type) (constantp offset))
(let ((record (assoc (eval type) +mem-ref-strings+)))
`(ffi:c-inline (,ptr ,offset)
(:pointer-void :cl-index) ; argument types
,(second record) ; return type
,(third record) ; the precomputed expansion
:one-liner t))
whole))
(define-compiler-macro %mem-set (&whole whole value ptr type &optional (offset 0))
(if (and (constantp type) (constantp offset))
(let ((record (assoc (eval type) +mem-set-strings+)))
`(ffi:c-inline (,ptr ,offset ,value) ; arguments with type translated
(:pointer-void :cl-index ,(second record))
:void ; does not return anything
,(third record) ; precomputed expansion
:one-liner t))
whole))
;;;# Calling Foreign Functions
(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")
(defun c-inline-function-call (thing fixed-types types values return-type dynamic-call variadic)
(when dynamic-call
(when (stringp thing)
(setf thing `(%foreign-symbol-pointer ,thing nil)))
(push thing values)
(push :pointer-void types))
(let* ((decl-args
(format nil "~{~A~^, ~}~A"
(mapcar #'ecl-type->c-type fixed-types) (if (null variadic) "" ", ...")))
(call-args
(if dynamic-call
;; #0 is already used in a cast (it is a function pointer)
(subseq +ecl-inline-codes+ 3 (max 3 (1- (* (length values) 3))))
;; #0 is not used, so we start from the beginning
(subseq +ecl-inline-codes+ 0 (max 0 (1- (* (length values) 3))))))
(clines
(if dynamic-call
nil
(format nil "extern ~A ~A(~A);"
(ecl-type->c-type return-type) thing decl-args)))
(call-code
(if dynamic-call
(format nil "((~A (*)(~A))(#0))(~A)"
(ecl-type->c-type return-type) decl-args call-args)
(format nil "~A(~A)" thing call-args))))
`(progn
(ffi:clines ,@(ensure-list clines))
(ffi:c-inline ,values ,types ,return-type ,call-code :one-liner t :side-effects t))))
(defun dffi-function-pointer-call (pointer types values return-type)
(when (stringp pointer)
(setf pointer `(%foreign-symbol-pointer ,pointer nil)))
#-dffi
`(error "In interpreted code, attempted to call a foreign function~% ~A~%~
but ECL was built without support for that." ,pointer)
#+dffi
`(si::call-cfun ,pointer ,return-type (list ,@types) (list ,@values)))
(defun foreign-funcall-parse-args (args)
"Return three values, lists of arg types, values, and result type."
(let ((return-type :void))
(loop for (type arg) on args by #'cddr
if arg collect (cffi-type->ecl-type type) into types
and collect arg into values
else do (setf return-type (cffi-type->ecl-type type))
finally (return (values types values return-type)))))
(defmacro %foreign-funcall (name args &key library convention)
"Call a foreign function."
(declare (ignore library convention))
(multiple-value-bind (types values return-type)
(foreign-funcall-parse-args args)
`(ext:with-backend
:bytecodes
,(dffi-function-pointer-call name types values return-type)
:c/c++
,(ecase *cffi-ecl-method*
(:dffi (dffi-function-pointer-call name types values return-type))
(:dlopen (c-inline-function-call name types types values return-type t nil))
(:c/c++ (c-inline-function-call name types types values return-type nil nil))))))
(defmacro %foreign-funcall-pointer (pointer args &key convention)
"Funcall a pointer to a foreign function."
(declare (ignore convention))
(multiple-value-bind (types values return-type)
(foreign-funcall-parse-args args)
`(ext:with-backend
:bytecodes
,(dffi-function-pointer-call pointer types values return-type)
:c/c++
,(if (eq *cffi-ecl-method* :dffi)
(dffi-function-pointer-call pointer types values return-type)
(c-inline-function-call pointer types types values return-type t nil)))))
(defmacro %foreign-funcall-varargs (name args varargs &key library convention)
(declare (ignore library convention))
(multiple-value-bind (fixed-types fixed-values)
(foreign-funcall-parse-args args)
(multiple-value-bind (varargs-types varargs-values return-type)
(foreign-funcall-parse-args varargs)
(let ((all-types (append fixed-types varargs-types))
(values (append fixed-values varargs-values)))
`(ext:with-backend
:bytecodes
,(dffi-function-pointer-call name all-types values return-type)
:c/c++
,(ecase *cffi-ecl-method*
(:dffi (dffi-function-pointer-call name all-types values return-type))
(:dlopen (c-inline-function-call name fixed-types all-types values return-type t t))
(:c/c++ (c-inline-function-call name fixed-types all-types values return-type nil t))))))))
(defmacro %foreign-funcall-pointer-varargs (pointer args varargs &key convention)
(declare (ignore convention))
(multiple-value-bind (fixed-types fixed-values)
(foreign-funcall-parse-args args)
(multiple-value-bind (varargs-types varargs-values return-type)
(foreign-funcall-parse-args varargs)
(let ((all-types (append fixed-types varargs-types))
(values (append fixed-values varargs-values)))
`(ext:with-backend
:bytecodes
,(dffi-function-pointer-call pointer all-types values return-type)
:c/c++
,(if (eq *cffi-ecl-method* :dffi)
(dffi-function-pointer-call pointer all-types values return-type)
(c-inline-function-call pointer fixed-types all-types values return-type t t)))))))
;;;# Foreign Libraries
(defun %load-foreign-library (name path)
"Load a foreign library."
(declare (ignore name))
#-dffi (error "LOAD-FOREIGN-LIBRARY requires ECL's DFFI support. Use ~
FFI:LOAD-FOREIGN-LIBRARY with a constant argument instead.")
#+dffi
(handler-case (si:load-foreign-module path)
(file-error ()
(error "file error while trying to load `~A'" path))))
(defun %close-foreign-library (handle)
"Close a foreign library."
(handler-case (si::unload-foreign-module handle)
(undefined-function ()
(restart-case (error "Detected ECL prior to version 15.2.21. ~
Function CFFI:CLOSE-FOREIGN-LIBRARY isn't implemented yet.")
(ignore () :report "Continue anyway (foreign library will remain opened).")))))
(defun native-namestring (pathname)
(namestring pathname))
;;;# Callbacks
;;; Create a package to contain the symbols for callback functions.
;;; We want to redefine callbacks with the same symbol so the internal
;;; data structures are reused.
(defpackage #:cffi-callbacks
(:use))
(defvar *callbacks* (make-hash-table))
;;; Intern a symbol in the CFFI-CALLBACKS package used to name the
;;; internal callback for NAME.
(eval-when (:compile-toplevel :load-toplevel :execute)
(defun intern-callback (name)
(intern (format nil "~A::~A"
(if-let (package (symbol-package name))
(package-name package)
"#")
(symbol-name name))
'#:cffi-callbacks)))
(defmacro %defcallback (name rettype arg-names arg-types body
&key convention)
(declare (ignore convention))
(let ((cb-name (intern-callback name))
(cb-type #.(if (> ext:+ecl-version-number+ 160102)
:default :cdecl)))
`(progn
(ffi:defcallback (,cb-name ,cb-type)
,(cffi-type->ecl-type rettype)
,(mapcar #'list arg-names
(mapcar #'cffi-type->ecl-type arg-types))
,body)
(setf (gethash ',name *callbacks*) ',cb-name))))
(defun %callback (name)
(multiple-value-bind (symbol winp)
(gethash name *callbacks*)
(unless winp
(error "Undefined callback: ~S" name))
(ffi:callback symbol)))
;;;# Foreign Globals
(defun %foreign-symbol-pointer (name library)
"Returns a pointer to a foreign symbol NAME."
(declare (ignore library))
(handler-case
(si:find-foreign-symbol (coerce name 'base-string)
:default :pointer-void 0)
(error (c) nil)))

View file

@ -0,0 +1,313 @@
;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
;;;
;;; cffi-gcl.lisp --- CFFI-SYS implementation for GNU Common Lisp.
;;;
;;; Copyright (C) 2005-2006, Luis Oliveira <loliveira(@)common-lisp.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.
;;;
;;; GCL specific notes:
;;;
;;; On ELF systems, a library can be loaded with the help of this:
;;; http://www.copyleft.de/lisp/gcl-elf-loader.html
;;;
;;; Another way is to link the library when creating a new image:
;;; (compiler::link nil "new_image" "" "-lfoo")
;;;
;;; As GCL's FFI is not dynamic, CFFI declarations will only work
;;; after compiled and loaded.
;;; *** this port is broken ***
;;; gcl doesn't compile the rest of CFFI anyway..
;;;# Administrivia
(defpackage #:cffi-sys
(:use #:common-lisp #:alexandria)
(:export
#:canonicalize-symbol-name-case
#:pointerp
#:%foreign-alloc
#:foreign-free
#:with-foreign-ptr
#:null-ptr
#:null-ptr-p
#:inc-ptr
#:%mem-ref
#:%mem-set
#:%foreign-funcall
#:%foreign-type-alignment
#:%foreign-type-size
#:%load-foreign-library
;#:make-shareable-byte-vector
;#:with-pointer-to-vector-data
#:foreign-var-ptr
#:make-callback))
(in-package #:cffi-sys)
;;;# Mis-*features*
(eval-when (:compile-toplevel :load-toplevel :execute)
(pushnew :cffi/no-foreign-funcall *features*))
;;; Symbol case.
(defun canonicalize-symbol-name-case (name)
(declare (string name))
(string-upcase name))
;;;# Allocation
;;;
;;; Functions and macros for allocating foreign memory on the stack
;;; and on the heap. The main CFFI package defines macros that wrap
;;; FOREIGN-ALLOC and FOREIGN-FREE in UNWIND-PROTECT for the common
;;; usage when the memory has dynamic extent.
(defentry %foreign-alloc (int) (int "malloc"))
;(defun foreign-alloc (size)
; "Allocate SIZE bytes on the heap and return a pointer."
; (%foreign-alloc size))
(defentry foreign-free (int) (void "free"))
;(defun foreign-free (ptr)
; "Free a PTR allocated by FOREIGN-ALLOC."
; (%free ptr))
(defmacro with-foreign-ptr ((var size &optional size-var) &body body)
"Bind VAR to SIZE bytes of foreign memory during BODY. The
pointer in VAR is invalid beyond the dynamic extent of BODY, 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* ((,size-var ,size)
(,var (foreign-alloc ,size-var)))
(unwind-protect
(progn ,@body)
(foreign-free ,var))))
;;;# Misc. Pointer Operations
(defun pointerp (ptr)
"Return true if PTR is a foreign pointer."
(integerp ptr))
(defun null-ptr ()
"Construct and return a null pointer."
0)
(defun null-ptr-p (ptr)
"Return true if PTR is a null pointer."
(= ptr 0))
(defun inc-ptr (ptr offset)
"Return a pointer OFFSET bytes past PTR."
(+ ptr offset))
;;;# 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.
;(defun make-shareable-byte-vector (size)
; "Create a Lisp vector of SIZE bytes that can passed to
;WITH-POINTER-TO-VECTOR-DATA."
; (make-array size :element-type '(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."
; `(ccl:with-pointer-to-ivector (,ptr-var ,vector)
; ,@body))
;;;# Dereferencing
(defmacro define-mem-ref/set (type gcl-type &optional c-name)
(unless c-name
(setq c-name (substitute #\_ #\Space type)))
(let ((ref-fn (concatenate 'string "ref_" c-name))
(set-fn (concatenate 'string "set_" c-name)))
`(progn
;; ref
(defcfun ,(format nil "~A ~A(~A *ptr)" type ref-fn type)
0 "return *ptr;")
(defentry ,(intern (string-upcase (substitute #\- #\_ ref-fn)))
(int) (,gcl-type ,ref-fn))
;; set
(defcfun ,(format nil "void ~A(~A *ptr, ~A value)" set-fn type type)
0 "*ptr = value;")
(defentry ,(intern (string-upcase (substitute #\- #\_ set-fn)))
(int ,gcl-type) (void ,set-fn)))))
(define-mem-ref/set "char" char)
(define-mem-ref/set "unsigned char" char)
(define-mem-ref/set "short" int)
(define-mem-ref/set "unsigned short" int)
(define-mem-ref/set "int" int)
(define-mem-ref/set "unsigned int" int)
(define-mem-ref/set "long" int)
(define-mem-ref/set "unsigned long" int)
(define-mem-ref/set "float" float)
(define-mem-ref/set "double" double)
(define-mem-ref/set "void *" int "ptr")
(defun %mem-ref (ptr type &optional (offset 0))
"Dereference an object of TYPE at OFFSET bytes from PTR."
(unless (zerop offset)
(incf ptr offset))
(ecase type
(:char (ref-char ptr))
(:unsigned-char (ref-unsigned-char ptr))
(:short (ref-short ptr))
(:unsigned-short (ref-unsigned-short ptr))
(:int (ref-int ptr))
(:unsigned-int (ref-unsigned-int ptr))
(:long (ref-long ptr))
(:unsigned-long (ref-unsigned-long ptr))
(:float (ref-float ptr))
(:double (ref-double ptr))
(:pointer (ref-ptr ptr))))
(defun %mem-set (value ptr type &optional (offset 0))
(unless (zerop offset)
(incf ptr offset))
(ecase type
(:char (set-char ptr value))
(:unsigned-char (set-unsigned-char ptr value))
(:short (set-short ptr value))
(:unsigned-short (set-unsigned-short ptr value))
(:int (set-int ptr value))
(:unsigned-int (set-unsigned-int ptr value))
(:long (set-long ptr value))
(:unsigned-long (set-unsigned-long ptr value))
(:float (set-float ptr value))
(:double (set-double ptr value))
(:pointer (set-ptr ptr value)))
value)
;;;# Calling Foreign Functions
;; TODO: figure out if these type conversions make any sense...
(defun convert-foreign-type (type-keyword)
"Convert a CFFI type keyword to a GCL type."
(ecase type-keyword
(:char 'char)
(:unsigned-char 'char)
(:short 'int)
(:unsigned-short 'int)
(:int 'int)
(:unsigned-int 'int)
(:long 'int)
(:unsigned-long 'int)
(:float 'float)
(:double 'double)
(:pointer 'int)
(:void 'void)))
(defparameter +cffi-types+
'(:char :unsigned-char :short :unsigned-short :int :unsigned-int
:long :unsigned-long :float :double :pointer))
(defcfun "int size_of(int type)" 0
"switch (type) {
case 0: return sizeof(char);
case 1: return sizeof(unsigned char);
case 2: return sizeof(short);
case 3: return sizeof(unsigned short);
case 4: return sizeof(int);
case 5: return sizeof(unsigned int);
case 6: return sizeof(long);
case 7: return sizeof(unsigned long);
case 8: return sizeof(float);
case 9: return sizeof(double);
case 10: return sizeof(void *);
default: return -1;
}")
(defentry size-of (int) (int "size_of"))
;; TODO: all this is doable inside the defcfun; figure that out..
(defun %foreign-type-size (type-keyword)
"Return the size in bytes of a foreign type."
(size-of (position type-keyword +cffi-types+)))
(defcfun "int align_of(int type)" 0
"switch (type) {
case 0: return __alignof__(char);
case 1: return __alignof__(unsigned char);
case 2: return __alignof__(short);
case 3: return __alignof__(unsigned short);
case 4: return __alignof__(int);
case 5: return __alignof__(unsigned int);
case 6: return __alignof__(long);
case 7: return __alignof__(unsigned long);
case 8: return __alignof__(float);
case 9: return __alignof__(double);
case 10: return __alignof__(void *);
default: return -1;
}")
(defentry align-of (int) (int "align_of"))
;; TODO: like %foreign-type-size
(defun %foreign-type-alignment (type-keyword)
"Return the alignment in bytes of a foreign type."
(align-of (position type-keyword +cffi-types+)))
#+ignore
(defun convert-external-name (name)
"Add an underscore to NAME if necessary for the ABI."
#+darwinppc-target (concatenate 'string "_" name)
#-darwinppc-target name)
(defmacro %foreign-funcall (function-name &rest args)
"Perform a foreign function all, document it more later."
`(format t "~&;; Calling ~A with args ~S.~%" ,name ',args))
(defun defcfun-helper-forms (name rettype args types)
"Return 2 values for DEFCFUN. A prelude form and a caller form."
(let ((ff-name (intern (format nil "%foreign-function/TildeA:~A" name))))
(values
`(defentry ,ff-name ,(mapcar #'convert-foreign-type types)
(,(convert-foreign-type rettype) ,name))
`(,ff-name ,@args))))
;;;# Callbacks
;;; XXX unimplemented
(defmacro make-callback (name rettype arg-names arg-types body-form)
0)
;;;# Loading Foreign Libraries
(defun %load-foreign-library (name)
"_Won't_ load the foreign library NAME."
(declare (ignore name)))
;;;# Foreign Globals
;;; XXX unimplemented
(defmacro foreign-var-ptr (name)
"Return a pointer pointing to the foreign symbol NAME."
0)

View file

@ -0,0 +1,417 @@
;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
;;;
;;; cffi-lispworks.lisp --- Lispworks CFFI-SYS implementation.
;;;
;;; Copyright (C) 2005-2006, James Bielman <jamesjb@jamesjb.com>
;;;
;;; 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 #:cl #: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
#:defcfun-helper-forms
#:%defcallback
#:%callback))
(in-package #:cffi-sys)
;;;# Misfeatures
#-lispworks-64bit (pushnew 'no-long-long *features*)
;;;# Symbol Case
(defun canonicalize-symbol-name-case (name)
(declare (string name))
(string-upcase name))
;;;# Basic Pointer Operations
(deftype foreign-pointer ()
'fli::pointer)
(defun pointerp (ptr)
"Return true if PTR is a foreign pointer."
(fli:pointerp ptr))
(defun pointer-eq (ptr1 ptr2)
"Return true if PTR1 and PTR2 point to the same address."
(fli:pointer-eq ptr1 ptr2))
;; We use FLI:MAKE-POINTER here instead of FLI:*NULL-POINTER* since old
;; versions of Lispworks don't seem to have it.
(defun null-pointer ()
"Return a null foreign pointer."
(fli:make-pointer :address 0 :type :void))
(defun null-pointer-p (ptr)
"Return true if PTR is a null pointer."
(check-type ptr fli::pointer)
(fli:null-pointer-p ptr))
;; FLI:INCF-POINTER won't work on FLI pointers to :void so we
;; increment "manually."
(defun inc-pointer (ptr offset)
"Return a pointer OFFSET bytes past PTR."
(fli:make-pointer :type :void :address (+ (fli:pointer-address ptr) offset)))
(defun make-pointer (address)
"Return a pointer pointing to ADDRESS."
(fli:make-pointer :type :void :address address))
(defun pointer-address (ptr)
"Return the address pointed to by PTR."
(fli:pointer-address ptr))
;;;# Allocation
(defun %foreign-alloc (size)
"Allocate SIZE bytes of memory and return a pointer."
(fli:allocate-foreign-object :type :byte :nelems size))
(defun foreign-free (ptr)
"Free a pointer PTR allocated by FOREIGN-ALLOC."
(fli:free-foreign-object ptr))
(defmacro with-foreign-pointer ((var size &optional size-var) &body body)
"Bind VAR to SIZE bytes of foreign memory during BODY. Both the
pointer in VAR and the memory it points to have dynamic extent and may
be stack allocated if supported by the implementation."
(unless size-var
(setf size-var (gensym "SIZE")))
`(fli:with-dynamic-foreign-objects ()
(let* ((,size-var ,size)
(,var (fli:alloca :type :byte :nelems ,size-var)))
,@body)))
;;;# Shareable Vectors
(defun make-shareable-byte-vector (size)
"Create a shareable byte vector."
#+(or lispworks3 lispworks4 lispworks5.0)
(sys:in-static-area
(make-array size :element-type '(unsigned-byte 8)))
#-(or lispworks3 lispworks4 lispworks5.0)
(make-array size :element-type '(unsigned-byte 8) :allocation :static))
(defmacro with-pointer-to-vector-data ((ptr-var vector) &body body)
"Bind PTR-VAR to a pointer at the data in VECTOR."
`(fli:with-dynamic-lisp-array-pointer (,ptr-var ,vector)
,@body))
;;;# Dereferencing
(defun convert-foreign-type (cffi-type)
"Convert a CFFI type keyword to an FLI type."
(ecase cffi-type
(:char :byte)
(:unsigned-char '(:unsigned :byte))
(:short :short)
(:unsigned-short '(:unsigned :short))
(:int :int)
(:unsigned-int '(:unsigned :int))
(:long :long)
(:unsigned-long '(:unsigned :long))
;; On 32-bit platforms, Lispworks 5.0+ supports long-long for
;; DEFCFUN and FOREIGN-FUNCALL.
(:long-long '(:long :long))
(:unsigned-long-long '(:unsigned :long :long))
(:float :float)
(:double :double)
(:pointer :pointer)
(:void :void)))
;;; Convert a CFFI type keyword to a symbol suitable for passing to
;;; FLI:FOREIGN-TYPED-AREF.
#+#.(cl:if (cl:find-symbol "FOREIGN-TYPED-AREF" "FLI") '(and) '(or))
(defun convert-foreign-typed-aref-type (cffi-type)
(ecase cffi-type
((:char :short :int :long #+lispworks-64bit :long-long)
`(signed-byte ,(* 8 (%foreign-type-size cffi-type))))
((:unsigned-char :unsigned-short :unsigned-int :unsigned-long
#+lispworks-64bit :unsigned-long-long)
`(unsigned-byte ,(* 8 (%foreign-type-size cffi-type))))
(:float 'single-float)
(:double 'double-float)))
(defun %mem-ref (ptr type &optional (offset 0))
"Dereference an object of type TYPE OFFSET bytes from PTR."
(unless (zerop offset)
(setf ptr (inc-pointer ptr offset)))
(fli:dereference ptr :type (convert-foreign-type type)))
;; Lispworks 5.0 on 64-bit platforms doesn't have [u]int64 support in
;; FOREIGN-TYPED-AREF. That was implemented in 5.1.
#+(and lispworks-64bit lispworks5.0)
(defun 64-bit-type-p (type)
(member type '(:long :unsigned-long :long-long :unsigned-long-long)))
;;; In LispWorks versions where FLI:FOREIGN-TYPED-AREF is fbound, use
;;; it instead of FLI:DEREFERENCE in the optimizer for %MEM-REF.
#+#.(cl:if (cl:find-symbol "FOREIGN-TYPED-AREF" "FLI") '(and) '(or))
(define-compiler-macro %mem-ref (&whole form ptr type &optional (off 0))
(if (constantp type)
(let ((type (eval type)))
(if (or #+(and lispworks-64bit lispworks5.0) (64-bit-type-p type)
(eql type :pointer))
(let ((fli-type (convert-foreign-type type))
(ptr-form (if (eql off 0) ptr `(inc-pointer ,ptr ,off))))
`(fli:dereference ,ptr-form :type ',fli-type))
(let ((lisp-type (convert-foreign-typed-aref-type type)))
`(locally
(declare (optimize (speed 3) (safety 0)))
(fli:foreign-typed-aref ',lisp-type ,ptr (the fixnum ,off))))))
form))
;;; Open-code the call to FLI:DEREFERENCE when TYPE is constant at
;;; macroexpansion time, when FLI:FOREIGN-TYPED-AREF is not available.
#-#.(cl:if (cl:find-symbol "FOREIGN-TYPED-AREF" "FLI") '(and) '(or))
(define-compiler-macro %mem-ref (&whole form ptr type &optional (off 0))
(if (constantp type)
(let ((ptr-form (if (eql off 0) ptr `(inc-pointer ,ptr ,off)))
(type (convert-foreign-type (eval type))))
`(fli:dereference ,ptr-form :type ',type))
form))
(defun %mem-set (value ptr type &optional (offset 0))
"Set the object of TYPE at OFFSET bytes from PTR."
(unless (zerop offset)
(setf ptr (inc-pointer ptr offset)))
(setf (fli:dereference ptr :type (convert-foreign-type type)) value))
;;; In LispWorks versions where FLI:FOREIGN-TYPED-AREF is fbound, use
;;; it instead of FLI:DEREFERENCE in the optimizer for %MEM-SET.
#+#.(cl:if (cl:find-symbol "FOREIGN-TYPED-AREF" "FLI") '(and) '(or))
(define-compiler-macro %mem-set (&whole form val ptr type &optional (off 0))
(if (constantp type)
(once-only (val)
(let ((type (eval type)))
(if (or #+(and lispworks-64bit lispworks5.0) (64-bit-type-p type)
(eql type :pointer))
(let ((fli-type (convert-foreign-type type))
(ptr-form (if (eql off 0) ptr `(inc-pointer ,ptr ,off))))
`(setf (fli:dereference ,ptr-form :type ',fli-type) ,val))
(let ((lisp-type (convert-foreign-typed-aref-type type)))
`(locally
(declare (optimize (speed 3) (safety 0)))
(setf (fli:foreign-typed-aref ',lisp-type ,ptr
(the fixnum ,off))
,val))))))
form))
;;; Open-code the call to (SETF FLI:DEREFERENCE) when TYPE is constant
;;; at macroexpansion time.
#-#.(cl:if (cl:find-symbol "FOREIGN-TYPED-AREF" "FLI") '(and) '(or))
(define-compiler-macro %mem-set (&whole form val ptr type &optional (off 0))
(if (constantp type)
(once-only (val)
(let ((ptr-form (if (eql off 0) ptr `(inc-pointer ,ptr ,off)))
(type (convert-foreign-type (eval type))))
`(setf (fli:dereference ,ptr-form :type ',type) ,val)))
form))
;;;# Foreign Type Operations
(defun %foreign-type-size (type)
"Return the size in bytes of a foreign type."
(fli:size-of (convert-foreign-type type)))
(defun %foreign-type-alignment (type)
"Return the structure alignment in bytes of foreign type."
#+(and darwin harp::powerpc)
(when (eq type :double)
(return-from %foreign-type-alignment 8))
;; Override not necessary for the remaining types...
(fli:align-of (convert-foreign-type type)))
;;;# Calling Foreign Functions
(defvar *foreign-funcallable-cache* (make-hash-table :test 'equal)
"Caches foreign funcallables created by %FOREIGN-FUNCALL or
%FOREIGN-FUNCALL-POINTER. We only need to have one per each
signature.")
(defun foreign-funcall-type-and-args (args)
"Returns a list of types, list of args and return type."
(let ((return-type :void))
(loop for (type arg) on args by #'cddr
if arg collect (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 create-foreign-funcallable (types rettype convention)
"Creates a foreign funcallable for the signature TYPES -> RETTYPE."
#+mac (declare (ignore convention))
(format t "~&Creating foreign funcallable for signature ~S -> ~S~%"
types rettype)
;; yes, ugly, this most likely wants to be a top-level form...
(let ((internal-name (gensym)))
(funcall
(compile nil
`(lambda ()
(fli:define-foreign-funcallable ,internal-name
,(loop for type in types
collect (list (gensym) type))
:result-type ,rettype
:language :ansi-c
;; avoid warning about cdecl not being supported on mac
#-mac ,@(list :calling-convention convention)))))
internal-name))
(defun get-foreign-funcallable (types rettype convention)
"Returns a foreign funcallable for the signature TYPES -> RETTYPE -
either from the cache or newly created."
(let ((signature (cons rettype types)))
(or (gethash signature *foreign-funcallable-cache*)
;; (SETF GETHASH) is supposed to be thread-safe
(setf (gethash signature *foreign-funcallable-cache*)
(create-foreign-funcallable types rettype convention)))))
(defmacro %%foreign-funcall (foreign-function args convention)
"Does the actual work for %FOREIGN-FUNCALL-POINTER and %FOREIGN-FUNCALL.
Checks if a foreign funcallable which fits ARGS already exists and creates
and caches it if necessary. Finally calls it."
(multiple-value-bind (types fargs rettype)
(foreign-funcall-type-and-args args)
`(funcall (load-time-value
(get-foreign-funcallable ',types ',rettype ',convention))
,foreign-function ,@fargs)))
(defmacro %foreign-funcall (name args &key library convention)
"Calls a foreign function named NAME passing arguments ARGS."
`(%%foreign-funcall
(fli:make-pointer :symbol-name ,name
:module ',(if (eq library :default) nil library))
,args ,convention))
(defmacro %foreign-funcall-pointer (ptr args &key convention)
"Calls a foreign function pointed at by PTR passing arguments ARGS."
`(%%foreign-funcall ,ptr ,args ,convention))
(defun defcfun-helper-forms (name lisp-name rettype args types options)
"Return 2 values for DEFCFUN. A prelude form and a caller form."
(let ((ff-name (intern (format nil "%cffi-foreign-function/~A" lisp-name))))
(values
`(fli:define-foreign-function (,ff-name ,name :source)
,(mapcar (lambda (ty) (list (gensym) (convert-foreign-type ty)))
types)
:result-type ,(convert-foreign-type rettype)
:language :ansi-c
:module ',(let ((lib (getf options :library)))
(if (eq lib :default) nil lib))
;; avoid warning about cdecl not being supported on mac platforms
#-mac ,@(list :calling-convention (getf options :convention)))
`(,ff-name ,@args))))
;;;# Callbacks
(defvar *callbacks* (make-hash-table))
;;; Create a package to contain the symbols for callback functions. We
;;; want to redefine callbacks with the same symbol so the internal data
;;; structures are reused.
(defpackage #:cffi-callbacks
(:use))
;;; Intern a symbol in the CFFI-CALLBACKS package used to name the internal
;;; callback for NAME.
(eval-when (:compile-toplevel :load-toplevel :execute)
(defun intern-callback (name)
(intern (format nil "~A::~A"
(if-let (package (symbol-package name))
(package-name package)
"#")
(symbol-name name))
'#:cffi-callbacks)))
(defmacro %defcallback (name rettype arg-names arg-types body
&key convention)
(let ((cb-name (intern-callback name)))
`(progn
(fli:define-foreign-callable
(,cb-name :encode :lisp
:result-type ,(convert-foreign-type rettype)
:calling-convention ,convention
:language :ansi-c
:no-check nil)
,(mapcar (lambda (sym type)
(list sym (convert-foreign-type type)))
arg-names arg-types)
,body)
(setf (gethash ',name *callbacks*) ',cb-name))))
(defun %callback (name)
(multiple-value-bind (symbol winp)
(gethash name *callbacks*)
(unless winp
(error "Undefined callback: ~S" name))
(fli:make-pointer :symbol-name symbol :module :callbacks)))
;;;# Loading Foreign Libraries
(defun %load-foreign-library (name path)
"Load the foreign library NAME."
(fli:register-module (or name path) :connection-style :immediate
:real-name path))
(defun %close-foreign-library (name)
"Close the foreign library NAME."
(fli:disconnect-module name :remove t))
(defun native-namestring (pathname)
(namestring pathname))
;;;# Foreign Globals
(defun %foreign-symbol-pointer (name library)
"Returns a pointer to a foreign symbol NAME."
(values
(ignore-errors
(fli:make-pointer :symbol-name name :type :void
:module (if (eq library :default) nil library)))))

View file

@ -0,0 +1,396 @@
;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
;;;
;;; cffi-mcl.lisp --- CFFI-SYS implementation for Digitool MCL.
;;;
;;; Copyright 2010 james.anderson@setf.de
;;; Copyright 2005-2006, James Bielman <jamesjb@jamesjb.com>
;;;
;;; 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.
;;;
;;; this is a stop-gap emulation. (at least) three things are not right
;;; - integer vector arguments are copied
;;; - return values are not typed
;;; - a shared library must be packaged as a framework and statically loaded
;;;
;;; on the topic of shared libraries, see
;;; http://developer.apple.com/library/mac/#documentation/DeveloperTools/Conceptual/MachOTopics/1-Articles/loading_code.html
;;; which describes how to package a shared library as a framework.
;;; once a framework exists, load it as, eg.
;;; (ccl::add-framework-bundle "fftw.framework" :pathname "ccl:frameworks;" )
;;;# Administrivia
(defpackage #:cffi-sys
(:use #:common-lisp #:ccl)
(:import-from #:alexandria #:once-only #:if-let)
(:export
#:canonicalize-symbol-name-case
#:foreign-pointer
#:pointerp ; ccl:pointerp
#:pointer-eq
#:%foreign-alloc
#:foreign-free
#:with-foreign-pointer
#:null-pointer
#:null-pointer-p
#:inc-pointer
#:make-pointer
#:pointer-address
#:%mem-ref
#:%mem-set
#:%foreign-funcall
#:%foreign-funcall-pointer
#:%foreign-type-alignment
#:%foreign-type-size
#:%load-foreign-library
#:%close-foreign-library
#:native-namestring
#:make-shareable-byte-vector
#:with-pointer-to-vector-data
#:%foreign-symbol-pointer
#:%defcallback
#:%callback))
(in-package #:cffi-sys)
;;;# Misfeatures
(pushnew 'flat-namespace *features*)
;;;# Symbol Case
(defun canonicalize-symbol-name-case (name)
(declare (string name))
(string-upcase name))
;;;# Allocation
;;;
;;; Functions and macros for allocating foreign memory on the stack
;;; and on the heap. The main CFFI package defines macros that wrap
;;; FOREIGN-ALLOC and FOREIGN-FREE in UNWIND-PROTECT for the common
;;; usage when the memory has dynamic extent.
(defun %foreign-alloc (size)
"Allocate SIZE bytes on the heap and return a pointer."
(#_newPtr size))
(defun foreign-free (ptr)
"Free a PTR allocated by FOREIGN-ALLOC."
;; TODO: Should we make this a dead macptr?
(#_disposePtr ptr))
(defmacro with-foreign-pointer ((var size &optional size-var) &body body)
"Bind VAR to SIZE bytes of foreign memory during BODY. The
pointer in VAR is invalid beyond the dynamic extent of BODY, 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 ((,size-var ,size))
(ccl:%stack-block ((,var ,size-var))
,@body)))
;;;# Misc. Pointer Operations
(deftype foreign-pointer ()
'ccl:macptr)
(defun null-pointer ()
"Construct and return a null pointer."
(ccl:%null-ptr))
(defun null-pointer-p (ptr)
"Return true if PTR is a null pointer."
(ccl:%null-ptr-p ptr))
(defun inc-pointer (ptr offset)
"Return a pointer OFFSET bytes past PTR."
(ccl:%inc-ptr ptr offset))
(defun pointer-eq (ptr1 ptr2)
"Return true if PTR1 and PTR2 point to the same address."
(ccl:%ptr-eql ptr1 ptr2))
(defun make-pointer (address)
"Return a pointer pointing to ADDRESS."
(ccl:%int-to-ptr address))
(defun pointer-address (ptr)
"Return the address pointed to by PTR."
(ccl:%ptr-to-int ptr))
;;;# 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.
(defun make-shareable-byte-vector (size)
"Create a Lisp vector of SIZE bytes that can passed to
WITH-POINTER-TO-VECTOR-DATA."
(make-array size :element-type '(unsigned-byte 8)))
;;; from openmcl::macros.lisp
(defmacro with-pointer-to-vector-data ((ptr ivector) &body body)
"Bind PTR-VAR to a foreign pointer to the data in VECTOR."
(let* ((v (gensym))
(l (gensym)))
`(let* ((,v ,ivector)
(,l (length ,v)))
(unless (typep ,v 'ccl::ivector) (ccl::report-bad-arg ,v 'ccl::ivector))
;;;!!! this, unless it's possible to suppress gc
(let ((,ptr (#_newPtr ,l)))
(unwind-protect (progn (ccl::%copy-ivector-to-ptr ,v 0 ,ptr 0 ,l)
(mutliple-value-prog1
(locally ,@body)
(ccl::%copy-ptr-to-ivector ,ptr 0 ,v 0 ,l)))
(#_disposePtr ,ptr))))))
;;;# Dereferencing
;;; Define the %MEM-REF and %MEM-SET functions, as well as compiler
;;; macros that optimize the case where the type keyword is constant
;;; at compile-time.
(defmacro define-mem-accessors (&body pairs)
`(progn
(defun %mem-ref (ptr type &optional (offset 0))
(ecase type
,@(loop for (keyword fn) in pairs
collect `(,keyword (,fn ptr offset)))))
(defun %mem-set (value ptr type &optional (offset 0))
(ecase type
,@(loop for (keyword fn) in pairs
collect `(,keyword (setf (,fn ptr offset) value)))))
(define-compiler-macro %mem-ref
(&whole form ptr type &optional (offset 0))
(if (constantp type)
(ecase (eval type)
,@(loop for (keyword fn) in pairs
collect `(,keyword `(,',fn ,ptr ,offset))))
form))
(define-compiler-macro %mem-set
(&whole form value ptr type &optional (offset 0))
(if (constantp type)
(once-only (value)
(ecase (eval type)
,@(loop for (keyword fn) in pairs
collect `(,keyword `(setf (,',fn ,ptr ,offset)
,value)))))
form))))
(define-mem-accessors
(:char %get-signed-byte)
(:unsigned-char %get-unsigned-byte)
(:short %get-signed-word)
(:unsigned-short %get-unsigned-word)
(:int %get-signed-long)
(:unsigned-int %get-unsigned-long)
(:long %get-signed-long)
(:unsigned-long %get-unsigned-long)
(:long-long ccl::%get-signed-long-long)
(:unsigned-long-long ccl::%get-unsigned-long-long)
(:float %get-single-float)
(:double %get-double-float)
(:pointer %get-ptr))
(defun ccl::%get-unsigned-long-long (ptr offset)
(let ((value 0) (bit 0))
(dotimes (i 8)
(setf (ldb (byte 8 (shiftf bit (+ bit 8))) value)
(ccl:%get-unsigned-byte ptr (+ offset i))))
value))
(setf (fdefinition 'ccl::%get-signed-long-long)
(fdefinition 'ccl::%get-unsigned-long-long))
(defun (setf ccl::%get-unsigned-long-long) (value ptr offset)
(let ((bit 0))
(dotimes (i 8)
(setf (ccl:%get-unsigned-byte ptr (+ offset i))
(ldb (byte 8 (shiftf bit (+ bit 8))) value))))
ptr)
(setf (fdefinition '(setf ccl::%get-signed-long-long))
(fdefinition '(setf ccl::%get-unsigned-long-long)))
;;;# Calling Foreign Functions
(defun convert-foreign-type (type-keyword)
"Convert a CFFI type keyword to a ppc-ff-call type."
(ecase type-keyword
(:char :signed-byte)
(:unsigned-char :unsigned-byte)
(:short :signed-short)
(:unsigned-short :unsigned-short)
(:int :signed-fullword)
(:unsigned-int :unsigned-fullword)
(:long :signed-fullword)
(:unsigned-long :unsigned-fullword)
(:long-long :signed-doubleword)
(:unsigned-long-long :unsigned-doubleword)
(:float :single-float)
(:double :double-float)
(:pointer :address)
(:void :void)))
(defun ppc-ff-call-type=>mactype-name (type-keyword)
(ecase type-keyword
(:signed-byte :sint8)
(:unsigned-byte :uint8)
(:signed-short :sint16)
(:unsigned-short :uint16)
(:signed-halfword :sint16)
(:unsigned-halfword :uint16)
(:signed-fullword :sint32)
(:unsigned-fullword :uint32)
;(:signed-doubleword :long-long)
;(:unsigned-doubleword :unsigned-long-long)
(:single-float :single-float)
(:double-float :double-float)
(:address :pointer)
(:void :void)))
(defun %foreign-type-size (type-keyword)
"Return the size in bytes of a foreign type."
(case type-keyword
((:long-long :unsigned-long-long) 8)
(t (ccl::mactype-record-size
(ccl::find-mactype
(ppc-ff-call-type=>mactype-name (convert-foreign-type type-keyword)))))))
;; There be dragons here. See the following thread for details:
;; http://clozure.com/pipermail/openmcl-devel/2005-June/002777.html
(defun %foreign-type-alignment (type-keyword)
"Return the alignment in bytes of a foreign type."
(case type-keyword
((:long-long :unsigned-long-long) 4)
(t (ccl::mactype-record-size
(ccl::find-mactype
(ppc-ff-call-type=>mactype-name (convert-foreign-type type-keyword)))))))
(defun convert-foreign-funcall-types (args)
"Convert foreign types for a call to FOREIGN-FUNCALL."
(loop for (type arg) on args by #'cddr
collect (convert-foreign-type type)
if arg collect arg))
(defun convert-external-name (name)
"no '_' is necessary here, the internal lookup operators handle it"
name)
(defmacro %foreign-funcall (function-name args &key library convention)
"Perform a foreign function call, document it more later."
(declare (ignore library convention))
`(ccl::ppc-ff-call
(ccl::macho-address ,(ccl::get-macho-entry-point (convert-external-name function-name)))
,@(convert-foreign-funcall-types args)))
(defmacro %foreign-funcall-pointer (ptr args &key convention)
(declare (ignore convention))
`(ccl::ppc-ff-call ,ptr ,@(convert-foreign-funcall-types args)))
;;;# Callbacks
;;; The *CALLBACKS* hash table maps CFFI callback names to OpenMCL "macptr"
;;; entry points. It is safe to store the pointers directly because
;;; OpenMCL will update the address of these pointers when a saved image
;;; is loaded (see CCL::RESTORE-PASCAL-FUNCTIONS).
(defvar *callbacks* (make-hash-table))
;;; Create a package to contain the symbols for callback functions. We
;;; want to redefine callbacks with the same symbol so the internal data
;;; structures are reused.
(defpackage #:cffi-callbacks
(:use))
;;; Intern a symbol in the CFFI-CALLBACKS package used to name the internal
;;; callback for NAME.
(defun intern-callback (name)
(intern (format nil "~A::~A"
(if-let (package (symbol-package name))
(package-name package)
"#")
(symbol-name name))
'#:cffi-callbacks))
(defmacro %defcallback (name rettype arg-names arg-types body
&key convention)
(declare (ignore convention))
(let ((cb-name (intern-callback name)))
`(progn
(ccl::ppc-defpascal ,cb-name
(;; ? ,@(when (eq convention :stdcall) '(:discard-stack-args))
,@(mapcan (lambda (sym type)
(list (ppc-ff-call-type=>mactype-name (convert-foreign-type type)) sym))
arg-names arg-types)
,(ppc-ff-call-type=>mactype-name (convert-foreign-type rettype)))
,body)
(setf (gethash ',name *callbacks*) (symbol-value ',cb-name)))))
(defun %callback (name)
(or (gethash name *callbacks*)
(error "Undefined callback: ~S" name)))
;;;# Loading Foreign Libraries
(defun %load-foreign-library (name path)
"Load the foreign library NAME."
(declare (ignore path))
(setf name (string name))
;; for mcl emulate this wrt frameworks
(unless (and (> (length name) 10)
(string-equal name ".framework" :start1 (- (length name) 10)))
(setf name (concatenate 'string name ".framework")))
;; if the framework was not registered, add it
(unless (gethash name ccl::*framework-descriptors*)
(ccl::add-framework-bundle name :pathname "ccl:frameworks;" ))
(ccl::load-framework-bundle name))
(defun %close-foreign-library (name)
"Close the foreign library NAME."
;; for mcl do nothing
(declare (ignore name))
nil)
(defun native-namestring (pathname)
(ccl::posix-namestring (ccl:full-pathname pathname)))
;;;# Foreign Globals
(deftrap-inline "_findsymbol"
((map :pointer)
(name :pointer))
:pointer
())
(defun %foreign-symbol-pointer (name library)
"Returns a pointer to a foreign symbol NAME."
(declare (ignore library))
(ccl::macho-address
(ccl::get-macho-entry-point (convert-external-name name))))

View file

@ -0,0 +1,342 @@
;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
;;;
;;; cffi-mkcl.lisp --- MKCL backend for CFFI.
;;;
;;; Copyright (C) 2010-2012, Jean-Claude Beaudoin
;;; Copyright (C) 2005-2006, James Bielman <jamesjb@jamesjb.com>
;;;
;;; 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)
;;;# Mis-features
(pushnew 'flat-namespace *features*)
;;;# Symbol Case
(defun canonicalize-symbol-name-case (name)
(declare (string name))
(string-upcase name))
;;;# Allocation
(defun %foreign-alloc (size)
"Allocate SIZE bytes of foreign-addressable memory."
(si:allocate-foreign-data :void size))
(defun foreign-free (ptr)
"Free a pointer PTR allocated by FOREIGN-ALLOC."
(si:free-foreign-data ptr)
nil)
(defmacro with-foreign-pointer ((var size &optional size-var) &body body)
"Bind VAR to SIZE bytes of foreign memory during BODY. The
pointer in VAR is invalid beyond the dynamic extent of BODY, 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* ((,size-var ,size)
(,var (%foreign-alloc ,size-var)))
(unwind-protect
(progn ,@body)
(foreign-free ,var))))
;;;# Misc. Pointer Operations
(deftype foreign-pointer ()
'si:foreign)
(defun null-pointer ()
"Construct and return a null pointer."
(si:make-foreign-null-pointer))
(defun null-pointer-p (ptr)
"Return true if PTR is a null pointer."
(si:null-pointer-p ptr))
(defun inc-pointer (ptr offset)
"Return a pointer OFFSET bytes past PTR."
(ffi:make-pointer (+ (ffi:pointer-address ptr) offset) :void))
(defun pointerp (ptr)
"Return true if PTR is a foreign pointer."
;;(typep ptr 'si:foreign)
(si:foreignp ptr))
(defun pointer-eq (ptr1 ptr2)
"Return true if PTR1 and PTR2 point to the same address."
(= (ffi:pointer-address ptr1) (ffi:pointer-address ptr2)))
(defun make-pointer (address)
"Return a pointer pointing to ADDRESS."
(ffi:make-pointer address :void))
(defun pointer-address (ptr)
"Return the address pointed to by PTR."
(ffi:pointer-address ptr))
;;;# 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.
(defun make-shareable-byte-vector (size)
"Create a Lisp vector of SIZE bytes that can passed to
WITH-POINTER-TO-VECTOR-DATA."
(make-array size :element-type '(unsigned-byte 8)))
;;; MKCL, built with the Boehm GC never moves allocated data, so this
;;; isn't nearly as hard to do.
(defun %vector-address (vector)
"Return the address of VECTOR's data."
(check-type vector (vector (unsigned-byte 8)))
#-mingw64
(ffi:c-inline (vector) (object)
:unsigned-long
"(uintptr_t) #0->vector.self.b8"
:side-effects nil
:one-liner t)
#+mingw64
(ffi:c-inline (vector) (object)
:unsigned-long-long
"(uintptr_t) #0->vector.self.b8"
:side-effects nil
:one-liner t))
(defmacro with-pointer-to-vector-data ((ptr-var vector) &body body)
"Bind PTR-VAR to a foreign pointer to the data in VECTOR."
`(let ((,ptr-var (make-pointer (%vector-address ,vector))))
,@body))
;;;# Dereferencing
(defun %mem-ref (ptr type &optional (offset 0))
"Dereference an object of TYPE at OFFSET bytes from PTR."
(let* ((type (cffi-type->mkcl-type type))
(type-size (ffi:size-of-foreign-type type)))
(si:foreign-ref-elt
(si:foreign-recast ptr (+ offset type-size) :void) offset type)))
(defun %mem-set (value ptr type &optional (offset 0))
"Set an object of TYPE at OFFSET bytes from PTR."
(let* ((type (cffi-type->mkcl-type type))
(type-size (ffi:size-of-foreign-type type)))
(si:foreign-set-elt
(si:foreign-recast ptr (+ offset type-size) :void)
offset type value)))
;;;# Type Operations
(defconstant +translation-table+
'((:char :byte "char")
(:unsigned-char :unsigned-byte "unsigned char")
(:short :short "short")
(:unsigned-short :unsigned-short "unsigned short")
(:int :int "int")
(:unsigned-int :unsigned-int "unsigned int")
(:long :long "long")
(:unsigned-long :unsigned-long "unsigned long")
(:long-long :long-long "long long")
(:unsigned-long-long :unsigned-long-long "unsigned long long")
(:float :float "float")
(:double :double "double")
(:pointer :pointer-void "void*")
(:void :void "void")))
(defun cffi-type->mkcl-type (type-keyword)
"Convert a CFFI type keyword to an MKCL type keyword."
(or (second (find type-keyword +translation-table+ :key #'first))
(error "~S is not a valid CFFI type" type-keyword)))
(defun mkcl-type->c-type (type-keyword)
"Convert a CFFI type keyword to an valid C type keyword."
(or (third (find type-keyword +translation-table+ :key #'second))
(error "~S is not a valid CFFI type" type-keyword)))
(defun %foreign-type-size (type-keyword)
"Return the size in bytes of a foreign type."
(nth-value 0 (ffi:size-of-foreign-type
(cffi-type->mkcl-type type-keyword))))
(defun %foreign-type-alignment (type-keyword)
"Return the alignment in bytes of a foreign type."
(nth-value 1 (ffi:size-of-foreign-type
(cffi-type->mkcl-type type-keyword))))
;;;# Calling Foreign Functions
#|
(defconstant +mkcl-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")
|#
(defun produce-function-pointer-call (pointer types values return-type)
#|
(if (stringp pointer)
(produce-function-pointer-call
`(%foreign-symbol-pointer ,pointer nil) types values return-type)
`(ffi:c-inline
,(list* pointer values)
,(list* :pointer-void types) ,return-type
,(with-output-to-string (s)
(let ((types (mapcar #'mkcl-type->c-type types)))
;; On AMD64, the following code only works with the extra
;; argument ",...". If this is not present, functions
;; like sprintf do not work
(format s "((~A (*)(~@[~{~A,~}...~]))(#0))(~A)"
(mkcl-type->c-type return-type) types
(subseq +mkcl-inline-codes+ 3
(max 3 (+ 2 (* (length values) 3)))))))
:one-liner t :side-effects t))
|#
;; The version here below is definitely not as efficient as the one above
;; but it has the great vertue of working in all cases, (contrary to the
;; silent and unsafe limitations of the one above). JCB
;; I should re-optimize this one day, when I get time... JCB
(progn
(when (stringp pointer)
(setf pointer `(%foreign-symbol-pointer ,pointer nil)))
`(si:call-cfun ,pointer ,return-type (list ,@types) (list ,@values))))
(defun foreign-funcall-parse-args (args)
"Return three values, lists of arg types, values, and result type."
(let ((return-type :void))
(loop for (type arg) on args by #'cddr
if arg collect (cffi-type->mkcl-type type) into types
and collect arg into values
else do (setf return-type (cffi-type->mkcl-type type))
finally (return (values types values return-type)))))
(defmacro %foreign-funcall (name args &key library convention)
"Call a foreign function."
(declare (ignore library convention))
(multiple-value-bind (types values return-type)
(foreign-funcall-parse-args args)
(produce-function-pointer-call name types values return-type)))
(defmacro %foreign-funcall-pointer (ptr args &key convention)
"Funcall a pointer to a foreign function."
(declare (ignore convention))
(multiple-value-bind (types values return-type)
(foreign-funcall-parse-args args)
(produce-function-pointer-call ptr types values return-type)))
;;;# Foreign Libraries
(defun %load-foreign-library (name path)
"Load a foreign library."
(declare (ignore name))
(handler-case (si:load-foreign-module path)
(file-error ()
(error "file error while trying to load `~A'" path))))
(defun %close-foreign-library (handle)
;;(declare (ignore handle))
;;(error "%CLOSE-FOREIGN-LIBRARY unimplemented.")
(si:unload-foreign-module handle))
(defun native-namestring (pathname)
(namestring pathname))
;;;# Callbacks
;;; Create a package to contain the symbols for callback functions.
;;; We want to redefine callbacks with the same symbol so the internal
;;; data structures are reused.
(defpackage #:cffi-callbacks
(:use))
(defvar *callbacks* (make-hash-table))
;;; Intern a symbol in the CFFI-CALLBACKS package used to name the
;;; internal callback for NAME.
(eval-when (:compile-toplevel :load-toplevel :execute)
(defun intern-callback (name)
(intern (format nil "~A::~A"
(if-let (package (symbol-package name))
(package-name package)
"#")
(symbol-name name))
'#:cffi-callbacks)))
(defmacro %defcallback (name rettype arg-names arg-types body
&key convention)
(declare (ignore convention))
(let ((cb-name (intern-callback name)))
`(progn
(ffi:defcallback (,cb-name :cdecl)
,(cffi-type->mkcl-type rettype)
,(mapcar #'list arg-names
(mapcar #'cffi-type->mkcl-type arg-types))
;;(block ,cb-name ,@body)
(block ,cb-name ,body))
(setf (gethash ',name *callbacks*) ',cb-name))))
(defun %callback (name)
(multiple-value-bind (symbol winp)
(gethash name *callbacks*)
(unless winp
(error "Undefined callback: ~S" name))
(ffi:callback symbol)))
;;;# Foreign Globals
(defun %foreign-symbol-pointer (name library)
"Returns a pointer to a foreign symbol NAME."
(declare (ignore library))
(values (ignore-errors (si:find-foreign-symbol name :default :pointer-void 0))))

View file

@ -0,0 +1,314 @@
;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
;;;
;;; cffi-openmcl.lisp --- CFFI-SYS implementation for OpenMCL.
;;;
;;; Copyright (C) 2005-2006, James Bielman <jamesjb@jamesjb.com>
;;;
;;; 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 #:ccl)
(:import-from #:alexandria #:once-only #:if-let)
(:export
#:canonicalize-symbol-name-case
#:foreign-pointer
#:pointerp ; ccl:pointerp
#:pointer-eq
#:%foreign-alloc
#:foreign-free
#:with-foreign-pointer
#:null-pointer
#:null-pointer-p
#:inc-pointer
#:make-pointer
#:pointer-address
#:%mem-ref
#:%mem-set
#:%foreign-funcall
#:%foreign-funcall-pointer
#:%foreign-type-alignment
#:%foreign-type-size
#:%load-foreign-library
#:%close-foreign-library
#:native-namestring
#:make-shareable-byte-vector
#:with-pointer-to-vector-data
#:%foreign-symbol-pointer
#:%defcallback
#:%callback))
(in-package #:cffi-sys)
;;;# Misfeatures
(pushnew 'flat-namespace *features*)
;;;# Symbol Case
(defun canonicalize-symbol-name-case (name)
(declare (string name))
(string-upcase name))
;;;# Allocation
;;;
;;; Functions and macros for allocating foreign memory on the stack
;;; and on the heap. The main CFFI package defines macros that wrap
;;; FOREIGN-ALLOC and FOREIGN-FREE in UNWIND-PROTECT for the common
;;; usage when the memory has dynamic extent.
(defun %foreign-alloc (size)
"Allocate SIZE bytes on the heap and return a pointer."
(ccl::malloc size))
(defun foreign-free (ptr)
"Free a PTR allocated by FOREIGN-ALLOC."
;; TODO: Should we make this a dead macptr?
(ccl::free ptr))
(defmacro with-foreign-pointer ((var size &optional size-var) &body body)
"Bind VAR to SIZE bytes of foreign memory during BODY. The
pointer in VAR is invalid beyond the dynamic extent of BODY, 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 ((,size-var ,size))
(%stack-block ((,var ,size-var))
,@body)))
;;;# Misc. Pointer Operations
(deftype foreign-pointer ()
'ccl:macptr)
(defun null-pointer ()
"Construct and return a null pointer."
(ccl:%null-ptr))
(defun null-pointer-p (ptr)
"Return true if PTR is a null pointer."
(ccl:%null-ptr-p ptr))
(defun inc-pointer (ptr offset)
"Return a pointer OFFSET bytes past PTR."
(ccl:%inc-ptr ptr offset))
(defun pointer-eq (ptr1 ptr2)
"Return true if PTR1 and PTR2 point to the same address."
(ccl:%ptr-eql ptr1 ptr2))
(defun make-pointer (address)
"Return a pointer pointing to ADDRESS."
(ccl:%int-to-ptr address))
(defun pointer-address (ptr)
"Return the address pointed to by PTR."
(ccl:%ptr-to-int ptr))
;;;# 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.
(defun make-shareable-byte-vector (size)
"Create a Lisp vector of SIZE bytes that can passed to
WITH-POINTER-TO-VECTOR-DATA."
(make-array size :element-type '(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."
`(ccl:with-pointer-to-ivector (,ptr-var ,vector)
,@body))
;;;# Dereferencing
;;; Define the %MEM-REF and %MEM-SET functions, as well as compiler
;;; macros that optimize the case where the type keyword is constant
;;; at compile-time.
(defmacro define-mem-accessors (&body pairs)
`(progn
(defun %mem-ref (ptr type &optional (offset 0))
(ecase type
,@(loop for (keyword fn) in pairs
collect `(,keyword (,fn ptr offset)))))
(defun %mem-set (value ptr type &optional (offset 0))
(ecase type
,@(loop for (keyword fn) in pairs
collect `(,keyword (setf (,fn ptr offset) value)))))
(define-compiler-macro %mem-ref
(&whole form ptr type &optional (offset 0))
(if (constantp type)
(ecase (eval type)
,@(loop for (keyword fn) in pairs
collect `(,keyword `(,',fn ,ptr ,offset))))
form))
(define-compiler-macro %mem-set
(&whole form value ptr type &optional (offset 0))
(if (constantp type)
(once-only (value)
(ecase (eval type)
,@(loop for (keyword fn) in pairs
collect `(,keyword `(setf (,',fn ,ptr ,offset)
,value)))))
form))))
(define-mem-accessors
(:char %get-signed-byte)
(:unsigned-char %get-unsigned-byte)
(:short %get-signed-word)
(:unsigned-short %get-unsigned-word)
(:int %get-signed-long)
(:unsigned-int %get-unsigned-long)
#+(or 32-bit-target windows-target) (:long %get-signed-long)
#+(and (not windows-target) 64-bit-target) (:long ccl::%%get-signed-longlong)
#+(or 32-bit-target windows-target) (:unsigned-long %get-unsigned-long)
#+(and 64-bit-target (not windows-target)) (:unsigned-long ccl::%%get-unsigned-longlong)
(:long-long ccl::%get-signed-long-long)
(:unsigned-long-long ccl::%get-unsigned-long-long)
(:float %get-single-float)
(:double %get-double-float)
(:pointer %get-ptr))
;;;# Calling Foreign Functions
(defun convert-foreign-type (type-keyword)
"Convert a CFFI type keyword to an OpenMCL type."
(ecase type-keyword
(:char :signed-byte)
(:unsigned-char :unsigned-byte)
(:short :signed-short)
(:unsigned-short :unsigned-short)
(:int :signed-int)
(:unsigned-int :unsigned-int)
(:long :signed-long)
(:unsigned-long :unsigned-long)
(:long-long :signed-doubleword)
(:unsigned-long-long :unsigned-doubleword)
(:float :single-float)
(:double :double-float)
(:pointer :address)
(:void :void)))
(defun %foreign-type-size (type-keyword)
"Return the size in bytes of a foreign type."
(/ (ccl::foreign-type-bits
(ccl::parse-foreign-type
(convert-foreign-type type-keyword)))
8))
;; There be dragons here. See the following thread for details:
;; http://clozure.com/pipermail/openmcl-devel/2005-June/002777.html
(defun %foreign-type-alignment (type-keyword)
"Return the alignment in bytes of a foreign type."
(/ (ccl::foreign-type-alignment
(ccl::parse-foreign-type
(convert-foreign-type type-keyword))) 8))
(defun convert-foreign-funcall-types (args)
"Convert foreign types for a call to FOREIGN-FUNCALL."
(loop for (type arg) on args by #'cddr
collect (convert-foreign-type type)
if arg collect arg))
(defun convert-external-name (name)
"Add an underscore to NAME if necessary for the ABI."
#+darwin (concatenate 'string "_" name)
#-darwin name)
(defmacro %foreign-funcall (function-name args &key library convention)
"Perform a foreign function call, document it more later."
(declare (ignore library convention))
`(external-call
,(convert-external-name function-name)
,@(convert-foreign-funcall-types args)))
(defmacro %foreign-funcall-pointer (ptr args &key convention)
(declare (ignore convention))
`(ff-call ,ptr ,@(convert-foreign-funcall-types args)))
;;;# Callbacks
;;; The *CALLBACKS* hash table maps CFFI callback names to OpenMCL "macptr"
;;; entry points. It is safe to store the pointers directly because
;;; OpenMCL will update the address of these pointers when a saved image
;;; is loaded (see CCL::RESTORE-PASCAL-FUNCTIONS).
(defvar *callbacks* (make-hash-table))
;;; Create a package to contain the symbols for callback functions. We
;;; want to redefine callbacks with the same symbol so the internal data
;;; structures are reused.
(defpackage #:cffi-callbacks
(:use))
;;; Intern a symbol in the CFFI-CALLBACKS package used to name the internal
;;; callback for NAME.
(defun intern-callback (name)
(intern (format nil "~A::~A"
(if-let (package (symbol-package name))
(package-name package)
"#")
(symbol-name name))
'#:cffi-callbacks))
(defmacro %defcallback (name rettype arg-names arg-types body
&key convention)
(let ((cb-name (intern-callback name)))
`(progn
(defcallback ,cb-name
(,@(when (eq convention :stdcall)
'(:discard-stack-args))
,@(mapcan (lambda (sym type)
(list (convert-foreign-type type) sym))
arg-names arg-types)
,(convert-foreign-type rettype))
,body)
(setf (gethash ',name *callbacks*) (symbol-value ',cb-name)))))
(defun %callback (name)
(or (gethash name *callbacks*)
(error "Undefined callback: ~S" name)))
;;;# Loading Foreign Libraries
(defun %load-foreign-library (name path)
"Load the foreign library NAME."
(declare (ignore name))
(open-shared-library path))
(defun %close-foreign-library (name)
"Close the foreign library NAME."
;; C-S-L sometimes ends in an endless loop
;; with :COMPLETELY T
(close-shared-library name :completely nil))
(defun native-namestring (pathname)
(ccl::native-translated-namestring pathname))
;;;# Foreign Globals
(defun %foreign-symbol-pointer (name library)
"Returns a pointer to a foreign symbol NAME."
(declare (ignore library))
(foreign-symbol-address (convert-external-name name)))

View file

@ -0,0 +1,408 @@
;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
;;;
;;; cffi-sbcl.lisp --- CFFI-SYS implementation for SBCL.
;;;
;;; Copyright (C) 2005-2006, James Bielman <jamesjb@jamesjb.com>
;;;
;;; 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 #:sb-alien)
(:import-from #:alexandria
#:once-only #:with-unique-names #:when-let #:removef)
(: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)
;;;# Misfeatures
(pushnew 'flat-namespace *features*)
;;;# Symbol Case
(declaim (inline canonicalize-symbol-name-case))
(defun canonicalize-symbol-name-case (name)
(declare (string name))
(string-upcase name))
;;;# Basic Pointer Operations
(deftype foreign-pointer ()
'sb-sys:system-area-pointer)
(declaim (inline pointerp))
(defun pointerp (ptr)
"Return true if PTR is a foreign pointer."
(sb-sys:system-area-pointer-p ptr))
(declaim (inline pointer-eq))
(defun pointer-eq (ptr1 ptr2)
"Return true if PTR1 and PTR2 point to the same address."
(declare (type system-area-pointer ptr1 ptr2))
(sb-sys:sap= ptr1 ptr2))
(declaim (inline null-pointer))
(defun null-pointer ()
"Construct and return a null pointer."
(sb-sys:int-sap 0))
(declaim (inline null-pointer-p))
(defun null-pointer-p (ptr)
"Return true if PTR is a null pointer."
(declare (type system-area-pointer ptr))
(zerop (sb-sys:sap-int ptr)))
(declaim (inline inc-pointer))
(defun inc-pointer (ptr offset)
"Return a pointer pointing OFFSET bytes past PTR."
(declare (type system-area-pointer ptr)
(type integer offset))
(sb-sys:sap+ ptr offset))
(declaim (inline make-pointer))
(defun make-pointer (address)
"Return a pointer pointing to ADDRESS."
;; (declare (type (unsigned-byte 32) address))
(sb-sys:int-sap address))
(declaim (inline pointer-address))
(defun pointer-address (ptr)
"Return the address pointed to by PTR."
(declare (type system-area-pointer ptr))
(sb-sys:sap-int ptr))
;;;# Allocation
;;;
;;; Functions and macros for allocating foreign memory on the stack
;;; and on the heap. The main CFFI package defines macros that wrap
;;; FOREIGN-ALLOC and FOREIGN-FREE in UNWIND-PROTECT for the common usage
;;; when the memory has dynamic extent.
(declaim (inline %foreign-alloc))
(defun %foreign-alloc (size)
"Allocate SIZE bytes on the heap and return a pointer."
;; (declare (type (unsigned-byte 32) size))
(alien-sap (make-alien (unsigned 8) size)))
(declaim (inline foreign-free))
(defun foreign-free (ptr)
"Free a PTR allocated by FOREIGN-ALLOC."
(declare (type system-area-pointer ptr)
(optimize speed))
(free-alien (sap-alien ptr (* (unsigned 8)))))
(defmacro with-foreign-pointer ((var size &optional size-var) &body body)
"Bind VAR to SIZE bytes of foreign memory during BODY. The
pointer in VAR is invalid beyond the dynamic extent of BODY, 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")))
;; If the size is constant we can stack-allocate.
(if (constantp size)
(let ((alien-var (gensym "ALIEN")))
`(with-alien ((,alien-var (array (unsigned 8) ,(eval size))))
(let ((,size-var ,(eval size))
(,var (alien-sap ,alien-var)))
(declare (ignorable ,size-var))
,@body)))
`(let* ((,size-var ,size)
(,var (%foreign-alloc ,size-var)))
(unwind-protect
(progn ,@body)
(foreign-free ,var)))))
;;;# 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."
; (declare (type sb-int:index size))
(make-array size :element-type '(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."
(let ((vector-var (gensym "VECTOR")))
`(let ((,vector-var ,vector))
(declare (type (sb-kernel:simple-unboxed-array (*)) ,vector-var))
(sb-sys:with-pinned-objects (,vector-var)
(let ((,ptr-var (sb-sys:vector-sap ,vector-var)))
,@body)))))
;;;# Dereferencing
;;; Define the %MEM-REF and %MEM-SET functions, as well as compiler
;;; macros that optimize the case where the type keyword is constant
;;; at compile-time.
(defmacro define-mem-accessors (&body pairs)
`(progn
(defun %mem-ref (ptr type &optional (offset 0))
(ecase type
,@(loop for (keyword fn) in pairs
collect `(,keyword (,fn ptr offset)))))
(defun %mem-set (value ptr type &optional (offset 0))
(ecase type
,@(loop for (keyword fn) in pairs
collect `(,keyword (setf (,fn ptr offset) value)))))
(define-compiler-macro %mem-ref
(&whole form ptr type &optional (offset 0))
(if (constantp type)
(ecase (eval type)
,@(loop for (keyword fn) in pairs
collect `(,keyword `(,',fn ,ptr ,offset))))
form))
(define-compiler-macro %mem-set
(&whole form value ptr type &optional (offset 0))
(if (constantp type)
(once-only (value)
(ecase (eval type)
,@(loop for (keyword fn) in pairs
collect `(,keyword `(setf (,',fn ,ptr ,offset)
,value)))))
form))))
;;; Look up alien type information and build both define-mem-accessors form
;;; and convert-foreign-type function definition.
(defmacro define-type-mapping (accessor-table alien-table)
(let* ((accessible-types
(remove 'void alien-table :key #'second))
(size-and-signedp-forms
(mapcar (lambda (name)
(list (eval `(alien-size ,(second name)))
(typep -1 `(alien ,(second name)))))
accessible-types)))
`(progn
(define-mem-accessors
,@(loop for (cffi-keyword alien-type fixed-accessor)
in accessible-types
and (alien-size signedp)
in size-and-signedp-forms
for (signed-ref unsigned-ref)
= (cdr (assoc alien-size accessor-table))
collect
`(,cffi-keyword
,(or fixed-accessor
(if signedp signed-ref unsigned-ref)
(error "No accessor found for ~S"
alien-type)))))
(defun convert-foreign-type (type-keyword)
(ecase type-keyword
,@(loop for (cffi-keyword alien-type) in alien-table
collect `(,cffi-keyword (quote ,alien-type))))))))
(define-type-mapping
((8 sb-sys:signed-sap-ref-8 sb-sys:sap-ref-8)
(16 sb-sys:signed-sap-ref-16 sb-sys:sap-ref-16)
(32 sb-sys:signed-sap-ref-32 sb-sys:sap-ref-32)
(64 sb-sys:signed-sap-ref-64 sb-sys:sap-ref-64))
((:char char)
(:unsigned-char unsigned-char)
(:short short)
(:unsigned-short unsigned-short)
(:int int)
(:unsigned-int unsigned-int)
(:long long)
(:unsigned-long unsigned-long)
(:long-long long-long)
(:unsigned-long-long unsigned-long-long)
(:float single-float
sb-sys:sap-ref-single)
(:double double-float
sb-sys:sap-ref-double)
(:pointer system-area-pointer
sb-sys:sap-ref-sap)
(:void void)))
;;;# Calling Foreign Functions
(defun %foreign-type-size (type-keyword)
"Return the size in bytes of a foreign type."
(/ (sb-alien-internals:alien-type-bits
(sb-alien-internals:parse-alien-type
(convert-foreign-type type-keyword) nil)) 8))
(defun %foreign-type-alignment (type-keyword)
"Return the alignment in bytes of a foreign type."
#+(and darwin ppc (not ppc64))
(case type-keyword
((:double :long-long :unsigned-long-long)
(return-from %foreign-type-alignment 8)))
;; No override necessary for other types...
(/ (sb-alien-internals:alien-type-alignment
(sb-alien-internals:parse-alien-type
(convert-foreign-type type-keyword) nil)) 8))
(defun foreign-funcall-type-and-args (args)
"Return an SB-ALIEN function type for ARGS."
(let ((return-type 'void))
(loop for (type arg) on args by #'cddr
if arg collect (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)))))
(defmacro %%foreign-funcall (name types fargs rettype)
"Internal guts of %FOREIGN-FUNCALL."
`(alien-funcall
(extern-alien ,name (function ,rettype ,@types))
,@fargs))
(defmacro %foreign-funcall (name args &key library convention)
"Perform a foreign function call, document it more later."
(declare (ignore library convention))
(multiple-value-bind (types fargs rettype)
(foreign-funcall-type-and-args args)
`(%%foreign-funcall ,name ,types ,fargs ,rettype)))
(defmacro %foreign-funcall-pointer (ptr args &key convention)
"Funcall a pointer to a foreign function."
(declare (ignore convention))
(multiple-value-bind (types fargs rettype)
(foreign-funcall-type-and-args args)
(with-unique-names (function)
`(with-alien ((,function (* (function ,rettype ,@types)) ,ptr))
(alien-funcall ,function ,@fargs)))))
;;;# Callbacks
;;; The *CALLBACKS* hash table contains a direct mapping of CFFI
;;; callback names to SYSTEM-AREA-POINTERs obtained by ALIEN-LAMBDA.
;;; SBCL will maintain the addresses of the callbacks across saved
;;; images, so it is safe to store the pointers directly.
(defvar *callbacks* (make-hash-table))
(defmacro %defcallback (name rettype arg-names arg-types body
&key convention)
(check-type convention (member :stdcall :cdecl))
`(setf (gethash ',name *callbacks*)
(alien-sap
(sb-alien::alien-lambda
#+alien-callback-conventions
(,convention ,(convert-foreign-type rettype))
#-alien-callback-conventions
,(convert-foreign-type rettype)
,(mapcar (lambda (sym type)
(list sym (convert-foreign-type type)))
arg-names arg-types)
,body))))
(defun %callback (name)
(or (gethash name *callbacks*)
(error "Undefined callback: ~S" name)))
;;;# Loading and Closing Foreign Libraries
#+darwin
(defun call-within-initial-thread (fn &rest args)
(let (result
error
(sem (sb-thread:make-semaphore)))
(sb-thread:interrupt-thread
;; KLUDGE: find a better way to get the initial thread.
(car (last (sb-thread:list-all-threads)))
(lambda ()
(multiple-value-setq (result error)
(ignore-errors (apply fn args)))
(sb-thread:signal-semaphore sem)))
(sb-thread:wait-on-semaphore sem)
(if error
(signal error)
result)))
(declaim (inline %load-foreign-library))
(defun %load-foreign-library (name path)
"Load a foreign library."
(declare (ignore name))
;; As of MacOS X 10.6.6, loading things like CoreFoundation from a
;; thread other than the initial one results in a crash.
#+darwin (call-within-initial-thread 'load-shared-object path)
#-darwin (load-shared-object path))
;;; SBCL 1.0.21.15 renamed SB-ALIEN::SHARED-OBJECT-FILE but introduced
;;; SB-ALIEN:UNLOAD-SHARED-OBJECT which we can use instead.
(eval-when (:compile-toplevel :load-toplevel :execute)
(defun unload-shared-object-present-p ()
(multiple-value-bind (foundp kind)
(find-symbol "UNLOAD-SHARED-OBJECT" "SB-ALIEN")
(if (and foundp (eq kind :external))
'(:and)
'(:or)))))
(defun %close-foreign-library (handle)
"Closes a foreign library."
#+#.(cffi-sys::unload-shared-object-present-p)
(sb-alien:unload-shared-object handle)
#-#.(cffi-sys::unload-shared-object-present-p)
(sb-thread:with-mutex (sb-alien::*shared-objects-lock*)
(let ((obj (find (sb-ext:native-namestring handle)
sb-alien::*shared-objects*
:key #'sb-alien::shared-object-file
:test #'string=)))
(when obj
(sb-alien::dlclose-or-lose obj)
(removef sb-alien::*shared-objects* obj)
#+(and linkage-table (not win32))
(sb-alien::update-linkage-table)))))
(defun native-namestring (pathname)
(sb-ext:native-namestring pathname))
;;;# Foreign Globals
(defun %foreign-symbol-pointer (name library)
"Returns a pointer to a foreign symbol NAME."
(declare (ignore library))
(when-let (address (sb-sys:find-foreign-symbol-address name))
(sb-sys:int-sap address)))

View file

@ -0,0 +1,322 @@
;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
;;;
;;; cffi-scl.lisp --- CFFI-SYS implementation for the Scieneer Common Lisp.
;;;
;;; Copyright (C) 2005-2006, James Bielman <jamesjb@jamesjb.com>
;;; Copyright (C) 2006-2007, Scieneer Pty Ltd.
;;;
;;; 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 #:alien #:c-call)
(:import-from #:alexandria #:once-only #:with-unique-names)
(: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)
;;;# Mis-features
(pushnew 'flat-namespace *features*)
;;;# Symbol Case
(defun canonicalize-symbol-name-case (name)
(declare (string name))
(if (eq ext:*case-mode* :upper)
(string-upcase name)
(string-downcase name)))
;;;# Basic Pointer Operations
(deftype foreign-pointer ()
'sys:system-area-pointer)
(declaim (inline pointerp))
(defun pointerp (ptr)
"Return true if 'ptr is a foreign pointer."
(sys:system-area-pointer-p ptr))
(declaim (inline pointer-eq))
(defun pointer-eq (ptr1 ptr2)
"Return true if 'ptr1 and 'ptr2 point to the same address."
(sys:sap= ptr1 ptr2))
(declaim (inline null-pointer))
(defun null-pointer ()
"Construct and return a null pointer."
(sys:int-sap 0))
(declaim (inline null-pointer-p))
(defun null-pointer-p (ptr)
"Return true if 'ptr is a null pointer."
(zerop (sys:sap-int ptr)))
(declaim (inline inc-pointer))
(defun inc-pointer (ptr offset)
"Return a pointer pointing 'offset bytes past 'ptr."
(sys:sap+ ptr offset))
(declaim (inline make-pointer))
(defun make-pointer (address)
"Return a pointer pointing to 'address."
(sys:int-sap address))
(declaim (inline pointer-address))
(defun pointer-address (ptr)
"Return the address pointed to by 'ptr."
(sys:sap-int ptr))
(defmacro with-foreign-pointer ((var size &optional size-var) &body body)
"Bind 'var to 'size bytes of foreign memory during 'body. The
pointer in 'var is invalid beyond the dynamic extent of 'body, 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 (symbol-name '#:size))))
;; If the size is constant we can stack-allocate.
(cond ((constantp size)
(let ((alien-var (gensym (symbol-name '#:alien))))
`(with-alien ((,alien-var (array (unsigned 8) ,(eval size))))
(let ((,size-var ,size)
(,var (alien-sap ,alien-var)))
(declare (ignorable ,size-var))
,@body))))
(t
`(let ((,size-var ,size))
(alien:with-bytes (,var ,size-var)
,@body)))))
;;;# Allocation
;;;
;;; Functions and macros for allocating foreign memory on the stack and on the
;;; heap. The main CFFI package defines macros that wrap 'foreign-alloc and
;;; 'foreign-free in 'unwind-protect for the common usage when the memory has
;;; dynamic extent.
(defun %foreign-alloc (size)
"Allocate 'size bytes on the heap and return a pointer."
(declare (type (unsigned-byte #-64bit 32 #+64bit 64) size))
(alien-funcall (extern-alien "malloc"
(function system-area-pointer unsigned))
size))
(defun foreign-free (ptr)
"Free a 'ptr allocated by 'foreign-alloc."
(declare (type system-area-pointer ptr))
(alien-funcall (extern-alien "free"
(function (values) system-area-pointer))
ptr))
;;;# Shareable Vectors
(defun make-shareable-byte-vector (size)
"Create a Lisp vector of 'size bytes that can passed to
'with-pointer-to-vector-data."
(make-array size :element-type '(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."
(let ((vector-var (gensym (symbol-name '#:vector))))
`(let ((,vector-var ,vector))
(ext:with-pinned-object (,vector-var)
(let ((,ptr-var (sys:vector-sap ,vector-var)))
,@body)))))
;;;# Dereferencing
;;; Define the %MEM-REF and %MEM-SET functions, as well as compiler
;;; macros that optimize the case where the type keyword is constant
;;; at compile-time.
(defmacro define-mem-accessors (&body pairs)
`(progn
(defun %mem-ref (ptr type &optional (offset 0))
(ecase type
,@(loop for (keyword fn) in pairs
collect `(,keyword (,fn ptr offset)))))
(defun %mem-set (value ptr type &optional (offset 0))
(ecase type
,@(loop for (keyword fn) in pairs
collect `(,keyword (setf (,fn ptr offset) value)))))
(define-compiler-macro %mem-ref
(&whole form ptr type &optional (offset 0))
(if (constantp type)
(ecase (eval type)
,@(loop for (keyword fn) in pairs
collect `(,keyword `(,',fn ,ptr ,offset))))
form))
(define-compiler-macro %mem-set
(&whole form value ptr type &optional (offset 0))
(if (constantp type)
(once-only (value)
(ecase (eval type)
,@(loop for (keyword fn) in pairs
collect `(,keyword `(setf (,',fn ,ptr ,offset)
,value)))))
form))))
(define-mem-accessors
(:char sys:signed-sap-ref-8)
(:unsigned-char sys:sap-ref-8)
(:short sys:signed-sap-ref-16)
(:unsigned-short sys:sap-ref-16)
(:int sys:signed-sap-ref-32)
(:unsigned-int sys:sap-ref-32)
(:long #-64bit sys:signed-sap-ref-32 #+64bit sys:signed-sap-ref-64)
(:unsigned-long #-64bit sys:sap-ref-32 #+64bit sys:sap-ref-64)
(:long-long sys:signed-sap-ref-64)
(:unsigned-long-long sys:sap-ref-64)
(:float sys:sap-ref-single)
(:double sys:sap-ref-double)
#+long-float (:long-double sys:sap-ref-long)
(:pointer sys:sap-ref-sap))
;;;# Calling Foreign Functions
(defun convert-foreign-type (type-keyword)
"Convert a CFFI type keyword to an ALIEN type."
(ecase type-keyword
(:char 'char)
(:unsigned-char 'unsigned-char)
(:short 'short)
(:unsigned-short 'unsigned-short)
(:int 'int)
(:unsigned-int 'unsigned-int)
(:long 'long)
(:unsigned-long 'unsigned-long)
(:long-long '(signed 64))
(:unsigned-long-long '(unsigned 64))
(:float 'single-float)
(:double 'double-float)
#+long-float
(:long-double 'long-float)
(:pointer 'system-area-pointer)
(:void 'void)))
(defun %foreign-type-size (type-keyword)
"Return the size in bytes of a foreign type."
(values (truncate (alien-internals:alien-type-bits
(alien-internals:parse-alien-type
(convert-foreign-type type-keyword)))
8)))
(defun %foreign-type-alignment (type-keyword)
"Return the alignment in bytes of a foreign type."
(values (truncate (alien-internals:alien-type-alignment
(alien-internals:parse-alien-type
(convert-foreign-type type-keyword)))
8)))
(defun foreign-funcall-type-and-args (args)
"Return an 'alien function type for 'args."
(let ((return-type nil))
(loop for (type arg) on args by #'cddr
if arg collect (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)))))
(defmacro %%foreign-funcall (name types fargs rettype)
"Internal guts of '%foreign-funcall."
`(alien-funcall (extern-alien ,name (function ,rettype ,@types))
,@fargs))
(defmacro %foreign-funcall (name args &key library convention)
"Perform a foreign function call, document it more later."
(declare (ignore library convention))
(multiple-value-bind (types fargs rettype)
(foreign-funcall-type-and-args args)
`(%%foreign-funcall ,name ,types ,fargs ,rettype)))
(defmacro %foreign-funcall-pointer (ptr args &key convention)
"Funcall a pointer to a foreign function."
(declare (ignore convention))
(multiple-value-bind (types fargs rettype)
(foreign-funcall-type-and-args args)
(with-unique-names (function)
`(with-alien ((,function (* (function ,rettype ,@types)) ,ptr))
(alien-funcall ,function ,@fargs)))))
;;; Callbacks
(defmacro %defcallback (name rettype arg-names arg-types body
&key convention)
(declare (ignore convention))
`(alien:defcallback ,name
(,(convert-foreign-type rettype)
,@(mapcar (lambda (sym type)
(list sym (convert-foreign-type type)))
arg-names arg-types))
,body))
(declaim (inline %callback))
(defun %callback (name)
(alien:callback-sap name))
;;;# Loading and Closing Foreign Libraries
(defun %load-foreign-library (name path)
"Load the foreign library 'name."
(declare (ignore name))
(ext:load-dynamic-object path))
(defun %close-foreign-library (name)
"Closes the foreign library 'name."
(ext:close-dynamic-object name))
(defun native-namestring (pathname)
(ext:unix-namestring pathname nil))
;;;# Foreign Globals
(defun %foreign-symbol-pointer (name library)
"Returns a pointer to a foreign symbol 'name."
(declare (ignore library))
(let ((sap (sys:foreign-symbol-address name)))
(if (zerop (sys:sap-int sap)) nil sap)))

View file

@ -0,0 +1,713 @@
;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
;;;
;;; early-types.lisp --- Low-level foreign type operations.
;;;
;;; Copyright (C) 2005-2006, James Bielman <jamesjb@jamesjb.com>
;;; Copyright (C) 2005-2007, Luis Oliveira <loliveira@common-lisp.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.
;;;
;;;# Early Type Definitions
;;;
;;; This module contains basic operations on foreign types. These
;;; definitions are in a separate file because they may be used in
;;; compiler macros defined later on.
(in-package #:cffi)
;;;# Foreign Types
;;;
;;; Type specifications are of the form (type {args}*). The type
;;; parser can specify how its arguments should look like through a
;;; lambda list.
;;;
;;; "type" is a shortcut for "(type)", ie, no args were specified.
;;;
;;; Examples of such types: boolean, (boolean), (boolean :int) If the
;;; boolean type parser specifies the lambda list: &optional
;;; (base-type :int), then all of the above three type specs would be
;;; parsed to an identical type.
;;;
;;; Type parsers, defined with DEFINE-PARSE-METHOD should return a
;;; subtype of the foreign-type class.
(defvar *type-parsers* (make-hash-table :test 'equal)
"Hash table of defined type parsers.")
(define-condition cffi-error (error)
())
(define-condition foreign-type-error (cffi-error)
((type-name :initarg :type-name
:initform (error "Must specify TYPE-NAME.")
:accessor foreign-type-error/type-name)
(namespace :initarg :namespace
:initform :default
:accessor foreign-type-error/namespace)))
(defun foreign-type-error/compound-name (e)
(let ((name (foreign-type-error/type-name e))
(namespace (foreign-type-error/namespace e)))
(if (eq namespace :default)
name
`(,namespace ,name))))
(define-condition simple-foreign-type-error (simple-error foreign-type-error)
())
(defun simple-foreign-type-error (type-name namespace format-control &rest format-arguments)
(error 'simple-foreign-type-error
:type-name type-name :namespace namespace
:format-control format-control :format-arguments format-arguments))
(define-condition undefined-foreign-type-error (foreign-type-error)
()
(:report (lambda (e stream)
(format stream "Unknown CFFI type ~S" (foreign-type-error/compound-name e)))))
(defun undefined-foreign-type-error (type-name &optional (namespace :default))
(error 'undefined-foreign-type-error :type-name type-name :namespace namespace))
;; TODO this is not according to the C namespace rules,
;; see bug: https://bugs.launchpad.net/cffi/+bug/1527947
(deftype c-namespace-name ()
'(member :default :struct :union))
;; for C namespaces read: https://stackoverflow.com/questions/12579142/type-namespace-in-c
;; (section 6.2.3 Name spaces of identifiers)
;; NOTE: :struct is probably an unfortunate name for the tagged (?) namespace
(defun find-type-parser (symbol &optional (namespace :default))
"Return the type parser for SYMBOL. NAMESPACE is either :DEFAULT (for
variables, functions, and typedefs) or :STRUCT (for structs, unions, and enums)."
(check-type symbol (and symbol (not null)))
(check-type namespace c-namespace-name)
(or (gethash (cons namespace symbol) *type-parsers*)
(undefined-foreign-type-error symbol namespace)))
(defun (setf find-type-parser) (func symbol &optional (namespace :default))
"Set the type parser for SYMBOL."
(check-type symbol (and symbol (not null)))
(check-type namespace c-namespace-name)
;; TODO Shall we signal a redefinition warning here?
(setf (gethash (cons namespace symbol) *type-parsers*) func))
(defun undefine-foreign-type (symbol &optional (namespace :default))
(remhash (cons namespace symbol) *type-parsers*)
(values))
;;; Using a generic function would have been nicer but generates lots
;;; of style warnings in SBCL. (Silly reason, yes.)
(defmacro define-parse-method (name lambda-list &body body)
"Define a type parser on NAME and lists whose CAR is NAME."
(discard-docstring body)
(warn-if-kw-or-belongs-to-cl name)
`(eval-when (:compile-toplevel :load-toplevel :execute)
(setf (find-type-parser ',name)
(lambda ,lambda-list ,@body))
',name))
;;; Utility function for the simple case where the type takes no
;;; arguments.
(defun notice-foreign-type (name type &optional (namespace :default))
(setf (find-type-parser name namespace) (lambda () type))
name)
;;;# Generic Functions on Types
(defgeneric canonicalize (foreign-type)
(:documentation
"Return the most primitive foreign type for FOREIGN-TYPE, either a built-in
type--a keyword--or a struct/union type--a list of the form (:STRUCT/:UNION name).
Signals an error if FOREIGN-TYPE is undefined."))
(defgeneric aggregatep (foreign-type)
(:documentation
"Return true if FOREIGN-TYPE is an aggregate type."))
(defgeneric foreign-type-alignment (foreign-type)
(:documentation
"Return the structure alignment in bytes of a foreign type."))
(defgeneric foreign-type-size (foreign-type)
(:documentation
"Return the size in bytes of a foreign type."))
(defgeneric unparse-type (foreign-type)
(:documentation
"Unparse FOREIGN-TYPE to a type specification (symbol or list)."))
;;;# Foreign Types
(defclass foreign-type ()
()
(:documentation "Base class for all foreign types."))
(defmethod make-load-form ((type foreign-type) &optional env)
"Return the form used to dump types to a FASL file."
(declare (ignore env))
`(parse-type ',(unparse-type type)))
(defmethod foreign-type-size (type)
"Return the size in bytes of a foreign type."
(foreign-type-size (parse-type type)))
(defclass named-foreign-type (foreign-type)
((name
;; Name of this foreign type, a symbol.
:initform (error "Must specify a NAME.")
:initarg :name
:accessor name)))
(defmethod print-object ((type named-foreign-type) stream)
"Print a FOREIGN-TYPEDEF instance to STREAM unreadably."
(print-unreadable-object (type stream :type t :identity nil)
(format stream "~S" (name type))))
;;; Return the type's name which can be passed to PARSE-TYPE. If
;;; that's not the case for some subclass of NAMED-FOREIGN-TYPE then
;;; it should specialize UNPARSE-TYPE.
(defmethod unparse-type ((type named-foreign-type))
(name type))
;;;# Built-In Foreign Types
(defclass foreign-built-in-type (foreign-type)
((type-keyword
;; Keyword in CFFI-SYS representing this type.
:initform (error "A type keyword is required.")
:initarg :type-keyword
:accessor type-keyword))
(:documentation "A built-in foreign type."))
(defmethod canonicalize ((type foreign-built-in-type))
"Return the built-in type keyword for TYPE."
(type-keyword type))
(defmethod aggregatep ((type foreign-built-in-type))
"Returns false, built-in types are never aggregate types."
nil)
(defmethod foreign-type-alignment ((type foreign-built-in-type))
"Return the alignment of a built-in type."
(%foreign-type-alignment (type-keyword type)))
(defmethod foreign-type-size ((type foreign-built-in-type))
"Return the size of a built-in type."
(%foreign-type-size (type-keyword type)))
(defmethod unparse-type ((type foreign-built-in-type))
"Returns the symbolic representation of a built-in type."
(type-keyword type))
(defmethod print-object ((type foreign-built-in-type) stream)
"Print a FOREIGN-TYPE instance to STREAM unreadably."
(print-unreadable-object (type stream :type t :identity nil)
(format stream "~S" (type-keyword type))))
(defvar *built-in-foreign-types* nil)
(defmacro define-built-in-foreign-type (keyword)
"Defines a built-in foreign-type."
`(eval-when (:compile-toplevel :load-toplevel :execute)
(pushnew ,keyword *built-in-foreign-types*)
(notice-foreign-type
,keyword (make-instance 'foreign-built-in-type :type-keyword ,keyword))))
;;;# Foreign Pointer Types
(defclass foreign-pointer-type (foreign-built-in-type)
((pointer-type
;; Type of object pointed at by this pointer, or nil for an
;; untyped (void) pointer.
:initform nil
:initarg :pointer-type
:accessor pointer-type))
(:default-initargs :type-keyword :pointer))
;;; Define the type parser for the :POINTER type. If no type argument
;;; is provided, a void pointer will be created.
(let ((void-pointer (make-instance 'foreign-pointer-type)))
(define-parse-method :pointer (&optional type)
(if type
(make-instance 'foreign-pointer-type :pointer-type (parse-type type))
;; A bit of premature optimization here.
void-pointer)))
;;; Unparse a foreign pointer type when dumping to a fasl.
(defmethod unparse-type ((type foreign-pointer-type))
(if (pointer-type type)
`(:pointer ,(unparse-type (pointer-type type)))
:pointer))
;;; Print a foreign pointer type unreadably in unparsed form.
(defmethod print-object ((type foreign-pointer-type) stream)
(print-unreadable-object (type stream :type t :identity nil)
(format stream "~S" (unparse-type type))))
;;;# Structure Type
(defgeneric bare-struct-type-p (foreign-type)
(:documentation
"Return true if FOREIGN-TYPE is a bare struct type or an alias of a bare struct type. "))
(defmethod bare-struct-type-p ((type foreign-type))
"Return true if FOREIGN-TYPE is a bare struct type or an alias of a bare struct type. "
nil)
(defclass foreign-struct-type (named-foreign-type)
((slots
;; Hash table of slots in this structure, keyed by name.
:initform (make-hash-table)
:initarg :slots
:accessor slots)
(size
;; Cached size in bytes of this structure.
:initarg :size
:accessor size)
(alignment
;; This struct's alignment requirements
:initarg :alignment
:accessor alignment)
(bare
;; we use this flag to support the (old, deprecated) semantics of
;; bare struct types. FOO means (:POINTER (:STRUCT FOO) in
;; functions declarations whereas FOO in a structure definition is
;; a proper aggregate type: (:STRUCT FOO), etc.
:initform nil
:initarg :bare
:reader bare-struct-type-p)))
(defun slots-in-order (structure-type)
"A list of the structure's slots in order."
(sort (loop for slots being the hash-value of (structure-slots structure-type)
collect slots)
#'<
:key 'slot-offset))
(defmethod canonicalize ((type foreign-struct-type))
(if (bare-struct-type-p type)
:pointer
`(:struct ,(name type))))
(defmethod unparse-type ((type foreign-struct-type))
(if (bare-struct-type-p type)
(name type)
(canonicalize type)))
(defmethod aggregatep ((type foreign-struct-type))
"Returns true, structure types are aggregate."
t)
(defmethod foreign-type-size ((type foreign-struct-type))
"Return the size in bytes of a foreign structure type."
(size type))
(defmethod foreign-type-alignment ((type foreign-struct-type))
"Return the alignment requirements for this struct."
(alignment type))
(defclass foreign-union-type (foreign-struct-type) ())
(defmethod canonicalize ((type foreign-union-type))
(if (bare-struct-type-p type)
:pointer
`(:union ,(name type))))
;;;# Foreign Typedefs
(defclass foreign-type-alias (foreign-type)
((actual-type
;; The FOREIGN-TYPE instance this type is an alias for.
:initarg :actual-type
:accessor actual-type
:initform (error "Must specify an ACTUAL-TYPE.")))
(:documentation "A type that aliases another type."))
(defmethod canonicalize ((type foreign-type-alias))
"Return the built-in type keyword for TYPE."
(canonicalize (actual-type type)))
(defmethod aggregatep ((type foreign-type-alias))
"Return true if TYPE's actual type is aggregate."
(aggregatep (actual-type type)))
(defmethod foreign-type-alignment ((type foreign-type-alias))
"Return the alignment of a foreign typedef."
(foreign-type-alignment (actual-type type)))
(defmethod foreign-type-size ((type foreign-type-alias))
"Return the size in bytes of a foreign typedef."
(foreign-type-size (actual-type type)))
(defclass foreign-typedef (foreign-type-alias named-foreign-type)
())
(defun follow-typedefs (type)
(if (typep type 'foreign-typedef)
(follow-typedefs (actual-type type))
type))
(defmethod bare-struct-type-p ((type foreign-typedef))
(bare-struct-type-p (follow-typedefs type)))
(defun structure-slots (type)
"The hash table of slots for the structure type."
(slots (follow-typedefs type)))
;;;# Type Translators
;;;
;;; Type translation is done with generic functions at runtime for
;;; subclasses of TRANSLATABLE-FOREIGN-TYPE.
;;;
;;; The main interface for defining type translations is through the
;;; generic functions TRANSLATE-{TO,FROM}-FOREIGN and
;;; FREE-TRANSLATED-OBJECT.
(defclass translatable-foreign-type (foreign-type) ())
;;; ENHANCED-FOREIGN-TYPE is used to define translations on top of
;;; previously defined foreign types.
(defclass enhanced-foreign-type (translatable-foreign-type
foreign-type-alias)
((unparsed-type :accessor unparsed-type)))
;;; If actual-type isn't parsed already, let's parse it. This way we
;;; don't have to export PARSE-TYPE and users don't have to worry
;;; about this in DEFINE-FOREIGN-TYPE or DEFINE-PARSE-METHOD.
(defmethod initialize-instance :after ((type enhanced-foreign-type) &key)
(unless (typep (actual-type type) 'foreign-type)
(setf (actual-type type) (parse-type (actual-type type)))))
(defmethod unparse-type ((type enhanced-foreign-type))
(unparsed-type type))
;;; Checks NAMEs, not object identity.
(defun check-for-typedef-cycles (type)
(let ((seen (make-hash-table :test 'eq)))
(labels ((%check (cur-type)
(when (typep cur-type 'foreign-typedef)
(when (gethash (name cur-type) seen)
(simple-foreign-type-error type :default
"Detected cycle in type ~S." type))
(setf (gethash (name cur-type) seen) t)
(%check (actual-type cur-type)))))
(%check type))))
;;; Only now we define PARSE-TYPE because it needs to do some extra
;;; work for ENHANCED-FOREIGN-TYPES.
(defun parse-type (type)
(let* ((spec (ensure-list type))
(ptype (apply (find-type-parser (car spec)) (cdr spec))))
(when (typep ptype 'foreign-typedef)
(check-for-typedef-cycles ptype))
(when (typep ptype 'enhanced-foreign-type)
(setf (unparsed-type ptype) type))
ptype))
(defun ensure-parsed-base-type (type)
(follow-typedefs
(if (typep type 'foreign-type)
type
(parse-type type))))
(defun canonicalize-foreign-type (type)
"Convert TYPE to a built-in type by following aliases.
Signals an error if the type cannot be resolved."
(canonicalize (parse-type type)))
;;; Translate VALUE to a foreign object of the type represented by
;;; TYPE, which will be a subclass of TRANSLATABLE-FOREIGN-TYPE.
;;; Returns the foreign value and an optional second value which will
;;; be passed to FREE-TRANSLATED-OBJECT as the PARAM argument.
(defgeneric translate-to-foreign (value type)
(:method (value type)
(declare (ignore type))
value))
(defgeneric translate-into-foreign-memory (value type pointer)
(:documentation
"Translate the Lisp value into the foreign memory location given by pointer. Return value is not used.")
(:argument-precedence-order type value pointer))
;;; Similar to TRANSLATE-TO-FOREIGN, used exclusively by
;;; (SETF FOREIGN-STRUCT-SLOT-VALUE).
(defgeneric translate-aggregate-to-foreign (ptr value type))
;;; Translate the foreign object VALUE from the type repsented by
;;; TYPE, which will be a subclass of TRANSLATABLE-FOREIGN-TYPE.
;;; Returns the converted Lisp value.
(defgeneric translate-from-foreign (value type)
(:argument-precedence-order type value)
(:method (value type)
(declare (ignore type))
value))
;;; Free an object allocated by TRANSLATE-TO-FOREIGN. VALUE is a
;;; foreign object of the type represented by TYPE, which will be a
;;; TRANSLATABLE-FOREIGN-TYPE subclass. PARAM, if present, contains
;;; the second value returned by TRANSLATE-TO-FOREIGN, and is used to
;;; communicate between the two functions.
;;;
;;; FIXME: I don't think this PARAM argument is necessary anymore
;;; because the TYPE object can contain that information. [2008-12-31 LO]
(defgeneric free-translated-object (value type param)
(:method (value type param)
(declare (ignore value type param))))
;;;## Macroexpansion Time Translation
;;;
;;; The following EXPAND-* generic functions are similar to their
;;; TRANSLATE-* counterparts but are usually called at macroexpansion
;;; time. They offer a way to optimize the runtime translators.
;;; This special variable is bound by the various :around methods
;;; below to the respective form generated by the above %EXPAND-*
;;; functions. This way, an expander can "bail out" by calling the
;;; next method. All 6 of the below-defined GFs have a default method
;;; that simply answers the rtf bound by the default :around method.
(defvar *runtime-translator-form*)
;;; EXPAND-FROM-FOREIGN
(defgeneric expand-from-foreign (value type)
(:method (value type)
(declare (ignore type))
value))
(defmethod expand-from-foreign :around (value (type translatable-foreign-type))
(let ((*runtime-translator-form* `(translate-from-foreign ,value ,type)))
(call-next-method)))
(defmethod expand-from-foreign (value (type translatable-foreign-type))
(declare (ignore value))
*runtime-translator-form*)
;;; EXPAND-TO-FOREIGN
;; The second return value is used to tell EXPAND-TO-FOREIGN-DYN that
;; an unspecialized method was called.
(defgeneric expand-to-foreign (value type)
(:method (value type)
(declare (ignore type))
(values value t)))
(defmethod expand-to-foreign :around (value (type translatable-foreign-type))
(let ((*runtime-translator-form* `(translate-to-foreign ,value ,type)))
(call-next-method)))
(defmethod expand-to-foreign (value (type translatable-foreign-type))
(declare (ignore value))
(values *runtime-translator-form* t))
;;; EXPAND-INTO-FOREIGN-MEMORY
(defgeneric expand-into-foreign-memory (value type ptr)
(:method (value type ptr)
(declare (ignore type))
value))
(defmethod expand-into-foreign-memory :around
(value (type translatable-foreign-type) ptr)
(let ((*runtime-translator-form*
`(translate-into-foreign-memory ,value ,type ,ptr)))
(call-next-method)))
(defmethod expand-into-foreign-memory (value (type translatable-foreign-type) ptr)
(declare (ignore value))
*runtime-translator-form*)
;;; EXPAND-TO-FOREIGN-DYN
(defgeneric expand-to-foreign-dyn (value var body type)
(:method (value var body type)
(declare (ignore type))
`(let ((,var ,value)) ,@body)))
(defmethod expand-to-foreign-dyn :around
(value var body (type enhanced-foreign-type))
(let ((*runtime-translator-form*
(with-unique-names (param)
`(multiple-value-bind (,var ,param)
(translate-to-foreign ,value ,type)
(unwind-protect
(progn ,@body)
(free-translated-object ,var ,type ,param))))))
(call-next-method)))
;;; If this method is called it means the user hasn't defined a
;;; to-foreign-dyn expansion, so we use the to-foreign expansion.
;;;
;;; However, we do so *only* if there's a specialized
;;; EXPAND-TO-FOREIGN for TYPE because otherwise we want to use the
;;; above *RUNTIME-TRANSLATOR-FORM* which includes a call to
;;; FREE-TRANSLATED-OBJECT. (Or else there would occur no translation
;;; at all.)
(defun foreign-expand-runtime-translator-or-binding (value var body type)
(multiple-value-bind (expansion default-etp-p)
(expand-to-foreign value type)
(if default-etp-p
*runtime-translator-form*
`(let ((,var ,expansion))
,@body))))
(defmethod expand-to-foreign-dyn (value var body (type enhanced-foreign-type))
(foreign-expand-runtime-translator-or-binding value var body type))
;;; EXPAND-TO-FOREIGN-DYN-INDIRECT
;;; Like expand-to-foreign-dyn, but always give form that returns a
;;; pointer to the object, even if it's directly representable in
;;; CL, e.g. numbers.
(defgeneric expand-to-foreign-dyn-indirect (value var body type)
(:method (value var body type)
(declare (ignore type))
`(let ((,var ,value)) ,@body)))
(defmethod expand-to-foreign-dyn-indirect :around
(value var body (type translatable-foreign-type))
(let ((*runtime-translator-form*
`(with-foreign-object (,var ',(unparse-type type))
(translate-into-foreign-memory ,value ,type ,var)
,@body)))
(call-next-method)))
(defmethod expand-to-foreign-dyn-indirect
(value var body (type foreign-pointer-type))
`(with-foreign-object (,var :pointer)
(translate-into-foreign-memory ,value ,type ,var)
,@body))
(defmethod expand-to-foreign-dyn-indirect
(value var body (type foreign-built-in-type))
`(with-foreign-object (,var ,type)
(translate-into-foreign-memory ,value ,type ,var)
,@body))
(defmethod expand-to-foreign-dyn-indirect
(value var body (type translatable-foreign-type))
(foreign-expand-runtime-translator-or-binding value var body type))
(defmethod expand-to-foreign-dyn-indirect (value var body (type foreign-type-alias))
(expand-to-foreign-dyn-indirect value var body (actual-type type)))
;;; User interface for converting values from/to foreign using the
;;; type translators. The compiler macros use the expanders when
;;; possible.
(defun convert-to-foreign (value type)
(translate-to-foreign value (parse-type type)))
(define-compiler-macro convert-to-foreign (value type)
(if (constantp type)
(expand-to-foreign value (parse-type (eval type)))
`(translate-to-foreign ,value (parse-type ,type))))
(defun convert-from-foreign (value type)
(translate-from-foreign value (parse-type type)))
(define-compiler-macro convert-from-foreign (value type)
(if (constantp type)
(expand-from-foreign value (parse-type (eval type)))
`(translate-from-foreign ,value (parse-type ,type))))
(defun convert-into-foreign-memory (value type ptr)
(translate-into-foreign-memory value (parse-type type) ptr))
(define-compiler-macro convert-into-foreign-memory (value type ptr)
(if (constantp type)
(expand-into-foreign-memory value (parse-type (eval type)) ptr)
`(translate-into-foreign-memory ,value (parse-type ,type) ,ptr)))
(defun free-converted-object (value type param)
(free-translated-object value (parse-type type) param))
;;;# Enhanced typedefs
(defclass enhanced-typedef (foreign-typedef)
())
(defmethod translate-to-foreign (value (type enhanced-typedef))
(translate-to-foreign value (actual-type type)))
(defmethod translate-into-foreign-memory (value (type enhanced-typedef) pointer)
(translate-into-foreign-memory value (actual-type type) pointer))
(defmethod translate-from-foreign (value (type enhanced-typedef))
(translate-from-foreign value (actual-type type)))
(defmethod free-translated-object (value (type enhanced-typedef) param)
(free-translated-object value (actual-type type) param))
(defmethod expand-from-foreign (value (type enhanced-typedef))
(expand-from-foreign value (actual-type type)))
(defmethod expand-to-foreign (value (type enhanced-typedef))
(expand-to-foreign value (actual-type type)))
(defmethod expand-to-foreign-dyn (value var body (type enhanced-typedef))
(expand-to-foreign-dyn value var body (actual-type type)))
(defmethod expand-into-foreign-memory (value (type enhanced-typedef) ptr)
(expand-into-foreign-memory value (actual-type type) ptr))
;;;# User-defined Types and Translations.
(defmacro define-foreign-type (name supers slots &rest options)
(multiple-value-bind (new-options simple-parser actual-type initargs)
(let ((keywords '(:simple-parser :actual-type :default-initargs)))
(apply #'values
(remove-if (lambda (opt) (member (car opt) keywords)) options)
(mapcar (lambda (kw) (cdr (assoc kw options))) keywords)))
`(eval-when (:compile-toplevel :load-toplevel :execute)
(defclass ,name ,(or supers '(enhanced-foreign-type))
,slots
(:default-initargs ,@(when actual-type `(:actual-type ',actual-type))
,@initargs)
,@new-options)
,(when simple-parser
`(define-parse-method ,(car simple-parser) (&rest args)
(apply #'make-instance ',name args)))
',name)))
(defmacro defctype (name base-type &optional documentation)
"Utility macro for simple C-like typedefs."
(declare (ignore documentation))
(warn-if-kw-or-belongs-to-cl name)
(let* ((btype (parse-type base-type))
(dtype (if (typep btype 'enhanced-foreign-type)
'enhanced-typedef
'foreign-typedef)))
`(eval-when (:compile-toplevel :load-toplevel :execute)
(notice-foreign-type
',name (make-instance ',dtype :name ',name :actual-type ,btype)))))
;;; For Verrazano. We memoize the type this way to help detect cycles.
(defmacro defctype* (name base-type)
"Like DEFCTYPE but defers instantiation until parse-time."
`(eval-when (:compile-toplevel :load-toplevel :execute)
(let (memoized-type)
(define-parse-method ,name ()
(unless memoized-type
(setf memoized-type (make-instance 'foreign-typedef :name ',name
:actual-type nil)
(actual-type memoized-type) (parse-type ',base-type)))
memoized-type))))

View file

@ -0,0 +1,369 @@
;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
;;;
;;; enum.lisp --- Defining foreign constants as Lisp keywords.
;;;
;;; Copyright (C) 2005-2006, James Bielman <jamesjb@jamesjb.com>
;;;
;;; 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.
;;;
(in-package #:cffi)
;; TODO the accessors names are rather inconsistent:
;; FOREIGN-ENUM-VALUE FOREIGN-BITFIELD-VALUE
;; FOREIGN-ENUM-KEYWORD FOREIGN-BITFIELD-SYMBOLS
;; FOREIGN-ENUM-KEYWORD-LIST FOREIGN-BITFIELD-SYMBOL-LIST
;; I'd rename them to: FOREIGN-*-KEY(S) and FOREIGN-*-ALL-KEYS -- attila
;; TODO bitfield is a confusing name, because the C standard calls
;; the "int foo : 3" type as a bitfield. Maybe rename to defbitmask?
;; -- attila
;;;# Foreign Constants as Lisp Keywords
;;;
;;; This module defines the DEFCENUM macro, which provides an
;;; interface for defining a type and associating a set of integer
;;; constants with keyword symbols for that type.
;;;
;;; The keywords are automatically translated to the appropriate
;;; constant for the type by a type translator when passed as
;;; arguments or a return value to a foreign function.
(defclass foreign-enum (named-foreign-type enhanced-foreign-type)
((keyword-values
:initform (error "Must specify KEYWORD-VALUES.")
:initarg :keyword-values
:reader keyword-values)
(value-keywords
:initform (error "Must specify VALUE-KEYWORDS.")
:initarg :value-keywords
:reader value-keywords))
(:documentation "Describes a foreign enumerated type."))
(deftype enum-key ()
'(and symbol (not null)))
(defparameter +valid-enum-base-types+ *built-in-integer-types*)
(defun parse-foreign-enum-like (type-name base-type values
&optional field-mode-p)
(let ((keyword-values (make-hash-table :test 'eq))
(value-keywords (make-hash-table))
(field-keywords (list))
(bit-index->keyword (make-array 0 :adjustable t
:element-type t))
(default-value (if field-mode-p 1 0))
(most-extreme-value 0)
(has-negative-value? nil))
(dolist (pair values)
(destructuring-bind (keyword &optional (value default-value valuep))
(ensure-list pair)
(check-type keyword enum-key)
;;(check-type value integer)
(when (> (abs value) (abs most-extreme-value))
(setf most-extreme-value value))
(when (minusp value)
(setf has-negative-value? t))
(if field-mode-p
(if valuep
(when (and (>= value default-value)
(single-bit-p value))
(setf default-value (ash value 1)))
(setf default-value (ash default-value 1)))
(setf default-value (1+ value)))
(if (gethash keyword keyword-values)
(error "A foreign enum cannot contain duplicate keywords: ~S."
keyword)
(setf (gethash keyword keyword-values) value))
;; This is completely arbitrary behaviour: we keep the last
;; value->keyword mapping. I suppose the opposite would be
;; just as good (keeping the first). Returning a list with all
;; the keywords might be a solution too? Suggestions
;; welcome. --luis
(setf (gethash value value-keywords) keyword)
(when (and field-mode-p
(single-bit-p value))
(let ((bit-index (1- (integer-length value))))
(push keyword field-keywords)
(when (<= (array-dimension bit-index->keyword 0)
bit-index)
(setf bit-index->keyword
(adjust-array bit-index->keyword (1+ bit-index)
:initial-element nil)))
(setf (aref bit-index->keyword bit-index)
keyword)))))
(if base-type
(progn
(setf base-type (canonicalize-foreign-type base-type))
;; I guess we don't lose much by not strictly adhering to
;; the C standard here, and some libs out in the wild are
;; already using e.g. :double.
#+nil
(assert (member base-type +valid-enum-base-types+ :test 'eq) ()
"Invalid base type ~S for enum type ~S. Must be one of ~S."
base-type type-name +valid-enum-base-types+))
;; details: https://stackoverflow.com/questions/1122096/what-is-the-underlying-type-of-a-c-enum
(let ((bits (integer-length most-extreme-value)))
(setf base-type
(let ((most-uint-bits (load-time-value (* (foreign-type-size :unsigned-int) 8)))
(most-ulong-bits (load-time-value (* (foreign-type-size :unsigned-long) 8)))
(most-ulonglong-bits (load-time-value (* (foreign-type-size :unsigned-long-long) 8))))
(or (if has-negative-value?
(cond
((<= (1+ bits) most-uint-bits)
:int)
((<= (1+ bits) most-ulong-bits)
:long)
((<= (1+ bits) most-ulonglong-bits)
:long-long))
(cond
((<= bits most-uint-bits)
:unsigned-int)
((<= bits most-ulong-bits)
:unsigned-long)
((<= bits most-ulonglong-bits)
:unsigned-long-long)))
(error "Enum value ~S of enum ~S is too large to store."
most-extreme-value type-name))))))
(values base-type keyword-values value-keywords
field-keywords (when field-mode-p
(alexandria:copy-array
bit-index->keyword :adjustable nil
:fill-pointer nil)))))
(defun make-foreign-enum (type-name base-type values)
"Makes a new instance of the foreign-enum class."
(multiple-value-bind
(base-type keyword-values value-keywords)
(parse-foreign-enum-like type-name base-type values)
(make-instance 'foreign-enum
:name type-name
:actual-type (parse-type base-type)
:keyword-values keyword-values
:value-keywords value-keywords)))
(defun %defcenum-like (name-and-options enum-list type-factory)
(discard-docstring enum-list)
(destructuring-bind (name &optional base-type)
(ensure-list name-and-options)
(let ((type (funcall type-factory name base-type enum-list)))
`(eval-when (:compile-toplevel :load-toplevel :execute)
(notice-foreign-type ',name
;; ,type is not enough here, someone needs to
;; define it when we're being loaded from a fasl.
(,type-factory ',name ',base-type ',enum-list))
,@(remove nil
(mapcar (lambda (key)
(unless (keywordp key)
`(defconstant ,key ,(foreign-enum-value type key))))
(foreign-enum-keyword-list type)))))))
(defmacro defcenum (name-and-options &body enum-list)
"Define an foreign enumerated type."
(%defcenum-like name-and-options enum-list 'make-foreign-enum))
(defun hash-keys-to-list (ht)
(loop for k being the hash-keys in ht collect k))
(defun foreign-enum-keyword-list (enum-type)
"Return a list of KEYWORDS defined in ENUM-TYPE."
(hash-keys-to-list (keyword-values (ensure-parsed-base-type enum-type))))
;;; These [four] functions could be good canditates for compiler macros
;;; when the value or keyword is constant. I am not going to bother
;;; until someone has a serious performance need to do so though. --jamesjb
(defun %foreign-enum-value (type keyword &key errorp)
(check-type keyword enum-key)
(or (gethash keyword (keyword-values type))
(when errorp
(error "~S is not defined as a keyword for enum type ~S."
keyword type))))
(defun foreign-enum-value (type keyword &key (errorp t))
"Convert a KEYWORD into an integer according to the enum TYPE."
(let ((type-obj (ensure-parsed-base-type type)))
(if (not (typep type-obj 'foreign-enum))
(error "~S is not a foreign enum type." type)
(%foreign-enum-value type-obj keyword :errorp errorp))))
(defun %foreign-enum-keyword (type value &key errorp)
(check-type value integer)
(or (gethash value (value-keywords type))
(when errorp
(error "~S is not defined as a value for enum type ~S."
value type))))
(defun foreign-enum-keyword (type value &key (errorp t))
"Convert an integer VALUE into a keyword according to the enum TYPE."
(let ((type-obj (ensure-parsed-base-type type)))
(if (not (typep type-obj 'foreign-enum))
(error "~S is not a foreign enum type." type)
(%foreign-enum-keyword type-obj value :errorp errorp))))
(defmethod translate-to-foreign (value (type foreign-enum))
(if (typep value 'enum-key)
(%foreign-enum-value type value :errorp t)
value))
(defmethod translate-into-foreign-memory
(value (type foreign-enum) pointer)
(setf (mem-aref pointer (unparse-type (actual-type type)))
(translate-to-foreign value type)))
(defmethod translate-from-foreign (value (type foreign-enum))
(%foreign-enum-keyword type value :errorp t))
(defmethod expand-to-foreign (value (type foreign-enum))
(once-only (value)
`(if (typep ,value 'enum-key)
(%foreign-enum-value ,type ,value :errorp t)
,value)))
;;; There are two expansions necessary for an enum: first, the enum
;;; keyword needs to be translated to an int, and then the int needs
;;; to be made indirect.
(defmethod expand-to-foreign-dyn-indirect (value var body (type foreign-enum))
(expand-to-foreign-dyn-indirect ; Make the integer indirect
(with-unique-names (feint)
(call-next-method value feint (list feint) type)) ; TRANSLATABLE-FOREIGN-TYPE method
var
body
(actual-type type)))
;;;# Foreign Bitfields as Lisp keywords
;;;
;;; DEFBITFIELD is an abstraction similar to the one provided by DEFCENUM.
;;; With some changes to DEFCENUM, this could certainly be implemented on
;;; top of it.
(defclass foreign-bitfield (foreign-enum)
((field-keywords
:initform (error "Must specify FIELD-KEYWORDS.")
:initarg :field-keywords
:reader field-keywords)
(bit-index->keyword
:initform (error "Must specify BIT-INDEX->KEYWORD")
:initarg :bit-index->keyword
:reader bit-index->keyword))
(:documentation "Describes a foreign bitfield type."))
(defun make-foreign-bitfield (type-name base-type values)
"Makes a new instance of the foreign-bitfield class."
(multiple-value-bind
(base-type keyword-values value-keywords
field-keywords bit-index->keyword)
(parse-foreign-enum-like type-name base-type values t)
(make-instance 'foreign-bitfield
:name type-name
:actual-type (parse-type base-type)
:keyword-values keyword-values
:value-keywords value-keywords
:field-keywords field-keywords
:bit-index->keyword bit-index->keyword)))
(defmacro defbitfield (name-and-options &body masks)
"Define an foreign enumerated type."
(%defcenum-like name-and-options masks 'make-foreign-bitfield))
(defun foreign-bitfield-symbol-list (bitfield-type)
"Return a list of SYMBOLS defined in BITFIELD-TYPE."
(field-keywords (ensure-parsed-base-type bitfield-type)))
(defun %foreign-bitfield-value (type symbols)
(declare (optimize speed))
(labels ((process-one (symbol)
(check-type symbol symbol)
(or (gethash symbol (keyword-values type))
(error "~S is not a valid symbol for bitfield type ~S."
symbol type))))
(declare (dynamic-extent #'process-one))
(cond
((consp symbols)
(reduce #'logior symbols :key #'process-one))
((null symbols)
0)
(t
(process-one symbols)))))
(defun foreign-bitfield-value (type symbols)
"Convert a list of symbols into an integer according to the TYPE bitfield."
(let ((type-obj (ensure-parsed-base-type type)))
(assert (typep type-obj 'foreign-bitfield) ()
"~S is not a foreign bitfield type." type)
(%foreign-bitfield-value type-obj symbols)))
(define-compiler-macro foreign-bitfield-value (&whole form type symbols)
"Optimize for when TYPE and SYMBOLS are constant."
(declare (notinline foreign-bitfield-value))
(if (and (constantp type) (constantp symbols))
(foreign-bitfield-value (eval type) (eval symbols))
form))
(defun %foreign-bitfield-symbols (type value)
(check-type value integer)
(check-type type foreign-bitfield)
(loop
:with bit-index->keyword = (bit-index->keyword type)
:for bit-index :from 0 :below (array-dimension bit-index->keyword 0)
:for mask = 1 :then (ash mask 1)
:for key = (aref bit-index->keyword bit-index)
:when (and key
(= (logand value mask) mask))
:collect key))
(defun foreign-bitfield-symbols (type value)
"Convert an integer VALUE into a list of matching symbols according to
the bitfield TYPE."
(let ((type-obj (ensure-parsed-base-type type)))
(if (not (typep type-obj 'foreign-bitfield))
(error "~S is not a foreign bitfield type." type)
(%foreign-bitfield-symbols type-obj value))))
(define-compiler-macro foreign-bitfield-symbols (&whole form type value)
"Optimize for when TYPE and SYMBOLS are constant."
(declare (notinline foreign-bitfield-symbols))
(if (and (constantp type) (constantp value))
`(quote ,(foreign-bitfield-symbols (eval type) (eval value)))
form))
(defmethod translate-to-foreign (value (type foreign-bitfield))
(if (integerp value)
value
(%foreign-bitfield-value type (ensure-list value))))
(defmethod translate-from-foreign (value (type foreign-bitfield))
(%foreign-bitfield-symbols type value))
(defmethod expand-to-foreign (value (type foreign-bitfield))
(flet ((expander (value type)
`(if (integerp ,value)
,value
(%foreign-bitfield-value ,type (ensure-list ,value)))))
(if (constantp value)
(eval (expander value type))
(expander value type))))
(defmethod expand-from-foreign (value (type foreign-bitfield))
(flet ((expander (value type)
`(%foreign-bitfield-symbols ,type ,value)))
(if (constantp value)
(eval (expander value type))
(expander value type))))

View file

@ -0,0 +1,111 @@
;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
;;;
;;; features.lisp --- CFFI-specific features (DEPRECATED).
;;;
;;; Copyright (C) 2006-2007, Luis Oliveira <loliveira@common-lisp.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.
;;;
(in-package #:cl-user)
(eval-when (:compile-toplevel :load-toplevel :execute)
(pushnew :cffi *features*))
;;; CFFI-SYS backends take care of pushing the appropriate features to
;;; *features*. See each cffi-*.lisp file.
;;;
;;; Not anymore, I think we should use TRIVIAL-FEATURES for the
;;; platform features instead. Less pain. CFFI-FEATURES is now
;;; deprecated and this code will stay here for a while for backwards
;;; compatibility purposes, to be removed in a future release.
(defpackage #:cffi-features
(:use #:cl)
(:export
#:cffi-feature-p
;; Features related to the CFFI-SYS backend. Why no-*? This
;; reflects the hope that these symbols will go away completely
;; meaning that at some point all lisps will support long-longs,
;; the foreign-funcall primitive, etc...
#:no-long-long
#:no-foreign-funcall
#:no-stdcall
#:flat-namespace
;; Only SCL supports long-double...
;;#:no-long-double
;; Features related to the operating system.
;; More should be added.
#:darwin
#:unix
#:windows
;; Features related to the processor.
;; More should be added.
#:ppc32
#:x86
#:x86-64
#:sparc
#:sparc64
#:hppa
#:hppa64))
(in-package #:cffi-features)
(defun cffi-feature-p (feature-expression)
"Matches a FEATURE-EXPRESSION against those symbols in *FEATURES*
that belong to the CFFI-FEATURES package."
(when (eql feature-expression t)
(return-from cffi-feature-p t))
(let ((features-package (find-package '#:cffi-features)))
(flet ((cffi-feature-eq (name feature-symbol)
(and (eq (symbol-package feature-symbol) features-package)
(string= name (symbol-name feature-symbol)))))
(etypecase feature-expression
(symbol
(not (null (member (symbol-name feature-expression) *features*
:test #'cffi-feature-eq))))
(cons
(ecase (first feature-expression)
(:and (every #'cffi-feature-p (rest feature-expression)))
(:or (some #'cffi-feature-p (rest feature-expression)))
(:not (not (cffi-feature-p (cadr feature-expression))))))))))
;;; for backwards compatibility
(mapc (lambda (sym) (pushnew sym *features*))
'(#+darwin darwin
#+unix unix
#+windows windows
#+ppc ppc32
#+x86 x86
#+x86-64 x86-64
#+sparc sparc
#+sparc64 sparc64
#+hppa hppa
#+hppa64 hppa64
#+cffi-sys::no-long-long no-long-long
#+cffi-sys::flat-namespace flat-namespace
#+cffi-sys::no-foreign-funcall no-foreign-funcall
#+cffi-sys::no-stdcall no-stdcall
))

View file

@ -0,0 +1,90 @@
;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
;;;
;;; foreign-vars.lisp --- High-level interface to foreign globals.
;;;
;;; Copyright (C) 2005-2008, Luis Oliveira <loliveira(@)common-lisp.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.
;;;
(in-package #:cffi)
;;;# Accessing Foreign Globals
;;; Called by FOREIGN-OPTIONS in functions.lisp.
(defun parse-defcvar-options (options)
(destructuring-bind (&key (library :default) read-only) options
(list :library library :read-only read-only)))
(defun get-var-pointer (symbol)
"Return a pointer to the foreign global variable relative to SYMBOL."
(foreign-symbol-pointer (get symbol 'foreign-var-name)
:library (get symbol 'foreign-var-library)))
;;; Note: this will lookup not only variables but also functions.
(defun foreign-symbol-pointer (name &key (library :default))
(check-type name string)
(%foreign-symbol-pointer
name (if (eq library :default)
:default
(foreign-library-handle
(get-foreign-library library)))))
(defun fs-pointer-or-lose (foreign-name library)
"Like foreign-symbol-ptr but throws an error instead of
returning nil when foreign-name is not found."
(or (foreign-symbol-pointer foreign-name :library library)
(error "Trying to access undefined foreign variable ~S." foreign-name)))
(defmacro defcvar (name-and-options type &optional documentation)
"Define a foreign global variable."
(multiple-value-bind (lisp-name foreign-name options)
(parse-name-and-options name-and-options t)
(let ((fn (symbolicate '#:%var-accessor- lisp-name))
(read-only (getf options :read-only))
(library (getf options :library)))
;; We can't really setf an aggregate type.
(when (aggregatep (parse-type type))
(setq read-only t))
`(progn
(setf (documentation ',lisp-name 'variable) ,documentation)
;; Save foreign-name and library for posterior access by
;; GET-VAR-POINTER.
(setf (get ',lisp-name 'foreign-var-name) ,foreign-name)
(setf (get ',lisp-name 'foreign-var-library) ',library)
;; Getter
(defun ,fn ()
(mem-ref (fs-pointer-or-lose ,foreign-name ',library) ',type))
;; Setter
(defun (setf ,fn) (value)
,(if read-only '(declare (ignore value)) (values))
,(if read-only
`(error ,(format nil
"Trying to modify read-only foreign var: ~A."
lisp-name))
`(setf (mem-ref (fs-pointer-or-lose ,foreign-name ',library)
',type)
value)))
;; While most Lisps already expand DEFINE-SYMBOL-MACRO to an
;; EVAL-WHEN form like this, that is not required by the
;; standard so we do it ourselves.
(eval-when (:compile-toplevel :load-toplevel :execute)
(define-symbol-macro ,lisp-name (,fn)))))))

View file

@ -0,0 +1,441 @@
;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
;;;
;;; functions.lisp --- High-level interface to foreign functions.
;;;
;;; Copyright (C) 2005-2006, James Bielman <jamesjb@jamesjb.com>
;;; Copyright (C) 2005-2007, Luis Oliveira <loliveira@common-lisp.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.
;;;
(in-package #:cffi)
;;;# Calling Foreign Functions
;;;
;;; FOREIGN-FUNCALL is the main primitive for calling foreign
;;; functions. It converts each argument based on the installed
;;; translators for its type, then passes the resulting list to
;;; CFFI-SYS:%FOREIGN-FUNCALL.
;;;
;;; For implementation-specific reasons, DEFCFUN doesn't use
;;; FOREIGN-FUNCALL directly and might use something else (passed to
;;; TRANSLATE-OBJECTS as the CALL-FORM argument) instead of
;;; CFFI-SYS:%FOREIGN-FUNCALL to call the foreign-function.
(defun translate-objects (syms args types rettype call-form &optional indirect)
"Helper function for FOREIGN-FUNCALL and DEFCFUN. If 'indirect is T, all arguments are represented by foreign pointers, even those that can be represented by CL objects."
(if (null args)
(expand-from-foreign call-form (parse-type rettype))
(funcall
(if indirect
#'expand-to-foreign-dyn-indirect
#'expand-to-foreign-dyn)
(car args) (car syms)
(list (translate-objects (cdr syms) (cdr args)
(cdr types) rettype call-form indirect))
(parse-type (car types)))))
(defun parse-args-and-types (args)
"Returns 4 values: types, canonicalized types, args and return type."
(let* ((len (length args))
(return-type (if (oddp len) (lastcar args) :void)))
(loop repeat (floor len 2)
for (type arg) on args by #'cddr
collect type into types
collect (canonicalize-foreign-type type) into ctypes
collect arg into fargs
finally (return (values types ctypes fargs return-type)))))
;;; While the options passed directly to DEFCFUN/FOREIGN-FUNCALL have
;;; precedence, we also grab its library's options, if possible.
(defun parse-function-options (options &key pointer)
(destructuring-bind (&key (library :default libraryp)
(cconv nil cconv-p)
(calling-convention cconv calling-convention-p)
(convention calling-convention))
options
(when cconv-p
(warn-obsolete-argument :cconv :convention))
(when calling-convention-p
(warn-obsolete-argument :calling-convention :convention))
(list* :convention
(or convention
(when libraryp
(let ((lib-options (foreign-library-options
(get-foreign-library library))))
(getf lib-options :convention)))
:cdecl)
;; Don't pass the library option if we're dealing with
;; FOREIGN-FUNCALL-POINTER.
(unless pointer
(list :library library)))))
(defun structure-by-value-p (ctype)
"A structure or union is to be called or returned by value."
(let ((actual-type (ensure-parsed-base-type ctype)))
(or (and (typep actual-type 'foreign-struct-type)
(not (bare-struct-type-p actual-type)))
#+cffi::no-long-long (typep actual-type 'emulated-llong-type))))
(defun fn-call-by-value-p (argument-types return-type)
"One or more structures in the arguments or return from the function are called by value."
(or (some 'structure-by-value-p argument-types)
(structure-by-value-p return-type)))
(defvar *foreign-structures-by-value*
(lambda (&rest args)
(declare (ignore args))
(restart-case
(error "Unable to call structures by value without cffi-libffi loaded.")
(load-cffi-libffi () :report "Load cffi-libffi."
(asdf:operate 'asdf:load-op 'cffi-libffi))))
"A function that produces a form suitable for calling structures by value.")
(defun foreign-funcall-form (thing options args pointerp)
(multiple-value-bind (types ctypes fargs rettype)
(parse-args-and-types args)
(let ((syms (make-gensym-list (length fargs)))
(fsbvp (fn-call-by-value-p ctypes rettype)))
(if fsbvp
;; Structures by value call through *foreign-structures-by-value*
(funcall *foreign-structures-by-value*
thing
fargs
syms
types
rettype
ctypes
pointerp)
(translate-objects
syms fargs types rettype
`(,(if pointerp '%foreign-funcall-pointer '%foreign-funcall)
;; No structures by value, direct call
,thing
(,@(mapcan #'list ctypes syms)
,(canonicalize-foreign-type rettype))
,@(parse-function-options options :pointer pointerp)))))))
(defmacro foreign-funcall (name-and-options &rest args)
"Wrapper around %FOREIGN-FUNCALL that translates its arguments."
(let ((name (car (ensure-list name-and-options)))
(options (cdr (ensure-list name-and-options))))
(foreign-funcall-form name options args nil)))
(defmacro foreign-funcall-pointer (pointer options &rest args)
(foreign-funcall-form pointer options args t))
(defun promote-varargs-type (builtin-type)
"Default argument promotions."
(case builtin-type
(:float :double)
((:char :short) :int)
((:unsigned-char :unsigned-short) :unsigned-int)
(t builtin-type)))
;; If cffi-sys doesn't provide a %foreign-funcall-varargs macros we
;; define one that use %foreign-funcall.
(eval-when (:compile-toplevel :load-toplevel :execute)
(unless (fboundp '%foreign-funcall-varargs)
(defmacro %foreign-funcall-varargs (name fixed-args varargs
&rest args &key convention library)
(declare (ignore convention library))
`(%foreign-funcall ,name ,(append fixed-args varargs) ,@args)))
(unless (fboundp '%foreign-funcall-pointer-varargs)
(defmacro %foreign-funcall-pointer-varargs (pointer fixed-args varargs
&rest args &key convention)
(declare (ignore convention))
`(%foreign-funcall-pointer ,pointer ,(append fixed-args varargs) ,@args))))
(defun foreign-funcall-varargs-form (thing options fixed-args varargs pointerp)
(multiple-value-bind (fixed-types fixed-ctypes fixed-fargs)
(parse-args-and-types fixed-args)
(multiple-value-bind (varargs-types varargs-ctypes varargs-fargs rettype)
(parse-args-and-types varargs)
(let ((fixed-syms (make-gensym-list (length fixed-fargs)))
(varargs-syms (make-gensym-list (length varargs-fargs))))
(translate-objects
(append fixed-syms varargs-syms)
(append fixed-fargs varargs-fargs)
(append fixed-types varargs-types)
rettype
`(,(if pointerp '%foreign-funcall-pointer-varargs '%foreign-funcall-varargs)
,thing
,(mapcan #'list fixed-ctypes fixed-syms)
,(append
(mapcan #'list
(mapcar #'promote-varargs-type varargs-ctypes)
(loop for sym in varargs-syms
and type in varargs-ctypes
if (eq type :float)
collect `(float ,sym 1.0d0)
else collect sym))
(list (canonicalize-foreign-type rettype)))
,@options))))))
(defmacro foreign-funcall-varargs (name-and-options fixed-args
&rest varargs)
"Wrapper around %FOREIGN-FUNCALL that translates its arguments
and does type promotion for the variadic arguments."
(let ((name (car (ensure-list name-and-options)))
(options (cdr (ensure-list name-and-options))))
(foreign-funcall-varargs-form name options fixed-args varargs nil)))
(defmacro foreign-funcall-pointer-varargs (pointer options fixed-args
&rest varargs)
"Wrapper around %FOREIGN-FUNCALL-POINTER that translates its
arguments and does type promotion for the variadic arguments."
(foreign-funcall-varargs-form pointer options fixed-args varargs t))
;;;# Defining Foreign Functions
;;;
;;; The DEFCFUN macro provides a declarative interface for defining
;;; Lisp functions that call foreign functions.
;; If cffi-sys doesn't provide a defcfun-helper-forms,
;; we define one that uses %foreign-funcall.
(eval-when (:compile-toplevel :load-toplevel :execute)
(unless (fboundp 'defcfun-helper-forms)
(defun defcfun-helper-forms (name lisp-name rettype args types options)
(declare (ignore lisp-name))
(values
'()
`(%foreign-funcall ,name ,(append (mapcan #'list types args)
(list rettype))
,@options)))))
(defun %defcfun (lisp-name foreign-name return-type args options docstring)
(let* ((arg-names (mapcar #'first args))
(arg-types (mapcar #'second args))
(syms (make-gensym-list (length args)))
(call-by-value (fn-call-by-value-p arg-types return-type)))
(multiple-value-bind (prelude caller)
(if call-by-value
(values nil nil)
(defcfun-helper-forms
foreign-name lisp-name (canonicalize-foreign-type return-type)
syms (mapcar #'canonicalize-foreign-type arg-types) options))
`(progn
,prelude
(defun ,lisp-name ,arg-names
,@(ensure-list docstring)
,(if call-by-value
`(foreign-funcall
,(cons foreign-name options)
,@(append (mapcan #'list arg-types arg-names)
(list return-type)))
(translate-objects
syms arg-names arg-types return-type caller)))))))
(defun %defcfun-varargs (lisp-name foreign-name return-type args options doc)
(with-unique-names (varargs)
(let ((arg-names (mapcar #'car args)))
`(defmacro ,lisp-name (,@arg-names &rest ,varargs)
,@(ensure-list doc)
`(foreign-funcall-varargs
,'(,foreign-name ,@options)
,,`(list ,@(loop for (name type) in args
collect `',type collect name))
,@,varargs
,',return-type)))))
(defgeneric translate-underscore-separated-name (name)
(:method ((name string))
(values (intern (canonicalize-symbol-name-case (substitute #\- #\_ name)))))
(:method ((name symbol))
(substitute #\_ #\- (string-downcase (symbol-name name)))))
(defun collapse-prefix (l special-words)
(unless (null l)
(multiple-value-bind (newpre skip) (check-prefix l special-words)
(cons newpre (collapse-prefix (nthcdr skip l) special-words)))))
(defun check-prefix (l special-words)
(let ((pl (loop for i from (1- (length l)) downto 0
collect (apply #'concatenate 'simple-string (butlast l i)))))
(loop for w in special-words
for p = (position-if #'(lambda (s) (string= s w)) pl)
when p do (return-from check-prefix (values (nth p pl) (1+ p))))
(values (first l) 1)))
(defgeneric translate-camelcase-name (name &key upper-initial-p special-words)
(:method ((name string) &key upper-initial-p special-words)
(declare (ignore upper-initial-p))
(values (intern (reduce #'(lambda (s1 s2)
(concatenate 'simple-string s1 "-" s2))
(mapcar #'string-upcase
(collapse-prefix
(split-if #'(lambda (ch)
(or (upper-case-p ch)
(digit-char-p ch)))
name)
special-words))))))
(:method ((name symbol) &key upper-initial-p special-words)
(apply #'concatenate
'string
(loop for str in (split-if #'(lambda (ch) (eq ch #\-))
(string name)
:elide)
for first-word-p = t then nil
for e = (member str special-words
:test #'equal :key #'string-upcase)
collect (cond
((and first-word-p (not upper-initial-p))
(string-downcase str))
(e (first e))
(t (string-capitalize str)))))))
(defgeneric translate-name-from-foreign (foreign-name package &optional varp)
(:method (foreign-name package &optional varp)
(declare (ignore package))
(let ((sym (translate-underscore-separated-name foreign-name)))
(if varp
(values (intern (format nil "*~A*"
(canonicalize-symbol-name-case
(symbol-name sym)))))
sym))))
(defgeneric translate-name-to-foreign (lisp-name package &optional varp)
(:method (lisp-name package &optional varp)
(declare (ignore package))
(let ((name (translate-underscore-separated-name lisp-name)))
(if varp
(string-trim '(#\*) name)
name))))
(defun lisp-name (spec varp)
(check-type spec string)
(translate-name-from-foreign spec *package* varp))
(defun foreign-name (spec varp)
(check-type spec (and symbol (not null)))
(translate-name-to-foreign spec *package* varp))
(defun foreign-options (opts varp)
(if varp
(funcall 'parse-defcvar-options opts)
(parse-function-options opts)))
(defun lisp-name-p (name)
(and name (symbolp name) (not (keywordp name))))
(defun %parse-name-and-options (spec varp)
(cond
((stringp spec)
(values (lisp-name spec varp) spec nil))
((symbolp spec)
(assert (not (null spec)))
(values spec (foreign-name spec varp) nil))
((and (consp spec) (stringp (first spec)))
(destructuring-bind (foreign-name &rest options)
spec
(cond
((or (null options)
(keywordp (first options)))
(values (lisp-name foreign-name varp) foreign-name options))
(t
(assert (lisp-name-p (first options)))
(values (first options) foreign-name (rest options))))))
((and (consp spec) (lisp-name-p (first spec)))
(destructuring-bind (lisp-name &rest options)
spec
(cond
((or (null options)
(keywordp (first options)))
(values lisp-name (foreign-name spec varp) options))
(t
(assert (stringp (first options)))
(values lisp-name (first options) (rest options))))))
(t
(error "Not a valid foreign function specifier: ~A" spec))))
;;; DEFCFUN's first argument has can have the following syntax:
;;;
;;; 1. string
;;; 2. symbol
;;; 3. \( string [symbol] options* )
;;; 4. \( symbol [string] options* )
;;;
;;; The string argument denotes the foreign function's name. The
;;; symbol argument is used to name the Lisp function. If one isn't
;;; present, its name is derived from the other. See the user
;;; documentation for an explanation of the derivation rules.
(defun parse-name-and-options (spec &optional varp)
(multiple-value-bind (lisp-name foreign-name options)
(%parse-name-and-options spec varp)
(values lisp-name foreign-name (foreign-options options varp))))
;;; If we find a &REST token at the end of ARGS, it means this is a
;;; varargs foreign function therefore we define a lisp macro using
;;; %DEFCFUN-VARARGS. Otherwise, a lisp function is defined with
;;; %DEFCFUN.
(defmacro defcfun (name-and-options return-type &body args)
"Defines a Lisp function that calls a foreign function."
(let ((docstring (when (stringp (car args)) (pop args))))
(multiple-value-bind (lisp-name foreign-name options)
(parse-name-and-options name-and-options)
(if (eq (lastcar args) '&rest)
(%defcfun-varargs lisp-name foreign-name return-type
(butlast args) options docstring)
(%defcfun lisp-name foreign-name return-type args options
docstring)))))
;;;# Defining Callbacks
(defun inverse-translate-objects (args types declarations rettype call)
`(let (,@(loop for arg in args and type in types
collect (list arg (expand-from-foreign
arg (parse-type type)))))
,@declarations
,(expand-to-foreign call (parse-type rettype))))
(defun parse-defcallback-options (options)
(destructuring-bind (&key (cconv :cdecl cconv-p)
(calling-convention cconv calling-convention-p)
(convention calling-convention))
options
(when cconv-p
(warn-obsolete-argument :cconv :convention))
(when calling-convention-p
(warn-obsolete-argument :calling-convention :convention))
(list :convention convention)))
(defmacro defcallback (name-and-options return-type args &body body)
(multiple-value-bind (body declarations)
(parse-body body :documentation t)
(let ((arg-names (mapcar #'car args))
(arg-types (mapcar #'cadr args))
(name (car (ensure-list name-and-options)))
(options (cdr (ensure-list name-and-options))))
`(progn
(%defcallback ,name ,(canonicalize-foreign-type return-type)
,arg-names ,(mapcar #'canonicalize-foreign-type arg-types)
,(inverse-translate-objects
arg-names arg-types declarations return-type
`(block ,name ,@body))
,@(parse-defcallback-options options))
',name))))
(declaim (inline get-callback))
(defun get-callback (symbol)
(%callback symbol))
(defmacro callback (name)
`(%callback ',name))

View file

@ -0,0 +1,458 @@
;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
;;;
;;; libraries.lisp --- Finding and loading foreign libraries.
;;;
;;; Copyright (C) 2005-2006, James Bielman <jamesjb@jamesjb.com>
;;; Copyright (C) 2006-2007, Luis Oliveira <loliveira@common-lisp.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.
;;;
(in-package #:cffi)
;;;# Finding Foreign Libraries
;;;
;;; We offer two ways for the user of a CFFI library to define
;;; his/her own library directories: *FOREIGN-LIBRARY-DIRECTORIES*
;;; for regular libraries and *DARWIN-FRAMEWORK-DIRECTORIES* for
;;; Darwin frameworks.
;;;
;;; These two special variables behave similarly to
;;; ASDF:*CENTRAL-REGISTRY* as its arguments are evaluated before
;;; being used. We used our MINI-EVAL instead of the full-blown EVAL
;;; and the evaluated form should yield a single pathname or a list of
;;; pathnames.
;;;
;;; Only after failing to find a library through the normal ways
;;; (eg: on Linux LD_LIBRARY_PATH, /etc/ld.so.cache, /usr/lib/, /lib)
;;; do we try to find the library ourselves.
(defun explode-path-environment-variable (name)
(mapcar #'uiop:ensure-directory-pathname
(split-if (lambda (c) (eql #\: c))
(uiop:getenv name)
:elide)))
(defun darwin-fallback-library-path ()
(or (explode-path-environment-variable "DYLD_FALLBACK_LIBRARY_PATH")
(list (merge-pathnames #p"lib/" (user-homedir-pathname))
#p"/opt/local/lib/"
#p"/usr/local/lib/"
#p"/usr/lib/")))
(defvar *foreign-library-directories*
(if (featurep :darwin)
'((explode-path-environment-variable "LD_LIBRARY_PATH")
(explode-path-environment-variable "DYLD_LIBRARY_PATH")
(uiop:getcwd)
(darwin-fallback-library-path))
'())
"List onto which user-defined library paths can be pushed.")
(defun fallback-darwin-framework-directories ()
(or (explode-path-environment-variable "DYLD_FALLBACK_FRAMEWORK_PATH")
(list (uiop:getcwd)
(merge-pathnames #p"Library/Frameworks/" (user-homedir-pathname))
#p"/Library/Frameworks/"
#p"/System/Library/Frameworks/")))
(defvar *darwin-framework-directories*
'((explode-path-environment-variable "DYLD_FRAMEWORK_PATH")
(fallback-darwin-framework-directories))
"List of directories where Frameworks are searched for.")
(defun mini-eval (form)
"Simple EVAL-like function to evaluate the elements of
*FOREIGN-LIBRARY-DIRECTORIES* and *DARWIN-FRAMEWORK-DIRECTORIES*."
(typecase form
(cons (apply (car form) (mapcar #'mini-eval (cdr form))))
(symbol (symbol-value form))
(t form)))
(defun parse-directories (list)
(mappend (compose #'ensure-list #'mini-eval) list))
(defun find-file (path directories)
"Searches for PATH in a list of DIRECTORIES and returns the first it finds."
(some (lambda (directory) (probe-file (merge-pathnames path directory)))
directories))
(defun find-darwin-framework (framework-name)
"Searches for FRAMEWORK-NAME in *DARWIN-FRAMEWORK-DIRECTORIES*."
(dolist (directory (parse-directories *darwin-framework-directories*))
(let ((path (make-pathname
:name framework-name
:directory
(append (pathname-directory directory)
(list (format nil "~A.framework" framework-name))))))
(when (probe-file path)
(return-from find-darwin-framework path)))))
;;;# Defining Foreign Libraries
;;;
;;; Foreign libraries can be defined using the
;;; DEFINE-FOREIGN-LIBRARY macro. Example usage:
;;;
;;; (define-foreign-library opengl
;;; (:darwin (:framework "OpenGL"))
;;; (:unix (:or "libGL.so" "libGL.so.1"
;;; #p"/myhome/mylibGL.so"))
;;; (:windows "opengl32.dll")
;;; ;; an hypothetical example of a particular platform
;;; ((:and :some-system :some-cpu) "libGL-support.lib")
;;; ;; if no other clauses apply, this one will and a type will be
;;; ;; automagically appended to the name passed to :default
;;; (t (:default "libGL")))
;;;
;;; This information is stored in the *FOREIGN-LIBRARIES* hashtable
;;; and when the library is loaded through LOAD-FOREIGN-LIBRARY (or
;;; USE-FOREIGN-LIBRARY) the first clause matched by FEATUREP is
;;; processed.
(defvar *foreign-libraries* (make-hash-table :test 'eq)
"Hashtable of defined libraries.")
(defclass foreign-library ()
((name :initform nil :initarg :name :accessor foreign-library-name)
(type :initform :system :initarg :type)
(spec :initarg :spec)
(options :initform nil :initarg :options)
(handle :initform nil :initarg :handle :accessor foreign-library-handle)
(pathname :initform nil)))
(defmethod print-object ((library foreign-library) stream)
(with-slots (name pathname) library
(print-unreadable-object (library stream :type t)
(when name
(format stream "~A" name))
(when pathname
(format stream " ~S" (file-namestring pathname))))))
(define-condition foreign-library-undefined-error (error)
((name :initarg :name :reader fl-name))
(:report (lambda (c s)
(format s "Undefined foreign library: ~S"
(fl-name c)))))
(defun get-foreign-library (lib)
"Look up a library by NAME, signalling an error if not found."
(if (typep lib 'foreign-library)
lib
(or (gethash lib *foreign-libraries*)
(error 'foreign-library-undefined-error :name lib))))
(defun (setf get-foreign-library) (value name)
(setf (gethash name *foreign-libraries*) value))
(defun foreign-library-type (lib)
(slot-value (get-foreign-library lib) 'type))
(defun foreign-library-pathname (lib)
(slot-value (get-foreign-library lib) 'pathname))
(defun %foreign-library-spec (lib)
(assoc-if (lambda (feature)
(or (eq feature t)
(featurep feature)))
(slot-value lib 'spec)))
(defun foreign-library-spec (lib)
(second (%foreign-library-spec lib)))
(defun foreign-library-options (lib)
(append (cddr (%foreign-library-spec lib))
(slot-value lib 'options)))
(defun foreign-library-search-path (lib)
(loop for (opt val) on (foreign-library-options lib) by #'cddr
when (eql opt :search-path)
append (ensure-list val) into search-path
finally (return (mapcar #'pathname search-path))))
(defun foreign-library-loaded-p (lib)
(not (null (foreign-library-handle (get-foreign-library lib)))))
(defun list-foreign-libraries (&key (loaded-only t) type)
"Return a list of defined foreign libraries.
If LOADED-ONLY is non-null only loaded libraries are returned.
TYPE restricts the output to a specific library type: if NIL
all libraries are returned."
(let ((libs (hash-table-values *foreign-libraries*)))
(remove-if (lambda (lib)
(or (and type
(not (eql type (foreign-library-type lib))))
(and loaded-only
(not (foreign-library-loaded-p lib)))))
libs)))
;; :CONVENTION, :CALLING-CONVENTION and :CCONV are coalesced,
;; the former taking priority
;; options with NULL values are removed
(defun clean-spec-up (spec)
(mapcar (lambda (x)
(list* (first x) (second x)
(let* ((opts (cddr x))
(cconv (getf opts :cconv))
(calling-convention (getf opts :calling-convention))
(convention (getf opts :convention))
(search-path (getf opts :search-path)))
(remf opts :cconv) (remf opts :calling-convention)
(when cconv
(warn-obsolete-argument :cconv :convention))
(when calling-convention
(warn-obsolete-argument :calling-convention
:convention))
(setf (getf opts :convention)
(or convention calling-convention cconv))
(setf (getf opts :search-path)
(mapcar #'pathname (ensure-list search-path)))
(loop for (opt val) on opts by #'cddr
when val append (list opt val) into new-opts
finally (return new-opts)))))
spec))
(defmethod initialize-instance :after
((lib foreign-library) &key search-path
(cconv :cdecl cconv-p)
(calling-convention cconv calling-convention-p)
(convention calling-convention))
(with-slots (type options spec) lib
(check-type type (member :system :test :grovel-wrapper))
(setf spec (clean-spec-up spec))
(let ((all-options
(apply #'append options (mapcar #'cddr spec))))
(assert (subsetp (loop for (key . nil) on all-options by #'cddr
collect key)
'(:convention :search-path)))
(when cconv-p
(warn-obsolete-argument :cconv :convention))
(when calling-convention-p
(warn-obsolete-argument :calling-convention :convention))
(flet ((set-option (key value)
(when value (setf (getf options key) value))))
(set-option :convention convention)
(set-option :search-path
(mapcar #'pathname (ensure-list search-path)))))))
(defun register-foreign-library (name spec &rest options)
(let ((old-handle
(when-let ((old-lib (gethash name *foreign-libraries*)))
(foreign-library-handle old-lib))))
(setf (get-foreign-library name)
(apply #'make-instance 'foreign-library
:name name
:spec spec
:handle old-handle
options))
name))
(defmacro define-foreign-library (name-and-options &body pairs)
"Defines a foreign library NAME that can be posteriorly used with
the USE-FOREIGN-LIBRARY macro."
(destructuring-bind (name . options)
(ensure-list name-and-options)
(check-type name symbol)
`(register-foreign-library ',name ',pairs ,@options)))
;;;# LOAD-FOREIGN-LIBRARY-ERROR condition
;;;
;;; The various helper functions that load foreign libraries can
;;; signal this error when something goes wrong. We ignore the host's
;;; error. We should probably reuse its error message.
(define-condition load-foreign-library-error (simple-error)
())
(defun read-new-value ()
(format *query-io* "~&Enter a new value (unevaluated): ")
(force-output *query-io*)
(read *query-io*))
(defun fl-error (control &rest arguments)
(error 'load-foreign-library-error
:format-control control
:format-arguments arguments))
;;;# Loading Foreign Libraries
(defun load-darwin-framework (name framework-name)
"Tries to find and load a darwin framework in one of the directories
in *DARWIN-FRAMEWORK-DIRECTORIES*. If unable to find FRAMEWORK-NAME,
it signals a LOAD-FOREIGN-LIBRARY-ERROR."
(let ((framework (find-darwin-framework framework-name)))
(if framework
(load-foreign-library-path name (native-namestring framework))
(fl-error "Unable to find framework ~A" framework-name))))
(defun report-simple-error (name error)
(fl-error "Unable to load foreign library (~A).~% ~A"
name
(format nil "~?" (simple-condition-format-control error)
(simple-condition-format-arguments error))))
;;; FIXME: haven't double checked whether all Lisps signal a
;;; SIMPLE-ERROR on %load-foreign-library failure. In any case they
;;; should be throwing a more specific error.
(defun load-foreign-library-path (name path &optional search-path)
"Tries to load PATH using %LOAD-FOREIGN-LIBRARY which should try and
find it using the OS's usual methods. If that fails we try to find it
ourselves."
(handler-case
(values (%load-foreign-library name path)
(pathname path))
(simple-error (error)
(let ((dirs (parse-directories *foreign-library-directories*)))
(if-let (file (find-file path (append search-path dirs)))
(handler-case
(values (%load-foreign-library name (native-namestring file))
file)
(simple-error (error)
(report-simple-error name error)))
(report-simple-error name error))))))
(defun try-foreign-library-alternatives (name library-list &optional search-path)
"Goes through a list of alternatives and only signals an error when
none of alternatives were successfully loaded."
(dolist (lib library-list)
(multiple-value-bind (handle pathname)
(ignore-errors (load-foreign-library-helper name lib search-path))
(when handle
(return-from try-foreign-library-alternatives
(values handle pathname)))))
;; Perhaps we should show the error messages we got for each
;; alternative if we can figure out a nice way to do that.
(fl-error "Unable to load any of the alternatives:~% ~S" library-list))
(defparameter *cffi-feature-suffix-map*
'((:windows . ".dll")
(:darwin . ".dylib")
(:unix . ".so")
(t . ".so"))
"Mapping of OS feature keywords to shared library suffixes.")
(defun default-library-suffix ()
"Return a string to use as default library suffix based on the
operating system. This is used to implement the :DEFAULT option.
This will need to be extended as we test on more OSes."
(or (cdr (assoc-if #'featurep *cffi-feature-suffix-map*))
(fl-error "Unable to determine the default library suffix on this OS.")))
(defun load-foreign-library-helper (name thing &optional search-path)
(etypecase thing
((or pathname string)
(load-foreign-library-path name (filter-pathname thing) search-path))
(cons
(ecase (first thing)
(:framework (load-darwin-framework name (second thing)))
(:default
(unless (stringp (second thing))
(fl-error "Argument to :DEFAULT must be a string."))
(let ((library-path
(concatenate 'string
(second thing)
(default-library-suffix))))
(load-foreign-library-path name library-path search-path)))
(:or (try-foreign-library-alternatives name (rest thing) search-path))))))
(defun %do-load-foreign-library (library search-path)
(flet ((%do-load (lib name spec)
(when (foreign-library-spec lib)
(with-slots (handle pathname) lib
(setf (values handle pathname)
(load-foreign-library-helper
name spec (foreign-library-search-path lib)))))
lib))
(etypecase library
(symbol
(let* ((lib (get-foreign-library library))
(spec (foreign-library-spec lib)))
(%do-load lib library spec)))
((or string list)
(let* ((lib-name (gensym
(format nil "~:@(~A~)-"
(if (listp library)
(first library)
(file-namestring library)))))
(lib (make-instance 'foreign-library
:type :system
:name lib-name
:spec `((t ,library))
:search-path search-path)))
;; first try to load the anonymous library
;; and register it only if that worked
(%do-load lib lib-name library)
(setf (get-foreign-library lib-name) lib))))))
(defun filter-pathname (thing)
(typecase thing
(pathname (namestring thing))
(t thing)))
(defun load-foreign-library (library &key search-path)
"Loads a foreign LIBRARY which can be a symbol denoting a library defined
through DEFINE-FOREIGN-LIBRARY; a pathname or string in which case we try to
load it directly first then search for it in *FOREIGN-LIBRARY-DIRECTORIES*;
or finally list: either (:or lib1 lib2) or (:framework <framework-name>)."
(let ((library (filter-pathname library)))
(restart-case
(progn
;; dlopen/dlclose does reference counting, but the CFFI-SYS
;; API has no infrastructure to track that. Therefore if we
;; want to avoid increasing the internal dlopen reference
;; counter, and thus thwarting dlclose, then we need to try
;; to call CLOSE-FOREIGN-LIBRARY and ignore any signaled
;; errors.
(ignore-some-conditions (foreign-library-undefined-error)
(close-foreign-library library))
(%do-load-foreign-library library search-path))
;; Offer these restarts that will retry the call to
;; %LOAD-FOREIGN-LIBRARY.
(retry ()
:report "Try loading the foreign library again."
(load-foreign-library library :search-path search-path))
(use-value (new-library)
:report "Use another library instead."
:interactive read-new-value
(load-foreign-library new-library :search-path search-path)))))
(defmacro use-foreign-library (name)
`(load-foreign-library ',name))
;;;# Closing Foreign Libraries
(defun close-foreign-library (library)
"Closes a foreign library."
(let* ((library (filter-pathname library))
(lib (get-foreign-library library))
(handle (foreign-library-handle lib)))
(when handle
(%close-foreign-library handle)
(setf (foreign-library-handle lib) nil)
t)))
(defun reload-foreign-libraries (&key (test #'foreign-library-loaded-p))
"(Re)load all currently loaded foreign libraries."
(let ((libs (list-foreign-libraries)))
(loop for l in libs
for name = (foreign-library-name l)
when (funcall test name)
do (load-foreign-library name))
libs))

View file

@ -0,0 +1,181 @@
;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
;;;
;;; package.lisp --- Package definition for CFFI.
;;;
;;; Copyright (C) 2005-2006, James Bielman <jamesjb@jamesjb.com>
;;;
;;; 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.
;;;
(in-package #:cl-user)
(defpackage #:cffi
(:use #:common-lisp #:cffi-sys #:babel-encodings)
(:import-from #:alexandria
#:compose
#:ensure-list
#:featurep
#:format-symbol
#:hash-table-values
#:if-let
#:ignore-some-conditions
#:lastcar
#:make-gensym-list
#:make-keyword
#:mappend
#:once-only
#:parse-body
#:simple-style-warning
#:symbolicate
#:unwind-protect-case
#:when-let
#:with-unique-names)
(:export
;; Types.
#:foreign-pointer
;; FIXME: the following types are undocumented. They should
;; probably be replaced with a proper type introspection API
;; though.
#:*built-in-foreign-types*
#:*other-builtin-types*
#:*built-in-integer-types*
#:*built-in-float-types*
;; Primitive pointer operations.
#:foreign-free
#:foreign-alloc
#:mem-aptr
#:mem-aref
#:mem-ref
#:pointerp
#:pointer-eq
#:null-pointer
#:null-pointer-p
#:inc-pointer
#:incf-pointer
#:with-foreign-pointer
#:make-pointer
#:pointer-address
;; Shareable vectors.
#:make-shareable-byte-vector
#:with-pointer-to-vector-data
;; Foreign string operations.
#:*default-foreign-encoding*
#:foreign-string-alloc
#:foreign-string-free
#:foreign-string-to-lisp
#:lisp-string-to-foreign
#:with-foreign-string
#:with-foreign-strings
#:with-foreign-pointer-as-string
;; Foreign array operations.
;; TODO: document these
#:foreign-array-alloc
#:foreign-array-free
#:foreign-array-to-lisp
#:lisp-array-to-foreign
#:with-foreign-array
#:foreign-aref
;; Foreign function operations.
#:defcfun
#:foreign-funcall
#:foreign-funcall-pointer
#:foreign-funcall-varargs
#:foreign-funcall-pointer-varargs
#:translate-camelcase-name
#:translate-name-from-foreign
#:translate-name-to-foreign
#:translate-underscore-separated-name
;; Foreign library operations.
#:*foreign-library-directories*
#:*darwin-framework-directories*
#:foreign-library
#:foreign-library-name
#:foreign-library-pathname
#:foreign-library-type
#:foreign-library-loaded-p
#:list-foreign-libraries
#:define-foreign-library
#:load-foreign-library
#:load-foreign-library-error
#:use-foreign-library
#:close-foreign-library
#:reload-foreign-libraries
;; Callbacks.
#:callback
#:get-callback
#:defcallback
;; Foreign type operations.
#:defcstruct
#:defcunion
#:defctype
#:defcenum
#:defbitfield
#:define-foreign-type
#:define-parse-method
#:define-c-struct-wrapper
#:foreign-enum-keyword
#:foreign-enum-keyword-list
#:foreign-enum-value
#:foreign-bitfield-symbol-list
#:foreign-bitfield-symbols
#:foreign-bitfield-value
#:foreign-slot-pointer
#:foreign-slot-value
#:foreign-slot-type
#:foreign-slot-offset
#:foreign-slot-count
#:foreign-slot-names
#:foreign-type-alignment
#:foreign-type-size
#:with-foreign-object
#:with-foreign-objects
#:with-foreign-slots
#:convert-to-foreign
#:convert-from-foreign
#:convert-into-foreign-memory
#:free-converted-object
#:translation-forms-for-class
;; Extensible foreign type operations.
#:define-translation-method ; FIXME: undocumented
#:translate-to-foreign
#:translate-from-foreign
#:translate-into-foreign-memory
#:free-translated-object
#:expand-to-foreign-dyn
#:expand-to-foreign
#:expand-from-foreign
#:expand-into-foreign-memory
;; Foreign globals.
#:defcvar
#:get-var-pointer
#:foreign-symbol-pointer
))

View file

@ -0,0 +1,305 @@
;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
;;;
;;; strings.lisp --- Operations on foreign strings.
;;;
;;; Copyright (C) 2005-2006, James Bielman <jamesjb@jamesjb.com>
;;; Copyright (C) 2005-2007, Luis Oliveira <loliveira@common-lisp.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.
;;;
(in-package #:cffi)
;;;# Foreign String Conversion
;;;
;;; Functions for converting NULL-terminated C-strings to Lisp strings
;;; and vice versa. The string functions accept an ENCODING keyword
;;; argument which is used to specify the encoding to use when
;;; converting to/from foreign strings.
(defvar *default-foreign-encoding* :utf-8
"Default foreign encoding.")
;;; TODO: refactor, sigh. Also, this should probably be a function.
(defmacro bget (ptr off &optional (bytes 1) (endianness :ne))
(let ((big-endian (member endianness
'(:be #+big-endian :ne #+little-endian :re))))
(once-only (ptr off)
(ecase bytes
(1 `(mem-ref ,ptr :uint8 ,off))
(2 (if big-endian
#+big-endian
`(mem-ref ,ptr :uint16 ,off)
#-big-endian
`(dpb (mem-ref ,ptr :uint8 ,off) (byte 8 8)
(mem-ref ,ptr :uint8 (1+ ,off)))
#+little-endian
`(mem-ref ,ptr :uint16 ,off)
#-little-endian
`(dpb (mem-ref ,ptr :uint8 (1+ ,off)) (byte 8 8)
(mem-ref ,ptr :uint8 ,off))))
(4 (if big-endian
#+big-endian
`(mem-ref ,ptr :uint32 ,off)
#-big-endian
`(dpb (mem-ref ,ptr :uint8 ,off) (byte 8 24)
(dpb (mem-ref ,ptr :uint8 (1+ ,off)) (byte 8 16)
(dpb (mem-ref ,ptr :uint8 (+ ,off 2)) (byte 8 8)
(mem-ref ,ptr :uint8 (+ ,off 3)))))
#+little-endian
`(mem-ref ,ptr :uint32 ,off)
#-little-endian
`(dpb (mem-ref ,ptr :uint8 (+ ,off 3)) (byte 8 24)
(dpb (mem-ref ,ptr :uint8 (+ ,off 2)) (byte 8 16)
(dpb (mem-ref ,ptr :uint8 (1+ ,off)) (byte 8 8)
(mem-ref ,ptr :uint8 ,off))))))))))
(defmacro bset (val ptr off &optional (bytes 1) (endianness :ne))
(let ((big-endian (member endianness
'(:be #+big-endian :ne #+little-endian :re))))
(ecase bytes
(1 `(setf (mem-ref ,ptr :uint8 ,off) ,val))
(2 (if big-endian
#+big-endian
`(setf (mem-ref ,ptr :uint16 ,off) ,val)
#-big-endian
`(setf (mem-ref ,ptr :uint8 (1+ ,off)) (ldb (byte 8 0) ,val)
(mem-ref ,ptr :uint8 ,off) (ldb (byte 8 8) ,val))
#+little-endian
`(setf (mem-ref ,ptr :uint16 ,off) ,val)
#-little-endian
`(setf (mem-ref ,ptr :uint8 ,off) (ldb (byte 8 0) ,val)
(mem-ref ,ptr :uint8 (1+ ,off)) (ldb (byte 8 8) ,val))))
(4 (if big-endian
#+big-endian
`(setf (mem-ref ,ptr :uint32 ,off) ,val)
#-big-endian
`(setf (mem-ref ,ptr :uint8 (+ 3 ,off)) (ldb (byte 8 0) ,val)
(mem-ref ,ptr :uint8 (+ 2 ,off)) (ldb (byte 8 8) ,val)
(mem-ref ,ptr :uint8 (1+ ,off)) (ldb (byte 8 16) ,val)
(mem-ref ,ptr :uint8 ,off) (ldb (byte 8 24) ,val))
#+little-endian
`(setf (mem-ref ,ptr :uint32 ,off) ,val)
#-little-endian
`(setf (mem-ref ,ptr :uint8 ,off) (ldb (byte 8 0) ,val)
(mem-ref ,ptr :uint8 (1+ ,off)) (ldb (byte 8 8) ,val)
(mem-ref ,ptr :uint8 (+ ,off 2)) (ldb (byte 8 16) ,val)
(mem-ref ,ptr :uint8 (+ ,off 3)) (ldb (byte 8 24) ,val)))))))
;;; TODO: tackle optimization notes.
(defparameter *foreign-string-mappings*
(instantiate-concrete-mappings
;; :optimize ((speed 3) (debug 0) (compilation-speed 0) (safety 0))
:octet-seq-getter bget
:octet-seq-setter bset
:octet-seq-type foreign-pointer
:code-point-seq-getter babel::string-get
:code-point-seq-setter babel::string-set
:code-point-seq-type babel:simple-unicode-string))
(defun null-terminator-len (encoding)
(length (enc-nul-encoding (get-character-encoding encoding))))
(defun lisp-string-to-foreign (string buffer bufsize &key (start 0) end offset
(encoding *default-foreign-encoding*))
(check-type string string)
(when offset
(setq buffer (inc-pointer buffer offset)))
(with-checked-simple-vector ((string (coerce string 'babel:unicode-string))
(start start) (end end))
(declare (type simple-string string))
(let ((mapping (lookup-mapping *foreign-string-mappings* encoding))
(nul-len (null-terminator-len encoding)))
(assert (plusp bufsize))
(multiple-value-bind (size end)
(funcall (octet-counter mapping) string start end (- bufsize nul-len))
(funcall (encoder mapping) string start end buffer 0)
(dotimes (i nul-len)
(setf (mem-ref buffer :char (+ size i)) 0))))
buffer))
;;; Expands into a loop that calculates the length of the foreign
;;; string at PTR plus OFFSET, using ACCESSOR and looking for a null
;;; terminator of LENGTH bytes.
(defmacro %foreign-string-length (ptr offset type length)
(once-only (ptr offset)
`(do ((i 0 (+ i ,length)))
((zerop (mem-ref ,ptr ,type (+ ,offset i))) i)
(declare (fixnum i)))))
;;; Return the length in octets of the null terminated foreign string
;;; at POINTER plus OFFSET octets, assumed to be encoded in ENCODING,
;;; a CFFI encoding. This should be smart enough to look for 8-bit vs
;;; 16-bit null terminators, as appropriate for the encoding.
(defun foreign-string-length (pointer &key (encoding *default-foreign-encoding*)
(offset 0))
(ecase (null-terminator-len encoding)
(1 (%foreign-string-length pointer offset :uint8 1))
(2 (%foreign-string-length pointer offset :uint16 2))
(4 (%foreign-string-length pointer offset :uint32 4))))
(defun foreign-string-to-lisp (pointer &key (offset 0) count
(max-chars (1- array-total-size-limit))
(encoding *default-foreign-encoding*))
"Copy at most COUNT bytes from POINTER plus OFFSET encoded in
ENCODING into a Lisp string and return it. If POINTER is a null
pointer, NIL is returned."
(unless (null-pointer-p pointer)
(let ((count (or count
(foreign-string-length
pointer :encoding encoding :offset offset)))
(mapping (lookup-mapping *foreign-string-mappings* encoding)))
(assert (plusp max-chars))
(multiple-value-bind (size new-end)
(funcall (code-point-counter mapping)
pointer offset (+ offset count) max-chars)
(let ((string (make-string size :element-type 'babel:unicode-char)))
(funcall (decoder mapping) pointer offset new-end string 0)
(values string (- new-end offset)))))))
;;;# Using Foreign Strings
(defun foreign-string-alloc (string &key (encoding *default-foreign-encoding*)
(null-terminated-p t) (start 0) end)
"Allocate a foreign string containing Lisp string STRING.
The string must be freed with FOREIGN-STRING-FREE."
(check-type string string)
(with-checked-simple-vector ((string (coerce string 'babel:unicode-string))
(start start) (end end))
(declare (type simple-string string))
(let* ((mapping (lookup-mapping *foreign-string-mappings* encoding))
(count (funcall (octet-counter mapping) string start end 0))
(nul-length (if null-terminated-p
(null-terminator-len encoding)
0))
(length (+ count nul-length))
(ptr (foreign-alloc :char :count length)))
(funcall (encoder mapping) string start end ptr 0)
(dotimes (i nul-length)
(setf (mem-ref ptr :char (+ count i)) 0))
(values ptr length))))
(defun foreign-string-free (ptr)
"Free a foreign string allocated by FOREIGN-STRING-ALLOC."
(foreign-free ptr))
(defmacro with-foreign-string ((var-or-vars lisp-string &rest args) &body body)
"VAR-OR-VARS is not evaluated and should be a list of the form
\(VAR &OPTIONAL BYTE-SIZE-VAR) or just a VAR symbol. VAR is
bound to a foreign string containing LISP-STRING in BODY. When
BYTE-SIZE-VAR is specified then bind the C buffer size
\(including the possible null terminator\(s)) to this variable."
(destructuring-bind (var &optional size-var)
(ensure-list var-or-vars)
`(multiple-value-bind (,var ,@(when size-var (list size-var)))
(foreign-string-alloc ,lisp-string ,@args)
(unwind-protect
(progn ,@body)
(foreign-string-free ,var)))))
(defmacro with-foreign-strings (bindings &body body)
"See WITH-FOREIGN-STRING's documentation."
(if bindings
`(with-foreign-string ,(first bindings)
(with-foreign-strings ,(rest bindings)
,@body))
`(progn ,@body)))
(defmacro with-foreign-pointer-as-string
((var-or-vars size &rest args) &body body)
"VAR-OR-VARS is not evaluated and should be a list of the form
\(VAR &OPTIONAL SIZE-VAR) or just a VAR symbol. VAR is bound to
a foreign buffer of size SIZE within BODY. The return value is
constructed by calling FOREIGN-STRING-TO-LISP on the foreign
buffer along with ARGS." ; fix wording, sigh
(destructuring-bind (var &optional size-var)
(ensure-list var-or-vars)
`(with-foreign-pointer (,var ,size ,size-var)
(progn
,@body
(values (foreign-string-to-lisp ,var ,@args))))))
;;;# Automatic Conversion of Foreign Strings
(define-foreign-type foreign-string-type ()
(;; CFFI encoding of this string.
(encoding :initform nil :initarg :encoding :reader encoding)
;; Should we free after translating from foreign?
(free-from-foreign :initarg :free-from-foreign
:reader fst-free-from-foreign-p
:initform nil :type boolean)
;; Should we free after translating to foreign?
(free-to-foreign :initarg :free-to-foreign
:reader fst-free-to-foreign-p
:initform t :type boolean))
(:actual-type :pointer)
(:simple-parser :string))
;;; describe me
(defun fst-encoding (type)
(or (encoding type) *default-foreign-encoding*))
;;; Display the encoding when printing a FOREIGN-STRING-TYPE instance.
(defmethod print-object ((type foreign-string-type) stream)
(print-unreadable-object (type stream :type t)
(format stream "~S" (fst-encoding type))))
(defmethod translate-to-foreign ((s string) (type foreign-string-type))
(values (foreign-string-alloc s :encoding (fst-encoding type))
(fst-free-to-foreign-p type)))
(defmethod translate-to-foreign (obj (type foreign-string-type))
(cond
((pointerp obj)
(values obj nil))
;; FIXME: we used to support UB8 vectors but not anymore.
;; ((typep obj '(array (unsigned-byte 8)))
;; (values (foreign-string-alloc obj) t))
(t (error "~A is not a Lisp string or pointer." obj))))
(defmethod translate-from-foreign (ptr (type foreign-string-type))
(unwind-protect
(values (foreign-string-to-lisp ptr :encoding (fst-encoding type)))
(when (fst-free-from-foreign-p type)
(foreign-free ptr))))
(defmethod free-translated-object (ptr (type foreign-string-type) free-p)
(when free-p
(foreign-string-free ptr)))
(defmethod expand-to-foreign-dyn-indirect
(value var body (type foreign-string-type))
(alexandria:with-gensyms (str)
(expand-to-foreign-dyn
value
str
(list
(expand-to-foreign-dyn-indirect str var body (parse-type :pointer)))
type)))
;;;# STRING+PTR
(define-foreign-type foreign-string+ptr-type (foreign-string-type)
()
(:simple-parser :string+ptr))
(defmethod translate-from-foreign (value (type foreign-string+ptr-type))
(list (call-next-method) value))

View file

@ -0,0 +1,133 @@
;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
;;;
;;; structures.lisp --- Methods for translating foreign structures.
;;;
;;; Copyright (C) 2011, Liam M. Healy <lhealy@common-lisp.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.
;;;
(in-package #:cffi)
;;; Definitions for conversion of foreign structures.
(defmethod translate-into-foreign-memory ((object list)
(type foreign-struct-type)
p)
(unless (bare-struct-type-p type)
(loop for (name value) on object by #'cddr
do (setf (foreign-slot-value p (unparse-type type) name)
(let ((slot (gethash name (structure-slots type))))
(convert-to-foreign value (slot-type slot)))))))
(defmethod translate-to-foreign (value (type foreign-struct-type))
(let ((ptr (foreign-alloc type)))
(translate-into-foreign-memory value type ptr)
ptr))
(defmethod translate-from-foreign (p (type foreign-struct-type))
;; Iterate over slots, make plist
(if (bare-struct-type-p type)
p
(let ((plist (list)))
(loop for slot being the hash-value of (structure-slots type)
for name = (slot-name slot)
do (setf (getf plist name)
(foreign-struct-slot-value p slot)))
plist)))
(defmethod free-translated-object (ptr (type foreign-struct-type) freep)
(unless (bare-struct-type-p type)
;; Look for any pointer slots and free them first
(loop for slot being the hash-value of (structure-slots type)
when (and (listp (slot-type slot)) (eq (first (slot-type slot)) :pointer))
do
;; Free if the pointer is to a specific type, not generic :pointer
(free-translated-object
(foreign-slot-value ptr type (slot-name slot))
(rest (slot-type slot))
freep))
(foreign-free ptr)))
(defmacro define-translation-method ((object type method) &body body)
"Define a translation method for the foreign structure type; 'method is one of :into, :from, or :to, meaning relation to foreign memory. If :into, the variable 'pointer is the foreign pointer. Note: type must be defined and loaded before this macro is expanded, and just the bare name (without :struct) should be specified."
(let ((tclass (class-name (class-of (cffi::parse-type `(:struct ,type))))))
(when (eq tclass 'foreign-struct-type)
(error "Won't replace existing translation method for foreign-struct-type"))
`(defmethod
,(case method
(:into 'translate-into-foreign-memory)
(:from 'translate-from-foreign)
(:to 'translate-to-foreign))
;; Arguments to the method
(,object
(type ,tclass)
,@(when (eq method :into) '(pointer))) ; is intentional variable capture a good idea?
;; The body
(declare (ignorable type)) ; I can't think of a reason why you'd want to use this
,@body)))
(defmacro translation-forms-for-class (class type-class)
"Make forms for translation of foreign structures to and from a standard class. The class slots are assumed to have the same name as the foreign structure."
;; Possible improvement: optional argument to map structure slot names to/from class slot names.
`(progn
(defmethod translate-from-foreign (pointer (type ,type-class))
;; Make the instance from the plist
(apply 'make-instance ',class (call-next-method)))
(defmethod translate-into-foreign-memory ((object ,class) (type ,type-class) pointer)
(call-next-method
;; Translate into a plist and call the general method
(loop for slot being the hash-value of (structure-slots type)
for name = (slot-name slot)
append (list slot-name (slot-value object slot-name)))
type
pointer))))
;;; For a class already defined and loaded, and a defcstruct already defined, use
;;; (translation-forms-for-class class type-class)
;;; to connnect the two. It would be nice to have a macro to do all three simultaneously.
;;; (defmacro define-foreign-structure (class ))
#|
(defmacro define-structure-conversion
(value-symbol type lisp-class slot-names to-form from-form &optional (struct-name type))
"Define the functions necessary to convert to and from a foreign structure. The to-form sets each of the foreign slots in succession, assume the foreign object exists. The from-form creates the Lisp object, making it with the correct value by reference to foreign slots."
`(flet ((map-slots (fn val)
(maphash
(lambda (name slot-struct)
(funcall fn (foreign-slot-value val ',type name) (slot-type slot-struct)))
(slots (follow-typedefs (parse-type ',type))))))
;; Convert this to a separate function so it doesn't have to be recomputed on the fly each time.
(defmethod translate-to-foreign ((,value-symbol ,lisp-class) (type ,type))
(let ((p (foreign-alloc ',struct-name)))
;;(map-slots #'translate-to-foreign ,value-symbol) ; recursive translation of slots
(with-foreign-slots (,slot-names p ,struct-name)
,to-form)
(values p t))) ; second value is passed to FREE-TRANSLATED-OBJECT
(defmethod free-translated-object (,value-symbol (p ,type) freep)
(when freep
;; Is this redundant?
(map-slots #'free-translated-object value) ; recursively free slots
(foreign-free ,value-symbol)))
(defmethod translate-from-foreign (,value-symbol (type ,type))
(with-foreign-slots (,slot-names ,value-symbol ,struct-name)
,from-form))))
|#

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,84 @@
;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
;;;
;;; utils.lisp --- Various utilities.
;;;
;;; Copyright (C) 2005-2008, Luis Oliveira <loliveira(@)common-lisp.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.
;;;
(in-package #:cffi)
(defmacro discard-docstring (body-var &optional force)
"Discards the first element of the list in body-var if it's a
string and the only element (or if FORCE is T)."
`(when (and (stringp (car ,body-var)) (or ,force (cdr ,body-var)))
(pop ,body-var)))
(defun single-bit-p (integer)
"Answer whether INTEGER, which must be an integer, is a single
set twos-complement bit."
(if (<= integer 0)
nil ; infinite set bits for negatives
(loop until (logbitp 0 integer)
do (setf integer (ash integer -1))
finally (return (zerop (ash integer -1))))))
;;; This function is here because it needs to be defined early. It's
;;; used by DEFINE-PARSE-METHOD and DEFCTYPE to warn users when
;;; they're defining types whose names belongs to the KEYWORD or CL
;;; packages. CFFI itself gets to use keywords without a warning.
(defun warn-if-kw-or-belongs-to-cl (name)
(let ((package (symbol-package name)))
(when (and (not (eq *package* (find-package '#:cffi)))
(member package '(#:common-lisp #:keyword)
:key #'find-package))
(warn "Defining a foreign type named ~S. This symbol belongs to the ~A ~
package and that may interfere with other code using CFFI."
name (package-name package)))))
(define-condition obsolete-argument-warning (style-warning)
((old-arg :initarg :old-arg :reader old-arg)
(new-arg :initarg :new-arg :reader new-arg))
(:report (lambda (c s)
(format s "Keyword ~S is obsolete, please use ~S"
(old-arg c) (new-arg c)))))
(defun warn-obsolete-argument (old-arg new-arg)
(warn 'obsolete-argument-warning
:old-arg old-arg :new-arg new-arg))
(defun split-if (test seq &optional (dir :before))
(remove-if #'(lambda (x) (equal x (subseq seq 0 0)))
(loop for start fixnum = 0
then (if (eq dir :before)
stop
(the fixnum (1+ (the fixnum stop))))
while (< start (length seq))
for stop = (position-if test seq
:start (if (eq dir :elide)
start
(the fixnum (1+ start))))
collect (subseq seq start
(if (and stop (eq dir :after))
(the fixnum (1+ (the fixnum stop)))
stop))
while stop)))