Programming Scripting Xtras with Visual C++ and the Director XDK
By Christophe Leske © 2003
This is a step by step how-to to set up the Visual C++ 6.0 compiler for the Director 8.5 XDK and programming your first scripting Xtra. The Macromedia XDK is used for programming Xtras for Macromedia Director, which are code extensions to the application.
I. Prerequisites
For this how-to, you will need
- Macromedia Director 8 or higher
- the MS Visual C++ 6.0 compiler (or higher)
- the Macromedia XDK (http://www.macromedia.com/support/xtras/xdks/xdk.html)
II. Step-by-step how-to
1) Download the XDK from macromedia.
2) Extract it to a directory of your choice. For the rest of this how-to, i will presume the XDK to be located at c:\XDK85_win
3) The directory structure should look like this:
C:\XDK85_win\readme.html --> readme file
C:\XDK85_win\Docs --> Director XDK documentation
C:\XDK85_win\examples --> Project and source files for several examples
C:\XDK85_win\Include --> required header files for development
C:\XDK85_win\Lib --> additional useful libs for development
Thus, only the later two directories are really needed for development, the include directory being the most important one as it includes the header files.
4) The examples folder is a good way to start. Scripting Xtras are the easiest ones to write.The ScriptingXtras example folder provides several scripting Xtra examples:
5) Let´s take the "skeleton2" example, which is an empty skeleton project for xripting Xtras. Open up the "script.dsw" workspace file in Visual C++ 6. The file is located at
C:\xdk85_win\examples\Script\skeleton2\winproj\script.dsw
6) This empty skeleton file won´t compile right away. Try it anyways by pressing F7 in Visual C++. You will get a warning like:
--------------------Configuration: Script - Win32 Debug--------------------
Compiling...
Script.cpp
c:\xdk85_win\examples\script\skeleton2\source\script.h(23) : fatal error C1189: #error :
PLEASE DEFINE A NEW CLSID
Error executing cl.exe.
Script.x32 - 1 error(s), 0 warning(s)
In Xtra programming, you use the so-called MOA (macromedia open architecture) model. It is closely related to microsoft COM programming. In this model, you don´t hook up your code directly with the host application, but go through an interface layer which standardizes access the to the underlying classes you programmed for the xtra.
Thus, this is a three layered approach:
Layer 1: Director as the host application you want to hook up your code in
Layer 2: the abstract interface layer which abstracts your code for Director
Layer 3: Your code, which, due to the abstracting interface layer, doesn´t need to be
written in C/C++ (although this is what this tutorial will be using)
When programming an Xtra, you will work in the second layer (the abstraction layer) and in the third layer (your code), obviously.
We will describe to Director what we want to hook up (layer 2: we want to register our code for new lingo commands) and what they are supposed to do (layer 3: our custom programmed methods).The abstraction layer will also be wired with the code layer in order to pass the events and data structures back and forth.
8) Xtras are registered with Director on startup. The application scans the Xtras folder for files with the .x32 extension. The registration process is done in the abstraction layer. In order for your Xtra to register correctly with Director, it needs to have a genuine ID, a so-called GUID. This is how your new Xtra identifies itself to Director. If two Xtras have the same GUID, then you will get the "duplicate Xtras found" startup message in Director.
9) the first step therefore is to create a unique ID for our Xtra. For that, you need the GUIDGEN tool which comes with Visual C++. It won´t show up in the startup menu, but can be found in the Visual C++ workring directory (C:\Program Files\Microsoft Visual Studio\Common\Tools). It looks like this:
The second entry is exactly what we need. Click “New GUID” then “Copy”. The clipboard now holds something like the following
// {E8DC4236-72CA-4233-AFBE-CC6517373296}
DEFINE_GUID(<>,
0xe8dc4236, 0x72ca, 0x4233, 0xaf, 0xbe, 0xcc, 0x65, 0x17, 0x37, 0x32, 0x96);
[NOTE: Since this is supposed to be a genuine ID, yours will differ.]
10) switch back to Visual C++ and make sure that you have “script.h” opened in the code editor. Locate the following code at the top of the file:
#error PLEASE DEFINE A NEW CLSID
DEFINE_GUID();
11) Things will now look like this:
#ifndef _H_Script
#define _H_Script
#include "moastdif.h"
#include "xmmvalue.h"
#include "mmiutil.h"
/* --------------------------------------------------- */
DEFINE_GUID(CLSID_TStdXtra, 0xe8dc4236, 0x72ca, 0x4233, 0xaf, 0xbe, 0xcc, 0x65, 0x17,
0x37, 0x32, 0x96);
[...]
Check everything by compiling the project files using “Build”, then “Rebuild all” in Visual C++. Although the Xtra is “empty”, it should work:
Deleting intermediate files and output files for project 'Script - Win32 Debug'.
--------------------Configuration: Script - Win32 Debug--------------------
Compiling resources...
Compiling...
Script.cpp
Linking...
Creating library .\Debug/Script.lib and object .\Debug/Script.exp
Creating browse info file...
Script.x32 - 0 error(s), 0 warning(s)
12) You now have a genuine scripting Xtra which registers correctly with Director on startup. From hereon, we will code our methods.
III. Exploring “script.h”
/*
Copyright (c) 1998-2001 Macromedia, Inc. All Rights Reserved
You may utilize this source file to create and compile object code for use within products
you may create. THIS CODE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, AND
MACROMEDIA DISCLAIMS ALL IMPLIED WARRANTIES INCLUDING, BUT NOT LIMITED TO,
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT WILL
MACROMEDIA BE LIABLE TO YOU FOR ANY CONSEQUENTIAL, INDIRECT OR INCIDENTAL DAMAGES ARISING OUT OF
YOUR USE OR INABILITY TO USE THIS CODE.
*/
#ifndef _H_Script
#define _H_Script
#include "moastdif.h"
#include "xmmvalue.h"
#include "mmiutil.h"
/* --------------------------------------------------- */
DEFINE_GUID(CLSID_TStdXtra, 0xe8dc4236, 0x72ca, 0x4233, 0xaf, 0xbe, 0xcc, 0x65, 0x17,
0x37, 0x32, 0x96);
EXTERN_BEGIN_DEFINE_CLASS_INSTANCE_VARS(TStdXtra)
PIMoaMmUtils2 pMoaUtils;
PIMoaMmValue pValueInterface;
/*
* ---> insert additional variable(s) -->
*/
EXTERN_END_DEFINE_CLASS_INSTANCE_VARS
The first part consists of a comment by macromedia, which is followed by a definition (?) and some include statements, which includes header files with definitions of MOA variables and utilities for later use.
The definition of our GUID completes starts the real programming section.
By the way, everything written in uppercase is a macro which is getting expanded at compilation time. Our GUID definition for instance is triggering a macro which will set CLSID_TStdXtra to our specified ID.
The last block with EXTERN_BEGIN and EXTERN_END defines two class instance variables for any instance of the TstdXtra class. These are global variables which will are available to us for our convenience:
- pMoaUtils is an object holding generic helper methods. We will see more of this later on.
- pValueInterface is to pass data back and forth between Director/Lingo and our Xtra.
Onto the next section:
/*****************************************************************************
* Xtra Moa Registration Interface - Class definition
****************************************************************************/
EXTERN_BEGIN_DEFINE_CLASS_INTERFACE(TStdXtra, IMoaRegister)
EXTERN_DEFINE_METHOD(MoaError, Register, (PIMoaCache, PIMoaXtraEntryDict))
EXTERN_END_DEFINE_CLASS_INTERFACE
This part is the beginning of the abstraction layer. Here, Director is informed that there is a standardized interface for registering the Xtra.
The macro above glues the Director registration routine to the method “Register” of the TstdXtra Class. The first line defines the class name which we will be using and provides a parameter defition for instantiating the class.
In simple words, it says:
“when registering the Xtra, do so by using the class 'TstdXtra' and provide its constructor with IMoaRegister. The registration process will be handled by the member function called 'Register'”.
IMoaRegister translates into “Interface for MoaRegistering” and is the hook into the Director application. The constructor of a class is called whenever an instance of that class is created.
For registration, we use the member function “Register”, and provide it with two parameters:
- PIMoaCache (?)
- PIMoaXtraEntryDict (method pointer for adding our method?)
The return value will be of type MoaError, indicating if everything went alright or not.
Note that the class method “Register” needs to match this definition as well, meaning that it really needs to return a MOAError and accept the two parameters provided.
This macro however is not the real programming. This is just the abstraction layer which explains to Director how to call the registration routine for the Xtra and which method to use how for the registration process.
The abstraction layer is used, so that you could program the real methods in another language. All they need to comply to is the interface definition given, namely to return a MoaError and to accept the parameters.
This part glues some of our code (namely the “Register” method in layer 3) to Director (layer 1) by defining the abstract way to initialize the Xtra.
Here is the function header of the “Register” method in the Xtra code (to be found in “Script.cpp” which holds our methods for the Xtra). Important as of now is just the fact that it does indeed return a MoaError value (which isn´t obvious because of the STDMETHODIMP macro), and that it does indeed accept the two parameters
PIMoaCache and
PIMoaXtraEntryDict which were described in the abstract layer definition.
PIMoaCache is a
Pointer to an
Interfaces for the MoaCache (?). PIMoaXtraEntryDict is a
Pointer to an Interface for the XtraEntry in the Director internal Dictionary in which all lingo command word symbols are stored.
/* ----------------------------------------------------------------------------- */
STDMETHODIMP TStdXtra_IMoaRegister::Register(
PIMoaCache pCache,
PIMoaXtraEntryDict pXtraDict
)
{
moa_try
PIMoaRegistryEntryDict pReg;
MoaBool bItsSafe = TRUE;
char versionStr[256];
PMoaVoid pMemStr = NULL;
[...]
But back to “script.h”. The following section is the rest of it:
/*****************************************************************************
* Xtra Moa Scripting Interface - Class definition
****************************************************************************/
EXTERN_BEGIN_DEFINE_CLASS_INTERFACE(TStdXtra, IMoaMmXScript)
EXTERN_DEFINE_METHOD(MoaError, Call, (PMoaDrCallInfo))
private:
// Methods that implement the Lingo commands.
// These are dispatched from "Call"
EXTERN_DEFINE_METHOD(MoaError, XScrpGlobalHandler, (PMoaDrCallInfo))
EXTERN_DEFINE_METHOD(MoaError, XScrpParentHandler, (PMoaDrCallInfo))
EXTERN_DEFINE_METHOD(MoaError, XScrpChildHandler, (PmoaDrCallInfo))
/*
* ---> insert additional method(s) -->
*/
EXTERN_END_DEFINE_CLASS_INTERFACE
#endif /* _H_Script */
This is where the bulk of the work happens. This part explains to Director which methods of our class we will be exposing by the Xtra.
Note the macro “EXTERN_BEGIN_DEFINE_CLASS_INTERFACE(TStdXtra, ImoaMmXScript)” at the beginning which is exactly the same as for the “register” interface. This block therefore defines the methods in our class that will be available to Director, including their return and parameter values, passed onto the member functions by the means of a PMOaDrCallInfo pointer.
This is a
Pointer to a
MoaDrCallInfo, which contains the whole lingo line itself, stored as member properties. We will cope with this later. Suffice to say, this thing holds the paramters to the lingo call.
The most important line for the Xtra is the next one:
EXTERN_DEFINE_METHOD(MoaError, Call, (PmoaDrCallInfo))
is our main dispatcher. This is the main routine for Director. Any lingo call to our Xtra will be send through this method and dispatched to the right member class functions.
Again, this is just the mere abstract definition of the function itself, not the real code. This just tells Director that we want to glue our “Call” method to its communications. All calls to our Xtra from Lingo will be passed through this method, which then will select which functions to call in our class in order to handle things correctly.
This is therefore the place where the logic between Director and our Xtra gets effectively tied together. Without this line, there is no communication between the application and the Xtra.
By the way, this is what the defined counterpart “Call” looks like in “Script.cpp”:
/* ----------------------------------------------------------------------------- */
STDMETHODIMP TStdXtra_IMoaMmXScript::Call (PMoaDrCallInfo callPtr)
{
moa_try
switch ( callPtr->methodSelector )
{
case m_new:
{
/* Setup any instance vars for you Xtra here. new() is
[...]
Note again that the function header matches against the definition in the macro: “Call” does indeed just take one parameter, and it is indeed of type PMOaDrCallInfo.
Back again to “script.h”: after defining the “Call” dispatcher method, there are 3 macro definitions for private methods:
EXTERN_DEFINE_METHOD(MoaError, XScrpGlobalHandler, (PMoaDrCallInfo))
EXTERN_DEFINE_METHOD(MoaError, XScrpParentHandler, (PMoaDrCallInfo))
EXTERN_DEFINE_METHOD(MoaError, XScrpChildHandler, (PMoaDrCallInfo))
These define 3 types of methods:
For the rest of this tutorial we will only be dealing with global methods, i.e. globally available lingo commands.
To sum things up so far:
- we defined a new Xtra by providing a new GUID in “script.h”
- we told Director to use the member function “Register” to register the Xtra with the application
- we also wired up the “Call” method through which Director will be passing any lingo call to our code, again by the means of a macro
- we also provided a list of available mebber functions in our code which can be reached from the main “Call” dispatcher
This list is pretty much all there is to be done in the abstration layer itself. From now on, we will deal with the code to be called when a new lingo command gets issued in layer 3.
Actually, the “skeleton2” project we are looking at does already implement lingo commands, including one global handler called, well, “globalHandler”.
Compile the whole project by hitting F7. The result should be a file called “Script.x32” in the “C:\xdk85_win\examples\Script\skeleton2\winproj\Debug” folder.
Either copy the file to the Director Xtras directory, or just a shortcut to it, either one works.
Start up Director and open the message window. Type “globalHandler”. This is what you will get back:
-- Weclome to Director --
globalHandler
GlobalHandler called
The second line was written by Director, therefore there must be code somewhere which handles the call.
When you type in the new lingo handler name, Director directs the call to the dispatching method defined for our Xtra. This would be the method “Call” in this example. It also passes a pointer to an object on, which holds the parameters passed to the call.
Let´s now have a closer look at the “Call” implementation in “Script.cpp” in order to get an idea how calls to the Xtra are handled:
/* ----------------------------------------------------------------------------- */
STDMETHODIMP TStdXtra_IMoaMmXScript::Call (PMoaDrCallInfo callPtr)
{
moa_try
switch ( callPtr->methodSelector )
{
case m_new:
{
/* Setup any instance vars for you Xtra here. new() is
called via Lingo when creating a new instance. */
/*
* --> insert additional code -->
*/
}
break;
/* Here is where new methods are added to the switch statement. Each
method should be defined in the msgTable defined in and have a
constant defined in the associated enum.
*/
case m_globalHandler:
ThrowErr(XScrpGlobalHandler(callPtr));
break;
case m_parentHandler:
ThrowErr(XScrpParentHandler(callPtr));
break;
case m_childHandler:
ThrowErr(XScrpChildHandler(callPtr));
break;
/*
* --> insert additional methodSelector cases -->
*/
}
moa_catch
moa_catch_end
moa_try_end
}
The function header is
STDMETHODIMP TStdXtra_IMoaMmXScript::Call (PMoaDrCallInfo callPtr)
Which means that “Call” gets implemented as STDMETHODIMP. I am not quite sure, but i think that this ensures that in the case of an error, a MoaError value is returned.
The function is then declared as being part of the TstdXtra_IMoaMmXScript class.
It accepts a parameter of type PMoaDrCallInfo, which furtheron is to be called “callPtr” (for “call pointer”), thus a pointer to the parameters which were provided for the method call.
The function body is the actual code to be executed when dispatching the call. It is embedded in a block of macros:
moa_try
[...]
moa_catch
moa_catch_end
moa_try_end
This try-catch block is there to make sure that an error won´t stop the code from executing.
The code enters a switch statement, in which gets evaluated what lingo method has been called. This is done by testing the value for the property “methodSelector” of the object that “callPtr” is pointing at.
Therefore, the callPtr does not only hold the parameters passed to the lingo call itself, but also which command was actually called.
Or, to state it otherwise, Director passes the whole lingo call to the Xtra and let it handle it. (“Here is something i don´t understand, but i know that it is for you. Deal with it.”)
Since we don´t know yet which of our lingo methods have been called (and thus which code of our class needs to be executed), the callPtr needs to show us the value of the methodSelector property.
[You derive a property from an object you hold a pointer to in C by doing “->”, which is the same as “object.property” in lingo.]
Depending on the value provided in the methodSelector property, the code branches to different member functions in the class:
switch ( callPtr->methodSelector )
{
case m_new:
{
/* Setup any instance vars for you Xtra here. new() is
called via Lingo when creating a new instance. */
/*
* --> insert additional code -->
*/
}
break;
/* Here is where new methods are added to the switch statement. Each
method should be defined in the msgTable defined in and have a
constant defined in the associated enum.
*/
case m_globalHandler:
ThrowErr(XScrpGlobalHandler(callPtr));
break;
case m_parentHandler:
ThrowErr(XScrpParentHandler(callPtr));
break;
case m_childHandler:
ThrowErr(XScrpChildHandler(callPtr));
break;
/*
* --> insert additional methodSelector cases -->
*/
}
As you can see, the methodSelector can have the following values:
- m_new
- m_globalHandler
- m_parentHandler
- m_childHandler
All calls are pretty much the same (except the first one, which is not interesting right now).
They are throwing an error exception with the value returned from the methods functions they call. Let´s look at the globalHandler definition, which gets called when the “globalHandler” lingo command gets invoked:
case m_globalHandler:
ThrowErr(XScrpGlobalHandler(callPtr));
break;
If the call to XscrpGlobalHandler() returns something other than 0, the return code is thrown as an error code. The callPtr passed from Director is also forwarded to the member function to be called, so that it can be evaluated.
Therefore, our next stop is the called member function “XscrpGlobalHandler” in “Script.cpp”, which is as part of the TstdXtra-Class (marked red in the code fragment below):
/*****************************************************************************
* Private Methods
* ------------------
* Implementation of Private Class Methods
*
* This is the actual code for the defined methods. They are defined as
* functions, and called from the switch statement in Call().
*
****************************************************************************/
/* ----------------------------------------------------- XScrpGlobalHandler */
MoaError TStdXtra_IMoaMmXScript::XScrpGlobalHandler(PMoaDrCallInfo pCall)
{
moa_try
ThrowErr(pObj->pMoaUtils->PrintMessage("GlobalHandler called\n"));
/*
* --> insert additional code -->
*/
moa_catch
moa_catch_end
moa_try_end
}
As you can see, this is where Director is answering when we type “globalHandler” in Lingo. Again, the code is encapsulated in a moa_try-catch block so that the code won´t stop from executing should there occur an error.
The main line of interest is of course:
ThrowErr(pObj->pMoaUtils->PrintMessage("GlobalHandler called\n"));
pObj is unknown to us so far. It is simply the pointer which points to the the current object instance of the TStdClass. From it, we look for the pMoaUtils property variable:
pObj->pMoaUtils...
We defined “pMoaUtils” earlier as a class instance variable in “script.h”:
EXTERN_BEGIN_DEFINE_CLASS_INSTANCE_VARS(TStdXtra)
PIMoaMmUtils2 pMoaUtils;
PIMoaMmValue pValueInterface;
/*
* ---> insert additional variable(s) -->
*/
EXTERN_END_DEFINE_CLASS_INSTANCE_VARS
So we learn that PMoaUtils, which is of type PIMoaMmUtils2, ergo a
Pointer to the
Interface to the
MoaMmUtils2, offers a method called “PrintMessage”. It apparently allows us to print a string to the Director message window.
This method must also be documented in the XDK documentation, and sure enough it is.
Excerpt from XDK documentation at C:\xdk85_win/Docs/mmref/mmutils2.htm#PrintMessage
PrintMessage
Description
Prints the message pMsg in the host application debugging or message window [...]
The text provided is returned to the message window and showed, then execution returns from the function, which terminates the call to the Xtra.
Summary
When a lingo call to an Xtra is made, the code execution follows this path:
- the lingo call gets entered and parsed
- Director tries to match the lingo command to an Xtra
- the main dispatching method of the Xtra gets called and receives a filled PMoaDrCallInfo which contains the command being called, as well as the paramters specified
- the dispatcher evaluates the “methodSelector” property value and matches it agains the corresponding member function
- the member function gets called and receives the PmoaDrCallInfo info pointer as well
- the member functions executes, and eventually returns results to Lingo and an internal MoaError value if anything went wrong
- the code execution returns to Director
IV. How to implement your own global lingo commands
So far, we just drilled down the existing code. Now, we would like to add our own lingo commands to Director. For this, we need to do several things:
- we need to decide about the lingo commands to add,
- and then implement the abstraction layer and
- the programming itself.
You probably want to preserve the “skeleton2” example, which is why I recommed you to save the current project under a different name, e.g. “MyXtra”.
In this example, we will be implementing two simple global commands:
- GetActiveWindow()
- FocusWindow(int winhandle)
GetActiveWindow will get us the window handle to the active Window by returning an integer. The windows handle is a unique number which identifies the currently active (=selected) window.
FocusWindowwill be a function that takes an integer number and activates the window with the corresponding windows handle, effectively bringing it to the front.
In order to implement these two functions, we need to:
- add a descriptive entry into the message table of the Xtra so that users can see what commands are available by issueing:
put interface (xtra “script”)
- implement two member functions methods that actually “do” what we want
- wire up those two functions with the dispatcher in the application so that they can get called from Lingo (this actually involves several sub-steps)
All of this will be done in “Script.cpp” our main implementation file, so let´s look at it:
/*
Copyright (c) 1998-2001 Macromedia, Inc. All Rights Reserved
You may utilize this source file to create and compile object code for use within products
you may create. THIS CODE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, AND
MACROMEDIA DISCLAIMS ALL IMPLIED WARRANTIES INCLUDING, BUT NOT LIMITED TO, MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT WILL MACROMEDIA BE
LIABLE TO YOU FOR ANY CONSEQUENTIAL, INDIRECT OR INCIDENTAL DAMAGES ARISING OUT OF YOUR
USE OR INABILITY TO USE THIS CODE.
*/
#define INITGUID 1
#include "script.h"
#include <string.h>
#include <stdlib.h>
#include "xclassver.h"
#include "moatry.h"
#include "driservc.h"
#include "drivalue.h"
#include "mmivalue.h"
#include "mmillist.h"
#include "mmiplist.h"
#include "mmidate.h"
#include "mmiclr.h"
/*******************************************************************************
* SCRIPTING XTRA MESSAGE TABLE DESCRIPTION.
*
* The general format is:
* xtra <nameOfXtra>
* new object me [ args ... ]
* <otherHandlerDefintions>
* -
* The first line must give the name of the Scripting xtra.
* The remaining lines give the names of the handlers that this xtra implements
* along with the required types of the arguments for each handler.
*
* -- Pre-defined handler new
* The new handler will be called when a child object is created,
* the first argument is always the child object and you defined any remaining arguments.
* The new handler is the place to initialize any memory required by the child object.
*
* -- Simple Handler Definitions
* Each handler definition line is format like this:
* <handlerName> <argType1> <argName1>, <argType2> <argName2> ...
* The first word is the handler name. Following this are types description for
* the argument to the handler, each separated by a comma.
* The argument name <argName>, may be omited.
* Permited argument types are:
* integer
* float
* string
* symbol
* object
* any
* *
* For integer, float, string, symbol, and object, the type of the argument must
* match. The type any means allow any type. The asterisk (*) means any number and
* any type of arguments.
*
* The first argument is the child object and is always declared to be of type object.
*
* -- Global Handlers
* An asterisk (*) preceeding the handler name signifies a global handler.
* This handler is at the global scope level and can be called from any
* movie.
*
* -- Xtra level handlers
* A plus (+) preceeding the handler name signifies an Xtra level handler.
* This kind of handler can be called directly from the Xtra reference,
* without creating a child object.
*
* The enumerated list that follows must correspond directly with the msgTable
* (i.e. they must be in the same order).
*
*******************************************************************************/
/* This is the list of handlers for the xtra. The versionInfo string is combined
/* with the msgTable string in the register method to create a single string that
/* used when registering the xtra as a scripting xtra. */
static char versionInfo[] = "xtra Script -- version %s.%s.%s\n";
static char msgTable[] = {
"new object me\n" /* standard first handler entry in all message tables */
"-- Template handlers --\n"
"* globalHandler -- prints global handler message\n"
"+ parentHandler object xtraRef -- prints parent handler message\n"
"childHandler object me, integer number -- prints child handler message, returns a number\n"
/*
* ---> insert additional handler(s) MUST MATCH WITH ENUMS BELOW -->
*/
};
/* This is the enumerated scripting method list. This list should
* directly correspond to the msgTable defined above. It is used
* to dispatch method calls via the methodSelector. The 'm_XXXX' method must
* be last.
*/
enum
{
m_new = 0, /* standard first entry */
m_globalHandler,
m_parentHandler,
m_childHandler,
/*
* ---> insert additional names(s) MUST MATCH MESSAGE TABLE ABOVE -->
*/
m_XXXX
};
...
Our first stop is the message table of the Xtra, which is where we will be adding text to explain the new functions to Lingo users.
As you can see, the comments in the code describe pretty well how to add our info to the message table of the Xtra, so can start to change code accordingly (changes are in bold):
/* This is the list of handlers for the xtra. The versionInfo string is combined
/* with the msgTable string in the register method to create a single string that
/* used when registering the xtra as a scripting xtra. */
static char versionInfo[] = "xtra Script -- version %s.%s.%s\n";
static char msgTable[] = {
"new object me\n" /* standard first handler entry in all message tables */
"-- Template handlers --\n"
"* GetActiveWindow -- returns winhandle of active window\n"
"* FocusWindow integer -- focus the window integer, returns true or false\n"
};
This part is the informative bit for the user, but also gets evaluated by the code, which is why our method defintions here MUST ABSOLUTELY MATCH what is really needed!
We now need to wire the new lingo functions to the corresponding functions in our class. This is done in several places. We first do this by first adding new values to the enum structure below the message table we just changed:
/* This is the enumerated scripting method list. This list should
* directly correspond to the msgTable defined above. It is used
* to dispatch method calls via the methodSelector. The 'm_XXXX' method must
* be last.
*/
enum
{
m_new = 0, /* standard first entry */
m_getActiveWindow, /* get Active Window handle */
m_focusWindow, /* focus Window */
m_XXXX /* must be last entry */
};
This tells the Xtra that there are two new code paths, which can be reached for Lingo. It does not know how to reach them though.
This is done by adjusting the choices available in the switch statement of the “Call” function that dispatches the lingo calls to the according member functions. As of now, it looks like this:
/* ----------------------------------------------------------------------------- */
STDMETHODIMP TStdXtra_IMoaMmXScript::Call (PMoaDrCallInfo callPtr)
{
moa_try
switch ( callPtr->methodSelector )
{
case m_new:
{
/* Setup any instance vars for you Xtra here. new() is
called via Lingo when creating a new instance. */
/*
* --> insert additional code -->
*/
}
break;
/* Here is where new methods are added to the switch statement. Each
method should be defined in the msgTable defined in and have a
constant defined in the associated enum.
*/
case m_globalHandler:
ThrowErr(XScrpGlobalHandler(callPtr));
break;
case m_parentHandler:
ThrowErr(XScrpParentHandler(callPtr));
break;
case m_childHandler:
ThrowErr(XScrpChildHandler(callPtr));
break;
/*
* --> insert additional methodSelector cases -->
*/
}
moa_catch
moa_catch_end
moa_try_end
}
It should be changed to reflect the new symbols added to the enum structure:
STDMETHODIMP TStdXtra_IMoaMmXScript::Call (PMoaDrCallInfo callPtr)
{
moa_try
switch ( callPtr->methodSelector )
{
case m_new:
{
/* Setup any instance vars for you Xtra here. new() is
called via Lingo when creating a new instance. */
/*
* --> insert additional code -->
*/
}
break;
case m_getActiveWindow:
ThrowErr(GetActiveWindow(callPtr));
break;
case m_focusWindow:
ThrowErr(FocusWindow(callPtr));
break;
}
moa_catch
moa_catch_end
moa_try_end
}
The next step would be to actually implement the two member functions methods we are trying to call, namely FocusWindow() and GetActiveWindow() in the TstdXtra-class.
Look for this comment block further down in the file:
/*****************************************************************************
* Private Methods
* ------------------
* Implementation of Private Class Methods
*
* This is the actual code for the defined methods. They are defined as
* functions, and called from the switch statement in Call().
*
****************************************************************************/
You will recognize the implementations for the three different handler methods cited before, global, parent and child. Since we will be replacing them with our own code, you can delete anything below the commented block until the end of the file.
This will give you a fresh start for your implementation of the two methods. Let´s start with the Implementation of GetActiveWindow first:
STDMETHODIMP TStdXtra_IMoaMmXScript::GetActiveWindow(PMoaDrCallInfo callPtr)
{
moa_try
MoaMmHWnd activWnd; // activWnd = active Window handle returned by Windows
///////////////////////////////////////////
// get the winhandle of the active window//
///////////////////////////////////////////
activWnd=(MoaMmHWnd)::GetFocus();
///////////////////////////////////////////
// return it to Lingo as simple integer ///
///////////////////////////////////////////
ThrowErr(pObj->pValueInterface->IntegerToValue((MoaLong)activWnd, &callPtr->resultValue));
moa_catch
moa_catch_end
moa_try_end
}
Enclosed in a try-catch block, we first define a local variable called activWnd, which will be holding active window handle as returned by the Windows API.
In order to get the active Window handle, we then call the global windows API method “GetFocus()”, which returns the handle to the currently focussed window. This gets immediately casted into a MoaMmHwnd value (a macromedia equivalent of HWND, the type of which windows handles are), and stored in the activWnd variable.
The next block is already about returning the value to Lingo.
Coming from the class object pObj, i call the member variable “pValueInterface”, which has been defined in “Script.h” earlier as being of type PIMoaMmValue, thus a
Pointerto the
Interfacefor MoaMmValue(s):
EXTERN_BEGIN_DEFINE_CLASS_INSTANCE_VARS(TStdXtra)
PIMoaMmUtils2 pMoaUtils;
PIMoaMmValue pValueInterface;
EXTERN_END_DEFINE_CLASS_INSTANCE_VARS
Looking up the interface “PIMoaMmValue” (at C:\xdk85_win\Docs\mmref\mmvalue.htm) in the XDK reveals the following:
IMoaMmValue
Interface ID: IID_IMoaMmValue
Pointer type: PIMoaMmValue
Inheritance: IMoaUnknown
Header file: mmivalue.h
Description
This interface provides support for converting C and MOA types to values that can be passed through the IMoaMmPropertyOwner
interface and elsewhere in the multimedia API. The MoaMmValue
type provides a general, platform and application-independent mechanism for moving data of various types between an Xtra and an application.
So when our class was instantiated, we also got a variable which holds a pointer to this important interface. It allows us to move data back and forth between C and the Director application.
Back to the code:
Err(pObj->pValueInterface->IntegerToValue((MoaLong)activWnd, &callPtr->resultValue));
From pObj->pValueInterface, we derive the function pointer to the method “IntegerToValue”, which, according to the the XDKdocs, does the following:
IntegerToValue
Description
Creates a new integer-type MoaMmValue
from a MoaLong
. num contains the MoaLong
to be used as the basis for the new value. pValue contains a pointer to a MoaMmValue
to receive the result. This call populates the MoaMmValue
at pValue with a new MoaMmValue
, overwriting any current value. Make sure to release any preexisting value before making this call. The caller is responsible for releasing the returned value using IMoaMmValue::ValueRelease()
.
To this, we pass<
... IntegerToValue((MoaLong)activWnd, &callPtr->resultValue));
Together, this becomes a combined call for conversion and result.
First, it casts the window handle to a MoaLong value, as this is needed according to the method description above. Then, it passes “resultValue” property as the second parameter (&callPtr->resultValue is equivalent with resultValue itself).
The method itself converts the value passed to a MoaValue and stores the result in the resultValue property of the object callPtr is pointing to. ResultValue is then passed on to the message window or the lingo execution engine. This is how results get returned to Lingo.
V. Implementing a function that takes a parameter
The second function to be implemented is FocusWindow(), and it takes an integer value, which represents the windows handle of the window to be focussed.
Here is the implementation, which is to be added right below the GetActiveWindow() one:
STDMETHODIMP TStdXtra_IMoaMmXScript::FocusWindow(PMoaDrCallInfo callPtr)
{
moa_try
MoaMmValue tempArg;
bool ok;
PMoaLong wndHandle=new MoaLong; // allocate memory for MoaLong data object
// and acquire pointer to it
HWND windowHandle=0;
/* Arg 1 is windows handle */
AccessArgByIndex(1, &tempArg);
ThrowErr(pObj->pValueInterface->ValueToInteger(&tempArg, wndHandle));
windowHandle=(HWND) *wndHandle;
ok=(::IsWindow(windowHandle) && ::SetFocus(windowHandle));
delete wndHandle; // clears the data object and pointer declared earlier
pObj->pValueInterface->IntegerToValue(ok, &callPtr->resultValue);
moa_catch
moa_catch_end
moa_try_end
}
Packed again into a moa_try-catch block, a couple of variables are declared at first:
- tempArg which is to hold the argument passed to the lingo command
- a boolean value which will show if focussing the window was sucessful
- a pointer to a MoaLong data object called wndHandle, which will hold the transformed paramter coming from Lingo
- and windowHandle, a HWND variable which will hold the true window Handle of the window to be set
The interesting part is how to get the paramter passed on coming from Lingo.
Example>:
You called the new method from lingo using the following line
FocusWindow(13843)
Using the global function
AccessArgByIndex(1, &tempArg);
You charge the variable “tempArg” with the value provided as the first paramter to the lingo command (“13843”). TempArg being of the unspecified type MoaMmValue, it will hold the generic data until it gets converted to a normal integer in the next line:
ThrowErr(pObj->pValueInterface->ValueToInteger(&tempArg, wndHandle));
This takes the value found at the address of tempArg, and converts it to an integer, then storing the result into “wndHandle”. It then gets casted to a real HWND, which is the variable type for windows handles under Window.
windowHandle=(HWND) *wndHandle;
Next, we do the actual work:
We probe the window handle passed for validity and then we try to set the focus to the window which owns this handle. The value returned is stored in a boolean variable called “ok”:
ok=(::IsWindow(windowHandle) && ::SetFocus(windowHandle));
Both IsWindow() and SetFocus() are global Windows API calls, which is why they are preceded by two colons in order to break out of the current class namespace.
The rest is to clean up the pointer declared, and to return the boolean value to Lingo as we did before for the GetActiveWindow() method:
delete wndHandle; // clears the data object declared earlier
pObj->pValueInterface->IntegerToValue(ok, &callPtr->resultValue);
We are done for the member function implementation. The code for our Xtra is written.
In order for you new methods to work, we need to declare them against Director in the last step. This is done in the header file for the Xtra, “Script.h”. You need to declare all available member functions to Director, including their function headers as done previously for the “Register” function:
[...]
/*****************************************************************************
* Xtra Moa Scripting Interface - Class definition
****************************************************************************/
EXTERN_BEGIN_DEFINE_CLASS_INTERFACE(TStdXtra, IMoaMmXScript)
EXTERN_DEFINE_METHOD(MoaError, Call, (PMoaDrCallInfo))
private:
// Methods that implement the Lingo commands.
// These are dispatched from "Call"
EXTERN_DEFINE_METHOD(MoaError, GetActiveWindow, (PMoaDrCallInfo))
EXTERN_DEFINE_METHOD(MoaError, FocusWindow, (PMoaDrCallInfo))
EXTERN_END_DEFINE_CLASS_INTERFACE
#endif /* _H_Script */
As you can see, we declare GetActiveWindow and FocusWindow to both accept the PmoaDrCallInfo and to return a MoaError value.
That´s it. Compile the new Xtra, copy it to the Xtras subfolder of your Director installation directory and fire up the application.
In the message window, type:
put getActiveWindow()
-- 1443136
put FocusWindow(1443136)
-- 1
Note:
the window id returned from your call to getActiveWindow() will be different.
Summary:
In order to add your own code to an Xtra, you need to ...
- adapt the message table in the dispatching function by adding new names for the new lingo commands
- adapt the enum structure with new symbols for your new methods
- then modify the switch statement in your dispatching function to recognize the new values
- hook up the new values with the corresponding member functions of your class that are supposed to do the work
- actually program the new member functions
- then let the header file know about the new private member functions that are available (in the block right beneatch the main dispatcher interface definition)
- compile and run the new Code
VI. Layer Discussion
Xtra programming involves working at 3 different layers:
- Application level (Director)
- Abstraction level (registration and wiring of the Xtra)
- Implementation level (actual code)
Each level offers and receives something from the underlying layer.
1.) Application Level (Director)
Director offers a main hook into the application, which, in the skeleton2 example, gets set up for you automatically in the variable pObj, from which you derive all the needed interfaces in your programming.
These defined interfaces allow you to work with its internal data structures like movies, casts, members and sprites, but also offer other services like data type conversion and file path resolution.
Each of this interfaces offers methods you can access. You can therefore think of interfaces as “groups” of methods related to one specific topic.
Example:
one of the most needed interface is probably the ImoaMmValue interface, used to convert and transfer data types back and forth from the Xtra to Director and vice-versa. From the XDK API description:
IMoaMmValue
Interface ID: IID_IMoaMmValue
Pointer type: PIMoaMmValue
Inheritance: IMoaUnknown
Header file: mmivalue.h
Description
This interface provides support for converting C and MOA types to values that can be passed through the IMoaMmPropertyOwner
interface and elsewhere in the multimedia API. The MoaMmValue
type provides a general, platform and application-independent mechanism for moving data of various types between an Xtra and an application.
Note the last sentence: MoaMmValue is just one generic container for all kinds of data taypes for Director. This means that you can stuff your data into a MoaMmValue using methods of this interface, and Director will recognize and automatically convert it to an according lingo type.
When you program the XDK, you first acquire one main hook into the Director main application, which is itself an interface. On this main interface, you then query references to the rest of the interfaces you are interested in and usually maintain variables which hold pointers to them.
Director therefore gives you access to the application and all the objects and methods needed to work with them. It also sends your Xtra the parameters which were specified when a call to your Xtra was invoked.
Director takes the return values from your Xtra and returns them to Lingo.
2.) Abstraction Level (MOA)
The abstraction layer is the middle layer between Director and your code. It explains to Director what kind of code you will be using and standardizes it at the same time.
This is because MOA is supposed to be platform independend: if you code for the Mac or Windows version of Director shouldn´t matter, at least not at this level. Platform specific code is to be written below the abstration layer, in your actual implementation.
There is also another interesting point in this: MOA is language independend. It doesn´t matter if you implement your code in C/C++ or, as for the famous BuddyAPI Xtra for instance (
http://www.mods.com.au) in Delphi/Pascal. This is why you always have to convert back and forth between your data types and the MOA ones when talking with the Xtra.
As long as your code complies to what you have defined in the MOA Layer, you are free to use whatever language you want.
The abstraction layer provides the following services to Director:
- wiring of the registering function to be used for the Xtra registration, incl the ID to be used for registering the class of the Xtra
- wiring of the main dispatcher function in your Xtra code (“where to route the calls”)
- declaring all your member functions to be used to Director
The abstraction layer however is merely transparent most of the times.
3.) Implementation level
This is were things happen. Your code must comply to a couple of conventions for the MOA abstraction layer though, namely ...
- to have a MoaCreate and MoaDestroy() method for instantiating/destroying the Xtra in order to acquire the needed interfaces from the main application callback
- to have a registering method which registers the Xtra (note: this is were you can place startup code)
- to have a dispatching method which will receive info from the app and route the call to the according member function which is to handle the request
- to explain and define the new lingo commands to be implemented
The call from the application is routed through the dispatchign function, which then branches to the according member function in your code. The calling information is passed as well (as a pointer), so that you can get to optional parameters.
Your code then does its thing, then eventually populates the returnValue of the pointer passed to you, which is how you pass data back to Director.
VII. Putting the knowledge to work
As a programming exercise, we will try to return all different lingo data types to Director coming from the XDK. We will return:
- Floats
- Strings
- Symbols
- Points and Rects
- Linear Lists and Property Lists
[Vectors, transforms and objects are omitted for the moment, to be added later.]
1. Returning Floats
We want a float to be returned from ActiveWindow(), thus we look up the methods available by the interface and find FloatToValue(), which fits our needs.
The handle of the active Window is stored in a MoaMmHWnd variable. This must be cast to an integer first, and then to a MoaDouble value, as needed by the method:
pObj->pValueInterface->FloatToValue((MoaDouble)(int)activWnd, &callPtr->resultValue);
Or (with implicitly understood cast from (int)activWnd to a MoaDouble):
pObj->pValueInterface->FloatToValue((int)activWnd, &callPtr->resultValue);
Just stuffing a MoaDouble in this method will convert the value to a float in lingo. Compile and test:
put getActiveWindow()
-- 394700.0000
Indeed, a float is returned.
2. Returning Strings
Since that was so much fun, how about returning a string? Let´s look up StringToValue() in the XDK:
StringToValue()
Syntax
StringToValue(PIMoaMmValue This,
ConstPMoaChar pString,
PMoaMmValue pValue)
Parameters
This
Pointer to the IMoaMmValue
interface
pString
ConstPMoaChar
Pointer to a null-terminated string used for the value
pValue
PMoaMmValue
Pointer to a MoaMmValue
that receives the result
We need to provide a const pointer to a null terminated string to the function. What we want to put out is a string of a WndHandle, which in return is a simple int. Thus, before passing out the return value, we need to convert it to a string using the C-function sprintf().
char buf[20]; // make char array to hold converted int
sprintf(buf, "%d", (int)activWnd); // convert int to char array (=string)
const PMoaChar pString=buf; // assign char array to pString
pObj->pValueInterface->StringToValue(pString , &callPtr->resultValue));
delete pString();
In Director, this yields to:
put getActiveWindow()
-- "1377944"
3. Returning Symbols
SymbolToValue()
Syntax
SymbolToValue(PIMoaMmValue This,
MoaMmSymbol symbol,
PMoaMmValue pValue)
Parameters
This
Pointer to the IMoaMmValue
interface
symbol
MoaMmSymbol
The symbol to translate
pValue
PMoaMmValue
Pointer to a MoaMmValue
to receive the result
Returns
MoaError
Description
Creates a new symbol-type MoaMmValue
from a MoaMmSymbol
. This call populates the MoaMmValue
at pValue with a new MoaMmValue
, overwriting any current value. Make sure to release any preexisting value before making this call. The caller is responsible for releasing the returned value using IMoaMmValue::ValueRelease()
.
Symbols must not start with a numeric value, which is why I changed the return value for this example.
Returning a symbol to Lingo involves 3 steps:
- creating a string of the symbol to be
- converting the string to a symbol ( StringToSymbol())
- passing the symbol back to lingo (SymbolToValue())
In C++ Code:
MoaMmSymbol symb; // create a MoaMmSymbol value
MoaChar buf [20]="test"; // create an array of chars with
// the string to be used for the symbol
///////////////////////////////////////////////////////////////
//convert string to symbol and store at symb´s address
///////////////////////////////////////////////////////////////
ThrowErr(pObj->pValueInterface->StringToSymbol(buf, &symb));
///////////////////////////////////////////////////////////////
//convert the symbol to a MoaMmValue and hand it back to Lingo
///////////////////////////////////////////////////////////////
ThrowErr(pObj->pValueInterface->SymbolToValue(symb, &callPtr->resultValue));
In Director, this yields to:
put getActiveWindow()
-- #test
4. Returning Points and Rects
A point() is a lingo data type which describes the position of a sprite or registration point for a member. This is one way to return a point to lingo from your member function (i am sure there are better ones) using PointToValue():
PointToValue()
Syntax
PointToValue(PIMoaMmValue This,
ConstPMoaPoint pPoint,
PMoaMmValue pValue)
Parameters
This
Pointer to the IMoaMmValue
interface
pPoint
ConstPMoaPoint
Pointer to a ConstPMoaPoint
used for the new value
pValue
PMoaMmValue
Pointer to a MoaMmValue
to receive the result
Returns
MoaError
Description
Creates a new point-type MoaMmValue
from a MoaPoint
. On entry, pPoint contains a pointer to a MoaPoint
to be used as the basis for the new value. pValue contains a pointer to a MoaMmValue
to receive the result. This call populates the MoaMmValue
at pValue with a new MoaMmValue
, overwriting any current value. Make sure to release any preexisting value before making this call. The caller is responsible for releasing the returned value (see IMoaMmValue:: ValueRelease()
).
In Code:
MoaPoint point;
point.x =(int)activWnd;
point.y =(int)activWnd;
ThrowErr(pObj->pValueInterface->PointToValue(&point, &callPtr->resultValue));
We define a MoaPoint variable named point. Since MoaPoint is defined as a struct like this:
typedef struct MoaPoint {
MoaCoord y;
MoaCoord x;
} MoaPoint;
We can set point.x and point.y. MoaCoord are based on MoaLong, which in return are based on 32bit “long” values on Windows.
We are abusing the active Window Handle as the two values for the xy-position of the point. Once the Xtra is compiled, this yields to the following Lingo:
put getActiveWindow()
-- point(1573986, 1573986)
Same goes for rects:
MoaRect rect;
rect.left =(int)activWnd;
rect.top =(int)activWnd;
rect.right =(int)activWnd;
rect.bottom =(int)activWnd;
ThrowErr(pObj->pValueInterface->RectToValue(&rect, &callPtr->resultValue));
Since MoaRect is defined as a struct:
typedef struct MoaRect {
MoaCoord top;
MoaCoord left;
MoaCoord bottom;
MoaCoord right;
} MoaRect;
we can set top, left, bottom and right of the newly defined rect variable.
In Lingo, this becomes:
put getActiveWindow()
-- rect(591152, 591152, 591152, 591152)
5. Returning Linear Lists or Property Lists
In order to return lists to lingo, we need to get hold of another interface first, called
IMoaMmList
Interface ID: IID_IMoaMmList
Pointer type: PIMoaMmList
Inheritance: IMoaUnknown
Header file: mmiservc.h
Description
Lists are a type of value used to represent a collection of elements. Lists can contain elements of multiple types, so it is possible to have a single list containing any combination of integer, float, string, and other values. In general, there are two types of lists: linear and property. Linear lists contain an ordered sequence of values which are referred to by index. Property lists contain a sequence of propertyName value pairs. Elements of a property list are referred to by property name.
A list is a type of MoaMmValue
. Since lists are MoaMmValues, they can be used as properties of objects (such as assets and sprites) obtained and set using the standard IMoaMmPropowner::GetProp()
and SetProp()
methods, just like strings, integers, and other simple value types. The elements of lists are also MoaMmValues
. Thus, a list can itself contain lists (sublists).
Thus, we need to define another helper variable which will hold the pointer to the list interface in script.h:
DEFINE_GUID(CLSID_TStdXtra,
0x8109329e, 0x9121, 0x4033, 0x99, 0x26, 0xfa, 0xaa, 0xde, 0xdd, 0x57, 0x2e);
EXTERN_BEGIN_DEFINE_CLASS_INSTANCE_VARS(TStdXtra)
PIMoaMmUtils pMoaUtils;
PIMoaMmValue pValueInterface;
PIMoaMmList pMmList;
/*
* ---> insert additional variable(s) -->
*/
EXTERN_END_DEFINE_CLASS_INSTANCE_VARS
So far however, we have never used a new interface. We silently relied on the fact that the interfaces we use will be acquired for us.
This can´t be assumed for the new one any more, we have to get it by our own and also dispose of it at the end.
Interfaces are acquired in MoaCreate() and disposed in MoaDestroy() of script.cpp:
/* ============================================================================= */
/* Create/Destroy for class TStdXtra */
/* ============================================================================= */
STDMETHODIMP_(MoaError) MoaCreate_TStdXtra (TStdXtra FAR * This)
{
moa_try
ThrowErr (This->pCallback->QueryInterface(&IID_IMoaMmValue, (PPMoaVoid) &This->pValueInterface));
ThrowErr (This->pCallback->QueryInterface(&IID_IMoaMmUtils2,(PPMoaVoid) &This->pMoaUtils));
ThrowErr (This->pCallback->QueryInterface(&IID_IMoaMmList, (PPMoaVoid) &This->pMmList));
moa_catch
moa_catch_end
moa_try_end
}
Using the main callback hook into the Director application, we query the available interfaces, providing the ID for the ImoaMmList interface we are interested in. It then gets stored in the class instance variable pMmList we defined earlier.
This is how your Xtra gets hold of the interfaces: one main interface hooks into Director, and this gets queried for other interfaces, specified by their respective IDs.
Once the Xtra gets disposed, we also need to release the acquired interface:
STDMETHODIMP_(void) MoaDestroy_TStdXtra(TStdXtra FAR * This)
{
moa_try
if (This->pValueInterface != NULL)
ThrowErr (This->pValueInterface->Release());
if (This->pMoaUtils != NULL)
ThrowErr (This->pMoaUtils->Release());
if (This->pMmList != NULL)
ThrowErr (This->pMmList->Release());
moa_catch
moa_catch_end
moa_try_end_void
}
We can now create our list to be returned to Lingo. Back in the ActiveWindow() member function of script.cpp:
STDMETHODIMP TStdXtra_IMoaMmXScript::GetActiveWindow(PMoaDrCallInfo callPtr)
{
moa_try
MoaMmHWnd activWnd; // activWnd to hold active Window handle
// returned by Windows
MoaMmValue tempValue; // tempValue will hold temporary copies of
// our return values
///////////////////////////////////////////
// get the winhandle of the active window//
///////////////////////////////////////////
activWnd=(MoaMmHWnd) ::GetFocus();
// make new list
pObj->pMmList->NewListValue(&callPtr->resultValue);
// convert all the values you want to return to a MoaMmValue
// and append them to the list
pObj->pValueInterface->IntegerToValue((int)activWnd, &tempValue);
// add a reference to our value to the list
pObj->pMmList->AppendValueToList(&callPtr->resultValue, &tempValue);
// call this method as many times as you need to add elements to the list
// release the tempArg variable value
pObj->pValueInterface->ValueRelease(&tempValue);
moa_catch
moa_catch_end
moa_try_end
}
The code comments describe the way:
- create a new list first – a list is also a MoaMmValue, which is why we can write it directly into our returnValue to be returned to Lingo
- convert the values to be added to the list to MoaMmValues (here an int) and store it somewhere
- add the somwhere stored MoaMmValue to the list using AppendValueToList()
- repeat previous step as many times needed for your values to be returned
- release the stored value(s) at the end
In Director, this yields to something like:
put getActiveWindow()
-- [312802]
Returning a property list to Lingo is nearly as easy, but requires a bit more work:
STDMETHODIMP TStdXtra_IMoaMmXScript::GetActiveWindow(PMoaDrCallInfo callPtr)
{
moa_try
MoaMmHWnd activWnd; // activWnd to hold active Window handle
// returned by Windows
MoaMmValue tempArg; // tempArg will hold temporary copies of
// our return values
MoaMmValue tempArg2; // another temp variable, to hold symbol
MoaMmSymbol symb; // symbol for prop list
///////////////////////////////////////////
// get the winhandle of the active window//
///////////////////////////////////////////
activWnd=(MoaMmHWnd) ::GetFocus();
// convert all the values you want to return to a MoaMmValue
// and append them to the list
pObj->pValueInterface->IntegerToValue((int)activWnd, &tempArg);
// make new property list and write it directly to the output
pObj->pMmList->NewPropListValue(&callPtr->resultValue);
// create a symbol by converting a string to a symbol
pObj->pValueInterface->StringToSymbol((MoaChar *)"propertyName", &symb);
//convert the symbol to MoaMmValue
pObj->pValueInterface->SymbolToValue(symb, &tempArg2);
// add prop and value to proplist
pObj->pMmList->AppendValueToPropList(&callPtr->resultValue, &tempArg2, &tempArg);
// drop the values
pObj->pValueInterface->ValueRelease(&tempArg);
pObj->pValueInterface->ValueRelease(&tempArg2);
moa_catch
moa_catch_end
moa_try_end
}
Same as previously for linear lists, just a bit more work involved to create a symbol and to convert it into the MoaMmValue format needed for the AppendValueToPropList() method.
In Director:
put getActiveWindow()
-- [#propertyName: 1377246]
This closes the discussion on how to return all the different lingo data types to Lingo.
Vectors and transforms are missing, but these are specific to 3D programming. They are treated in the IMoa3dVectorValueUtils interface, which offers similar methods than the IMoaMmUtils2 interface (vectorToValue, ValueToVector, etc).