Delphi `EOleException:被调用方在遍历“Office.Interop.Word.Documents”时拒绝了调用`

Delphi `EOleException:被调用方在遍历“Office.Interop.Word.Documents”时拒绝了调用`,delphi,ms-word,office-interop,Delphi,Ms Word,Office Interop,在过去的一周里,我一直在使用Word2010.pas,一切都进行得很顺利,直到我发现如果你手动打开一个文档,编辑它(但不保存),按Alt+F4,就会出现一个提示,提示你是否要保存文档,就这样离开它进入代码并尝试访问该文档,所有调用将导致EOleException:被调用方拒绝调用。一旦取消Word保存提示,一切正常 我在编写定期检查文档是否打开的代码时遇到了这个问题。下面是检查文档是否打开的函数:(函数在计时器中每2秒运行一次) 有没有人有这方面的经验?有什么我不知道的锁吗 更新#1:到目前为止

在过去的一周里,我一直在使用
Word2010.pas
,一切都进行得很顺利,直到我发现如果你手动打开一个文档,编辑它(但不保存),按Alt+F4,就会出现一个提示,提示你是否要保存文档,就这样离开它进入代码并尝试访问该文档,所有调用将导致
EOleException:被调用方拒绝调用
。一旦取消Word保存提示,一切正常

我在编写定期检查文档是否打开的代码时遇到了这个问题。下面是检查文档是否打开的函数:(函数在计时器中每2秒运行一次)

有没有人有这方面的经验?有什么我不知道的锁吗

更新#1:到目前为止,我能找到的唯一解决方案是实现
IOleMessageFilter
,这样我不会收到任何异常,但程序会停止并在线等待
WordApp.Documents.Item(I).FullName
,但这不是我想要的。
IOleMessageFilter
的实现如下:

type
  IOleMessageFilter = class(TInterfacedObject, IMessageFilter)
  public
    function HandleInComingCall(dwCallType: Longint; htaskCaller: HTask;
      dwTickCount: Longint; lpInterfaceInfo: PInterfaceInfo): Longint;stdcall;
    function RetryRejectedCall(htaskCallee: HTask; dwTickCount: Longint;
      dwRejectType: Longint): Longint;stdcall;
    function MessagePending(htaskCallee: HTask; dwTickCount: Longint;
      dwPendingType: Longint): Longint;stdcall;
    procedure RegisterFilter();
    procedure RevokeFilter();
  end;

implementation

function IOleMessageFilter.HandleInComingCall(dwCallType: Integer; htaskCaller: HTask; dwTickCount: Integer; lpInterfaceInfo: PInterfaceInfo): Longint;
begin
  Result := 0;
end;

function IOleMessageFilter.MessagePending(htaskCallee: HTask; dwTickCount, dwPendingType: Integer): Longint;
begin
  Result := 2 //PENDINGMSG_WAITDEFPROCESS
end;

procedure IOleMessageFilter.RegisterFilter;
var
  OldFilter: IMessageFilter;
  NewFilter: IMessageFilter;
begin
  OldFilter := nil;
  NewFilter := IOleMessageFilter.Create;
  CoRegisterMessageFilter(NewFilter,OldFilter);
end;

function IOleMessageFilter.RetryRejectedCall(htaskCallee: HTask; dwTickCount, dwRejectType: Integer): Longint;
begin
  Result := -1;
  if dwRejectType = 2 then
    Result := 99;
end;

procedure IOleMessageFilter.RevokeFilter;
var
  OldFilter: IMessageFilter;
  NewFilter: IMessageFilter;
begin
  OldFilter := nil;
  NewFilter := nil;
  CoRegisterMessageFilter(NewFilter,OldFilter);
end;

end;
到目前为止最好的解决方案:我使用了
IOleMessageFilter
这样的实现:(记住,这将在我之前遇到异常的地方停止并等待)


事实上,我认为问题在于Word忙于进行模态对话,因此无法响应外部COM调用。这段琐碎的代码会产生相同的错误:

procedure TForm1.Button1Click(Sender: TObject);
begin
  Caption := MSWord.ActiveDocument.Name;
end;
也许避免这个问题的最简单的方法就是在它发生之前阻止它。如果您使用的是Delphi附带的TWordApplication server(在“服务器组件”选项卡上),则可以将事件处理程序附加到其
OnDocumentBeforeClose
,并使用该事件处理程序显示您自己的“Save Y/N?”对话框,并将事件的
Cancel
参数设置为True,以防止出现Word对话框

更新:如果在弹出
保存
对话框时尝试使用此代码

procedure TForm1.Button1Click(Sender: TObject);
var
  vWin,
  vDoc,
  vApp : OleVariant;
begin
  vWin := MSWord.ActiveWindow;
  Caption := vWin.Caption;
  vDoc := vWin.Document;

  vApp := vDoc.Application;  //  Attempt to read Word Document property

  Caption := vDoc.Path + '\';
  Caption := Caption + vDoc.Name;
end;
我想您会发现,任何读取vDoc对象的尝试都会导致“Call was rejected…”消息,因此我开始认为这种行为是经过设计的——它告诉您对象未处于可与之交互的状态

有趣的是,可以读取vWin窗口对象的
Caption
属性,它将告诉您文件名,但不会告诉您文件路径

实际上,我仍然认为您最好的选择是在关闭事件之前尝试获取
onDocument。我没有在Word 2007之前在这台机器上安装Word 2010,它可以很好地处理从Word2000.Pas派生的Word服务器对象,所以您可以尝试使用这些对象,而不是Word2010.Pas,只是为了看看

另一种可能是捕获“Call was rejected…”异常,可能返回“Unavailable”作为文档全名,稍后再试

如果您没有使用TWordApplication,并且不知道如何捕获用于访问Word的方法的
OnDocumentBeforeClose
,请告诉我您是如何访问它的,我将看看是否可以找到一些代码来执行此操作


我模模糊糊地回忆起有一种方法可以检测出单词正忙于模态对话——如果你还需要的话,我会看看我是否能在稍后找到我看到它的地方。不过,您的iIOleMessageFilter看起来比我目前发现的任何东西都更有希望。

我在活动结束前尝试了OnDocument,但似乎无法启动并运行它。我宁愿手动执行此检查,所以如果您知道一种检查Word是否忙的方法,那将非常棒!我会试一试,但首先(我应该早点问)你到底想做什么,“被呼叫者拒绝了呼叫”阻止了你?获取
WordDocument
WordApp.Documents.Item(I)的
FullName
.FullName
这是崩溃的地方。请注意,
TWordApplication.Caption
不会返回文档的名称,它实际上会返回包含
Word
的字符串,直到文档被激活,但激活它将再次导致
EOleException
procedure TForm1.Button1Click(Sender: TObject);
begin
  Caption := MSWord.ActiveDocument.Name;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
  vWin,
  vDoc,
  vApp : OleVariant;
begin
  vWin := MSWord.ActiveWindow;
  Caption := vWin.Caption;
  vDoc := vWin.Document;

  vApp := vDoc.Application;  //  Attempt to read Word Document property

  Caption := vDoc.Path + '\';
  Caption := Caption + vDoc.Name;
end;