views:

515

answers:

3

I have run into a very strange problem with Flash and Flex. It appears that under certain circumstances, movie clips from a SWF loaded at runtime (using Loader) cannot be instantiated if another SWF has been loaded in the mean time. Here is the complete code for a program that reproduces the error. It is compiled using mxmlc, via Ensemble Tofino:

package 
{
    import flash.display.*;
    import flash.events.*;
    import flash.net.*;
    import flash.system.*;

    public class DynamicLoading extends Sprite
    {
        private var testAppDomain:ApplicationDomain;

        public function DynamicLoading()
        {
            var request:URLRequest = new URLRequest("http://localhost/content/test.swf");
            var loader:Loader = new Loader();
            loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onTestLoadComplete);
            loader.load(request);
        }

        private function onTestLoadComplete(e:Event):void
        {
            var loaderInfo:LoaderInfo = LoaderInfo(e.target);
            testAppDomain = loaderInfo.applicationDomain;

            // To get the error, uncomment these lines...
            //var request:URLRequest = new URLRequest("http://localhost/content/tiny.swf");
            //var loader:Loader = new Loader();
            //loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onTinyLoadComplete);
            //loader.load(request);

            // ...and comment this one:
            onTinyLoadComplete();
        }

        private function onTinyLoadComplete(e:Event = null):void
        {
            var spriteClass:Class = Class(testAppDomain.getDefinition("TopSymbol"));
            var sprite:Sprite = Sprite(new spriteClass());

            sprite.x = sprite.y = 200;

            addChild(sprite);
        }
    }
}

With the second loading operation commented out as shown above, the code works. However, if the second loading operation is uncommented and onTinyLoadComplete runs after the second SWF is loaded, the line containing new spriteClass() fails with the following exception:

TypeError: Error #1034: Type Coercion failed: cannot convert flash.display::MovieClip@2dc8ba1 to SubSymbol.
    at flash.display::Sprite/constructChildren()
    at flash.display::Sprite()
    at flash.display::MovieClip()
    at TopSymbol()
    at DynamicLoading/onTinyLoadComplete()[C:\Users\...\TestFlash\DynamicLoading.as:38]

test.swf and tiny.swf were created in Flash CS4. test.swf contains two symbols, both exported for ActionScript, one called TopSymbol and one called SubSymbol. SubSymbol contains a simple graphic (a scribble) and TopSymbol contains a single instance of SubSymbol. tiny.swf contains nothing; it is the result of publishing a new, empty ActionScript 3 project.

If I modify test.swf so that SubSymbol is not exported for ActionScript, the error goes away, but in our real project we need the ability to dynamically load sprite classes that contain other, exported sprite classes as children.

Any ideas as to what is causing this, or how to fix it?

Edit: A couple of people have suggested that tiny.swf may contain a class with the same name as a class from test.swf or the parent (DynamicLoading.swf). It does not. As I said above, I created tiny.swf myself by simply publishing a brand-new, empty Flash CS4 project. Here is the complete output of svndump -D when run on tiny.swf:

[HEADER]        File version: 10
[HEADER]        File is zlib compressed. Ratio: 41%
[HEADER]        File size: 1343
[HEADER]        Frame rate: 30.000000
[HEADER]        Frame count: 1
[HEADER]        Movie width: 550.00
[HEADER]        Movie height: 400.00
[045]         4 FILEATTRIBUTES as3 symbolclass
[04d]      1284 METADATA
[009]         3 SETBACKGROUNDCOLOR (ff/ff/ff)
[056]        11 SCENEDESCRIPTION
[001]         0 SHOWFRAME 1 (00:00:00,000)
[000]         0 END
A: 

I've seen funky behavior when two swfs are loaded. The issues arises when the two swfs have different versions of the same class. Check to make sure that the SubSymbol instance within TopSymbol is the same as the the SubSymbol you're loaded directly.

ablerman
What do you mean by "the SubSymbol you've loaded directly"? The only SubSymbol is the one within TopSymbol.
Aaron
A: 

This does seem like problem due to having more than one class with the same name. Since the code works as long as tiny.swf is not loaded, I'm incline to think that's our culprit. Make sure the library is indeed empty.

Thyrius
I checked tiny.swf using swfdump; see the results in my edit to the original question. tiny.swf is indeed empty, which is why this problem is so confusing.
Aaron
A: 

According to Adobe's documentation, if you want to directly access symbol classes in a loaded SWF (such as TopSymbol), you should specify the current application domain in the loading context. So for me, the bigger question is why your first example with the second loading commented out works. My only guess is that the complete event may be executing in the context of the Loader's current application domain.

Try explicitly loading test.swf into the current application domain, like this:

var context:LoaderContext = new LoaderContext();
context.securityDomain = SecurityDomain.currentDomain;
context.applicationDomain = ApplicationDomain.currentDomain;

var request:URLRequest = new URLRequest("http://localhost/content/test.swf");
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onTestLoadComplete);
loader.load(request, context);

Edit:

Since you're wanting to make sure dynamically loaded SWFs are loaded in a separate application domain, you may want to explicitly create your ApplicationDomain object for each loading. I have no idea what Loader uses for its app domain by default. Try something like this:

public class DynamicLoading extends Sprite
{
    private var testAppDomain:ApplicationDomain;
    private var tinyAppDomain:ApplicationDomain;

    public function DynamicLoading()
    {
        testAppDomain = new ApplicationDomain();
        var context:LoaderContext = new LoaderContext(false, testAppDomain);
        var request:URLRequest = new URLRequest("http://localhost/content/test.swf");
        var loader:Loader = new Loader();
        loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onTestLoadComplete);
        loader.load(request, context);
    }

    private function onTestLoadComplete(e:Event):void
    {
        tinyAppDomain = new ApplicationDomain();
        var context:LoaderContext = new LoaderContext(false, tinyAppDomain);
        var request:URLRequest = new URLRequest("http://localhost/content/tiny.swf");
        var loader:Loader = new Loader();
        loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onTinyLoadComplete);
        loader.load(request, context);
    }

    private function onTinyLoadComplete(e:Event = null):void
    {
        var spriteClass:Class = Class(testAppDomain.getDefinition("TopSymbol"));
        var sprite:Sprite = Sprite(new spriteClass());

        sprite.x = sprite.y = 200;

        addChild(sprite);
    }
}    
Jacob
You're right that loading the SWF into the current application domain causes the error to go away, but I want to load the SWF into a separate domain so that I can unload it later. (In my actual project, the main SWF will be loading potentially dozens of child SWFs, but only one at a time, so I would like to be able to save resources by unloading those child SWFs when they are done being used.) Again according to [Adobe's documentation](http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/system/LoaderContext.html#applicationDomain), the default when no LoaderContext is specified...
Aaron
...(as in my code above) is to load the content into a child of the loader's application domain. In this case, "if the parent wishes to use the child's classes, it must call ApplicationDomain.getDefinition() to retrieve them." That is exactly what my code does, and that is why it works with the second loading commented out. What I want to know is, why should uncommenting the second loading make any difference? And more importantly, if I want to be able to load and unload multiple SWFs, how can I get it to work?
Aaron
I'm not sure why the second loading is different, unless the `complete` event runs in a different context. If that was true, then it would make a difference if you were running your constructor in a callback after loading test.swf as opposed to a callback after loading tiny.swf. What happens if you still load tiny.swf but do your sprite instantiation in later code (in response to a button press, etc.)?
Jacob
Also, does testAppDomain.getDefinition("SubSymbol") work? Maybe the getDefinition("TopSymbol") doesn't do a cascading load of dependencies.
Jacob
Both of your comments sounded promising, but unfortunately neither worked. I tried moving the `getDefinition` call into a button click handler, but still got the same message. `testAppDomain.getDefinition("SubSymbol")` does work, but only because SubSymbol does not contain any further children that have their own class types (remember, the error is a type coercion error). Doing `getDefinition("SubSymbol")` followed by `getDefinition("TopSymbol")` also doesn't help.
Aaron
Maybe explicitly creating your `ApplicationDomain` objects would cause more consistent behavior. Take a look at my edit.
Jacob
Nope, that doesn't seem to make any difference either. I think I'm just going to give up on this and work around it by making sure my dynamically loaded symbols never contain child symbols of custom class types. In my particular project that is a reasonable constraint, and it will enable me to move on.
Aaron