Scripting Additions for Mac OS X

This Technote describes the API that allow developers to create AppleScript scripting additions for Mac OS X. It is directed at application developers who are interested in creating scripting additions.





This Technote describes the API that allow developers to create AppleScript scripting additions for Mac OS X. It is directed at application developers who are interested in creating scripting additions.

What are Scripting Additions?

Scripting additions provide a mechanism for delivery of additional functionality that can be used in AppleScripts. A scripting addition can provide Apple event handling and Apple event data coercion handling.

The Apple event handlers and Apple event data coercion handlers installed by a scripting addition are written in basically the same way that handlers used inside an application are written. What differs between scripting additions and applications is the packaging of the code and the mechanisms by which the scripting addition is installed in the system. These differences are discussed in the sections that follow.

Back to Top 

Packaging Scripting Additions

Scripting additions are packaged as bundles with a name ending with the extension .osax and a CFBundleSignature of osax.

Note: Single-file CFM binaries linked with CarbonLib are still supported, but are deprecated because they only work on PowerPC systems. Support for them will be removed entirely in a future OS release.

Back to Top 

Mac OS X version 10.5 (Leopard): Info.plist

Mac OS X version 10.5 (Leopard) introduces a new data-driven scheme for installing the handlers in a scripting addition. It is optional, but recommended since it is simpler to write and faster to load, which affects Applescript's initialization time.

The Info.plist file may contain a OSAXHandlers key whose value is a dictionary containing up to three other dictionaries:

  • Events

    Event handlers.

  • DescCoercions

    Coercion handlers that use the AECoerceDesc interface.

  • PtrCoercions

    Coercion handlers that use the AECoercePtr interface.

The value in each case is a dictionary whose keys are the eight-character event code (event class and event id) or the "from" and "to" DescType values, and whose values are name of the handler function to call. For example, the Info.plist fragment in Listing 1 declares a handler for the syso/dlog event (better known as display dialog), a descriptor-based coercion for STXT to utxt, and a pointer-based coercion for TEXT to utxt.

Listing 1: Info.plist fragment declaring scripting addition functions.

<key>OSAXHandlers</key>
<dict>
  <key>Events</key>
  <dict>
    <key>sysodlog</key>
    <string>DisplayDialogEventHandler</string>
  </dict>
  <key>DescCoercions</key>
  <dict>
    <key>STXTutxt</key>
    <string>CoerceStyledTextToUnicodeText</string>
  </dict>
  <key>PtrCoercions</key>
  <dict>
    <key>TEXTutxt</key>
    <string>CoercePlainTextToUnicodeText</string>
  </dict>
</dict>

All of the functions named must be exported as entry points into the executable. Given this data, the system will take care of installing and uninstalling the handlers for you: there is no initialization or cleanup for you to write.

Back to Top 

Mac OS X version 10.4 (Tiger) and earlier: Required Functions

If your scripting addition will only run on Leopard or later versions, then this Info.plist entry is all you need. If you want it to run on older versions of Mac OS X, then you must supply three additional functions to install, terminate, and reference-count your scripting addition.

Note: When running in Leopard, the initialization function will only be called if you do not provide the Info.plist data described above. The reference-count function will never be called, since Leopard will never attempt to unload a scripting addition from a process once it is loaded. The termination functions will also never be called. If your addition needs to do work at process termination time, such as releasing resources, use atexit(3). Bear in mind that many resources, such as allocated memory, will be freed automatically when the process exits.

Initialization

Your scripting addition's initialization routine is responsible for installing your scripting addition's handler routines. To implement it, the scripting addition must export a routine named SAInitialize, which will be called by AppleScript and must install all the event and coercion handlers. Listing 2 provides a sketch of a scripting addition's initialization routine:

Listing 2: Sample scripting addition initialization routine.

OSErr SAInitialize(CFBundleRef additionBundle)
{
    OSErr err;

    Boolean isSysHandler = true;
    err = AEInstallEventHandler(theAEEventClass, theAEEventID, eventHandlerUPP, refcon, isSysHandler);
    err = AEInstallCoercionHandler(fromType, toType, coercionHandlerUPP, refcon, fromIsDesc, isSysHandler);

    return err;
}

All scripting addition handlers must be installed in the system dispatch table, as shown above.

IMPORTANT: If your initialization routine returns any result other than noErr, then the addition's termination function will not be called, so the initialization function is responsible for cleaning up anything it did. In particular, it should not leave any Apple event handlers installed.

Your scripting addition should always install all of its handlers, even if functionality they require is not present in the system. That way, you have an opportunity to provide a meaningful error message when your addition is invoked, rather than the generic "doesn't understand" or "can't coerce" message.

Your initialization function may perform additional setup operations, such as allocating memory or finding files, but this is not recommended: it creates additional startup cost, and may not even be necessary, since your scripting addition may never be invoked. Initialization should be deferred until your addition is actually invoked.

The additionBundle parameter passed to SAInitialize is a reference to the scripting addition's bundle. This is only present for historical reasons. If a handler needs access to the scripting addition's bundle resources, it can locate the bundle using CFBundleGetBundleWithIdentifier.

Information about bundle references and the bundle format can be found in the References section at the end of this article.

Back to Top 

Termination

Your scripting addition's termination routine is responsible for removing your scripting addition's handler routines. To implement it, a scripting addition must export a routine named SATerminate, which will be called by AppleScript and must remove all the event and coercion handlers. Listing 4 provides a sketch of a scripting addition's initialization routine:

Listing 3: Sample scripting addition termination routine.

void SATerminate(void)
{
    AERemoveEventHandler(theAEEventClass, theAEEventID, gTheHandler, true);
    DisposeAEEventHandlerUPP(gTheHandler);

    ...other cleanup operations...
}

The termination routine may perform other cleanup operations, such as releasing resources. However, the termination function will never be called in Leopard.

If your addition needs to do work at process termination time, such as releasing resources, use atexit(3). Bear in mind that many resources, such as allocated memory, will be freed automatically when the process exits.

Back to Top 

Reference Counting

This routine exists for historical reasons: in classic Mac OS, it was possible to crash the system by removing a scripting addition while it was still running. Therefore, scripting additions exported a function named SAIsBusy to indicate whether or not they were busy, and therefore not safe to unload. This possible crash was solved by other means in Mac OS X 10.2, rendering SAIsBusy obsolete. It still needs to exist, but can simply return false, as shown in Listing 4.

Listing 4: Sample SAIsBusy routine for a scripting addition.

Boolean SAIsBusy(void)
{
    return false;
}

Back to Top 

Helpful Tips

Runtime Considerations

Your scripting addition may be loaded into any application's process, and should therefore take care to not disturb the global state outside of the addition handler itself, such as the Resource Manager resource chain, effective user id, and so on.

Scripting additions are loaded separately into each application process that uses them. As a result, you should design your scripting addition keeping in mind that there may be many instances of your scripting addition open in many different applications at the same time. Some scripting additions may require additional code if they use a single shared resource such as a printer or a serial port.

Back to Top 

Locating Your Scripting Addition's Bundle Resources

Scripting additions may need to access resources and files located inside of their bundle. To do this, get a CFBundleRef using CFBundleGetBundleWithIdentifier, passing it the bundle identifier of your scripting addition.

Information about bundle references and the bundle format can be found in the References section at the end of this article.

Back to Top 

Local and Remote Requests

Each of your scripting addition's handler routines is responsible for detecting and rejecting events from remote systems, if appropriate, typically because the command would constitute a security risk. A handler can determine the source of an event by examining the keyEventSourceAttr attribute in the incoming event. An event from a remote system will have an attribute value of kAERemoteProcess.

DescType sourceAttr;

err = AEGetAttributePtr(eventPtr, keyEventSourceAttr, typeType, NULL, &sourceAttr, sizeof(sourceAttr), NULL);

if (err == noErr && sourceAttr == kAERemoteProcess)
{
    return errAEEventNotHandled;
}

Back to Top 

References

Back to Top 

Downloadables

Back to Top 

Document Revision History

DateNotes
2008-04-24Add sample project.
2008-01-15Update to cover API changes for Leopard.
2004-04-26Unspecified content revisions.
2001-09-13Talks about how to create AppleScript scripting additions (OSAX) for Mac OS X.

Posted: 2008-04-24


Did this document help you?
Yes: Tell us what works for you.
It’s good, but: Report typos, inaccuracies, and so forth.
It wasn’t helpful: Tell us what would have helped.