;;;; 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) ;;; (@* "Method invocation") (defun invoke (receiver message-start &rest message-components) "Send a message to an Objective-C instance. ## Arguments and Values: *receiver* --- an Objective-C wrapper object. *message-start* --- a **symbol**. *message-components* --- an alternating **list** of **object**s and **symbol**s. Returns: *result* --- the return value of the method invocation. ## Description: All even-numbered *message components* are collected in order and the resulting **list** used as if as additional **argument**s to __invoke-by-name__. All uneven-numbered *message components*, which must be **symbol**s, are first split into parts separated by hyphens and each part converted into a **string** according to the following rules:
    1. If the keywords' **symbol name**s do contain **lowercase** **character**s, their case is left intact.
    2. If the keywords' **symbol name**s do not contain any **lowercase** **character**s, the following steps are taken in order to adjust their case.
      1. The first part is fully converted to **lowercase**.
      2. Any additional parts are also fully converted to **lowercase** except for their first letters, which are left intact.
  1. If the symbol is a **keyword**, the resulting **string** is suffixed by a **colon** (`:').
After that, all parts are concatenated in order to form a single *message name component*. The *message name components* are in turn concatenated in order to form the *message name* which is used as if as the second **argument** to __invoke-by-name__. ## Examples: (invoke (find-objc-class 'ns-string) :string-with-u-t-f-8-string \"Mulk.\") ;=> # (invoke (find-objc-class 'ns-string) '|:stringWithUTF8String| \"Mulk.\") ;=> # (invoke (find-objc-class 'ns-object) 'self) ;=> # (invoke (find-objc-class 'ns-object) 'name) ;=> \"NSObject\" (invoke (find-objc-class 'ns-string) :string-with-c-string \"Mulk.\" :encoding 4) ;=> # #.(setq \\*readtable\\* (copy-readtable)) #.(setf (readtable-case \\*readtable\\*) :invert) (invoke (find-objc-class 'ns-string) :stringWithCString \"Mulk.\" :encoding 4) ;=> # ## Note: Setting the **readtable case** of the **current readtable** to `:INVERT` is a good way of making the Lisp system behave as traditionally as possible while making Objective-C method names case-sensitive. On the other hand, writing all method names in lower case while separating parts by hyphens works nicely in all of the `:INVERT`, `:UPCASE`, `:DOWNCASE`, and `:PRESERVE` modes as well as Allegro CL's *modern mode*. ## Note 2: 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 (though the last one needs the syntax enhancement provided by __enable-method-syntax__ enabled and the selector registered by way of __collect-methods__): (invoke-by-name class \"stringWithCString:encoding:\" \"Mulk.\" 4) (invoke class :string-with-c-string \"Mulk.\" :encoding 4) (funcall (selector \"stringWithCString:encoding:\") class \"Mulk.\" 4) (apply (selector \"stringWithCString:encoding:\") (list class \"Mulk.\" 4)) (#/stringWithCString:encoding: class \"Mulk.\" 4) ## See also: __invoke-by-name__" (multiple-value-bind (message arglist) (split-method-call message-start message-components) (apply #'invoke-by-name receiver message arglist))) (defun invoke-by-name (receiver method-name &rest args) "Send a message to an Objective-C object by the name of the method. ## Arguments and Values: *receiver* --- an Objective-C wrapper object. *method-name* --- a *selector designator*. *args* --- a list of **object**s. Returns: *result* --- the return value of the method invocation. ## Description: __invoke-by-name__ is like __invoke__ except in its syntax. It sends the message whose selector is designated by *method-name*, which must be either a *string*, a *symbol*, a list of message name components as in a call to __invoke__, or an object of *type* __selector__, to *receiver*. ## Examples: (invoke-by-name (find-objc-class 'ns-string) '(:string-with-u-t-f-8-string) \"Mulk.\") ;=> # (invoke-by-name (find-objc-class 'ns-object) \"self\") ;=> # (invoke-by-name (find-objc-class 'ns-string) \"stringWithCString:encoding:\" \"Mulk.\" 4) ;=> # ## Note: __selector__ objects are funcallable. Therefore, 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) In fact, using __invoke-by-name__ is discouraged in favour of the latter form. ## Rationale: Whereas __invoke__ tries to make writing as well as reading method invocations easy by interspersing method name components with arguments as Objective-C does, __invoke-by-name__ is better suited for method selection at run time as well as code generation. It is also slightly easier to use with __apply__. ## See also: __invoke__" (invoke-by-name-super-v (typecase receiver (symbol (find-objc-class receiver)) ((or id objective-c-class) receiver) (t (pointer receiver))) method-name nil args)) (defun invoke-by-name-super-v (receiver method-name superclass-for-send-super args) ;; TODO: Support varargs. (let* ((selector (if (typep method-name 'selector) method-name (find-selector method-name)))) (multiple-value-bind (argc method-return-typestring method-return-type method-arg-typestrings method-arg-types) (retrieve-method-signature-info receiver selector) (assert (= argc (+ 2 (length args))) (args) "Wrong number of arguments (expected ~A, got ~A)." argc (+ 2 (length args))) (low-level-invoke receiver selector (if (and superclass-for-send-super (not (and (pointerp superclass-for-send-super) (null-pointer-p superclass-for-send-super)))) (pointer-to superclass-for-send-super) (null-pointer)) method-return-typestring method-return-type method-arg-typestrings method-arg-types argc args)))) (defun split-method-call (message-start message-components) (do* ((components-left (cons message-start message-components) (cddr components-left)) (message-list (list message-start) (cons (first components-left) message-list)) (arglist (if (null (rest components-left)) nil (list (second components-left))) (if (null (rest components-left)) arglist (cons (second components-left) arglist)))) ((null (cddr components-left)) (values (nreverse message-list) (nreverse arglist))))) (defun primitive-invoke (receiver method-name return-type &rest args) (flet ((ad-hoc-value->typespec (arg) (typespec (etypecase arg ;; According to Allegro CL, strings ;; are POINTERP (and thus elements of ;; the C-POINTER type), so they have ;; to come first in this TYPECASE ;; form. Weird. ;; ;; By the way, pointers are ;; represented as integers in Allegro ;; CL, so all integers are POINTERP, ;; too. (string '(:string ())) (selector '(selector ())) (c-pointer-wrapper '(id ())) (c-pointer '(:pointer ())) (integer '(:int ())))))) (let ((return-typespec (typespec `(,return-type ()))) (arg-typespecs (list* (typespec '(id ())) (typespec '(selector ())) (mapcar #'ad-hoc-value->typespec args)))) (low-level-invoke receiver (selector method-name) (null-pointer) (print-typespec-to-string return-typespec) return-typespec (mapcar #'print-typespec-to-string arg-typespecs) arg-typespecs (+ 2 (length args)) args)))) (define-cached-function retrieve-method-signature-info (receiver selector &aux (class-ptr (%objcl-object-get-class (pointer receiver)))) (cons (cffi:pointer-address class-ptr) (cffi:pointer-address (pointer-to selector))) (let* ((signature (objc-or (if (object-is-class-p receiver) (primitive-invoke receiver "methodSignatureForSelector:" 'id selector) (primitive-invoke (object-get-class receiver) "instanceMethodSignatureForSelector:" 'id selector)) (error (make-condition 'message-not-understood :class (object-get-class receiver) :selector selector)))) (argc (primitive-invoke signature "numberOfArguments" :unsigned-int)) (method-return-typestring (primitive-invoke signature "methodReturnType" :string)) (method-return-type (parse-typespec method-return-typestring t)) (method-arg-typestrings (loop for x from 0 below argc collect (primitive-invoke signature "getArgumentTypeAtIndex:" :string x))) (method-arg-types (mapcar #'parse-typespec method-arg-typestrings))) (values argc method-return-typestring method-return-type method-arg-typestrings method-arg-types))) (defun typespec->c-type (typespec) (case (typespec-primary-type typespec) ((:pointer pointer struct union id objective-c-class exception array selector :id :class :exception :selector) :pointer) ((:string) :string) (otherwise (typespec-primary-type typespec)))) (defun low-level-invoke (receiver selector superclass-pointer-for-send-super return-typestring return-type arg-typestrings arg-types argc args) (when (object-is-class-p receiver) (foreign-class-ensure-registered receiver)) (let ((return-c-type (typespec->c-type return-type)) (arg-c-types (mapcar #'typespec->c-type arg-types))) (with-foreign-string-pool (register-temporary-string allocate-string-and-register) (cffi:with-foreign-objects ((objc-arg-typestrings :string (- argc 2)) (objc-arg-ptrs :pointer argc) (objc-return-value-cell ;; Note that this cell is not used if ;; the method returns a struct, array ;; or union. For these, see ;; OBJC-STRUCT-RETURN-VALUE-CELL ;; below. (if (eq return-c-type :void) :int return-c-type)) (objc-arg-buffer +pessimistic-allocation-type+ argc)) ;; Prepare the argument pointer vector. (loop for i from 0 below argc do (setf (cffi:mem-aref objc-arg-ptrs :pointer i) (cffi:inc-pointer objc-arg-buffer (* i +pessimistic-allocation-size+)))) ;; Prepare the argument typestring vector. Note that we don't ;; pass the first two strings, as they are always the same. (loop for i from 0 for arg-typestring in (cddr arg-typestrings) do (setf (mem-aref objc-arg-typestrings :string i) (allocate-string-and-register arg-typestring))) (macrolet ((argref (type num) `(cffi:mem-ref objc-arg-buffer ,type (* ,num +pessimistic-allocation-size+)))) ;; Prepare the arguments. (setf (argref :pointer 0) (if (pointerp receiver) receiver (pointer-to receiver))) (setf (argref :pointer 1) (if (pointerp selector) selector (pointer-to selector))) (loop for i from 2 for arg in args for arg-type in (cddr arg-types) ;skip the first two arguments for arg-c-type in (cddr arg-c-types) ;likewise do (case (typespec-primary-type arg-type) ((:pointer) (setf (argref :pointer i) arg)) ((objective-c-class exception) (setf (argref :pointer i) (pointer-to arg))) ((selector) (setf (argref :pointer i) (pointer-to (selector arg)))) ((:string) (setf (argref :string i) (allocate-string-and-register arg))) ((struct union) ;; This is not very sophisticated, but, at ;; present, we don't care about the internals of ;; structs and unions much. Functions returning ;; structs actually just give us pointers to them, ;; so we just put those pointers back into the ;; functions as arguments. ;; ;; Note that the target type is a struct/union, ;; not a pointer. This means that we actually ;; have to pass a struct/union as an argument. We ;; therefore ignore the memory space reserved for ;; argument cells in the argument buffer and ;; simply set the argument pointer directly. (setf (cffi:mem-aref objc-arg-ptrs :pointer i) arg)) ((array) (error "Method ~A of object ~A tried to accept an array ~ as argument #~D. It must be mistaken." selector receiver i)) ((id) ;; This case is actually interesting. We can do a ;; lot of automatic conversion between different ;; kinds of stuff. The conversion rules are ;; somewhat arbitrary, but in the absence of more ;; detailed method signature type information, ;; it's the best we can do. (setf (argref arg-c-type i) (pointer-to (coerce-object arg :id)))) (t (setf (argref arg-c-type i) (case arg ;; Do the right thing for booleans. ;; ;; Note that Objective-C method ;; invocations do not understand ;; generalised booleans. Among other ;; things, this means that passing 0 for ;; a boolean is the same as passing NIL, ;; not the same as passing T. ((nil) 0) ((t) 1) (otherwise arg))))))) (let* ((objc-struct-return-value-cell (if (member (typespec-primary-type return-type) '(struct union array)) ;; Note that sizeof(char) is defined to be 1. That ;; is, sizeof returns a size in units of chars, not ;; in units of bytes. (foreign-alloc :char :count (%objcl-sizeof-type return-typestring)) nil)) (error-cell (%objcl-invoke-with-types (- argc 2) superclass-pointer-for-send-super return-typestring objc-arg-typestrings (or objc-struct-return-value-cell objc-return-value-cell) objc-arg-ptrs))) (unless (cffi:null-pointer-p error-cell) (error (make-condition 'exception :pointer error-cell) #+(or) (intern-pointer-wrapper 'exception :pointer error-cell))) (when (eq (typespec-primary-type return-type) 'array) (error "Method ~A of object ~A tried to return an array. ~ It must be mistaken." selector receiver)) (convert-from-foreign-value (or objc-struct-return-value-cell objc-return-value-cell) return-type (or *skip-retaining* (constructor-name-p (selector-name selector))) (returned-char-is-bool-p receiver selector))))))) ;;; (@* "Helper functions") (defun constructor-name-p (method-name) (flet ((method-name-starts-with (prefix) (let ((mismatch (mismatch method-name prefix))) (or (not mismatch) (>= mismatch (length prefix)))))) (or (method-name-starts-with "alloc") (method-name-starts-with "new"))))