blog@tomauger.com


Interoperability between two Flash EXE Projectors (or how to dynamically read SharedObjects across two SWFs)

Posted in Technology, Tips and Tricks, Flash / ActionScript, Web Development by admin on the June 7th, 2008

So here’s a real doozie that came from a question someone asked on ExpertsExchange: he had two separate Flash Projectors (EXEs) - that needed to be able to intercommunicate (the first projector needed to know when the second projector had actually finished loading). Since we’re dealing with external projector files that have no dependencies (rather than using the movieClipLoader to load an external SWF into the parent movie), the only thing I could think of that would work reliably was local SharedObjects.

As I put together a solution for this brave soul, I encountered a few interesting challenges, and a few frustrations. Thought I’d document it here because I couldn’t find anyone else who had addresses the intricacies of having two SWF share a SharedObject and monitor the status of that shared SharedObject so they could respond to a change in status.

Here’s the basic principle:

  • Application 1 creates a local SharedObject and sets an initial status
  • Application 1 also registers an enterFrame event listener that listens for a status change on the SO
  • Application 2 accesses the same SO and at some point, changes the status
  • Application 1’s enterFrame handler detects the status change, and responds to the event.

As I write this, I see there’s an opportunity for a custom event class that does all this, but I’m going to leave that to the real gurus. I don’t pretend to have that intimate a knowledge of ActionScript to be able to pull that off. I should also point out at this time that the original EE user’s question was for AS2, so this solution is only tested in AS2, though since the issues I was running into were related to variable scope, I would only imaging that an AS3 implementation would be easier, if anything.

Here are the challenges I encountered:

  • how to launch one EXE Projector from another EXE Projector
  • how to share a SharedObject between two Flash movies (SWFs)
  • how to continuously monitor the value of a shared SharedObject

Launching an EXE from a Flash projector

This one’s pretty straightforward, if you Google it, and read some LiveDocs documentation. It tells us:

exec Path to application Executes an application from within the projector.

Well that sounds easy, doesn’t it? But you need to read the fine print:

The exec command can contain only the characters A-Z, a-z, 0-9, period (.), and underscore (_). The exec command runs in the subdirectory fscommand only. In other words, if you use the exec command to call an application, the application must reside in a subdirectory named fscommand. The exec command works only from within a Flash projector file.

Did you get that? Let’s break it down:

  • This particular fscommand ONLY works from within a Projector. In other words: not an SWF or a webpage. Good luck testing this out using CTRL+Enter - won’t work.
  • The EXE that you’re trying to launch must reside in a subdirectory of the root directory in which the calling Projector resides.
  • This subdirectory must be called “fscommand”.

Okay, I get it. Security reasons, right? Fair enough. Good to know. All right, moving on to:

Sharing a SharedObject between two Flash movies

Next up - getting two different SWFs to share a local SharedObject. This one is also pretty easy to find if you Google it. In case you don’t know, a SharedObject is the “Flash cookie”. Unlike JS cookies, the sharedObject can store anything, including Objects, so it’s more powerful than a Flash cookie. Creating and accessing a SharedObject from within a single SWF is child’s play:

var mySO:SharedObject = SharedObject.getLocal("cookieName");

This creates a new SharedObject on the local hard drive called “cookieName” (obviously, you call it what you want). From that point on, you access the data that you want using your new SharedObject instance’s data object (mySO.data). Armed with this, we’ll want to create our first test application to see the SharedObject in action:

// remember this is all AS2, so I haven't tested for AS3
var mySO:SharedObject = SharedObject.getLocal("test");
trace(mySO.data.numRuns);
if (mySO.data.numRuns){  
  mySO.data.numRuns++;  
} else {  
  mySO.data.numRuns = 1;  
}

The first time you test or run this application, it will trace “undefined”, because the SharedObject had no data in it. But, the last part of the code sets the value to 1. When you exit the application, this data is then stored to the SharedObject.When you run the demo application a second time, it now reads 1 and so increments it by 1, so when the app exits, it writes 2 to numRuns. And so it goes.

So it stands to reason that if we just cloned this application to a new SWF with a different name, and ran it, it should pick up where the other app left off. After all, they’re both accessing the same SharedObject named “test”, aren’t they? Not really. The application path is somehow embedded into the SharedObject’s file path on the local hard drive, so there will actually be two copies of the “test” SharedObject created.

 Unless you pass “/” as the subdomain for the SharedObject. Like this:

var mySO:SharedObject = SharedObject.getLocal("test", "/");

This magic bit of code makes the local SharedObject file available to any SWFs on that hard drive. So BE CAREFUL would you? Don’t put any really critical information into these shared objects (like passwords, credit card info, your mother’s social insurance number) in there, because any other SWF that guesses at the SharedObject’s name can read it, iterate through the .data object and have access to all the goods. Just be smart.

So, we modify the code like this, and clone the SWF to a second SWF, and when we run them, they both access the same SharedObject:

var mySO:SharedObject = SharedObject.getLocal("test", "/");
trace(mySO.data.numRuns);
if (mySO.data.numRuns){ 
	mySO.data.numRuns++;}
else {    

 	mySO.data.numRuns = 1;    

}

So, now on to….

Continuously Monitoring a SharedObject using an EnterFrame loop

Now it stands to reason that if, instead of just reading the sharedObject’s value once, if we read the value in an EnterFrame loop, we’ll know exactly when that value changes. Right? Well, let’s see. In this next example, I have added a dynamic text field called txDisplay so that we don’t have to trace, which means that we don’t need to use the debugger version (which makes it easier to have both clones open as SWFs at the same time). I also changed the behaviour so that the counter increments every time we click a button called btUpdate. This allows us to trigger the update multiple times without having to close the app each time.

var mySO:SharedObject = SharedObject.getLocal("test", "/");
btUpdate.addEventListener("click", incrementCounter);
function incrementCounter(evt:Object){ 
	if (mySO.data.numRuns){    

  		mySO.data.numRuns++;    

 	} else {    

  		mySO.data.numRuns = 1;    

 	}    

 	mySO.flush(); // write the data immediately    

}
onEnterFrame = function(){ 
	txDisplay.text = mySO.data.numRuns;
}

Now the mySo.flush() method call is extremely important here, because without that, the SharedObject is actually only updated when the app closes. This creates some really odd behaviour, as you think the counter is increasing (because the textfield is saying so) but the file on disk is not. The SharedObject uses the cached mySO variable to store and retrieve the data, cutting down on disk access time I guess. Normally this wouldn’t be a problem, except that we have two files working with the same SharedObject, so unless the SO updates each and every time we change its value, there’s no way for the other app to read it.

No if you’ve tried this out, and you’ve created two SWFs (perhaps soTest1.swf and soTest2.swf) and played around, you’ll notice that if you have both SWFs open at the same time, incrementing the counter on one of them does NOT register with the second one, even though we’re monitoring the SharedObject’s data 12 times a second (onEnterFrame). However, if you change the counter in one, and THEN open the second one, the second one registers the same counter value as the first, so clearly they ARE working with the same SharedObject.

So what gives?

Well, I’m not In The Know, so I can only guess. But it appears that even though we’re flush()ing the SO instance, which is WRITING out the data, nothing can convince that SharedObject that the data it has in memory (in the AS variable) might actually be different from the data on the hard drive (in the local SharedObject file), so it will never READ it back in from disk. And no amount of coercing (that I’ve found) will get it to do it.

Calling mySO.getLocal() a second time doesn’t work. In fact it just chokes at that point and you get an empty value altogether. flush()ing mySO before read does bubkus. Even assigning undefined() to mySO and then trying to re-initialize the variable with a new call to SharedObject.getLocal() does sweet Jack-all. Now maybe that’s just because I never really figured out how to properly destroy an AS variable (seems delete(mySO); doesn’t work either). Bottom line though, as long as ActionScript keeps a reference to the SharedObject instance variable, it will never go back to the local SharedObject file on the hard drive to see if the value there syncs up.

So, since I don’t know how to destroy a variable (you gurus out there who might stumble across this remote blog entry out on the very periphery of the known blogosphere, please do leave a comment and let me know), there’s only one recourse: scope.

Thank God scope works properly now. Maybe it was just me, but five years ago, scope in AS was worthy of pulling out hair. I’m still not 100% convinced that in AS2 it works as nicely as in other languages (like Perl, for God’s sake), but at least it works well enough to do what I wanted it to do here.

In a nutshell, we don’t create a SharedObject instance variable with global (by which I mean root-level) scope. We create a tightly scoped instance variable every time we write out the new value, and we use a tightly scoped instance variable (which for all intents and purposes is a different variable each time, though the name remains the same) in our enterFrame loop to force it to read.

There may be memory implications here, and there is almost surely a performance hit by creating and destroying the local SharedObject instance variable 12 times a second, so use judiciously. Again, just be aware of this, and be smart.

Here’s the final code, with everything put into their little scoped compartments:

btUpdate.addEventListener("click", incrementCounter);
function incrementCounter(evt:Object){ 
	var mySO:SharedObject = SharedObject.getLocal("test", "/");
 	if (mySO.data.numRuns){  
		mySO.data.numRuns++; 
	} else {    

  		mySO.data.numRuns = 1;    

 	}    

 	mySO.flush();    

}
onEnterFrame = function(){ 
	var mySO:SharedObject = SharedObject.getLocal("test", "/"); 
	txDisplay.text = mySO.data.numRuns;    

}

Summing it up

As I get to the end of this I wonder to myself - who the hell will ever want to do this anyway? Most asnychronous activities between two apps involve a database, inter-application communication between two SWFs in the same domain on the same webpage can just use ExternalInterface and a small JavaScript hook to talk. But hey, I spent two hours on a Saturday morning figuring this shite out for 2000 measely points on EE that had ALREADY been awarded to me for a non SharedObject solution to the poster’s question, not to mention another 3 hours writing this thing up (and, to be honest, installing a WordPress TinyMCE plugin so I can make better use of semantic markup without switching to Code View every 50 seconds). So dammit, I’m hitting Publish!






	

Leave a Reply

You must be logged in to post a comment.