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.
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.
In implementing an Xtra, both the Xtra interfaces you provide and the callback interfaces you use are defined by Adobe. 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 Adobe. 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.
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.
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.
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.
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.
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.
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.
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.
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 * 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 * 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 * 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 * 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.
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
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.
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 * 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 * 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.
World's Hello() method implementation, World_Hello(), is very straightforward, highlighting several MOA coding conventions.
STDMETHODIMP_(MoaError) World_Hello (World_IMoaHello * 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 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.
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 * 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 }
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.
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 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.
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 * 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 * 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 * 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 */
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. You should write 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.
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.
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.
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.
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.
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++
All MOA objects are provided with certain instance variables automatically. Variables of interest to the MOA Xtra programmer are:
The interfaces provided by the pCallback and pCalloc instance variables are described in the next section.
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
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.
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.
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.
All MOA objects have a pCallback instance variable to refer to this interface.
The calloc object provides the IMoaCalloc interface to your Xtra. This interface provides a pair of methods for allocating non-relocatable memory:
All MOA objects have a pCalloc instance variable to refer to this interface. This instance variable is set when the object is first instantiated.
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.
For more on the API of this interface, see chapter 3, "MOA API Reference."
The callback object provides the IMoaHandle interface to your Xtra. This interface provides a number of methods for allocating relocatable memory:
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.
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.
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.
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.
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.
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.
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
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: