Tuesday, August 28, 2007

Ajax not only for Web: Using XmlHTTPRequest in the standalone application.

If the term AJAX is familiar to you, for sure, you know that the heart of this technology is the XMLHttpRequest object. It may be used in, practically, all the modern browsers that support javascript. Although you would instantiate it in Firefox and new versions of Internet Explorer with the instruction:

var d = new XMLHttpRequest();

originally in Internet Explorer it was not a built-in JavaScript object.
In fact, the command to get XMLHttpRequest was:

var d = new ActiveXObject("MSXML2.XMLHTTP");

what reveals the fact that it was realized as an ActiveX object. Indeed, it was implemented in library called Microsoft XML which exposed COM object MSXML2.XMLHTTP. Its type library is present in msxml3.dll file in windows system folder.
So, if it is nothing more than normal ActiveX class, why not to use it in standalone application, for instance written in Delphi, to transfer data between the application and some web server.
In ActiveX-enabled languages that offer dynamic binding (like pascal) it is not more difficult that javascript code embedded in HTML. Here comes an example in Delphi, which fills a memo control with the code of the website http://www.somewhere.com:

procedure TForm1.Button1Click(Sender: TObject);
var
d: OleVariant;
begin
d := CreateOleObject('MSXML2.XMLHttp');
d.open('get', 'http://www.somewhere.com', false, EmptyParam, EmptyParam);
d.send('');
if (d.readyState = 4) and (d.status = 200) then
Memo1.Lines.Text := d.responseText;
end;

Simple, isn't it?
If you prefer static binding (for example, to take advantage of code completion) you may import ActiveX type library Microsoft XML 3.0. Then, your code would look like:

uses msxml2_tlb;

procedure TForm1.Button1Click(Sender: TObject);
var
d: IXMLHTTPRequest;
begin
d := CoXMLHTTP40.Create;
d.open('get', 'http://www.somewhere.com', false, EmptyParam, EmptyParam);
d.send('');
if (d.readyState = 4) and (d.status = 200) then
Memo1.Lines.Text := d.responseText;
end;

You may now be thinking "Wait!, but A in Ajax stands for asynchronous, and here your are blocking program, using XMLHttpRequest synchronously". Yes, we may do it also asynchronously, but one problem arouses here - handling onreadystatechange event. In reality, it is not an event as understood in ActiveX. You may know that, in fact, JScript is incapable of sinking ActiveX events. Thus, Microsoft architects made onreadystatechange to be a property you may assign JScript function to. Therefore, what we have to assign must be, at least, the imitation of JScript routine. As you may have realized, everything in JScript is an object - internally represented by a pointer to IDispatch interface. So are functions. To execute the function you call Invoke with DISPID equal to 0 on its IDispatch.
In Delphi, therefore, JScript's function may be imitated by an object of the class derived from TInterfacedObject and implementing IDispatch interface (providing implementation for Invoke method is enough).

The full example of asynchronous HTTP request follows here:

uses comobj, msxml2_tlb;

type
TAjaxEvenFunc = procedure (d: Variant) of object;
TAjaxEvent = class(TInterfacedObject, IDispatch)
private
d: Variant;
func: TAjaxEvenFunc;
public
constructor Create(const d: Variant; const func: TAjaxEvenFunc);
function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer;
Flags: Word; var Params; VarResult: Pointer; ExcepInfo: Pointer;
ArgErr: Pointer): HRESULT; stdcall;
function GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount: Integer;
LocaleID: Integer; DispIDs: Pointer): HRESULT; stdcall;
function GetTypeInfo(Index: Integer; LocaleID: Integer;
out TypeInfo): HRESULT; stdcall;
function GetTypeInfoCount(out Count: Integer): HRESULT; stdcall;
destructor Destroy; override;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
d: IXMLHTTPRequest;
begin
d := CreateOleObject('MSXML2.XMLHttp.3.0') as IXMLHTTPRequest;
d.open('get', 'http://www.onet.pl', true, EmptyParam, EmptyParam);
d.onreadystatechange := TAjaxEvent.Create(d, EventHandl) as IDispatch;
d.send('');
d := nil;
end;

procedure TForm1.EventHandl(d: Variant);
begin
if (d.readyState = 4) and (d.status = 200) then
Memo1.Lines.Text := d.responseText;
end;

{ TAjaxEvent }

constructor TAjaxEvent.Create(const d: Variant;
const func: TAjaxEvenFunc);
begin
inherited Create;
self.d := d;
self.func := func;
end;

destructor TAjaxEvent.Destroy;
begin
func := nil;
d := Null;
inherited;
end;

function TAjaxEvent.GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount,
LocaleID: Integer; DispIDs: Pointer): HRESULT;
begin
Result := E_NOTIMPL;
end;

function TAjaxEvent.GetTypeInfo(Index, LocaleID: Integer;
out TypeInfo): HRESULT;
begin
Result := E_NOTIMPL;
end;

function TAjaxEvent.GetTypeInfoCount(out Count: Integer): HRESULT;
begin
Result := E_NOTIMPL;
end;

function TAjaxEvent.Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer;
Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HRESULT;
begin
if DispID <> 0 then
begin
Result := E_INVALIDARG;
exit;
end;
if Assigned(func) and not VarIsNull(d) then
func(d);
end;

1 comment:

FrykT said...

Hi! Please explain in details what do you do to free memory for ajax. My application use your's ajax example and I creating many ajax requests and all of my requests getting all of memory that raise OutOfMemory. How will I fix this bug?
p.s. sorry my English is maybe too hard to you, but he isn't my native language.

There were visits to this blog since 20.08.2007.