If you follow the Flex world, you know that the ability to execute external applications has probably been one of the most-often requested features since the Apollo days. After all, once you gain the ability to run your Flex application in a desktop environment, it’s only natural to want to stretch out a little and extend your reach into the other goodies that are available to you.
As AIR2—currently in the release candidate stage—becomes finally a reality, this functionality is finally available to developers in two different forms: either invoking the default application associated with a given file type or, much more interestingly, instantiating an external process and communicating with it.
Getting Started
I am assuming here that you use Flash Builder 4—if you’re still an FB3 user, the process is similar, but subtly different, particularly when it comes to the source code; therefore, your mileage may (and probably will) vary.
The first step towards world domination consists of downloading the AIR2 SDK (from the link above) and overlaying it on top of your existing FB installation. The instructions for doing this are a little convoluted, but nothing that any developer should be able to handle. My only recommendation, if you are on a Mac, is to copy the SDK files over from the terminal rather than using Finder—the latter just doesn’t seem to work for me.
Onward and upwards! Next, you can simply create an AIR application the “normal” way. Make sure to select the 2.0 SDK that you have just installed (you can also do so from the project’s settings window if you forget).
Next, you need to make a few changes to your project; open up your application descriptor and change the default namespace:
<application xmlns="http://ns.adobe.com/air/application/2.0">
This will make sure that adl picks up the correct SDK when it compiles the application. The actual native-process instantiation mechanism is also platform-dependent, which means that the only way to take advantage of it is to make your application native as well. This is done by adding an element to the descriptor:
<supportedProfiles>extendedDesktop</supportedProfiles>
It bears mentioning that when you package your application for export, you will no longer be able to distribute it as an AIR package—rather, you will now have to compile it as a native installer (DMG for Macs, EXE for Windows and DEB or RPM for Linux). This process cannot be performed directly from FB4, but must be taken care of from the command line—and you will have to do it on the platform for which you want to distribute (e.g.: you need to package on a Mac to create a DMG, and separately on Windows to create an EXE file).
Invoking external applications
With the preliminaries out of the way, let’s look at how the invocation of external processes actually works. The functionality is provided by a class aptly called NativeProcess, which encapsulates an external process of some kind. You can determine if native processes are supported by checking the isSupported static property of the class:
if (NativeProcess.isSupported) {
// Invoke external application here
}
In order to invoke a native process, you need to first specify a process descriptor in the form of an instance of NativeProcessInfo, which is simply a collection of three elements: the external application’s path, the command-line parameters you want to pass to it, and the initial working directory in which it will run. For example, suppose you want to run a copy of PHP that can be found in /usr/bin/php:
var info:NativeProcessStartupInfo = new NativeProcessStartupInfo();
info.executable = new File("/usr/bin/php");
info.workingDirectory = new File("/");
As you can see, I am not passing any parameters here—that’s because I want to be able to pass the actual PHP code that must be executed at runtime. In my particular case, this is necessary because I am building a simple interface to the PHP system we use internally at php|architect to render our articles into a format that can be passed to the software our production team uses for layout; however, it also makes a good use-case story for the fact that NativeProcess supports communication to external processes using the standard input, output and error pipes.
Communicating with a native process
An instance of NativeProcess exposes three properties, called standardInput, standardOutput and standardError that encapsulate the three standard streams of the running external process in the form of data streams. However, because Flex is an event-driven environment, you cannot simply read from them at will—instead, you must wait for data to be available or incur the wrath of the system in the form of an exception.
Rather than sticking around and continuing to check whether data is available from any of the streams, we can simply subscribe to one of two appropriate events: ProgressEvent.STANDARD_OUTPUT_DATA and ProgressEvent.STANDARD_ERROR_DATA. Naturally, there is no corresponding event for standardInput, given that STDIN is a write-only stream. However, we still need to wait for any data that we write to it to be successfully sent over to the external process before we can close it:
nativeProcess = new NativeProcess();
nativeProcess.addEventListener(ProgressEvent.STANDARD_OUTPUT_DATA, receivedData);
nativeProcess.addEventListener(ProgressEvent.STANDARD_ERROR_DATA, receivedError);
nativeProcess.addEventListener(ProgressEvent.STANDARD_INPUT_PROGRESS, dataSent);
We can now start the external process by using the start() method. The entire invocation process looks like this:
protected function invokePHP(phpScript:String):void {
if (NativeProcess.isSupported) {
var info:NativeProcessStartupInfo = new NativeProcessStartupInfo();
info.executable = new File("/usr/bin/php");
info.workingDirectory = new File("/");
nativeProcess = new NativeProcess();
nativeProcess.addEventListener(ProgressEvent.STANDARD_OUTPUT_DATA, receivedData);
nativeProcess.addEventListener(ProgressEvent.STANDARD_ERROR_DATA, receivedError);
nativeProcess.addEventListener(ProgressEvent.STANDARD_INPUT_PROGRESS, dataSent);
nativeProcess.start(info);
nativeProcess.standardInput.writeUTFBytes(phpScript);
}
}
Note that I also take the opportunity here to pass the script that I want to execute directly to the app through STDIN; in order to trigger its execution, I also have to close the input stream to signal PHP that no more data will be more forthcoming—but that needs to be done once the NativeProcess instance signals that it has pushed all the data through the stream:
protected function dataSent(e:Event):void {
nativeProcess.closeInput();
}
All that remains now is to listen for events and find out when we get data back:
protected function receivedData(e:Event):void {
trace("App result: " + nativeProcess.standardOutput.readUTFBytes(nativeProcess.standardOutput.bytesAvailable));
}
protected function receivedError(e:Event):void {
trace("App error: " + nativeProcess.standardError.readUTFBytes(nativeProcess.standardError.bytesAvailable));
}
Tying it all together
Did you know that AIR comes with a fully-functional HTML rendering engine based on Webkit? While you can’t take advantage of it in a regular Flex application (which, after is already supposed to be running in a browser), it’s a perfect companion to the ability to run PHP in an AIR2-based application.
Here’s a super-simple example to entertain you:
<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx">
<fx:Script>
<![CDATA[
import flash.desktop.NativeProcess;
protected var nativeProcess:NativeProcess;
protected function invokePHP(phpScript:String):void {
if (NativeProcess.isSupported) {
var info:NativeProcessStartupInfo = new NativeProcessStartupInfo();
info.executable = new File("/usr/bin/php");
info.workingDirectory = new File("/");
nativeProcess = new NativeProcess();
nativeProcess.addEventListener(ProgressEvent.STANDARD_OUTPUT_DATA, receivedData);
nativeProcess.addEventListener(ProgressEvent.STANDARD_ERROR_DATA, receivedError);
nativeProcess.addEventListener(ProgressEvent.STANDARD_INPUT_PROGRESS, dataSent);
output.htmlText = "";
nativeProcess.start(info);
nativeProcess.standardInput.writeUTFBytes(phpScript);
}
}
protected function receivedData(e:Event):void {
output.htmlText += nativeProcess.standardOutput.readUTFBytes(nativeProcess.standardOutput.bytesAvailable);
}
protected function receivedError(e:Event):void {
output.htmlText += nativeProcess.standardError.readUTFBytes(nativeProcess.standardError.bytesAvailable);
}
protected function dataSent(e:Event):void {
nativeProcess.closeInput();
}
]]>
</fx:Script>
<fx:Declarations>
</fx:Declarations>
<s:VGroup left="0" top="0" bottom="0" right="0">
<s:TextArea id="source" width="100%" height="50%" />
<s:HGroup width="100%" horizontalAlign="center">
<s:Button label="Execute" click="invokePHP(source.text)" />
</s:HGroup>
<mx:HTML id="output" width="100%" height="50%"/>
</s:VGroup>
</s:WindowedApplication>
And the result:

Why would you want to, anyway?
The question that’s probably going through your mind right now is—why on Earth would anyone want to run PHP from an AIR application?
First of all, this is just an example. While this code happens to run PHP, you could be doing one of a million different things—run your own executables, invoke a system utility, or whatever else comes to your mind. When you consider the fact that you can embed any arbitrary executables as resources in your AIR packages, the possibilities are virtually unlimited.
Of course, you do have to consider the price tag of taking advantage of this functionality: you will have to compile your applications as native installers, which, as I mentioned earlier, means that you also have to create individual packages for each platform you intend to target on that platform. When dealing with executables that are available on a number of different platforms, like PHP, this may feel a little absurd, but that, as they say, is the way it is.
But back to PHP. The example in this post is obviously contrived—nobody in their right mind would want to create something this simple (although this could be the beginning of an interactive PHP editor). The problem that sparked the post, however, was very real: the need to execute an application that I had already written in another language from a desktop environment. If you already use PHP for some of your work, native processes create one more opportunity for you to use a common code base throughout all your products.