Knowledge base KB0122

Con think-cell, alcune istanze di Excel COM Automation non riescono

Problema

Una volta installato think-cell, alcune chiamate di automazione COM di Excel hanno esito negativo. Il codice di errore HRESULT è 0x8001010A RPC_E_SERVERCALL_RETRYLATER. In LotusScript, l'errore è denominato Automation object error.

I server COM sono in grado di filtrare le chiamate in arrivo utilizzando il meccanismo IMessageFilter. In particolare, se l'implementazione IMessageFilter::HandleInComingCall restituisce SERVERCALL_RETRYLATER, la chiamata client restituisce RPC_E_SERVERCALL_RETRYLATER.

Excel utilizza questo meccanismo indipendentemente da think-cell. In presenza di think-cell RPC_E_SERVERCALL_RETRYLATER diventa più frequente, e di conseguenza anche il problema si verifica più spesso.

Un prodotto che notoriamente manifesta errori in queste circostanze è il motore di script di IBM Lotus Notes, LotusScript.

Soluzione

In risposta a RPC_E_SERVERCALL_RETRYLATER, il chiamante deve aspettare qualche secondo quindi ripetere la chiamata. In Visual Basic for Applications questo tipo di gestione è integrata, quindi è probabile che si tratti di un bug se altri ambienti di scripting di Windows che supportano COM, come ad esempio LotusScript, non sono in grado di gestire l'errore.

Al di fuori degli ambienti di scripting, oppure se l'ambiente di scripting non se ne occupa, è necessario che i chiamanti gestiscano RPC_E_SERVERCALL_RETRYLATER personalmente. COM facilita l'operazione poiché è in grado di ripetere automaticamente le chiamate da parte del client dietro apposite istruzioni. Il codice chiamante effettivo può rimanere immutato.

Per attivare questo comportamento, il client deve implementare il proprio IMessageFilter, facendo in modo che il metodo IMessageFilter::RetryRejectedCall restituisca un valore diverso da -1 se il parametro dwRejectType è SERVERCALL_RETRYLATER. Quindi, in ciascuno thread STA che effettua chiamate COM, è necessario utilizzare CoRegisterMessageFilter per registrare un'istanza di questa implementazione presso COM.

Di seguito un esempio di implementazione 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);
  }
};