This must either be really trivial or completely impossible: suppose I need to create an instance of a C structure that has a void* slot. I want to fill that slot with (some representation of) an arbitrary Lisp value so that when that structure is handed back to my Lisp code I can get back to the Lisp object. Other than keeping my own integer->object mapping and passing the integer to C and translating it back to the object when I get it back, is there some easier way to do this in CFFI?
-Peter
Peter Seibel peter@gigamonkeys.com writes:
Other than keeping my own integer->object mapping and passing the integer to C and translating it back to the object when I get it back, is there some easier way to do this in CFFI?
AFAIK, that is the best way to do it. You could also serialize Lisp objects into a foreign array, though the situations where that would be a better approach seem limited and you loose object identity.
SBCL and some other Lisps will let you get pointers to vectors of some types. With Allegro you apparently can get pointers to arbitrary Lisp objects with EXCL:LISPVAL-TO-ADDRESS and then get it back with SYS:MEMREF (though you have to get the offset right, and AFAICT handle immediate values yourself); it seems easier to do your own mapping instead. You have to worry about the GC moving objects around too.
Hmm, since you're asking whether this is doable in *CFFI*, the answer is no then. :-) You can, however, make good use of CFFI's type system!
(use-package :cffi)
(define-foreign-type lisp-object-type () ((weakp :initarg :weakp)) (:actual-type :unsigned-int))
(define-parse-method lisp-object (&key weak-mapping) (make-instance 'lisp-object-type :weakp weak-mapping))
(defvar *regular-hashtable* (make-hash-table))
(defvar *weak-hashtable* (trivial-garbage:make-weak-hash-table :weakness :value))
(defvar *regular-counter* 0) (defvar *weak-counter* 0)
(defun increment-counter (value) (mod (1+ value) (expt 2 (* 8 (foreign-type-size :unsigned-int)))))
(define-modify-macro incf-counter () increment-counter)
(defmethod translate-to-foreign (value (type lisp-object-type)) (with-slots (weakp) type (let ((id (if weakp (incf-counter *weak-counter*) (incf-counter *regular-counter*))) (ht (if weakp *weak-hashtable* *regular-hashtable*))) (setf (gethash id ht) value) id)))
(defmethod translate-from-foreign (int (type lisp-object-type)) (with-slots (weakp) type ;; If isn't a weak hashtable, we should probably remhash. (gethash int (if weakp *weak-hashtable* *regular-hashtable*))))
;;;; Silly example
CL-USER> (defctype weak-mapping (lisp-object :weak-mapping t)) WEAK-MAPPING
CL-USER> (foreign-funcall "abs" weak-mapping (lambda (x) x) weak-mapping) #<FUNCTION (LAMBDA (X)) {11AB46F5}> T
This type has obvious shortcomings. In order to be generally useful it should probably take different arguments such as an hashtable so that different CFFI users don't share the same hashtable, have a smarter (possibly user-provided) counter mechanism, etc... I hope it's a useful example nevertheless.
On Wed, 2007-03-14 at 17:03 +0000, Luis Oliveira wrote:
Peter Seibel peter@gigamonkeys.com writes:
Other than keeping my own integer->object mapping and passing the integer to C and translating it back to the object when I get it back, is there some easier way to do this in CFFI?
Hmm, since you're asking whether this is doable in *CFFI*, the answer is no then. :-) You can, however, make good use of CFFI's type system!
If the use case is passing Lisp data to callbacks limited to a certain dynamic extent, you could do this easily by consing bindings from ints to Lisp objects on an association list... Something like:
;;; Counter used to generate unique IDs for Lisp objects passed to ;;; foreign code. The counters are only guaranteed to be unique ;;; during the dynamic extent of which they are bound. (defvar *lisp-object-counter* 0)
;;; Association list of Lisp object IDs to Lisp objects. (defvar *lisp-objects* nil)
;;; Create a binding for a unique ID to a Lisp object during the ;;; extent of BODY. The ID may be coerced to a pointer and passed to ;;; foreign code, then looked up from callback functions. (defmacro with-lisp-object-id ((id object) &body body) `(let* ((,id *lisp-object-counter*) (*lisp-object-counter* (1+ *lisp-object-counter*)) (*lisp-objects* (acons ,id ,object *lisp-objects*))) ,@body))
Then you can pass the IDs as pointers to functions that take a callback and user data pointer, and ASSOC them in *LISP-OBJECTS* inside the callback.
James