Com o think-cell, algumas chamadas de Automação COM do Excel falham
Problema
Com o think-cell instalado, algumas chamadas de Automação COM do Excel falham. O código de erro HRESULT é 0x8001010A RPC_E_SERVERCALL_RETRYLATER
. No LotusScript, o erro é Automation object error
.
Os servidores COM têm a capacidade de filtrar chamadas de entrada usando o mecanismo IMessageFilter
. Mais especificamente, se a implementação IMessageFilter::HandleInComingCall
retornar SERVERCALL_RETRYLATER
, a chamada do cliente retorna com RPC_E_SERVERCALL_RETRYLATER
.
O Excel usa este mecanismo independentemente do think-cell. A presença do think-cell aumenta a frequência de RPC_E_SERVERCALL_RETRYLATER
, que torna o problema mais comum.
Um produto conhecido por exibir erros nessas circunstâncias é o mecanismo de script do IBM Lotus Notes, o LotusScript.
Solução
Em resposta a RPC_E_SERVERCALL_RETRYLATER
, o chamador tem que aguardar um pouco e depois repetir a chamada. O Visual Basic for Applications tem esse tratamento de erro integrado, por isso trata-se provavelmente de um bug, se outros ambientes de script do Windows que suportam COM, como o LotusScript, não o fizerem.
Fora dos ambientes de script, ou se o ambiente de script não fizer isso, os chamadores têm que tratar o erro RPC_E_SERVERCALL_RETRYLATER
sozinhos. O COM torna esse processo bastante fácil, porque pode repetir automaticamente chamadas em nome do cliente, se lhe for indicado que deve fazer isso. O código de chamada real pode permanecer inalterado.
Para ativar esse comportamento, o cliente tem que implementar seu próprio IMessageFilter
com o método IMessageFilter::RetryRejectedCall
, que retorna um valor diferente de -1 se o parâmetro dwRejectType
for SERVERCALL_RETRYLATER
. A seguir, em cada thread STA que realizar chamadas de COM, ele tem que usar CoRegisterMessageFilter
para registrar uma instância dessa implementação com COM.
Como exemplo, aqui está uma implementação ATL RAII de C++:
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);
}
};