在Windows 10上,Cancel对象永远不会返回RPC_E_CALL_COMPLETE

时间:2017-03-27 20:01:17

标签: winapi mfc com

在我们已有15年以上的应用程序中,我们有一种进度条。  这个进度条用于持久的C ++操作,并且还有一种情况,当我们进行COM调用时,COM调用返回需要很长时间。  一般来说,我们希望用户知道某些事情需要很长时间才能完成,并且如果他认为这需要花费太多时间,他就有机会取消。

对于COM操作,多年前我们为COM调用实现了一个定制的IMessageFilter,耗时太长。我们希望用户能够取消,但是当操作完成时,取消的提示也会自行消失。它已经工作多年了。我们的大多数客户都很保守,并且仍在运行Windows 7.最近,使用Windows 10的客户发现了一个问题,即Windows 10上的COM调用似乎永远不会完成。

我们的进度条出现,进度控制循环和循环,但操作似乎永远不会完成。在调查之后,似乎ICancelMethodCalls :: TestCancel()方法总是返回RPC_S_CALLPENDING,它永远不会返回RPC_E_CALL_COMPLETE。在Windows 7,以前版本的Windows和Windows 8.1上,它可以正常工作,但不能在Windows 10上运行。

我在Visual Studio 2013中创建了一个演示此问题的最小测试解决方案。一个项目是ATL服务器,另一个项目是MFC客户端应用程序。这里是一个示例解决方案的zip文件的链接:https://www.dropbox.com/s/1dkchplsi7d6tda/MyTimeOutServer.zip?dl=0

基本上ATL服务器有一个设置延迟的属性,以及一个等待延迟长度的方法。目的是模拟花费太长时间的COM操作。

interface IMyTimoutServer : IDispatch{
   [propget, id(1), helpstring("Timeout value in milliseconds")] HRESULT TimeOut([out, retval] LONG* pVal);
   [propput, id(1), helpstring("Timeout value in milliseconds")] HRESULT TimeOut([in] LONG newVal);
   [id(2)] HRESULT DoTimeOut([out, retval] VARIANT_BOOL* Result);
};

下一个重要的事情是客户端应用程序中的IMessageFilter。在某些时候,有人认为从COleMessageFilter派生是好的,这是默认的MFC实现。 (我们不要争论这是否是一个好主意。)

该类的重要方法是MessagePending()和MyNotResponding()。

DWORD CMyMessageFilter::XMyMessageFilter::MessagePending(HTASK htaskCallee, DWORD dwTickCount, DWORD dwPendingType)
{
   METHOD_PROLOGUE_EX(CMyMessageFilter, MyMessageFilter);
   ASSERT_VALID(pThis);

   MSG msg;
   if (dwTickCount > pThis->m_nTimeout && !pThis->m_bUnblocking && pThis->IsSignificantMessage(&msg))
   {
      if (pThis->m_bEnableNotResponding)
      {
         pThis->m_bUnblocking = TRUE;    // avoid reentrant calls

         // eat all mouse and keyboard messages in our queue
         while (PeekMessage(&msg, NULL, WM_MOUSEFIRST, AFX_WM_MOUSELAST, PM_REMOVE | PM_NOYIELD));
         while (PeekMessage(&msg, NULL, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE | PM_NOYIELD));

         // show not responding dialog
         pThis->m_dwTickCount = dwTickCount;
         bool bCancel = pThis->MyNotResponding(htaskCallee) == RPC_E_CALL_CANCELED;

         pThis->m_bUnblocking = FALSE;

         return bCancel ? PENDINGMSG_CANCELCALL : PENDINGMSG_WAITNOPROCESS; // if canceled, the COM call will return RPC_E_CALL_CANCELED
      }
   }

   // don't process re-entrant messages
   if (pThis->m_bUnblocking)
      return PENDINGMSG_WAITDEFPROCESS;

   // allow application to process pending message
   if (::PeekMessage(&msg, NULL, NULL, NULL, PM_NOREMOVE | PM_NOYIELD))
      pThis->OnMessagePending(&msg); // IK: could also return a value from extended OnMessagePending() to cancel the call

   // by default we return pending MSG wait
   return PENDINGMSG_WAITNOPROCESS;
}

超时后,我们会显示一个状态类型窗口,该窗口在NotMyResponding()方法中更新。

int CMyMessageFilter::MyNotResponding(HTASK hTaskBusy)
{
   TRY
   {
      CComPtr<ICancelMethodCalls> pCancel;
      HRESULT hRes = ::CoGetCancelObject(0, IID_ICancelMethodCalls, (void**)&pCancel);
      ATLASSERT(SUCCEEDED(hRes)); // COM should automatically create Cancel objects for pending calls [both on client and server]

      if (pCancel == NULL)
      {
         return COleBusyDialog::retry;
      }

      m_pFrame->EnableWindow(FALSE);
      CMyCancelDlg dlg;
      dlg.Create(CMyCancelDlg::IDD);
      dlg.ShowWindow(SW_SHOWNORMAL);
      HWND hWndDlg = dlg.GetSafeHwnd();

      do
      {
         MSG msg;
         for (int i = 0; i < 100 && PeekMessage(&msg, 0, 0, 0, PM_REMOVE | PM_NOYIELD); ++i)
         {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
         }

         if (dlg.StepAndCheckCancel())
         {
            dlg.DestroyWindow();
            m_pFrame->EnableWindow(TRUE);
            return RPC_E_CALL_CANCELED; // signals to MessagePending() that the COM call should be cancelled
         }

         Sleep(250); // this call has been pending for many seconds now... sleep for some time to avoid CPU utilization by this loop and yield if needed

         hRes = pCancel->TestCancel();

      } while (hRes == RPC_S_CALLPENDING);

      ATLASSERT(hRes == RPC_E_CALL_COMPLETE);

      dlg.DestroyWindow();
      m_pFrame->EnableWindow(TRUE);

   }
   END_TRY

   return RPC_E_CALL_COMPLETE;
}

基本上,在MyNotResponding()中,我们创建一个Cancel对象,创建一个带有取消按钮的窗口,泵消息,并查找按下取消按钮或者TestCancel()是否返回RPC_S_CALLPENDING之外的东西。

好吧,在Windows 10上,它保持在循环中,并且始终从TestCancel()返回RPC_S_CALLPENDING。

有没有人在Windows 10上看到过这样的内容?我们做错了什么,我们真的只是在Windows 7上运气好吗?

MFC的默认实现会建立一个OLEUIBusy对话框,用于提取消息。它永远不会测试取消对象。

0 个答案:

没有答案