How to avoid exceptions when using TGeckoBrowser in a Delphi app

571 Views Asked by At

Prompted by a q here yesterday, I'm trying to re-familiarise myself with TGeckoBrowser from here: http://sourceforge.net/p/d-gecko/wiki/Home.

(Nb: requires the Mozilla XulRunner package to be installed)

Things seem to have moved backwards a bit since I last tried in the WinXP era, in that with a minimal D7 project to navigate to a URL, I'm getting errors that I don't recall seeing before. I've included my code below. These are the errors which I've run into navigating to sites like www.google.com, news.bbc.co.uk, and here, of course.

  1. The first exception - "Exception in Safecall method" - occurs as my form first displays, before naviagting anywhere at all. I have a work-around in the form of a TApplication.OnException handler.

My q is: a) Does anyone know how to avoid it in the first place or b) is there a tidier way of catching it than setting up a TApplication.Exception handler, which always feels to me like a bit of an admission of defeat (I mean having one to avoid the user seeing an exception, not having an application-wide handler at all).

This exception occurs in this code:

procedure TCustomGeckoBrowser.Paint;
var
  rc: TRect;
  baseWin: nsIBaseWindow;
begin
  if csDesigning in ComponentState then
  begin
    rc := ClientRect;
    Canvas.FillRect(rc);
  end else
  begin
    baseWin := FWebBrowser as nsIBaseWindow;
    baseWin.Repaint(True);
  end;
  inherited;
end;

in the call to baseWin.Repaint, so presumably it's presumably coming from the other side of the interface. I only get it the first time .Paint is called. I noticed that at that point, the baseWin returns False for GetVisibility, hence the experimental code in my TForm1.Loaded, to see if that would avoid it. It does not.

2.a After calling GeckoBrowser1.LoadURI, I get "Invalid floating point operation" once or more depending on the URL being loaded.

2.b Again, depending on the URL, I get: "Access violation at address 556318B3 in module js3250.dll. Read of address 00000008." or similar. On some pages it occurs every few seconds (thanks I imagine to some JS timer code in the page).

2a & 2b are avoided by the call to Set8087CW in TForm1.OnCreate below but I'm mentioning them mainly in case anyone recognises them and 1 together as symptomatic of a systemic problem of some sort, but also so google will find this q for others who run into those symptoms.

Reverting to my q 1b), the "Exception in Safecall method" occurs from StdWndProc-> TWinControl.MainWndProc->[...]->TCustomGeckoBrowser.Paint. Instead of using an TApplication.OnException handler, is there a way of catching the exception further up the call-chain, so as to avoid modifying the code of TCustomGeckoBrowser.Paint by putting a handler in there?

Update: A comment drew my attention to this documentation relating to SafeCall:

ESafecallException is raised when the safecall error handler has not been set up and a safecall routine returns a non-0 HResult, or if the safecall error handler does not raise an exception. If this exception occurs, the Comobj unit is probably missing from the application's uses list (Delphi) or not included in the project source file (C++). You may want to consider removing the safecall calling convention from the routine that gave rise to the exception.

The GeckoBrowser source comes with a unit, BrowserSupports, which looks like a type library import unit, except that it seems to have been manually prepared. It contains an interface which includes the Repaint method which is producing the SafeCall exception.

  nsIBaseWindow = interface(nsISupports)
  ['{046bc8a0-8015-11d3-af70-00a024ffc08c}']
    procedure InitWindow(parentNativeWindow: nativeWindow; parentWidget: nsIWidget; x: PRInt32; y: PRInt32; cx: PRInt32; cy: PRInt32); safecall;
    procedure Create(); safecall;
    procedure Destroy(); safecall;
  [...]
    procedure Repaint(force: PRBool); safecall;
  [...]
  end;

Following the suggestion in the quoyed documentation, I changed th "safecall" to StdCall on the Repaint member (but only that member) and, presto!, the exception stopped occurring. If it doesn't reappear in the next couple of days, I'll post that as an answer, unless anyone comes up with a better one.

My project code:

uses
  BrowserSupports;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Set8087CW($133F);
  Application.OnException := HandleException;
end;

procedure TForm1.HandleException(Sender: TObject; E: Exception);
begin
  Inc(Errors);
  Caption := Format('Errors %d, msg: %s', [Errors, E.Message]);
  Screen.Cursor := crDefault;
end;

type
  TMyGeckoBrowser = class(TGeckoBrowser);

procedure TForm1.Loaded;
begin
  inherited;
  GeckoBrowser1.HandleNeeded;
 (TMyGeckoBrowser(GeckoBrowser1).WebBrowser as nsIBaseWindow).SetVisibility(True);
end;

procedure TForm1.btnLoadUrlClick(Sender: TObject);
begin
  try
    GeckoBrowser1.LoadURI(edUrl.Text);
  except
  end;
end;
1

There are 1 best solutions below

2
On

Looking at the headers, the prototype for Repaint is effectively as follows:

HRESULT __stdcall Repaint(PRBool force);

and that means that

procedure Repaint(force: PRBool); safecall;

is a reasonable declaration. Remember that safecall performs parameter re-writing to convert COM error codes into exceptions.

This does mean that if the call to Repaint returns a value that indicates failure, then the safecall mechanism will surface that as an exception. If you wish to ignore this particular exception then it is cleaner to do so at source:

try
  baseWin.Repaint(True);
except
  on EOleException do
    ; // ignore
end;

If you wish to avoid dealing with exceptions then you could switch to stdcall, but you must remember to undo the parameter re-writing.

function Repaint(force: PRBool): HRESULT; stdcall;

Now you can write it like this:

if Failed(baseWin.Repaint(True)) then
  ; // handle the error if you really wish to, or just ignore it

Note that Failed is defined in the ActiveX unit.

If you want to troubleshoot the error further then you can look at the error code:

var
  hres: HRESULT;
....
hres := baseWin.Repaint(True);
// examine hres

Or if you are going to leave the function as safecall then you can retrieve the error code from the EOleException instance's ErrorCode property.