How to Load a Resource from Your Application Bundle in Mac OS X

Introduction

Recently I launched myself into the world of Mac OS X programming. The entire experience has been of mixed results. On the one hand I appreciate that there are so many tools and guides out there to help programmers develop for the Mac OS X platform, but I feel that most of the Apple Developer Guides are sadly lacking in proper examples of how to do basic things.

One of the basic things that I wanted to do was find a way to get a C string path to a file located in the application bundle.

As some of you Mac OS X users may know one of the great things about professional Apple programs is that they are located inside bundles. All the data and files associated with the program are inside one tidy little bundle that can be dragged into your application directory to install it. Or you can drag the bundle to your home folder, or run it off of your desktop, or rename it. The application continues to run no matter how you change its name or location.

This is extremely nice for the end use, but finding out how to achieve this as a programmer was a frustrating process that required hours of research for me.

After searching through forum threads, Apple documentation, email list servers, and other sources I finally put together a flexible set of functions that allows you to easily get a string path reference to data inside your Mac OS X application bundle.

The C++ Source Code

#include "CoreFoundation/CFBundle.h"

CFBundleRef mainBundle;

bool initializeBundleLoading()
{
mainBundle = CFBundleGetMainBundle();

if(!mainBundle)
{
  return false;
}

return true;
}

char *absoluteBundleResourcePath(const char *name_of_file_in_bundle)

{
CFURLRef resourceURL;

// Look for the resource in the main bundle by name and type. resourceURL = CFBundleCopyResourceURL(
mainBundle,

CFStringCreateWithCString(
NULL,
name_of_file_in_bundle,
kCFStringEncodingASCII),

NULL,
NULL
);

if(!resourceURL)
{
  throw new message_list("Failed to locate a file in the loaded bundle!");
}

char *fileurl = new char [200];


if(!CFURLGetFileSystemRepresentation(
    resourceURL,
    true,
    (UInt8*)
    fileurl,
    200))

{
  throw new message_list("Failed to turn a bundle resource URL into a filesystem path representation!");
}

else
{
  return fileurl;
}
}
Function References

bool initializeBundleLoading();

This function must be called once at the start of your program before trying to access any files inside the application bundle. Basically it retrieves a reference to the main application bundle. Without calling this you have no access to the bundle, and trying to load files from it will crash the program.

If the code is unable to locate the main application bundle (a possibility if it was launched from some older nonstandard file system browsers), then it returns the Boolean value false as a way of indicating that it failed.

char *absoluteBundleResourcePath(const char *name_of_file_in_bundle);

This function is what translates the name and relative path of a file in your application's bundle, into a C string absolute path pointing to the file inside the bundle. You can pass the result of this function into any file opening function that needs an absolute path to open a file.

This is very important because ordinarily, when you go to open a file, the application starts to access the file from the root directory: "/"

So not only does this return an absolute path to the bundle, starting from the root directory, but it also gives you the path within the bundle to the file it contains.

Note: You are responsible for freeing up the memory allocated by absoluteBundleResourcePath(). The C string that it returns must be manually deleted to free up the memory. So watch out for memory leaks.....

Other Notes

You'll notice that the code in absoluteBundleResourePath() throws an exception if it fails. In doing this it references a "message_list" class that I programmed. Since this sample code obviously doesn't include that class, then before you use it you will have to replace that exception code with your own error handling routine.

For simplicity you could just make it return "false" on a crash, just like initializeBundleLoading().

If you get any strange compiler errors then it probably means that you haven't added the Core Foundation Framework to your code. This code is dependent on it.

Feel free to use this code in your projects if it helps you. If you have any questions ask them in the comments. If you are looking for a good book that can help you with Mac OS X programming, then check out the highly recommended "Mac OS X Internals: A Systems Approach".

6 comments:

  1. Gawd, I hate bundles. This is the #1 reason I hate programming on a mac. But thank you for posting this, this is exactly what I was looking for.

    ReplyDelete
  2. You are welcome Brandon. I'm glad it could help.

    ReplyDelete
  3. This works very well and you've saved me some time. Thank you.

    ReplyDelete
  4. Very useful, thanks a lot for your work!

    ReplyDelete
  5. Thank you so much! After hours of Googling, you solved my problem in 2 minutes!

    ReplyDelete