Flirt
an open source extensible Flash™ runtime

Extension API

I got this working last night at 3.30am, with a more than a few empty PBR cans on the desk. (I wonder if I could get some kind of sponsorship deal?) It seems to work. It will probably change in the future.

Adding a new class to the ActionScript environment only requires two functions; one to create the class:

ddActionClass*
ddPlayer_addClass
(
    ddPlayer* player,
    ddActionClass* superclass,
    const char* name,
    ddNativeFunction constructor,
    int nargs
);

and another to assign methods to it:

void
ddActionClass_addNativeMethod
(
    ddActionClass* classObject,
    char* name,
    ddNativeFunction function,
    int nargs
);

Here's a very short function which creates a new "Image" class with the render, setWidth, and setHeight methods:

void
setupImageClass(ddPlayer* player)
{
  ddActionClass* class =
    ddPlayer_addClass(player, ddActionObjectClass, "Image", Image_constructor, 1);
	
  ddActionClass_addNativeMethod(class, "render", Image_render, 1);
  ddActionClass_addNativeMethod(class, "setWidth", Image_setWidth, 1);
  ddActionClass_addNativeMethod(class, "setHeight", Image_setHeight, 1);
}

We still have to implement these methods, though, so we'll have to create some functions of the type Flirt likes. A ddNativeFunction is defined here:

typedef ddActionValue
  (*ddNativeFunction)(ddActionObject* object, ddActionContext* context, int nargs);

That is, it takes as arguments: an object, an actionscript context, and the number of arguments the function wants (FUNCTION_VARARGS if it can vary), and returns a ddActionValue. Chances are, we'll want to create a custom data type. We could use normal object properties to store all of our object state, but that's not very flexible; instead, we define a struct that extends the ddActionObject struct so that it can be safely passed around as a ddActionObject*, but carries along our custom data:

struct imageObject
{
	ddActionObject parent;
	ddActionMovieClip* clip;
	int width;
	int height;
};

typedef struct imageObject ImageObject;

Normal method implementations are pretty straightforward compared to the constructor method, so let's look at the more complex code. Remember that our functions have to be ddNativeFunction types, like this:

ddActionValue
Image_constructor(ddActionObject* unused, ddActionContext* context, int nargs)
{

We want to pass a MovieClip object to our constructor, which is why we specified 1 for the nargs argument in ddPlayer_addClass()—that's the movie clip that we'll render and save with our render() method. Here we pull the passed argument off the script context stack, pull the object out of it (if it exists), and see if it's actually a MovieClip object:

  ImageObject* image;
  ddActionValue value = ddActionContext_popValue(context);
  ddActionObject* object = ddActionValue_getObjectValue(value);

  if ( !ddActionObject_isKindOfClass(object, ddActionMovieClipClass) )
    return ddNullValue;

If that worked, we create an object of our custom type and initialize it as an object:

  image = malloc(sizeof(struct imageObject));

  ddActionObject_init((ddActionObject*)image);

The previous function has marked our object as an Object type, so we have to override that with our custom Image class, otherwise this object will only act like an Object and not an Image. Currently, here's how you do that:

  ddActionObject_setClass((ddActionObject*)image, 
    (ddActionClass*)ddActionObject_getPrototype(unused));

But that's pretty ugly. The "unused" object passed in (that's normally the this object in a function, but there is no this in a constructor) holds a reference to the called class in its prototype. I could pass the class in as that object, but that conflicts with some weird subclassing issues. This will doubtless change soon.

Now we set our custom object attributes. First, we keep a reference to the MovieClip object that was passed in:

  image->clip = (ddActionMovieClip*)ddActionObject_retain(object));

We have to call the ddActionObject_retain() function on the object so that the script environment won't delete the object out from under us if all other references to the object expire.

Finally, we set the rest of our default values and return the new Image object:

  image->width = 0;
  image->height = 0;

  return dd_newActionValue_object((ddActionObject*)image);
}

One last thing—remember how we retained that movie clip object? Shouldn't we un-retain it when we're done? Well, yeah, but I haven't added the API for that yet. If you dig into the source, you'll see that classes have hooks for custom destructors as well as overriding the normal object properties set and get functions (for example, we could do image.width = 300 instead of image.setWidth(300)). If we were doing this properly, we'd include a destructor that released the movie clip when the Image object itself expired. For now, we'll let it leak..

Using your extension

The Flirt source tree contains the full Image object implementation in imageext.c, and a demo program exttest.c shows how it's used. The Makefile knows about it, so you can just do make exttest in the root of the source tree. The OS X project also includes the Image class in its runtime. The Flash movie exttest.swf in the source package demonstrates usage of the Image class; it has a bunch of squiggles in the root clip, and this simple actionscript on frame one of the root timeline:

image = new Image(_root);

if ( image.render('test.png') )
  trace('rendered');
else
  trace('failed');

Run exttest exttest.swf, and if all works properly it renders the contents of the _root clip into the file test.png. Just like the previous example does, but this is done via script code. And that's good.

All content copyright (C) 2004 Dave Hayden except where noted otherwise.
Macromedia and Flash are registered trademarks of Macromedia, Inc. in the United States and/or other countries.