| 
 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. |  
 
 
 
 
 
 
 
 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 .scptfile. Once you have loaded your script into memory you can
call your event handler using the OSAExecuteEventroutine.  listing 2 illustrates how an application can call a subroutine
handler in a script using theOSAExecuteEvent.  Of course,
for this to work the event provided toOSAExecuteEventmust
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 OSAExecuteEventto 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 thekOSAModeCompileIntoContextflag
so the subroutine handler is compiled into an AppleScript context, and you
callOSAExecuteEventto 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 thekeyASSubroutineNameparameter 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,
andAEPutDesccalls.  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 AEBuildroutines 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
AEBuildAppleEventroutine. |  
 
 
 
 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 AEDescof typetypeOSAGenericStorage,
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 callingGet1IndResourceto 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 theosacompilecommand line tool with
the'-d'option to save the compiled script to the data fork
of a file (seeosacompile's man page for more information). |  
 
 
 [Mar 13 2002] |