source: trunk/source/objc-bridge/objc-support.lisp @ 12659

Last change on this file since 12659 was 12659, checked in by gb, 11 years ago

Arrgh. Need to straighten out Darwin leading underscores nonsense.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 21.4 KB
Line 
1;;;-*-Mode: LISP; Package: CCL -*-
2
3(in-package "CCL")
4
5(eval-when (:compile-toplevel :load-toplevel :execute)
6  (require "BRIDGE"))
7
8(defun allocate-objc-object (class)
9  (#/alloc class))
10
11(defun conforms-to-protocol (thing protocol)
12  (#/conformsToProtocol: thing (objc-protocol-address protocol)))
13
14
15
16
17#+(or apple-objc cocotron-objc)
18(defun iterate-over-objc-classes (fn)
19  (let* ((n (#_objc_getClassList (%null-ptr) 0)))
20    (declare (fixnum n))
21    (%stack-block ((buffer (the fixnum (ash n target::word-shift))))
22      (#_objc_getClassList buffer n)
23      (do* ((i 0 (1+ i)))
24           ((= i n) (values))
25        (declare (fixnum i))
26        (funcall fn (paref buffer (:* :id) i))))))
27
28#+(or apple-objc cocotron-objc)
29(defun count-objc-classes ()
30  (#_objc_getClassList (%null-ptr) 0)) 
31
32#+gnu-objc
33(defun iterate-over-objc-classes (fn)
34  (rletZ ((enum-state :address))
35    (loop
36      (let* ((class (#_objc_next_class enum-state)))
37        (if (%null-ptr-p class)
38          (return)
39          (funcall fn class))))))
40
41#+gnu-objc
42(defun count-objc-classes ()
43  (let* ((n 0))
44    (declare (fixnum n))
45    (rletZ ((enum-state :address))
46      (if (%null-ptr-p (#_objc_next_class enum-state))
47        (return n)
48        (incf n)))))
49
50(defun %note-protocol (p)
51  (with-macptrs ((cname (objc-message-send p "name" :address)))
52    (let* ((namelen (%cstrlen cname))
53           (name (make-string namelen)))
54      (declare (dynamic-extent name))
55      (%str-from-ptr cname namelen name)
56      (let* ((proto (or (gethash name *objc-protocols*)
57                        (progn
58                          (setq name (subseq name 0))
59                          (setf (gethash name *objc-protocols*)
60                                (make-objc-protocol :name name))))))
61        (unless (objc-protocol-address proto)
62          (setf (objc-protocol-address proto) (%inc-ptr p 0)))
63        proto))))
64
65(defun note-class-protocols (class)
66  #-(or apple-objc-2.0 cocotron-objc)
67  (do* ((protocols (pref class :objc_class.protocols)
68                   (pref protocols :objc_protocol_list.next)))
69       ((%null-ptr-p protocols))
70    (let* ((count (pref protocols :objc_protocol_list.count)))
71      (with-macptrs ((list (pref protocols :objc_protocol_list.list)))
72        (dotimes (i count)
73          (with-macptrs ((p (paref list (:* (:* (:struct :<P>rotocol))) i)))
74            (%note-protocol p))))))
75  #+(or apple-objc-2.0 cocotron-objc)
76  (rlet ((p-out-count :int 0))
77    (with-macptrs ((protocols (#_class_copyProtocolList class p-out-count)))
78      (let* ((n (pref p-out-count :int)))
79        (dotimes (i n)
80          (with-macptrs ((p (paref protocols (:* (:* (:struct :<P>rotocol))) i)))
81            (%note-protocol p))))
82      (unless (%null-ptr-p protocols) (#_free protocols)))))
83           
84
85(defun map-objc-classes (&optional (lookup-in-database-p t))
86  (iterate-over-objc-classes
87   #'(lambda (class)
88       (note-class-protocols class)
89       (install-foreign-objc-class class lookup-in-database-p))))
90
91(let* ((nclasses 0)
92       (lock (make-lock)))
93  (declare (fixnum nclasses))
94  (defun maybe-map-objc-classes (&optional use-db)
95    (with-lock-grabbed (lock)
96      (let* ((new (count-objc-classes)))
97        (declare (fixnum new))
98        (unless (= nclasses new)
99          (setq nclasses new)
100          (map-objc-classes use-db))
101        t)))
102  (defun reset-objc-class-count ()
103    (with-lock-grabbed (lock)
104      (setq nclasses 0))))
105
106(register-objc-class-decls)
107(maybe-map-objc-classes t)
108(register-objc-init-messages)
109
110#+gnu-objc
111(defun iterate-over-class-methods (class method-function)
112  (do* ((mlist (pref class :objc_class.methods)
113               (pref mlist :objc_method_list.method_next)))
114       ((%null-ptr-p mlist))
115    (do* ((n (pref mlist :objc_method_list.method_count))
116          (i 0 (1+ i))
117          (method (pref mlist :objc_method_list.method_list)
118                  (%incf-ptr method (record-length :objc_method))))
119         ((= i n))
120      (declare (fixnum i n))
121      (funcall method-function method class))))
122
123#+gnu-objc
124(progn
125  ;; Er, um ... this needs lots-o-work.
126  (let* ((objc-class-count 0))
127    (defun reset-objc-class-count () (setq objc-class-count 0))
128    (defun note-all-library-methods (method-function)
129      (do* ((i objc-class-count (1+ i))
130            (class (id->objc-class i) (id->objc-class i)))
131           ((eq class 0))
132        (iterate-over-class-methods class method-function)
133        (iterate-over-class-methods (id->objc-metaclass i) method-function))))
134  (def-ccl-pointers revive-objc-classes ()
135    (reset-objc-class-count)))
136
137(defun retain-obcj-object (x)
138  (objc-message-send x "retain"))
139
140
141#+apple-objc-2.0
142(progn
143(defun setup-objc-exception-globals ()
144  (flet ((set-global (offset name)
145           (setf (%get-ptr (%int-to-ptr (+ (target-nil-value) (%kernel-global-offset offset))))
146                 (foreign-symbol-address name))))
147    (set-global 'objc-2-personality "___objc_personality_v0")
148    (set-global 'objc-2-begin-catch "objc_begin_catch")
149    (set-global 'objc-2-end-catch "objc_end_catch")
150    (set-global 'unwind-resume "__Unwind_Resume")))
151
152
153(def-ccl-pointers setup-objc-exception-handling ()
154  (setup-objc-exception-globals))
155
156(setup-objc-exception-globals)
157)
158
159
160(defvar *condition-id-map* (make-id-map) "Map lisp conditions to small integers")
161
162;;; Encapsulate an NSException in a lisp condition.
163(define-condition ns-exception (error)
164  ((ns-exception :initarg :ns-exception :accessor ns-exception))
165  (:report (lambda (c s)
166             (format s "Objective-C runtime exception: ~&~a"
167                     (nsobject-description (ns-exception c))))))
168
169
170
171(defclass ns-lisp-exception (ns::ns-exception)
172    ((condition :initarg :condition :initform nil :reader ns-lisp-exception-condition))
173  (:metaclass ns::+ns-object))
174
175(objc:defmethod #/init ((self ns-lisp-exception))
176  (#/initWithName:reason:userInfo: self #@"lisp exception" #@"lisp exception" +null-ptr+))
177
178
179(defun recognize-objc-exception (x)
180  (if (typep x 'ns:ns-exception)
181    (ns-exception->lisp-condition x)))
182
183(pushnew 'recognize-objc-exception *foreign-error-condition-recognizers*)
184
185(defun %make-nsstring (string)
186  (with-encoded-cstrs :utf-8 ((s string))
187    (#/initWithUTF8String: (#/alloc ns:ns-string) s)))
188
189(defmacro with-autoreleased-nsstring ((nsstring lisp-string) &body body)
190  `(let* ((,nsstring (%make-nsstring ,lisp-string)))
191     (#/autorelease ,nsstring)
192     ,@body))
193
194(defmacro with-autoreleased-nsstrings (speclist &body body)
195  (with-specs-aux 'with-autoreleased-nsstring speclist body))
196
197(defun retain-objc-instance (instance)
198  (#/retain instance))
199
200;;; May have to create/release autorelease pools before the bridge
201;;; is fully reinitialized, so use low-level OBJC-MESSAGE-SEND
202;;; and @class.
203(defun create-autorelease-pool ()
204  (objc-message-send
205   (objc-message-send (@class "NSAutoreleasePool") "alloc") "init"))
206
207(defun release-autorelease-pool (p)
208  (objc-message-send p "release" :void))
209
210
211#-ascii-only
212(progn
213#-windows-target
214(defun lisp-string-from-nsstring (nsstring)
215  ;; The NSData object created here is autoreleased.
216  (let* ((data (#/dataUsingEncoding:allowLossyConversion:
217                nsstring
218                #+little-endian-target #x9c000100
219                #+big-endian-target #x98000100
220                nil)))
221    (unless (%null-ptr-p data)
222      (let* ((nbytes (#/length data))
223             (string (make-string (ash nbytes -2))))
224        ;; BLT the 4-byte code-points from the NSData object
225        ;; to the string, return the string.
226        (%copy-ptr-to-ivector (#/bytes data) 0 string 0 nbytes)))))
227
228#+windows-target
229(defun lisp-string-from-nsstring (nsstring)
230  (let* ((n (#/length nsstring)))
231    (%stack-block ((buf (* (1+ n) (record-length :unichar))))
232      (#/getCharacters: nsstring buf)
233      (setf (%get-unsigned-word buf (+ n n)) 0)
234      (%get-native-utf-16-cstring buf))))
235       
236)
237
238#+ascii-only
239(defun lisp-string-from-nsstring (nsstring)
240  (with-macptrs (cstring)
241    (%setf-macptr cstring
242                  (#/cStringUsingEncoding: nsstring #$NSASCIIStringEncoding))
243    (unless (%null-ptr-p cstring)
244      (%get-cstring cstring))))
245
246
247(objc:defmethod #/reason ((self ns-lisp-exception))
248  (with-slots (condition) self
249    (if condition
250      (#/autorelease (%make-nsstring (format nil "~A" condition)))
251      (call-next-method))))
252
253(objc:defmethod #/description ((self ns-lisp-exception))
254  (#/stringWithFormat: ns:ns-string #@"Lisp exception: %@" (#/reason self)))
255
256
257                     
258(defun ns-exception->lisp-condition (nsexception)
259  (if (typep nsexception 'ns-lisp-exception)
260    (ns-lisp-exception-condition nsexception)
261    (make-condition 'ns-exception :ns-exception nsexception)))
262
263
264(defmethod ns-exception ((c condition))
265  "Map a lisp condition object to an NSException.  Note that instances
266of the NS-EXCEPTION condition class implement this by accessing an
267instance variable."
268  ;;; Create an NSLispException with a lispid that encapsulates
269  ;;; this condition.
270
271  ;; (dbg (format nil "~a" c))
272  ;;(#_NSLog #@"Lisp exception: %@" :id (%make-nsstring (format nil "~a" c)))
273  (make-instance 'ns-lisp-exception :condition c))
274
275
276
277#+(or apple-objc cocotron-objc)         ; not really
278(progn
279
280
281#+ppc-target
282(defun objc-callback-error-return (condition return-value-pointer return-address-pointer)
283  ;; On PPC, the "address" of an external entry point is always
284  ;; aligned on a 32-bit word boundary.  On PPC32, it can always
285  ;; be represented as a fixnum; on PPC64, it might be a pointer
286  ;; instead.
287  ;; Note that this clobbers the actual (foreign) return address,
288  ;; replacing it with the address of #__NSRaiseError.  Note also
289  ;; that storing the NSException object as the return value has
290  ;; the desired effect of causing #__NSRaiseError to be called
291  ;; with that NSException as its argument (because r3 is used both
292  ;; as the canonical return value register and used to pass the
293  ;; first argument on PPC.)
294  (process-debug-condition *current-process* condition (%get-frame-ptr))
295  (let* ((addr (%reference-external-entry-point (load-time-value (external "__NSRaiseError")))))
296    (if (typep addr 'fixnum)
297      (%set-object return-address-pointer 0 addr)
298      (setf (%get-ptr return-address-pointer 0) addr)))
299  (setf (%get-ptr return-value-pointer 0) (ns-exception condition))
300  nil)
301
302#+x8664-target
303(progn
304(defloadvar *x8664-objc-callback-error-return-trampoline*
305    (let* ((code-bytes '(#x48 #x89 #xc7      ; movq %rax %rdi
306                         #x66 #x48 #x0f #x7e #xc0 ; movd %xmm0,%rax
307                         #x52                ; pushq %rdx
308                         #xff #xe0))         ; jmp *rax
309           (nbytes (length code-bytes))
310           (ptr (%allocate-callback-pointer 16)))
311      (dotimes (i nbytes ptr)
312        (setf (%get-unsigned-byte ptr i) (pop code-bytes)))))
313
314(defun objc-callback-error-return (condition return-value-pointer return-address-pointer) 
315  ;; The callback glue reserves space for %rax at return-value-pointer-8,
316  ;; for %rdx at -16, for %xmm0 at -24.  Store NS-EXCEPTION in the
317  ;; %rax slot, the address of #_objc_exception_throw in the %rdx slot, the
318  ;; original return address in the %xmm0 slot, and force a return to
319  ;; the trampoline code above.
320  (process-debug-condition *current-process* condition (%get-frame-ptr))
321  (setf (%get-ptr return-value-pointer -8) (ns-exception condition)
322        (%get-ptr return-value-pointer -16) (%get-ptr return-address-pointer 0)
323        (%get-ptr return-address-pointer 0) *x8664-objc-callback-error-return-trampoline*)
324  ;; A foreign entry point is always an integer on x8664.
325  (let* ((addr (%reference-external-entry-point (load-time-value (external "_objc_exception_throw")))))
326    (if (< addr 0)                      ;unlikely
327      (setf (%%get-signed-longlong return-value-pointer -24) addr)
328      (setf (%%get-unsigned-longlong return-value-pointer -24) addr)))
329  nil)
330
331
332)
333
334#+x8632-target
335(progn
336
337(defloadvar *x8632-objc-callback-error-return-trampoline*
338    (let* ((code-bytes '(#x83 #xec #x10      ; subl $16,%esp
339                         #x89 #x04 #x24      ; movl %eax,(%esp)
340                         #x52                ; pushl %edx
341                         #xff #xe1))         ; jmp *ecx
342           (nbytes (length code-bytes))
343           (ptr (%allocate-callback-pointer 16)))
344      (dotimes (i nbytes ptr)
345        (setf (%get-unsigned-byte ptr i) (pop code-bytes)))))
346
347(defun objc-callback-error-return (condition return-value-pointer return-address-pointer)
348  (process-debug-condition *current-process* condition (%get-frame-ptr))
349  (let* ((addr (%reference-external-entry-point (load-time-value (external #+cocotron-objc "_NSRaiseException" #-cocotron-objc "__NSRaiseError")))))
350    (setf (%get-unsigned-long return-value-pointer -12 ) addr))
351  (setf (%get-ptr return-value-pointer -8) (ns-exception condition)
352        (%get-ptr return-value-pointer -4) (%get-ptr return-address-pointer)
353        (%get-ptr return-address-pointer) *x8632-objc-callback-error-return-trampoline*)
354  nil)
355)
356
357)
358
359
360
361(defun open-main-bundle ()
362  (#/mainBundle ns:ns-bundle))
363
364;;; Create a new immutable dictionary just like src, replacing the
365;;; value of each key in key-value-pairs with the corresponding value.
366(defun copy-dictionary (src &rest key-value-pairs)
367  (declare (dynamic-extent key-value-pairs))
368  ;(#_NSLog #@"src = %@" :id src)
369  (let* ((count (#/count src))
370         (enum (#/keyEnumerator src))
371         (keys (#/arrayWithCapacity: ns:ns-mutable-array count))
372         (values (#/arrayWithCapacity: ns:ns-mutable-array count)))
373    (loop
374        (let* ((nextkey (#/nextObject enum)))
375          (when (%null-ptr-p nextkey)
376            (return))
377          (do* ((kvps key-value-pairs (cddr kvps))
378                (newkey (car kvps) (car kvps))
379                (newval (cadr kvps) (cadr kvps)))
380               ((null kvps)
381                ;; Copy the key, value pair from the src dict
382                (#/addObject: keys nextkey)
383                (#/addObject: values (#/objectForKey: src nextkey)))
384            (when (#/isEqualToString: nextkey newkey)
385              (#/addObject: keys nextkey)
386              (#/addObject: values newval)
387              (return)))))
388    (make-instance 'ns:ns-dictionary
389                   :with-objects values
390                   :for-keys keys)))
391
392
393
394
395(defparameter *objc-description-max-length* 1024 "Limit on the length of NSObject description strings if non-NIL.")
396
397(defun %cf-instance-p (instance)
398  #-apple-objc (declare (ignore instance))
399  #+apple-objc
400  (> (objc-message-send instance "_cfTypeID" #>CFTypeID) 1))
401 
402
403(defun initialized-nsobject-p (nsobject)
404  (or (objc-class-p nsobject)
405      (objc-metaclass-p nsobject)
406      (has-lisp-slot-vector nsobject)
407      (let* ((cf-p (%cf-instance-p nsobject)) 
408             (isize (if cf-p (external-call "malloc_size" :address nsobject :size_t) (%objc-class-instance-size (#/class nsobject))))
409             (skip (if cf-p (+ (record-length :id) 4 #+64-bit-target 4) (record-length :id))))
410        (declare (fixnum isize skip))
411        (or (> skip isize)
412            (do* ((i skip (1+ i)))
413                 ((>= i isize))
414              (declare (fixnum i))
415              (unless (zerop (the (unsigned-byte 8) (%get-unsigned-byte nsobject i)))
416                (return t)))))))
417 
418(defun nsobject-description (nsobject)
419  "Returns a lisp string that describes nsobject.  Note that some
420NSObjects describe themselves in more detail than others."
421  (if (initialized-nsobject-p nsobject)
422    (with-autorelease-pool
423        (let* ((desc (#/description nsobject)))
424          (if (or (null *objc-description-max-length*)
425                  (< (#/length desc) *objc-description-max-length*))
426            (lisp-string-from-nsstring desc)
427            (ns:with-ns-range (r 0 *objc-description-max-length*)
428              (format nil "~a[...]"(lisp-string-from-nsstring (#/substringWithRange: desc r)))))))
429    "[uninitialized]"))
430
431
432
433
434
435;;; This can fail if the nsstring contains non-8-bit characters.
436(defun lisp-string-from-nsstring-substring (nsstring start length)
437  (%stack-block ((cstring (1+ length)))
438    (#/getCString:maxLength:range:remainingRange:
439       nsstring  cstring  length (ns:make-ns-range start length) +null-ptr+)
440    (%get-cstring cstring)))
441
442(def-standard-initial-binding *listener-autorelease-pool* nil)
443
444(setq *listener-autorelease-pool* (create-autorelease-pool))
445
446(define-toplevel-command :global rap () "Release and reestablish *LISTENER-AUTORELEASE-POOL*"
447  (when (eql *break-level* 0)
448    (without-interrupts
449     (when (boundp '*listener-autorelease-pool*)
450       (let* ((old *listener-autorelease-pool*))
451         (if old (release-autorelease-pool old))
452         (setq *listener-autorelease-pool* (create-autorelease-pool)))))))
453
454#+apple-objc
455(defun show-autorelease-pools ()
456  (objc-message-send (@class ns-autorelease-pool) "showPools" :void))
457
458#+gnu-objc
459(defun show-autorelease-pools ()
460  (do* ((current (objc-message-send (@class ns-autorelease-pool) "currentPool")
461                 (objc-message-send current "_parentAutoreleasePool"))
462        (i 0 (1+ i)))
463       ((%null-ptr-p current) (values))
464    (format t "~& ~d : ~a [~d]"
465            i
466            (nsobject-description current)
467            (pref current :<NSA>utorelease<P>ool._released_count))))
468
469#+cocotron-objc
470(defun show-autorelease-pools ()
471  (%string-to-stderr  "No info about current thread's autorelease pools is available"))
472
473(define-toplevel-command :global sap () "Log information about current thread's autorelease-pool(s) to C's standard error stream"
474  (show-autorelease-pools))
475
476(define-toplevel-command :global kap () "Release (but don't reestablish) *LISTENER-AUTORELEASE-POOL*"
477  (when (eql *break-level* 0)
478    (without-interrupts
479     (when (boundp '*listener-autorelease-pool*)
480       (let* ((p *listener-autorelease-pool*))
481         (setq *listener-autorelease-pool* nil)
482         (release-autorelease-pool p))))))
483
484;;; Use the interfaces for an add-on ObjC framework.  We need to
485;;; tell the bridge to reconsider what it knows about the type
486;;; signatures of ObjC messages, since the new headers may define
487;;; a method whose type signature differs from the message's existing
488;;; methods.  (This probably doesn't happen too often, but it's
489;;; possible that some SENDs that have already been compiled would
490;;; need to be recompiled with that augmented method type info, e.g.,
491;;; because ambiguity was introduced.)
492
493(defun augment-objc-interfaces (dirname)
494  (use-interface-dir dirname)
495  (register-objc-class-decls)
496  (update-objc-method-info))
497
498;;; A list of "standard" locations which are known to contain
499;;; framework bundles.  We should look in ~/Library/Frameworks/" first,
500;;; if it exists.
501(defparameter *standard-framework-directories*
502  (list #p"/Library/Frameworks/"
503        #p"/System/Library/Frameworks/"))
504
505
506
507;;; This has to run during application (re-)initializtion, so it
508;;; uses lower-level bridge features.
509(defun %reload-objc-framework (path)
510  (when (probe-file path)
511    (let* ((namestring (native-translated-namestring path)))
512      (with-cstrs ((cnamestring namestring))
513        (with-nsstr (nsnamestring cnamestring (length namestring))
514          (with-autorelease-pool
515              (let* ((bundle (objc-message-send (@class "NSBundle")
516                                                "bundleWithPath:"
517                                                :id nsnamestring :id)))
518                (unless (%null-ptr-p bundle)
519                  (objc-message-send bundle "load" :<BOOL>)))))))))
520
521
522(defun load-objc-extension-framework (name)
523  (let* ((dirs *standard-framework-directories*)
524         (home-frameworks (make-pathname :defaults nil
525                                         :directory
526                                         (append (pathname-directory
527                                                  (user-homedir-pathname))
528                                                 '("Library" "Frameworks"))))
529         (fname (list (format nil "~a.framework" name))))
530    (when (probe-file home-frameworks)
531      (pushnew home-frameworks dirs :test #'equalp))
532    (dolist (d dirs)
533      (let* ((path (probe-file (make-pathname :defaults nil
534                                              :directory (append (pathname-directory d)
535                                                                 fname)))))
536        (when path
537          (let* ((namestring (native-translated-namestring path)))
538            (with-cstrs ((cnamestring namestring))
539              (with-nsstr (nsnamestring cnamestring (length namestring))
540                (with-autorelease-pool
541                    (let* ((bundle (#/bundleWithPath: ns:ns-bundle nsnamestring))
542                           (winning (unless (%null-ptr-p bundle)
543                                      t)))
544                      (when winning
545                        (let* ((libpath (#/executablePath bundle)))
546                          (unless (%null-ptr-p libpath)
547                            (open-shared-library (lisp-string-from-nsstring
548                                                  libpath))))
549                        (#/load bundle)
550                        (pushnew path *extension-framework-paths*
551                                 :test #'equalp)
552                        (map-objc-classes)
553                        ;; Update info about init messages.
554                        (register-objc-init-messages))
555                      (return winning)))))))))))
556
557(defun objc:load-framework (framework-name interfaces-name)
558  (use-interface-dir interfaces-name)
559  (or (load-objc-extension-framework framework-name)
560      (error "Can't load ObjC framework ~s" framework-name))
561  (augment-objc-interfaces interfaces-name))
562
563                     
564(defmethod print-object ((p ns:protocol) stream)
565  (print-unreadable-object (p stream :type t)
566    (format stream "~a (#x~x)"
567            (%get-cstring (#/name p))
568            (%ptr-to-int p))))
569
570                                         
571(defmethod terminate ((instance objc:objc-object))
572  (objc-message-send instance "release"))
573
574
575(provide "OBJC-SUPPORT")
Note: See TracBrowser for help on using the repository browser.