[Openmcl-devel] Using Cocoa with MCL

Randall Beer beer at eecs.cwru.edu
Thu May 15 12:38:17 PDT 2003

>>>> least in broad terms.  Then we can decide what the right 
>>>> intermediate
>>>> layers of abstraction might be.  Is the goal (1) Lisp sugar for 
>>>> ObjC,
>>>> (2) CLOSified Cocoa, (3) an MCL-like CLOS system, (4) something like
>>>> CLIM (or (5), something I haven't thought of)?
> I vote against (3), for reasons that others have mentioned.  I think
> that it's really hard to design (and implement!) something like this
> and harder still to do so in a way that keeps up with changes in the
> underlying window system.

I agree. I just mentioned it for completeness, and because it would 
abstract over Cocoa interfacing completely and potentially be portable 
across platforms.

> I want to try to keep this reply high-level and abstract, but can't
> resist the temptation to sink into the gutter (or at least some lower-
> level details) for a moment.  I don't know if anyone's ever looked at
> the function OBCJ-OBJECT-P (in "ccl:examples;apple-objc.lisp"); it
> does a very, very good job of heuristically determining whether or not
> a given MACPTR is an ObjC object (and if so, whether it's an instance,
> class, or metaclass object and what its class is.)  One might be able
> to artificially construct a MACPTR that passed these heuristic tests,
> but such a MACPTR would have to look so much like an ObjC object that
> the ObjC runtime would be fooled as well; for the sake of argument,
> anything that OBJC-OBJECT-P says is an ObjC object pretty much -is-
> an ObjC object.  (If anyone's skeptical about this, note that it's
> also possible to make OBJC-OBJECT-P more paranoid than it currently
> is.  If we don't want to make this (as) heuristic, we can also modify
> the FFI to -really- distinguish between :ADDRESS (arbitrary pointer)
> and :ID (pointer to ObjC object, or - perhaps more generally - "pointer
> to runtime-typed foreign object", which might also provide a way to
> deal with CFObjects.)
> To crawl back out of the gutter and try to address high-level questions
> again: if we had the ability to treat ObjC objects as CLOS objects,
> would that be a better way of integrating CLOS and ObjC than approaches
> that involve STANDARD-OBJECTs that encapsulate MACPTRs to foreign 
> objects ?

Yes, I have looked at OBJC-OBJECT-P and I've also been giving some 
thought to essentially making ObjC MACPTRs act like a CLOS class.  As 
you point out, there is much to recommend this idea.  I guess that my 
biggest hesitations with this approach are mostly the things you've 
mentioned: (1) I want to be able to redefine my *own* classes and 
methods freely; (2) I want to be able to store any Lisp object in my 
own classes; (3)  I don't want to have to keep manually converting back 
and forth between Lisp and ObjC data; (4) I don't want to program ObjC 
in Lisp. So, let me push the parallel approach just a bit more below, 
if you don't mind.

>   - I think that schemes that people may have considered (where
>     state - instance variables/slots - is maintained in parallel) are
>     impractical.  Such schemes might depend migrating "dirty" objects
>     to the other universe whenever the ObjC/Lisp boundary is crossed.
>     If multiple threads are on different sides of that boundary,
>     concurrently modifying unrelated instance variables of (different
>     representations of) the "same" object, it's not clear how or when
>     these modifications would ever be accurately synchonized.

I was never proposing keeping parallel Lisp/ObjC copies of all instance 
variables. Sorry if this wasn't clear.

>   - Perhaps it's merely a lack of vision on my part, but I'm concerned
>     about the viability of schemes where CLOS starts doing more of the
>     heavy lifting.  There's some handwaving involved here, but it -may-
>     be possible to use the AHMOP to keep CLOS constrained at this 
> level:
>     there's no multiple inheritence where ObjC objects are involved,
>     there's only a certain kind of method combination (involving an
>     ObjC message send), etc.

No matter how Cocoa is represented in Lisp, it will certainly have some 
restrictions relative to CLOS.

To be as clear as possible, let me spell out the parallel approach I 
had in mind, with some sample CLOS code for how it might work.  
Although this code was obviously written manually, the idea would be 
for much of it to be generated automatically.

Suppose we define the following OBJC-OBJECT class to serve as the 
superclass of any CLOS object which is associated with a corresponding 
ObjC object:

(defclass objc-object ()
   ((objc-object :accessor objc-object)))

In order to ensure that the ObjC memory allocation works, we need 
something like:

(defmethod initialize-instance :after ((o objc-object))
     [(objc-object w) "retain"]
     (terminate-when-unreachable o)))

(defmethod terminate ((o objc-object))
     [(objc-object o) "release"]
     (setf (objc-object w ) (%null-ptr))))

Now, for each class built-in to Cocoa, we have to do something like:

(defclass ns-window (objc-object) ())

Below is a simple-minded INITIALIZE-INSTANCE. In order to do this 
properly, we probably need some code that requires *sets* of  initargs 
that match Cocoa initializers (e.g., NS-WINDOW would require 
(:CONTENT-RECT :STYLE-MASK :BACKING :DEFER)  as initargs). It would 
certainly be possible and very convenient to have reasonable defaults 
for these, but I don't see how that can be done automatically.

(defmethod initialize-instance :before ((w ns-window) &rest initargs)
   (declare (ignore initargs))
   (setf (objc-object w) [[(@class "NSWindow") "alloc"] "init"]))

Now, for each built-in Cocoa method for each built-in Cocoa class, we 
need to define a CLOS wrapper method:

(defmethod make-key-and-order-front ((w ns-window))
   [(objc-object w) "makeKeyAndOrderFront:" :address (%null-ptr)])

(defmethod set-title ((w ns-window) (title string))
     [(objc-object w) "setTitle:" :address (%make-nsstring title)]

The above code should be sufficient for the following to work:

(setq w (make-instance 'ns-window))
(make-key-and-order-front w)
(set-title w "My Window")

Note that I can also easily define my own new methods:

(defmethod window-fu ((w ns-window)) ...)

A key point here is that this is a *Lisp* extension and OBJC DOESN'T 
NEED TO KNOW ANYTHING ABOUT IT because it can only be invoked from Lisp.

Likewise, we can easily define a subclass of NS-WINDOW whose slots can 
contain any Lisp objects we want.

(defclass my-window (ns-window)
   (slot1 slot2))

Note that we need to enforce the restriction that any subclass of 
OBJC-OBJECT can only inherit OBJC-OBJECT along one path.  Thus, 
multiple inheritance is still allowed, just not involving multiple 

Note also that if I make an instance of MY-WINDOW, the actual OBJC 
object created will be an instance of "NSWindow", so once again, OBJC 
instance of MY-WINDOW gets passed down to ObjC, it will treat it as an 
instance of "NSWindow" because it is.

Finally, suppose that I want to provide my own "drawRect:" method:

(defmethod draw-rect :before ((w my-window) (r ns-rect)) ...)

I think that this is the trickiest case.  Whenever we define a method 
with a specializer matching a subclass of OBJC-CLASS and with the 
method selector matching a built-in Cocoa method, we need to do 
something like:

1) Define a primary method for that generic function that invokes the 
"original" ObjC method
2) Replace that ObjC method with one that invokes the Lisp generic 
function (once only)

Thus, whenever ObjC asks this object to "drawRect:", our Lisp method 
will get invoked and then eventually the "default" ObjC method (which 
in this case doesn't do anything). A major issue here is how the 
"patched" ObjC method can invoke the corresponding generic function on 
the corresponding *Lisp* object.

Whew!  Anyway, that's the idea.  It's probably full of holes, so please 
let me know what I've missed.


Openmcl-devel mailing list
Openmcl-devel at clozure.com

More information about the Openmcl-devel mailing list