Wednesday, January 5, 2011

OS X Frameworks & Bundles 1

In this post, I have two more examples from Dalrymple & Hillegass (Advanced Mac OS X Programming), which illustrate the very simplest use of a Framework, and loading of a "plug-in" from a bundle.

Project 1:


In Xcode start a new project: Framework; I named it Adder. Drag add1.c and add2.c (from this post or the linked project files) into Classes. Write declarations of the two functions into a new file adder.h and drag that in too. I removed the unused Cocoa, Foundation and AppKit Frameworks under External Frameworks .. Build it (Release config).
Now, from the Desktop do:

$ gcc -g -o useadd -F./Adder/build/Release -framework Adder useadd.c

This is like what we've done before, except for the -F flag which tells the linker where to look for our framework now, at link time. At run time, it will look in a few standard locations, one of which is ~/Library/Frameworks. So move the Adder.framework there (from Adder/build/Release). And it works:

$ export DYLD_PRINT_LIBRARIES=1
$ ./useadd
dyld: loaded: /Users/telliott_admin/Desktop/./useadd
dyld: loaded: /Users/telliott_admin/Library/Frameworks/Adder.framework/Versions/A/Adder
dyld: loaded: /usr/lib/libSystem.B.dylib
dyld: loaded: /usr/lib/system/libmathCommon.A.dylib
f1: 1; main 2
f2: 10; main 12

$ unset DYLD_PRINT_LIBRARIES

Project 2:


This one is for a simple "Foundation tool" that loads code from a bundle at runtime. We've seen a bit of that before (here and a few other posts). It makes you appreciate the whole Framework approach, because the natural question is, once I've got it working, where do I put the bundle? You can put it in Library/Application Support or something, but it's an issue. The example also introduces the notion of a Protocol.

The code listing for 3 files is given below. The first two, BundlePrinter.h and SimpleMessage.m go into a new Xcode project: a Cocoa Bundle named SimpleMessage. Set the Principal Class for SimpleMessage: under Resources > Info.plist > Principal Class > SimpleMessage.

Next start a second new Xcode project: Command Line Tool > Foundation, named BundlePrinter. Put in BundlePrinter.m and another copy of BundlePrinter.h. Build it.

Copy SimpleMessage.bundle to BundlePrinter/build/Release.

In Xcode, run BundlePrinter from the Console:

run
[Switching to process 10570]
Running…
2011-01-05 08:46:39.511 BundlePrinter[10570:a0f] processing plug-in: SimpleMessage.bundle
2011-01-05 08:46:39.556 BundlePrinter[10570:a0f] SimpleMessage plug-in activated
2011-01-05 08:46:39.557 BundlePrinter[10570:a0f] SimpleMessage plug-in deactivated
2011-01-05 08:46:39.558 BundlePrinter[10570:a0f]
message is: 'This is a simple message'


Debugger stopped.
Program exited with status value:0.

It works!
BundlePrinter.h

@protocol BundlePrinterProtocol

+ (BOOL)activate;
+ (void)deactivate;

- (NSString *)message;

@end

SimpleMessage.m

#import <Foundation/Foundation.h>
#import "BundlePrinter.h"

@interface SimpleMessage:NSObject <BundlePrinterProtocol>
{ }
@end

@implementation SimpleMessage

+ (BOOL)activate {
NSLog(@"SimpleMessage plug-in activated");
return (YES);
}

+ (void)deactivate {
NSLog(@"SimpleMessage plug-in deactivated");
}

- (NSString *)message {
return (@"This is a simple message");
}
@end

BundlePrinter.m

#import <Foundation/Foundation.h>
#import "BundlePrinter.h"

NSString *processPlugin(NSString *path) {
NSBundle *plugin;
Class principalClass;
id pluginInstance;
NSString *message = nil;

NSLog(@"processing plug-in: %@", path);
plugin = [NSBundle bundleWithPath:path];
if (plugin == nil) {
NSLog(@"count not load plug-in at path %@",path);
goto bailout;
}

principalClass = [plugin principalClass];
if (principalClass == nil) {
NSLog(@"could not load principal class for plug-in at path %@",path);
goto bailout;
}

if (![principalClass conformsToProtocol:
@protocol(BundlePrinterProtocol)])
NSLog(@"plug-in must conform to the BundlePrinterProtocol");

if (![principalClass activate]) {
NSLog(@"could not activate class for plug-in at path %@", path);
goto bailout;
}
pluginInstance = [[principalClass alloc] init];
message = [pluginInstance message];
[pluginInstance release];
[principalClass deactivate];

bailout:

return message;
}

int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSDirectoryEnumerator *enumerator;
NSString *path, *message;
enumerator = [[NSFileManager defaultManager] enumeratorAtPath:@"."];
while (path = [enumerator nextObject]) {
//NSLog(@"here %@", path);
if ([[path pathExtension] isEqualToString:@"bundle"]) {
message = processPlugin(path);
if (message != nil) {
//printf("\nmessage is: '%s'\n\n", [message cString]);
NSLog(@"\nmessage is: '%@'\n\n", message);
}
}
}
[pool drain];
return 0;
}


That's the first time I've ever used goto (and probably the last).