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, чтобы он не разделял поток с пользовательским интерфейсом и работал параллельно.