The art of linking

Sorry for the bold title, but I’d like to clarify some issue that occurs in latest stage of application building: linking.

Static and dynamic symbol references

When you link your application, the Mac OS X static linker, ld tries to resolve (ie, find) every symbol you reference to in your code. This creates a table which tells the dynamic linker which symbols to bind to at runtime. What happens if you reference a symbol which the static linker cannot find? Build fails. This can happen if you reference symbols defined in loadable bundles or custom frameworks/dynamic library.

A common solution to this is to add the option “-undefined dynamic_lookup” which delegates the job of both finding and resolving references to the dynamic linker (dynamic linking happens at runtime). Although this is an easy solution, it can give many headaches. One problem with this approach is that any mistyped function name won’t result in a compilation error (the same is true¬†misspelled¬†variables and constants declared as extern) but will result in a crash at runtime. As the crash may not happen at launch time you may have an hard time spotting the problem.

Although we’ll see many ways to avoid having to leave symbol lookup to the dynamic compiler, the best way to handle dynamic lookup is to handle it programmatically were needed instead, following the direction found at this ADC article.

Avoiding dynamic symbol references

So here you can find a few quick tips to avoid dynamic symbol lookup where possible.

  • If you are referencing symbol found in a dynamic library or framework, link to it using (respectively) the flags “-l” or “-framework”. If the library/framework isn’t available system-wide make sure that their install path is relative to the application bundle (usually @executable_path/../Frameworks for frameworks). If you only have the framework/library available in binary form, but you are allowed to redistribute it, you can adjust its install path using “install_name_tool”.
  • If you are writing a loadable bundle, and reference symbols defined in the host application, use the “-bundle_loader” option (followed by the path to the host application’s executable) which will cause ld to search for symbols also in the referenced executable.
  • If you are referencing symbols found in a loadable bundle instead, you have to look them up dynamically. In Cocoa you can usually do it through NSBundle. CoreFoundation also provides CFBundle-related functions for this task.

Compatibility and graceful degradation

If some symbols, referenced either statically or dynamically are not found at execution time, the application will crash. We might see this, for example, running an application using Leopard features on Tiger. What if we want to retain Tiger compatibility? We may conditionally reimplement some system provided capabilities or disable some features on the older OS. The Mac OS X linker provides an extremely classy way to handle this kind of situations through correct choice of SDK and Deployment Target.

A common mistake is to use the SDK of the oldest OS we want our application to run on. This isn’t generally a good idea and becomes a problem as soon as we reference symbols defined in newer versions of Mac OS X (ld will fail).

A better idea is to instead use the latest SDK and set the Deployment Target to the oldest version of Mac OS X you want your application to run on. This will cause the static linker to mark references to symbols not defined in system frameworks as of your deployment target of choice (but found in the used SDK) to be marked as “weak references”. Weak references are mostly like regular references, but if binding the symbol fails at runtime, the reference is set to NULL. This gives you a great way to check if a certain symbol is available at runtime and avoids nasty crashes (this does not work for selectors because they are handled by the objective-c runtime, you can check for those using respondsToSelector:).

For your information, weak references are created adding the __attribute__((weak_import) directive to a symbol declaration (usually to externs) but usually don’t need to declare them explicitly (the system frameworks have preprocessor directives to do that automatically based on deployment target).

Comments are closed.