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 .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]
|