With think-cell some Excel COM Automation calls fail
Problem
With think-cell installed, some Excel COM Automation calls fail. The HRESULT error code is 0x8001010A RPC_E_SERVERCALL_RETRYLATER
. In LotusScript, the error is Automation object error
.
COM servers have the ability to filter incoming calls using the IMessageFilter
mechanism. More specifically, if the IMessageFilter::HandleInComingCall
implementation returns SERVERCALL_RETRYLATER
, the client call returns with RPC_E_SERVERCALL_RETRYLATER
.
Excel uses this mechanism irrespective of think-cell. The presence of think-cell increases the frequency of RPC_E_SERVERCALL_RETRYLATER
, which makes the problem more prevalent.
One product known to show errors in these circumstances is the scripting engine of IBM Lotus Notes, LotusScript.
Solution
In response to RPC_E_SERVERCALL_RETRYLATER
, the caller must briefly wait and then repeat the call. Visual Basic for Applications has this handling built in, so it is arguably a bug if other Windows scripting environments that support COM, such as LotusScript, do not do it.
Outside of scripting environments, or if the scripting environment does not do it, callers must handle RPC_E_SERVERCALL_RETRYLATER
themselves. COM makes this fairly easy because it can automatically repeat calls on behalf of the client if it is told to do so. The actual calling code can stay unchanged.
To enable this behavior, the client must implement its own IMessageFilter
, with the method IMessageFilter::RetryRejectedCall
returning something other than -1 if the dwRejectType
parameter is SERVERCALL_RETRYLATER
. Then, in each STA thread doing COM calls, it must use CoRegisterMessageFilter
to register an instance of this implementation with COM.
As an example, here is a C++ ATL RAII implementation:
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);
}
};