;;;; Objective-CL, an Objective-C bridge for Common Lisp. ;;;; Copyright (C) 2007 Matthias Andreas Benkard. ;;;; ;;;; This program is free software: you can redistribute it and/or ;;;; modify it under the terms of the GNU Lesser General Public License ;;;; as published by the Free Software Foundation, either version 3 of ;;;; the License, or (at your option) any later version. ;;;; ;;;; This program is distributed in the hope that it will be useful, but ;;;; WITHOUT ANY WARRANTY; without even the implied warranty of ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ;;;; Lesser General Public License for more details. ;;;; ;;;; You should have received a copy of the GNU Lesser General Public ;;;; License along with this program. If not, see ;;;; . (in-package #:mulk.objective-cl) ;;;; (@* "Convenience types") (deftype c-pointer () '(satisfies pointerp)) (deftype argument-number () `(integer 0 ,call-arguments-limit)) ;;;; (@* "Foreign data types") (defctype char-pointer :pointer) ;;;; (@* "Objective-C object wrapper classes") (with-compilation-unit () ; needed for class finalization (defclass c-pointer-wrapper () ((pointer :type c-pointer :reader pointer-to :initarg :pointer :initform (cffi:null-pointer))))) (defmethod make-load-form ((instance c-pointer-wrapper) &optional environment) (declare (ignore environment)) ;; (TYPE-OF INSTANCE) works because MAKE-POINTER-WRAPPER accepts ;; subclasses of ID as well as ID itself. `(make-pointer-wrapper ',(type-of instance) :pointer (make-pointer ,(pointer-address (pointer-to instance))))) ;; The following may be needed by some implementations (namely Allegro ;; CL). (eval-when (:compile-toplevel :load-toplevel :execute) (loop for class-name in '(c2mop:funcallable-standard-object c-pointer-wrapper) for class = (find-class class-name nil) when class unless (c2mop:class-finalized-p class) do (c2mop:finalize-inheritance class))) ;; FIXME: I'm not confident about this, but it is needed in order to ;; make (DEFCLASS SELECTOR ...) work. ;; ;; On the other hand, CLISP's implementation notes specify this method ;; to return true by default for "some `obvious' cases" [29.12] such as ;; this one. Therefore, we needn't override it. In fact, we can't, at ;; least without disabling #'s package lock. #-clisp (defmethod c2mop:validate-superclass ((class c2mop:funcallable-standard-class) (superclass standard-class)) t) (defclass selector (c2mop:funcallable-standard-object c-pointer-wrapper) () (:metaclass c2mop:funcallable-standard-class) (:documentation "An Objective-C method selector. ## Description: Method selectors are Objective-C's equivalent to what Common Lisp calls **symbols**. Their use, however, is restricted to retrieving methods by name. In Common Lisp, you can **funcall** a __selector__ directly (see the note below for details and why you may want to do this). __selector__ objects cannot be created by means of __make-instance__. Use __find-selector__ instead. ## Note: Instead of using __invoke__, which is neither macro-friendly nor very useful for method selection at run-time, you may **funcall** selectors directly. Naturally, __apply__ works as well. The following calls are all equivalent: (invoke-by-name instance \"stringWithCString:encoding:\" \"Mulk.\" 4) (invoke instance :string-with-c-string \"Mulk.\" :encoding 4) (funcall (selector \"stringWithCString:encoding:\") instance \"Mulk.\" 4) (apply (selector \"stringWithCString:encoding:\") (list instance \"Mulk.\" 4)) ## See also: __find-selector__")) (defmethod shared-initialize :after ((selector selector) slot-names &rest initargs &key &allow-other-keys) (declare (ignore slot-names initargs)) (c2mop:set-funcallable-instance-function selector #'(lambda (receiver &rest args) (apply #'invoke-by-name receiver selector args)))) (defmethod make-load-form ((selector selector) &optional environment) (declare (ignore environment)) `(make-pointer-wrapper 'selector :pointer (make-pointer ,(pointer-address (pointer-to selector))))) (defclass id (c-pointer-wrapper) () (:documentation "The type of all Objective-C objects. ## Description: The class __id__ is the supertype of all Objective-C instance types. It comprises all kinds of Objective-C objects that are instances of some Objective-C class, that is, neither primitive C values nor __selector__, __class__ or __exception__ objects. __id__ objects cannot be created by means of __make-instance__. Use a suitable class method instead as you would in Objective-C. ## Examples: (invoke (find-objc-class 'ns-object) 'self) ;=> # (invoke (find-objc-class 'ns-string) :string-with-c-string \"Mulk.\") ;=> # (invoke (find-objc-class 'ns-string) 'new) ;=> # ## See also: __invoke__, __invoke-by-name__, __exception__")) (defclass objective-c-class (standard-class c-pointer-wrapper) ()) (defclass objective-c-meta-class (objective-c-class) ()) (define-condition exception (error) ((pointer :type c-pointer :reader pointer-to :initarg :pointer)) (:report (lambda (condition stream) (format stream "The Objective-C runtime has issued an exception of ~ type `~A'.~&~ Reason: ~A." (invoke-by-name (invoke-by-name condition "name") "UTF8String") (invoke-by-name (invoke-by-name condition "reason") "UTF8String")))) (:documentation "The condition type for Objective-C exceptions. ## Description: Whenever an Objective-C call made by means of __invoke__ or __invoke-by-name__ raises an exception, the exception is propagated to the Lisp side by being encapsulated in an __exception__ object and signaled. Note that it is currently impossible to directly extract the original Objective-C exception from an __exception__ object, although it might arguably be desirable to do so. As __exception__ objects behave just like __id__ objects in almost all circumstances, this is not much of a problem, though. If you really do need an __id__ instance rather than an __exception__, you can simply send it the `self' message. ## Examples: \(With __install-reader-syntax__ enabled.) (handler-case [NSArray selph] ; oops, typo (exception (e) e)) ;=> # (class-of *) ;=> # (class-of [** self]) ;=> # ## See also: __id__")) (defclass foreign-value (c-pointer-wrapper) ((lisp-managed-cell :type (array boolean ()) :accessor foreign-value-lisp-managed-cell-p :initarg :lisp-managed-cell :documentation "Whether we need to handle deallocation."))) ;; FIXME: Document. (defclass foreign-struct (foreign-value) ((name :type (or null string) :accessor foreign-struct-name :initarg :name))) ;; The following are for private use only. (defclass opaque-struct (foreign-struct) ()) (defclass tagged-struct (foreign-struct) ((typespec :reader foreign-value-typespec :initarg :typespec))) (defclass opaque-union (opaque-struct) ()) (defclass tagged-union (tagged-struct) ()) ;; FIXME: Either document or throw away. (Does the C language actually ;; support returning arrays from functions? It certainly does not ;; support passing them as arguments.) (defclass foreign-array (foreign-value) ((element-type :type symbol :reader foreign-array-element-type :initarg :element-type) (length :type integer :reader foreign-array-length :initarg :type) (typespec :reader foreign-value-typespec :initarg :typespec))) ;; FIXME: Document. (defgeneric foreign-value-lisp-managed-p (foreign-value)) (defmethod foreign-value-lisp-managed-p ((foreign-value foreign-value)) (with-slots (lisp-managed-cell) foreign-value (aref lisp-managed-cell))) ;; FIXME: Document. (defgeneric (setf foreign-value-lisp-managed-p) (managedp foreign-value)) (defmethod (setf foreign-value-lisp-managed-p) (managedp (foreign-value foreign-value)) (with-slots (lisp-managed-cell) foreign-value (setf (aref lisp-managed-cell) (if managedp t nil)))) ;; FIXME: Document. (defgeneric foreign-value-pointer (foreign-value)) (defmethod foreign-value-pointer ((foreign-value foreign-value)) (pointer-to foreign-value)) (defun make-struct-wrapper (pointer typespec managedp) ;; We use a zero-dimensional array that the finaliser can close over ;; so that it (the finaliser) can decide whether to garbage-collect ;; the foreign data. ;; ;; Using the instance slot directly would be both easier and more ;; transparent, of course, but it also wouldn't work, because during ;; finalisation, the instance is not in a usable state anymore. (let ((managedp-cell (make-array '() :element-type 'boolean :initial-element managedp))) (flet ((finaliser () (when (aref managedp-cell) (foreign-free pointer)))) (let ((new-wrapper (make-instance (ecase (typespec-primary-type typespec) (struct 'tagged-struct) (union 'tagged-union)) :name (third typespec) :typespec typespec :pointer pointer :lisp-managed-cell managedp-cell))) (when managedp (trivial-garbage:finalize new-wrapper #'finaliser)))))) (defgeneric objcl-eql (obj1 obj2)) (defmethod objcl-eql ((obj1 c-pointer-wrapper) (obj2 c-pointer-wrapper)) (pointer-eq (pointer-to obj1) (pointer-to obj2))) (defmethod objcl-eql (obj1 obj2) (eql obj1 obj2))