如何从TWebBrowser控件与TAutoIntfObject对象进行互操作?

4
我有一个实现IDocHostUIHandlerTWebBrowser控件,通过IDispatch容器扩展JavaScript互操作的控制。这很好用,但是我不知道如何从JavaScript分派事件回到Web浏览器控件。
扩展对象是基于TAutoIntfObject的容器,就像this example中一样。正如您在示例中所看到的,没有与Web浏览器控件的互操作。理想情况下,我希望在该扩展对象上实现事件,但我不知道如何在我的Web浏览器控件中正确声明TAutoIntfObject对象。假设我有这个扩展对象:
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;

还有像这样的一个TWebBrowser控件:

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;

问题在于,如果我将FExtObj对象声明为TOpenLayersExt,则事件方法会被绑定,但似乎在第一次扩展对象方法调用(来自JavaScript)后,FExtObj对象引用已被释放。
如果我将其声明为IDispatch,则在JavaScript函数调用之后,引用不会被释放,但OnZoomChange事件未被绑定。
由于代码由多个部分组成,因此很难在此处发布完整代码, 这里是使用Delphi 7制作的完整项目
因此,我的问题是如何从Web浏览器控件中消耗TAutoIntfObject扩展对象的事件;如何声明扩展对象,以便能够处理来自Web浏览器控件的事件并将其传递给IDocHostUIHandler::GetExternal方法参数,同时保持接口对象引用?

或者从EmbeddedWB检出源代码... - whosrdaddy
@whosrdaddy,那就是我编写代码的地方(甚至在我的问题中都有链接),但我不知道how the IOleClientSite在那里起到了什么作用。每当您通过external从JavaScript调用方法时,IDocHostUIHandler::GetExternal方法会请求一个外部对象,该对象必须具有您正在调用的名称的分派方法。这里不需要IOleClientSite。关于EWB,它如何帮助我?只有一个OnGetExternal事件,在其中传递扩展容器,但这正是我所拥有的。 - TLama
我不太确定你需要什么。你提供的示例对我来说完美地工作了(与糟糕的EmbeddedWB实现相反)。看看源代码:TExternalContainer = class(TNulWBContainer, IDocHostUIHandler, IOleClientSite),在主项目中 fContainer := TExternalContainer.Create(WebBrowser1);。这有点棘手,但实际上是一个非常好的想法,可以避免像你所做的那样使用中介类:TMapBrowser = class(TWebBrowser, IDocHostUIHandler) - kobik
1
我曾经做过相反的事情:从在网页中加载的ActiveX获取容器:http://yoy.be/item.asp?i98 这就是IOleClientSite的作用。不过我应该抽出一些时间来深入研究你在这里发布的代码,看起来非常像我所做的事情。 - Stijn Sanders
显示剩余3条评论
1个回答

6

使用引用计数,即将FExtObj作为接口的引用而不是对象:

  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;

2
我确认,修复引用计数解决了这个问题。如果你想要一个简洁的修复方法,在第140行插入FExtObj._AddRef; - Stijn Sanders
@Stijn,就像微软在他们的示例中展示的那样。TOndrej,谢谢!这也很好用。 - TLama
@Stijn,我终于使用了这个解决方案,因为似乎IE不规律地释放引用,如果我尝试使用“_AddRef”,它们的计数永远不会减少。无论如何,还是谢谢! - TLama

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接