开发者

Why does my IMessageFilter not always work?

I'm working on Word automation and to get rid of "Call was rejected by callee" / "the message filter indicated that the application is busy" errors I implemented an IMessageFilter. The messagefilter works 开发者_如何学Clike a charm when I automate Word directly like:

Word.Documents.Open(...)
Document.SaveAs(...)

But when I call TOleContainer.DoVerb(ovPrimary), I still get errors when Word is displaying a modal dialog. Why does the MessageFilter not work with TOleContainers DoVerb methode?


"Call was rejected by callee" is what you always get when Word is in interactive state, ie displaying a dialog. This is not restricted to Word. It also happens with Excel, for example when the user was editing a cell. And it does not have to be obvious in the user interface either. When you start editing a cell, move focus to another application and come back to Excel, the UI doesn't give you a clue but it is still in "interactive" mode and will reject automation calls with the "Call was rejected by callee" error.

So basically when you automate Word in conjunction with user interaction (and not just with Word in a background process), you should be prepared to get and handle these errors.

Edit If you want to know whether Excel or Word is in interactive mode before calling any other COM method: just ask the COM-server whether it is "Ready":

Result := _GetActiveOleObject('Excel.Application');

try
  aSharedInstance := not VarIsClear(Result);
  if aSharedInstance then
    Version := Result.Version;  // If this produces an exception, then use a dedicated instance.

  // In case checking the version does not produce an exception, but Excel still isn't
  // ready, we'll check that as well.
  // By the way, for some unclear reason, partial evaluation does not work on .Ready, 
  // so we'll do it like this:
  if aSharedInstance and (StrToIntDef(StringBefore('.', Version), 0) >= EXCEL_VERSION_2002) then
    aSharedInstance := Result.Ready;
except
  aSharedInstance := False;
end;

if not aSharedInstance then
  Result := CreateOleObject('Excel.Application');

Update Apparently Word doesn't have a "Ready" property (whoever said Microsoft was consistent?). In that case you need to determine its readiness yourself by calling a simple (and fast) property before the actual call, and assuming that when that throws an exception, Word isn't ready. In the above example the Version is retrieved before the Ready property. If that throws an exception, we just assume that the application (Excel in this case) isn't ready and proceed accordingly.

Something along the lines of:

while Tries <= MaxTries do
  try
    Version := Word.Version;
    Tries := MaxTries + 1; // Indicate success
    Word.TheCallYouReallyWantToDo;
  except
    Inc(Tries);
    sleep(0);
  end;

Note Word.Version does not throw an exception when a dialog is open, so that is no use for figuring out whether Word is ready. :( You will have to experiment to find one that does.


IMessageFilter doesn't handle all exceptions, for example, at some points, office applications 'suspend' their object model, at which point it cannot be invoked and throws: 0x800AC472 (VBA_E_IGNORE)

In order to get around this, you have to put your call in a loop and wait for it to succeed:

while(true)
{
    try
    {
        office_app.DoSomething();
        break;
    }
    catch(COMException ce)
    {
        LOG(ce.Message);
    }
}

// continue after successful call

See here for more details.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜