Friday, January 7, 2011

OS X Frameworks & Bundles 2

Last time I worked through two examples from Dalrymple & Hillegass (Advanced Mac OS X Programming) (here).

We built a Framework and then separately compiled and linked the useadd.c module against it. The Framework was later placed in ~Library/Frameworks, and the linker was able to load it at runtime.

The other one was a Cocoa Bundle compiled separately and placed in a known directory, which a second project that is a command line tool searches to find it. Since the tool doesn't have a place to stash resources, that's a little awkward.

They have 3 more examples in the chapter. One has a module called simplemessage.m (no header) that is compiled as a "bundle" (though not the same as the real bundles) and given the extension .msg. (It doesn't really matter what we use). Then, that code is loaded by a second module called bundleprinter, which uses a bunch of functions from <mach-o/dyld.h>:

$ gcc -Wall -o simplemessage.msg -bundle simplemessage.m
$ gcc -o bundleprinter bundleprinter.m



bundleprinter.m: In function ‘addressOfSymbol’:
bundleprinter.m:20: warning: ‘NSLookupSymbolInModule’ is deprecated (declared at /usr/include/mach-o/dyld.h:181)
bundleprinter.m:25: warning: ‘NSAddressOfSymbol’ is deprecated (declared at /usr/include/mach-o/dyld.h:188)
bundleprinter.m: In function ‘processPlugin’:
bundleprinter.m:35: warning: ‘NSCreateObjectFileImageFromFile’ is deprecated (declared at /usr/include/mach-o/dyld.h:145)
bundleprinter.m:43: warning: ‘NSLinkModule’ is deprecated (declared at /usr/include/mach-o/dyld.h:161)
bundleprinter.m:70: warning: ‘NSUnLinkModule’ is deprecated (declared at /usr/include/mach-o/dyld.h:169)
$ ./bundleprinter
SimpleMessage plug-in activated
SimpleMessage plug-in deactivated

message is: 'This is a simple message'


As I think you can see, this setup works, but the functions involved are all deprecated, so it seems pointless to go through it. The fourth example is an updated version of the same one which goes more smoothly. It uses functions declared in <dlfcn.h>: dlopen and dlsym. Their (simple version of the) plug-in is just this:

// cc -Wall -o simplemessage.msg -bundle simplemessage.m

#import <string.h>
#import <stdio.h>

int activate(void) {
printf("SimpleMessage plug-in activated\n");
return (1);
}

void deactivate(void) {
printf("SimpleMessage plug-in deactivated\n");
}

char *mymessage(void) {
return (strdup("This is a simple message"));
}

I did not use the -g flag to the compiler because the resulting .dSYM file interferes with the loading, later. You could remove it after the build if you wanted. The code which finds and loads the plug-in is below. And the output is:


$ ./bundleprinter-dl
SimpleMessage plug-in activated
SimpleMessage plug-in deactivated

message is: 'This is a simple message'


bundleprinter-dl.m:

// cc -g -o bundleprinter-dl bundleprinter-dl.m

#import <dirent.h>
#import <stdlib.h>
#import <stdio.h>
#import <errno.h>
#import <string.h>
#import <dlfcn.h>

typedef int (*ActivateFP) (void);
typedef void (*DeactivateFP) (void);
typedef char * (*MessageFP) (void);

char *processPlugin(const char *path) {
char *message = NULL;
void *module;
module = dlopen(path, RTLD_LAZY);
if (module == NULL) {
fprintf(stderr, "couldn't load plugin in at path %s. error is %s\n",
path, dlerror());
goto bailout;
}
ActivateFP activator;
DeactivateFP deactivator;
MessageFP messagator;
activator = dlsym(module, "activate");
deactivator = dlsym(module, "deactivate");
messagator = dlsym(module, "mymessage");

if (activator == NULL || deactivator == NULL
|| messagator == NULL) {
fprintf(stderr,
"could not find message symbol (%p %p %p)\n",
activator, deactivator, messagator);
goto bailout;
}
int result;
result = (activator)();
if (!result) { goto bailout; }
message = (messagator)();
(deactivator)();

bailout:
if (module != NULL) {
result = dlclose(module);
if (result != 0) {
fprintf(stderr,
"could not dlclose %s. Error is %s\n",
path, dlerror());
}
}
return (message);
}

int main (int argc, char *argv[]) {
DIR *directory;
struct dirent *entry;
directory = opendir(".");
if (directory == NULL) {
fprintf (stderr,
"could not open directory for plugins\n");
fprintf(stderr, "error: %d (%s)\n", errno, strerror(errno));
exit(EXIT_FAILURE);
}
while ((entry = readdir(directory)) != NULL) {
if (strstr(entry->d_name, ".msg") != NULL) {
char *message;
message = processPlugin(entry->d_name);
printf("\nmessage is: '%s'\n", message);
if (message != NULL) { free(message); }
}
}
closedir(directory);
return EXIT_SUCCESS;
}

The last example from this Chapter is one where a Framework is embedded in an application. That's for next time.