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 に登録する必要があります。
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);
}
};