Plug-ins the Cocoa way

For many graphics applications, plug-ins are a way of life. This essay presents a method that minimizes the amount of work required to support different host applications, or even different versions of the same host. I used these techniques in our IconBuilder plug-in for Photoshop. These instructions have already helped one fellow developer; with the help of Google, hopefully there will be more.

My goal was to use Cocoa and NIB files produced by Interface Builder for driving the plug-in user interface. As I began to work on this project, I also realized that it was possible make the user interface independent of the underlying binary file formats: a CFM shim can load a Mach-O bundle.

Here are the steps to accomplish the task:

  1. Photoshop loads the plug-in which is built with Carbon.
  2. The Carbon plug-in searches for a Cocoa bundle in the plug-in’s resources with CFBundleGetBundleWithIdentifier and then loads it with CFBundleCreate.
  3. Once the bundle is loaded, I get a function pointer to the Cocoa code to execute using CFBundleGetFunctionPointerForName.
  4. After I’m in the Cocoa code, I create my own NSAutoreleasePool and then call NSApplicationLoad(). At this point, you can work with controller objects to get things setup and call [NSApp run] to get a run loop going.
  5. From this point on, I can use all the Cocoa and Foundation frameworks. Interface Builder NIB files work fine, too.

I use the Carbon code base with both Codewarrior and Xcode to produce both the CFM and Mach-O versions of the shim.

The biggest advantage to this approach is that your user interface and processing code is independent of the glue code that passes data back and forth to the host application. You could, for example, easily adapt the “plug-in” part to work with Aperture’s new plug-in architecture and not have to worry about 99% of the rest of the code. We take this approach with our IconBuilder product: PS7 and CS3 have slightly different plug-in specifications, so we offer two plug-ins that use the same UI/processing bundle.

Another advantage to this approach is that you can do testing without having to run under the host application. I use a small test harness that loads the UI/processing bundle and feeds it data. This makes it much simpler to work in a debugger without the host application getting in the way. You could also use a similar technique to build a standalone application.

Finally, there’s much better documentation than when I figured all this stuff out. Here’s something at ADC that will help get you started.