Base de connaissances 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 d’erreur HRESULT est 0x8001010A RPC_E_SERVERCALL_RETRYLATER. Dans LotusScript, l’erreur est Automation object error.

Les serveurs COM ont la capacité de filtrer les appels entrants à l’aide du mécanisme IMessageFilter. Plus précisément, si la mise en œuvre IMessageFilter::HandleInComingCall renvoie SERVERCALL_RETRYLATER, l’appel du client retourne avec 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 mettre en œuvre son propre IMessageFilter avec la méthode IMessageFilter::RetryRejectedCall qui renvoie une valeur autre que -1 si le paramètre dwRejectType est 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);
  }
};

Partager