Перекрывающийся обратный вызов WSARecv() не вызывается в приложении MFC

У меня есть компонент COM, реализованный на C++ с ATL, который использует ввод-вывод с перекрывающимися сокетами. Сразу после установления соединения с сервером он начинает перекрывающееся чтение сокета с таким кодом:

// Pass pointer to this instance as hEvent parameter, for use by callback
m_recvOverlapped.hEvent = reinterpret_cast<HANDLE>(this);

int rc = ::WSARecv(m_s, &wsabuf, 1, &m_recvNumberOfBytes, &m_recvFlags, &m_recvOverlapped, RecvCallback);
if (rc == SOCKET_ERROR)
{
    // If error is WSA_IO_PENDING, then the I/O is still in progress.  Otherwise, something bad happened.
    int error = ::WSAGetLastError();
    if (error != WSA_IO_PENDING)
    {
        ReceiveError(error);
    }
}

И у меня есть функция обратного вызова, которая выглядит примерно так:

void CALLBACK CMySocket::RecvCallback(DWORD dwError, DWORD cbTransferred, LPWSAOVERLAPPED lpOverlapped, DWORD dwFlags)
{
CMySocket* socket = reinterpret_cast<CMySocket*>(lpOverlapped->hEvent);
ATLASSERT(socket != 0);
if (!socket)
    return;

socket->ReceiveCompleted(dwError, cbTransferred, lpOverlapped, dwFlags);
}

Этот COM-компонент отлично работает в модульных тестах, при использовании в приложении командной строки и при использовании в приложении .NET GUI (через COM-взаимодействие). Однако, когда я использую этот компонент в приложении MFC, RecvCallback никогда не вызывается, когда сервер отправляет ему данные.

WSARecv() возвращает SOCKET_ERROR, а WSAGetLastError() возвращает WSA_IO_PENDING, как и ожидалось для асинхронного чтения с перекрытием.

Когда я использую приложение SysInternals TcpView для наблюдения за происходящим, оно указывает, что клиент получает данные. Но обратный вызов никогда не вызывается.

Отправка данных на сервер через подключенный сокет работает нормально.

Я вызываю CoInitializeEx() и WSAStartup() в методе InitInstance() моего приложения MFC.

Любые идеи?


person Kristopher Johnson    schedule 25.10.2010    source источник


Ответы (2)


Да, это действительно так. APC обрабатываются только тогда, когда поток входит в "предупреждающее" состояние ожидания - вызывает функцию SleepEx, WaitForMultipleObjectsEx или MsgWaitForMultipleObjectsEx.

Один момент, который я хотел бы исправить, заключается в том, что использование члена OVERLAPPED hEvent в качестве заполнителя для ваших «пользовательских» данных — плохая идея. Потому что ОС попытается установить это «событие», когда ваш ввод-вывод завершится.

Обычный способ передать некоторую «пользовательскую» информацию в вашу маршрутизацию обратного вызова на самом деле использует пользовательскую структуру, которая заменяет OVERLAPPED, добавляя при необходимости больше членов (также известных как OVERLAPPED_PLUS). Затем ваша маршрутизация обратного вызова может привести OVERLAPPED к вашему OVERLAPPED_PLUS, там вы увидите всех участников.

Еще один момент: поскольку вы пишете COM-объект - у вас, вероятно, нет возможности написать свой собственный цикл сообщений, поэтому может быть сложно гарантировать вход в ожидающее оповещение.

person valdo    schedule 25.10.2010
comment
Документация по состояниям WSARecv Если параметр lpCompletionRoutine не равен NULL, параметр hEvent игнорируется и может использоваться приложением для передачи информации о контексте процедуре завершения. - person Kristopher Johnson; 25.10.2010

Хорошо, я нашел ответ, выполнив поиск других вопросов о переполнении стека, касающихся WSARecv.

Из ответа Лена Холгейта на перекрывающийся ввод-вывод Win32 - процедуры завершения или WaitForMultipleObjects?:

. . . вы можете передать процедуру завершения, которая вызывается, когда происходит завершение. Это известно как "предупреждающий ввод-вывод" и требует, чтобы поток, выпустивший вызов WSARecv(), находился в "предупреждающем" состоянии для вызова подпрограммы завершения. Потоки могут переводить себя в предупреждающее состояние несколькими способами (вызов SleepEx() или различных EX-версий функций ожидания и т. д.). . . .

Оказывается, если я запускаю таймер в приложении MFC, который периодически вызывает SleepEx(0, TRUE), то вызывается обратный вызов получения. Таким образом, проблема заключается в том, что основной поток MFC, который вызывает метод моего COM-объекта, который вызывает WSARecv(), никогда не переходит в состояние предупреждения сам по себе.

Итак, мне, вероятно, придется изменить реализацию моего COM-объекта, чтобы он использовал порты завершения ввода-вывода, а не обратные вызовы, или запускал свой собственный поток, который вызывает WSARecv() и сохраняет себя в состоянии готовности.

person Community    schedule 25.10.2010