KB0122: Con thinkcell alcune chiamate COM Automation di Excel non riescono

  1. Home
  2. Risorse
  3. Knowledge base
  4. KB0122

Problema

Con thinkcell installato, alcune chiamate COM Automation di Excel non riescono. Il codice di errore HRESULT è 0x8001010A RPC_E_SERVERCALL_RETRYLATER. In LotusScript, l’errore è Automation object error.

I server COM hanno la possibilità di filtrare le chiamate in ingresso usando il meccanismo IMessageFilter. Più specificamente, se l’implementazione IMessageFilter::HandleInComingCall restituisce SERVERCALL_RETRYLATER, la chiamata del client restituisce RPC_E_SERVERCALL_RETRYLATER.

Excel usa questo meccanismo indipendentemente da thinkcell. La presenza di thinkcell aumenta la frequenza di RPC_E_SERVERCALL_RETRYLATER, rendendo il problema più diffuso.

Un prodotto noto per mostrare errori in queste circostanze è il motore di scripting di IBM Lotus Notes, LotusScript.

Soluzione

In risposta a RPC_E_SERVERCALL_RETRYLATER, il chiamante deve attendere brevemente e quindi ripetere la chiamata. Visual Basic for Applications dispone di questa gestione integrata, quindi si può sostenere che sia un bug se altri ambienti di scripting Windows che supportano COM, come LotusScript, non lo fanno.

Al di fuori degli ambienti di scripting, o se l’ambiente di scripting non lo fa, i chiamanti devono gestire autonomamente RPC_E_SERVERCALL_RETRYLATER. COM rende questa operazione abbastanza semplice perché può ripetere automaticamente le chiamate per conto del client se configurato in tal senso. Il codice di chiamata effettivo può rimanere invariato.

Per abilitare questo comportamento, il client deve implementare il proprio IMessageFilter, con il metodo IMessageFilter::RetryRejectedCall che restituisce un valore diverso da -1 se il parametro dwRejectType è SERVERCALL_RETRYLATER. Quindi, in ogni thread STA che esegue chiamate COM, deve usare CoRegisterMessageFilter per registrare un’istanza di questa implementazione con COM.

Ad esempio, ecco un’implementazione C++ ATL RAII: Scopri di più

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

  BEGIN_COM_MAP(CHandleRetryLaterBase)
    COM_INTERFACE_ENTRY(IMessageFilter)
  END_COM_MAP()

public:
  // Implementazione di IMessageFilter
  DWORD STDMETHODCALLTYPE HandleInComingCall(
    /* [in] */ DWORD dwCallType,
    /* [in] */ HTASK htaskCaller,
    /* [in] */ DWORD dwTickCount,
    /* [in] */ LPINTERFACEINFO lpInterfaceInfo
  ) {
    if( m_imessagefilterOld ) {
      // passa al gestore precedente, nessuna modifica del comportamento
      return m_imessagefilterOld->HandleInComingCall(
        dwCallType,
        htaskCaller,
        dwTickCount,
        lpInterfaceInfo
      );
    } else {
      // comportamento predefinito
      return SERVERCALL_ISHANDLED;
    }
  }

  DWORD STDMETHODCALLTYPE RetryRejectedCall(
    /* [in] */ HTASK htaskCallee,
    /* [in] */ DWORD dwTickCount,
    /* [in] */ DWORD dwRejectType
  ) {
    if( SERVERCALL_RETRYLATER==dwRejectType ) {
      // in uno script, attendere indefinitamente per garantire robustezza sotto carico
      return 100;
    } else if( m_imessagefilterOld ) {
      // passa al gestore precedente, nessuna modifica del comportamento
      return m_imessagefilterOld->RetryRejectedCall(
        htaskCallee, 
        dwTickCount,
        dwRejectType );			
    } else {
      // comportamento predefinito
      return (DWORD)-1;
    }
  }

  DWORD STDMETHODCALLTYPE MessagePending(
    /* [in] */ HTASK htaskCallee,
    /* [in] */ DWORD dwTickCount,
    /* [in] */ DWORD dwPendingType
  ) {
    if( m_imessagefilterOld ) {
      // passa al gestore precedente, nessuna modifica del comportamento
      return m_imessagefilterOld->MessagePending(
        htaskCallee,
        dwTickCount,
        dwPendingType );
    } else {
      // comportamento predefinito
      return PENDINGMSG_WAITDEFPROCESS;
    }
  }
};

// Creare un'istanza di questa classe nell'ambito per abilitare IMessageFilter robusto.
class CHandleRetryLater :
  public CHandleRetryLaterBase
{
public:
  CHandleRetryLater() {
    // imposta il nuovo gestore e salva quello precedente
    _ASSERT( m_dwRef==0 );
    HRESULT hr=CoRegisterMessageFilter( this, &m_imessagefilterOld );
    // m_imessagefilterOld può essere nullptr se non è presente alcun
    // filtro installato in precedenza
    _ASSERT( SUCCEEDED(hr) );
  }

  ~CHandleRetryLater() {
    // ripristina il gestore precedente
    {
      CComPtr<IMessageFilter> iMessageFilter;
      HRESULT hr=CoRegisterMessageFilter(
        m_imessagefilterOld,
        &iMessageFilter );
      _ASSERT( SUCCEEDED(hr) );
      // assicurarsi che nessuno abbia sostituito il nostro IMessageFilter
      _ASSERT( iMessageFilter==static_cast<IMessageFilter*>(this) );
    } // iMessageFilter è l'ultimo rilascio
    _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);
  }
};