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.
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.
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
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.
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.
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.
GetMemFragment()
to load Xtras. However,
the Metrowerks debugger won't properly debug code fragments loaded
with this call. However, if you add a file in the Xtras folder
named "_XtraDebugMode_", the call GetDiskFragment()
is always used to load PPC Xtras, regardless of the VM state.
This trick is intended for debugging purposes ONLY; your Xtras
should be tested without this workaround in place before shipping.)
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>
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
#include <Quickdraw.h>
#define qd (*((QDGlobals *) (*((GrafPtr **) LMGetCurrentA5()) + 1) - 1))
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).
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).
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
This means that cast members 3, 4, 5, 6, 10, 11,
12, 13, and 14 are selected.
[[3,5], [6,6] [10, 14]]
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.
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.
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).
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.
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. Y
ou 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.
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:
auxInfo
structure, optionally having Director dither it if necessary
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.
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; }
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.
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.
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 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.
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.
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.
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
.
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.
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"
To evaluate an EXPRESSION like
nArgs: 1
arg[1]: string MoaMmValue containing "open window foo"
or "set the loc of sprite 3 to point(5,10)"
pResult: NULL
"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
openxlib
to force opening a Lingo Xtra, don't include
the extension -- use openxlib("D:\Special\MyXtra")
, not
openxlib("D:\MyXtra.x32")
.
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.
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.
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
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 L
ingo
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.
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.
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".
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:
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
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.
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.
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; }
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.
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.
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(); }