Con think-cell, algunas llamadas de automatización COM de Excel fallan
Problema
Con think-cell instalado, algunas llamadas de automatización COM de Excel fallan. El código de error HRESULT es 0x8001010A RPC_E_SERVERCALL_RETRYLATER
. En LotusScript, el error es Automation object error
.
Los servidores COM tienen la capacidad de filtrar las llamadas entrantes mediante el mecanismo IMessageFilter
. Más concretamente, si la implantación de IMessageFilter::HandleInComingCall
devuelve SERVERCALL_RETRYLATER
, la llamada del cliente devuelve RPC_E_SERVERCALL_RETRYLATER
.
Excel utiliza este mecanismo independientemente de think-cell. La presencia de think-cell aumenta la frecuencia de RPC_E_SERVERCALL_RETRYLATER
, lo que hace que el problema sea más prevalente.
Un producto conocido por mostrar errores en estas circunstancias es el motor de secuencias de comandos de IBM Lotus Notes, LotusScript.
Solución
En respuesta a RPC_E_SERVERCALL_RETRYLATER
, el proceso que llama deberá esperar brevemente y luego repetir la llamada. Visual Basic para Aplicaciones tiene esta gestión integrada, por lo que se puede decir que es un error si otros entornos de secuencias de comandos de Windows compatibles con COM, como LotusScript, no lo hacen.
Fuera del entorno de secuencias de comandos, o si este entorno no lo hace, los procesos que llaman deberán gestionar RPC_E_SERVERCALL_RETRYLATER
por sí mismos. COM hace que esto sea bastante fácil, ya que puede repetir automáticamente las llamadas en nombre del cliente si se le indica que así lo haga. El código de llamada real puede permanecer sin cambios.
Para habilitar este comportamiento, el cliente deberá implementar su propio IMessageFilter
, procurando que el método IMessageFilter::RetryRejectedCall
devuelva algún resultado distinto de -1 si el parámetro dwRejectType
es SERVERCALL_RETRYLATER
. A continuación, en cada subproceso STA que realice llamadas COM, deberá usar CoRegisterMessageFilter
para registrar una instancia de esta implementación con COM.
Como ejemplo, aquí se muestra una implantación 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);
}
};