cffi-clisp.lisp - clic - Clic is an command line interactive client for gopher written in Common LISP
 (HTM) git clone git://bitreich.org/clic/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/clic/
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Tags
 (DIR) README
 (DIR) LICENSE
       ---
       cffi-clisp.lisp (15949B)
       ---
            1 ;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
            2 ;;;
            3 ;;; cffi-clisp.lisp --- CFFI-SYS implementation for CLISP.
            4 ;;;
            5 ;;; Copyright (C) 2005-2006, James Bielman  <jamesjb@jamesjb.com>
            6 ;;; Copyright (C) 2005-2006, Joerg Hoehle  <hoehle@users.sourceforge.net>
            7 ;;;
            8 ;;; Permission is hereby granted, free of charge, to any person
            9 ;;; obtaining a copy of this software and associated documentation
           10 ;;; files (the "Software"), to deal in the Software without
           11 ;;; restriction, including without limitation the rights to use, copy,
           12 ;;; modify, merge, publish, distribute, sublicense, and/or sell copies
           13 ;;; of the Software, and to permit persons to whom the Software is
           14 ;;; furnished to do so, subject to the following conditions:
           15 ;;;
           16 ;;; The above copyright notice and this permission notice shall be
           17 ;;; included in all copies or substantial portions of the Software.
           18 ;;;
           19 ;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
           20 ;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
           21 ;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
           22 ;;; NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
           23 ;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
           24 ;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
           25 ;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
           26 ;;; DEALINGS IN THE SOFTWARE.
           27 ;;;
           28 
           29 ;;;# Administrivia
           30 
           31 (defpackage #:cffi-sys
           32   (:use #:common-lisp #:alexandria)
           33   (:export
           34    #:canonicalize-symbol-name-case
           35    #:foreign-pointer
           36    #:pointerp
           37    #:pointer-eq
           38    #:null-pointer
           39    #:null-pointer-p
           40    #:inc-pointer
           41    #:make-pointer
           42    #:pointer-address
           43    #:%foreign-alloc
           44    #:foreign-free
           45    #:with-foreign-pointer
           46    #:%foreign-funcall
           47    #:%foreign-funcall-pointer
           48    #:%foreign-type-alignment
           49    #:%foreign-type-size
           50    #:%load-foreign-library
           51    #:%close-foreign-library
           52    #:native-namestring
           53    #:%mem-ref
           54    #:%mem-set
           55    #:make-shareable-byte-vector
           56    #:with-pointer-to-vector-data
           57    #:%foreign-symbol-pointer
           58    #:%defcallback
           59    #:%callback))
           60 
           61 (in-package #:cffi-sys)
           62 
           63 (eval-when (:compile-toplevel :load-toplevel :execute)
           64   (unless (find-package :ffi)
           65     (error "CFFI requires CLISP compiled with dynamic FFI support.")))
           66 
           67 ;;;# Symbol Case
           68 
           69 (defun canonicalize-symbol-name-case (name)
           70   (declare (string name))
           71   (string-upcase name))
           72 
           73 ;;;# Built-In Foreign Types
           74 
           75 (defun convert-foreign-type (type)
           76   "Convert a CFFI built-in type keyword to a CLisp FFI type."
           77   (ecase type
           78     (:char 'ffi:char)
           79     (:unsigned-char 'ffi:uchar)
           80     (:short 'ffi:short)
           81     (:unsigned-short 'ffi:ushort)
           82     (:int 'ffi:int)
           83     (:unsigned-int 'ffi:uint)
           84     (:long 'ffi:long)
           85     (:unsigned-long 'ffi:ulong)
           86     (:long-long 'ffi:sint64)
           87     (:unsigned-long-long 'ffi:uint64)
           88     (:float 'ffi:single-float)
           89     (:double 'ffi:double-float)
           90     ;; Clisp's FFI:C-POINTER converts NULL to NIL. For now
           91     ;; we have a workaround in the pointer operations...
           92     (:pointer 'ffi:c-pointer)
           93     (:void nil)))
           94 
           95 (defun %foreign-type-size (type)
           96   "Return the size in bytes of objects having foreign type TYPE."
           97   (nth-value 0 (ffi:sizeof (convert-foreign-type type))))
           98 
           99 ;; Remind me to buy a beer for whoever made getting the alignment
          100 ;; of foreign types part of the public interface in CLisp. :-)
          101 (defun %foreign-type-alignment (type)
          102   "Return the structure alignment in bytes of foreign TYPE."
          103   #+(and darwin ppc)
          104   (case type
          105     ((:double :long-long :unsigned-long-long)
          106      (return-from %foreign-type-alignment 8)))
          107   ;; Override not necessary for the remaining types...
          108   (nth-value 1 (ffi:sizeof (convert-foreign-type type))))
          109 
          110 ;;;# Basic Pointer Operations
          111 
          112 (deftype foreign-pointer ()
          113   'ffi:foreign-address)
          114 
          115 (defun pointerp (ptr)
          116   "Return true if PTR is a foreign pointer."
          117   (typep ptr 'ffi:foreign-address))
          118 
          119 (defun pointer-eq (ptr1 ptr2)
          120   "Return true if PTR1 and PTR2 point to the same address."
          121   (eql (ffi:foreign-address-unsigned ptr1)
          122        (ffi:foreign-address-unsigned ptr2)))
          123 
          124 (defun null-pointer ()
          125   "Return a null foreign pointer."
          126   (ffi:unsigned-foreign-address 0))
          127 
          128 (defun null-pointer-p (ptr)
          129   "Return true if PTR is a null foreign pointer."
          130   (zerop (ffi:foreign-address-unsigned ptr)))
          131 
          132 (defun inc-pointer (ptr offset)
          133   "Return a pointer pointing OFFSET bytes past PTR."
          134   (ffi:unsigned-foreign-address
          135    (+ offset (ffi:foreign-address-unsigned ptr))))
          136 
          137 (defun make-pointer (address)
          138   "Return a pointer pointing to ADDRESS."
          139   (ffi:unsigned-foreign-address address))
          140 
          141 (defun pointer-address (ptr)
          142   "Return the address pointed to by PTR."
          143   (ffi:foreign-address-unsigned ptr))
          144 
          145 ;;;# Foreign Memory Allocation
          146 
          147 (defun %foreign-alloc (size)
          148   "Allocate SIZE bytes of foreign-addressable memory and return a
          149 pointer to the allocated block.  An implementation-specific error
          150 is signalled if the memory cannot be allocated."
          151   (ffi:foreign-address
          152    (ffi:allocate-shallow 'ffi:uint8 :count (if (zerop size) 1 size))))
          153 
          154 (defun foreign-free (ptr)
          155   "Free a pointer PTR allocated by FOREIGN-ALLOC.  The results
          156 are undefined if PTR is used after being freed."
          157   (ffi:foreign-free ptr))
          158 
          159 (defmacro with-foreign-pointer ((var size &optional size-var) &body body)
          160   "Bind VAR to a pointer to SIZE bytes of foreign-addressable
          161 memory during BODY.  Both PTR and the memory block pointed to
          162 have dynamic extent and may be stack allocated if supported by
          163 the implementation.  If SIZE-VAR is supplied, it will be bound to
          164 SIZE during BODY."
          165   (unless size-var
          166     (setf size-var (gensym "SIZE")))
          167   (let ((obj-var (gensym)))
          168     `(let ((,size-var ,size))
          169        (ffi:with-foreign-object
          170            (,obj-var `(ffi:c-array ffi:uint8 ,,size-var))
          171          (let ((,var (ffi:foreign-address ,obj-var)))
          172            ,@body)))))
          173 
          174 ;;;# Memory Access
          175 
          176 ;;; %MEM-REF and its compiler macro work around CLISP's FFI:C-POINTER
          177 ;;; type and convert NILs back to null pointers.
          178 (defun %mem-ref (ptr type &optional (offset 0))
          179   "Dereference a pointer OFFSET bytes from PTR to an object of
          180 built-in foreign TYPE.  Returns the object as a foreign pointer
          181 or Lisp number."
          182   (let ((value (ffi:memory-as ptr (convert-foreign-type type) offset)))
          183     (if (eq type :pointer)
          184         (or value (null-pointer))
          185         value)))
          186 
          187 (define-compiler-macro %mem-ref (&whole form ptr type &optional (offset 0))
          188   "Compiler macro to open-code when TYPE is constant."
          189   (if (constantp type)
          190       (let* ((ftype (convert-foreign-type (eval type)))
          191              (form `(ffi:memory-as ,ptr ',ftype ,offset)))
          192         (if (eq type :pointer)
          193             `(or ,form (null-pointer))
          194             form))
          195       form))
          196 
          197 (defun %mem-set (value ptr type &optional (offset 0))
          198   "Set a pointer OFFSET bytes from PTR to an object of built-in
          199 foreign TYPE to VALUE."
          200   (setf (ffi:memory-as ptr (convert-foreign-type type) offset) value))
          201 
          202 (define-compiler-macro %mem-set
          203     (&whole form value ptr type &optional (offset 0))
          204   (if (constantp type)
          205       ;; (setf (ffi:memory-as) value) is exported, but not so nice
          206       ;; w.r.t. the left to right evaluation rule
          207       `(ffi::write-memory-as
          208         ,value ,ptr ',(convert-foreign-type (eval type)) ,offset)
          209       form))
          210 
          211 ;;;# Shareable Vectors
          212 ;;;
          213 ;;; This interface is very experimental.  WITH-POINTER-TO-VECTOR-DATA
          214 ;;; should be defined to perform a copy-in/copy-out if the Lisp
          215 ;;; implementation can't do this.
          216 
          217 (declaim (inline make-shareable-byte-vector))
          218 (defun make-shareable-byte-vector (size)
          219   "Create a Lisp vector of SIZE bytes can passed to
          220 WITH-POINTER-TO-VECTOR-DATA."
          221   (make-array size :element-type '(unsigned-byte 8)))
          222 
          223 (deftype shareable-byte-vector ()
          224   `(vector (unsigned-byte 8)))
          225 
          226 (defmacro with-pointer-to-vector-data ((ptr-var vector) &body body)
          227   "Bind PTR-VAR to a foreign pointer to the data in VECTOR."
          228   (with-unique-names (vector-var size-var)
          229     `(let ((,vector-var ,vector))
          230        (check-type ,vector-var shareable-byte-vector)
          231        (with-foreign-pointer (,ptr-var (length ,vector-var) ,size-var)
          232          ;; copy-in
          233          (loop for i below ,size-var do
          234                (%mem-set (aref ,vector-var i) ,ptr-var :unsigned-char i))
          235          (unwind-protect (progn ,@body)
          236            ;; copy-out
          237            (loop for i below ,size-var do
          238                  (setf (aref ,vector-var i)
          239                        (%mem-ref ,ptr-var :unsigned-char i))))))))
          240 
          241 ;;;# Foreign Function Calling
          242 
          243 (defun parse-foreign-funcall-args (args)
          244   "Return three values, a list of CLISP FFI types, a list of
          245 values to pass to the function, and the CLISP FFI return type."
          246   (let ((return-type nil))
          247     (loop for (type arg) on args by #'cddr
          248           if arg collect (list (gensym) (convert-foreign-type type)) into types
          249           and collect arg into fargs
          250           else do (setf return-type (convert-foreign-type type))
          251           finally (return (values types fargs return-type)))))
          252 
          253 (defun convert-calling-convention (convention)
          254   (ecase convention
          255     (:stdcall :stdc-stdcall)
          256     (:cdecl :stdc)))
          257 
          258 (defun c-function-type (arg-types rettype convention)
          259   "Generate the apropriate CLISP foreign type specification. Also
          260 takes care of converting the calling convention names."
          261   `(ffi:c-function (:arguments ,@arg-types)
          262                    (:return-type ,rettype)
          263                    (:language ,(convert-calling-convention convention))))
          264 
          265 ;;; Quick hack around the fact that the CFFI package is not yet
          266 ;;; defined when this file is loaded.  I suppose we could arrange for
          267 ;;; the CFFI package to be defined a bit earlier, though.
          268 (defun library-handle-form (name)
          269   (flet ((find-cffi-symbol (symbol)
          270            (find-symbol (symbol-name symbol) '#:cffi)))
          271     `(,(find-cffi-symbol '#:foreign-library-handle)
          272        (,(find-cffi-symbol '#:get-foreign-library) ',name))))
          273 
          274 (eval-when (:compile-toplevel :load-toplevel :execute)
          275   ;; version 2.40 (CVS 2006-09-03, to be more precise) added a
          276   ;; PROPERTIES argument to FFI::FOREIGN-LIBRARY-FUNCTION.
          277   (defun post-2.40-ffi-interface-p ()
          278     (let ((f-l-f (find-symbol (string '#:foreign-library-function) '#:ffi)))
          279       (if (and f-l-f (= (length (ext:arglist f-l-f)) 5))
          280           '(:and)
          281           '(:or))))
          282   ;; FFI::FOREIGN-LIBRARY-FUNCTION and FFI::FOREIGN-LIBRARY-VARIABLE
          283   ;; were deprecated in 2.41 and removed in 2.45.
          284   (defun post-2.45-ffi-interface-p ()
          285     (if (find-symbol (string '#:foreign-library-function) '#:ffi)
          286         '(:or)
          287         '(:and))))
          288 
          289 #+#.(cffi-sys::post-2.45-ffi-interface-p)
          290 (defun %foreign-funcall-aux (name type library)
          291   `(ffi::find-foreign-function ,name ,type nil ,library nil nil))
          292 
          293 #-#.(cffi-sys::post-2.45-ffi-interface-p)
          294 (defun %foreign-funcall-aux (name type library)
          295   `(ffi::foreign-library-function
          296     ,name ,library nil
          297     #+#.(cffi-sys::post-2.40-ffi-interface-p)
          298     nil
          299     ,type))
          300 
          301 (defmacro %foreign-funcall (name args &key library convention)
          302   "Invoke a foreign function called NAME, taking pairs of
          303 foreign-type/value pairs from ARGS.  If a single element is left
          304 over at the end of ARGS, it specifies the foreign return type of
          305 the function call."
          306   (multiple-value-bind (types fargs rettype)
          307       (parse-foreign-funcall-args args)
          308     (let* ((fn (%foreign-funcall-aux
          309                 name
          310                 `(ffi:parse-c-type
          311                   ',(c-function-type types rettype convention))
          312                 (if (eq library :default)
          313                     :default
          314                     (library-handle-form library))))
          315           (form `(funcall
          316                   (load-time-value
          317                    (handler-case ,fn
          318                      (error (err)
          319                        (warn "~A" err))))
          320                   ,@fargs)))
          321       (if (eq rettype 'ffi:c-pointer)
          322           `(or ,form (null-pointer))
          323           form))))
          324 
          325 (defmacro %foreign-funcall-pointer (ptr args &key convention)
          326   "Similar to %foreign-funcall but takes a pointer instead of a string."
          327   (multiple-value-bind (types fargs rettype)
          328       (parse-foreign-funcall-args args)
          329     `(funcall (ffi:foreign-function
          330                ,ptr (load-time-value
          331                      (ffi:parse-c-type ',(c-function-type
          332                                           types rettype convention))))
          333               ,@fargs)))
          334 
          335 ;;;# Callbacks
          336 
          337 ;;; *CALLBACKS* contains the callbacks defined by the CFFI DEFCALLBACK
          338 ;;; macro.  The symbol naming the callback is the key, and the value
          339 ;;; is a list containing a Lisp function, the parsed CLISP FFI type of
          340 ;;; the callback, and a saved pointer that should not persist across
          341 ;;; saved images.
          342 (defvar *callbacks* (make-hash-table))
          343 
          344 ;;; Return a CLISP FFI function type for a CFFI callback function
          345 ;;; given a return type and list of argument names and types.
          346 (eval-when (:compile-toplevel :load-toplevel :execute)
          347   (defun callback-type (rettype arg-names arg-types convention)
          348     (ffi:parse-c-type
          349      `(ffi:c-function
          350        (:arguments ,@(mapcar (lambda (sym type)
          351                                (list sym (convert-foreign-type type)))
          352                              arg-names arg-types))
          353        (:return-type ,(convert-foreign-type rettype))
          354        (:language ,(convert-calling-convention convention))))))
          355 
          356 ;;; Register and create a callback function.
          357 (defun register-callback (name function parsed-type)
          358   (setf (gethash name *callbacks*)
          359         (list function parsed-type
          360               (ffi:with-foreign-object (ptr 'ffi:c-pointer)
          361                 ;; Create callback by converting Lisp function to foreign
          362                 (setf (ffi:memory-as ptr parsed-type) function)
          363                 (ffi:foreign-value ptr)))))
          364 
          365 ;;; Restore all saved callback pointers when restarting the Lisp
          366 ;;; image.  This is pushed onto CUSTOM:*INIT-HOOKS*.
          367 ;;; Needs clisp > 2.35, bugfix 2005-09-29
          368 (defun restore-callback-pointers ()
          369   (maphash
          370    (lambda (name list)
          371      (register-callback name (first list) (second list)))
          372    *callbacks*))
          373 
          374 ;;; Add RESTORE-CALLBACK-POINTERS to the lists of functions to run
          375 ;;; when an image is restarted.
          376 (eval-when (:load-toplevel :execute)
          377   (pushnew 'restore-callback-pointers custom:*init-hooks*))
          378 
          379 ;;; Define a callback function NAME to run BODY with arguments
          380 ;;; ARG-NAMES translated according to ARG-TYPES and the return type
          381 ;;; translated according to RETTYPE.  Obtain a pointer that can be
          382 ;;; passed to C code for this callback by calling %CALLBACK.
          383 (defmacro %defcallback (name rettype arg-names arg-types body
          384                         &key convention)
          385   `(register-callback
          386     ',name
          387     (lambda ,arg-names
          388       ;; Work around CLISP's FFI:C-POINTER type and convert NIL values
          389       ;; back into a null pointers.
          390       (let (,@(loop for name in arg-names
          391                     and type in arg-types
          392                     when (eq type :pointer)
          393                     collect `(,name (or ,name (null-pointer)))))
          394         ,body))
          395     ,(callback-type rettype arg-names arg-types convention)))
          396 
          397 ;;; Look up the name of a callback and return a pointer that can be
          398 ;;; passed to a C function.  Signals an error if no callback is
          399 ;;; defined called NAME.
          400 (defun %callback (name)
          401   (multiple-value-bind (list winp) (gethash name *callbacks*)
          402     (unless winp
          403       (error "Undefined callback: ~S" name))
          404     (third list)))
          405 
          406 ;;;# Loading and Closing Foreign Libraries
          407 
          408 (defun %load-foreign-library (name path)
          409   "Load a foreign library from PATH."
          410   (declare (ignore name))
          411   #+#.(cffi-sys::post-2.45-ffi-interface-p)
          412   (ffi:open-foreign-library path)
          413   #-#.(cffi-sys::post-2.45-ffi-interface-p)
          414   (ffi::foreign-library path))
          415 
          416 (defun %close-foreign-library (handle)
          417   "Close a foreign library."
          418   (ffi:close-foreign-library handle))
          419 
          420 (defun native-namestring (pathname)
          421   (namestring pathname))
          422 
          423 ;;;# Foreign Globals
          424 
          425 (defun %foreign-symbol-pointer (name library)
          426   "Returns a pointer to a foreign symbol NAME."
          427   (prog1 (ignore-errors
          428            (ffi:foreign-address
          429             #+#.(cffi-sys::post-2.45-ffi-interface-p)
          430             (ffi::find-foreign-variable name nil library nil nil)
          431             #-#.(cffi-sys::post-2.45-ffi-interface-p)
          432             (ffi::foreign-library-variable name library nil nil)))))