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.

23 March 2009

Automating the Setup of Xcode Executables

Problem

Xcode does not store Executable settings in the project file. The settings end up in the user.pbxuser. These files do not typically get checked in to version control system, making sharing the setup of these executable impossible. This causes problems if you have environment settings, or command line arguments that need to be called in your executable in a consistent way, across all developer machines.

The situation that motivates this post is the need to set the DYLD_FALLBACK_LIBRARY_PATH in a unit test executable to help it find a third-party dynamic library. Simple command line tools do not get the same benefits of application bundles, which have their dependent frameworks and libraries packaged neatly away. Command-line tools are simply executable code without packaged resources or private frameworks. Because there is no package, when the executable can only link to libraries in known locations such as /usr/lib/ or /System/Library/Frameworks.

At times this can cause problems. When unit testing code, you might create a simple executable that links in required frameworks and runs tests on them and produces code coverage. The problem is that your application will be able to link to a variety of dynamic libraries and frameworks and embed them its bundle. The unit test executables, on the other hand, do not have a bundle. How then, can your executable find these dynamic libraries and frameworks at runtime?

Note: A solution to this particular problem is to copy all of the frameworks and libraries into the $BUILT_PRODUCTS_DIR, this can be done simply with a build phase in your test target, but doesn't serve to demonstrate the automation of other environment variables or command line arguments.

AppleScript to the Rescue

Fortunately for us, Xcode is an extremely scriptable application. Here is a script that modifies the currently building target and sets up some environment variables on the linked executable.

-- Function: setEnvironmentVariable

-- Sets an environment variable in the executable, which is linked to the current target. If the

-- environment variable exists, the value is overwritten. If the environment variable doesn't exist

-- the variable is created and set.

--

-- Args:

--    variableName - name of the environment variable to set

--    variableValue - value of the environment variable being set

on setEnvironmentVariable(variableName, variableValue)

tell application "Xcode"

tell active project document

set executableName to name of executable of active target as string

tell executable executableName

-- Check to see if the fallback path already exists

set hasVariable to false as boolean

repeat with environmentVariable in environment variables

if name of environmentVariable is equal to variableName then

-- Overwrite any value

set value of environmentVariable to variableValue

set active of environmentVariable to yes

set hasVariable to true as boolean

exit repeat

end if

end repeat

-- Since the fallback path doesn't exist yet, create it

if not hasVariable then

make new environment variable with properties ¬

{name:variableName, value:variableValue, active:yes}

end if

end tell -- executable

end tell -- active project document

end tell -- Xcode

end setEnvironmentVariable


tell application "Xcode"

tell active project document

-- First, find the path to the project directory

set projectPath to path as string

-- Create a library path to the third-party and in-house libraries

set dyldLibraryPath to projectPath & "../relative/path/to/third-party/lib" & ":" & ¬

projectPath & "../relative/path/to/in-house/lib"

-- Create a framework path to the third-party and in-house frameworks

set dyldFrameworkPath to projectPath & "../relative/path/to/third-party/frameworks" & ":" & ¬

projectPath & "../relative/path/to/in-house/frameworks"

-- Next, add path to the DYLD fallback paths

my setEnvironmentVariable("DYLD_FALLBACK_FRAMEWORK_PATH", dyldFrameworkPath)

my setEnvironmentVariable("DYLD_FALLBACK_LIBRARY_PATH", dyldLibraryPath)

end tell -- active project document

end tell -- Xcode

Adding the Script to your Target

To add the above script to a target, first, save the script as an applescript. Then add a "Run Script" build phase with the one line

osascript path/to/script.applescript

Obviously replacing the path and file name to that of your own script.

28 January 2009

Awesome Truncation for (nearly) Free!

Sometimes you need to shorten a string to make it fit within certain bounds. using the NSString - (void)drawAtPoint:(NSPoint)aPoint withAttributes:(NSDictionary *)attributes method will draw the string, but won't perform any truncation. The - (void)drawInRect:(NSRect)aRect withAttributes:(NSDictionary *)attributesmethod will truncate, but just clips at the end. Sometimes what we really want is the behavior that window titles show, an ellipsis at the end. Here's an example:

This behavior can be selected using the Inspector in Interface Builder for NSTextFields. But what do we do if we are drawing text in a custom view? It turns out that it is very simple to get automatic truncation, in at the head, in the middle, or at the tail of the string. Here's how:

  1. Add a paragraph style to the attributes dictionary. In this example, we do this in theinit method.
      // Set the paragraph style attribute to truncate the tail.
      NSMutableParagraphStyle* style = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] autorelease];
      // Here we specify the truncation location with a constant.
      [style setLineBreakMode:NSLineBreakByTruncatingTail];
    
      // Make an attribute dictionary for the attributed string
      NSDictionary _textAttributes = [[NSDictionary dictionaryWithObjectsAndKeys:
            [NSFont systemFontOfSize:[NSFont labelFontSize]], NSFontAttributeName,
            style, NSParagraphStyleAttributeName,
            nil] retain];
    
  2. Calculate the rectangle in which you want to draw the string.
  3. Then draw the string using the NSString drawInRect:withAttributes:. Here's an example from our custom view's drawRect: method.
      [_text drawInRect:textRect withAttributes:_textAttributes];
    

There are actually three kinds of truncation that you can specify in this method, NSLineBreakByTruncatingHead NSLineBreakByTruncatingMiddle, and as observed, NSLineBreakByTruncatingTail. It is that easy.