Archive for August, 2007

Async APIs and Callback Functions

OK, let’s start digging into asynchronous APIs–starting simple.

Perhaps the most basic and obvious technique for dealing with asynchronous APIs is to deal with the events via callback functions. For example:

class SomeClass {

var fs:FileStream = new FileStream();

function someFunction( file:File ):void {

fs.addEventListener( Event.COMPLETE, onFileStreamComplete );
fs.openAsync( file, FileMode.READ );


When the file has been completely read, the function onFileStreamComplete will be "called back" by the FileStream. At that point all the bytes of the file are available to read:

function onFileStreamComplete( event:Event ):void {

var firstInt:int = fs.readInt();
// ... etc. ...

I said we’d start simple, and that’s almost all there is to this approach. However, there is one subtle issue to be aware of. Note in the example above I made the FileStream a member variable of SomeClass. This is convenient because it makes the FileStream easily available in the callback function.

Making the FileStream a member variable is also essential. If the FileStream were instead declared inside someFunction(), it would go out of scope when someFunction() returned and could be garbage collected before the file is read or events are delivered. Many people are surprised by this, but note that the only reference to the FileStream object is the member variable. The FileStream object has references on event listeners, etc. but only references to an object will protect it from collection.

Now, this will almost never happen to you because (a) the garbage collector runs incrementally and it’ll take it a while to collect the FileStream object and (b) most file reads will finish and dispatch a complete event long before then. But it can happen, which of course means it’ll probably only show up when doing big important demos.

While this is a fine technique for simple programs, there are a couple of problems with it for larger endeavors:

  1. The number of callback functions increases quickly as program size goes up. It becomes difficult to keep track of each callback and what it does.
  2. Code for performing a single logical operation (i.e., read the contents of a file) is spread across multiple functions, making it more complex than necessary.

Tune in next time for techniques that help mitigate these problems.

Partial Explanation of MSI Feature Selection

I really do want to write some entries about handling async APIs as I recently promised, but I’ve been engaged in hand-to-hand combat with some installer issues recently, so for now–a post about installers.

Installation of both Adobe AIR itself and AIR applications is done via Windows Installer–more commonly referred to as MSI. Using MSI is the doing the "right thing", but it certainly isn’t the easy thing.

One of the most frustrating aspects of MSI is that its internal model is opaque. I can only assume it’s terribly complicated; it certainly isn’t documented. And while usually the inner workings of a technology can be gleaned not only from documentation but also its API, the MSI API is itself rather convoluted. Whether that’s because the underlying model is convoluted or just obscuring that model I just don’t know.

(By the way, for a great discussion of internal models of operation and how they’re surfaced in everyday things like refrigerators, check out Don Norman’s classic, "The Design of Everyday Things".)

With that introduction, here’s my best attempt at a partial explanation of how features are selected for installation or removal during an MSI install operation. At the very least this will serve to document what I just spent several hours figuring out.

Every feature has a level assigned to it, the level being a non-negative number. Unless you override the defaults, MSI will install every feature with a level of one. A feature with level zero is never installed, and in fact setting a feature level to zero can be a dangerous thing to do–but that’s a topic for another post. Features with higher numbers can be installed by changing the INSTALLLEVEL property, but that’s not what this post is about either so no more on that topic.

You can also change the set of installed features using a set of properties. There are many of these, but for my purposes I’ll stick with just ADDLOCAL, REMOVE, and REINSTALL. Once you set one or more of these properties the level-based behavior above no longer applies. Instead, very feature listed in ADDLOCAL is installed, and every feature in REMOVE is–well, removed. Features in REINSTALL are essentially patched–if they’re installed.

Now, you might be wondering about REINSTALL, since it sounded like we were talking about install scenarios–not patches. But–and here’s one of those strange parts of the MSI model–in MSI, any "installation" can add, remove, or patch features. In other words, what you might naively call installs, patches, and uninstalls.

(To make things even weirder, other parts of MSI are inconsistent about this. For example, the "msiexec" command line tool does, as the model would predict, allow you to uninstall a product using the /i (install) option. But it also has a /x (uninstall) option. The latter would seem to suggest that the model included a notion of uninstalling, but I’m pretty sure it doesn’t. The API, for example, doesn’t have an uninstall function–products are typically uninstalled by calling MsiConfigureProduct. Go figure.)

Let me illustrate with a simple scenario. Suppose I have a product P with three features, F1, F2, and F3:

To install both features I can either (a) not set any properties, because they’ll be installed by default or (b) set ADDLOCAL=F1,F2,F3.

If I want to install just F1 and not F2 or F3: ADDLOCAL=F1.

Finally, suppose F1 and F2 are installed (but not F3) and I wish to patch F1, install F3, but uninstall F2: ADDLOCAL=F3 REMOVE=F2 REINSTALL=F1.

So there you have the essence of the model; an install operation is really a set of install, uninstall, and patch operations. Of course there’s more to it than that, as there are different ways to install features, etc. And that’s why this entry is titled a "partial explanation."

A note in closing: The log files generated by msiexec are your friend for debugging MSI issues; use /lv*x to get the most information. If you’re debugging issues related to feature selection like I describe here, look for entries with this form:

Feature: Runtime; Installed: Local; Request: Reinstall; Action: Reinstall

They’ll give you a good idea of which operations MSI thinks you’ve requested, including the state of the feature before the operations begin.