fxlogoI started playing with Alchemy a couple days ago, with the intent of finally writing OpensIFRr3 — you know, the one working with sIFR3. Since the previous release was in Java, I was planning on creating this one in Flex.
I am not quite there yet, due to the nightmare that porting libxml would be. But I’ve learnt that reading Alchemy’s currently meager documentation could have saved me a few headaches. For instance, do not assume that you will find here some of the magic that other “gateways” offer. More specifically, do not assume that a ByteArray will automatically be passed from ActionScript to C++ as a char *.
Nope, it’s passed as a ByteArray structure, with all sorts of fun overhead that will drive you crazy if you assume a “StringType” object.

If this is gibberish to you, keep reading:
Yes, Alchemy is verbose.
No, you should not fear its complexity.

Here is how I read a compressed flash file in a ByteArray, in Flex, then pass it to C++ for parsing. Obviously it is the start of OpensIFRr3 (jump to the end of the listing if you are only curious about seeing an Alchemy call in action):

?View Code ACTIONSCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
package
{
	import flash.display.Sprite;
	import flash.filesystem.File;
	import flash.filesystem.FileMode;
	import flash.filesystem.FileStream;
	import flash.utils.ByteArray;
	import flash.utils.CompressionAlgorithm;
	import cmodule.swfmill.CLibInit; // My C code
 
	public class EchoTest extends Sprite
	{
		private var length:uint;
		private var compressed:Boolean;
		private var version:uint
		private var sig:String;
		private var data:ByteArray;
 
		private function main_fread_uint(fs:FileStream):uint
		{
			return fs.readUnsignedByte();	
		}
 
		private function main_fread_char(fs:FileStream):String
		{
			return String.fromCharCode(fs.readUnsignedByte());	
		}
 
		private function main_fread_str(fs:FileStream, offset:uint, length:uint):String
		{
			var ba:ByteArray = new ByteArray();
			fs.readBytes(ba, offset, length);
			return ba.toString();
		}
 
		private function main_load(name:String):Boolean
		{
			try
			{
				var file:File = new File("/Users/Chris/Projects/sIFRr3/rockwell.swf");
				var fs:FileStream = new FileStream();
				if(!file.exists)
				{
					trace("File does not exist");
					return false;
				}
				fs.open(file, FileMode.READ);
				sig = main_fread_str(fs, 0, 3);
				version = main_fread_uint(fs);
				if(sig != "CWS" && sig != "FWS")
				{
					trace("ERROR: input is no SWF");
					return false;
				}
				length = main_fread_uint(fs);
				length += main_fread_uint(fs) << 8;
				length += main_fread_uint(fs) << 16;
				length += main_fread_uint(fs) << 24;
				length -= 8;
				compressed = sig.charAt(0) == 'C';
				if(length != file.size - 8)
				{
					if( length > file.size - 8 && !compressed)
					{
						trace("WARNING: size specified in SWF (" + length + ") != file.size (" + file.size + "), using filesize-8.");
						length = file.size - 8;
					}
				}
				data = new ByteArray();
				fs.readBytes(data);
				if(compressed)
				{
					data.uncompress(CompressionAlgorithm.ZLIB);					
				}
				fs.close();
			}
			catch(e:Error)
			{
				trace("Error: " + e.message);
				return false;
			}				
			return true;
		}
 
		public function SWFTest()
		{
			var loader:CLibInit = new CLibInit;
			var lib:Object = loader.init();
 
			if(main_load("/Users/Chris/Projects/sIFRr3/rockwell.swf"))
			{
				trace("ASLength = " + data.length);
				lib.swfmill_swf2xml(version, data.length, data); // Exported method
			}
		}
	}
}

The magic happens here: lib.swfmill_swf2xml(…)

Let’s have a look a this little guy in his natural habitat — in swmill.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int main()
{
	//define the methods exposed to ActionScript
	//typed as an ActionScript Function instance
	AS3_Val swfmill_swf2xml_method = AS3_Function( NULL, swfmill_swf2xml);
 
	// construct an object that holds references to the functions
	AS3_Val result = AS3_Object( "swfmill_swf2xml: AS3ValType", swfmill_swf2xml_method );
 
	// Release
	AS3_Release( swfmill_swf2xml_method );
 
	// notify that we initialized -- THIS DOES NOT RETURN!
	AS3_LibInit( result );
 
	// should never get here!
	return 0;
}

So, main() will be invoked when the program instance comes up and Flex will be informed that there is a method of interest (swfmill_swf2xml).

Digression:
I know, this is a bit hard to read, but so far most of Alchemy is. After all, the whole bridge relies on the C++ code being compiled to bytecode that is linked indirectly against Flash libraries, the result being turned into ActionScript that will then be compiled to asm-asc. If you look at the work files, you will see ActionScript mixed directly with what looks like assembly code. To quote Adobe, the end-result is “basically one huge finite state machine.”
This means that your C++ code will run “somewhat” in parallel with ActionScript. Beware!

How does this swfmill_swf2xml method manage with our ByteArray? After all, C++ doesn’t know anything about that type of structure.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static AS3_Val swfmill_swf2xml( void *self, AS3_Val args )
{
	bool success = false;
	unsigned int e_version;
	unsigned int e_length;
	AS3_Val		 e_data = AS3_Undefined();
 
	AS3_ArrayValue( args, "IntType, IntType, AS3ValType", &e_version, &e_length, &e_data );
	fprintf(stderr, "Clength: %u", e_length);
	unsigned char * data = (unsigned char *)malloc(sizeof(unsigned char) * (e_length + 1));
	for(int runner=0; runner < e_length; runner++)
	{
		*(data + runner) = AS3_IntValue(AS3_Get(e_data, AS3_Int(runner))) & 0xFF;
	}
 
	...
 
	return AS3_Int(success ? 0 : -1);
}

Did you see it? Did you see the awesomely hackish way we have to retrieve our arguments using AS3_ArrayValue?
Use of AS3ValType means “You, C++, will store this object as a blob because you do not know better. However, if you use the correct AS3 calls, you will be able to ask ActionScript to manipulate this object on your behalf.”
Therefore, to convert that object, which, I happen to know, is a ByteArray, I will ask ActionScript to read its content and I will store it in an array of chars. Note that I am masking the returned value with 0xFF, because there is no function to read a byte, therefore I need to get rid of the irrelevant bits after reading an integer. And, yes, AS3_IntValue() is a function that maps AS3 integers to C integers. More info here.

If you read the page I just provided a link to, you may wonder why I am passing AS3_Int(runner) to AS3_Get(…)
After all, the second parameter is supposed to be a method name. And this is the case – in a way. By passing an integer, I am asking ActionScript to use the ‘[]‘ form to access our ByteArray.
Note that I had no joy trying to use readUnsignedByte() instead. That’s to be expected; after all, Alchemy is far from stable — or complete.

But it’s certainly promising.

If you enjoyed this post, make sure you subscribe to my RSS feed!