Base de dados 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 é um Erro de objeto de automação.

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 fizerem isso, os chamadores têm que tratar do erro RPC_E_SERVERCALL_RETRYLATER. 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. Em seguida, 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);
  }
};