Base de conhecimento KB0122

Com o think-cell, algumas chamadas de Automação COM do Excel falham

Problema

Com o think-cell instalado, algumas chamadas de Automação COM do Excel falham. O código de erro HRESULT é 0x8001010A RPC_E_SERVERCALL_RETRYLATER. No LotusScript, o erro é Automation object error.

Os servidores COM têm a capacidade de filtrar chamadas de entrada usando o mecanismo IMessageFilter. Mais especificamente, se a implementação IMessageFilter::HandleInComingCall retornar SERVERCALL_RETRYLATER, a chamada do cliente retorna com RPC_E_SERVERCALL_RETRYLATER.

O Excel usa este mecanismo independentemente do think-cell. A presença do think-cell aumenta a frequência de RPC_E_SERVERCALL_RETRYLATER, que torna o problema mais comum.

Um produto conhecido por exibir erros nessas circunstâncias é o mecanismo de script do IBM Lotus Notes, o LotusScript.

Solução

Em resposta a RPC_E_SERVERCALL_RETRYLATER, o chamador tem que aguardar um pouco e depois repetir a chamada. O Visual Basic for Applications tem esse tratamento de erro integrado, por isso trata-se provavelmente de um bug, se outros ambientes de script do Windows que suportam COM, como o LotusScript, não o fizerem.

Fora dos ambientes de script, ou se o ambiente de script não fizer isso, os chamadores têm que tratar o erro RPC_E_SERVERCALL_RETRYLATER sozinhos. O COM torna esse processo bastante fácil, porque pode repetir automaticamente chamadas em nome do cliente, se lhe for indicado que deve fazer isso. O código de chamada real pode permanecer inalterado.

Para ativar esse comportamento, o cliente tem que implementar seu próprio IMessageFilter com o método IMessageFilter::RetryRejectedCall, que retorna um valor diferente de -1 se o parâmetro dwRejectType for SERVERCALL_RETRYLATER. A seguir, em cada thread STA que realizar chamadas de COM, ele tem que usar CoRegisterMessageFilter para registrar uma instância dessa implementação com COM.

Como exemplo, aqui está uma implementação ATL RAII de C++:

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);
  }
};

Compartilhar