2010

2009

2007

2006

2005

Nasty hacking with 64-bit Objective-C

Entry published sep 10 2009

If you’re loading code dynamically and using an unsupported API under Cocoa 64bit, you’ll be greeted with this unpleasant surprise:

9/7/09 2:34:06 PM   Mail[8641]  MailTest.mailbundle failed to load. The error was:
Error Domain=NSCocoaErrorDomain Code=3588 UserInfo=0x100563840 "The bundle “MailTest” couldn’t beloaded."

After many hours of slamming my head against the desk, and help from others (namely Erik Hinterbichler, I’ve figured out how to get around this error. Why would you want to do this in the first place? Well, it’s useful for creating addons to closed source products like Mail.app and Safari. The usual procedure involves dumping the headers for the app using class-dump, determining how to swizzle your code in, and finally figuring out how to load your code into the app.

With the switch to 64bit, Objective-C has a new dynamic loader that pays attention to symbol visibility. The 32bit loader didn’t, and would allow your code to bind to any symbol in the app. With the 64bit loader, only symbols that are explicitly exported are available for dynamic binding. As a result, nearly all the symbols in an app like Mail.app aren’t exported for binding. If you do try to bind, you’ll see that error message above.

So what’s the trick? Never reference any of the symbols directly! Instead you’re going to us NSClassFromString along with the Objective-C runtime methods available from the header <objc/runtime.h>

Here’s how I now set the super class for a Mail.app plugin:

+ (void) initialize {
    class_setSuperclass([self class], NSClassFromString(@"MVMailBundle"));
    [MattsMailBundle registerBundle];
}

Notice, this is done in the initialize class method, and no Mail.app symbols (in this case MVMailBundle) are referenced directly.

Hope that helps, happy hacking!

← Previous: 13 more things that don't make sense  //  Next: Announcing Rocketbox & Central Atomics

comments

Scott Little, 11 months, 2 weeks ago:

This is a great help and has allowed me to get a bundle loaded, but only in a test case. My existing plugin, still seems to get rejected with this error.

One thing you say is that one should “Never reference any of the symbols directly!” Does this mean anywhere in the plugin? My test case references the NSPreferencesModule object, but the plugin still loads without the error message.

If I have to do this for every reference, this is going to be a long update!

Thanks for any input you have. scott

Matt Ronge, 11 months, 2 weeks ago:

This has to be done only for symbols that have their visibility set as private. In this case NSPreferencesModule has public visibility. You can check this by running nm /Applications/Mail.app/Contents/MacOS/Mail and a list of public symbols will be shown.

Scott Little, 11 months, 2 weeks ago:

Thanks Matt,

The nm command will definately help. It’d be nice if I could do the inverse and run a command against my plugin to tell me which objects are not public in Mail, but oh well….

Am I correct in assuming that only cals that invoke the object itself are a problem? I.E. I can still do this:

MailApp *theApp = [NSClassFromString(@”MailApp”) sharedApplication];

the information for the compiler won’t give me this issue.

Also this means that creating a category on a private object becomes way more difficult as well, doesn’t it?

Thanks, scott

Matt Ronge, 11 months, 2 weeks ago:

Yes, creating a category on a private object does become more difficult. You’ll have to use the obj-c runtime methods to add your method instead.

I’m not sure about the MailApp code you have, I replaced mine with id instead of the class name. However it’s worth a try.