Question

I'm having a TWebBrowser control which implements IDocHostUIHandler to extend the control of a JavaScript interop through the IDispatch container. This works fine, except that I don't know, how to dispatch event from JavaScript back to the web browser control.

The extension object is a container based on TAutoIntfObject like in this example. As you can see in the example, there is no interop with the web browser control. Ideally, I'd like to implement events on that extension object, but I don't know how to properly declare the TAutoIntfObject object in my web browser control. Let's say I'm having this extension object:

type
  TZoomChangeEvent = procedure(Sender: TObject; ZoomLevel: Integer) of object;
  TOpenLayersExt = class(TAutoIntfObject, IOpenLayers)
  private
    FOnZoomChange: TZoomChangeEvent;
    // the ZoomChange method is invoked from JavaScript
    procedure ZoomChange(ZoomLevel: Integer); safecall;
  public
    property OnZoomChange: TZoomChangeEvent read FOnZoomChange write FOnZoomChange;
  end;

implementation

procedure TOpenLayersExt.ZoomChange(ZoomLevel: Integer);
begin
  if Assigned(FOnZoomChange) then
    FOnZoomChange(Self, ZoomLevel);
end;

And a TWebBrowser control like this:

type
  TMapBrowser = class(TWebBrowser, IDocHostUIHandler)
  private
    // the extension object
    FExtObj: TOpenLayersExt;
    // IDocHostUIHandler::GetExternal method
    function GetExternal(out ppDispatch: IDispatch): HRESULT; stdcall;
    // this is the TOpenLayersExt.OnZoomChange event method implementation
    procedure OnZoomChange(Sender: TObject; Zoom: Integer);
  public
    // ordinary constructor
    constructor Create(AOwner: TComponent); override;
  end;

implementation

constructor TMapBrowser.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  // create extension object
  FExtObj := TOpenLayersExt.Create;
  // here the event method is properly binded; if I'd change the FExtObj type
  // to IDispatch with TOpenLayersExt(FExtObj) typecast, it wouldn't
  FExtObj.OnZoomChange := OnZoomChange;
end;

function TMapBrowser.GetExternal(out ppDispatch: IDispatch): HRESULT;
begin
  // the problem is that I don't know how to properly pass this object to the
  // ppDispatch parameter; if this GetExternal method is called second time,
  // the FExtObj seems to be released, but I don't get why
  ppDispatch := FExtObj as IDispatch;
  Result := S_OK;
end;

The problem is that if I declare the FExtObj object as TOpenLayersExt, the event method is binded but the FExtObj object reference seems to be released after the first extension object method invoke (from JavaScript).

If I declare it as IDispatch, the reference is not released after the JavaScript function invoke, but the OnZoomChange event is not binded.

It's hard to post the full code here as it's composed from more parts, here is a complete project made in Delphi 7.

So my question is, how to consume events from TAutoIntfObject extension object in a web browser control; how to declare the extension object, so I'll be able to handle events from a web browser control and pass it to the IDocHostUIHandler::GetExternal method parameter still keeping the interface object reference ?

Was it helpful?

Solution

Use reference counting, ie. keep FExtObj as a reference to an interface, rather than object:

  private
    // the extension object
    FExtObj: IDispatch;

...

constructor TMapBrowser.Create(AOwner: TComponent);
var
  AExtObj: TOpenLayersExt;
begin
  inherited Create(AOwner);
  // create extension object
  AExtObj := TOpenLayersExt.Create;
  AExtObj.OnZoomChange := OnZoomChange;
  FExtObj := AExtObj as IDispatch;
end;

destructor TMapBrowser.Destroy;
begin
  FExtObj := nil;
  inherited Destroy;
end;
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top