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 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.
Listing 2. Extracting script data from a document's data fork
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;
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.*