30 June 2009

Loading Frameworks at Runtime

I recently looked at an application that was trying to solve the "install at first launch" problem. The application in question needed to install several third-party frameworks and a daemon at first launch. Then, it needed to launch the daemon and use frameworks itself. The application needed to be relocatable, we couldn't rely on the application being in a certain location, and it couldn't have a separate installer. Cosmetically, we didn't like the look of the double-launch application where an application bounces in the doc, disappears, and then comes back to life.

Is this beginning to sound contrived? It was, indeed, a real situation, due partly to the issues that arise from working with third-party software and a marketing department with strict user experience oversight. At first glance the requirements seemed contradictory but we were able to get things to work using weak linking and runtime loading. Fortunately the frameworks were written in Objective-C with its awesome runtime message dispatching.

Weak Linking

The first problem was getting our application to even launch when the frameworks were not installed. Since the application linked to the frameworks in their installed location, a location dictated by the third-party daemon, it was obvious that they would not exist until after our application installed them. They would be missing on the first launch for every user.

To let our application load even if it couldn't find the libraries we used weak linking. Weak linking tells the application loader to continue if it can't link to libraries that are specified in a binary's load commands. You can see which libraries will be loaded in your application by using the otool -L <executable> command. By default, all of these libraries must be in their specified location or the application will fail launching.

When you specify that you want a library to be weakly linked, the loader will continue loading the application even if it cannot find the required libraries. This is extremely useful for using features in a new operating system while continuing to support an older OS. For example, an application could weakly link to a Leopard only framework, but continue to support Tiger. The application would be responsible for checking at runtime for the existence of symbols in any weakly linked framework before using it.

Xcode 3.0 makes weak linking very easy. Simply go to the "Linked Libraries" list in the "General Tab" of the "Target Info" dialog. On the right-hand-side, click on the pop-up button that says "Required" and change it to "Weak". Piece of cake.

Here is a great article on weak linking from Apple.

However, weak linking only lets our application load if frameworks are missing. It doesn't let us use functionality from frameworks that appear after we install them. To do that, we have to use the dynamic loader, dlopen().

Dynamic Loading

Once our application has launched, it has the chance to install the frameworks and the daemon in the correct locations; authenticating with the user, if necessary. However, if we try to access any of the functionality in the frameworks, the application will crash. Since the frameworks were not around when the application loaded, the loader didn't resolve the symbols in that framework.

To load a framework at runtime, simply issue the dlopen() with a path to the framework you need to load. Apple describes the solution in the Dynamic Library Usage Guidelines.

For example, to load an imaginary framework "Imaginary.framework" from "/Library/Frameworks", you would use:

framework_handle = dlopen("/Library/Frameworks/Imaginary.framework/Imaginary", RTLD_LAZY);

Now, the symbols within the loaded framework are available for use within your application. But wait! not quite.

Loading Symbols

If your application is using the Objective-C runtime for messaging and loading classes, your are finished. You can now create classes and call methods on these classes without modifying the rest of your application. However, if you need to load C functions or static objects (such as string constants), the individual symbols must be loaded too. Continuing our example above, if we had an NSString* constant "IFWhiteRabbit" defined in our framework, we could get access to this constant using the dlsym() function.

NSString** whiteRabbit_handle = (NSString**)dlsym(framework_handle, "IFWhiteRabbit");

With non-Objective-C symbols, each one needs to be loaded separately, including functions. This can cause significant complexity with the code, or alternative paths whether the frameworks were available at runtime, or if they were loaded later. If the frameworks were loaded at runtime, the IFWhiteRabbit constant could be used directly in the code. If the framework was loaded at runtime, a handle would have to be loaded and then dereferenced to get access to the data.

Apple encourages the use of a top-level class that would provide strings or other constants via accessor methods. In this way, no code differences would be required for frameworks that were loaded at runtime or at launch. Also, if at all possible, using the high-level CFBundle or NSBundle can simplify loading code at runtime.

Conclusion

With a combination of weak linking and dynamic loading, we were able to provide a single application for the user that installed necessary third-party software at runtime and could be located at any location on the disk. It also avoided the double-bouncing doc icon. Everyone was happy. Even marketing.

1 comment:

Unknown said...

Brilliant blog! Thanks a million for posting this