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

Director Xtra Development Kit: Multimedia Developer's Guide

Tips, Guidelines, and Gotchas


This discussion contains some general guidelines for all types of Xtra development, as well as specific suggestions for developing the four types of Xtras supported by the Director XDK. It's somewhat of a grab bag, including both very general and very specific coverage of a number of topics.

You may want to read through this information before you start developing Xtras. You should also review the information here as you develop your Xtras, since much of the information applies to specific areas of Xtra development.

General

Use the skeleton and example projects

This XDK comes with skeleton projects in the Examples folder for Interrogator, Transition, Sprite, Scripting, and Tool Xtras. These templates include projects for Visual C++ and Metrowerks CodeWarrior, and skeleton code files containing comments that briefly describe modifications to make to create your Xtra.

There are also a number of working example projects that demonstrate various coding features described in this documentation. One way to begin Xtra development is to start with a working example that implements many of the features you plan to provide, and modify it to include your code.

Note that when modifying the template and example projects, you should only need to change source file names--and possibly add new source files--in the projects. If you are developing in C++, you can change the code model in the project.

IMPORTANT: YOU MAY FREELY CHANGE SOURCE FILES IN THE EXAMPLE PROJECTS. HOWEVER, TAKE CARE IN MODIFYING ANY PROJECT SETTINGS. THESE PROJECTS PROVIDE A NUMBER OF STANDARD SETTINGS REQUIRED TO MAKE XTRAS WORK CORRECTLY.

Use unique values

Be sure all class IDs you define are unique; these should be generated with the MSVC GUID program or Macromedia's GenUID app.

Windows DLLs: Make sure the library name in your .DEF files is unique

Use resource files

If possible, use resources to store any strings, including your type symbol, display name, etc. This will provide a way for some users to resolve conflicts if there is a duplicate symbol name. Also it will make it easier to localize your Xtra.

Coding details

Unless otherwise noted, all string lengths specified include the terminating NULL (0x00) character (ie, standard C strings). Thus, where a string length is specified as 32, the maximum number of useable characters in the string is actually 31.

Do not depend on globals being initialized in Xtras. The behavior of global variables in code resources/code fragments/DLLs varies.

Remember that your code may be shared by multiple applications or projectors running simultaneously. Again, this makes it very difficult to rely on global variables on platforms that only initialize them on a per-load basis (rather than per-instance).

Make your code re-entrant. Your Xtra may be called back as a result of a call your xtra makes to the host application. For example, if you post a moveable modal dialog using MacModalDialog, your Xtra may be called back (from within the MacModalDialog() call) to redraw thumbnails, re-image a sprite on the stage, etc., if the dialog is moved. This means that the data required to perform these operations must never be left in an inconsistent state while a call back to the host application is being made.

Registration and Initialization

In general, all interfaces you acquire through QueryInterface should be tested before you attempt any calls to them This is particularly crucial with regard to application-specific calls in your registration class (supporting IMoaRegister). The MoaCreate(), MoaDestroy(), and Register() methods of this class may be called by any MOA application. One approach is for your Register() implementation to test for the interfaces your Xtra needs from an application by calling QueryInterface(), and not register itself if they aren't present.

The IMoaMmInterrogate interface is provided for initializing libraries and allocating large blocks of memory at startup time. This process slows application startup, so you should be certain you need it before adding it to your Xtra. Whenever possible, use the IMoaRegister interface to cache initialization information, since it 's only instantiated the first time an application encounters your Xtra.

Testing
Accessing QuickDraw Globals on the Macintosh

Prior to Macintosh OS X, there was a global memory structure called "Quickdraw globals." This global data does not exist with OS X, so the following discussion only applies to earlier versions of the Macintosh operating system. There are two methods to access QuickDraw globals. Here is the first method:

#include <LowMem.h>
#include <Quickdraw.h>
#define qd (*((QDGlobals *) (*((GrafPtr **) LMGetCurrentA5()) + 1) - 1))

If you're already accessing Director's stage window's graphics context, then the pointer to Director's QuickDraw globals is available as part of the nativeGCInfo which you can get using the IMoaMmGC:GetNativeGCInfo interface. IMoaMmGC interfaces are supplied to sprite and transition Xtras at drawing time; you can also get the graphics context for a movie's stage window by calling IMoaDrMovie::GetStageWindowGC(). Note that at start-up initialization time, a movie is not yet open so you'll get an error if you try to obtain its graphics context at that time. However, it should be available in normal circumstances. (Always check your error codes!) Here is an example of the second technique:

{
	MoaMmNativeGCInfo nativeInfo;
	PIMoaDrMovie pMovie = NULL;
	PIMoaMmGC pGC = NULL;
	QDGlobals * pQDGlobals = NULL;
 
	HANDLE_ERR( DrPlayer_GetActiveMovie(&pMovie) );
	HANDLE_ERR( pMovie->GetStageWindowGC(&pGC) );
	HANDLE_ERR( pGC->GetNativeGCInfo(&nativeInfo ) );

	pQDGlobals = nativeInfo.mac_window.qdGlobalsPtr;

	/* Do your stuff with the QuickDraw globals. It's okay to save it too, since it won't change during the session.*/

done:

	/* Release interfaces we obtained */
	if (pGC) pGC->Release();
	if (pMovie) pMovie->Release();
}
You might have to be careful with the first technique since support for A5 on PPC may go away in some future system software release (then again, so might the QuickDraw globals!). The second technique is currently Director API-specific, so it won't work if you're writing Xtras to be used in multiple products (for example, transitions for Director/Authorware).
Cast Member Media Notes

You can only get the media in the formats listed for IMoaDrCastMem. The available formats depend on the cast member type and the platform (Macintosh or Windows). Only the macPICT and winDIB formats are currently supported for image media.

If you are specifying macPICT format media for an image, the pMoaVoid mediaData parameter to IMoaDrUtils::NewMediaInfo() must be of type Handle, or more precisely a PicHandle; in other words, it must be a pointer to a pointer to a Picture structure.

IMoaDrUtils::NewMediaInfo() encourages you to provide the parameters to fill in the MoaDrMediaInfo structure. The IMoaDrCastMem::SetMedia() call will actually test.

Director tends to report an out-of-memory condition for errors in setting bitmap media, assuming that the bitmap was too large.

Although the table of media formats gives "composite" and "moaHandle" as the media label and format for Digital Video, digital video is not currently stored in a movie. Instead, Digital Video is always linked, regardless of whether you check the Linked box or not when you import it. As a result, you can't get the media of digital video cast members. You have to read the linked file (accessible with the fileName property).

How do I get the Cast selection?

The selection of a castLib is a Lingo list of lists. Each sublist represents a continuous range of selected cast members in that cast. Try selecting a bunch of cast members (using shift and command/control to do continuous and non-contiguous selections), then view the list in Lingo:

put the selection of castlib 1
[[3,5], [6,6] [10, 14]]

This means that cast members 3, 4, 5, 6, 10, 11, 12, 13, and 14 are selected.

To traverse a list-type MoaMmValue, use the callback class' IMoaMmList interface. See MMISERVC.H for the interface declaration. Use this interface to create new lists and to traverse existing lists. You can find out how many items are in a list MoaMmValue (CountElements()), get the nth item (GetValueByIndex()), and so on. This interface lets you manipulate both linear and property lists.

The cast selection is a linear list where each element is itself a linear list containing two elements. You are guaranteed that each sublist which describes a range always contains exactly two elements-- the start and end of the range-- and that these elements are integers.

How do you know which cast to get the selection from in a multiple-cast movie? You should first obtain the "activeCastLib" property from the movie interface using IMoaDrMovie::GetProp(). This tells you the index of the castLib in the currently active cast window (or the most recently active cast window if one is not currently active). Then using IMoaDrMovie::GetNthCast() to get the corresponding cast interface.

Bitmap Media

macPICT is only supported on the Macintosh; winDIB is only supported on Windows. Director doesn't have the notion of a single, cross-platform image format in the MOA APIs.

DIB's

When supplying a DIB to SetMedia() or AttachMedia(), it must be a global handle. If you get errors, make sure you can lock the handle with GlobalLock().

Manipulating DIB's is tricky. Microsoft provides samples with routines that manipulate DIB's in MSVC 5.0 and higher (see Samples\Win32\Wincap32).

What is a winDIB, the media format for a Windows PICT castmember type?

Also known as a "packed DIB," winDIB is a Windows global HANDLE containing the BITMAPINFO followed by the pixel data. This turns out to be the same format as a Windows BITMAP resource. Remember that the color table, whose length depends on the pixel depth, is part of the BITMAPINFO structure, so you need to account for the pixel depth when calculating the offset to the lpBits. To see an example of calculating the offset to the bits and using the IMoaMmGC::Blit() callback to do blitting with DIBs, see the file IMAGEWIN.C in the Windows "INKTEST" sprite xtra sample.

Also, remember to check out the SetMedia() options for image media. These are defined in DRTYPES.H. The options let you specify new pixel depth and/or palette to use when applying the image media to a bitmap cast member.

PICT's

Typically, to manipulate a PICT, you render it into a GWorld. Create a GWorld which is the bounds, pixel depth, and palette of the PICT. You can get this information using the PICT utilities Macintosh toolbox calls, then call DrawPicture() to draw it into the GWorld. There, you've got direct access to the pixels, just like a DIB on Windows. Also, since it's a GWorld, you can use the normal Macintosh QuickDraw calls to draw into it and manipulate it like a GrafPort. To create a PICT again, you open a picture, CopyBits() the GWorld onto itself, and close the picture. This is somewhat cumbersome and relatively slow, but PICT is pretty universal on the Macintosh and as such is the most convenient choice as an interchange format.

Bitmaps provided by Director

Everything should be uncompressed, flat pixels by the time you get it (in your GWorld on the Macintosh or DIB on Windows). The pixel depth can be 1, 2, 4, 8, 16, or 32. The palette can be any palette if the pixel depth is less than or equal to 8.

When setting bitmap media, there are several useful options for specifying the pixel depth and palette to use. You can:

The Xtras Menu in Director

Director has an Xtras menu in authoring mode. Director places various items in this from the Xtras folders.

Tool Xtras appear in the Xtras menu automatically, displaying using the user interface name and category (organized as a submenu of Xtras) that you register. You can supply an About box for any kind of Xtra this way.

Director will display movies and casts that it finds in its Xtras folder in the Xtras menu. Director recognizes their type and puts them in its Xtras menu. You can supply a sample movie or user interface that manipulates your Lingo or sprite Xtra this way.

You can combine the two approaches to provide a sample movie in a submenu. Movies and casts don't ordinarily appear in a submenu, but you can create a simple Tool Xtra that loads a related movie by sending Lingo commands with IMoaDrPlayer::CallHandler(). This way your movie will be grouped in your submenu of the Xtras menu. You can still keep the movie in a subdirectory within the Xtras folder by giving it a name starting with a dash (-). It appears that Director will not display movie names start with a dash (-) in its Xtras menu.

Be aware that not all products implement all kinds of Xtras, so don't depend on all Xtras being loaded.

If I register two menu items for a Tool Xtra (so that I have a submenu under "Xtras", with two items on it), how can I tell which menu item was selected in my Invoke method?

There are two ways to do this; the first method is to register a different class for each item. You'll know which item is selected because Director will create an instance of the corresponding class. The drawbacks are that there is more code for you to write and you need to generate multiple class IDs, and so on.

The second technique is to register the same class multiple times, once for each item. To distinguish each entry, you must put a private piece of data into the registry for each entry, then, when your IMoaInitFromDict is called and you get access to the registry entry, you can look at your private data to figure out which entry was chosen.

This is why there's a for loop in the sample code for IMoaRegister_Register. Simply increase "kToolCount" to the number of items you want to register. Add the name, category, and "alwaysEnabled" setting to the registry array initializers declared in CToolRegister_IMoaRegister_Register. The "myData" array in this function is there to show you that you can put any arbitrary info into the registry and then retrieve it later at initialization time.

A simple way to distinguish your entries is just to put an index here, if you have four items to register:

myData[kMyToolCount] = {0, 1, 2, 3};

Inside the for loop, add a statement to put the correct element of the myData array into the registry:

for (count = 0; count < kMyToolCount; count ++)
{
	MyData myData;
	...AddRegistryEntry()...
	/* Register standard tool entries */
	...
	/* Register private entry - we use made up key called "myData" to use to reference the data */
	/* Add a line like this that puts the myData value into the registry */
	if ((err = pRegDict->Put(kMoaDictType_Bytes, &&myData[count], sizeof(MyData), "myData")) != kMoaErr_NoErr)
		break;
}
Now, the time to access this data is in your IMoaInitFromDict call. Here you are passed in a pointer to the corresponding registry for the entry that was used to create the instance (ie, corresponds to the item selected). For example:
STDMETHODIMP CMyTool_IMoaInitFromDict_InitFromDict(... This, PIMoaRegistryEntryDict pRegistryDict)
{
	MyData myData;
	
	if (pRegistryDict)
	{
		if (pRegistryDict->Get(kMoaDictType_Bytes, &&myData, sizeof(MyData), "myData") == kMoaErr_NoErr)
		{
			/* Found the "myData" entry in the dictionary and stuffed it into the local variable myData. 
			This is one of the { 0, 1, 2, 3 } values we stuffed in earlier. Use this value to initialize
			your instance data so you know which item was selected when your Invoke() method is called. */
			
			/* Example - assumes we have an instance variable called itemSelected */
			This->pObj->itemSelected = (MoaLong) myData; /* Just save off the index */
		}
		else
			This->pObj->itemSelected = -1L; /* Use to indicate an initialization error */
	}

	return kMoaErr_NoErr;
}

Later in your Invoke() method, switch off of This->pObj->itemSelected to determine which item was chosen. If using the case above, if itemSelected is (-1) there was some kind of error, otherwise, it's 0, 1, 2, or 3-- the index into the item selected. This index does not necessarily correspond to the order in which the items appear in the menu; it corresponds to the order in which the items were registered.
What happens to Director's event loop when a menu is active?

In general, the Director event loop is suspended while a menu is active. This is also the case if you're in a tight Lingo repeat loop or the host application is busy doing some other processing; it is a general issue with cooperative multitasking.

If you want to ensure you get time slices on a more regular basis, you should use threads or VBL tasks on the Macintosh. Use periodic multimedia timers on Windows. You have to be careful when executing code at interrupt time, however, since there are memory restrictions, reentrancy issues, and context-switching to deal with.

Xtras and resources

On the Macintosh, put any custom resources for your Xtra in the resource file associated with your CodeWarrior project. To use your resources, you must surround calls which access your resources with calls to MoaBeginUsingResources() and MoaEndUsingResources(). This is necessary to allow the host application to set up the resource chain to point to your Xtra's resources (otherwise, using Macintosh resource manager calls will look for the resources in the host application, not your Xtra). You should do this on Macintosh and Windows although it currently is not operational on Windows. For example:

{
	XtraResourceCookie myCookie, saveCookie;
	
	/* Set up resource chain */
	myCookie = This->pObj->pCallback->MoaBeginUsingResources(gXtraFileRef, &saveCookie);
	
	/* Access your resources here */
	GetIndString(myString, kMyStringListID, kMyStringID);
	
	/* Restore resource chain */
	This->pObj->pCallback->MoaEndUsingResources(gXtraFileRef, saveCookie);
}
IMPORTANT: You must call MoaEndUsingResources()before returning from your Xtra method implementation function, otherwise, when control returns to the application, the resource chain will still be set to point to your Xtra; the application may not be able to access its own resources. So, your MoaBeginUsingResources() and MoaEndUsingResources() calls must both occur in the context of a single call to one of your Xtra's methods.
Xtras and Shockwave

Xtras needed to run a movie in Shockwave or in projectors can be downloaded automatically (Xtra downloading). For an explanation of this feature, see the related TechNotes on the Director support section of www.adobe.com. (Search for "Xtra Downloading".)

In all cases, the host MOA application (Shockwave for Director in this case) traverses subdirectories of the support folder, up to four levels deep, so you can place Xtras in folders. It is a particularly good idea to put Xtras for Microsoft Internet Explorer in a \Windows\System\Xtras\.... folder.

Xtras must be unique

Remember that in general, Xtras have more than one GUID. There's a GUID for each class, so if you have a separate registration class, you need a unique GUID for that in addition to your asset and actor, Lingo, or tool classes. To ensure this, try running your Xtra with every sample Xtra in the Xtras folder; you should not get any conflict notices.

When you create an Xtra in addition to making sure that all of your GUID's are unique, you want to ensure that other items for your Xtra are unique.

If you create an asset Xtra (sprite or transition), your asset's type symbol (kMoaMmDictKey_SymbolString) must be unique. This is a single string, maximum 32 characters (including a trailing null) that's typically defined in your implementation file and registered in your IMoaRegister::Register() implementation. Note that this is separate from your Xtra's display name (kMoaMmDictType_DisplayNameString) and display category (kMoaMmDictType_DisplayCategoryString), which are what the user sees in the host application's user interface. The type symbol is used internally to differentiate your Xtra from others, and is used when referring to your asset from Lingo.

If you create a Lingo Xtra, your Lingo Xtra's name (the name after Xtra in the msgTable) must not conflict with other Lingo reserved words and other Xtras. Also, any global handlers (names that appear with an asterisk (*) before them in the msgTable) must not conflict with other Lingo words.

It's a good idea to use a unique short prefix for your organization, which can be the same for all the Xtras you implement. Macromedia will register these symbols and keywords. Macromedia will not register GUID's.

Handling Errors

Here is one way to handle errors in a Director-only Xtra:

Trap the error internally and use CallHandler() to execute the Lingo Alert() function to display your own error dialog.

Managing interfaces and values

Throughout this API, you'll find cases where you create or use instances of MOA interfaces, sets of methods for manipulating objects, and MoaMmValues, object-like entities that represent specific types of values. Both MOA interfaces and MoaMmValues use reference counting to determine when they can be removed from memory.

There are various ways to acquire a value or interface, and the way you acquire it determines whether or not you are responsible for releasing it. Call the IMoaUnknown::Release() method to remove a reference to an interface; call the IMoaMmUtils::ValueRelease() method to remove a reference to a MoaMmValue.

The "caller owns" rule

Objects acquire interfaces and values by calling methods or by having their methods called. The "caller owns" rule define who is responsible for releasing an interface or value.

The rule is this: The owner of an interface or value is the object that calls the method requesting or providing it. The owner is always responsible for releasing an interface or value when through with it.

Here are the four cases to help clarify this rule:

* When your Xtra calls a method to request a value or interface from another object, you own the interface or value returned.

Examples of methods that your Xtra might call are IMoaUnknown::QueryInterface() and IMoaMmPropOwner::GetProp(). You must call IMoaUnknown::Release() on interfaces or IMoaMmPropOwner::ValueRelease() on values acquired by calling these methods when you are through with them.

* When another object calls a method in your Xtra to request a value or interface, the other object owns the interface or value you pass back.

Examples of such methods are NewXActor() and GetProp() in the IMoaMmXAsset Xtra interface. Note that this transfers ownership to the caller. That is, if you create the interface of value within the function being called, your ownership ends once you have passed it along. However, if you pass an interface or value that you are holding in an instance variable, you must call IMoaUnknown::AddRef() or IMoaMmUtils::ValueAddRef() to increment the reference count before passing it to the caller.

* When your Xtra calls a method in another object, providing a value or interface as one of the arguments, you own the interface or value.

An example of such a method is IMoaMmUtils::MacRegisterWindow(). When you call this method to register a window, one of the arguments you pass is an IMoaMmMacEventHandler interface. After you call MacUnregisterWindow, you are responsible for disposing of the event handler interface.

* When another object calls a method in your Xtra, providing an interface or value as one of the arguments it passes to you, that object retains ownership.

An example of this type of method is IMoaMmXAsset::SetCallback(), used to pass a callback interface to media asset Xtras. If your asset wants to hold onto this interface, it must call IMoaUnknown::AddRef() on it to assert ownership, then call IMoaUnknown::Release() on it when finished.

As you develop your Xtras, make sure the instances and values you create and released when you expect. Similarly, make sure memory you allocate (via IMoaCalloc or IMoaHandle) is being released properly as your Xtra performs.

Notes on using CallHandler()

There are some commands that you can execute in Lingo that don't comply with the syntax which CallHandler() is built for:

command value1, value2, value3, ...

or

result = function(value1, value2, value3, ...)

Luckily, you can use Lingo to get around this. You basically have to execute or evaluate a command or function as a string. You do this with the "do" command and "value()" functions, respectively.

To execute the COMMAND "open window foo" use CallHandler() with
mmethod: "do"
nArgs: 1
arg[1]: string MoaMmValue containing "open window foo" or "set the loc of sprite 3 to point(5,10)"
pResult: NULL

To evaluate an EXPRESSION like "the loc of sprite 3" use CallHandler() with
mmethod "value"
nArgs: 1
arg1: string MoaMmValue containing "the loc of sprite 3"
pResult: pointer to MoaMmValue to get value of expression

Windows Xtras
Xtras must match the platform of Director, a projector, the Shockwave for Director plugin because Director doesn't go through a thunking layer for Xtras. This is good, it helps performance and a .x32 Xtra can work under Windows NT.

If you use openxlib to force opening a Lingo Xtra, don't include the extension -- use openxlib("D:\Special\MyXtra"), not openxlib("D:\MyXtra.x32").

If you include the extension and you're on the wrong platform, Director will open the file and it will show up in the list from 'showxlib' (but not in 'put the number of xtras' or 'put the name of xtra 3'). But it doesn't do anything with the code.

Asset Xtras

Use a unique symbol string for your Xtra. We recommend a symbol in the format "vendorName_assetType". There is a 32 character maximum length.

Don't forget to pick a category and display name (both have a 64 character maximum) for your asset type. These should be localized, human-readable strings.

Do not invoke UI in your IMoaMmXAsset::PrepareNewContent() method unless the newFlags kMoaMmNewFlags_UiPermitted flag is set. If this bit is not set, you should silently initialize default props/media for your asset. This will be the case if your asset is being created programatically or at a time where UI should be deferred until explicitly requested by the user (ie, a call to your InvokePropsEditor() or InvokeMediaEditor()).

If supporting animated thumbnails, remember that you are responsible for "pacing" the animation. The host app will call your ServiceImageAnimThumb() method as quickly as possible; you should be prepared to run on very fast machines, which may cause this to get called hundreds of times per second. Use a system OS timer to "throttle" your animation speed; simply return immediately from ServiceImageAnimThumb() if it is not yet time for you to draw a new frame.

Test your Xtra in Movies in a Window and in projectors in addition to the main movie.

Byte swapping

If streaming media and/or data into/out of the host application document file, don't forget to consider byte-swapping issues. If you intend to read the data back in on a different platform, are responsible for byte swapping it. In general, there are two solutions for byte-swapping:

* When writing to a stream, always use a fixed byte-ordering, regardless of the executing platform. This way, you know that your file always contains data in a certain byte order, and you only need to do swapping if reading/writing on the "other" platform. The drawback is that this always penalizes one platform since you must always swap when both reading and writing files on that platform.

* When writing to a stream, begin the sequence with a "tag" byte which indicates the byte ordering for the sequence. When reading the data, check the tag byte value; if it's the same byte-ordering as the executing platform, no need to do any swapping; if it isn't, swap the bytes. This technique never causes penalties if you're only working on a single platform. It can, however, cause penalties on either platform if the data read is in the wrong byte order-- this means that both the Motorola and Intel versions of your code must do byte-swapping checks.

Sprite Xtras

Make sure you're getting events when you expect. Use the message window to print debug information when receiving events. If you wish these events to be passed on to Director, be sure to set the *pHandled flag to kMoaMmSprEvent_Pass.

SPAN events: understand that when a movie is stopped, you will not get an SpanEnd event if your sprite is still on the stage (you will, however, get a PlayEnd event). Span and Play events must be used together when determining when to "start" and "stop" your sprite from playing.

Your Xtra is responsible for handling the appearance of your sprite in different ink modes, colors, etc. which may be applied to it. It is not required that your xtra support these parameters, however (although many users may expect it). Try to support the common ink modes, Copy and Background Transparent, if possible. The IMoaMmGC::Blit() method is provided to aid you in supporting ink modes and colorization.

Test your sprite along side other built-in sprites and xtra-based sprites. Make sure compositing is happening correctly if you are imaging "off screen".

Your asset and sprite SetProp(), CallFunction(), InvokeMediaEditor, and InvokePropsEditor() methods are responsible for invalidating any of your sprites that may appear on the stage if your appearance is affected by one of these calls. Use the IMoaMmSpriteCallback::InvalSpriteRect() sprite callback for this purpose.

Your asset is responsible for "dirtying" itself if any changes are made to its props or media that are to be saved on disk. Otherwise, your StreamOutProps() and/or StreamOutMedia() methods may not get called. This is handled easily by returning the correct "modifyFlags" from your InvokeMediaEditor() and InvokePropsEditor() methods. However, if a SetProp() or CallFunction() call modifies your data, you must explicitly call back the host app to notify it. Use the IMoaMmAssetCallback::SetModified() asset callback for this purpose. This allows you to "dirty" your asset at any time.

Test your Xtra in filmloops, linked movies and projectors

What's the best way for a sprite actor to intercept a mouse event and generate an event of its own? How do you write widget-type sprite Xtras?

A sprite actor can send an arbitrary Lingo event that will progress through the normal Lingo message chain (sprite --> cast member --> frame --> movie) using the SendSpriteMessage() sprite callback in the IMoaDrSpriteCallback interface. The sprite callback object, which supports two interfaces (IMoaMmSpriteCallback and IMoaDrSpriteCallback), is supplied to your sprite actor through its SetCallback() method just after instantiation. You are supplied with the IMoaMmSpriteCallback interface. If you want to use the IMoaMmSpriteCallback callbacks, you should call AddRef() on the interface and store it off; if you want to use the IMoaDrSpriteCallback interface, QueryInterface() for it and store it off.

This mechanism lets you emit an arbitrary message from your sprite object. It was intended for allowing widget-type sprite Xtras to be written. You'll get a return value indicating whether it was handled or not. So, for example, in your code that handles your mouseDown or mouseUp event, you could pipe off a "customEvent" message. If you have a "customEvent" handler at for that sprite, that gets called, otherwise the cast member, frame, movie is tried. You can use the standard Lingo scheme for passing events down the chain too.

If you want the original mouse event sent to your Xtra passed off to lingo as well, simply set the "pHandled" return value for the Event() call to FALSE. This results in the mouseDown or mouseUp Lingo handler for the sprite being called. The corresponding Lingo handler then gets called after control is returned from your Event() method. This also works for keyboard events.

Transition Xtras

Test in Movies in a Window and in projectors

Test with Export to QuickTime, AVI, BMP.

Test with the playback window obscured by other authoring windows, especially if doing direct screen memory access.

You are responsible for "throttling" your transition; the host app will call your Continue() method as often as possible. Use the supplied elapsedTime or an OS timer to guage the rate of your transition and pace it accordingly. Do not depend on a fixed CPU performance.

Scripting Xtras

Use a unique string for your Xtra name (the first line of your message table)

Use unique names for any global handlers you define.

Make sure your global handler names don't conflict with any built-in scripting language commands, handlers, or keywords.

In Director, test your Scripting Xtra with both "manual" and "automatic" loading. Placing it in your Xtras folder(s) will cause it to be automatically loaded at start-up. Placing it elsewhere requires the Lingo programmer to open the file using "openxlib" and close it using "closexlib".

Javascript and Memory Issues

Here's a little more detail on the new behavior of memory management in Director MX 2004. The addition of JavaScript supoort has created some new memory behavior that may affect your xtra.

The JS Spider Monkey Engine reclaims memory by doing a mark-and-sweep garbage collection (gc) when it allocates over a certain threshold (currently 8Meg). Therefore, JS object are only finally reclaimed when this threshold is hit or when a _sytem.gc() call is made. When you use JS code you'll usually see memory usage creep higher, and then bounce down. Also if an Xtra instance is held in JS memory it will only be finally released when a gc is triggered. This can cause problems with existing Xtras that do critical clean up in their de-allocate method. Specifiically, if a global variable has the only reference to an xtra object, setting the value of the variable to zero will NOT cause the xtra object to get deleted because there is a JS "wrapper object" reference to the object.

The other thing to note is the JS Engine can get involved when you don’t expect it. Once *any* JS code is used the JS Engine is initialized and any subsequent setting of Lingo globals is also reflected in JS. To work around this issue scripting xtra developers can:

  1. Avoid all JS code. This is not really practical when you are offering your extra to other users, but it may be practical if you are using an xtra internally in projector based projects where you control the entire environment. Note: Director MX 2004's start-page-movie uses JS scripts.
  2. Use the undocumented _system.gc() call to explicitly free xtra instance references. This can be an expensive operation since JS has to scan thru all referenced objects.
  3. Add an explicit "close" or "finish" method method to your scripting xtra. I would strongly recommend this approach, since it does not involved the gc overhead, and example scripts can continue to work in pre-Director 2004 MX.

Tool Xtras

Use the "alwaysEnabled" setting if possible; this allows the host app to defer loading your xtra until it is actually selected. Otherwise, it must be loaded early for its GetEnabledState() method to be called to determine if it should be enabled or not.

Remember that Tool Xtras only function in the Director authoring application, not in projectors

Protecting your Xtras

If you want to ensure that your Xtras are used only by authorized customers should follow some simple guidelines.

Create two versions of your Xtras, one for authoring and one for playback. You can use the IMoaAppInfo interface (documented in the Moa Reference document) to determine whether the Xtra is being presented in the authoring or playback environment. To work correctly, each version of your Xtra should have the same CLSID for the various classes it implements. (Note that Tool Xtras work only in the authoring application.)

One way to implement protection is to simply choose not to register your Xtra if the context provided by IMoaAppInfo is wrong. Another way to provide protection for the authoring Xtra is to cache and test the serial number provided by IMoaAppInfo. You could implement a registration scheme for your Xtra using this technique:

* Implement IMoaRegister::Register() to request a registration number for your Xtra from the user. (You provide the algorithm for testing your registration numbers.)

* If the user inputs the registration correctly, get the host application registration number from IMoaAppInfo, and put it in the dictionary provided by AddRegistryEntry().

* Implement IMoaInitFromDict to compare the serial number in the registry with that provided by IMoaAppInfo, and to initialize the Xtra only if there's a match.

Note that this mechanism requires the user to re-register the Xtra for each authoring application that supports MOA Xtras. Note also that the serial number isn't provided in Director projectors, so it works only for authoring mode Xtras.

To protect media asset Xtras, you can tailor the functionality to the version. The authoring Xtra could be implemented to support features such as media and property editing and streaming data to disk. The playback version could support only features required for presentation, such as streaming data from disk and drawing.

You could also alter the drawing mode or some other user interface feature of your Xtra so that if it's run in the wrong context, it's immediately obvious to the user.

Be sure to advise authors in your Xtra documentation that they need to test their presentations using the playback version of the Xtra. You should also include a restriction in your licensing agreement that authors can only ship the playback version of the Xtra with presentations they create.


Streaming Files

Developers can use IMoaFile2 interface provided in Netfile to stream files from the internet.

This->pCallback->MoaCreateInstance(&CLSID_CNetFile, &IID_IMoaFile2, (PPMoaVoid)&This->pMoaFile);

Important: When streaming a file you must return control to Director/Shockwave periodically. You can't just sit in a code loop in your xtra waiting for the file download. The stream support code in Director (or the browser) needs to get control.

File Streaming Code Snippet

Here's a bit of code that can be used to find out if an entire file has been successfully downloaded. This illustrates how to check for EOF conditions for streams.

// returns TRUE if the file is completely streamed in from the net, or if there has been an error
// note that a kMoaStreamErr_DataNotAvail error just means that the information hasn't been 
// retrieved from the internet yet.
// returns FALSE if the file hasn't completely streamed in yet
MoaBool IsStreamDone(PIMoaStream2 piStream, MoaError *pErr)
{
	bool done = false;
	MoaError err;
	MoaStreamPosition endPos, length;

	*pErr = kMoaErr_NoErr;

	err = piStream->GetEnd(&endPos);
	if (err == kMoaErr_NoErr)
	{
		err = piStream->GetCurrentLength(&length);
		if (err == kMoaErr_NoErr && length >= endPos)
			done = TRUE;
	}
	if (err != kMoaErr_NoErr && err != kMoaStreamErr_DataNotAvail)
	{
		done = TRUE;
		*pErr = err;
	}

	return done;
}

Strings with Nulls

(thanks to G. Picher for the description of this technique)

Director Lingo can deal with strings that have null chars both as parameters and return values to Xtra methods. You can build a string of arbitrary length filled with nonzero characters and then use syntax like ...

put numToChar(0) into char 23 of someString

... to make a string to send to the Xtra. When the Xtra method receives it, rather than using ValueToString, you would use ValueToStringPtr to get to the data coming in. In Director 8.5 and later you can use ValueStringLength to figure out how large the data coming in is, including any null characters in the middle of the string but not including the final terminating null character. There was a bug in versions of Director before Director 8.5 (not sure how many versions going back) which kept ValueStringLength from working correctly in these situations; the workaround is to treat the .it element of the MoaMmValue coming in to the Xtra method as a MoaHandle, which you can use IMoaHandle::GetSize to get the size of (which *will* include the size of the final terminating null character). Note that this workaround stopped working in Director 8.5. So your code has to use IMoaAppInfo to figure out what Director version you are running in and handle it differently based on the version.

To return a string value from an Xtra method to Lingo, you can build a null terminated C string of arbitrary length made of of nonzero characters and use StringToValue to make a string. Then use ValueToStringPtr on the resulting MoaMmValue to get a pointer through which you can change the nonzero filler characters to 0. Lingo that examines this returned string can use syntax like ...

the number of chars in returnString
if(numToChar(0) = char 23 of returnString then ...

... to deal with null characters in the middle of the string. In either direction don't forget ValueReleaseStringPtr.

You simply can't do this in Authorware. String values you pass from a calc icon are copied into new MoaMmValue's before your Xtra gets to look at the string, using an algorithm that stops copying the string at the first null character. Similarly, the return value you set in callPtr->resultValue is returned as a copy to the calc icon scripting, chopping off the string at the first null character in the copied value. So if you want your Xtra to work in Authorware you need some sort of a solution to create an escape sequence for a null character (and an escape sequence to represent the literal character that begins your escape sequence). Another solution might be to pass and return a list, which can contain any number of sequential string elements not containing null characters, with integer elements with a run length of null characters between the string elements, if necessary.


Getting the Interface of an Xtra Asset for a Cast Member

In Director you may want to access the interface for an Cast Member that is based on an xtra. But there's no obvious way to do do this. Here is an approach that will work.

First, in lingo consider the following:

-- assume member 1 is an xtra based cast member
put member(1).interface
-- 8830092 -- this number is actually the interface reference
-- i don't think you can use this value from lingo, but you could pass it to an xtra if you want
-- you get an error if member(1) is not based on an xtra
-- There's no way to release this interface from lingo, so you just created a memory leak

And here's some pseudo C++ code that shows how to use this information in an xtra.

MoaMmValue item; // value reference to a cast member you got previously
PIMoaDrValue pValue; // value interface you got previously

PIMoaDrMove pMovie; // movie interface you got previously
MoaMmSymbol symInterface;
PIMoaDrCastMem pCastMem;
MoaDrCmRef assetCMR;
MoaMmValue assetInterface;

pValue->StringToSymbol("interface", &symInterface);
pValue->ValueToCMRef(&item, &assetCMR);
pMovie->GetCastMemFromCMRef(&assetCMR, &pCastMem);
pCastMem->GetProp(symInterface, &assetInterface);

The assetInterface valueType will be an integer, and the actual value is the xtra interface for the member. The player did an AddRef() on this interface before passing it on to you, so do a Release() when you're done with it.


Determining the Sound Volume

Here is a code snippet from Glenn Picher that shows how to determine the sound volume inside an xtra.

{
	MoaMmSymbol doSym;
	MoaMmValue callHandlerArgs[2];
	PIMoaMmUtils pMmUtils;
	PIMoaDrPlayer pPlayer;
	MoaLong iVolume;

	err = pObj->pCallback->QueryInterface(&IID_IMoaMmUtils, (PPMoaVoid)&pMmUtils);
	err = pObj->pCallback->QueryInterface(&IID_IMoaDrPlayer, (PPMoaVoid)&pPlayer);
	err = pMmUtils->StringToSymbol("do", &doSym);
	err = pMmUtils->StringToValue("return _sound.channel(1).volume", &callHandlerArgs[0]);
	err = pPlayer->CallHandler(doSym, 1, &callHandlerArgs[0], &callHandlerArgs[1]);
	pMmUtils->ValueRelease(&callHandlerArgs[0]);
	pMmUtils->ValueToInteger(&callHandlerArgs[1], &iVolume);
	                pMmUtils->ValueRelease(&callHandlerArgs[1]);
	pPlayer->Release();
	pMmUtils->Release();
}

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