قاعدة المعارف KB0122

عند تثبيت think-cell، تفشل بعض عمليات استدعاء التنفيذ التلقائي لوظيفة Excel COM.

المشكلة

عند تثبيت think-cell، تفشل بعض عمليات استدعاء التنفيذ التلقائي لوظيفة Excel COM. رمز خطأ HRESULT هو 0x8001010A RPC_E_SERVERCALL_RETRYLATER. في LotusScript، الخطأ هو Automation object error.

تستطيع خوادم COM تصفية طلبات الاستدعاء الواردة باستخدام الآلية IMessageFilter. بصورة أكثر تحديدًا، إذا أدت عملية التنفيذ IMessageFilter::HandleInComingCall إلى إرجاع SERVERCALL_RETRYLATER، يعود طلب الاستدعاء الخاص بالعميل بالنتيجة RPC_E_SERVERCALL_RETRYLATER.

يستخدم Excel هذه الآلية بصرف النظر عن think-cell. يؤدي وجود think-cell إلى زيادة معدل تكرار RPC_E_SERVERCALL_RETRYLATER، مما يؤدي إلى انتشار المشكلة بصورة أكبر.

يعد محرك البرمجة النصية LotusScript لبرنامج IBM Lotus Notes، أحد المنتجات المعروفة بإظهار أخطاء في هذه الحالات.

الحل

استجابةً لـ RPC_E_SERVERCALL_RETRYLATER، يجب أن ينتظر صاحب طلب الاستدعاء لفترة وجيزة ثم يقوم بتكرار طلب الاستدعاء. يحتوي Visual Basic for Applications على هذه المعالجة مضمّنة، لذلك يمكن القول إنه خطأ إذا كانت بيئات البرمجة النصية الأخرى الخاصة بنظام Windows والتي تدعم COM، مثل LotusScript، لا تقوم بتنفيذه.

خارج بيئات البرمجة النصية، أو إذا لم تقم بيئة البرمجة النصية بتنفيذه، يجب على أصحاب طلبات الاستدعاء معالجة RPC_E_SERVERCALL_RETRYLATER بأنفسهم. تتيح COM هذا الأمر بسهولة إلى حد ما نظرًا لأنها تستطيع تكرار طلبات الاستدعاء تلقائيًا بالنيابة عن العميل في حالة مطالبتها بذلك. قد تظل التعليمات البرمجية الفعلية لطلبات الاستدعاء كما هي دون تغيير.

لتمكين هذا السلوك، يجب أن يقوم العميل بتنفيذ IMessageFilter الخاص به، على أن يقوم الأسلوب IMessageFilter::RetryRejectedCall بإرجاع شيء آخر بخلاف -1 إذا كانت المعلمة dwRejectType هي SERVERCALL_RETRYLATER. بعد ذلك، في كل مؤشر ترابط STA يقوم بتنفيذ طلبات استدعاء COM، يجب أن يستخدم العميل CoRegisterMessageFilter لتسجيل مثيل لهذا التنفيذ باستخدام COM.

على سبيل المثال، فيما يلي تنفيذ 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);
  }
};

مشاركة