views:

463

answers:

4

Background

I am developing a highly modular application in pure Action Script 3 (we are using Flex 4 SDK to automate our builds, but all our code must be able to compile directly in Flash CS4 Professional).

We have a "framework.swc" file which contains interface definitions which are shared between all our modules, we have a "mainmodule.swf" that loads our other modules, and then we have various .swf files for our other modules. We are using the Loader class, in conjuction with ApplicationDomain::getDefinition() for loading classes dynamically [we use "new LoaderContext(false,ApplicationDomain.currentDomain)"].

Problem

All our modules implement the "AbstractModule" interface, which is defined in "framework.swc". When I instantiate a dynamically loaded module, however, (module is AbstractModule) returns false. More importantly, if I call module.someMethod(someobject), where someobject implements an interface defined in "framework.swc" and where the module's method expects an object of the same interface defined in "framework.swc", I get a runtime error "TypeError: Error #1034: Type Coercion failed: cannot convert _ to _."

It seems that "mainmodule.swf" and "loadedmodule.swf" (the module I have been loading for testing), are, internally, using separate definitions for the shared interfaces in "framework.swc"

Question

How can I make "mainmodule.swf" and "loadedmodule.swf" resolve their common interfaces to a shared definition, so that class casting and class comparison succeed correctly?

A: 

you should try -compiler.external-library-path as documented here ... that way, you can build one swc having dependancies on an interface, that is not in it, but comes from another, thus avoiding collisions ... don't know how to do that in CS4 though ...

back2dos
It seems that when this is done, the *.swc file must be distributed with the *.swf file. We would like to distribute only *.swf files.
Michael Aaron Safyan
A: 

You may want to use a Runtime Shared Library (RSL). An RSL allows you to do dynamic linking. However, I don't know if CS4 can build those. Maybe you could reconsider the "must be able to compile directly in Flash CS4" requirement, or look into compiling using the Flex SDK through macros/scripts in the CS4 IDE.

If that's absolutely not an option, another approach would be to have a looser coupling between modules, and rely on more of an implied common external interface for the module SWFs instead of code-level integration.

Jacob
Unfortunately, I am not able to waive the requirement that it build with Flash CS4 Professional. All this code is eventually going to be used in *.fla projects with embedded artwork and other media. Unless there is a way to automate building the *.fla files, building in Flash CS4 is a strict requirement. The automated build was kind of my own thing, so that I could be more productive. I'm more of a UNIX guy, so I like to build it with "make". ;)
Michael Aaron Safyan
+1  A: 

Ok. This isn't the prettiest solution, but it will work. Basically, for every interface "AbstractX" (replace "X" with something else), you need to create two wrapper classes: "ImportX" and "ExportX". The goal of ExportX is to successfully widen AbstractX to type Object, by wrapping an AbstractX, providing all the same methods as type AbstractX, but to use only builtin/predefined data types or data types which are part of flash in their signatures. The goal of ImportX is to narrow a dynamically loaded object with the same characteristics as type AbstractX (but which cannot be cast to type AbstractX and which is not recognized as type AbstractX) but is of type Object to the AbstractX interface. Both ExportX and ImportX use ImportY, ImportZ, etc.; however, ExportX uses ImportY, ImportZ, etc. to wrap parameters, which it delegates to an object of type AbstractX, while ImportX uses them to wrap return values, which come about from delegating to an object of type Object. To make this a little more understandable, I present the following examples:

public interface AbstractX
{
    // The export/import functions are mandatory 
    // for all such interfaces. They allow
    // for the wrappers to be correctly manipulated.
    function export() : Object;
    function original() : Object;

    // The interface functions vary from 
    // interface to interface. They can
    // be called something much more appropriate.
    function interfaceFunction1(param : AbstractY) : AbstractZ;
    function interfaceFunction2(param : AbstractA) : AbstractB;
}
// A class of type Import_ always implements Abstract_
public class ImportX implements AbstractX
{
    // The constructor for an Import_ Object
    // is always of type Object.
    public function ImportX(obj : Object) : void {
        _loadedobj = obj;
        _exportobj = obj.export();
    }

    // Every Import_ class must implement a similar "wrap" function:
    public static function wrap(obj : Object) : AbstractX {
        var result : AbstractX = null; 
        if ( obj != null ){
            if ( obj is AbstractX ){ // Don't wrap if convertible, directly.
                result = obj as AbstractX;
            }else if ( obj.original() is AbstractX ){ // Don't double wrap
                result = obj.original() as AbstractX;
            }else{
                // Needs to be wrapped.
                result = new ImportX(obj);
            }
         }
         return result;
     }

    public function export() : Object {
        return _exportobj;
    }

    public function original() : Object {
        return _loadedobj;
    }

    // For the interface functions, we delegate to _exportobj
    // and we wrap the return values, but not the parameters.
    public function interfaceFunction1(param : AbstractY) : AbstractZ {
        return AbstractZ.wrap(_exportobj.interfaceFunction1(param));
    }

    public function interfaceFunction2(param : AbstractA) : AbstractB {
        return AbstractB.wrap(_exportobj.interfaceFunction2(param));
    }

    private var _loadedobj : Object;
    private var _exportobj : Object;
}
// Although an Export_ object provides SIMILAR methods to type Abstract_,
// the signatures need to be changed so that only builtin/predefined types
// appear. Thus Export_ NEVER implements Abstract_.
public class ExportX
{
    // The constructor to Export_ always takes an object of type Abstract_
    public function ExportX(obj : AbstractX) : void {
        _obj = obj;
    }

    public function original() : Object {
        return _obj;
    }

    public function export() : Object {
        return this;
    }

    // For the interface functions, we delegate to _obj
    // and we wrap the parameters, not the return values.
    // Also note the change in signature.
    public function interfaceFunction1(param : Object) : Object {
        return _obj.interfaceFunction1(AbstractY.wrap(param));
    }

    public function interfaceFunction2(param : Object) : Object {
        return _obj.interfaceFunction2(AbstractA.wrap(param));
    }

    private var _obj : AbstractX = null;
}
// The definition of class X can occur in and be loaded by any module.
public class X implements AbstractX
{
    public function X( /* ... */ ) : void {
        //...
    }

    public function export() : Object {
        if ( ! _export ){
            _export = new ExportX(this);
        }
        return _export;
    }

    public function original() : Object {
        return this;
    }

    public function interfaceFunction1(param : AbstractY) : AbstractZ {
        // ...
    }

    public function interfaceFunction2(param : AbstractA) : AbstractB {
       // ...
    }

    private var _export : Object = null;
}
// Ok. So here is how you use this...
var classx   : Class = dynamicallyLoadClassFromModule("X","module.swf"); 
var untypedx : Object = new classx();
var typedx   : AbstractX = ImportX.wrap(untypedx);
// Use typedx ...
Michael Aaron Safyan
Note: "dynamicallyLoadClassFromModule" doesn't exist. You have to create it. It is here for illustrative purposes only.
Michael Aaron Safyan
This is tested and works. Unfortunately, it requires 3 additional classes/interfaces for every dynamically loaded class. If there is a better, reliable way to do this, I'd be interested in hearing it.
Michael Aaron Safyan
A: 

I'm not 100% sure if its what you need, but Gaia Framework implements a global API, shared by many swfs to interact with each other. You could check it out and maybe get some ideas. Right now I'm confronted with quite a similar situation as yours, so I'm checking alternatives... this post will be very useful, thanks!

Cay
Thanks for the link. Unfortunately, I cannot use Gaia in my current project, as it is commercial and therefore we are unable to use software licensed under the GNU General Public License.
Michael Aaron Safyan