ADC Home > Reference Library > Technical Q&As > Scripting & Automation > Carbon >

Calling an AppleScript and providing parameters from an Application


Q: Is there a convenient way to pass parameters to an AppleScript running inside my application?

A: Yes there is. In simple terms, this can be accomplished by creating a script containing an subroutine and then sending an appropriately formatted event to that handler from your application. What follows are the exact steps that you can follow to accomplish this.



Creating a Script

The first step is to define a script containing a subroutine with positional arguments that will do the operations you wish to perform. Listing 1 illustrates an adaptation of the script provided in Technical Q&A QA1018, "Using AppleScript to send an email with an attachment" which includes an subroutine with a number of arguments used in constructing the email message.




on send_email_message(targetAddress, targetName, ¬
        subjectLine, messageText, fileAttachment)

    tell application "Mail"
        (* MAIL APPLICATION VERSION *)
        set mailversion to version as string

        (* SPECIFY DISPLAY OR SEND OPERATION - if displayForManualSend
        is true, then the message is prepared and displayed in a window
        for the user to send it.  if displayForManualSend is false, then
        the message is sent right away.*)
        set displayForManualSend to true

        (* SPECIFY GENERAL CONTENT OF MESSAGE *)
        set bodyvar to messageText
        set addrVar to targetAddress
        set addrNameVar to targetName

        (* DEFINE THE SUBJECT LINE *)
        set subjectvar to subjectLine

        (* CREATE THE MESSAGE *)
        set composeMessage to (a reference to (make new compose message ¬
            at beginning of compose messages))
        tell composeMessage
            make new to recipient at beginning of to recipients ¬
                with properties {address:addrVar, display name:addrNameVar}
            set the subject to subjectvar
            set the content to bodyvar

           (* SPECIFY THE ATTACHMENT *)
           tell content
                make new text attachment ¬
                    with properties {file name:fileAttachment} ¬
                    at before the first word of the ¬
                    first paragraph
            end tell
        end tell

        (* SEND OR DISPLAY THE MESSAGE *)
        if displayForManualSend then
            set messageEditor to make new message editor ¬
                at beginning of message editors

            (* the following is a work around for a bug fixed in later
            versions of the Mail application that was present in versions
            1.0 and 1.1.  *)
            if mailversion is "1.0" or mailversion is "1.1" then
                set compose message of last message editor to composeMessage
            else
                set compose message of first message editor to composeMessage
            end if
        else
            send composeMessage
        end if
    end tell
end send_email_message

Listing 1. A script containing an AppleScript subroutine with positional arguments.





Note:
The script shown in listing 1 illustrates how to attach a single file to an email message. It is possible to attach multiple files to a message by way of an AppleScript script. To see a sample showing how to do that, consult the sample script in QA1018, "Using AppleScript to send an email with an attachment".





Executing Your Script

Once you have defined a script, there are a number of ways that you can store it. For maximum performance and Mac OS X compatibility, it is recommended that you store your script as a compiled data fork based AppleScript in a .scpt file.

Once you have loaded your script into memory you can call your event handler using the OSAExecuteEvent routine. listing 2 illustrates how an application can call a subroutine handler in a script using the OSAExecuteEvent. Of course, for this to work the event provided to OSAExecuteEvent must be formatted appropriately.




static OSStatus ExecuteCompiledAppleScriptEvent(AEDesc *scriptData,
                          AppleEvent *theEvent, AEDesc *resultData) {
    ComponentInstance theComponent;
    AEDesc scriptTextDesc;
    OSStatus err;
    OSAID contextID, resultID;

        /* set up locals to a known state */
    theComponent = NULL;
    AECreateDesc(typeNull, NULL, 0, &scriptTextDesc);
    contextID = kOSANullScript;
    resultID = kOSANullScript;

        /* open the scripting component */
    theComponent = OpenDefaultComponent(kOSAComponentType, typeAppleScript);
    if (theComponent == NULL) { err = paramErr; goto bail; }

        /* compile the script into a new context */
    err = OSALoad(theComponent, scriptData,
                    kOSAModeNull, &contextID);
    if (err != noErr) goto bail;

        /* run the script */
    err = OSAExecuteEvent( theComponent, theEvent,
                    contextID, kOSAModeNull, &resultID);

        /* collect the results - if any */
    if (resultData != NULL) {
        AECreateDesc(typeNull, NULL, 0, resultData);
        if (err == errOSAScriptError) {
            OSAScriptError(theComponent, kOSAErrorMessage,
                        typeChar, resultData);
        } else if (err == noErr && resultID != kOSANullScript) {
            OSADisplay(theComponent, resultID, typeChar,
                        kOSAModeNull, resultData);
        }
    }
bail:
    AEDisposeDesc(&scriptTextDesc);
    if (contextID != kOSANullScript) OSADispose(theComponent, contextID);
    if (resultID != kOSANullScript) OSADispose(theComponent, resultID);
    if (theComponent != NULL) CloseComponent(theComponent);
    return err;
}

Listing 2. Executing a script containing an event handler.



Of course, there may be circumstances where it is desirable to compile a script on the fly and call OSAExecuteEvent to execute the event handler. The techniques for doing so are very similar to those presented in QA1026, "Calling AppleScript from an Application", but with two essential changes: when compiling, you provide the kOSAModeCompileIntoContext flag so the subroutine handler is compiled into an AppleScript context, and you call OSAExecuteEvent to run the script. listing 3 shows the steps in the compile/execute process.




static OSStatus ExecuteAppleScriptEvent(const void* text,
          long textLength, AppleEvent *theEvent, AEDesc *resultData) {
    ComponentInstance theComponent;
    AEDesc scriptTextDesc;
    OSStatus err;
    OSAID contextID, resultID;

        /* set up locals to a known state */
    theComponent = NULL;
    AECreateDesc(typeNull, NULL, 0, &scriptTextDesc);
    contextID = kOSANullScript;
    resultID = kOSANullScript;

        /* open the scripting component */
    theComponent = OpenDefaultComponent(kOSAComponentType,
                    typeAppleScript);
    if (theComponent == NULL) { err = paramErr; goto bail; }

        /* put the script text into a Apple event descriptor record */
    err = AECreateDesc(typeChar, text, textLength, &scriptTextDesc);
    if (err != noErr) goto bail;

        /* compile the script into a new context.  The flag
        'kOSAModeCompileIntoContext' is used when compiling a
        script containing a handler into a context.  */
    err = OSACompile(theComponent, &scriptTextDesc,
                    kOSAModeCompileIntoContext, &contextID);
    if (err != noErr) goto bail;

        /* run the script */
    err = OSAExecuteEvent( theComponent, theEvent,
                    contextID, kOSAModeNull, &resultID);

        /* collect the results - if any */
    if (resultData != NULL) {
        AECreateDesc(typeNull, NULL, 0, resultData);
        if (err == errOSAScriptError) {
            OSAScriptError(theComponent, kOSAErrorMessage,
                        typeChar, resultData);
        } else if (err == noErr && resultID != kOSANullScript) {
            OSADisplay(theComponent, resultID, typeChar,
                        kOSAModeDisplayForHumans, resultData);
        }
    }
bail:
    AEDisposeDesc(&scriptTextDesc);
    if (contextID != kOSANullScript) OSADispose(theComponent, contextID);
    if (resultID != kOSANullScript) OSADispose(theComponent, resultID);
    if (theComponent != NULL) CloseComponent(theComponent);
    return err;
}

Listing 3. Compiling and executing a script containing an event handler.





Okay, so what's in the event?

Well, it's a standard Apple event record formatted the same way as any event you would create and target at your own application. There are two parameters to this event containing information required to execute the handler. The subroutine arguments are provided by position in a list stored in the direct parameter (keyDirectObject) and the name of the subroutine (converted to all lowercase letters) is stored in the second event parameter (keyASSubroutineName) as a string.



IMPORTANT:
The keyASSubroutineName ('snam') parameter must contain the name of the subroutine that is being called with every letter converted to lowercase. For example, if name of the subroutine in your script is "GetDocumentSize", then the string provided in the keyASSubroutineName parameter should be "getdocumentsize".





Note:
Events formatted as described in this section may also be sent (by way of AESend) to compiled and running AppleScript applet applications to call their subroutine handlers.



Putting together the Apple event containing this information is relatively simple and consists of a sequence of AECreateList, AEPutPtr, and AEPutDesc calls. listing 4 shows one way you could create an Apple event suitable for calling the handler defined in listing 1.



OSStatus CreateEmailMessageEvent(AppleEvent *theEvent,
        char* targetAddress, char* targetName, char* subjectLine,
        char* messageText, AliasHandle fileAttachment) {
    AEAddressDesc targetAddr;
    AEDescList theParameters;
    OSStatus err;
    ProcessSerialNumber PSN = {0, kCurrentProcess};
    char* handlerName = "send_email_message";
    char theState;

        /* set up a recoverable state */
    AECreateDesc(typeNull, NULL, 0, theEvent);
    AECreateDesc(typeNull, NULL, 0, &theParameters);
    AECreateDesc(typeNull, NULL, 0, &targetAddr);


        /* create self targeting address */
    err = AECreateDesc(typeProcessSerialNumber, (Ptr) &PSN,
            sizeof(PSN), &targetAddr);
    if (err != noErr) goto bail;

        /* create the apple event */
    err = AECreateAppleEvent('ascr', kASSubroutineEvent,
        &targetAddr, kAutoGenerateReturnID,
        kAnyTransactionID, theEvent);
    if (err != noErr) goto bail;

        /* create and add the list of arguments */

            /* create the container list */
        err = AECreateList(NULL, 0, false, &theParameters);
        if (err != noErr) goto bail;

            /* add the strings */
        err = AEPutPtr(&theParameters, 0, typeText, targetAddress,
            strlen(targetAddress));
        if (err != noErr) goto bail;
        err = AEPutPtr(&theParameters, 0, typeText, targetName,
            strlen(targetName));
        if (err != noErr) goto bail;
        err = AEPutPtr(&theParameters, 0, typeText, subjectLine,
            strlen(subjectLine));
        if (err != noErr) goto bail;
        err = AEPutPtr(&theParameters, 0, typeText, messageText,
            strlen(messageText));
        if (err != noErr) goto bail;

            /* add the alias handle */
        theState = HGetState((Handle) fileAttachment);
        HLock((Handle) fileAttachment);
        err = AEPutPtr(&theParameters, 0, typeAlias, *fileAttachment,
                GetHandleSize((Handle) fileAttachment));
        HSetState((Handle) fileAttachment, theState);
        if (err != noErr) goto bail;


        /* add the parameter list created above */
    err = AEPutParamDesc(theEvent, keyDirectObject,
        &theParameters);


        /* add the handler name */
    err = AEPutParamPtr(theEvent, keyASSubroutineName,
        typeText, handlerName, strlen(handlerName));
    if (err != noErr) goto bail;


        /* clean up and leave */
bail:
    AEDisposeDesc(&targetAddr);
    AEDisposeDesc(&theParameters);
    if (err != noErr) {
        AEDisposeDesc(theEvent);
    }
    return err;
}

Listing 4. Creating an Apple event suitable for calling the AppleScript subroutine defined in listing 1.



But, there's a better and simpler way to construct such an event. The new set of AEBuild routines provide facilities for constructing complex Apple events in a simplified way. Listing 5 illustrates how you can lump all of the calls from listing 4 into a single system call that constructs the event you would like to send.



OSStatus CreateEmailMessageEvent(AppleEvent *theEvent,
        char* targetAddress, char* targetName, char* subjectLine,
        char* messageText, AliasHandle fileAttachment) {

    OSStatus err;
    ProcessSerialNumber PSN = {0, kCurrentProcess};

        /* create the container list */
    err = AEBuildAppleEvent(
        'ascr', kASSubroutineEvent,
        typeProcessSerialNumber, (Ptr) &PSN, sizeof(PSN),
        kAutoGenerateReturnID, kAnyTransactionID,
        theEvent,
        NULL,
        "'----':[TEXT(@),TEXT(@),TEXT(@),TEXT(@),alis(@@)],"
        "'snam':TEXT(@)",
        targetAddress, targetName, subjectLine, messageText,
        fileAttachment, "send_email_message");

    return err;
}

Listing 5. Creating an Apple event suitable for executing an event handler in a script using the AEBuildAppleEvent routine.





Putting it all together

Combining all of the calls described in this document, calling a script with some parameter values would proceed as shown in listing 6. Essentially, the steps are to load the compiled script into an AEDesc of type typeOSAGenericStorage, create the Apple event, and use the Apple event to call the script's subroutine.



AppleEvent theAEvent
AEDesc scriptData;
OSStatus err;
AliasHandle theAlias;
Ptr data;
Size count;

theAlias = GetAnAliasToSomeFile();

err = LoadMyScriptData(&data, &count);
if (err == noErr) {
    err = AECreateDesc(typeOSAGenericStorage,
                        data, count, &compiledScript);
    if (err == noErr) {
        err = CreateEmailMessageEvent(&theAEvent,
            "bogus@apple.com",
            "bogus address",
            "hello world subject",
            "This is a test message\n\n",
            theAlias);
        if (err == noErr) {
                err = ExecuteCompiledAppleScriptEvent(&scriptData,
                    &theAEvent, NULL);
                ...

Listing 6. Ordering of calls provided in listings above.





Note:
The Script Editor normally stores compiled script data in a resource of type 'scpt' in the files it creates. You can load this data by calling Get1IndResource to retrieve the first 'scpt' resource in one of those files. In Mac OS X, the preferred convention is to save compiled script data to the data fork of files with the '.scpt' file name extension. To retrieve that data, simply read the entire data fork. When compiling your own scripts, you can use the osacompile command line tool with the '-d' option to save the compiled script to the data fork of a file (see osacompile's man page for more information).




[Mar 13 2002]


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.