[wx-dev] Obj-C "class unifiying"?
David Elliott
dfe at cox.net
Sun May 27 10:50:34 PDT 2007
Hi Robert,
On May 27, 2007, at 4:35 AM, Robert Roebling wrote:
>
> What is "class unification"?
>
> Robert
>
> Modified Files:
> NSButton.mm NSControl.mm NSView.mm app.mm control.mm
> radiobox.mm toplevel.mm window.mm
> Log Message:
> Allow many Objective-C classes to be uniquified at runtime.
Not unification, uniquification. That is, to make the class names
unique at runtime.
Imagine a situation where wxWidgets is built once as a static
library. Now imagine that you are developing something a bundle
(plugin) for a Cocoa app. Let's take the stock Mail.app as an
example. Now, suppose you need to build two different bundles to do
two different things. One bundle implements some new message
transfer protocol (say like the HTTPMail plugin). Another bundle
implements some user interface enhancement (say like the widescreen
mail plugin).
One can keep the two wxWidgets copies contained within the bundle by
telling the linker not to export any symbols from the library when
building the Mach-O bundles. This will ensure that each plugin has
its own self-contained copy of wxWidgets. Unfortunately, when
Objective-C classes are defined there is a big gotcha with that. You
see, the Objective-C runtime keeps track of all classes by name. If
it encounters a class with the same name as one already loaded it
will simply ignore it.
Thus what happens is the first bundle has its WXNSView class loaded.
Objective-C reads through the Mach-O bundle (the __OBJC, __class
section) and maps the class name (using the objc_class name data
member) to the class structure (the objc_class itself). From then
on, the name alone can be used to get a pointer to the objc_class
structure which is what is needed to send class messages like alloc.
Sometime later the program creates a WXNSView (by doing the normal
[WXNSView alloc]. The actual assembler for this is to create a class
reference in the __OBJC, __cls_refs section then to use that
reference as the message receiver (the first implicit self argument
of any Objective-C message send) and @selector(alloc) as the selector
(the second implicit cmd argument of any Objective-C message send).
Upon compilation, the class reference is a plain C string literally
"WXNSView". Upon bundle load the Objective-C runtime looks through
the bundle's __OBJC, __cls_refs section and replaces the strings with
pointers to objc_class which works because const char* is the same
size (4 or 8 bytes) as struct objc_class*. It does this using its
internal class hash which maps class names to class structures.
Now the second bundle is loaded. Objective-C reads through the
__OBJC, __class section but finds that it already has these classes.
It thus ignores them entirely. Now later on when the second bundle
creates an NSView the same process occurs as before except the
returned objc_class pointer is a pointer to the originally loaded class.
What the uniquifying code does is to uniquify the name at runtime.
Unfortunately, this is somewhat of a misrepresentation of all that it
does because the primary thing is to not use __OBJC, __cls_refs when
sending a class message but to instead use a pointer to the compiler-
generated objc_class structure. Thus why you now see
[WX_GET_OBJC_CLASS(WXNSView) alloc]. What happens here is that this
causes the compiler to generate code to get its own generated
objc_class directly. This bypasses the class hash entirely and
corrects the problem.
You may notice upon looking at the code that it does just a bit more
than that. You see, when the Objective-C runtime does not load a
class (because it has already loaded one with the same name) it does
not fix up the super_class or isa.isa or isa.super_class pointers.
Those are needed for messaging to work. The wx code can't be certain
whether the runtime did or did not fix up the class structure so it
simply always does it upon first uses (hence the function static
variable).
Do note that this hackery is more compiler-specific than anything.
The only alternative to this is to create the class structures
manually and fill in the method lists with pointers to C functions.
That is a possible way to do it but then consider the case of [super
doSomething]. The super in that statement is actually a language
keyword not a function parameter (self is an implicit parameter like
this is in C++). The super keyword can only ever appear in a
method's implementation and the compiler implements it by using the
super_class data member of the compiler generated objc_class for the
class that the method is being implemented in. So within
@implementation WXNSView, the super keyword means to use the second
pointer of L_OBJC_CLASS_WXNSView. That's why the fixup of
super_class and so on occurs not on a copy of the objc_class
structure but directly on the compiler-generated structure.
This superclass pointer is then used with the objc_msgSendSuper
function instead of the objc_msgSend function. Like objc_msgSend, it
takes a self (the instance) and a cmd (the selector) along with the
varargs parameters. The difference is that instead of using self-
>isa to look up the instance methods it instead takes the objc_class
pointer as a parameter.
There is no Objective-C syntax besides super to cause the compiler to
generate calls to objc_msgSendSuper so if we wanted to implement
Objective-C methods as plain C methods we'd have to use
objc_msgSendSuper. That is why I chose to let the compiler do most
of the work and then fix up the structures rather than going all out
and generating the objc_class structures purely at runtime.
So, there you have it. That's what the code does. Now, can I ask
you a question? Why do you ask?
-Dave
More information about the wx-dev
mailing list