Knowledge-Base-Artikel (KB0122)

Bei installiertem think-cell schlagen manche Excel COM-Automatisierungsaufrufe fehl.

Problem

Bei installiertem think-cell schlagen manche Excel COM-Automatisierungsaufrufe fehl. Der HRESULT-Fehlercode lautet 0x8001010A RPC_E_SERVERCALL_RETRYLATER. In LotusScript heißt der Fehler Automation object error.

COM-Server können eingehende Aufrufe mithilfe des IMessageFilter-Verfahrens filtern. Wenn die IMessageFilter::HandleInComingCall-Implementierung den Wert SERVERCALL_RETRYLATER zurückgibt, gibt der Clientaufruf den Wert RPC_E_SERVERCALL_RETRYLATER zurück.

Excel nutzt dieses Verfahren unabhängig von think-cell. Die Installation von think-cell erhöht jedoch die Häufigkeit von RPC_E_SERVERCALL_RETRYLATER, sodass das Problem häufiger auftritt.

Ein Produkt, bei dem in diesem Fall bekanntermaßen Fehler auftreten, ist das Skriptmodul LotusScript von IBM Lotus Notes.

Lösung

Wegen RPC_E_SERVERCALL_RETRYLATER muss der Aufrufer kurz warten und dann den Aufruf wiederholen. In Visual Basic for Applications ist die Behandlung integriert. Das Problem lässt sich also durchaus als Fehler bezeichnen, wenn andere Windows-Skriptumgebungen, die COM unterstützen (zum Beispiel LotusScript), diese Behandlung nicht vorsehen.

Außerhalb von Skriptumgebungen müssen Aufrufer RPC_E_SERVERCALL_RETRYLATER selbst ausführen. Das gilt auch für den Fall, dass die Skriptumgebung den Vorgang nicht durchführt. COM erleichtert den Umgang, da Aufrufe für den Client automatisch wiederholt werden können, wenn eine entsprechende Anweisung vorliegt. Der aktuelle Aufrufcode muss dabei nicht verändert werden.

Um den Vorgang zu aktivieren, muss der Client seinen eigenen IMessageFilter implementieren. Dabei muss die Methode IMessageFilter::RetryRejectedCall einen anderen Wert als -1 zurückgeben, wenn der Parameter dwRejectType SERVERCALL_RETRYLATER lautet. In jedem STA-Thread zur Ausführung von COM-Aufrufen muss dann CoRegisterMessageFilter verwendet werden, um eine Instanz dieser Implementierung bei COM zu registrieren.

Das folgende Beispiel ist eine Implementierung mit C++ ATL RAII:

class CHandleRetryLaterBase :
 public CComObjectRootEx<CComSingleThreadModel>,
 public IMessageFilter
{
protected:
 CComPtr<IMessageFilter> m_imessagefilterOld;

 BEGIN_COM_MAP(CHandleRetryLaterBase)
 COM_INTERFACE_ENTRY(IMessageFilter)
 END_COM_MAP()

public:
 // IMessageFilter implementation
 DWORD STDMETHODCALLTYPE HandleInComingCall(
 /* [in] */ DWORD dwCallType,
 /* [in] */ HTASK htaskCaller,
 /* [in] */ DWORD dwTickCount,
 /* [in] */ LPINTERFACEINFO lpInterfaceInfo
  ) {
 if( m_imessagefilterOld ) {
 // pass on to old handler, no change in behavior
 return m_imessagefilterOld->HandleInComingCall(
 dwCallType,
 htaskCaller,
 dwTickCount,
 lpInterfaceInfo
      );
 } else {
 // default behavior
 return SERVERCALL_ISHANDLED;
    }
  }

 DWORD STDMETHODCALLTYPE RetryRejectedCall(
 /* [in] */ HTASK htaskCallee,
 /* [in] */ DWORD dwTickCount,
 /* [in] */ DWORD dwRejectType
  ) {
 if( SERVERCALL_RETRYLATER==dwRejectType ) {
 // in a script, wait indefinitely for robustness under load
 return 100;
 } else if( m_imessagefilterOld ) {
 // pass on to old handler, no change in behavior
 return m_imessagefilterOld->RetryRejectedCall(
 htaskCallee, 
 dwTickCount,
 dwRejectType ); 
 } else {
 // default behavior
 return (DWORD)-1;
    }
  }

 DWORD STDMETHODCALLTYPE MessagePending(
 /* [in] */ HTASK htaskCallee,
 /* [in] */ DWORD dwTickCount,
 /* [in] */ DWORD dwPendingType
  ) {
 if( m_imessagefilterOld ) {
 // pass on to old handler, no change in behavior
 return m_imessagefilterOld->MessagePending(
 htaskCallee,
 dwTickCount,
 dwPendingType );
 } else {
 // default behavior
 return PENDINGMSG_WAITDEFPROCESS;
    }
  }
};

// Instantiate this class in your scope to enable robust IMessageFilter.
class CHandleRetryLater :
 public CHandleRetryLaterBase
{
public:
 CHandleRetryLater() {
 // set new handler and save old one
 _ASSERT( m_dwRef==0 );
 HRESULT hr=CoRegisterMessageFilter( this, &m_imessagefilterOld );
 // m_imessagefilterOld may be nullptr if there is no
 // filter previously installed
 _ASSERT( SUCCEEDED(hr) );
  }

 ~CHandleRetryLater() {
 // reset old handler
    {
 CComPtr<IMessageFilter> iMessageFilter;
 HRESULT hr=CoRegisterMessageFilter(
 m_imessagefilterOld,
 &iMessageFilter );
 _ASSERT( SUCCEEDED(hr) );
 // make sure noone replaced our IMessageFilter
 _ASSERT( iMessageFilter==static_cast<IMessageFilter*>(this) );
 } // iMessageFilter is last release
 _ASSERT( m_dwRef==0 );
  }

 STDMETHOD_(ULONG, AddRef)() throw() {
#ifdef _DEBUG
 return InternalAddRef();
#else
 return 0;
#endif
  }

 STDMETHOD_(ULONG, Release)() throw() {
#ifdef _DEBUG
 return InternalRelease();
#else
 return 0;
#endif
  }

 STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject) throw() {
 return _InternalQueryInterface(iid, ppvObject);
  }
};