KB0122: Mit thinkcell schlagen einige Excel COM Automation-Aufrufe fehl
- Startseite
- Ressourcen
- Knowledge Base
- KB0122
Problem
Wenn thinkcell installiert ist, schlagen einige Excel COM Automation-Aufrufe fehl. Der HRESULT-Fehlercode lautet 0x8001010A RPC_E_SERVERCALL_RETRYLATER. In LotusScript lautet der Fehler Automation object error.
COM-Server haben die Möglichkeit, eingehende Aufrufe mithilfe des Mechanismus IMessageFilter zu filtern. Genauer gesagt: Wenn die Implementierung von IMessageFilter::HandleInComingCall SERVERCALL_RETRYLATER zurückgibt, kehrt der Clientaufruf mit RPC_E_SERVERCALL_RETRYLATER zurück.
Excel verwendet diesen Mechanismus unabhängig von thinkcell. Das Vorhandensein von thinkcell erhöht die Häufigkeit von RPC_E_SERVERCALL_RETRYLATER, wodurch das Problem häufiger auftritt.
Ein Produkt, von dem bekannt ist, dass es unter diesen Umständen Fehler anzeigt, ist die Skript-Engine von IBM Lotus Notes, LotusScript.
Lösung
Als Reaktion auf RPC_E_SERVERCALL_RETRYLATER muss der Aufrufer kurz warten und den Aufruf dann wiederholen. Visual Basic for Applications verfügt über diese Behandlung bereits, daher handelt es sich wohl um einen Fehler, wenn andere Windows-Skriptumgebungen, die COM unterstützen, wie etwa LotusScript, dies nicht tun.
Außerhalb von Skriptumgebungen oder wenn die Skriptumgebung dies nicht übernimmt, müssen Aufrufer RPC_E_SERVERCALL_RETRYLATER selbst behandeln. COM macht dies recht einfach, da es Aufrufe im Namen des Clients automatisch wiederholen kann, wenn es entsprechend angewiesen wird. Der eigentliche Aufrufcode kann unverändert bleiben.
Um dieses Verhalten zu aktivieren, muss der Client einen eigenen IMessageFilter implementieren, bei dem die Methode IMessageFilter::RetryRejectedCall etwas anderes als -1 zurückgibt, wenn der Parameter dwRejectType den Wert SERVERCALL_RETRYLATER hat. Anschließend muss in jedem STA-Thread, der COM-Aufrufe ausführt, mit CoRegisterMessageFilter eine Instanz dieser Implementierung bei COM registriert werden.
Hier ist als Beispiel eine C++-ATL-RAII-Implementierung: Mehr lesen
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-Implementierung
DWORD STDMETHODCALLTYPE HandleInComingCall(
/* [in] */ DWORD dwCallType,
/* [in] */ HTASK htaskCaller,
/* [in] */ DWORD dwTickCount,
/* [in] */ LPINTERFACEINFO lpInterfaceInfo
) {
if( m_imessagefilterOld ) {
// an alten Handler weiterleiten, keine Änderung des Verhaltens
return m_imessagefilterOld->HandleInComingCall(
dwCallType,
htaskCaller,
dwTickCount,
lpInterfaceInfo
);
} else {
// Standardverhalten
return SERVERCALL_ISHANDLED;
}
}
DWORD STDMETHODCALLTYPE RetryRejectedCall(
/* [in] */ HTASK htaskCallee,
/* [in] */ DWORD dwTickCount,
/* [in] */ DWORD dwRejectType
) {
if( SERVERCALL_RETRYLATER==dwRejectType ) {
// in einem Skript aus Gründen der Robustheit unter Last unbegrenzt warten
return 100;
} else if( m_imessagefilterOld ) {
// an alten Handler weiterleiten, keine Änderung des Verhaltens
return m_imessagefilterOld->RetryRejectedCall(
htaskCallee,
dwTickCount,
dwRejectType );
} else {
// Standardverhalten
return (DWORD)-1;
}
}
DWORD STDMETHODCALLTYPE MessagePending(
/* [in] */ HTASK htaskCallee,
/* [in] */ DWORD dwTickCount,
/* [in] */ DWORD dwPendingType
) {
if( m_imessagefilterOld ) {
// an alten Handler weiterleiten, keine Änderung des Verhaltens
return m_imessagefilterOld->MessagePending(
htaskCallee,
dwTickCount,
dwPendingType );
} else {
// Standardverhalten
return PENDINGMSG_WAITDEFPROCESS;
}
}
};
// Instanziieren Sie diese Klasse in Ihrem Gültigkeitsbereich, um einen robusten IMessageFilter zu aktivieren.
class CHandleRetryLater :
public CHandleRetryLaterBase
{
public:
CHandleRetryLater() {
// neuen Handler festlegen und alten speichern
_ASSERT( m_dwRef==0 );
HRESULT hr=CoRegisterMessageFilter( this, &m_imessagefilterOld );
// m_imessagefilterOld kann nullptr sein, wenn zuvor kein
// Filter installiert war
_ASSERT( SUCCEEDED(hr) );
}
~CHandleRetryLater() {
// alten Handler zurücksetzen
{
CComPtr<IMessageFilter> iMessageFilter;
HRESULT hr=CoRegisterMessageFilter(
m_imessagefilterOld,
&iMessageFilter );
_ASSERT( SUCCEEDED(hr) );
// sicherstellen, dass niemand unseren IMessageFilter ersetzt hat
_ASSERT( iMessageFilter==static_cast<IMessageFilter*>(this) );
} // iMessageFilter ist die letzte Freigabe
_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);
}
};