MOA Developer's Guide
MOA Interfaces | MOA Methods | MOA Types and Misc API

Director Xtra Development Kit: MOA Developer's Guide

Implementing MOA Xtras


Introduction

This chapter describes coding details for implementing MOA Xtras.

The first part describes how to code a MOA Xtra. It presents a complete coding example of an Xtra implementing the hypothetical Xtra interface, IMoaHello. This discussion includes details about how to code Xtras in both C and C++. The second section discusses some standard calling conventions used in implementing MOA Xtras. The final part provides a synopsis of the MOA standard interfaces.

Implementing a MOA Xtra

In general, there are three things you do to implement Xtras. First, you identify the interfaces to use--both the Xtra interfaces to implement and the callback interfaces to call in the application. Next, you define a class, specifying its name, instance variables, and the Xtra interfaces it implements. Finally, you write implementations for each method in those interfaces. Identifying an interface and defining a class both require MOA identifiers, which are discussed first. The sections that follow describe how to actually code MOA objects.

MOA identifiers

For a provider and user to agree on an interface, they need a common way to identify it. MOA uses MOA identifiers (type MoaID), an implementation detail shared with COM (which refers to them as globally unique identifiers or GUIDs). In addition to unique interface identifiers, MOA uses unique identifiers for each class. The class identifier provides a hook for an application to access your Xtra implementation.

MOA identifiers are large (128 bit) values unique to each interface and class. MOA identifiers are assigned to globals shared by the providers and users of an interface.

Your Xtra code uses MOA identifiers in several ways:

The various uses of MOA identifiers are discussed in the sections that follow. To define a MoaID for an interface or class, you use a standard utility program. On Macintosh OS 9 the program GenUID.app is provided with the MOA XDK for this purpose; on Macintosh OSX use the uuidgen command in the terminal window; on Windows use the Microsoft utility GUIDGEN.EXE, which is provided with the Visual C++ compiler.

Acquiring an interface

In implementing an Xtra, both the Xtra interfaces you provide and the callback interfaces you use are defined by Macromedia. Thus, Xtras can be programmed entirely without defining interfaces of their own. In some cases, you may choose to define a MOA interface to represent behavior shared by your objects but not specified by Macromedia. In any case, understanding how an interface is defined will help you understand how to implement a class.

Each interface has a distinct MoaID--referred to as the interface identifier or IID--used to acquire instances of the interface. Each interface has a name, used to represent the specific type of the interface. An interface also includes the names, parameters, and return types for its methods. The following example shows these details, looking at the definition of a simple interface--IMoaHello--with just one method, Hello().

Note that in MOA, as in COM, all interfaces are typed using a name with the prefix I. Interface types are used to identify an interface when it is specified as a parameter of a function or method, or as the type of a variable.

The following code demonstrates the minimal definition of a simple interface, IMoaHello. This code would appear in a .h file, for example moahello.h:

#include "moaxtra.h"
#define INTERFACE IMoaHello

DEFINE_GUID(IID_IMoaHello, 0xAC3590A6L, 0x0062, 0xE623, 0x00, 0x00, 
		0x08, 0x00, 0x07, 0x57, 0xFC, 0x90);

DECLARE_INTERFACE_(IMoaHello, IMoaUnknown)
{
        STD_IUNKNOWN_METHODS
        STDMETHOD(Hello) (THIS_ PIMoaStream pStream) PURE;
};

To understand what this definition does, take a closer look at the code, line by line:

#include "moaxtra.h"
#define INTERFACE

The first line of code includes the standard header for defining MOA Xtras. The file moaxtra.h provides a set of macros used to define MOA interfaces and classes, and contains the IIDs for some standard MOA interfaces. The second line defines a macro, INTERFACE, which must be defined for the macros used in the subsequent declarations. The lines following this preamble provide the actual interface declaration.

Declaring the interface identifier

To declare the interface identifier, you create a unique 128-bit value and identify it to MOA, using the DEFINE_GUID macro. Interface identifiers are assigned to global variables, whose name is the name of the interface with the prefix IID_. This is demonstrated for IMoaHello in the following code:

DEFINE_GUID(IID_IMoaHello, 0xAC3590A6L, 0x0062, 0xE623, 0x00, 0x00, 
		0x08, 0x00, 0x07, 0x57, 0xFC, 0x90);

This identifier must be defined with the same value in every module that implements or uses this interface. For interfaces defined by MOA or an application, interface identifiers are provided by the standard header files provided with the Xtra Development Kit. For interfaces you define yourself, you use a utility program to generate a unique MoaID. On Macintosh OS 9 the program GenUID.app is provided with the MOA XDK for this purpose; on Macintosh OSX use the uuidgen command in the terminal window;on Windows use the Microsoft utility GUIDGEN.EXE, which is provided with the Visual C++ compiler.

Users of this interface use the interface identifier, in this case IID_IMoaHello, to request the interface from a provider. Users of your Xtra objects similarly use interface identifiers to request the interfaces you implement. MOA provides a standard technique for requesting interfaces from objects, through the QueryInterface() method inherited by all MOA interfaces.

Declaring interface methods

As described earlier, an interface is a set of methods, or function prototypes, for a complete, specific behavior. The heart of an interface definition is its method declarations.

In the case of IMoaHello, the interface has just one method, Hello(). Every class that implements IMoaHello will provide its own implementation of this method. Here is the code declaring the IMoaHello interface:

DECLARE_INTERFACE_(IMoaHello, IMoaUnknown)
{
        STD_IUNKNOWN_METHODS

        STDMETHOD_(void, Hello) (THIS_ PIMoaStream pStream) PURE;
};

In the first line, the DECLARE_INTERFACE_ macro describes the name of the interface and the interface it inherits from. Inheritance in this case simply means that the first interface, IMoaHello, includes the methods defined in the second interface, IMoaUnknown. All MOA interfaces inherit from IMoaUnknown, which provides the standard method QueryInterface() for requesting interfaces from an object.

Note that some object models such as C++ provide for one class to inherit from another, thus allowing implementations to be inherited. In MOA, as in COM, there is no inheritance of implementations. Inheritance is limited to the abstract behavior defined by an interface. (However, MOA provides a default implementation of the methods defined by IMoaUnknown.)

In the second line, the STD_IUNKNOWN_METHODS macro declares the default MOA implementation of the IMoaUnknown interface. This is required to complete the inheritance specified for IMoaHello.

In the third line, the STDMETHOD_ macro is used to declare the sole method in the IMoaHello interface. Within the parentheses of the macro itself, the return type and name of the method (void, Hello) are declared. The next parentheses contain the type and parameter of any arguments to the method. There are two arguments to the Hello method, THIS and pStream. THIS is always the first argument to a method, and represents the interface itself. The meaning of THIS is described in greater detail in "Writing method implementations" later in this section. The pStream parameter represents a pointer to an instance of the IMoaStream interface. This parameter provides the application's interface to the stream used to write the Xtra's greeting.

Note: In declaring a MOA interface, all method parameters must be exactly 4 bytes in length. Parameters smaller than this (e.g., chars, Booleans) must be expanded into an explicit 4-byte parameter. Parameters larger than this must be passed by pointer. There is one exception: type MoaDouble (8-byte floating point) may always be passed by value.

Note that the macro PURE is provided for compatibility with C++. This macro is described in the section "Implementing Xtras in C++," later in this chapter.

Defining a class

To provide a specific Xtra interface, your Xtra defines a class that implements that interface. To define a class, you provide a MoaID--referred to as the class identifier or CLSID--for that class, similar to the interface identifier. You then specify the instance variables, private data available within the method implementations of your class. You also specify interfaces implemented by the class. Finally, you specify method implementations for each method in each interface provided by the class.

Here's the code defining a simple class, the World class, that implements just one interface, the IMoaHello interface.

#include "moahello.h"

DEFINE_GUID(CLSID_World, 0xAC3593FFL, 0x0063, 0xAF65,
0x00, 0x00, 0x08, 0x00, 0x07, 0x57, 0xFC, 0x90);

EXTERN_BEGIN_DEFINE_CLASS_INSTANCE_VARS( World )
        char * greeting; 
EXTERN_END_DEFINE_CLASS_INSTANCE_VARS

EXTERN_BEGIN_DEFINE_CLASS_INTERFACE( World, IMoaHello)
        EXTERN_DEFINE_METHOD( void, Hello, (THIS_ PIMoaStream pStream))
EXTERN_END_DEFINE_CLASS_INTERFACE

EXTERN_BEGIN_DEFINE_CLASS_INTERFACE( World, IMoaRegister)
        EXTERN_DEFINE_METHOD( void, Hello, (THIS_ PIMoaCache pCache, PIMoaDict pDict) 
EXTERN_END_DEFINE_CLASS_INTERFACE

The first line includes the previously defined interface IMoaHello, contained in the file moahello.h. The following discussions describe the significant details of this code sample.

Defining the class identifier

Each class has a unique identifier, which is defined in exactly the same way you define an interface identifier. The only distinction is that the global name is given the prefix CLSID.

DEFINE_GUID(CLSID_World, 0xAC3593FFL, 0x0063, 0xAF65, 0x00,
0x00, 0x08, 0x00, 0x07, 0x57, 0xFC, 0x90); 

As with interface identifiers, class identifiers must be distinct from the identifiers of any interfaces or classes. Unique class identifier values are created using a utility application. On Macintosh OS 9 the program GenUID.app is provided with the MOA XDK for this purpose; on Macintosh OSX use the uuidgen command in the terminal window;on Windows use the Microsoft utility GUIDGEN.EXE, which is provided with the Visual C++ compiler.

Declaring instance variables

The instance variables represent private data that is allocated for each instance of the class. The World class has a single instance variable, greeting, intended to contain a string used by an object to greet its users. This instance variable is declared as follows:

EXTERN_BEGIN_DEFINE_CLASS_INSTANCE_VARS( World )
        PMoaChar greeting; 
EXTERN_END_DEFINE_CLASS_INSTANCE_VARS

The greeting instance variable can be accessed only within the method implementations of the World class. Use of the greeting instance variable is demonstrated in the discussion "Writing method implementations" later in this section.

Declaring interface methods
EXTERN_BEGIN_DEFINE_CLASS_INTERFACE(World, IMoaHello )
EXTERN_DEFINE_METHOD( void, Hello, (THIS_ PIMoaStream pStream) )
EXTERN_END_DEFINE_CLASS_INTERFACE

The final set of macros in this code declares the single method of the IMoaHello interface and identifies it with the World class.

The Xtra code includes a similar declaration for each interface implemented by a class. As in this example, all Xtras should provide at least one class that implements the IMoaRegister interface. This interface is implemented in a way that's specific to the type of functionality being implemented by the Xtra. For example, Transition Xtras implement IMoaRegister in one way, SoundEdit Xtras implement IMoaRegister in another.

Writing method implementations

A method implementation represents a class's specific code for a method in an interface. The method implementation is associated with a method through the virtual function table, which is set up in the same source file as the method implementations. Writing method implementations is the bulk of the work in implementing Xtras. This section looks at a simple example of a method implementation.

Implementing the World class

The following code sample is a complete implementation of the World class:

#define INITGUID 1 /*at least one file defines this macro*/

#include "worldcls.h" /* MoaID and globals for class*/

BEGIN_DEFINE_CLASS_INTERFACE( World, IMoaHello )
        DEFINE_METHOD( Hello, World_Hello )
END_DEFINE_CLASS_INTERFACE

BEGIN_DEFINE_CLASS_INTERFACE( World, IMoaRegister )
        DEFINE_METHOD( Register, World_Register )
END_DEFINE_CLASS_INTERFACE

BEGIN_XTRA
        BEGIN_XTRA_DEFINES_CLASS( World, 1 )
                CLASS_DEFINES_INTERFACE( World, IMoaHello, 1 )
                CLASS_DEFINES_INTERFACE( World, IMoaRegister, 1 )
        END_XTRA_DEFINES_CLASS
END_XTRA

/* class creator and destructor */

STDMETHODIMP_(MoaError) MoaCreate_World(World FAR * This > 
{
X_ENTER
        char    *theString = "Hello, world"
        long    theSize = strlen(theString) + 1
        if (This->greeting = \
                This->pCalloc->lpVtbl->NRAlloc(This->pCalloc, theSize))
        {
                strcpy(This->greeting, theString);
                X_RETURN( MoaError, kMoaErr_NoErr ); 
        }
        X_RETURN( MoaError, kMoaErr_OutOfMem );
X_EXIT
}

STDMETHODIMP_(void) MoaDestroy_World(World FAR * This) 
{
X_ENTER

        if (This->greeting)
        {
                This->pCalloc->lpVtbl-> NRFree(This->pCalloc, This->greeting); 
        }
        X_RETURN_VOID; 
X_EXIT
}

/* interface creator and destructor functions */

STDMETHODIMP_(MoaError) MoaCreate_World_IMoaHello(World_IMoaHello* This) 
{
X_ENTER
        X_RETURN( MoaError, kMoaErr_NoErr );
X_EXIT
}

STDMETHODIMP_(void) MoaDestroy_World_IMoaHello(World_IMoaHello* This)
{
X_ENTER
        X_RETURN_VOID;
X_EXIT
}

/*
implement IMoaRegister creator and destructor here
*/


/* interface method implementation */


STDMETHODIMP_(MoaError) World_Hello (World_IMoaHello FAR * This,
PIMoaStream pStream)
{X_ENTER
pStream->lpVtbl->write(pStream, this->pObj->greeting, strlen(this->pObj->greeting) + 1, NULL);
        X_RETURN( MoaError, kMoaErr_NoErr );
X_EXIT
}

STDMETHODIMP_(MoaError) World_Register (World_IMoaRegister FAR * This,
PIMoaCache pCache, PIMoaDict pDict)
(
/* see description of Register() implementation */
}

While World implements only two interfaces, each with a single method, this sample demonstrates that a complete implementation requires some additional code.

Setting up virtual function tables

A class implements an interface by defining a function for each method, the method implementation. Each method implementation has a name distinct from its actual method name. In this example, the method implementation for the Hello method is named World_Hello. The virtual function table is an internal MOA structure that records the relationship between methods and implementations. The tables for IMoaHello and IMoaRegister are set up at the beginning of the file, with the code for World's IMoaHello function table as follows: BEGIN_DEFINE_CLASS_INTERFACE( World, IMoaHello ) DEFINE_METHOD( Hello, World_Hello ) END_DEFINE_CLASS_INTERFACE

Declaring implemented interfaces

Next, the class declares all the interfaces it implements. In this case, the World class implements both IMoaHello and IMoaRegister. The following block specifies this:

BEGIN_XTRA
        BEGIN_XTRA_DEFINES_CLASS( World, 1 ) 
                CLASS_DEFINES_INTERFACE( World, IMoaHello, 1 )
                CLASS_DEFINES_INTERFACE( World, IMoaRegister, 1 )
        END_XTRA_DEFINES_CLASS
END_XTRA

The BEGIN_XTRA_DEFINES_CLASS macro specifies the name of the class being defined and the version number. Xtra developers are responsible for tracking and incrementing class version numbers with new releases of their Xtras. When MOA finds multiple versions of the same class among its Xtras, it selects the one with the highest version number to instantiate. The CLASS_DEFINES_INTERFACE macro specifies an interface being implemented. The Xtra developer also supplies a version number for each interface of a class, and MOA similarly selects the interface with the highest version number to instantiate.

Since the World class implements two interfaces, each is listed using the CLASS_DEFINES_INTERFACE macro. If the Xtra implemented more than one class, each would be declared in this block using the BEGIN_XTRA_DEFINES_CLASS and END_XTRA_DEFINES_CLASS macros.

Creator and destructor functions

For each Xtra, an application needs the ability to create new instances of its classes, and to acquire interfaces to those instances. Applications also need to manage the presence of external code resources--such as Xtras--in their allocated memory space, and to purge those resources when they are no longer in use. The creator and destructor functions provide the mechanism.

Class creator and destructor functions are similar in several ways. The creator initializes the object by allocating memory, acquiring handles to interfaces, and assigning values to instance variables; the destructor deallocates memory and disposes of handles. The names of the two functions are formed using standard prefixes (MoaCreate_ or MoaDestroy_) and the class name.

You never call a creator or destructor function directly. The IMoaCallback method MoaCreateInstance() is provided to create instances of specific classes; this method in turns calls the creator function after the instance has been allocated. An allocated object stays in memory as long as there are references to any of its interfaces. When all interface handles for the object have been dropped, MOA can call the destructor function to purge it from memory.

Note that creator and destructor functions don't implement methods in an interface. Instead, they provide standard entry points to your Xtra for use by MOA.

World's creator function, MoaCreate_World(), allocates memory for a greeting string, then assigns the address of the string to the greeting instance variable:

STDMETHODIMP_(MoaError) MoaCreate_World(World FAR * This)
{
X_ENTER
        char    *theString = "Hello, world"
        long    theSize = strlen(theString) + 1

        if (This->greeting = \
                This->pCalloc->lpVtbl->NRAlloc(This->pCalloc, theSize))
        {
                strcpy(This->greeting, theString);
                X_RETURN( MoaError, kMoaErr_NoErr );
        }
        X_RETURN_VOID;
X_EXIT
}

This code allocates memory for the greeting string using a standard MOA interface, IMoaCalloc. The pointer pCalloc refers to this interface, while NRAlloc() is a method in this interface. Xtras always use memory allocation provided by an application through this interface and another, handle-based allocation interface, IMoaHandle.

The World class's destructor function, MOADestroy_World(), is implemented to deallocate the string allocated in the creator:

STDMETHODIMP_(void) MoaDestroy_World(World FAR * This)
{
X_ENTER
        if (This->greeting)
        {
                This->pCalloc->lpVtbl-> \
                        NRFree(This->pCalloc, This->greeting);
        }
        X_RETURN_VOID;
X_EXIT
}

Again, the IMoaCalloc interface is used to free the memory it previously allocated.

In addition to an object's instance variables, memory needs to be allocated for each interface of an object. The virtual function table requires memory, provided automatically by MOA. MOA also defines some standard instance variables for interfaces, such as pObj, a pointer to the object providing the interface.

The interface creator and destructor functions are implemented in much the same ways as the corresponding functions for the class. The example above shows the creator and destructor for IMoaHello; similar code would be used for the IMoaRegister interface.

Implementing the Hello() method

World's Hello() method implementation, World_Hello(), is very straightforward, highlighting several MOA coding conventions.

STDMETHODIMP_(MoaError) World_Hello (World_IMoaHello FAR * This,
PIMoaStream pStream)
{
X_ENTER
        pStream->lpVtbl->write(pStream, 
                               This->pObj->greeting, 
                               strlen(This->pObj->greeting) + 1,
                               NULL);
        X_RETURN( MoaError, kMoaErr_NoErr );
X_EXIT
}

First, the macro STDMETHODIMP_ is used to identify the implementation and declare its return type. Following the function name, the parameters are listed in standard C-language declarations. The first parameter to C-language method implementation is a pointer to the interface itself, referred to by This. (A C++ implementation differs slightly with regard to this argument. See "Implementing Xtras in C++," the next section in this document, for more information.)

In the implementation of IMoaHello, the first parameter is a far pointer to an instance of the IMoaHello interface. Note that the type of This, World_IMoaHello, is a subtype of the interface itself. This must be typed in this way in order to provide access to the instance variables declared in its class. The second parameter is a pointer to an instance of the IMoaStream interface. When the application calls an the Hello() method, it passes in a stream where the greeting can be written.

The code for writing the string calls the write() method in the IMoaStream interface. To call write(), the code goes through pStream's lpVtbl instance variable, representing the stream object's virtual function table. To access its own greeting instance variable, the code uses the reference This->pObj->greeting. pObj provides access to an object from an interface, which in turn provides access to the instance variables of the object.

Note that this code assumes all characters are written to the stream. To determine how many characters were actually written, the method call would pass a pointer to a MoaStreamCount rather than NULL as the last argument.

Implementing the Register() method

In addition to implementing a specific Xtra interface, at least one class in each Xtra must implement the IMoaRegister interface. This interface is called by an application the first time it detects your Xtra in one of the standard Xtra folders. Its purpose is to add information about your Xtra to the application cache, from which the application gets information about the Xtras it has available.

The IMoaRegister interface consists of one method, Register(). In implementing this method, your Xtra must provide the IDs of classes that provide interfaces of interest to the application. Each MOA application specifies which interfaces it needs to register. The declaration of the Xtra interface should include a specification of keys that Xtras need to add to the cache. For example, the earlier declaration of the IMoaHello interface might have included a definition of a key such as key_IMoaHello_Menu that would specify the name of the menu entry to associate with your Xtra, such as:

/* registry entry */
#define key_IMoaHello_Menu "key_IMoaHello_Menu" 

/* 
 * all implementations of IMoaHello should use this key to
 * register a C string to display in the menu 
 */

In addition to specifying the interfaces of interest to it, a MOA application may specify other registry entries for its Xtras, such as menu items or other data. See the documentation for a particular Xtra interface for any such entries and the corresponding key definitions. In addition to the information required by MOA and a specific interface, you may cache your own information to use in initializing your Xtra, or at any time during the life of your Xtra.

Caching capabilities to support these requirements are provided through several interfaces, including the callback interfaces IMoaCache and IMoaDict, and the Xtra interfaces IMoaRegister and IMoaInitFromDict.

The following example shows World's implementation of IMoaRegister. It uses IMoaCache and IMoaDict to register the class and interface IDs of the World Xtra. It also registers an item with the key key_IMoaHello_Menu to be displayed by the application.

STDMETHODIMP_(MoaError) StdHelloWorld_IMoaRegister_Register(
        StdHelloWorld_IMoaRegister FAR * This, 
        PIMoaCache pCache, 
        PIMoaXtraEntryDict pXtraDict)
{
X_ENTER
        MoaError err;
        PIMoaRegistryEntryDict pRegDict;

/* 
 * standard for all Xtras:
 * register classes and interfaces provided
 */
        err = pCache->lpVtbl->AddRegistryEntry(pCache,
                pXtraDict,
                &CLSID_World,
                &IID_IMoaHello,
                &pRegDict );
        if (err) X_RETURN(MoaError, err);

/* 
 * specific to IMoaHello interface:
 * specify a menu entry using the defined key
 */
        err = pRegDict->lpVtbl->Put(pRegDict, 
                kMoaDictType_CString,
                "World",
                0, 
                key_IMoaHello_Menu);
        if (err) X_RETURN(MoaError, err);
        X_STD_RETURN(kMoaErr_NoErr);
X_EXIT
}

Providing Resources

In addition to the code for your Xtra, on the Macintosh you'll also need to add some magic resources to your Xtra.

All Macintosh Xtras must have an MXcf resource. The Rez definition of this type looks like so:

type 'MXcf' {
        longint = $$CountOf(memberArray);
        wide array memberArray {
                literal longint archType,
                kPowerPC = 'pwpc', k68kCodeResource = 'cr68';
                longint offset, kZeroOffset = 0;
                longint length, kWholeFork = 0;
        };
};

You'll typically include an MXcf that includes both a "cr68" and "pwpc" entry:

resource 'MXcf' (128) {
        {
                kPowerPC, kZeroOffset, kWholeFork,
                k68kCodeResource, kZeroOffset, kWholeFork
        }
};

Note that you don't need to add an icon resource for your Xtra; MOA applications automatically provide the appropriate icon for all Xtras.

Implementing Xtras in C++

MOA is written in C for compatibility on platforms that offer only C-language compilers. However, in creating Xtras for particular platforms, the developer may choose to use either C or C++. The previous example demonstrated conventions for coding Xtras in C.

C++ provides a useful programming model for implementing MOA objects. The main differences between coding Xtras with C and C++ is in the way interfaces are declared and implemented. This section explores these differences.

Declaring an interface in C++

In C++, a MOA interface is declared as an abstract class. The methods in an interface are declared as pure virtual functions. The MOA macros for declaring interfaces provide the implementation automatically when you specify C++ as your coding model. Look again at how the IMoaHello interface is declared:

DECLARE_INTERFACE_(IMoaHello, IMoaUnknown)
{
        STD_IUNKNOWN_METHODS
        STDMETHOD (Hello) (THIS) PURE;
};

The first macro of interest, DECLARE_INTERFACE_, evaluates in C++ as:

struct FAR IMoaHello: public IMoaUnknown

This provides a standard C++ declaration for the interface IMoaHello, declaring it as a class that inherits from the class IMoaUnknown.

The second macro of interest, PURE, evaluates as = 0 in C++ (it evaluates to a null string in C). This represents the C++ initializer for a pure virtual function. Pure virtual functions are member functions with no implementation in the class being declared. A class that declares only pure virtual functions is an abstract class. Thus the interface is declared as an abstract C++ class.

Implementing an interface in C++

A MOA class implements an interface using a C++ class. The implementing C++ class inherits from the abstract class of the interface. In effect, a MOA object in C++ is a collection of C++ objects, each representing an interface of the object.

The name of the implementing C++ class takes a standard form, combining the MOA class name and the interface name. For example, the name of the C++ class for World's implementation of the IMoaHello interface is World_IMoaHello.

The method implementations for a MOA interface are C++ member functions. The names of the method implementations conform to standard C++ naming conventions. For example, world's implementation of the Hello method takes the name World_IMoaHello::Hello.

In C++, the local variable this is provided implicitly within a method implementation. Thus, there is no need explicitly declare this as a function parameter. However, this implementation detail doesn't apply to the MOA class creator and destructor functions, which are standard C functions, coded exactly as in the previous example.

Note that this (with a lowercase `t') is a C++ keyword and is used rather than This within a C++ method implementation. This (with a lowercase `T') is used in C-language MOA code, including the class creators and destructors, to avoid conflict with this when compiled with a C++ compiler.

Here's a source file that implements the World class and its IMoaHello interface using C++:

#define INITGUID 1 /*at least one file defines this macro*/
#define CPLUS

#include "worldcls.h" /* MoaID and globals for class*/

BEGIN_DEFINE_CLASS_INTERFACE( World, IMoaRegister )
        DEFINE_METHOD( Register, World_IMoaRegister::Register )
END_DEFINE_CLASS_INTERFACE

BEGIN_DEFINE_CLASS_INTERFACE( World, IMoaHello )
        DEFINE_METHOD( Hello, World_IMoaHello::Hello )
END_DEFINE_CLASS_INTERFACE

BEGIN_XTRA
        BEGIN_XTRA_DEFINES_CLASS( World, 1)
                CLASS_DEFINES_INTERFACE( World, IMoaHello, 1 )
                CLASS_DEFINES_INTERFACE( World, IMoaRegister, 1 )
        END_XTRA_DEFINES_CLASS
END_XTRA

/* class creator and destructor */

/* note that This is passed as the first parameter */
/* in the MOA class creator and destructor functions, */
/* This is a MOA object, not an interface */

STDMETHODIMP_(MoaError) MoaCreate_World(World FAR * This)
{
X_ENTER
        char    *theString = "Hello, world"
        long    theSize = strlen(theString) + 1
        if (This->greeting = This->pCalloc->NRAlloc(theSize))
        {
                strcpy(This->greeting, theString);
                X_RETURN( MoaError, kMoaErr_NoErr );
        }
        X_RETURN( MoaError, kMoaErr_OutOfMem );
X_EXIT
}

STDMETHODIMP_(void) MoaDestroy_World(World FAR * This)
{
X_ENTER
        if (This->greeting)
        {
                This->pCalloc->NRFree(This->greeting);
        }
        X_RETURN_VOID;
X_EXIT
}

/* C++ interface creator and destructor */

/* note that these function names follow C++ conventions */
/* for class creator and destructors */ 
/* also note this is not passed as first parameter */

World_IMoaHello::World_IMoaHello (MoaError FAR * pErr)
{
X_ENTER
        *pErr = kMoaErr_NoErr;
X_EXIT
}

World_IMoaHello::~World_IMoaHello (void)
{
X_ENTER
        X_RETURN_VOID;
X_EXIT
}

/* creators and destructors for IMoaRegister go here */
/* method implementation */
/* note that function name follows C++ conventions */
/* and that this is not passed as the first parameter */

STDMETHODIMP_(MoaError) World_IMoaHello::Hello (PIMoaStream pStream) 
{
X_ENTER

        /* Calling from C++: no need to go through lpVtbl or pass "this" */

        pStream->Write(strlen(this->pObj->greeting) + 1, NULL);
        X_RETURN( MoaError, kMoaErr_NoErr );

X_EXIT
}

/* implement World_IMoaRegister::Register() here */

Implementing well-behaved Xtras

In writing an Xtra, it's important to remember that your code may not always be invoked by a particular Macromedia application. Xtras are potentially clients of all Macromedia applications. This means that they may be called by applications that provide different callback interfaces than described in a particular XDK. It's your responsibility to ensure that you interact appropriately, whatever the host application, and that you handle all errors raised in your Xtra code.

Error check method calls

The standard return for MOA methods is a MoaError value. You should take advantage of this feature and error test calls to MOA methods. This is particuarly important in calling the IMoaUnknown::QueryInterface() method provided by all interfaces. If you request an interface that an object doesn't provide, you'll get the error kMoaErr_BadInterface. If your Xtra ignores this return value and calls a method in the requested interface, your Xtra may cause MOA and the host application to fail.

Bullet proof your registration class

One critical section of Xtra code is the creator function for the class providing your Xtra's IMoaRegister interface. This code is invoked by every MOA application that finds your Xtra. Every MOA-capable Macromedia application registers every Xtra in the general Macromedia/Xtras directory, regardless of its type. Thus, you must write your the class creator function in a way that doesn't depend on a specific application or set of callback interfaces. In general, be sure to perform error checking when using any interfaces provided by the host application. One way to avoid trouble in this section of code is to create a separate class specifically to provide the IMoaRegisterinterface. This avoids initializing instance variables for the Xtra at registration time.

Write re-entrant code

Xtras should be written to be re-entrant. Specifically, you should avoid the use of global variables. If you use static variables in function implementations, be sure to include a "lock" variable that is set when a caller enters the code and reset upon exit. This ensures that two processes won't attempt to access the same code and alter the same static variables simultaneously.

MOA standard API

This section details some of the API used in programming with MOA. MOA defines a small set of calling conventions and a handful of standard interfaces that can be used in coding Xtras for any Macromedia application.

MOA calling conventions

The World examples demonstrate a very simple implementation of a MOA Xtra. When coding method implementations for a MOA Xtra, you frequently use the following four basic operations for accessing MOA API:

The calling conventions for these operations are described in the following sections.

Calling a method in an interface

In MOA objects, methods are organized in a virtual function table belonging to the interface, referenced through the lpVtbl instance variable of all interfaces. To access this structure, assuming pInterface is the pointer to a MOA interface, use the following form:

pInterface->lpVtbl 

To call a specific method in an interface, you reference the method through the virtual function table. For example, the following represents a method call for the oneArgMethod of the interface pInterface:

pInterface->lpVtbl->oneArgMethod(pInterface)

In this example, the oneArgMethod has just one argument. In MOA, the first argument to a method is the interface to which the method belongs, and is represented by the parameter This. (Note that you don't need to know whether the particular implementation of an interface is coded in C or C++; you can always supply the interface as the first parameter to a method.) Returning to the earlier example, you could access a World object's Hello method through a pointer to the IMoaHello interface. In the following code fragment, a local variable pHello provides such a pointer:

pHello->lpVtbl->Hello(pHello)

The interface pointer is used both to reference the method and as the first argument to the method.

Requesting one interface from another

All MOA interfaces inherit the QueryInterface() method of the IMoaUnknown interface, and MOA provides a standard implementation of this method to all classes. This method is used to request any interface belonging to an object.

Take the case of an object that provides two hypothetical interfaces, IMoaReason and IMoaEmotion. In the following example, pReason is an existing pointer to the object's IMoaReason interface. IID_IMoaEmotion is the MoaID of the IMoaEmotion interface. pEmotion is a freshly allocated pointer of type IMoaEmotion. This code fragment calls QueryInterface() on the IMoaReason interface to get a pointer to the IMoaEmotion:

pReason->lpVtbl->QueryInterface(pReason, IID_IMoaEmotion, pEmotion) 

When the call returns, pEmotion has a pointer to the object's IMoaEmotion interface.

You can use the QueryInterface() method to get access to interfaces provided by the callback object. The QueryInterface() method uses the interface identifier (described earlier) to specify the interface being requested.

After you have finished using a particular interface, you explicitly release it, using the Release() method. Like QueryInterface(), Release() is also inherited from IMoaUnknown.

pInterface->lpVtbl->Release(pInterface)

Calling Release() on an interface drops the reference to it. MOA provides the application with ways to purge an object when there are no longer any references to any of its interfaces.

Accessing an object from a method implementation

All MOA method implementations are passed a parameter, This, providing a pointer to the interface they belong to. (This is provided explicitly in C; the corresponding this is provided implicitly in C++). All interfaces, in turn, have a pointer to the MOA object they belong to, represented by the pObj member of the interface structure. To access an object within a method implementation, use the following reference:

This->pObj // in plain C
this->pObj // in C++

You only access an object directly from within the implementation of a method in a class. Since interfaces are the fundamental units of behavior in MOA, interaction with other objects is performed solely through the interfaces provided by those objects.

Accessing instance variables from an object

The instance variables defined by a class are private to its instances. To access an instance variable within the implementation of a method, you go through the object. Thus, to access a variable named myVariable, use the following reference:

This->pObj->myVariable // in plain C 
this->pObj->myVariable // in C++ 

If you define instance variables for your Xtra object, you use this calling convention to access them. For example, to access World's greeting instance variable, the World class uses the following code:

This->pObj->greeting // in plain C
this->pObj->greeting // in C++

MOA instance variables

All MOA objects are provided with certain instance variables automatically. Variables of interest to the MOA Xtra programmer are:

pCallback
IMoaCallback interface provided by callback object
pCalloc
IMoaCalloc interface provided by calloc object
pClassID
Pointer to object's class identifier (CLSID)

The interfaces provided by the pCallback and pCalloc instance variables are described in the next section.

MOA interfaces

Every MOA application implements several standard callback interfaces provided for use by your Xtra. These interfaces provide access to the application, representing MOA and system services through platform-independent API

IMoaCallback
General purpose MOA interface
IMoaCalloc
Fixed memory allocation interface
IMoaHandle
Relocatable memory allocation interface
IMoaCache
Provides the application's registry of Xtras
IMoaDict
Provides individual entries within the application's registry
IMoaStream
Provides access to memory or file buffers (optional)
IMoaProgressBox
Report progress of data processing from callback object (optional)

The IMoaCallback and IMoaCalloc interfaces are automatically supplied through the standard instance variables pCallback and pCalloc. To access the IMoaHandle interface of an application, you call the QueryInterface method on the pCallback instance variable.

The following demonstrates a complete call of QueryInterface to get access to the IMoaHandle interface: PIMoaCallback

pCallback = This->pObj->pCallback; 
PIMoaHandle pHandle; 
err = pCallback->lpVtbl->QueryInterface(pCallback, IID_IMoaHandle, (PPMoaVoid)&pHandle); 
/* check for errors| */
/* use pHandle */ 
pHandle->lpVtbtl->Release(pHandle); 

In this example, the callback object's IMoaCallback interface is queried for access to the IMoaHandle interface. The location of the IMoaHandle interface is assigned to the previously declared variable pHandle. You can then use pHandle to call methods in the IMoaHandle interface. The following discussions summarize methods provided by each of the standard MOA interfaces.

IMoaCache interface

Every application provides the IMoaCache interface to its Xtras. This interface provides methods for caching registration information about the Xtra. An application's IMoaCache interface is provided to an Xtra through its implementation of the IMoaRegister and IMoaInitFromCache interfaces. You may get the application cache at any time by calling the method MoaGetCache() on an object's pCallback instance variable.

AddRegistryEntry()
Adds new interface entry to cache
CreateNewInstanceFromRegistry()
Creates new instance
EnumerateFileEntries()
Lists dictionaries for files
EnumerateRegistryEntries()
Lists dictionaries for specific interfaces
EnumerateRegistryEntriesFromFile()
Lists all registry entries for a file
EnumerateXtraEntries ()
Lists dictionaries for Xtras
GetFileSpecFromFileDict()
Lists file specs for all files
GetInstanceFromRegistry()
Creates or retrieves single instance
SetXtraEntryNoncacheable()
Prevents Xtra from being cached
IMoaCallback interface

The callback object provides the IMoaCallback interface to your Xtra. This interface provides several methods for interacting with MOA classes, and for accessing and releasing the resources belonging to your Xtra.

MoaCreateInstance()
Create instance of specified class and interface
MoaGetCache()
Get the IMoaCache interface
MoaBeginUsingResources()
Returns a reference to the Xtra's resource file
MoaEndUsingResources()
Deletes reference to the Xtra's resource file

All MOA objects have a pCallback instance variable to refer to this interface.

IMoaCalloc interface

The calloc object provides the IMoaCalloc interface to your Xtra. This interface provides a pair of methods for allocating non-relocatable memory:

NRAlloc()
Allocate memory for use by the Xtra
NRFree()
Free memory allocated with NRAlloc

All MOA objects have a pCalloc instance variable to refer to this interface. This instance variable is set when the object is first instantiated.

IMoaDict interface

Every application provides implementations of the IMoaDict interface to its Xtras. This interface provides methods for registering and accessing the capabilities of your Xtra. The IMoaDict interface is provided to you through your implementation of the IMoaRegister and IMoaInitFromCache interfaces. You may get specific dictionaries at any time by calling methods of the IMoaCache interface.

Count
() Returns the number of entries in a dictionary
FindKey()
Returns the value for a specified key
Get()
Gets an entry from a dictionary
GetInfo()
Returns information about a dictionary entry
GetNth()
Gets the specified entry in a dictionary
MakeDict()
Makes a new dictionary belonging to the current dictionary
Put()
Puts an entry in a dictionary
Remove()
Removes an entry from a dictionary
SetSize()
Sets the size of the dictionary

For more on the API of this interface, see chapter 3, "MOA API Reference."

IMoaHandle interface

The callback object provides the IMoaHandle interface to your Xtra. This interface provides a number of methods for allocating relocatable memory:

Alloc
() Allocate relocatable memory block
Free()
Free previously allocated handle
GetLargestFreeBlock()
Optionally implemented to return free space available
GetSize()
Return logical size of handle
Lock()
Lock handle and return starting address pointer
Resize()
Attempt to resize previously allocated handle
Unlock()
Unlock handle
ZeroAlloc()
Allocate zeroed-out relocatable memory block

This interface is a standard MOA interface, provided by all Xtra-capable applications. To access this interface, you call QueryInterface on the pCallback instance variable of any MOA object.

IMoaInitFromDict interface

Your Xtra may optionally provide the IMoaInitFromDict interface to a MOA application. This standard interface provides one method for retrieving information from the application's registration dictionary when objects are initialized. Each Xtra determines whether or not to implement this interface and what type of information to retrieve from the registration dictionary on initialization. Information retrieved from the dictionary by this interface must be placed there through the IMoaRegister interface.

InitFromDict()
Initialize an object using registry information

Implementations of the IMoaInitFromDict interface use the IMoaCache and IMoaDict interfaces provided through the InitFromDict() method. The IMoaInitFromDict interface is an optional MOA interface that may be provided by any Xtra.

IMoaProgressBox interface

The callback object optionally provides the IMoaProgressBox interface to your Xtra. This standard interface provides methods for interacting with the user to report progress while an Xtra processes data.

OpenProgress()
Display progress dialog box
SetCaption ()
Set caption displayed by progress dialog box
SetUserMessage()
Set message to user while dialog box is displayed
SetProgress()
Set completion value displayed by progress bar
TestUserCanceled()
Test for user clicking Cancel button
CloseProgress()
Hide progress dialog box
IMoaRegister interface

Your Xtra provides the IMoaRegister interface to a MOA application. This standard interface provides one method for describing the capabilities of the Xtra to the application. Each application defines specific information to be supplied by the Xtra as it is registered.

Register()
Registers capabilities of Xtra

Implementations of the IMoaRegister interface use the IMoaCache and IMoaDict interfaces provided by the application. The IMoaRegister interface is a standard MOA interface, provided by all Xtras.

IMoaStream interface

Certain MOA applications provide the IMoaStream interface to enable Xtra objects to archive and retrieve data. This interface is designed to be independent of the storage media, and thus equally useful for accessing data through disk files, memory buffers, network sockets, and other mechanisms. It is useful in applications that generate output files containing a mixture of internal and Xtra objects, such as movies created by Director. This interface defines several methods for reading and writing data

Open()
Opens the stream
Close()
Closes the stream
Read()
Reads data from the stream
ReadAhead()
Reads data from the stream without seeking
Write()
Writes data to the stream
GetPosition()
Gets the current position in the stream
SetPosition()
Sets the current position in the stream
GetEnd()
Gets the size of the stream
Flush()
Flushes the stream
GetModes()
Gets the read/write modes of the stream
IMoaUnknown interface

All MOA objects provide the base interface IMoaUnknown. This is the interface you use to request other interfaces an object provides and to dispose of interfaces when you're done with them. The IMoaUnknown interface is inherited by all other interfaces implemented in MOA. It consists of three methods of interest:

QueryInterface()
Query an object for a particular interface
AddRef()
Increment the reference count for an interface
Release()
Decrement the reference count for an interface
This interface is standard in MOA as in COM. The MOA macros provide a default implementation of the methods in this interface.

Copyright © 1995-2007 Adobe Macromedia Software LLC, Inc.