3 (defun ensure-list (thing)
4 (if (listp thing) thing (list thing)))
6 (defun slot->cstruct-slot (slot)
7 (destructuring-bind (name type &key &allow-other-keys) slot
10 (defun slot->slot-name (slot)
11 (destructuring-bind (name type &key &allow-other-keys) slot
12 (declare (ignore type))
15 (defun cstruct-definition (name slots)
16 `(defcstruct ,name ,@(mapcar #'slot->cstruct-slot slots)))
18 (defun maybe-unlist (thing)
19 (if (or (not (listp thing)) (cdr thing))
23 (defun slot->struct-slot (slot)
24 (destructuring-bind (name type &key initform &allow-other-keys) slot
25 (declare (ignore type))
26 (maybe-unlist `(,name ,@(when initform (list initform))))))
28 (defun struct-definition (name superclass slots)
29 `(defstruct ,@(if superclass
30 (list `(,name (:include ,superclass)))
32 ,@(mapcar #'slot->struct-slot slots)))
34 (define-foreign-type g-boxed-pointer-type ()
35 ((name :accessor g-boxed-pointer-type-name :initarg :name)
36 (outp :accessor g-boxed-pointer-type-outp :initarg :outp)))
38 (define-parse-method g-boxed-ptr (name &optional (type :in))
39 (make-instance 'g-boxed-pointer-type :name name :actual-type :pointer :outp (ecase type
43 (defmethod translate-from-foreign (value (type g-boxed-pointer-type))
44 (unless (null-pointer-p value)
45 (parse-g-boxed value (g-boxed-pointer-type-name type))))
47 (defmethod translate-to-foreign (value (type g-boxed-pointer-type))
48 (let ((ptr (foreign-alloc (boxed-c-structure-name (g-boxed-pointer-type-name type)))))
49 (real-unparse-g-boxed ptr value)
52 (defmethod free-translated-object (ptr (type g-boxed-pointer-type) param)
53 (when (g-boxed-pointer-type-outp type)
54 (let ((original-object param)
55 (new-real-name (g-boxed-real-name ptr (g-boxed-pointer-type-name type))))
56 (if (eq new-real-name (type-of original-object))
57 (real-parse-g-boxed ptr original-object)
58 (error "Type has changed!"))))
61 (defmethod expand-to-foreign-dyn (value var body (type g-boxed-pointer-type))
62 (let ((value-var (gensym)))
63 `(with-foreign-object (,var ',(boxed-c-structure-name (g-boxed-pointer-type-name type)))
64 (let ((,value-var ,value))
65 (real-unparse-g-boxed ,var ,value-var)
67 ,@(when (g-boxed-pointer-type-outp type)
68 (list `(let ((new-real-name (g-boxed-real-name ,var ',(g-boxed-pointer-type-name type))))
69 (if (eq new-real-name (type-of ,value-var))
70 (real-parse-g-boxed ,var ,value-var)
71 (error "Type has changed from ~A to ~A" (type-of ,value-var) new-real-name)))))))))
73 (define-foreign-type g-boxed-inline-type ()
74 ((name :accessor g-boxed-inline-type :initarg :name)))
76 (define-parse-method g-boxed-inline (name)
77 (make-instance 'g-boxed-inline-type :name name :actual-type name))
79 (defgeneric real-parse-g-boxed (pointer object))
80 (defgeneric real-unparse-g-boxed (pointer object))
82 (defun parse-g-boxed (pointer name)
83 (let* ((real-name (g-boxed-real-name pointer name))
84 (object (make-instance real-name)))
85 (real-parse-g-boxed pointer object)
88 (defun boxed-alloc (type alloc-type)
90 (:cffi (foreign-alloc type))
91 (:boxed (let ((pointer (foreign-alloc type)))
92 (prog1 (g-boxed-copy (g-type-from-name (boxed-type-gname type)) pointer)
93 (foreign-free pointer))))))
95 (defun g-boxed->cstruct (object &key (alloc-type :cffi))
96 (let ((pointer (boxed-alloc (type-of object) alloc-type)))
97 (real-unparse-g-boxed pointer object)
100 (defun g-boxed-real-name (pointer name)
102 for (sub-name slot values) in (get name 'boxed-dispatch)
103 do (debugf "Checking ~A ~A ~A against ~A.~A = ~A~%" sub-name slot values name slot (foreign-slot-value pointer name slot))
104 when (member (foreign-slot-value pointer name slot) values :test 'equalp)
105 return (g-boxed-real-name pointer sub-name))
108 (defun slot->slot-parser (class-name pointer-var slot)
109 (bind (((slot-name slot-type &key parser &allow-other-keys) slot))
112 `(setf ,slot-name (funcall ,parser ',class-name ,pointer-var)))
113 ((and (listp slot-type) (eq 'g-boxed-inline (first slot-type)))
114 `(setf ,slot-name (parse-g-boxed (foreign-slot-pointer ,pointer-var ',class-name ',slot-name) ',(second slot-type))))
116 `(setf ,slot-name (foreign-slot-value ,pointer-var ',class-name ',slot-name))))))
118 (defun parse-method-definition (name slots)
119 (let ((slot-names (mapcar #'slot->slot-name slots)))
120 `(defmethod real-parse-g-boxed (pointer (object ,name))
121 (with-slots (,@slot-names) object
122 ,@(mapcar (lambda (slot) (slot->slot-parser name 'pointer slot)) slots)))))
124 (defun slot->slot-unparser (class-name pointer-var slot object)
125 (bind (((slot-name slot-type &key unparser &allow-other-keys) slot))
128 `(funcall ,unparser ',class-name ,pointer-var ,object))
129 ((and (listp slot-type) (eq 'g-boxed-inline (first slot-type)))
130 `(real-unparse-g-boxed (foreign-slot-pointer ,pointer-var ',class-name ',slot-name) ,slot-name))
132 `(setf (foreign-slot-value ,pointer-var ',class-name ',slot-name) ,slot-name)))))
134 (defun unparse-method-definition (name slots)
135 (let ((slot-names (mapcar #'slot->slot-name slots)))
136 `(defmethod real-unparse-g-boxed (pointer (object ,name))
137 (with-slots (,@slot-names) object
138 ,@(mapcar (lambda (slot) (slot->slot-unparser name 'pointer slot 'object)) slots)))))
140 (defun slot->export-accessor (class-name slot)
141 (destructuring-bind (slot-name slot-type &key &allow-other-keys) slot
142 (declare (ignore slot-type))
143 (let ((accessor-name (intern (format nil "~A-~A" (symbol-name class-name) (symbol-name slot-name))
144 (symbol-package class-name))))
145 `(export ',accessor-name (symbol-package ',accessor-name)))))
147 (defun struct-constructor-name (name)
148 (intern (format nil "MAKE-~A" (symbol-name name)) (symbol-package name)))
150 (defun get-g-boxed-direct-subclasses (name)
151 (mapcar (lambda (spec) (bind (((name slot values) spec))
152 (declare (ignore slot values))
154 (get name 'boxed-dispatch)))
156 (defun map-append (f &rest lists)
157 (reduce #'append (apply #'mapcar f lists)))
159 (defun get-g-boxed-all-subclasses (name)
161 (map-append #'get-g-boxed-all-subclasses (get-g-boxed-direct-subclasses name))))
163 (defun get-g-boxed-completed-c-definition (name union-name)
164 `(defcunion ,union-name
165 ,@(mapcar (lambda (sub-name)
166 `(,sub-name ,sub-name))
167 (get-g-boxed-all-subclasses name))))
169 (defun g-boxed-root (name)
170 (if (get name 'superclass)
171 (g-boxed-root (get name 'superclass))
174 (defmacro update-g-boxed-root-c-class (name)
175 (when (get name 'c-name)
176 (get-g-boxed-completed-c-definition (g-boxed-root name) (get name 'c-name))))
178 (defmacro define-g-boxed-class (g-name-and-c-name name (&optional superclass-and-dispatch (export t)) &body slots)
179 (bind (((&optional g-name c-name) (ensure-list g-name-and-c-name))
180 ((&optional superclass dispatch-slot dispatch-values) superclass-and-dispatch)
181 (superclass-slots (get superclass 'boxed-combined-slots))
182 (combined-slots (append superclass-slots slots)))
183 (setf c-name (or c-name (gensym "C-UNION-")))
184 `(progn ,(cstruct-definition name combined-slots)
185 ,(struct-definition name superclass slots)
186 ,(parse-method-definition name combined-slots)
187 ,(unparse-method-definition name combined-slots)
188 (eval-when (:load-toplevel :compile-toplevel :execute)
189 (setf (get ',name 'boxed-slots) ',slots
190 (get ',name 'boxed-combined-slots) ',combined-slots
191 (get ',name 'superclass) ',superclass
192 (get ',name 'c-name) (or (get ',name 'c-name) ',c-name))
194 (list `(pushnew '(,name ,dispatch-slot ,(ensure-list dispatch-values)) (get ',superclass 'boxed-dispatch) :test 'equalp))))
195 (update-g-boxed-root-c-class ,name)
197 (list `(register-boxed-type ,g-name ',name)))
199 (append (list `(export ',name (symbol-package ',name))
200 `(export ',(struct-constructor-name name) (symbol-package ',(struct-constructor-name name))))
201 (mapcar (lambda (slot) (slot->export-accessor name slot)) slots))))))
203 (defun boxed-c-structure-name (name)
204 (get (g-boxed-root name) 'c-name))
206 (defclass g-boxed-ref () ((pointer :accessor pointer :initarg :pointer)))
208 (defvar *g-boxed-gc-lock* (make-recursive-lock "g-boxed-gc-lock"))
209 (defvar *known-boxed-refs* (tg:make-weak-hash-table :test 'equal :weakness :value))
210 (defvar *boxed-ref-count* (make-hash-table :test 'equal))
211 (defvar *boxed-ref-owner* (make-hash-table :test 'equal))
213 (defun boxed-ref-free-function (name)
214 (or (get name 'free-function)
215 (error "g-boxed-ref class ~A has no free-function" name)))
217 (defun disown-boxed-ref (object)
218 (setf (gethash (pointer-address (pointer object)) *boxed-ref-owner*) :foreign))
220 (defun dispose-boxed-ref (type pointer)
221 (debugf "disposing g-boxed-ref ~A~%" pointer)
223 (unless (gethash (pointer-address pointer) *boxed-ref-count*)
224 (error "g-boxed-ref ~A is already disposed from lisp-side" pointer))
225 (with-recursive-lock-held (*g-boxed-gc-lock*)
226 (awhen (gethash (pointer-address pointer) *known-boxed-refs*)
227 (debugf "Removing finalization from ~A for pointer ~A~%" it pointer)
228 (tg:cancel-finalization it))
229 (when (eq :lisp (gethash (pointer-address pointer) *boxed-ref-owner*))
230 (funcall (boxed-ref-free-function type) pointer))
231 (remhash (pointer-address pointer) *known-boxed-refs*)
232 (remhash (pointer-address pointer) *boxed-ref-count*)
233 (remhash (pointer-address pointer) *boxed-ref-owner*)
234 (debugf "Disposed of g-boxed-ref ~A (object ~A)~%"
236 (gethash (pointer-address pointer) *known-boxed-refs*))))
238 (defmethod initialize-instance :after ((object g-boxed-ref) &key)
239 (with-recursive-lock-held (*g-boxed-gc-lock*)
240 (let ((address (pointer-address (pointer object))))
241 (awhen (gethash address *known-boxed-refs*)
242 (tg:cancel-finalization it))
243 (setf (gethash address *known-boxed-refs*) object)
244 (setf (gethash address *boxed-ref-count*) 1)
245 (setf (gethash address *boxed-ref-owner*)
246 (gethash address *boxed-ref-owner* :foreign)))
247 (debugf "setting g-boxed-ref-count of ~A (for ptr ~A) to 1~%" object (pointer object))
248 (let ((p (pointer object))
249 (type (type-of object))
250 (s (format nil "~A" object)))
251 (tg:finalize object (lambda ()
253 (dispose-boxed-ref type p)
254 (error (e) (format t "Error ~A for ~A~%" e s))))))))
256 (defmethod release ((object g-boxed-ref))
257 (debugf "releasing g-boxed-ref ~A~%" (pointer object))
258 (unless (gethash (pointer-address (pointer object)) *boxed-ref-count*)
259 (error "g-boxed-ref ~A is already disposed from lisp-side" (pointer object)))
260 (decf (gethash (pointer-address (pointer object)) *boxed-ref-count*))
261 (when (zerop (gethash (pointer-address (pointer object)) *boxed-ref-count*))
262 (dispose-boxed-ref (type-of object) (pointer object))))
264 (define-foreign-type g-boxed-ref-type ()
265 ((class-name :reader g-boxed-ref-class-name :initarg :class-name)
266 (owner :reader g-boxed-ref-owner :initarg :owner :initform nil))
267 (:actual-type :pointer))
269 (define-parse-method g-boxed-ref (class-name &key (owner :foreign))
270 (unless (get class-name 'is-g-boxed-ref)
271 (error "~A is not a subtype of G-BOXED-REF (~A: ~S)" class-name class-name (symbol-plist class-name)))
272 (make-instance 'g-boxed-ref-type :class-name class-name :owner owner))
274 (defmethod translate-to-foreign (value (type g-boxed-ref-type))
279 (defun convert-g-boxed-ref-from-pointer (pointer name type)
280 (unless (null-pointer-p pointer)
281 (with-recursive-lock-held (*g-boxed-gc-lock*)
282 (or (aprog1 (gethash (pointer-address pointer) *known-boxed-refs*)
283 (when it (debugf "Boxed-ref for ~A is found (~A)~%" pointer it))
284 (when it (incf (gethash (pointer-address pointer) *boxed-ref-count*)))
286 (aprog1 (make-instance name :pointer pointer)
287 (setf (gethash (pointer-address pointer) *boxed-ref-owner*) (g-boxed-ref-owner type))
288 (debugf "Boxed-ref for ~A is created (~A) with owner ~A~%" pointer it
289 (gethash (pointer-address pointer) *boxed-ref-owner*))
292 (defmethod translate-from-foreign (value (type g-boxed-ref-type))
293 (let ((owner (or (gethash (pointer-address value) *boxed-ref-owner*) (g-boxed-ref-owner type)))) ;;This is needed to prevent changing ownership of already created
295 (convert-g-boxed-ref-from-pointer value (g-boxed-ref-class-name type) type)
296 (setf (gethash (pointer-address value) *boxed-ref-owner*) owner))))
298 (defun g-boxed-ref-slot->methods (class slot)
299 (bind (((slot-name &key reader writer type (accessor slot-name)) slot))
300 `(progn ,@(when reader
301 (list `(defmethod ,accessor ((object ,class))
302 ,(if (stringp reader)
303 `(foreign-funcall ,reader :pointer (pointer object) ,type)
304 `(,reader object)))))
306 (list `(defmethod (setf ,accessor) (new-value (object ,class))
307 ,(if (stringp writer)
308 `(foreign-funcall ,writer :pointer (pointer object) ,type new-value)
309 `(,writer new-value object))))))))
311 (defmacro define-g-boxed-ref (gobject-name name &rest properties)
312 (let ((free-fn (second (find :free-function properties :key 'first)))
313 (alloc-fn (second (find :alloc-function properties :key 'first)))
314 (slots (rest (find :slots properties :key 'first))))
315 (unless (and free-fn alloc-fn) (error "All of :free-function, :alloc-function must be specified"))
316 `(progn (defclass ,name (g-boxed-ref) ())
317 (defmethod initialize-instance :before ((object ,name) &key pointer)
318 (unless (or pointer (slot-boundp object 'pointer))
319 (setf (pointer object) (,alloc-fn)
320 (gethash (pointer-address (pointer object)) *boxed-ref-owner*) :lisp)))
321 (setf (get ',name 'free-function) ',free-fn)
322 (eval-when (:compile-toplevel :load-toplevel :execute)
323 (setf (get ',name 'is-g-boxed-ref) t))
324 ,@(mapcar (lambda (slot)
325 (g-boxed-ref-slot->methods name slot))
327 (register-boxed-type ,gobject-name ',name))))
329 (define-foreign-type fixed-array ()
330 ((element-type :reader fixed-array-element-type :initarg :element-type :initform (error "Element type must be specified"))
331 (array-size :reader fixed-array-array-size :initarg :array-size :initform (error "Array size must be specified")))
332 (:actual-type :pointer))
334 (define-parse-method fixed-array (element-type array-size)
335 (make-instance 'fixed-array :element-type element-type :array-size array-size))
337 (defmethod translate-from-foreign (ptr (type fixed-array))
338 (when (not (null-pointer-p ptr))
339 (let ((result (make-array (fixed-array-array-size type)))
340 (el-type (fixed-array-element-type type)))
342 for i from 0 below (fixed-array-array-size type)
343 do (setf (aref result i) (mem-aref ptr el-type i)))
346 (defvar *registered-boxed-types* (make-hash-table :test 'equal))
347 (defvar *registered-boxed-names* (make-hash-table))
348 (defun register-boxed-type (name type)
349 (setf (gethash name *registered-boxed-types*) type
350 (gethash type *registered-boxed-names*) name))
351 (defun get-registered-boxed-type (name)
352 (gethash name *registered-boxed-types*))
354 (defun boxed-type-gname (type)
355 (gethash type *registered-boxed-names*))
357 (defun set-gvalue-boxed (gvalue value)
361 ((typep value 'g-boxed-ref)
362 (g-value-set-boxed gvalue (pointer value)))
363 (t (g-value-take-boxed gvalue (g-boxed->cstruct value :alloc-type :boxed)))))
364 (g-value-set-boxed gvalue (null-pointer))))
366 (defun parse-gvalue-boxed (gvalue)
367 (let* ((g-type (gvalue-type gvalue))
368 (type-name (g-type-name g-type))
369 (boxed-type (get-registered-boxed-type type-name)))
371 (warn t "Type ~A is a not registered GBoxed~%" type-name)
372 (return-from parse-gvalue-boxed nil))
373 (unless (null-pointer-p (g-value-get-boxed gvalue))
375 ((subtypep boxed-type 'g-boxed-ref) (convert-g-boxed-ref-from-pointer (g-value-get-boxed gvalue) boxed-type (make-instance 'g-boxed-ref-type :class-name boxed-type :owner :foreign)))
376 (t (parse-g-boxed (g-value-get-boxed gvalue) boxed-type))))))