Recently, in my infinite wisdom, I decided I wanted to have a framework that embeds a private framework. Frameworks, are those are uber-cool versions of DLLs complete with multiple versions, public headers, dynamically linked object code, etc for you recent Cocoa converts following along at home.
Before I get started, you may be asking, why ever would I want to do this?
Well, my application is distributed (err, planned to be distributed) with a set of plug-ins that live in the application’s bundle. One of the plug-ins wants to use a framework common to my organization, but not public. I don’t want to develop an installer to put the framework the plug-in needs in the system or user’s library, especially because it’s private to my plug-in’s implementation.
Ok, now that THAT’s out of the way, let’s get back to the subject at hand. When I first started splitting my code up into frameworks I was surprised to learn that the dynamic library at the heart of a framework actually embeds the path it expects to be installed at. Generally speaking having the absolute path of where the framework expects to be installed is not that big a deal, and helps lift OS X out of DLL hell. There are only a handful of standard places you should be putting your framework (such as $HOME/Library/Frameworks, etc).
Since frameworks can contain multiple versions of their code, the DLL hell that .NET helps clean up with the GAC is largely obviated. The main downside to installing your framework in a standard location is well, just that, the installation part.
If your framework is unlikely (or not intended) to be used by more than your one application, a nice alternative to installing it in a standard location is to embed it in your application’s bundle. The standard place for this is in the MyApp.app/Contents/Frameworks directory inside your bundle.
The catch is that you don’t really know where the application is going to be copied by the user (when they say drag/drop it from the .dmg disk image that you distributed it in). The problem with not knowing where it’s going to be copied, is that pesky hard coded path inside of the dynamic library (the one used by the linker to bake references to it into your application).
Fortunately, starting with OS X Tiger, you can use a the string “@executable_path” in the hard coded installation path of your framework. Assuming that you plan to put your private framework in the standard place in your application bundle (the MyApp.app/Contents/Frameworks directory) that path is typically “@executable_path/../Frameworks”. When the dynamic linker attempts to locate the library at runtime, it substitutes the path of the current executable in place of, you guessed it, “@executable_path”. That’s likely to be something like /Applications/MyApp.app/Contents/MacOS.
So far so good. And so far, pretty reasonably documented by Apple.
Now, back to those plug-ins. The plug-ins are themselves frameworks; frameworks that uses other frameworks. Both the standard Cocoa ones, and also the private one-off kind. The symbolic constant “@executable_path” isn’t too much help to us here because we can’t say for sure where the plug-ins are going to live relative to the application. I suppose for my originally stated “plug-ins distributed in the bundle of my application” case I could come up with some really knarly relative path, but what about user installed plug-ins?
There are a few different reasonable places (and I’m sure quite a few unreasonable places) that plug-ins might live on the user’s system. Here again Apple provides nice documentation to describe the details and best practices. So really, “@executable_path” is not quite what we’re looking for.
Thankfully, “@executable_path” has a little less well known friend named “@loader_path”. This friend is exactly the kind of friend we need to implement frameworks with private frameworks. As you may already be able to guess, “@loader_path” is resolved by the dynamic linker at load time to the path of the dynamic library inside the framework bundle (i.e. my plug-in) that is actually doing the loading.
The standard place to put a private framework inside of another framework is “MyCoolPlugin.framework/Versions/Current/Frameworks” (although as per the Apple documentation, your plug-in should pick a unique extension, and not be named with the “.framework” extension). Since the actual dynamic library lives as a direct peer to the “Frameworks” path (rather than under a sub-directory like “MacOS”) the typical installation path for the private framework used by another framework is “@loader_path/Frameworks”.
And there you have it, frameworks with private frameworks!