`EOleException: Call was rejected by callee` while iterating through `Office.Interop.Word.Documents`

限于喜欢 提交于 2020-01-03 01:10:21

问题


I've been working with Word2010.pas for the past week and everything went well, until I found out that if you open a document manually, edit it (but don't save), press Alt+F4, a prompt will show up saying if you want to save your document or not, leave it like that. Go into code and try to access that document, all calls will result in EOleException: Call was rejected by callee. Once you cancel that Word save prompt, everything works fine.

I came across this while writing code that periodically checks if a document is open. Here is the function that checks if the document is open: (function runs in a timer every 2 seconds)

function IsWordDocumentOpen(FileName: string): Boolean;
var
  WordApp: TWordApplication;
  I: Integer;
begin
  Result := False;
  try
    WordApp := TWordApplication.Create(nil);
    try          
      WordApp.Connect;
      for I := 1 to WordApp.Documents.Count do
      begin
        try
          if WordApp.Documents.Item(I).FullName = FileName then
          begin
            Result := True;
            System.Break;
          end;
        except
          on E: EOleException do
            // I always end up here while the document has the prompt
        end;
      end;
    finally
      FreeAndNil(WordApp);
    end;
  finally
    //
  end;
end;

Does anyone have any experience with this? Is there some sort of a lock that I'm not aware of?

UPDATE #1: So far the only solution I could find was to implement IOleMessageFilter, this way I do not receive any exceptions but the program stops and waits on the line WordApp.Documents.Item(I).FullName, but that is not what I want. Implementation of IOleMessageFilter goes like this:

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;

BEST SOLUTION SO FAR: I used IOleMessageFilter implementation like this: (remember this will stop and wait on the line where I previously got an exception)

function IsWordDocumentOpen(FileName: string): Boolean;
var
  OleMessageFilter: IOleMessageFilter;
  WordApp: TWordApplication;
  I: Integer;
begin
  Result := False;
  try
    OleMessageFilter := IOleMessageFilter.Create;
    OleMessageFilter.RegisterFilter;

    WordApp := TWordApplication.Create(nil);
    try
      WordApp.Connect;
      for I := 1 to WordApp.Documents.Count do
      begin
        if WordApp.Documents.Item(I).FullName = FileName then
        begin
          Result := True;
          System.Break;
        end;
      end;
    finally
      OleMessageFilter.RevokeFilter;
      FreeAndNil(WordApp);
      FreeAndNil(OleMessageFilter);
    end;
  finally
    //
  end;
end;

回答1:


Actually, I think that the problem is simply that Word is busy doing a modal dialog and so can't respond to external COM calls. This trivial code produces the same error:

procedure TForm1.Button1Click(Sender: TObject);
begin
  Caption := MSWord.ActiveDocument.Name;
end;

Probably the simplest way to avoid this problem is to head it off before if happens. If you are using the TWordApplication server that comes with Delphi (on the Servers components tab), you can attach an event handler to its OnDocumentBeforeClose and use that to present your own "Save Y/N?" dialog and set the event's Cancel param to True to prevent Word's dialog from appearing.

Update: If you try experimenting with this code while the Save dialog is popped up

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;

I think you'll find that any attempt to read from the vDoc object will result in the "Call was rejected ..." message, so I am beginning to think that this behaviour is by design - it's telling you that the object is not in a state that it can be interacted with.

Interestingly, it is possible to read the Caption property of the vWin Window object, which will tell you the filename of the file but not the file's path.

Realistically, I still think your best option is to try and get the OnDocumentBeforeClose event working. I don't have Word 2010 installed on this machine by Word 2007 works fine with the Word server objects derived from Word2000.Pas so you might try those instead of Word2010.Pas, just to see.

Another possibility is simply to catch the "Call was rejected ..." exception, maybe return "Unavailable" as the document FullName, and try again later.

If you're not using TWordApplication and don't know how to catch the OnDocumentBeforeClose for the method your using to access Word, let me know how you are accessing it and I'll see if I can dig out some code to do it.

I vaguely recall there's a way of detecting that Word is busy with a modal dialog - I'll see if I can find where I saw that a bit later if you still need it. Your IOleMessageFilter looks more promising than anything I've found as yet, though.



来源:https://stackoverflow.com/questions/37273951/eoleexception-call-was-rejected-by-callee-while-iterating-through-office-int

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!