Base de conocimientos KB0122

Con think-cell, algunas llamadas de automatización COM de Excel fallan

Problema

Con think-cell instalado, algunas llamadas de automatización COM de Excel fallan. El código de error HRESULT es 0x8001010A RPC_E_SERVERCALL_RETRYLATER. En LotusScript, el error es Automation object error.

Los servidores COM tienen la capacidad de filtrar las llamadas entrantes mediante el mecanismo IMessageFilter. De forma más específica, si la implantación IMessageFilter::HandleInComingCall devuelve SERVERCALL_RETRYLATER, la llamada del cliente devuelve RPC_E_SERVERCALL_RETRYLATER.

Excel utiliza este mecanismo independientemente de think-cell. La presencia de think-cell aumenta la frecuencia de RPC_E_SERVERCALL_RETRYLATER, lo que hace que el problema sea más prevalente.

Un producto conocido por mostrar errores en estas circunstancias es el motor de secuencias de comandos de IBM Lotus Notes, LotusScript.

Solución

En respuesta a RPC_E_SERVERCALL_RETRYLATER, el proceso que llama debe esperar brevemente y luego repetir la llamada. Visual Basic para Aplicaciones tiene esta gestión integrada, por lo que se puede decir que es un error si otros entornos de secuencias de comandos de Windows compatibles con COM, como LotusScript, no lo hacen.

Fuera del entorno de secuencias de comandos, o si el entorno de secuencias de comandos no lo hace, los procesos que llaman deben gestionar RPC_E_SERVERCALL_RETRYLATER por sí mismos. COM hace que esto sea bastante fácil, ya que puede repetir automáticamente las llamadas en nombre del cliente si se le indica que así lo haga. El código de llamada real puede permanecer sin cambios.

Para habilitar este comportamiento, el cliente debe implementar su propio IMessageFilter, haciendo que el método IMessageFilter::RetryRejectedCall devuelva algo distinto de -1 si el parámetro dwRejectType es SERVERCALL_RETRYLATER. A continuación, en cada subproceso STA que realice llamadas COM, debe usar CoRegisterMessageFilter para registrar una instancia de esta aplicación con COM.

Como ejemplo, aquí se muestra una implantación 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);
  }
};