ナレッジベース KB0122

think-cellを使用すると一部のExcel COMオートメーション呼び出しが失敗する

問題

think-cellをインストールすると、一部のExcel COMオートメーション呼び出しが失敗します。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 for Applicationsは、この対応が内蔵されているため、COMをサポートする他のWindowsスクリプト環境(LotusScriptなど)が対応できない場合はバグになる可能性があります。

スクリプト環境の外では、またはスクリプト環境が対応しない場合は、発信者がRPC_E_SERVERCALL_RETRYLATERに自ら対応しなくてはなりません。COMでは、これがかなり容易になります。指示があれば、クライアントの代わりに呼び出しが自動的に繰り返されるためです。実際の呼び出し側コードは変更なしのままにできます。

この動作を有効にするには、クライアントは独自の IMessageFilter を実装する必要があります。dwRejectType パラメーターが SERVERCALL_RETRYLATER の場合、メソッド IMessageFilter::RetryRejectedCall は -1 以外のものを返します。次に、COM 呼び出しを実行する各 STA スレッドで、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);
  }
};

共有する