The Mental Blog

Software with Intellect

2 notes

Embedding Resource Files in a Cocoa Foundation Command Line Tool

When we work with Cocoa, we are usually developing an app, and resources such as images can be stored in the application bundle. Xcode handles everything, and most of us don’t give a second thought to it. But what if you don’t have a bundle? What if you are developing a command line tool using the Foundation framework, and have no directory to store your resources in?

I encountered this situation working on a developer tool for the Core Data Ensembles project. I wanted to have a standalone command line tool, but the tool had to make use of Core Data, and had to access a Core Data model file. I didn’t want to have to supply a path to the model as an argument; I wanted the model embedded in the executable itself.

Graham Lee and several others pointed me to two standard solutions: you can use the linker to embed the files in the __text section of the Mach-O binary, or you can use a tool called xxd to convert the file’s data to a C array, and include that directly in your source code. I ended up with the second solution, which I will explain further below. I didn’t investigate using the linker, but Quinn “The Eskimo!” assures me that you use getsectXXX APIs to extract the data at run time. (Update: Daniel Jalkut has a post describing this approach.)

Returning to xxd, I ended up adding a script build phase in Xcode which tarred up all of my resources.

RESOURCESDIR="${SRCROOT}/../../Framework/Resources"
MODELPATH="$RESOURCESDIR/CDEEventStoreModel.xcdatamodeld"
CONVERTTMPDIR=$(mktemp -d $TMPDIR/cdeconvert.XXXXX)

MOMDNAME="CDEEventStoreModel.momd"
MOMDPATH="$CONVERTTMPDIR/$MOMDNAME"
/Applications/Xcode.app/Contents/Developer/usr/bin/momc "$MODELPATH" "$MOMDPATH"

cd "$CONVERTTMPDIR"
TARFILENAME=eventModelTarredData
TARPATH=$CONVERTTMPDIR/$TARFILENAME
tar -cf "$TARPATH" "$MOMDNAME" -C .

cd `dirname "$TARPATH"`
/usr/bin/xxd -i "$TARFILENAME" "${SRCROOT}/cdeconvert/model.h"

rm -r $CONVERTTMPDIR

The first part of the script just compiles the Core Data model file into a momd file. After that, a tar command converts the momd directory into a single file.

tar -cf "$TARPATH" "$MOMDNAME" -C .

And lastly, xxd generates a model.h header which is included in my source code.

/usr/bin/xxd -i "$TARFILENAME" "${SRCROOT}/cdeconvert/model.h"

The build phase runs before the main compilation stage, so any changes in the header are compiled into the executable.

The header file produced creates a static array containing the binary data, and a variable for the length of the data. The name of the variable is taken from the name of the input file.

unsigned char eventModelTarredData[] = {
  0x43, 0x44, 0x45, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x6f, 0x72,
  0x65, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x6d, 0x6f, 0x6d, 0x64, 0x2f,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  ...
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
unsigned int eventModelTarredData_len = 26624;

All that’s left is to unpack the data at run time.

#import "model.h"

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        ...

        // Write model tar data to file
        NSData *eventModelData = [NSData dataWithBytes:eventModelTarredData length:eventModelTarredData_len];
        NSString *tarredDataPath = [tempPath stringByAppendingPathComponent:@"eventModel.tar"];
        [eventModelData writeToFile:tarredDataPath atomically:NO];

        // Extract tar file
        NSTask *task = [[NSTask alloc] init];
        task.currentDirectoryPath = tempPath;
        task.launchPath = @"/usr/bin/tar";
        task.arguments = @[@"-xf", tarredDataPath];
        [task launch];
        [task waitUntilExit];

        // Model
        NSString *modelPath = [tempPath stringByAppendingPathComponent:@"CDEEventStoreModel.momd"];

That’s it. A few painful hours for me, but hopefully not nearly as bad for you, if you should ever need to develop a Core Data Foundation Tool, or embed other resources in an executable binary.

  1. mentalfaculty posted this