Calling a function in a Flex module
Using f-in-box in Delphi or javascript in a web browser, you can call a function inside a Flex module. The examples given will be for f-in-box in Delphi.
First you must register the function inside your Flex application.
Code: Select all
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="cc()">
<mx:Script>
<![CDATA[
private function cc():void
{
ExternalInterface.addCallback("externalFunc", externalFunc);
}
private function externalFunc(input:String):String
{
trace(input);
return "got it!"
}
]]>
</mx:Script>
</mx:Application>
Invoke String
In Delphi, use the CallFunction method of your TFlashPlayerControl object to call the function. You must pass in the arguments as an XML invoke string.
Code: Select all
var
request: WideString;
response: WideString;
begin
request := '<invoke name="externalFunc"><arguments><string>did you get it?</string></arguments></invoke>';
response := Flash.CallFunction(request);
end;
The allowed types for the arguments in the invoke string are string, array, object and property. These names are case-sensitive. There is no integer type, so number must be used in the invoke string. But if you use an int variable in Flex to receive it, it will automatically be converted to an int. Boolean values may be passed as <true/> and <false/>. They are not wrapped in a <boolean> tag. <property> is used to indicate that an element is a property of an object or an index of an array. It has an id attribute that indicates the property name or the array index. <null/> indicates a null value.
Important: Do not put whitespace inside the <arguments> section. The only exception is putting it inside a <string> element. The whitespace will break the parser.
Here is a complicated example showing all the possible types. I have broken it up with whitespace for readability, but as I said above the parser does not like whitespace.
Code: Select all
<object>
<property id="aString">
<string>foo</string>
</property>
<property id="aNumber">
<number>12345</number>
</property>
<property id="aTrueBoolean">
<true/>
</property>
<property id="aFalseBoolean">
<false/>
</property>
<property id="anArray">
<array>
<property id="0">
<string>a</string>
</property>
<property id="1">
<string>b</string>
</property>
<property id="2">
<number>987</number>
</property>
<property id="3">
<true/>
</property>
</array>
</property>
<property id="aSubObject">
<object>
<property id="field1">
<string>foo</string>
</property>
<property id="field2">
<number>42</number>
</property>
</object>
</property>
</object>
This is equivalent to the ActionScript declaration:
Code: Select all
var o:Object = {
aString:"foo",
aNumber:12345,
aTrueBoolean:true,
aFalseBoolean:false,
anArray:["a", "b", 987, true],
aSubObject:{field1:"foo", field2:42}
};
Keep in mind that since this is XML, characters such as < or & must be escaped in both the invoke string and the return value. Good encode and decode functions can be found in the HTTPApp unit. You may also embed unescaped text in CDATA sections such as in the following example (whitespace added for readability, but make sure you don't do that in the actual code):
Code: Select all
<invoke name="externalFunc">
<arguments>
<string><![CDATA[<html><body>some text<p></body></html>]]></string>
</arguments>
</invoke>
Return Value
By default, the response will also be in XML. There is also a javascript return type that returns data in the JSON format. Since I have never found a useful parser for JSON, I am only giving examples for XML.
For the first example, the response would be:
Code: Select all
<string>got it!</string>
You can also pass back arrays and simple objects without doing anything special.
Code: Select all
private function externalFunc(input:String):Object
{
trace(input);
var o:Object = {
aString:"foo",
aNumber:12345,
aTrueBoolean:true,
aFalseBoolean:false,
anArray:["a", "b", 987, true],
aSubObject:{field1:"foo", field2:42}
};
return o;
}
This will be returned to Delphi as shown in the earlier invoke string. To load the result into an TXpObjModel (freeware XML parser available at here), you need to do the following:
Code: Select all
const
LE_BOM = #$FEFF; // little-endian beginning of message character
...
var
response: WideString;
xml: TXpObjModel;
...
response := LE_BOM + Flash.CallFunction(request);
xml.LoadMemory(response[1], Length(response) * SizeOf(WideChar));
You can then use the xpath functions of the XpObjModel object to extract data:
Code: Select all
xml.DocumentElement.SelectInteger('/object/property[@id="anArray"]/array/property[@id="2"]/number')
Note: When you return an object, the order of the properties may not be the same as how you returned them. Arrays do seem to keep their order. Keep this in mind if you use plain text parsing.
Note: More complicated objects will only be returned as <object/>. I haven't determined if this is just because there are too many properties, or because some properties cannot be represented in XML.
Calling a function from a Flex module
Flex modules can call functions external to them, such as javascript functions in a browser or callbacks in f-in-box in Delphi. The basic method is the same as above.
In your Flex application, you connect an OnFlashCall event handler to your TFlashPlayerControl's object. You return a response by calling SetReturnValue:
Code: Select all
procedure TMainForm.FlashCall(ASender: TObject; const request: WideString);
begin
ShowMessage(request);
TFlashPlayerControl(Sender).SetReturnValue(
'<object><property id="field1"><string>bar</string></property></object>');
end;
You call this function in your Flex application with the following code:
Code: Select all
var o:Object = ExternalInterface.call("delphiFunc", {field1:"foo", field2:123});
Using the two pieces of code above, the following would be displayed by the Delphi application (with added whitespace):
Code: Select all
<invoke name="delphiFunc" returntype="xml">
<arguments>
<object>
<property id="field1">
<string>foo</string>
</property>
<property id="field2">
<number>123</number>
</property>
</object>
</arguments>
</invoke>
The Flex application would receive a response that is equivalent to:
Code: Select all
{field1:"foo", field2:123}
Thus you can easily pass arrays and more complex objects back to Flex this way.
Other methods of passing data into a Flex module
One limitation of ExternalInterface is that all data must be passed in text. To get around this, you can use the TFlashPlayerControl event handler for OnLoadExternalResource.
On use of this is to allow the Flex app to load files off disk, an operation that is normally prevented by the security sandbox:
Code: Select all
procedure TMainForm.FlashLoadExternalResource(ASender: TObject; const URL: WideString; Stream: TStream);
var
fs: TFileStream;
fileName: String;
begin
fileName := ExtractFilePath(Application.ExeName) + StringReplace(URL, '/', '\', [rfReplaceAll]);
if FileExists(fileName) then
begin
fs := TFileStream.Create(fileName, fmOpenRead or fmShareDenyNone);
Stream.CopyFrom(fs, fs.Size);
fs.Free;
end;
end;
With this code set, the following code would load an image out of the images subdirectory of the Delphi program:
Code: Select all
<mx:Image source="images/icon.png"/>
But this could be used more cleverly to allow a query to the Delphi application return any binary data to Flex, which it can then access as a ByteArray:
Code: Select all
<mx:Script>
<![CDATA[
private function loadData(user:String):void
{
var loader:URLLoader = new URLLoader;
loader.dataFormat = URLLoaderDataFormat.BINARY;
loader.addEventListener(Event.COMPLETE, loadComplete);
loader.load(new URLRequest("getData?userID=" + user));
}
private function loadComplete(e:Event):void
{
trace("load complete");
var ba:ByteArray = ByteArray(e.currentTarget.data);
ba.endian = Endian.LITTLE_ENDIAN;
dataResults.text = "uid: " + ba.readShort() + "\n" +
"status: " + ba.readShort() + "\n" +
"gid: " + ba.readShort() + "\n" +
"hasdbaccess: " + ba.readInt();
}
]]>
</mx:Script>
<mx:TextInput id="userID" text="jpierce"/>
<mx:Button label="Go!" click="loadData(userID.text)"/>
<mx:TextArea id="dataResults" height="100"/>
Code: Select all
// the following is assigned to the FlashPlayerControl's OnLoadExternalResource event
procedure TMainForm.FlashLoadExternalResource(ASender: TObject; const URL: WideString; Stream: TStream);
var
params: TStringList;
connection: TADOConnection;
query: TADOQuery;
value4Byte: Integer;
value2Byte: Smallint;
begin
params := TStringList.Create;
try
params.Delimiter := '?';
params.DelimitedText := URL;
if (params.Count > 1) and (params[0] = 'getData') then
begin
params.Delimiter := '&';
params.DelimitedText := params[1];
if params.IndexOfName('userID') <> -1 then
begin
connection := TADOConnection.Create(nil);
query := TADOQuery.Create(nil);
try
query.Connection := connection;
connection.ConnectionString := 'Provider=SQLOLEDB.1;' +
'Integrated Security=SSPI;Persist Security Info=False;' +
'Initial Catalog=master;Data Source=mydbserver';
query.SQL.Text := 'select uid, status, gid, hasdbaccess from ' +
'sysusers where name=:username';
query.Parameters.ParamValues['username'] := params.Values['userID'];
query.Open;
value2Byte := query.FieldByName('uid').AsInteger;
Stream.Write(value2Byte, SizeOf(value2Byte));
value2Byte := query.FieldByName('status').AsInteger;
Stream.Write(value2Byte, SizeOf(value2Byte));
value2Byte := query.FieldByName('gid').AsInteger;
Stream.Write(value2Byte, SizeOf(value2Byte));
value4Byte := query.FieldByName('hasdbaccess').AsInteger;
Stream.Write(value4Byte, SizeOf(value4Byte));
finally
query.Free;
connection.Free;
end;
end;
end;
finally
params.Free;
end;
end;