KB0122: Con thinkcell alcune chiamate COM Automation di Excel non riescono
- Home
- Risorse
- Knowledge base
- KB0122
Problema
Con thinkcell installato, alcune chiamate COM Automation di Excel non riescono. Il codice di errore HRESULT è 0x8001010A RPC_E_SERVERCALL_RETRYLATER. In LotusScript, l’errore è Automation object error.
I server COM hanno la possibilità di filtrare le chiamate in ingresso usando il meccanismo IMessageFilter. Più specificamente, se l’implementazione IMessageFilter::HandleInComingCall restituisce SERVERCALL_RETRYLATER, la chiamata del client restituisce RPC_E_SERVERCALL_RETRYLATER.
Excel usa questo meccanismo indipendentemente da thinkcell. La presenza di thinkcell aumenta la frequenza di RPC_E_SERVERCALL_RETRYLATER, rendendo il problema più diffuso.
Un prodotto noto per mostrare errori in queste circostanze è il motore di scripting di IBM Lotus Notes, LotusScript.
Soluzione
In risposta a RPC_E_SERVERCALL_RETRYLATER, il chiamante deve attendere brevemente e quindi ripetere la chiamata. Visual Basic for Applications dispone di questa gestione integrata, quindi si può sostenere che sia un bug se altri ambienti di scripting Windows che supportano COM, come LotusScript, non lo fanno.
Al di fuori degli ambienti di scripting, o se l’ambiente di scripting non lo fa, i chiamanti devono gestire autonomamente RPC_E_SERVERCALL_RETRYLATER. COM rende questa operazione abbastanza semplice perché può ripetere automaticamente le chiamate per conto del client se configurato in tal senso. Il codice di chiamata effettivo può rimanere invariato.
Per abilitare questo comportamento, il client deve implementare il proprio IMessageFilter, con il metodo IMessageFilter::RetryRejectedCall che restituisce un valore diverso da -1 se il parametro dwRejectType è SERVERCALL_RETRYLATER. Quindi, in ogni thread STA che esegue chiamate COM, deve usare CoRegisterMessageFilter per registrare un’istanza di questa implementazione con COM.
Ad esempio, ecco un’implementazione C++ ATL RAII: Scopri di più
class CHandleRetryLaterBase :
public CComObjectRootEx<CComSingleThreadModel>,
public IMessageFilter
{
protected:
CComPtr<IMessageFilter> m_imessagefilterOld;
BEGIN_COM_MAP(CHandleRetryLaterBase)
COM_INTERFACE_ENTRY(IMessageFilter)
END_COM_MAP()
public:
// Implementazione di IMessageFilter
DWORD STDMETHODCALLTYPE HandleInComingCall(
/* [in] */ DWORD dwCallType,
/* [in] */ HTASK htaskCaller,
/* [in] */ DWORD dwTickCount,
/* [in] */ LPINTERFACEINFO lpInterfaceInfo
) {
if( m_imessagefilterOld ) {
// passa al gestore precedente, nessuna modifica del comportamento
return m_imessagefilterOld->HandleInComingCall(
dwCallType,
htaskCaller,
dwTickCount,
lpInterfaceInfo
);
} else {
// comportamento predefinito
return SERVERCALL_ISHANDLED;
}
}
DWORD STDMETHODCALLTYPE RetryRejectedCall(
/* [in] */ HTASK htaskCallee,
/* [in] */ DWORD dwTickCount,
/* [in] */ DWORD dwRejectType
) {
if( SERVERCALL_RETRYLATER==dwRejectType ) {
// in uno script, attendere indefinitamente per garantire robustezza sotto carico
return 100;
} else if( m_imessagefilterOld ) {
// passa al gestore precedente, nessuna modifica del comportamento
return m_imessagefilterOld->RetryRejectedCall(
htaskCallee,
dwTickCount,
dwRejectType );
} else {
// comportamento predefinito
return (DWORD)-1;
}
}
DWORD STDMETHODCALLTYPE MessagePending(
/* [in] */ HTASK htaskCallee,
/* [in] */ DWORD dwTickCount,
/* [in] */ DWORD dwPendingType
) {
if( m_imessagefilterOld ) {
// passa al gestore precedente, nessuna modifica del comportamento
return m_imessagefilterOld->MessagePending(
htaskCallee,
dwTickCount,
dwPendingType );
} else {
// comportamento predefinito
return PENDINGMSG_WAITDEFPROCESS;
}
}
};
// Creare un'istanza di questa classe nell'ambito per abilitare IMessageFilter robusto.
class CHandleRetryLater :
public CHandleRetryLaterBase
{
public:
CHandleRetryLater() {
// imposta il nuovo gestore e salva quello precedente
_ASSERT( m_dwRef==0 );
HRESULT hr=CoRegisterMessageFilter( this, &m_imessagefilterOld );
// m_imessagefilterOld può essere nullptr se non è presente alcun
// filtro installato in precedenza
_ASSERT( SUCCEEDED(hr) );
}
~CHandleRetryLater() {
// ripristina il gestore precedente
{
CComPtr<IMessageFilter> iMessageFilter;
HRESULT hr=CoRegisterMessageFilter(
m_imessagefilterOld,
&iMessageFilter );
_ASSERT( SUCCEEDED(hr) );
// assicurarsi che nessuno abbia sostituito il nostro IMessageFilter
_ASSERT( iMessageFilter==static_cast<IMessageFilter*>(this) );
} // iMessageFilter è l'ultimo rilascio
_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);
}
};