; 1. Compile obj-support.m with ; gcc -g -framework Foundation -framework Cocoa -dynamiclib -o objc-support.dylib -fvisibility=default objc-support.m ; 2. Run ; mzscheme -t objc.scm #lang scheme (provide (all-defined-out)) (require scheme/foreign mzlib/etc (only-in '#%foreign ffi-call ffi-obj)) (unsafe!) (display "Loading objc-support.dylib...\n") (flush-output) (define lib (parameterize ([current-directory (this-expression-source-directory)]) (ffi-lib "objc-support.dylib"))) (cond [lib (display "objc-support.dylib successfully loaded.\n") (flush-output)] [else (display "FAILED: Unable to load objc-support.dylib \n") (flush-output) (exit)]) ; Define Scheme structs to hold values returned from the ObjC-runtime (define-struct objc:instance (ptr)) (define-struct objc:class (ptr)) (define-struct objc:selector (ptr)) (define-struct objc:signature (ptr)) (define _instance (make-ctype _pointer objc:instance-ptr make-objc:instance)) (define _class (make-ctype _pointer objc:class-ptr make-objc:class)) (define _selector (make-ctype _pointer objc:selector-ptr make-objc:selector)) (define _signature (make-ctype _pointer objc:signature-ptr make-objc:signature)) (define _instance_or_class (make-ctype _pointer (lambda (x) (cond [(objc:class? x) (objc:class-ptr x)] [(objc:instance? x) (objc:instance-ptr x)] [else (error '_instance_or_class "got: ~a" x)])) error)) (define _something (make-ctype _pointer (lambda (x) (cond [(objc:class? x) (objc:class-ptr x)] [(objc:instance? x) (objc:instance-ptr x)] [(objc:selector? x) (objc:selector-ptr x)] [(objc:signature? x) (objc:signature-ptr x)] [else (error '_somehing "got: ~a" x)])) error)) ; Get the support functions in objc-support.m (define objc-description (get-ffi-obj "objc_description" lib (_fun _instance_or_class -> _string))) (define string->class (get-ffi-obj "string_to_class" lib (_fun _string -> _class))) (define string->selector (get-ffi-obj "string_to_selector" lib (_fun _string -> _selector))) (define selector-chars (get-ffi-obj "selector_chars" lib (_fun _selector -> _string))) (define raw:selector->signature (get-ffi-obj "selector_to_signature" lib (_fun _instance_or_class _selector _bool -> _signature))) (define method-return-type (get-ffi-obj "method_return_type" lib (_fun _signature -> _string))) (define method-argument-count (get-ffi-obj "method_argument_count" lib (_fun _signature -> _int))) (define method-argument-type (get-ffi-obj "method_argument_type" lib (_fun _signature _int -> _string))) (define make-nsstring (get-ffi-obj "make_nsstring" lib (_fun _string -> _instance))) (define is-nsstring? (get-ffi-obj "is_nsstring" lib (_fun _instance -> _bool))) (define objc-release (get-ffi-obj "objc_release" lib (_fun _something -> _void))) ;; Cocoa (define objc-application-main (get-ffi-obj "objc_NSApplicationMain" lib (_fun -> _int))) ;; Make signatures easier to work with (define (selector->signature ic s) (raw:selector->signature ic s (objc:class? ic))) (define (method-signature o sel) (let ([sig (selector->signature o sel)]) (and sig (let ([n (method-argument-count sig)]) (begin0 (if (<= n 2) (list (method-return-type sig)) (cons (method-return-type sig) (map (lambda (j) (method-argument-type sig (+ j 2))) (build-list (- n 2) values)))) (objc-release sig)))))) ; for debugging... (define (print-objc o) (let ([d (objc-description o)]) (display (cond [(and (objc:instance? o) (is-nsstring? o)) d] [(objc:instance? o) (format "#" d)] [(objc:class? o) (format "#" d)] [(objc:selector? o) (format "#" d)] [(objc:signature? o) (format "#" d)] [else (error 'objc:print "huh")])))) ; Initialize the autorelase allocation pool ; (otherwise we will get "leaking" errors) (define objc-allocate-autorelease-pool (get-ffi-obj "objc_allocate_autorelease_pool" lib (_fun -> _void))) (objc-allocate-autorelease-pool) ; Before calling a method one must use method-return-type to find ; out the return type of the method. The return type is represented ; as a string. The function return-type below converts the string representation ; into the ctypes used by PLT's foreign function interface. ; (For now only simple types, instances, classes, and selectors are supported) (define (return-type ot) ; Table 12-1 page 123 in the Objective C v2 reference ; TODO: #\q = long long ; [array type] ; {name=type ...} ; structure ; (name=type,...) ; union ; bnum ; A bit field of num bits ; ^type ; A pointer to type ; ? ; An unknown type (e.g. function pointers) (let loop ([i 0]) (case (string-ref ot i) [(#\c) _int] ; signed char [(#\v) _void] [(#\s #\i #\l #\C #\I #\S #\L) _int] [(#\f) _float] [(#\d) _double] [(#\*) _string] [(#\@) _instance] [(#\#) _class] [(#\:) _selector] [(#\^) _pointer] ; TODO ? ; these encode const, in, inout, out, bycopy, byref, and oneway respectively [(#\r #\n #\N #\o #\O #\R #\V) (loop (+ i 1))] [else (error 'return-type "got: ~a" ot)]))) ; We need the same conversion for function arguments... (define in-type return-type) ; The heart: ; obj-send calls the method of obj given by the selector. ; Since the arguments and return type depend of obj-send ; depend on the method in question, ffi-call is used ; to construct new Scheme representations of objc_msgSend ; each time it is called (TODO: These values could ; be cached to improve performance) (define obj-send ;; NOTE: The value constructed by ffi-call could be stored in a cache (let ([msg-send (ffi-obj #"objc_msgSend" lib)]) (lambda (obj sel . args) (let* ([sel (if (symbol? sel) (string->selector (symbol->string sel)) sel)] [sig (selector->signature obj sel)] [out-type (return-type (method-return-type sig))]) (apply (ffi-call msg-send (append (list _something _selector) (map in-type (cdr (method-signature obj sel)))) out-type) obj sel args))))) ;;; TEST ;; TEST1 ;(display (obj-send (make-nsstring "Fooz") 'length)) ;(newline) ;; TEST2 ;(display (method-signature (make-nsstring "Fooz") ; (string->selector "characterAtIndex:"))) ;(newline) ;; TEST3 ;(display (integer->char ; (obj-send (make-nsstring "abcde") (string->selector "characterAtIndex:") 2))) ;(newline) ;; THE PROBLEM ; The return value of objc-application-main returns 0, ; which indicate the bundle was not loaded. Why, oh why? (display "Bundle loaded?: ") (display (objc-application-main)) (newline) (display "Press enter to stop.\n") (flush-output) (read-line) (exit)