Checking for Weak Linked Frameworks

Saturday, 2009-03-07; 22:15:46

Let’s say you have an Xcode project, and you’d like to have support for a non-essential framework. Typically, when you add a framework to a project, you go to [Project —> Add to Project…] and select the framework you’d like to add. This is linking. However, this method causes your program to simply not launch if that framework isn’t present. This is bad.

So to let your program still run even when the framework in question isn’t present, you weakly link the framework instead. Apple has some good docs about weak linking that explain the details.

Suffice it to say, if you’re weak linking a framework, uncheck the target membership box in the project sidebar for the framework you want to weak link, then get info on your target and add “-weak_framework framework_name”, where “framework_name” would be “SDL_mixer” if you want to weak link SDL_mixer.framework.

Apple’s documentation also helpfully includes code that you can use to check whether or not the framework in question is installed or not. Instead of specifically checking for the framework, you simply check if the pointer to the function you’re about to call from that framework actually exists. Here’s the code from Apple’s docs:

So you check if MyWeakLinkedFunction exists, and then execute it if it does. Now take a look at a QuickTime movie of me debugging an example of such code. No, I’m not debugging with optimization on, and ignore all the other weird stuff about this code like the duplicated lines. Just focus on which lines the debugger goes through.

Mix_OpenAudio is a function from SDL_mixer, correctly weakly linked in my project. Notice that the debugger simply skips over the first Mix_OpenAudio if statement, and skips the entire second #ifdef __macosx__ block, even though __macosx__ is clearly defined since the debugger went through a similar #ifdef __macosx__ block at the top.

What’s going on?

The short explanation is that Apple’s suggested code simply doesn’t work.

Here’s what’s happening. When you compile on Leopard, Mix_OpenAudio, or MyWeakLinkedFunction as in the case of Apple’s sample code, always evaluates to true regardless of whether the framework is present or not. So in the movie of me debugging, the line if (Mix_OpenAudio == NULL) always evaluates to false. The compiler simply doesn’t compile those lines of code because of this, which explains why it simply skips over that if statement. The compiler actually generates an “always evaluates to true” warning if you change the line to if (Mix_OpenAudio).

Similarly, the second identical if statement inside the #ifdef __macosx__ block also always evaluates to false, and so the compiler omits that entire block, since no lines of code will ever be executed regardless of the outcome of the if (gameOptions [0].sound.bUseSDLMixer) { line.

Apple’s docs spout BALD-FACED LIES.

A Google search for “compiler weak linking” actually produces the solution as the second search result. Instead of the Apple sample code in the docs (as reproduced above), use this code instead:

UPDATE 2009-03-11: Actually, it looks like that (now striked-out) solution doesn’t work either. This was working fine in a debug build, but when I finally made a release build, the program crashed yet again when the weakly linked framework was not present.

After banging my head against the wall for a long time, I finally e-mailed the Cocoa-Dev mailing list, and Greg Parker from Apple helpfully suggested that the compiler was optimizing my code out. He also included some other code that isn’t optimized out, and I have confirmed that it works under both Debug and Release builds: