views:

73

answers:

1

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 like 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?

+5  A: 

"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.

Marjan Venema
Of course, I know that, that's why I implemented the IMessageFilter. That way I can display a "Server is busy" dialog when Word is in a modal state (like displaying font dialog). The problem is, my IMessageFilter implementation does not work when DoVerb is called on the TOleContainer.
The_Fox
@The_Fox: Are you saying that calling DoVerb on TOleContainer even when Word is _not_ displaying a dialogue or in some other way "engaged"? If that is the case, that wasn't clear to me from your question.
Marjan Venema
No, what I mean: when I call DoVerb while Word is displaying a model dialog, I get the mentioned errors, despited having registered an IMessageFilter.
The_Fox
As expected because Word is in interactive mode. Correct me if I am wrong, but isn't IMessageFilter intended to filter the messages coming into your app, not filter the ones going out from your app to other applications? MSDN: http://msdn.microsoft.com/en-us/library/system.windows.forms.imessagefilter.aspx. So implementing IMessageFilter probably isn't going to help in "no-op"-ing the DoVerb. You are better of checking the "Ready" method of Word/Excel before calling DoVerb. This call is there specifically for that reason and is available as of Office2002.
Marjan Venema
You can use IMessageFilter also for outgoing calls, I already have that working: http://social.msdn.microsoft.com/forums/en-US/vsto/thread/70ef972b-51b6-4ece-a4af-d6b4e111eea5. Unfortunatly there is no Ready property for Word.
The_Fox
@The_Fox: ah ok, didn't know that. But reading the thread you referenced, I guess you are stuck with trying to determine in your message filter implementation whether Word is busy or not and acting accordingly. As Word doesn't have the Ready property (whoever said MS was consistent?), then you need to determine yourself whether Word is ready. I'd use a simple (and fast) property for that, and if an exception is thrown (as can happen with the Result.Version in my example, see its comment) accept that as an indication that Word isn't ready.
Marjan Venema
I will look into it tomorrow. I'm afraid that I have to build some sort of construction like you mentioned for every call that can go wrong and there are multiple: `OleContainer.DoVerb(ovPrimary)` `OleContainer.Iconic := False` `OleContainer.OleObjectInterface as _Document`. PS: Word.Version will not fail when a modal dialog shows.
The_Fox