База знаний KB0122

Некоторые вызовы автоматизации COM Excel при включенном think-cell завершаются ошибкой

Проблема

При установленном think-cell некоторые вызовы автоматизации COM Excel завершаются ошибкой. Код ошибки HRESULT: 0x8001010A RPC_E_SERVERCALL_RETRYLATER. В LotusScript код ошибки: Automation object error.

Сервера COM фильтруют входящие вызовы с помощью механизма IMessageFilter. В частности, если осуществление IMessageFilter::HandleInComingCall возвращает значение SERVERCALL_RETRYLATER, то клиентский вызов возвращает RPC_E_SERVERCALL_RETRYLATER.

Excel задействует этот механизм независимо от наличия think-cell. Наличие think-cell увеличивает частоту получения значения RPC_E_SERVERCALL_RETRYLATER, что делает проблему более очевидной.

Обработчик сценариев IBM Lotus Notes, LotusScript — один из тех продуктов, в котором возникают подобные ошибки при таких условиях.

Решение

Получив в ответ RPC_E_SERVERCALL_RETRYLATER, вызывающая сторона должна немного подождать и затем повторить вызов. В Visual Basic для приложений встроена такая обработка, поэтому это, вероятно, программная ошибка, если другие среды исполнения сценариев Windows, поддерживающие COM, вроде LotusScript, не выполняют такую обработку.

За пределами среды исполнения сценариев или, если подобная среда не выполняет такую обработку, вызывающая сторона должна обрабатывать получение результата RPC_E_SERVERCALL_RETRYLATER самостоятельно. COM упрощает такое поведение, т.к. автоматически повторяет запросы от лица клиента, получив подобную инструкцию. Фактический код вызова можно оставить неизменным.

Для обеспечения подобного поведения клиент должен реализовать собственный механизм IMessageFilter, в котором методIMessageFilter::RetryRejectedCall будет возвращать значение отличное от -1, если параметр dwRejectType равен SERVERCALL_RETRYLATER. Затем в каждом потоке STA, из которого осуществляется вызовы COM, следует использовать CoRegisterMessageFilter для регистрации экземпляра этой реализации с COM.

Вот пример реализации на 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);
  }
};

Поделиться