Knowledge base KB0122

Avec think-cell, certains appels d'automatisation Excel COM ne fonctionnent pas

Problème

Avec think-cell, certains appels d'automatisation Excel COM ne fonctionnent pas. Le code erreur HRESULT est 0x8001010A RPC_E_SERVERCALL_RETRYLATER. Dans LotusScript, l'erreur est Automation object error (Erreur d'objet d'automatisation).

Les serveurs COM ont la capacité de filtrer les appels entrants à l'aide du mécanisme IMessageFilter. Plus précisément, si l'implémentation IMessageFilter::HandleInComingCall renvoie SERVERCALL_RETRYLATER, l'appel du client renvoie RPC_E_SERVERCALL_RETRYLATER.

Excel utilise ce mécanisme indépendamment de think-cell. La présence de think-cell augmente la fréquence de RPC_E_SERVERCALL_RETRYLATER et, par là même, la fréquence du problème.

Le moteur de script LotusScript d'IBM Lotus Notes est réputé pour afficher les erreurs dans ces circonstances.

Solution

En réponse à RPC_E_SERVERCALL_RETRYLATER, le programme appelant doit attendre quelques instants, puis répéter l'appel. Visual Basic for Applications comporte cette fonctionnalité intégrée. Si le comportement est différent avec des environnements de script Windows prenant en charge COM, il s'agit incontestablement d'un bogue.

En dehors des environnements de script ou si l'environnement de script ne le permet pas, les appelants doivent gérer RPC_E_SERVERCALL_RETRYLATER eux-mêmes. COM permet d'effectuer cela assez aisément car il peut répéter les appels automatiquement pour le compte du client s'il reçoit l'ordre de le faire. Le code appelant physique peut demeurer inchangé.

Pour activer ce comportement, le client doit implémenter son propre IMessageFilter, avec la méthode IMessageFilter::RetryRejectedCall qui renvoie une valeur autre que -1 si le paramètre dwRejectType a pour valeur SERVERCALL_RETRYLATER. Puis, dans chaque unité d'exécution STA effectuant des appels COM, il doit utiliser CoRegisterMessageFilter pour enregistrer une instance de cette implémentation auprès de COM.

À titre d'exemple, voici une implémentation 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);
  }
};