One of the least-implemented powerful capabilities you can add to your application is attaching and embedding scripts. In this column I'll give you an idea of how to do this, and clear up some confusion along the way.
Here are the steps for implementing this attachable behavior:
FUNCTION RunAttachedScript(theAlias: AliasHandle): OSAError; VAR fileSpec: FSSpec; scriptRes: Handle; scriptDesc: AEDesc; scriptID, resultID: OSAID; myErr, ignoredErr: OSAError; savedRes, refNum: Integer; specChanged: Boolean; BEGIN (* Get the file specification corresponding to the menu item chosen. *) myErr := ResolveAlias(NIL, theAlias, fileSpec, specChanged); IF myErr <> noErr THEN MyErrorProc(myErr); (* Open the resource fork and grab the script resource. *) savedRes := CurResFile; refNum := FSpOpenResFile(fileSpec, fsRdPerm); IF refNum = -1 THEN MyErrorProc(-1); UseResFile(refNum); scriptRes := Get1Resource(kOSAScriptResourceType, 128); IF ResError <> noErr THEN MyErrorProc(ResError); (* Prepare and run the script. *) myErr := AECreateDesc(typeOSAGenericStorage, scriptRes^, GetHandleSize(scriptRes), scriptDesc); IF myErr <> noErr THEN MyErrorProc(myErr); myErr := OSALoad(gGenericComponent, scriptDesc, kOSAModeNull, scriptID); IF myErr <> noErr THEN MyErrorProc(myErr); myErr := OSAExecute(gGenericComponent, scriptID, kOSANullScript, kOSAModeNull, resultID); ignoredErr := OSADispose(gGenericComponent, scriptID); ignoredErr := AEDisposeDesc(scriptDesc); IF myErr <> noErr THEN MyErrorProc(myErr); (* Finish up. *) ReleaseResource(scriptRes); CloseResFile(refNum); UseResFile(savedRes); (* You might want to do something with the result. *) IF resultID <> kOSANullScript THEN MyDealWithResult(resultID); RunAttachedScript := myErr; END;Before executing a script, you must establish a connection to a scripting component. The easiest thing to do is to connect to the generic scripting component with OpenDefaultComponent. When you're done, disconnect from the component with CloseComponent. Depending on how you design your application, you can open this connection and keep it open while your program is running, or you can open and close the connection each time you load and execute a script. For more information on choosing and connecting scripting components, see Inside Macintosh: Interapplication Communication, Chapter 10.
If you reserve this technique for your own use, you can revise your software simply by replacing scripts with corrected or enhanced versions. Or, if you allow your users to change the embedded scripts, your application becomes easily customizable: users can modify or augment your application's capabilities simply by substituting scripts. You could even ship your application with replacement scripts, which users can substitute for default scripts that you provide.
Aliases to script files. This is the same technique as described above for attached scripts. This method is used primarily for maintaining a list of scripts. You'd use it, for instance, if you kept a collection of scripts in a folder on disk. I don't recommend this technique if the scripts are associated with actual interface elements, because the links that aliases provide to the script files can too easily be broken.
In
the document's resource fork. Storing the scripts as resources is convenient
because you can easily use your favorite resource editor to copy a script
resource from a compiled script or script application and paste it into the
special application file or the document. It also makes it easy to grab the
scripts for loading and executing, using the method shown in Listing 1 (though
in this situation I'd suggest using an ID number other than 128 for the script
resource). The drawback is that your users can get their hands on the script
with their favorite resource editor.
In
the document's data fork. Maintaining the scripts within the data for a
document is a more secure method, since it makes it harder for users to extract
the scripts. It's also more difficult for you, though, because you may have to
keep track of the location within the document's data, and then convert the
script into the form required for execution. You'll want to store three pieces
of information: the four-character ID 'scpt' (typeOSAGenericStorage), the
length of the script data that follows, and the script data itself. The ID
isn't essential, but it may come in handy, especially if there are other types
of data present or if you load your document's data sequentially.
There are many ways to keep track of multiple types of data in a document file.
If you have a lot of different types of data in the file, you can even develop
a small database for the data, complete with a directory, so that you can gain
quick access to particular types of data, including the script. A simpler way
is to maintain the data in one long stream, embedding the script data within
the stream. If you know the location of the script within the stream, you can
just load and execute it when a user wants to run it. One developer I know
reads all the data in the data fork (including scripts) sequentially when the
file is opened, so that he doesn't need to keep track of the script's location
within the file. Listing 2 shows an example of loading script data from the
data fork of a document file.
Listing 2. Extracting script data from a document's data fork
Making your application capable of attaching or embedding scripts puts new
power into your users' hands, giving them unprecedented ability to develop
custom solutions to their problems. It's not hard to do, and the benefits are
enormous. Do it today.
CAL SIMONE (mainevent@his.com, AppleLink MAIN.EVENT) Few people know it, but
before Cal was in the software business, he used to produce records (the
musical kind) in Washington DC and New York. At a time when computers were used
mostly to make robotic dance music, Cal was one of the first to painstakingly
create "human" performances in pop records with about 60 MIDI synthesizers and,
of course, a Macintosh. He now works toward a day when every application will
be scriptable.*
FUNCTION RunEmbeddedScriptFromDataFork(theAlias: AliasHandle;
scriptLoc: LongInt): OSAError;
VAR
fileSpec: FSSpec;
scriptData: Handle;
scriptDesc: AEDesc;
dataType: DescType;
scriptID, resultID: OSAID;
myErr, ignoredErr: OSAError;
refNum: Integer;
scriptLen, readLen: LongInt;
specChanged: Boolean;
BEGIN
(* Open the file. *)
myErr := ResolveAlias(NIL, theAlias, fileSpec, specChanged);
IF myErr <> noErr THEN MyErrorProc(myErr);
myErr := FSpOpenDF(fileSpec, fsRdPerm, refNum);
IF myErr <> noErr THEN MyErrorProc(myErr);
(* Grab the data. *)
IF MemError <> noErr THEN MyErrorProc(MemError);
myErr := SetFPos(refNum, fsFromStart, scriptLoc);
readLen := sizeof(dataType);
IF myErr = noErr THEN myErr := FSRead(refNum, readLen, @dataType);
(* dataType should be typeOSAGenericStorage. *)
readLen := sizeof(scriptLen);
IF myErr = noErr
THEN myErr := FSRead(refNum, readLen, @scriptLen);
IF myErr = noErr THEN scriptData := NewHandle(scriptLen);
IF MemError <> noErr THEN MyErrorProc(MemError);
myErr := FSRead(refNum, scriptLen, scriptData^);
IF myErr <> noErr THEN MyErrorProc(myErr);
myErr := FSClose(refNum);
(* Prepare and run the script. *)
myErr := AECreateDesc(typeOSAGenericStorage, scriptData^,
GetHandleSize(scriptData), scriptDesc);
DisposeHandle(scriptData);
IF myErr <> noErr THEN MyErrorProc(myErr);
myErr := OSALoad(gGenericComponent, scriptDesc, kOSAModeNull,
scriptID);
IF myErr <> noErr THEN MyErrorProc(myErr);
myErr := OSAExecute(gGenericComponent, scriptID, kOSANullScript,
kOSAModeNull, resultID);
ignoredErr := OSADispose(gGenericComponent, scriptID);
ignoredErr := AEDisposeDesc(scriptDesc);
IF myErr <> noErr THEN MyErrorProc(myErr);
(* You might want to do something with the result. *)
IF resultID <> kOSANullScript
THEN MyDealWithResult(resultID);
RunEmbeddedScriptFromDataFork := myErr;
END;
GIVING
IT AWAY
The information in this column is not offered as a complete solution, but is
intended to get you moving with implementing attachability. There are many
other issues surrounding attachability that are worth exploring, such as
getting time during script execution, using attached scripts to allow users to
tinker with some of the core functionality of your application, and providing a
consistent way for your users to edit attached and embedded scripts. I plan to
delve into these other issues in upcoming columns.