A Cocoa Bridge for OpenMCL Randall D. Beer beer@eecs.cwru.edu http://vorlon.cwru.edu/~beer INTRODUCTION The purpose of CocoaBridge is to make Cocoa as easy as possible to use from OpenMCL, in order to support GUI application and development environment activities. It builds on the capabilities provided in the APPLE-OBJC example. The eventual goal is complete integration of Cocoa into CLOS. The current release provides Lisp-like syntax and naming conventions for ObjC object creation and message sending, with automatic type processing and compile-time checking of message sends. It also provides some convenience facilities for working with Cocoa. A small sample Cocoa program can be invoked by evaluating (REQUIRE 'TINY) and then (CCL::TINY-SETUP). This program provides a simple example of using several of the bridge's capabilities BASICS The main things you need to know are: 1) You create and initialize ObjC objects using MAKE-OBJC-INSTANCE. This should be replaced by MAKE-INSTANCE as CLOS integration improves Example: [[NSNumber alloc] initWithFloat: 2.7] in ObjC becomes (MAKE-OBJC-INSTANCE 'NS-NUMBER :INIT-WITH-FLOAT 2.7) in Lisp Note that class names and init keywords are translated from ObjC to Lisp in pretty much the obvious way 2) You send messages to ObjC objects using SEND Examples: [w alphaValue] becomes (SEND W 'ALPHA-VALUE) [w setAlphaValue: 0.5] becomes (SEND W :SET-ALPHA-VALUE 0.5) [v mouse: p inRect: r] becomes (SEND V :MOUSE P :IN-RECT R) Note that message keywords are translated to Lisp in pretty much the obvious way. From within a method, you can also use SEND-SUPER. 3) The @CLASS macro from APPLE-OBJC is currently used to refer to named ObjC classes, which can also be sent messages via SEND. This should be replaced by FIND-CLASS as CLOS integration improves. Example: [NSColor whiteColor] becomes (SEND (@CLASS NS-COLOR) 'WHITE-COLOR) 4) New ObjC classes and methods are currently defined using DEF-OBJC-CLASS and DEFINE-OBJC-METHOD from APPLE-OBJC. This should be replaced by DEFCLASS and DEFMETHOD as CLOS integration improves. NAME TRANSLATION There are a standard set of naming conventions for Cocoa classes, messages, etc. As long as these are followed, the bridge is fairly good at automaticallly translating between ObjC and Lisp names. Examples: "NSURLHandleClient" <==> NS-URL-HANDLE-CLIENT "NSOpenGLView" <==> NS-OPENGL-VIEW "nextEventMatchingMask:untilDate:inMode:dequeue:" <==> (:NEXT-EVENT-MATCHING-MASK :UNTIL-DATE :IN-MODE :DEQUEUE) To see how a given ObjC or Lisp name will be translated by the bridge, you can use the following functions: OBJC-TO-LISP-CLASSNAME string LISP-TO-OBJC-CLASSNAME symbol OBJC-TO-LISP-MESSAGE string LISP-TO-OBJC-MESSAGE keyword-list OBJC-TO-LISP-INIT string LISP-TO-OBJC-INIT keyword-list Of course, there will always be exceptions to any naming convention. Please let me know if you come across any name translation problems that seem to be bugs. Otherwise, the bridge provides two ways of dealing with exceptions: 1) You can pass a string as the class name of MAKE-OBJC-INSTANCE and as the message to SEND. These strings will be directly interpreted as ObjC names, with no translation. This is useful for a one-time exception. Examples: (MAKE-OBJC-INSTANCE "WiErDclass") (SEND o "WiErDmEsSaGe:WithARG:" x y) 2) You can define a special translation rule for your exception. This is useful for an exceptional name that you need to use throughout your code. Examples: (DEFINE-CLASSNAME-TRANSLATION "WiErDclass" WEIRD-CLASS) (DEFINE-MESSAGE-TRANSLATION "WiErDmEsSaGe:WithARG:" (:WEIRD-MESSAGE :WITH-ARG)) (DEFINE-INIT-TRANSLATION "WiErDiNiT:WITHOPTION:" (:WEIRD-INIT :OPTION) The normal rule in ObjC names is that each word begins with a capital letter (except possibly the first). Using this rule literally, "NSWindow" would be translated as N-S-WINDOW, which seems wrong. "NS" is a special word in ObjC that should not be broken at each capital letter. Likewise "URL", "PDF", "OpenGL", etc. Most common special words used in Cocoa are already defined in the bridge, but you can define new ones as follows: (DEFINE-SPECIAL-OBJC-WORD "QuickDraw") Note that message keywords in a SEND such as (SEND V :MOUSE P :IN-RECT R) may look like Lisp keyword args, but they really aren't. All keywords must be present and the order is significant. Neither (:IN-RECT :MOUSE) nor (:MOUSE) translate to "mouse:inRect:" Note that an "init" prefix is optional in the initializer keywords, so (MAKE-OBJC-INSTANCE 'NS-NUMBER :INIT-WITH-FLOAT 2.7) can also be expressed as (MAKE-OBJC-INSTANCE 'NS-NUMBER :WITH-FLOAT 2.7) STRETS Some Cocoa methods return small structures (such as those used to represent points, rects, sizes and ranges). Although this is normally hidden by the ObjC compiler, such messages are sent in a special way, with the storage for the STructure RETurn (STRET) passed as an extra argument. This STRET and special SEND must normally be made explicit in Lisp. Thus NSRect r = [v1 bounds]; [v2 setBounds r]; in ObjC becomes (RLET ((R :ect)) (SEND/STRET R V1 'BOUNDS) (SEND V2 :SET-BOUNDS R)) In order to make STRETs easier to use, the bridge provides two conveniences: 1) The SLET and SLET* macros may be used to define local variables that are initialized to STRETs using a normal SEND syntax. Thus, the following is equivalent to the above RLET: (SLET ((R (SEND V 'BOUNDS))) (SEND V2 :SET-BOUNDS R)) 2) The arguments to a SEND are evaluated inside an implicit SLET, so instead of the above, one could in fact just write: (SEND V1 :SET-BOUNDS (SEND V2 'BOUNDS)) There are also several psuedo-functions provided for convenience by the ObjC compiler. The following are currently supported by the bridge: NS-MAKE-POINT, NS-MAKE-RANGE, NS-MAKE-RECT, and NS-MAKE-SIZE. These can be used within a SLET initform or within a message send: (SLET ((P (NS-MAKE-POINT 100.0 200.0))) (SEND W :SET-FRAME-ORIGIN P)) or (SEND W :SET-ORIGIN (NS-MAKE-POINT 100.0 200.0)) However, since these aren't real functions, a call like the following won't work: (SETQ P (NS-MAKE-POINT 100.0 200.0)) The following convenience macros are also provided: NS-MAX-RANGE, NS-MIN-X, NS-MIN-Y, NS-MAX-X, NS-MAX-Y, NS-MID-X, NS-MID-Y, NS-HEIGHT, and NS-WIDTH. Note that there is also a SEND-SUPER/STRET for use within methods. OPTIMIZATION The bridge works fairly hard to optimize message sends under two conditions. In both of these cases, a message send should be nearly as efficient as in ObjC: 1) When both the message and the receiver's class are known at compile-time. In general, the only way the receiver's class is known is if you declare it, which you can do either via a DECLARE or THE form. For example: (SEND (THE NS-WINDOW W) 'CENTER) Note that there is no way in ObjC to name the class of a class. Thus the bridge provides a @METACLASS declaration. The type of an instance of "NSColor" is NS-COLOR. The type of the *class* "NSColor" is (@METACLASS NS-COLOR): (LET ((C (@CLASS NS-COLOR))) (DECLARE ((@METACLASS NS-COLOR) C)) (SEND C 'WHITE-COLOR)) 2) When only the message is known at compile-time, but its type signature is unique. Of the over 6000 messages currently provided by Cocoa, only about 50 of them have nonunique type signatures. An example of a message whose type signature is not unique is SET. It returns VOID for NSColor, but ID for NSSet. In order to optimize sends of messages with nonunique type signatures, the class of the receiver must be declared at compile-time. If the type signature is nonunique or the message is unknown at compile-time, then a slower runtime call must be used. The ability of the bridge to optimize most constant message sends even when the receiver's class is unknown crucially depends on a type signature table that the bridge maintains. When the bridge is first loaded, it initializes this table by scanning all methods of all ObjC classes defined in the environment. If new methods are later defined, this table must be updated. After a major change (such as loading a new framework with many classes), you should evaluate (UPDATE-TYPE-SIGNATURES) to rebuild the type signature table. Because SEND, SEND-SUPER, SEND/STRET and SEND-SUPER/STRET are macros, they cannot be FUNCALLed, APPLYed or passed as functional arguments. The functions %SEND and %SEND/STRET are provided for this purpose. There are also %SEND-SUPER and %SEND-SUPER/STRET functions for use within methods. However, these functions should be used only when necessary since they perform general (nonoptimized) message sends. VARIABLE ARITY MESSAGES There are a few messages in Cocoa that take variable numbers of arguments. Perhaps the most common examples involve formatted strings: [NSClass stringWithFormat: "%f %f" x y] In the bridge, this would be written as follows: (SEND (@CLASS NS-STRING) :STRING-WITH-FORMAT #@"%f %f" (:DOUBLE-FLOAT X :DOUBLE-FLOAT Y)) Note that the types of the variable arguments must be given, since the compiler has no way of knowing these types in general. Variable arity messages can also be sent with the %SEND function: (%SEND (@CLASS NS-STRING) :STRING-WITH-FORMAT #@"%f %f" (LIST :DOUBLE-FLOAT X :DOUBLE-FLOAT Y)) Because the ObjC runtime system does not provide any information on which messages are variable arity, they must be explicitly defined. The standard variable arity messages in Cocoa are predefined. If you need to define a new variable arity message, use (DEFINE-VARIABLE-ARITY-MESSAGE "myVariableArityMessage:") TYPE COERCION OpenMCL's FFI handles many common conversions between Lisp and foreign data, such as unboxing floating-point args and boxing floating-point results. The bridge adds a few more automatic conversions: 1) NIL is equivalent to (%NULL-PTR) for any message argument that requires a pointer 2) T/NIL are equivalent to #$YES/#$NO for any boolean argument 3) A #$YES/#$NO returned by any method that returns BOOL will be automatically converted to T/NIL To make this last conversion work, the bridge has to engage in a bit of hackery. The bridge uses ObjC run-time type info. Unfortunately, BOOL is typed as CHAR by ObjC. Thus, a method that returns CHAR might actually return only BOOL, or it might return any CHAR. The bridge currently assumes that any method that returns CHAR actually returns BOOL. But it provides a facility for defining exceptions to this assumption: (DEFINE-RETURNS-BOOLEAN-EXCEPTION "charValue"). Eventually, the best way to handle issues like this is probably to get our method type info directly from the header files rather than using ObjC's runtime type system. Note that no automatic conversion is currently performed between Lisp strings and NSStrings. However, APPLE-OBJ provides a convenient syntax for creating constant NSStrings: (SEND W :SET-TITLE #@"My Window"), as well as facilities for converting between Lisp strings and NSStrings. Note that #@"Hello" is a full ObjC object, so messages can be sent to it: (SEND #@"Hello" 'LENGTH)