简体   繁体   English

从Delphi应用程序接收MS Word的自动化事件

[英]Receiving MS Word's automation events from a Delphi app

I've been trying to use the technique shown in the answer to this q 我一直在尝试使用此问题答案中显示的技术

Detect when the active element in a TWebBrowser document changes 检测TWebBrowser文档中的活动元素何时更改

to implement a DIY version of MS Word's Automation events. 实现MS Word自动化事件的DIY版本。

A fuller extract from my app is below, from which you'll be able to see the declaration of the variables in these methods: 下面是我的应用程序的完整摘录,从中可以查看这些方法中变量的声明:

procedure TForm1.StartWord;
var
  IU : IUnknown;
begin
  IU := CreateComObject(Class_WordApplication);
  App := IU as WordApplication;
  App.Visible := True;
  IEvt := TEventObject.Create(DocumentOpen);
end;

procedure TForm1.OpenDocument;
var
  CPC : IConnectionPointContainer;
  CP : IConnectionPoint;
  Res : Integer;
  MSWord : OleVariant;
begin
  Cookie := -1;
  CPC := App as IConnectionPointContainer;
  Res := CPC.FindConnectionPoint(DIID_ApplicationEvents2, CP);
  Res := CP.Advise(IEvt, Cookie);

  MSWord := App;
  WordDoc:= MSWord.Documents.Open('C:\Docs\Test.Docx');
end;

The StartWord routine works fine. StartWord例程运行良好。 The problem is in OpenDocument . 问题出在OpenDocument The value of Res returned by Res := CP.Advise(IEvt, Cookie); Res返回的Res := CP.Advise(IEvt, Cookie);Res := CP.Advise(IEvt, Cookie); is $80040200 This isn't present amongst the HResult status codes in Windows.Pas and googling "ole error 80040200" returns a few hits involving setting up Ado events from Delphi, but nothing apparently relevant. 是$80040200。Windows.Pas和谷歌搜索“ ole error 80040200”返回的命中结果涉及从Delphi设置Ado事件,但没有明显相关。

Anyway, the upshot of this is that the Invoke method of the EventObject is never called, so I don't receive notifications of the WordApplication's events. 无论如何,这样做的结果是从来没有调用EventObject的Invoke方法,因此我没有收到有关WordApplication事件的通知。

So, my question is what does this error $80040200 signify and/or how do I avoid it? 因此,我的问题是$ 80040200的错误代表什么和/或如何避免它?

Fwiw, I've also tried connecting to the ApplicationEvents2 interface using this code 首先,我也尝试使用此代码连接到ApplicationEvents2接口

procedure TForm1.OpenDocument2;
var
  MSWord : OleVariant;
  II : IInterface;
begin
  II := APP as IInterface;
  InterfaceConnect(II, IEvt.EventIID, IEvt as IUnknown, Cookie);
  MSWord := App;
  WordDoc:= MSWord.Documents.Open('C:\Docs\Test.Docx');
end;

That executes without complaint, but again the EventObject's Invoke method is never called. 那会毫无抱怨地执行,但是EventObject的Invoke方法再也不会被调用。

If I drop a TWordApplication onto the blank form of a new application, the events like OnDocumentOpen work fine. 如果将TWordApplication放到新应用程序的空白表单上,则OnDocumentOpen之类的事件可以正常工作。 I'm mentioning that because it seems to confirm that Delphi and MS Word (2007) are correctly set up on my machine. 我之所以这样说是因为它似乎可以确认在我的计算机上正确设置了Delphi和MS Word(2007)。

Code: 码:

  uses
    ... Word2000 ...

  TForm1 = class(TForm)
    btnStart: TButton;
    btnOpenDoc: TButton;
    procedure FormCreate(Sender: TObject);
    procedure btnOpenDocClick(Sender: TObject);
    procedure btnStartClick(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure WordApplication1DocumentOpen(ASender: TObject; const Doc: _Document);
  private
    procedure DocumentOpen(Sender : TObject; DispID : Integer; var Params);
    procedure StartWord;  // see above for implementation
    procedure OpenDocument; // --"--
    procedure OpenDocument2;  // --"--
  public
    WordDoc: OleVariant;
    IEvt : TEventObject;  // see linked question
    Cookie : Integer;
    App : WordApplication;
[...]

procedure TForm1.WordApplication1DocumentOpen(ASender: TObject; const Doc:
    _Document);
begin
  //
end;

I could post an MCVE instead, but it would mostly be just the code from the earlier answer. 我可以发布一个MCVE,但是它主要只是先前答案中的代码。

This had me scratching my head for a while, I can tell you. 我可以告诉你,这让我挠了一下头。 Anyway, eventually the penny dropped that the answer must lie in the difference between the way TEventObject is implemented and TServerEventDispatch in OleServer.Pas. 无论如何,最终,一分钱都没有得到答案,答案必须在于实现TEventObject的方式与OleServer.Pas中的TServerEventDispatch之间的差异。

The key is that TServerEventDispatch implements a custom QueryInterface 关键是TServerEventDispatch实现了自定义QueryInterface

function TServerEventDispatch.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
  if GetInterface(IID, Obj) then
  begin
    Result := S_OK;
    Exit;
  end;
  if IsEqualIID(IID, FServer.FServerData^.EventIID) then
  begin
    GetInterface(IDispatch, Obj);
    Result := S_OK;
    Exit;
  end;
  Result := E_NOINTERFACE;
end;

whereas TEventObject does not. 而TEventObject则没有。 Once I'd spotted that, it was straightforward to extend TEventObject to do likewise, and voila! 一旦发现这一点,就可以直接扩展TEventObject来做到这一点,瞧! the error returned by "CP.Advise" went away. “ CP.Advise”返回的错误消失了。

For completeness, I've included the complete source of the updated TEventObject below. 为了完整起见,我在下面提供了更新的TEventObject的完整源代码。 It is the 它是

if IsEquallIID then ... 

which makes the difference between 这之间的区别

Res := CP.Advise(IEvt, Cookie);

returning the $800040200 error and zero for success. 返回$ 800040200错误,成功返回零。 With the "if IsEquallIID then ..." commented out, the RefCount on IEvt is 48 (!) after "CP.Advise ..." returns, by which time TEventObject.QueryInterface has been called no less than 21 times. 注释掉“ if IsEquallIID then ...”后,返回“ CP.Advise ...”后,IEvt上的RefCount为48(!),此时已调用TEventObject.QueryInterface不少于21次。

I hadn't realised previously (because TEventObject didn't previously have its own version to observe) that when "CP.Advise ..." is executed, the COM system calls "TEventObject.QueryInterface" with a succession of different IIDs until it returns S_Ok on one of them. 我以前没有意识到(因为TEventObject以前没有自己要观察的版本),当执行“ CP.Advise ...”时,COM系统会使用一系列不同的IID调用“ TEventObject.QueryInterface”,直到它对其中之一返回S_Ok。 When I have some free time, maybe I'll try to look up what these other IIDs are: as it is, the IID for IDispatch is quite a long way down the list of IIDs that are queried for, which seems strangely sub-optimal seeing as I'd have though that would be the one that IConnectionPoint.Advise would be trying to get. 当我有空闲时间时,也许我会尝试查找其他IID的含义:确实,IDispatch的IID在要查询的IID列表的下方还有很长的路要走,这似乎是次优的就像我所看到的那样,那将是IConnectionPoint.Advise试图得到的那种。

Code for updated TEventObject is below. 更新的TEventObject的代码如下。 It includes a rather rough'n ready customization of its Invoke() which is specific to handling Word's DocumentOpen event. 它包括对其Invoke()的相当粗糙的自定义,该自定义专门用于处理Word的DocumentOpen事件。

type
   TInvokeEvent = procedure(Sender : TObject; const Doc : _Document) of object;

  TEventObject = class(TInterfacedObject, IUnknown, IDispatch)
  private
    FOnEvent: TInvokeEvent;
    FEventIID: TGuid;
  protected
    function GetTypeInfoCount(out Count: Integer): HResult; stdcall;
    function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; stdcall;
    function GetIDsOfNames(const IID: TGUID; Names: Pointer;
      NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall;
    function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer;
      Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall;
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
  public
    constructor Create(const AnEvent : TInvokeEvent);
    property OnEvent: TInvokeEvent read FOnEvent write FOnEvent;
    property EventIID : TGuid read FEventIID;
  end;

constructor TEventObject.Create(const AnEvent: TInvokeEvent);
begin
  inherited Create;
  FEventIID := DIID_ApplicationEvents2;
  FOnEvent := AnEvent;
end;

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

function TEventObject.GetTypeInfo(Index, LocaleID: Integer;
  out TypeInfo): HResult;
begin
  Pointer(TypeInfo) := nil;
  Result := E_NOTIMPL;
end;

function TEventObject.GetTypeInfoCount(out Count: Integer): HResult;
begin
  Count := 0;
  Result := E_NOTIMPL;
end;

function TEventObject.Invoke(DispID: Integer; const IID: TGUID;
  LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo,
  ArgErr: Pointer): HResult;
var
  vPDispParams: PDispParams;
  tagV : TagVariant;
  V : OleVariant;
  Doc : _Document;
begin
  vPDispParams := PDispParams(@Params);
  if (vPDispParams <> Nil) and (vPDispParams^.rgvarg <> Nil) then begin
    tagV := vPDispParams^.rgvarg^[0];
    V := OleVariant(tagV);
    Doc := IDispatch(V) as _Document;
    //  the DispID for DocumentOpen of Word's ApplicationEvents2 interface is 4
    if (DispID = 4) and Assigned(FOnEvent) then
      FOnEvent(Self, Doc);
    end;
  Result := S_OK;
end;

function TEventObject.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
  if GetInterface(IID, Obj) then
  begin
    Result := S_OK;
    Exit;
  end;
  if IsEqualIID(IID, EventIID) then
  begin
    GetInterface(IDispatch, Obj);
    Result := S_OK;
    Exit;
  end;
  Result := E_NOINTERFACE;
end;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM