Knowledge Base 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 ist 0x8001010A RPC_E_SERVERCALL_RETRYLATER. In LotusScript ist 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 Client-Aufruf 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 dwRejectType Parameter 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);
  }
};

Teilen