WM_PAINT и незавершенные COM-вызовы (ATL OOP Server "Deadlock")

Мы разработали сервер STA ATL COM OOP, и все работает почти нормально. Мы сталкиваемся с проблемой: поскольку COM-клиент внутренне получает результат любого COM-вызова через сообщение Windows, сообщение WM_PAINT (или любое другое сообщение, которое я предполагаю) может быть обработано в ожидании ответа на COM-вызов. Само по себе это может быть проблемой, но большая проблема заключается в том, что до тех пор, пока предыдущий COM-вызов не завершит все вызовы на сервер, произойдет сбой (HRESULT=0x80010005), и это довольно большая проблема, так как это действительно часто происходит в нашем приложении. Мы не можем удалить вызовы COM из краски.

Я довольно много исследовал эту тему и не смог ничего найти по этому поводу (кроме этой статьи 2006 года, в которой говорится о проблеме и некоторых решениях, которые не могут быть применены в нашем случае), но, как я вижу, не только WM_PAINT будет опасно, что любой вызов на сервер, который мы выполняем внутри любого события Windows (сообщения), потенциально может вызвать ту же проблему.

Таким образом, в основном у нас могут быть разные решения, но нет понятия о том, какие методы COM использовать и как: 1- метод, который ожидает обработки последнего COM-вызова и возвращаемое значение; 2- метод, чтобы узнать, есть ли ожидающие ответа на вызов в сервер и метод для обработки COM-ответов (все они вызываются из клиента). 3. Знать, можем ли мы реализовать класс IMessageFilter в клиенте и как его обрабатывать, обрабатывать вызовы и так далее.

Спасибо!

2 ответа

Решение

Мы исправили проблему с реализацией класса IMessageFilter в клиентском проекте (C++):

MessageFilter.h:

#include <atlbase.h>
#include <atlcom.h>

namespace SDKCOMSrvInterface
{
 class MessageFilter : public CComObjectRootEx<CComSingleThreadModel>, public IMessageFilter
 {
  BEGIN_COM_MAP(MessageFilter)
   COM_INTERFACE_ENTRY(IMessageFilter)
  END_COM_MAP()

  // Implement IMessageFilter methods here
  STDMETHODIMP_(DWORD) HandleInComingCall(
  DWORD dwCallType,
  HTASK threadIDCaller,
  DWORD dwTickCount,
  LPINTERFACEINFO lpInterfaceInfo)
  {
   return E_NOTIMPL;
  }

  STDMETHODIMP_(DWORD) RetryRejectedCall(
   HTASK threadIDCallee,
   DWORD dwTimeOut,
   DWORD dwRejectType)
  {
   return SERVERCALL_RETRYLATER;
  }

  STDMETHODIMP_(DWORD) MessagePending(
   HTASK threadIDCallee,
   DWORD dwTickCount,
   DWORD dwPendingType)
  {
   return PENDINGMSG_WAITDEFPROCESS;
  }
 };
}

И регистрируем его сразу после CoInitialize(NULL):

CComObject<MessageFilter>* l_MessageFilter = NULL;
CComObject<MessageFilter>::CreateInstance(&l_MessageFilter);
CComPtr<IMessageFilter> l_OldMessageFilter;
hr = CoRegisterMessageFilter(l_MessageFilter, &l_OldMessageFilter);

Это гарантирует, что если мы попытаемся вызвать сервер с клиента, а сервер занят, то COM-вызов будет повторяться до тех пор, пока сервер не обработает предыдущий вызов, а это именно то, что необходимо для решения проблемы параллелизма.

Поскольку COM-клиент внутренне получает результат любого COM-вызова через сообщение Windows, сообщение WM_PAINT (или любое другое предполагаемое сообщение) может быть обработано в ожидании ответа COM-вызова.

Нет ничего связывающего COM-вызовы с WM_PAINT обработка. Эти две задачи существуют одновременно, но, поскольку вы выбрали STA в качестве модели, обе эти задачи выполняются в одном потоке и могут блокировать друг друга.

Вы мало что можете сделать с рисованием, поскольку в конечном итоге у вас будет обработчик сообщений для рисования. Однако вы можете изменить ваш COM-сервер и ваш обработчик рисования, чтобы не мешать COM.

Вам не нужно делать COM-звонки прямо с WM_PAINT, Создайте свой сервер так, чтобы он отправлял все обновления пользовательского интерфейса асинхронно, а обработчик рисования просто использовал самые последние доступные данные.

Также вы можете переместить свой сервер в другую квартиру, STA или MTA, чтобы он не разделял поток с пользовательским интерфейсом и работал параллельно.

Другие вопросы по тегам