MacDevCenter    
 Published on MacDevCenter (http://www.macdevcenter.com/)
 See this if you're having trouble printing code examples


Component Object Model (COM) Development on Mac OS X

by Christopher Hunt
04/16/2004

Ssssh -- don't tell anyone, especially Microsoft enterprise salesmen, but COM can now be enjoyed by Mac OS X programmers. I am being quiet about it because obviously, Apple does not want anyone to know.

Mac OS X has been able to do COM for quite some time. The thing is, no one has really broadcast this -- until now. You can write COM components that have the potential to run on both Windows and Mac OS X with no code changes. This is another reason for Mac OS X to be considered as a serious platform for enterprise applications (for those of you who like creating lists).

COM is commonplace on the Microsoft Windows platform. The majority of Windows programmers out there have had to deal with COM at some stage, given that many Windows programs communicate with each other that way.

In simplistic terms, COM provides a mechanism to share an object-oriented library as a dynamic link library. Neither the client software to the COM object, nor the server software that implements the COM object, need be written in an object-oriented language -- although it helps if they are.

Interestingly, Mac OS X's COM architecture appears to exist mainly to support Apple's Core Foundation Plugins architecture. With a little help that I am about to provide, you can now develop with Apple's COM architecture in a similar way to how you would on Windows.

A special message for Windows programmers new to Mac OS X: "Welcome home."

What Is COM Again?

This article does not intend to describe the ins and outs of COM; many others before me have beaten that path (check out this overview on Microsoft's COM home page and these documents on Google). For the purposes of getting those COM-neurons firing again, though, COM allows software components with a high potential for reuse to be shared between software programs. The object-oriented way COM software components are described means that the benefits of encapsulation, inheritance, and polymorphism can be realized with language independence.

COM also supports location transparency, where the program that calls upon the COM object need not have knowledge of its physical location; e.g. whether the COM object is managed within the same executable, in a local dynamic link library, or by a library on another machine.

In summary, COM is pretty useful and not just for Windows programmers.

How COM Applies to Mac OS X

COM has been implemented on Mac OS X as part of the Core Foundation framework. COM on OS X largely exists to support the Core Foundation Plugins architecture. As such, only in-process COM servers are supported. In English terms, this means that COM can only be implemented using dynamic link libraries on Mac OS X (on Windows, COM can also be housed within the same executable that it is used from, and housed in a dynamic link library on another machine).

In reality, the above does not present a problem. Most COM housings are implemented using dynamic link libraries on Windows.

An important difference between using COM on Mac OS X and COM on Windows is that there is no registry of COM components on the Mac. To explore this difference, you might recall that Windows provides a registry where the class and interface identifiers can be registered for use. This registration process decouples the need for a program to know which dynamic link library a COM component resides in, and also where the library is on disk. Consequently, you can just tell Windows to create an instance of a COM object using CoCreateInstance.

While you could fabricate your own registry and write a CoCreateInstance implementation, you normally load the dynamic link library into memory manually on Mac OS X. The good news is that you can also load a dynamic link library manually on Windows; i.e., you do not have to use the Windows COM registration mechanism or, if you do, you can bypass it. This means that you can load your COM component exactly the same way on Mac OS X and Windows, if you want to. I actually illustrate this later in my COM client code and provide some of the Windows Automation functions so that loading libraries can be done simply and with portability.

In summary, your Mac OS X COM components are housed in a dynamic link library that you manually load. I shall now get to the good stuff and show you how.

Your Mac OS X COM Projects

There are two Xcode projects available to you. The first one is named Sample Carbon COM Client and provides a standard console application that calls upon a COM component. The other project is named Sample Carbon COM Server and is a CFPlugin Bundle project that implements our COM component. The complete list of folders in the download are:

Carbon COM provides Carbonized versions of the Windows interfaces for COM and Automation that we need. Sample Portable COM Client provides the source that implements our COM client in a platform-independent manner. Sample Portable COM Interface provides the COM interface definition files we need to describe our COM object. These files are shared between the client and the server and are platform independent. Sample Portable COM Server provides the source that implements our COM server in a platform-independent manner.

A COM Client Example

Here is something to get your teeth into:


// Initialise COM

CoInitialize(0);

// Build our server's complete filespec into the UTF16 
// format (Unicode) required by Automation

unsigned short* theServerSpecP = UTF16Create(
                        ".."
                        "/.."
                        "/Sample Carbon COM Server"
                        "/build"
                        "/server.bundle"
                        );

// Load our servers type lib so that we can create 
// instances of our COM interfaces

ITypeLib* theTypeLibraryP;
HRESULT theResult = LoadTypeLib(
                            theServerSpecP, 
                            &theTypeLibraryP
                            );
delete[] theServerSpecP;
theServerSpecP = 0;     // No longer required

if (SUCCEEDED(theResult))
{
    // Here's the meat of it - now we've done the hard 
    // bit by loading our type lib, we can create 
    // instances using it - this is a standard 
    // Automation thing. First we obtain an ITypeInfo 
    // instance for our PhoneDialer class. ITypeInfo 
    // 'describes' a class i.e. provides meta 
    // information.
    
    ITypeInfo* theInterfaceTypeP;
    theResult = theTypeLibraryP->GetTypeInfoOfGuid(
                                    CLSID_PhoneDialer,                
                                    &theInterfaceTypeP 
                                    );

    if (SUCCEEDED(theResult))
    {
        // Now we have our type info for IPhoneDialer, 
        // we can create instances of the interface 
        // we're interested in.
        
        IPhoneDialer* thePhoneDialerP;
        theResult = theInterfaceTypeP->CreateInstance(
                            0, 
                            IID_IPhoneDialer, 
                            reinterpret_cast>void**<(
                                &thePhoneDialerP
                                )
                            );
        if (SUCCEEDED(theResult))
        {
            // Now call on our interface to do something
            // interesting. This is what all of the 
            // effort is about!
            
            thePhoneDialerP->Dial("1234");
            
            // We're done with our interface so release
            
            thePhoneDialerP->Release();
        }
        
        // We're done with our type info now so release
        
        theInterfaceTypeP->Release();
    }

    // We're done with our type lib now so we release

    theTypeLibraryP->Release();
}

// Finish up COM

CoUninitialize();

Creating a COM Interface

The centerpiece to any COM development is the creation of one or more interfaces. This interface describes the class and the interfaces that the class supports. We use C++ to describe our interface; however, you can also describe the same interface in other programming languages, if you like.

Our COM interface describes a fictitious IPhoneDialer component. The following code is taken from Sample Portable COM Interface/IPhoneDialer.h:


// 9B5FD3E4-83A2-11D8-8ED2-000393C360A2
DEFINE_GUID(
    CLSID_PhoneDialer,  
    0x9B5FD3E4, 
    0x83A2, 
    0x11D8, 
    0x8E, 0xD2, 0x00, 0x03, 0x93, 0xC3, 0x60, 0xA2
    );

// AE809768-83A2-11D8-B171-000393C360A2
DEFINE_GUID(
    IID_IPhoneDialer,  
    0xAE809768, 
    0x83A2, 
    0x11D8, 
    0xB1, 0x71, 0x00, 0x03, 0x93, 0xC3, 0x60, 0xA2
    );

class IPhoneDialer : public IUnknown
{
  public:

    // IPhoneDialer methods
    
    STDMETHOD(Dial(char* inPhoneNumP)) PURE;
};

The first two lines declare the class and interface's unique identifiers. Unique identifiers are used so that Mac OS X can be told which interface of a class is being referred to; e.g., when a COM component is to be instantiated. The identifiers have a high probability of being unique across networks and machines and can be generated from the command line by using the uuidgen tool.

Incidentally, Universally Unique Identifiers (UUIDs) and Globally Unique Identifiers (GUIDs) are one and the same. Microsoft coined the term GUID.

The class declaration defines our interface. All COM interfaces inherit from IUnknown so that they can be queried and reference counted. Our interface declares just one method (Dial) and is declared using the regular COM convention of STDMETHOD. The method is also declared abstract (PURE) so that client programs can only instantiate the interface via COM. COM needs to control the instantiation so that components can be reference counted.

Creating a COM Server

Now we have created our COM interface declaration, we are ready to create our COM server -- the dynamic link library that will house our COM component.

Server Groups and Files Creating a COM server on OS X involves using the CFPlugin Bundle template when generating a new project with Xcode. The image to the right shows the groups and files declared with my sample COM server.

PhoneDialer.h declares a concrete subclass of IPhoneDialer that also declares methods for IUnknown so that reference counting and querying can be implemented. The header file also declares a class factory, and some counters for reference counting the COM objects housed by this server. Class factories are responsible for instantiating an interface of a COM class.

PhoneDialer.cpp is the implementation of our COM IPhoneDialer component. The implementation describes what the fictitious Dial method does, how the COM component is reference counted and queried, and how the class factory instantiates our COM component. This file is the meat of our server, and where you will undoubtedly spend most of your time.

Server.h declares a Core Foundation UUID for our PhoneDialer factory. This UUID is only used within Server.cpp.

Server.cpp provides all that is necessary to create a PhoneDialer factory and manage components created by the factory. CFPhoneDialer is a private subclass of PhoneDialer for Core Foundation activities and records the factory that creates it. It is important to register components that have been created with the Core Foundation so that OS X can unload the dynamic link library when it is not being used. CFPhoneDialer provides this registration mechanism.

Carbon COM provides the COM, Automation header files, and implementation required so that you can develop COM components with platform independence.

Carbon COM projects also depend on the Core Foundation and CoreServices frameworks. Core Foundation is used to manage CFPlugin activities and CoreServices provides some utility functions for performing memory management across threads.

In terms of what you will generally end up changing for your implementation, the following applies:

Core Foundation Plugins also require that you make a couple of declarations in the info.plist file under our project's Resources grouping. Essentially, you need to declare the UUIDs of the factories in your server, what function handles factory creation, and how the UUIDs of COM classes map to factories. The following code does this:


<key>CFPlugInFactories</key>
<dict>
    <key>AB3831E4-83AB-11D8-B989-000393C360A2</key>
    <string>PluginFactory</string>
</dict>
<key>CFPlugInTypes</key>
<dict>
    <key>9B5FD3E4-83A2-11D8-8ED2-000393C360A2</key>
    <array>
        <string>AB3831E4-83AB-11D8-B989-000393C360A2</string>
    </array>
</dict>

Creating a COM Client

Client Groups and Files Creating a COM client is much less work! The image to the right shows the groups and files associated with the client project.

Once again, you need the interface declaration of the COM component you wish to use -- IPhoneDialer.h. This is the only file that you need to share between the client and the server.

main.cpp demonstrates how to use a COM component in a platform-independent manner. The source of this was included in the previous section, A COM Client Example.

Carbon COM provides the COM, Automation header files, and implementation as per the server project.

Creating a COM client on Mac OS X can be done with most templates provided by Xcode -- ours is based on the C++ Tool template. Just like its server counterpart, the client project depends on the Core Foundation and CoreServices frameworks.

Running the COM client will instantiate our PhoneDialer COM component and call upon its Dial method. Impress your friends and give it a run!

Final Thoughts

We use cross-platform COM to create plugins for our Mac OS X and Win32 products. Doing so provides the potential to save on reimplementing code. We save time and money and are able to more easily produce a result that is well-tested and of high quality. If this is something that you or your organization considers important, then I hope that the above is insightful.

While writing this article it became apparent to me that what I have actually done is ported some of Win32's Automation functionality to OS X. COM is already part of OS X -- it just needed a little help to make it something more than just being useful for Core Foundation Plugins.

Happy COM programming!

Christopher Hunt has 22 years experience in the software development industry. Chris founded and runs Class Action, which has developed a number of "best of category"software products.


Return to the Mac DevCenter

Copyright © 2009 O'Reilly Media, Inc.