January 94 - OSL Components
OSL Components
Bo Klintberg
In this crazy, brave, new world of collaboration, Mr. Peabody might suddenly decide to
use your application as a server when he's running his amazing new script. Let's have
a look at what happens in an application written with MacApp 3 and the OSL Scripting
Components source code framework.
PREPARING A SERVER APPLICATION
Let's begin with how easy it is to create a server application with the OSL Scripting
Components package. The only thing that you need to do to create an skeleton server
application that supports the Required, Core, Miscellaneous and OSLSC suites is to
inherit your application class from TOSLServerApplication and to call
IOSLServerApplication from your own TYourApplication::IYourApplication method.
pascal void TScriptServerApplication::IScriptServerApplication()
{
this->IOSLServerApplication(
kScriptServerMainFileType, kScriptServerAppSignature);
}
THE SERVER SIDE
To describe and simplify the concept of an application that communicates in a
collaborative environment, I decided to introduce the concept of communication side. As
I see it, any application has two communication sides, which may or may not be
actively used by the application. One side, the client side, is responsible for the
sending of events to other applications and handling the reply coming back. The other
side, the server side, is responsible for handling incoming events from other
applications and returning a reply (see Figure 1). This figure shows that any
application in fact has two communication sides: the Server side and the Client side,
thus making it possible to handle incoming messages from client applications, as well
as being able to send messages to server applications.
This means that if you want to build your own server (or recorder) application, then
you should add a server side to your application; for the developer's convenience, the
"normal" cases are already implemented in the OSL Scripting Components package. The
following method in TOSLClientServerApplication, the abstract superclass from which
TOSLServerApplication and TOSLRecorderApplication inherits, illustrates the main
things that an installation of a server side should do.
pascal void TOSLClientServerApplication::DoAddServerSide()
{
this->DoMakeServerSideNodes();
this->DoMakeOSLResolverManager();
this->DoAddServerSideBehaviors();
}
The hooked-up clients manager
First, the DoMakeServerSideNodes method should create a manager for the client nodes
that are going to hook up to this server. For example, the result of the
DoMakeServerSideNodes method in the TOSLServerApplication class is that it will
create a TServerSideManager, which, indeed, manages all hooked-up clients. Each new
client that sends an Apple event to such a server application will be added to the
TServerSideManager's list of clients.
The Apple events resolver
Second, the DoMakeOSLResolverManager method should create a resolving manager.
The resolving manager controls the resolving process when the data received in the
Apple event need to be interpreted.
The Apple events handler
Third, the DoAddServerSideBehaviors method should create any number of behaviors
to actually dispatch the Apple events. I chose to create one behavior per event suite,
thus ending up with adding four behaviors to the TOSLServerApplication: the
TReqSuiteHandler (required suite), TCoreSuiteHandler (core suite),
TMiscSuiteHandlerForServerSide (Miscellaneous suite) and the
TSCSuiteHandlerForServerSide (OSLSC suite).
pascal void TOSLServerApplication::DoAddServerSideBehaviors()
{
inherited::DoAddServerSideBehaviors();
this->DoAddReqSuiteHandler();
this->DoAddCoreSuiteHandler();
this->DoAddMiscSuiteHandler();
this->DoAddSCSuiteHandlerForServerSide();
}
By overriding the DoAddServerSideBehaviors method in your own
TYourServerApplication class, you could add any number of suite-dispatching
behaviors. You might, for example, want to dispatch any of the other standard suites
like the Text suite and Database suite or dispatch the events of your own suite.
TRAPPING APPLE events
Now, let's look at what the application needs to know in order to work as an Apple
events server.
• The 'SIZE' resource must be set up properly
• Each Apple event suite and its events must be defined
• There must be dispatching in the code for each Apple event
The 'SIZE' resource
First, you must make sure that your application actually will be delivered an Apple
event by the operating system. You do this by setting the highLevelEventAware flag in
the 'SIZE' resource of your application. Now, all incoming Apple events can be
dispatched in your application.
Suite RESOURCES
To prepare a suite's events for dispatching, you need to add two or three resource files,
depending on which suite you want to be able to dispatch: a Suite Definition resource, a
Command Constants resource, and an AEDispatch resource.
Note, however, that the OSL Scripting Components package already has defined
everything you need if you are going to work with the Required, Core, Miscellaneous
and OSLSC suites, both the resource files and the actual dispatching code inside the
application.
The Suite Definition
A suite definition resource file contains all the constants used by this suite, including
the suite ID, event IDs, additional properties and data types and more. If it's a standard
suite, like the Required or Core suites, constants for these are already defined in the
AppleEvent resource files supplied by Apple. In this case, for example, you won't have
to create a RequiredSuite.r or CoreSuite.r resource file.
However, if you are implementing your own suite and your suite's name is, for
example, MySuite, then you have to create a MySuite.r resource file, containing
constants for the suite ID and the suite's event IDs (see figure 2). This figure shows
the three different resource files you'll need to specify and dispatch your own Apple
event suite. Note that the constants in the MySuite.r file also will be used if you are
creating a client-type application that issues command from your suite.. In the case of
the OSL Scripting Components suite, that resource file is named SCSuite.r, and it
contains all the constants used by this suite. Remember, this file is not necessarily
used only to compile your server application, but could be used to compile your client
application as well, if you want that client to issue Apple events from the MySuite
suite.
An example of the contents of a Suite Definition resource file: the OSL Scripting
Components suite's SCSuite.r:
//***********************************************************
// OSL Scripting Components suite
//***********************************************************
#define kOSLScriptingComponentsSuiteEventClass 'SCec'
#define kSCInformServerThatClientQuits 'SCis'
#define kSCInformClientThatServerQuits 'SCic'
The Command Constants
As you may know, MacApp's handling of incoming Apple events is normally to redirect
the events so that they will end up in the application class' DoAppleCommand method,
ready to be dispatched by you. This means that we must define the command constants
that the server's application's DoAppleCommand method (or any of its installed
behaviors DoAppleCommand methods) use to dispatch.
In the OSL Scripting Components package, all command constants that are a result of an
incoming Apple event are placed in special file for the suite the event belongs to. For
example, the command constants that are corresponding to incoming events from the
Core suite are placed in the HandleCoreSuiteCommandID.r file.
#define cHandleAEClone 10100
#define cHandleAEClose 10101
#define cHandleAECountElements 10102
(more)
Note that all command constants for incoming Apple events begin with "cHandle" to
point out that they are on an application's server side.
The Dispatch Table
To actually accomplish the redirection of an Apple event to an Apple command, we must
map each event ID to a commandID in a resource of type 'aedt'-apple event dispatch
table.
An example of such a resource from the CoreSuiteAEDispatch.r file is shown below,
where the constants for the incoming Apple events of the Core suite are mapped to
corresponding command constants.
// **********************************************************
// APPLE EVENT DISPATCHING - CORE SUITE
// **********************************************************
resource 'aedt' (kAECoreSuiteDispatchTable) {{
kAECoreSuite, kAEClone, cHandleAEClone;
kAECoreSuite, kAEClose, cHandleAEClose;
kAECoreSuite, kAECountElements, cHandleAECountElements;
(more)
}};
DISPATCHING THE Suite
Normally, you'd expect to catch the Apple commands in the application's
DoAppleCommand method. Well, that's perfectly OK to do, especially if you are going to
handle just an event or two. But as soon as you realize that you probably have to
support dozens of events from many different suites (including your own suites, too),
you may want to look for other ways to do it.
In order to bring some system into this, I decided to work with events on a suite-level
basis-that is, a suite and its events is a building block, which I would like to add or
remove from the application. To accomplish this, I used the concept of behaviors that I
can install into the application. Each behavior contains the handling of all the
commands in one suite.
For example, the handling of the Core suite events are done in the behavior called
TCoreSuiteHandler, located in the UHandleCoreSuiteCmds unit (see example code
below).
pascal void TCoreSuiteHandler::DoAppleCommand(
CommandNumber aCmdNumber,
const AppleEvent& message,
const AppleEvent& reply) // override
{
switch (aCmdNumber)
{
case cHandleAEClone:
this->DoHandleAECloneCommand(aCmdNumber,message,reply);
break;

case cHandleAEClose:
this->DoHandleAECloseCommand(aCmdNumber,message,reply);
break;

case cHandleAECountElements:
this->DoHandleAECountElementsCommand(aCmdNumber,message,
reply);
break;

(more)

default:
inherited::DoAppleCommand(aCommandNumber,
message, reply);
break;
}
}
The DoHandleAECloneCommand, DoHandleAECloseCommand and
DoHandleAECountElementsCommand methods in the code example above create,
initialize and post a new instance of THandleAECloneCommand,
THandleAECloseCommand and THandleAECountElementsCommand, respectively. These
server-type command classes (they all inherit from TServerCommand, even though
not direct descendants) are also physically located in the UHandleCoreSuiteCmds unit,
together with the behavior (see Figure 3).
The server-type command
Each server-type command class has the following main responsibilities:
1. The TServerCommand-subclass object reads the raw bytes in the event
2. The TServerCommand hands over these bytes to the Toolbox's AEResolve
function
3. The TServerCommand subclass object asks the resolver manager for the
newly resolved TOSLObjectResolver object.
4. The TServerCommand-subclass object tells the TOSLObjectResolver its
message
5. The TServerCommand-subclass object writes the result back to the
client
To be able to manage the complexity of these tasks and to simplify and clarify the
responsibilities, I decided to divide the functionality into several subclasses:
• TOSLServerCommand class
• THandleAppleEventCommand class
• THandleOSLObjectCommand class
• the actual command to handle a specific Apple event, for example the
THandleAEGetDataCommand class
TOSLServerCommand
This class is responsible for taking care of the type-casting of a TAppleEvent to a
TOSLAppleEvent. The TOSLAppleEvent is a subclass of TAppleEvent with a more
complete set of routines. The TOSLServerCommand also administrates the automatic
registration of the calling client and the deregistering of the client when the
communication closes.
class TOSLServerCommand : public TServerCommand
{
public :
// Creating and freeing:
virtual pascal void InitializeFromAppleEvent(