4
votes

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 swfdump -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
4
You might want to run swfdump on test.swf and tiny.swf to see what is inside of them.James Ward
I ran swfdump on both files. test.swf contains three classes, test, TopSymbol, and SubSymbol, as expected. tiny.swf contains no classes at all; see the swfdump output in my edit above. Thanks for pointing me to swfdump; I can see that it's a useful tool. However, this does not solve my original problem.Aaron

4 Answers

1
votes

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.

1
votes

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.

1
votes

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);
    }
}    
1
votes

To rule out the many bugs in Adobe's loading classes, i'd definitely use the greensock LoaderMax library... http://www.greensock.com/loadermax/ Simple, free + powerful.