5. Carbon Plug-ins

“I got creative and ambitious—a dangerous combination.”

—Unknown (from CocoaDev Mailing List)

What did developers do before Cocoa? They ate, drank, and slept Carbon—that's what they did! Carbon is a set of APIs that bridge the gap between previous versions of the Mac OS and Mac OS X. If you've programmed the Macintosh in the past, many of the APIs will look familiar to you: File Manager, Memory Manager, and Resource Manager. Both Carbon and Cocoa applications can access the Carbon APIs, which is an advantage for developers pursuing Cocoa development.

Depending on the last time you wrote a Macintosh application, things might have changed. A nib-based Carbon application used in Project Builder can literally be as simple as the code in Listing 5.1. It probably looks different from what you remember in THINK C.

Listing 5.1. Carbon Application Main



int main(int argc, char* argv[])
{
    IBNibRef         nibRef;
    WindowRef        window;
    OSStatus         err;

    // Create a Nib reference passing the name of the nib file
    // (without the .nib extension)
    // CreateNibReference only searches in the application bundle.
    err = CreateNibReference(CFSTR("main"), &nibRef);
    require_noerr(err, CantGetNibRef);

    // Once the nib reference is created, set the menu bar.
    // "MainMenu" is the name of the menu bar object.
    // This name is set in InterfaceBuilder when the nib is created.
    err = SetMenuBarFromNib(nibRef, CFSTR("MenuBar"));
    require_noerr(err, CantSetMenuBar);

    // Then create a window.
    // "MainWindow" is the name of the window object.
    // This name is set in InterfaceBuilder when the nib is created.
    err = CreateWindowFromNib(nibRef, CFSTR("MainWindow"), &window);
    require_noerr(err, CantCreateWindow);

    // We don't need the nib reference anymore.
    DisposeNibReference(nibRef);

    // The window was created hidden so show it.
    ShowWindow(window);

    // Call the plugin
    CallPlugin();

    // Call the event loop
    RunApplicationEventLoop();

CantCreateWindow:
CantSetMenuBar:
CantGetNibRef:
    return err;
}


Mind you, this application doesn't actually do anything except display a simple menu bar and an empty window. These items are pulled from a nib file as created in Interface Builder. Figure 5.1 shows the Starting Point choices that Interface Builder offers when creating a new nib file. Our application uses the Main Window with Menu Bar. You'll note that once our application initializes everything, it makes a single function call to RunApplicationEventLoop that handles everything else.

Figure 5.1. Choosing a nib file type in Interface Builder.

image

You've learned that Carbon applications can make use of nib files. This allows developers of these applications to avoid having to use ResEdit (you remember ResEdit, don't you) to manage the resources of their application. You can simply create your windows and dialogs directly in Interface Builder. The entire project in Project Builder for our simple Carbon application is as shown in Figure 5.2. Project Builder contains a template, Carbon Application (Nib Based), that we used to start this project.

Figure 5.2. A Carbon application project in Project Builder.

image

The Results

You've seen one project already, but this chapter actually has three projects that come out of it. The primary project is the CFPlugin-based Carbon plug-in itself. The other two are a Carbon application (which you've seen previously) and a Cocoa application that call the plug-in. Both applications call the exact same C language function that calls the exact same plug-in. The applications themselves are nothing more than quick and dirty wrappers to demonstrate the process. We'll step through them later in this chapter.

Figures 5.3 and 5.4 show what each application looks like when calling the plug-in. The window to the left is part of the application. The alert window to the right is called from within the plug-in itself. The "flag = YES" display is showing the value of a Boolean value passed into the plug-in from the calling application. Note that both applications are very similar: the only difference is that the Carbon application calls the plug-in immediately upon launching, and the Cocoa application has a button that must be clicked to call the plug-in. There is no reason for this other than it's easier to create a button in Cocoa than it is in Carbon, so we decided to mix it up a bit and make sure that you were paying attention.

Figure 5.3. A Carbon application calling a Carbon plug-in.

image

Figure 5.4. A Cocoa application calling a Carbon plug-in.

image

Before we look at the plug-in project, here's a few words about how CFPlugins work at a very high level. The basic tenants of the plug-in model are as follows:

• You define one or more plug-in types.

• Each plug-in type consists of one or more plug-in interfaces.

Each plug-in implements all functions in all interfaces for all types that the plug-in supports.

• Each plug-in also provides factory functions that create each type it implements.

• When a host application loads a plug-in, it can call any of the factory functions within the plug-in to instantiate any of the types that the plug-in supports. The application also receives a pointer to the IUnknown interface to the type it instantiated.

• The host application then uses the IUnknown interface to query the implemented types for their function tables. Once this is obtained, the host application can call the unique functions for the types that are implemented within the plug-in.

An example might be an audio-editing plug-in. The type of plug-in is Audio Editor. We might then have two interfaces—one for Real-Time audio processing and one for File-based audio processing. Because our plug-in supports both interfaces, we will implement all functions that pertain to Real-Time audio processing and File-based audio processing. We will provide one factory function to create the Audio Editor type. The host application would then use the factory function to obtain the IUnknown interface for the type and then use the QueryInterface function to obtain the actual interface with function pointers to the unique functions in each implementation. The last thing to do is call the function in the plug-in—processRealTimeAudioPlease.

Keep these points in mind as we walk through the code of the plug-in and the host application, and you will see each of them accounted for. They might seem confusing now, but when you see all the pieces fit together, it will make much more sense.

The CFPlugin Project

The primary point of this chapter is to discuss the CFPlugin-based Carbon plug-in, so let's dive in. In Project Builder, we started with the CFPlugin Bundle project template. This didn't really give us any default source code, but it set up a few things in the project settings for us. Note that the project file in Figure 5.5 is not overcrowded. Let's verify a few settings before we continue.

Figure 5.5. The CFPlugin project in Project Builder.

image

The first item you will want to verify is the WRAPPER_EXTENSION build setting (see Figure 5.6). Although it makes little difference what this extension is set to, “plugin” is the standard for this type of project. You will want to make sure that this is set accordingly.

Figure 5.6. The CFPlugin project Target build settings in Project Builder.

image

The Info.plist entries (see Figure 5.7) are a bit more complex for this project than any other we have worked with up to this point.

Figure 5.7. The CFPlugin project Info.plist entries in Project Builder.

image

You will notice the standard entries, but a few are new to you here. Let's look at the new items in the order that they appear:

CFPlugInDynamicRegisterFunction contains the name of a custom function to be called to perform dynamic registration. This is only called if CFPlugInDynamicRegistration is set to YES. If CFPlugInDynamicRegistration is set to YES and this key is not defined, a function named CFPlugInDynamicRegister is called. This function is defined as follows: typedef void (*CFPlugInDynamicRegisterFunction) (CFPlugInRef plugIn);.

CFPlugInDynamicRegistration determines how the plug-in wants to be registered. If the value is NO, static registration is used. If the value is YES, dynamic registration is used. We will be using static registration in this project.

CFPlugInFactories is used for static registration. Its value is a dictionary whose keys are factory UUIDs (Universally Unique Identifiers) in string format and whose values are function names within the plug-in. Factories are functions within the plug-in that are used to create instances of the plug-in. A plug-in can have multiple factories that create multiple types of plug-ins; that is, a plug-in can actually contain multiple plug-ins. This function is defined as follows: typedef void *(*CFPlugInFactoryFunction) (CFAllocatorRef allocator, CFUUIDRef typeUUID);.

CFPlugInTypes is used for static registration. Its value is a dictionary whose keys are type UUIDs in string format and whose values are arrays of factory UUIDs. You are essentially linking the factories to the types they create in this entry.

CFPluginUnloadFunction, although not listed here, can be used to define a custom function that is called when the plug-in's code is unloaded. If it is not defined, no function is called. This function is defined as follows: typedef void (*CFPlugInUnloadFunction) (CFPlugInRef plugIn);.

Note

As you can see by my example, I will be using static registration to demonstrate the CFPlugin architecture. However, you can implement dynamic registration if your plug-in has the need to check for specific hardware configuration, and so on. Dynamic registration allows you to create your factory/type relationships in code at runtime as opposed to in a static Info.plist entry. The use of dynamic registration loads the code of the plug-in immediately because the CFPlugInDynamicRegisterFunction must be called. With static registration, however, the code doesn't need to be loaded until the host application needs to call it—which is a good thing with regards to memory management.

What Is a UUID?

You will notice that we are making use of something called a UUID to describe our interface, factories, and types. A UUID is a Universally Unique Identifier, which is essentially a value that is unique across all computers in the entire world. It is a 128-bit value that combines a value unique to the computer, usually a hardware Ethernet address, and a value representing the number of 100-nanosecond intervals since October 15, 1582 at 00:00:00.

You can use the UUIDCreator application (see Figure 5.8), written especially for this book, to generate these numbers for you. You will notice that UUIDCreator creates UUIDs in both ASCII and HEX formats. You need to use the ASCII format in your property lists, whereas in code, you will tend to use the HEX version more. The software and source code is available on this book's Web site, along with all the source code for every project.

Figure 5.8. The UUIDCreator application.

image

The CFPlugin Project Source Code

Now that the project settings are out of the way, let's dive into the source code. The good news is that there is only one source file with less than a dozen functions in it. The bad news is that it can be very confusing, so put on your thinking cap.

The CFPlugin architecture is based on Microsoft's COM (Component Object Model) technology. This means that, in theory, you can write plug-ins that are cross-platform and compatible with any host that supports COM. We are not going to worry about that right now though; we're going to concentrate on getting one working under OS X first.

The Plug-in Interface

The first thing a plug-in needs is an interface (see Listing 5.2). The plug-in interface is used by the application to access the plug-in's functionality. It also defines the UUIDs for the types, factories, and the interface itself. Notice that the definitions are made using the HEX format of the UUID, beginning with a NULL.

Listing 5.2. Plug-in Interface in MyCFPluginInterface.h



// Define the UUID for the type
#define kMyTypeID (CFUUIDGetConstantUUIDWithBytes(NULL, 0xDC, 0x83, 0xB5,
0xC8, 0xDD, 0x18, 0x11, 0xD6, 0xB3, 0xD0, 0x00, 0x03, 0x93, 0x0E, 0xDB, 0x36))

// The UUID for the factory function
#define kMyFactoryID (CFUUIDGetConstantUUIDWithBytes(NULL, 0xDC, 0x3F, 0x0D,
0x89, 0xDD, 0x19, 0x11, 0xD6, 0xB3, 0xD0, 0x00, 0x03, 0x93, 0x0E, 0xDB, 0x36))

// Define the UUID for the interface
// MyType objects must implement MyInterface
#define kMyInterfaceID (CFUUIDGetConstantUUIDWithBytes(NULL, 0x07, 0x1D, 0x88,
0x1D, 0xDD, 0x1A, 0x11, 0xD6, 0xB3, 0xD0, 0x00, 0x03, 0x93, 0x0E, 0xDB, 0x36))

// The function table for the interface
typedef struct MyInterfaceStruct {
    IUNKNOWN_C_GUTS;
    void (*myPluginFunction)(void *this, Boolean flag);
} MyInterfaceStruct;


Also in the interface is a structure that defines the functions in our plug-in. In this case, we only have one function, myPluginFunction, which returns void and accepts two parameters. The first parameter, this, is a pointer to the interface itself. The second parameter, flag, is a Boolean that is displayed in the dialog box that the plug-in displays. You can define as many functions as you need using whatever parameters you require.

Also in the interface structure is something called IUNKNOWN_C_GUTS. IUNKNOWN_C_GUTS is a macro defined in CFPluginCOM.h. It defines the bare minimum needed in the function table structure MyInterfaceStruct. IUnknown can be considered the “base” interface that all COM objects are modeled on (the super class if you will). This means that all COM objects are really IUnknown objects at heart and all start with the three functions QueryInterface, AddRef, and Release as shown in Listing 5.3.

Listing 5.3. IUNKNOWN_C_GUTS in CFPluginCOM.h



#define IUNKNOWN_C_GUTS
    void *_reserved;
    HRESULT (STDMETHODCALLTYPE *QueryInterface)
    (void *thisPointer, REFIID iid, LPVOID *ppv);
    ULONG (STDMETHODCALLTYPE *AddRef)(void *thisPointer);
    ULONG (STDMETHODCALLTYPE *Release)(void *thisPointer)


Note

To gain more insight into the magic of COM, be sure to browse the header file CFPluginCOM.h. This is where IUnknown is defined, as well as many other useful tidbits.

The Plug-in Main Function

Let's look at the source code that is our Carbon plug-in. Everything in this section can be found in main.c in the CFPlugin project, as shown in Figure 5.5.

You've seen the function table for our plug-in interface in Listing 5.2. The interface needs a type to implement it. Our type could be considered an object because it contains both data and functions (via the interface). When we ultimately allocate and initialize our type of object, we want to return a complete unit as shown in Listing 5.4.

Listing 5.4. MyType Definition in main.c



// The layout for an instance of MyType
typedef struct _MyType {
    MyInterfaceStruct*    myInterface;
    CFUUIDRef             factoryID;
    UInt32                refCount;
} MyType;


The first function that will be called in our plug-in is our factory function (see Listing 5.5). Shortly after creating the plug-in, the host application will find all factories in the plug-in that pertain to a particular object type that the application is interested in. The factory functions returned are directly related to the definitions in the Info.plist entries in Figure 5.7.

Listing 5.5. myFactoryFunction in main.c



// Implementation of the factory function for this type
void *myFactoryFunction(CFAllocatorRef allocator, CFUUIDRef typeID)
{
    printf("myFactoryFunction ");

    // If correct type is being requested,
    // allocate an instance of MyType and return the IUnknown interface
    if (CFEqual(typeID, kMyTypeID)) {
        MyType *result = _allocMyType(kMyFactoryID);
        return result;
    } else {
        // If the requested type is incorrect, return NULL
        return NULL;
    }
}


When the host application calls CFPlugInInstanceCreate and causes our factory function to be called, we verify that the caller wants to create a type that we know about. Once validated, we create the type by calling the private _allocMyType function and return the result, which will be a pointer to a MyType structure as shown in Listing 5.4. Note the printf function used for debugging purposes and to help trace our code at runtime.

Listing 5.6. _allocMyType in main.c



// Utility function that allocates a new instance
static MyType *_allocMyType(CFUUIDRef factoryID)
{
    // Allocate memory for the new instance
    MyType *newOne = (MyType*)malloc(sizeof(MyType));

    // Point to the function table
    newOne->_myInterface = &myInterfaceFtbl;

    // Retain and keep an open instance refcount for each factory
    newOne->_factoryID = CFRetain(factoryID);
    CFPlugInAddInstanceForFactory(factoryID);

    // This function returns the IUnknown interface so set the refCount to one
    newOne->_refCount = 1;
    return newOne;
}


Listing 5.6 shows the _allocMyType function that actually allocates and initializes our type structure. Note that it fills the myInterface variable with a pointer to myInterfaceFtbl, shown in Listing 5.7. myInterfaceFtbl is a static declaration of MyInterfaceStruct that contains a NULL pad, the “my” versions of the standard IUnknown functions (QueryInterface, AddRef, and Release), and the pointer to our one and only unique plug-in function, myPluginFunction. Note that this static declaration mirrors the MyInterfaceStruct in Listing 5.2 and the IUNKNOWN_C_GUTS in Listing 5.3. We also keep track of the factory UUID so that we can follow the usage status of our type.

Listing 5.7. myInterfaceFtbl in main.c



// The MyInterface function table
static MyInterfaceStruct myInterfaceFtbl = {
        NULL,               // Required padding for COM
        myQueryInterface,   // These three are the required COM functions
        myAddRef,
        myRelease,
        myPluginFunction }; // Interface implementation (specific to my plugin)


At this point, the host will have a copy of the IUnknown interface and can use the QueryInterface function to obtain the interface that is my “subclass” of IUnknown—the “my” interface. When QueryInterface is called, my plug-in's myQueryInterface function (see Listing 5.8) is subsequently executed.

Listing 5.8. myQueryInterface in main.c



// Implementation of the IUnknown QueryInterface function
static HRESULT myQueryInterface(void *this, REFIID iid, LPVOID *ppv)
{
    HRESULT hResult = E_NOINTERFACE;

    printf("myQueryInterface ");

    // Create a CoreFoundation UUIDRef for the requested interface
    CFUUIDRef interfaceID = CFUUIDCreateFromUUIDBytes(NULL, iid);

    // Test the requested ID against the valid interfaces
    if (CFEqual(interfaceID, kMyInterfaceID)) {

        // If the MyInterface was requested, bump the ref count,
        // set the ppv parameter equal to the instance, and return good status
        // This calls through to myAddRef
        ((MyType*)this)->_myInterface->AddRef(this);
    *ppv = this;
        hResult = S_OK;

    } else if (CFEqual(interfaceID, IUnknownUUID)) {

        // If the IUnknown interface was requested, bump the ref count,
        // set the ppv parameter equal to the instance, and return good status
        // This calls through to myAddRef
    ((MyType*)this)->_myInterface->AddRef(this);
        *ppv = this;
        hResult = S_OK;

    } else {

        // Requested interface unknown, bail with error
        *ppv = NULL;
        hResult = E_NOINTERFACE;

    }

    // Release interface
    CFRelease(interfaceID);
    return hResult;
}


The myQueryInterface function converts the incoming interface UUID to a format that it can easily compare against using the CFUUIDCreateFromUUIDBytes function. It then validates if the requested interface is one that is supported here. If so, we increment the reference count by calling AddRef (which really calls through to myAddRef in Listing 5.9) and return the this pointer, the actual plug-in interface.

Listing 5.9. myAddRef in main.c



// Implementation of reference counting for this type
// Whenever an interface is requested, bump the refCount for
// the instance NOTE: returning the refcount is a convention
// but is not required so don't rely on it
static ULONG myAddRef(void *this)
{
        printf("myAddRef ");

        return ((MyType*)this)->_refCount++;
}


At this point, the host application will release the IUnknown interface by calling Release, which will subsequently call our myRelease function in Listing 5.10.

Listing 5.10. myRelease in main.c



// When an interface is released, decrement the refCount
// If the refCount goes to zero, deallocate the instance
static ULONG myRelease(void *this)
{
        printf("myRelease ");

        ((MyType*)this)->_refCount—;
        if (((MyType*)this)->_refCount == 0) {
            _deallocMyType((MyType*)this);
            return 0;
        } else
            return ((MyType*)this)->_refCount;
}


After decrementing the reference count, if it is zero, the type is deallocated by calling the private mirrored function of _allocMyType, _deallocMyType (see Listing 5.11).

Listing 5.11. _deallocMyType in main.c



// Utility function that deallocates the instance
// when the refCount goes to zero
static void _deallocMyType(MyType *this)
{
        CFUUIDRef factoryID = this->_factoryID;
        free(this);
        if (factoryID) {
            CFPlugInRemoveInstanceForFactory(factoryID);
            CFRelease(factoryID);
        }
}


The only thing left to do at this point is actually call the function that is unique to our type and interface. It's amazing how 95% of the source code is the setup of the mechanism to call this one simple function. The function in Listing 5.12 is the entire reason for this chapter. When called, it will display a standard alert with the value of the flag variable displayed as flag = YES or flag = NO. The host application will ultimately release this interface as well when it is done with it.

Listing 5.12. myPlugInFunction in main.c



// The implementation of the MyInterface function
static void myPluginFunction(void *this, Boolean flag)
{
    SInt16 outItemHit;
    StandardAlert(kAlertNoteAlert,
        flag ? "pCFPlugin (flag = YES)" : "pCFPlugin (flag = NO)",
        "pThis alert is being called from myPluginFunction in CFPlugin.",
        NULL, &outItemHit);
}


Before you move on to see how the Carbon application makes this all come together, take a short break. Stretch, go look out a window, walk the dog, or play with the cat. When you return, we'll dive into the application source code. It's only one function, so it should be easy enough.

The CFPluginCarbonApp Host Application Project

What good is a CFPlugin-based Carbon plug-in without an application to call it? This section shows you how to call your plug-in from your Carbon application. Figure 5.3 shows the results of the application. There is actually only one source file to deal with in this application; the MyCFCallPlugin.c file contains the CallPlugin function (see Listing 5.13) that does all the work.

Listing 5.13. CallPlugin in MyCFCallPlugin.c



void CallPlugin(void)
{

    // Create a URL that points to the plug-in using a hard-coded path
    // (You will need to change this to point to the plugin)
    CFURLRef url = CFURLCreateWithFileSystemPath(NULL,
        CFSTR("/Users/zobkiw/Documents/OSXBook/_SOURCE_/
               5. Carbon Plugins/CFPlugin/build/CFPlugin.plugin"),
        kCFURLPOSIXPathStyle, TRUE);

    // Create a CFPlugin using the URL
    // This step causes the plug-in's types and factories
    // to be registered with the system
    // Note that the plug-in's code is not loaded unless
    // the plug-in is using dynamic registration
    CFPlugInRef plugin = CFPlugInCreate(NULL, url);
    if (plugin) {
        // See if this plug-in implements the "My" type
        CFArrayRef factories = CFPlugInFindFactoriesForPlugInType(kMyTypeID);

        // If there are factories for the requested type, attempt to get
        // the IUnknown interface
        if ((factories != NULL) && (CFArrayGetCount(factories) > 0)) {

            // Get the factory ID for the first location in the array of IDs
            CFUUIDRef factoryID = CFArrayGetValueAtIndex(factories, 0);

            // Use the factory ID to get an IUnknown interface
            // Here the code for the PlugIn is loaded
            // IUnknownVTbl is a struct containing the IUNKNOWN_C_GUTS
            // CFPlugin::MyFactoryFunction is called here
            IUnknownVTbl **iunknown = CFPlugInInstanceCreate(NULL,
                factoryID, kMyTypeID);

            // If this is an IUnknown interface, query for the "My" interface
            if (iunknown) {
                MyInterfaceStruct **interface = NULL;

                // CFPlugin::myQueryInterface is called here
                // CFPlugin::myAddRef is called here
                (*iunknown)->QueryInterface(iunknown,
                            CFUUIDGetUUIDBytes(kMyInterfaceID),
                            (LPVOID *)(&interface));

                // Done with IUnknown
                // CFPlugin::myRelease is called here
                (*iunknown)->Release(iunknown);

                // If this is a "My" interface, try to call its function
                if (interface) {
                    (*interface)->myPluginFunction(interface, TRUE);
                    (*interface)->myPluginFunction(interface, FALSE);

                    // Done with interface
                    // This causes the plug-in's code to be unloaded
                    // CFPlugin::myRelease is called here
                    (*interface)->Release(interface);
                } else printf( "Failed to get interface. " );
            } else printf( "Failed to create instance. " );
        } else  printf( "Could not find any factories. " );

        // Release the CFPlugin memory
        CFRelease(plugin);

    } else printf( "Could not create CFPluginRef. " );

}


Note

One thing you will note in this function is that we use printf to display error messages during development. If you want to leave these debugging statements in your code, you might consider using fprintf and printing your errors to standard error, such as this: fprintf(stderr, "Could notblah!").

This function finds the plug-in explicitly. There is no enumeration through directories, although you could add that easily enough. We use a full path to the plug-in and pass it to the CFURLCreateWithFileSystemPath function. When you run this on your computer, you will need to change this path accordingly.

Once we have the path to the plug-in, we create a CFPlugInRef from the URL. This is really nothing more than a bundle reference because our plug-in is really nothing more than a bundle. At this point, the plug-in's Info.plist entries are read in, and all the factories and types are registered with the operating system. If we were using dynamic registration, our dynamic registration function would be called at this point.

With the plug-in reference, we use the CFPlugInFindFactoriesForPlugInType function to see if the plugin implements the “my” type represented by the UUID kMyTypeID. If the array comes back with any items in it, we know that there is a factory to create this type. In our case, we only have one factory, but there might be any number. We then extract the factory UUID from the array and use it to get the IUnknown interface for the “my” type by calling the CFPlugInInstanceCreate function. This is where our plug-ins factory function (refer to Listing 5.5) is called. If multiple factories were returned, we would simply loop through the array to access each of them in turn. We would then examine each one further to decide which one to use, as follows.

After we obtain the IUnknown interface, we can further query the plug-in for the specific “my” interface by calling the QueryInterface function. This is where our plug-in's myQueryInterface (refer to Listing 5.8) is called. When an interface is returned, we are through with the IUnknown interface and can release it.

At this point, we have a pointer to the MyInterfaceStruct (the interface that our plug-in implements) that we can use to call the myPluginFunction passing whatever parameters we have defined. In our case, we pass the interface itself (this) and a Boolean value (flag). This is when you will see the alert appear from within the plug-in (refer to Figure 5.3). After we are done with the interface, we release it as well and then ultimately release the plug-in reference.

Let's look at a diagram that might help to solidify your understanding of how these pieces fit together (see Figure 5.9). Note that this model allows for an extremely complex set of relationships between the plug-ins and the host application, including the use of multiple factories for multiple types. You need not implement every possible complexity to use the architecture successfully. Don't over engineer for the sake of over engineering.

Figure 5.9. The CFPlugin architecture.

image

Note the relationships to the plug-in, the factory, the type, and the interface in Figure 5.9. Multiple interfaces are possible as are multiple types and multiple factories at any stage of this diagram. This is a simple example of what we have presented in this chapter.

A lot of steps and intricacies need to come together to make all this work properly. You might need to reread both the plug-in and the application sides again for it to all fit together. Be sure to look at the source code too.

The CFPluginCocoaApp Host Application Project

It is possible to call a CFPlugin-based Carbon plug-in from within a Cocoa application. There really isn't much to talk about here because the same CallPlugin function in Listing 5.13 is called from within the Cocoa application to call the same plug-in developed earlier. It's code reuse at its finest!

The CallPlugin button (as you saw in Figure 5.4) is connected to the doCallPlugin: method in Listing 5.14. When the button is pressed, the method is called, which does nothing more than call through to the CallPlugin function reviewed previously. This is not only an example of a Cocoa application calling a standard C function, but also a Cocoa application calling a CFPlugin-based Carbon plug-in.

Listing 5.14. doCallPlugin in AppController.c



- (IBAction)doCallPlugin:(id)sender
{
    CallPlugin();
}


Note

So, should you use Cocoa or Carbon when it comes to plug-ins? When I asked many professional Mac OS X developers this question, the answer came up as expected:

If you are supporting legacy code or operating systems in any way, you might need to use CFPlugins. This gives you the advantage of compatibility with pre-Mac OS X operating systems as well as the ability to unload unused code at the expense of a more complex architecture.

If you are developing brand new applications for OS X only and not looking back, use NSBundle. NSBundle provides an elegant interface to your plug-ins, but it is only compatible with Mac OS X and does not (currently) allow code to be unloaded when no longer needed.

Try This

Here are some ideas to expand the plug-in project in this chapter; try these on your own.

Load the Carbon plug-in from your own application bundle as in Chapter 4 using directory enumeration. Don't forget to set up the project file to copy the plug-in into the PlugIns directory.

Choose your poison, Carbon or Cocoa, and write a useful application that makes use of the CFPlugin mechanism. If you're brave, you might try writing the image-editing application from Chapter 4 using CFPlugin instead of NSBundle.

Conclusion

You now have a good platform to base your plug-in development on. Not only have you learned about calling Cocoa plug-ins from Cocoa in Chapter 4, but also Carbon plugins from Carbon and Cocoa in this chapter. Throughout the chapters of this book, you will see plug-in type code implemented in various ways, proving that there truly are as many ways to implement something as there are programmers! Use the examples presented here to get you started, and be sure to share your improvements with the developer community.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.66.185