1

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;
MartynA
  • 30,454
  • 4
  • 32
  • 73
  • Safecall exception wraps an HRESULT. What is it? – David Heffernan Aug 30 '14 at 12:53
  • I'm not sure how to get the HRESULT value. In the CPU window, the .Repaint does a "call dword ptr [eax + $30]" and the following line is "call @CheckAutoResult" - tracing into the first of these leads to impenetrable assembler with no source in sight. However, at the call to CheckAutoResult, EAX is 8004005 (doesn't a low byte of 5 often mean "access denied", iirrc?), which CheckAutoResult turns into 80004018, of which the 018 is presumably the 24 decimal I saw in the eval window earlier, being the "Exception in Safecall method". Sorry if this isn't answering your query. – MartynA Aug 30 '14 at 13:55
  • What exception class is raised – David Heffernan Aug 30 '14 at 14:00
  • Perhaps we're talking at cross purposes. The Exception my OnException handler sees is just ESafecallException, with the Message text "Exception in Safecall Method". – MartynA Aug 30 '14 at 14:14
  • The docs say: *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.* – David Heffernan Aug 30 '14 at 14:37
  • Do you have `ComObj` in a uses list somewhere? – David Heffernan Aug 30 '14 at 14:38
  • @DavidHeffernan: I have now and it changes the exception's class to EOleException and the Message to "Unspecified error". Anyway, we always used to say at work that the sign of a true expert was asking the right question. Trying to unmask the error behind the ESafecall, I found the inbound side of the interface, changed Safecall to StdCall on Repaint() and the exception no longer occurs - see update to my q, so + about 100 for that! I'm guessing that the object behind the interface is returning the wrong HRESULT value, because the GeckoBrowser now seems to work fine? – MartynA Aug 30 '14 at 16:54
  • Changing to stdcall just suppresses the error. It will still occur, just not be converted to exception. – David Heffernan Aug 30 '14 at 17:44

1 Answers1

0

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.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Thanks. What I was hoping to do, of course, was to find a way of handling the problem, or avoiding it in the first place, that doesn't involve touching the TGeckoBrowser source. Several days testing has left me fairly sure that the error only occurs the first time Repaint() is called, so it seems likely that it's an initialisation problem on one side or the other of the interface. As things stand, a TGeckoBrowser straight from the palette fails its first attempt to appear at r/t. I will whittle down my q to something a bit more focussed. – MartynA Sep 02 '14 at 13:20
  • I don't know about the a) in your Q, but I think I dealt with the b) – David Heffernan Sep 02 '14 at 13:21