Coverage report: /home/luis/src/cffi/src/cffi-sbcl.lisp
Kind | Covered | All | % |
expression | 168 | 253 | 66.4 |
branch | 13 | 16 | 81.3 |
Key
Not instrumented
Executed
Not executed
Both branches taken
One branch taken
Neither branch taken
1
;;;; -*- Mode: lisp; indent-tabs-mode: nil -*-
3
;;; cffi-sbcl.lisp --- CFFI-SYS implementation for SBCL.
5
;;; Copyright (C) 2005-2006, James Bielman <jamesjb@jamesjb.com>
7
;;; Permission is hereby granted, free of charge, to any person
8
;;; obtaining a copy of this software and associated documentation
9
;;; files (the "Software"), to deal in the Software without
10
;;; restriction, including without limitation the rights to use, copy,
11
;;; modify, merge, publish, distribute, sublicense, and/or sell copies
12
;;; of the Software, and to permit persons to whom the Software is
13
;;; furnished to do so, subject to the following conditions:
15
;;; The above copyright notice and this permission notice shall be
16
;;; included in all copies or substantial portions of the Software.
18
;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
22
;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
23
;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25
;;; DEALINGS IN THE SOFTWARE.
30
(defpackage #:cffi-sys
31
(:use #:common-lisp #:sb-alien #:cffi-utils)
33
#:canonicalize-symbol-name-case
43
#:with-foreign-pointer
45
#:%foreign-funcall-pointer
46
#:%foreign-type-alignment
48
#:%load-foreign-library
49
#:%close-foreign-library
53
#:make-shareable-byte-vector
54
#:with-pointer-to-vector-data
55
#:%foreign-symbol-pointer
60
#:%lisp-string-octet-length
61
#:%lisp-string-to-foreign
62
#:%lisp-string-into-foreign
63
#:%foreign-string-length
64
#:%foreign-string-to-lisp))
66
(in-package #:cffi-sys)
70
(eval-when (:compile-toplevel :load-toplevel :execute)
71
(mapc (lambda (feature) (pushnew feature *features*))
73
#+darwin cffi-features:darwin
74
#+(and unix (not win32)) cffi-features:unix
75
#+win32 cffi-features:windows
76
#+x86 cffi-features:x86
77
#+x86-64 cffi-features:x86-64
78
#+(and ppc (not ppc64)) cffi-features:ppc32
80
cffi-features:flat-namespace
85
(declaim (inline canonicalize-symbol-name-case))
86
(defun canonicalize-symbol-name-case (name)
87
(declare (string name))
90
;;;# Basic Pointer Operations
92
(declaim (inline pointerp))
94
"Return true if PTR is a foreign pointer."
95
(sb-sys:system-area-pointer-p ptr))
97
(declaim (inline pointer-eq))
98
(defun pointer-eq (ptr1 ptr2)
99
"Return true if PTR1 and PTR2 point to the same address."
100
(declare (type system-area-pointer ptr1 ptr2))
101
(sb-sys:sap= ptr1 ptr2))
103
(declaim (inline null-pointer))
104
(defun null-pointer ()
105
"Construct and return a null pointer."
108
(declaim (inline null-pointer-p))
109
(defun null-pointer-p (ptr)
110
"Return true if PTR is a null pointer."
111
(declare (type system-area-pointer ptr))
112
(zerop (sb-sys:sap-int ptr)))
114
(declaim (inline inc-pointer))
115
(defun inc-pointer (ptr offset)
116
"Return a pointer pointing OFFSET bytes past PTR."
117
(declare (type system-area-pointer ptr)
118
(type integer offset))
119
(sb-sys:sap+ ptr offset))
121
(declaim (inline make-pointer))
122
(defun make-pointer (address)
123
"Return a pointer pointing to ADDRESS."
124
(declare (type (unsigned-byte 32) address))
125
(sb-sys:int-sap address))
127
(declaim (inline pointer-address))
128
(defun pointer-address (ptr)
129
"Return the address pointed to by PTR."
130
(declare (type system-area-pointer ptr))
131
(sb-sys:sap-int ptr))
135
;;; Functions and macros for allocating foreign memory on the stack
136
;;; and on the heap. The main CFFI package defines macros that wrap
137
;;; FOREIGN-ALLOC and FOREIGN-FREE in UNWIND-PROTECT for the common usage
138
;;; when the memory has dynamic extent.
140
(declaim (inline %foreign-alloc))
141
(defun %foreign-alloc (size)
142
"Allocate SIZE bytes on the heap and return a pointer."
143
(declare (type (unsigned-byte 32) size))
144
(alien-sap (make-alien (unsigned 8) size)))
146
(declaim (inline foreign-free))
147
(defun foreign-free (ptr)
148
"Free a PTR allocated by FOREIGN-ALLOC."
149
(declare (type system-area-pointer ptr))
150
(free-alien (sap-alien ptr (* (unsigned 8)))))
152
(defmacro with-foreign-pointer ((var size &optional size-var) &body body)
153
"Bind VAR to SIZE bytes of foreign memory during BODY. The
154
pointer in VAR is invalid beyond the dynamic extent of BODY, and
155
may be stack-allocated if supported by the implementation. If
156
SIZE-VAR is supplied, it will be bound to SIZE during BODY."
158
(setf size-var (gensym "SIZE")))
159
;; If the size is constant we can stack-allocate.
161
(let ((alien-var (gensym "ALIEN")))
162
`(with-alien ((,alien-var (array (unsigned 8) ,(eval size))))
163
(let ((,size-var ,(eval size))
164
(,var (alien-sap ,alien-var)))
165
(declare (ignorable ,size-var))
167
`(let* ((,size-var ,size)
168
(,var (%foreign-alloc ,size-var)))
171
(foreign-free ,var)))))
173
;;;# Shareable Vectors
175
;;; This interface is very experimental. WITH-POINTER-TO-VECTOR-DATA
176
;;; should be defined to perform a copy-in/copy-out if the Lisp
177
;;; implementation can't do this.
179
(declaim (inline make-shareable-byte-vector))
180
(defun make-shareable-byte-vector (size)
181
"Create a Lisp vector of SIZE bytes can passed to
182
WITH-POINTER-TO-VECTOR-DATA."
183
; (declare (type sb-int:index size))
184
(make-array size :element-type '(unsigned-byte 8)))
186
(defmacro with-pointer-to-vector-data ((ptr-var vector) &body body)
187
"Bind PTR-VAR to a foreign pointer to the data in VECTOR."
188
(let ((vector-var (gensym "VECTOR")))
189
`(let ((,vector-var ,vector))
190
(declare (type (sb-kernel:simple-unboxed-array (*)) ,vector-var))
191
(sb-sys:with-pinned-objects (,vector-var)
192
(let ((,ptr-var (sb-sys:vector-sap ,vector-var)))
197
;;; Define the %MEM-REF and %MEM-SET functions, as well as compiler
198
;;; macros that optimize the case where the type keyword is constant
200
(defmacro define-mem-accessors (&body pairs)
202
(defun %mem-ref (ptr type &optional (offset 0))
204
,@(loop for (keyword fn) in pairs
205
collect `(,keyword (,fn ptr offset)))))
206
(defun %mem-set (value ptr type &optional (offset 0))
208
,@(loop for (keyword fn) in pairs
209
collect `(,keyword (setf (,fn ptr offset) value)))))
210
(define-compiler-macro %mem-ref
211
(&whole form ptr type &optional (offset 0))
214
,@(loop for (keyword fn) in pairs
215
collect `(,keyword `(,',fn ,ptr ,offset))))
217
(define-compiler-macro %mem-set
218
(&whole form value ptr type &optional (offset 0))
222
,@(loop for (keyword fn) in pairs
223
collect `(,keyword `(setf (,',fn ,ptr ,offset)
227
(define-mem-accessors
228
(:char sb-sys:signed-sap-ref-8)
229
(:unsigned-char sb-sys:sap-ref-8)
230
(:short sb-sys:signed-sap-ref-16)
231
(:unsigned-short sb-sys:sap-ref-16)
232
(:int sb-sys:signed-sap-ref-32)
233
(:unsigned-int sb-sys:sap-ref-32)
234
(:long sb-sys:signed-sap-ref-word)
235
(:unsigned-long sb-sys:sap-ref-word)
236
(:long-long sb-sys:signed-sap-ref-64)
237
(:unsigned-long-long sb-sys:sap-ref-64)
238
(:float sb-sys:sap-ref-single)
239
(:double sb-sys:sap-ref-double)
240
(:pointer sb-sys:sap-ref-sap))
242
;;;# Calling Foreign Functions
244
(defun convert-foreign-type (type-keyword)
245
"Convert a CFFI type keyword to an SB-ALIEN type."
248
(:unsigned-char 'unsigned-char)
250
(:unsigned-short 'unsigned-short)
252
(:unsigned-int 'unsigned-int)
254
(:unsigned-long 'unsigned-long)
255
(:long-long 'long-long)
256
(:unsigned-long-long 'unsigned-long-long)
257
(:float 'single-float)
258
(:double 'double-float)
259
(:pointer 'system-area-pointer)
262
(defun %foreign-type-size (type-keyword)
263
"Return the size in bytes of a foreign type."
264
(/ (sb-alien-internals:alien-type-bits
265
(sb-alien-internals:parse-alien-type
266
(convert-foreign-type type-keyword) nil)) 8))
268
(defun %foreign-type-alignment (type-keyword)
269
"Return the alignment in bytes of a foreign type."
270
#+(and darwin ppc (not ppc64))
272
((:double :long-long :unsigned-long-long)
273
(return-from %foreign-type-alignment 8)))
274
;; No override necessary for other types...
275
(/ (sb-alien-internals:alien-type-alignment
276
(sb-alien-internals:parse-alien-type
277
(convert-foreign-type type-keyword) nil)) 8))
279
(defun foreign-funcall-type-and-args (args)
280
"Return an SB-ALIEN function type for ARGS."
281
(let ((return-type 'void))
282
(loop for (type arg) on args by #'cddr
283
if arg collect (convert-foreign-type type) into types
284
and collect arg into fargs
285
else do (setf return-type (convert-foreign-type type))
286
finally (return (values types fargs return-type)))))
288
(defmacro %%foreign-funcall (name types fargs rettype)
289
"Internal guts of %FOREIGN-FUNCALL."
291
(extern-alien ,name (function ,rettype ,@types))
294
(defmacro %foreign-funcall (name args &key library calling-convention)
295
"Perform a foreign function call, document it more later."
296
(declare (ignore library calling-convention))
297
(multiple-value-bind (types fargs rettype)
298
(foreign-funcall-type-and-args args)
299
`(%%foreign-funcall ,name ,types ,fargs ,rettype)))
301
(defmacro %foreign-funcall-pointer (ptr args &key calling-convention)
302
"Funcall a pointer to a foreign function."
303
(declare (ignore calling-convention))
304
(multiple-value-bind (types fargs rettype)
305
(foreign-funcall-type-and-args args)
306
(with-unique-names (function)
307
`(with-alien ((,function (* (function ,rettype ,@types)) ,ptr))
308
(alien-funcall ,function ,@fargs)))))
312
;;; The *CALLBACKS* hash table contains a direct mapping of CFFI
313
;;; callback names to SYSTEM-AREA-POINTERs obtained by ALIEN-LAMBDA.
314
;;; SBCL will maintain the addresses of the callbacks across saved
315
;;; images, so it is safe to store the pointers directly.
316
(defvar *callbacks* (make-hash-table))
318
(defmacro %defcallback (name rettype arg-names arg-types body
319
&key calling-convention)
320
(declare (ignore calling-convention))
321
`(setf (gethash ',name *callbacks*)
323
(sb-alien::alien-lambda ,(convert-foreign-type rettype)
324
,(mapcar (lambda (sym type)
325
(list sym (convert-foreign-type type)))
329
(defun %callback (name)
330
(or (gethash name *callbacks*)
331
(error "Undefined callback: ~S" name)))
333
;;;# Loading and Closing Foreign Libraries
335
(declaim (inline %load-foreign-library))
336
(defun %load-foreign-library (name path)
337
"Load a foreign library."
338
(declare (ignore name))
339
(load-shared-object path))
341
(defun %close-foreign-library (handle)
342
"Closes a foreign library."
343
(sb-alien::dlclose-or-lose
344
(find (sb-ext:native-namestring handle) sb-alien::*shared-objects*
345
:key #'sb-alien::shared-object-file
348
(defun native-namestring (pathname)
349
(sb-ext:native-namestring pathname))
353
(defun %foreign-symbol-pointer (name library)
354
"Returns a pointer to a foreign symbol NAME."
355
(declare (ignore library))
356
(let-when (address (sb-sys:find-foreign-symbol-address name))
357
(sb-sys:int-sap address)))
359
;;;# Encoding-Aware String Interface
361
;;; Maps encodings to their terminator lengths.
362
(defparameter *encodings* (make-hash-table :test 'eq))
364
;;; Return the number of octets in a null terminator for a SBCL
365
;;; external-format. I'm not sure if this is a very good way to do
366
;;; this but it does seem to get the right results for me. Note that
367
;;; we do need to count the null bytes because a converted UTF-16
368
;;; string may contain a byte-order mark.
369
(defun charset-terminator-length (ef)
370
(max 1 (count-if #'zerop
372
(sb-ext:string-to-octets (string (code-char 0))
373
:external-format ef)))))
375
(defun cffi-to-sbcl-enc (enc)
377
(:shift-jis :shift_jis)
378
(:mac-cyrillic :x-mac-cyrillic)
381
(defun sbcl-to-cffi-enc (enc)
383
(:shift_jis :shift-jis)
384
(:x-mac-cyrillic :mac-cyrillic)
385
(:latin-1 :iso-8859-1)
386
(:latin-9 :iso-8859-9)
389
;;; Fill the *ENCODINGS* hash-table.
391
;; As of 2007-02-24, SBCL only supports EBCDIC for streams.
392
(unless (eq ef :ebcdic-us)
393
(setf (gethash (sbcl-to-cffi-enc ef) *encodings*)
394
(charset-terminator-length ef))))
395
(mapcar #'caar sb-impl::*external-formats*))
397
;;; Bind EXTERNAL-FORMAT and LENGTH to the values from *ENCODINGS* for
398
;;; ENCODING, signalling an error if the encoding is not supported.
399
(defmacro with-encoding ((external-format length) encoding &body body)
400
(once-only (encoding)
401
`(let ((,external-format (cffi-to-sbcl-enc ,encoding))
402
(,length (or (gethash ,encoding *encodings*)
403
(error "Unsupported encoding: ~S" ,encoding))))
406
;;; Return a list of CFFI encodings supported by this implementation.
407
(defun list-encodings ()
408
(loop for key being the hash-keys of *encodings* collect key))
410
;;; Convenience type for working with octet arrays.
411
(deftype ub8 () '(unsigned-byte 8))
413
(defun default-encoding ()
414
(sbcl-to-cffi-enc sb-alien::*default-c-string-external-format*))
416
;;; Expands into a loop that calculates the length of the foreign
417
;;; string at SAP plus OFFSET, using ACCESSOR and looking for a null
418
;;; terminator of LENGTH bytes.
419
(defmacro sap-string-length (sap offset accessor length)
420
(once-only (sap offset length)
421
`(do ((i 0 (+ i ,length)))
422
((zerop (,accessor ,sap (+ ,offset i))) i)
423
(declare (fixnum i)))))
425
;;; Return the length in octets of the null terminated foreign string
426
;;; at POINTER plus OFFSET octets, assumed to be encoded in ENCODING,
427
;;; a CFFI encoding. This should be smart enough to look for 8-bit vs
428
;;; 16-bit null terminators, as appropriate for the encoding.
429
(defun %foreign-string-length (pointer encoding &key (offset 0))
430
(with-encoding (ef length) encoding
431
(declare (ignore ef))
433
(1 (sap-string-length pointer offset sb-sys:sap-ref-8 1))
434
(2 (sap-string-length pointer offset sb-sys:sap-ref-16 2))
435
(4 (sap-string-length pointer offset sb-sys:sap-ref-32 4)))))
437
;;; Convert COUNT octets starting OFFSET bytes from POINTER into a
438
;;; freshly allocated Lisp string and return it. If COUNT is not
439
;;; supplied, the string is assumed to be null-terminated.
440
(defun %foreign-string-to-lisp (pointer encoding &key (offset 0) count)
442
(setf count (%foreign-string-length pointer encoding :offset offset)))
443
(let* ((octets (make-array count :element-type 'ub8)))
444
(sb-kernel:copy-ub8-from-system-area pointer offset octets 0 count)
445
(with-encoding (ef length) encoding
446
(declare (ignore length))
447
(sb-ext:octets-to-string octets :external-format ef))))
449
;;; Return the length of STRING from START to END, converted to
450
;;; ENCODING, in octets, not including the null terminator.
451
(defun %lisp-string-octet-length (string encoding &key (start 0) end)
452
(with-encoding (ef length) encoding
453
(declare (ignore length))
455
(sb-ext:string-to-octets string :external-format ef :start start
456
:end end :null-terminate nil))))
458
;;; Shorthand syntax for calling SB-EXT:STRING-TO-OCTETS.
459
(defmacro string-to-octets (string start end ef &optional (nullp t))
460
`(sb-ext:string-to-octets ,string :start ,start :end ,end
461
:external-format ,ef :null-terminate ,nullp))
463
;;; Convert characters from START to END (character indices) in STRING
464
;;; to a foreign string, encoding in ENCODING, a CFFI encoding.
465
;;; Returns a freshly allocated foreign string that must be freed with
467
(defun %lisp-string-to-foreign (string encoding &key (start 0) end)
468
(with-encoding (ef terminator-len) encoding
469
(declare (ignore terminator-len))
470
(let* ((octets (string-to-octets string start end ef))
471
(count (length octets))
472
(buf (%foreign-alloc count)))
473
(sb-kernel:copy-ub8-to-system-area octets 0 buf 0 count)
476
;;; Convert characters from START to END in STRING to a foreign
477
;;; string, encoded in ENCODING, a CFFI encoding. The maximum
478
;;; number of octets written to BUF will be:
480
;;; (- BUFSIZE OFFSET NULL-TERMINATOR-LENGTH)
482
;;; This ensures the resulting string will always be null-terminated.
484
;;; FIXME: This might result in an invalid Unicode string if the
485
;;; buffer isn't large enough. Is this a problem? [2006-01-07 JJB]
486
(defun %lisp-string-into-foreign (string encoding buffer bufsize
487
&key (start 0) end (offset 0))
488
(with-encoding (ef t-len) encoding
489
(let* ((octets (string-to-octets string start end ef nil))
490
(vector-length (length octets))
491
(count (min (- bufsize offset t-len) vector-length)))
492
(unless (minusp count)
493
(sb-kernel:copy-ub8-to-system-area octets 0 buffer offset count)
495
(setf (sb-sys:sap-ref-8 buffer (+ count i)) 0)))))