Статья
Версия для печати
Обсудить на форуме
Менеджер com-порта. (продолжение)


Автор: Алексей1153

Начало

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

DCBlength   Задает длину, в байтах, структуры DCB. Используется для контроля корректности структуры при передаче ее адреса в функции настройки порта.
BaudRate   Скорость передачи данных. Возможно указание следующих констант: CBR_110, CBR_300, CBR_600, CBR_1200, CBR_2400, CBR_4800, CBR_9600, CBR_14400, CBR_19200, CBR_38400, CBR_56000, CBR_57600, CBR_115200, CBR_128000, CBR_256000. Как видно, эти константы соответствуют всем стандартным скоростям обмена. На самом деле, это поле содержит числовое значение скорости передачи, а константы просто являются символическими именами. Поэтому можно указывать, например, и CBR_9600, и просто 9600. Однако рекомендуется указывать символические константы.
fBinary   Включает двоичный режим обмена. Win32 не поддерживает недвоичный режим, поэтому данное поле всегда должно быть равно 1, или логической константе TRUE (что предпочтительней). В Windows 3.1, если это поле было равно FALSE, включался текстовый режим обмена. В этом режиме поступивший на вход порта символ заданный полем EofChar свидетельствовал о конце принимаемых данных.
fParity      Включает режим контроля четности. Если это поле равно TRUE, то выполняется проверка четности, при ошибке, в вызывающую программу, выдается соответствующий код завершения.
fOutxCtsFlow      Включает режим слежения за сигналом CTS. Если это поле равно TRUE и сигнал CTS сброшен, передача данных приостанавливается до установки сигнала CTS. Это позволяет подключенному к компьютеру прибору приостановить поток передаваемой в него информации, если он не успевает ее обрабатывать.
fOutxDsrFlow   Включает режим слежения за сигналом DSR. Если это поле равно TRUE и сигнал DSR сброшен, передача данных прекращается до установки сигнала DSR.
fDtrControl   Задает режим управления обменом для сигнала DTR. Это поле может принимать следующие значения: DTR_CONTROL_DISABLE   Запрещает использование линии DTR
DTR_CONTROL_ENABLE   Разрешает использование линии DTR
DTR_CONTROL_HANDSHAKE   Разрешает использование рукопожатия для выхода из ошибочных ситуаций. Этот режим используется, в частности, модемами при восстановлении в ситуации потери связи.

fDsrSensitivity   Задает чувствительность коммуникационного драйвера к состоянию линии DSR. Если это поле равно TRUE, то все принимаемые данные игнорируются драйвером (коммуникационный драйвер расположен в операционной системе), за исключением тех, которые принимаются при установленном сигнале DSR.
fTXContinueOnXoff      Задает, прекращается ли передача при переполнении приемного буфера и передаче драйвером символа XoffChar. Если это поле равно TRUE, то передача продолжается, несмотря на то, что приемный буфер содержит более XoffLim символов и близок к переполнению, а драйвер передал символ XoffChar для приостановления потока принимаемых данных. Если поле равно FALSE, то передача не будет продолжена до тех пор, пока в приемном буфере не останется меньше XonLim символов и драйвер не передаст символ XonChar для возобновления потока принимаемых данных.
...Таким образом это поле вводит некую зависимость между управлением входным и выходным потоками информации.
fOutX      Задает использование XON/XOFF управления потоком при передаче. Если это поле равно TRUE, то передача останавливается при приеме символа XoffChar, и возобновляется при приеме символа XonChar.
fInX   Задает использование XON/XOFF управления потоком при приеме. Если это поле равно TRUE, то драйвер передает символ XoffChar, когда в приемном буфере находится более XoffLim, и XonChar, когда в приемном буфере остается менее XonLim символов.
fErrorChar   Указывает на необходимость замены символов с ошибкой четности на символ задаваемый полем ErrorChar. Если это поле равно TRUE, и поле fParity равно TRUE, то выполняется замена.
fNull   Определяет действие выполняемое при приеме нулевого байта. Если это поле TRUE, то нулевые байты отбрасываются при передаче.
fRtsControl   задает режим управления потоком для сигнала RTS. Если это поле равно NULL, то по умолчанию подразумевается RTS_CONTROL_HANDSHAKE. Поле может принимать одно из следующих значений:
RTS_CONTROL_DISABLE   Запрещает использование линии RTS
RTS_CONTROL_ENABLE   Разрешает использование линии RTS
RTS_CONTROL_HANDSHAKE   Разрешает использование RTS рукопожатия. Драйвер устанавливает сигнал RTS когда приемный буфер заполнен менее, чем на половину, и сбрасывает, когда буфер заполняется более чем на три четверти.
RTS_CONTROL_TOGGLE   Задает, что сигнал RTS установлен, когда есть данные для передачи. Когда все символы из передающего буфера переданы, сигнал сбрасывается.

fAbortOnError   Задает игнорирование всех операций чтения/записи при возникновении ошибки. Если это поле равно TRUE, драйвер прекращает все операции чтения/записи для порта при возникновении ошибки. Продолжать работать с портом можно будет только после устранения причины ошибки и вызова функции ClearCommError.
fDummy2   Зарезервировано и не используется.
wReserved   Не используется, должно быть установлено в 0.
XonLim   Задает минимальное число символов в приемном буфере перед посылкой символа XON.
XoffLim   Определяет максимальное количество байт в приемном буфере перед посылкой символа XOFF. Максимально допустимое количество байт в буфере вычисляется вычитанием данного значения из размера приемного буфера в байтах.
ByteSize   Определяет число информационных бит в передаваемых и принимаемых байтах.
Parity      Определяет выбор схемы контроля четности. Данное поле должно содержать одно из следующих значений:
EVENPARITY   Дополнение до четности
MARKPARITY   Бит четности всегда 1
NOPARITY      Бит четности отсутствует
ODDPARITY      Дополнение до нечетности
SPACEPARITY   Бит четности всегда 0

StopBits   Задает количество стоповых бит. Поле может принимать следующие значения:
ONESTOPBIT   Один стоповый бит
ONE5STOPBIT   Полтора стоповых бита
TWOSTOPBIT   Два стоповых бита

XonChar   Задает символ XON используемый как для приема, так и для передачи.
XoffChar   Задает символ XOFF используемый как для приема, так и для передачи.
ErrorChar   Задает символ, использующийся для замены символов с ошибочной четностью.
EofChar   Задает символ, использующийся для сигнализации о конце данных.
EvtChar   Задает символ, использующийся для сигнализации о событии.
wReserved1   Зарезервировано и не используется.

Так как поля структуры DCB используются для конфигурирования микросхем портов, на них накладываются некоторые ограничения. Размер байта должен быть 5, 6, 7 или 8 бит. Комбинация из пяти битного байта и двух стоповых бит является недопустимой. Так же как и комбинация из шести, семи или восьми битного байта и полутора стоповых бит.
Структура DCB - самая большая из всех, использующихся для настройки последовательных портов. Но она и самая важная. Заполнение всех полей этой структуры вручную может вызвать затруднения, так как надо очень четко представлять как работает последовательный порт. Поэтому для установки полей воспользуемся функцией GetCommState. Эта функция заполняет DCB информацией о текущем состоянии устройства, точнее о его настройках. А потом мы поменяем только те настройки, которые нужно. Вот как выглядит прототип функции:

Код:
BOOL GetCommState(HANDLE hFile, LPDCB lpDCB);

Функция очень проста и имеет всего два параметра:
hFile   Описатель открытого файла коммуникационного порта. Этот описатель возвращается функцией CreateFile. Следовательно, прежде чем получить параметры порта, Вы должны его открыть. Для функции BuildCommDCB это не требовалось.
lpDCB      Указатель на DCB. Для DCB должен быть выделен блок памяти.

При успешном завершении функция возвращает TRUE. При ошибке - FALSE. Получить параметры порта можно в любой момент, а не только при начальной настройке. Заполнив DCB можно приступать к собственно конфигурированию порта. Это делается с помощью функции SetCommState:

BOOL SetCommState(HANDLE hFile, LPDCB lpDCB);

Эта функция имеет точно такие же параметры, как GetCommState. Различается только направление передачи информации. Следует быть осторожным при вызове функции SetCommState, поскольку она изменит параметры даже в том случае, если очереди приема/передачи не пусты, что может вызвать искажение потока передаваемых или принимаемых данных. Еще одна тонкость этой функции заключается в том, что она завершится с ошибкой, если поля XonChar и XoffChar в DCB содержат одинаковые значения. Как всегда, в случае успешного завершения возвращается TRUE, а в случае ошибки - FALSE.

Следующей важной управляющей структурой является COMMTIMEOUTS (тайм-ауты com-порта). Она определяет параметры временных задержек при приеме и передаче. Значения, задаваемые полями этой структуры, оказывают большое влияние на работу функций чтения/записи.

Код:
typedef struct _COMMTIMEOUTS
{
DWORD ReadIntervalTimeout;
DWORD ReadTotalTimeoutMultiplier;
DWORD ReadTotalTimeoutConstant;
DWORD WriteTotalTimeoutMultiplier;
DWORD WriteTotalTimeoutConstant;
} COMMTIMEOUTS, *LPCOMMTIMEOUTS;

Поля структуры COMMTIMEOUTS имеют следующие значения:

ReadIntervalTimeout   Максимальное время, в миллисекундах, допустимое между двумя последовательными символами считываемыми с коммуникационной линии. Во время операции чтения временной период начинает отсчитываться с момента приема первого символа. Если интервал между двумя последовательными символами превысит заданное значение, операция чтения завершается и все данные, накопленные в буфере, передаются в программу. Нулевое значение данного поля означает, что данный тайм-аут не используется.
 Значение MAXDWORD, вместе с нулевыми значениями полей ReadTotalTimeoutConstant и ReadTotalTimeoutMultiplier, означает немедленный возврат из операции чтения с передачей уже принятого символа, даже если ни одного символа не было получено из линии.

ReadTotalTimeoutMultiplier   Задает множитель, в миллисекундах, используемый для вычисления общего тайм-аута операции чтения. Для каждой операции чтения данное значение умножается на количество запрошеных для чтения символов.

ReadTotalTimeoutConstant   Задает константу, в миллисекундах, используемую для вычисления общего тайм-аута операции чтения. Для каждой операции чтения данное значение прибавляется к результату умножения ReadTotalTimeoutMultiplier на количество запрошенных для чтения символов. Нулевое значение полей ReadTotalTimeoutMultiplier и ReadTotalTimeoutConstant означает, что общий тайм-аут для операции чтения не используется.

WriteTotalTimeoutMultiplier   Задает множитель, в миллисекундах, используемый для вычисления общего тайм-аута операции записи. Для каждой операции записи данное значение умножается на количество записываемых символов.

WriteTotalTimeoutConstant   Задает константу, в миллисекундах, используемую для вычисления общего тайм-аута операции записи. Для каждой операции записи данное значение прибавляется к результату умножения WriteTotalTimeoutMultiplier на количество записываемых символов. Нулевое значение полей WriteTotalTimeoutMultiplier и WriteTotalTimeoutConstant означает, что общий тайм-аут для операции записи не используется.

По тайм-аутам обычно возникает много вопросов. Поэтому немного подробнее. Пусть мы считываем 50 символов из порта со скоростью 9600. При этом используется 8 бит на символ, дополнение до четности и один стоповый бит. Таким образом на один символ в физической линии приходится 11 бит (включая стартовый бит). 50 символов на скорости 9600 будут приниматься 50 * 11 / 9600 = 0.0572916 секунд, или примерно 57.3 миллисекунды, при условии нулевого интервала между приемом последовательных символов.
 Если интервал между символами составляет примерно половину времени передачи одного символа, т.е. 0.5 миллисекунд, то время приема будет 50 * 11 / 9600 + 49 * 0.0005 = 0.0817916 секунд, или примерно 82 миллисекунды. Если в процессе чтения прошло более 82 миллисекунд, то мы вправе предположить, что произошла ошибка в работе внешнего устройства и прекратить считывание избежав тем самым зависания программы. Это и есть общий тайм-аут операции чтения. Аналогично существует и общий тайм-аут операции записи.
 Если тайм-аут при чтении понятен, то тайм-аут при записи вызывает недоумение. В самом деле, что нам мешает передавать? Управление потоком! Внешнее устройство может использовать, например, аппаратное управление потоком. При этом пропадание питания во внешнем устройстве заставит компьютер приостановить передачу данных. Если не контролировать тайм-аут возможно точно такое же зависание компьютера, как и при операции чтения.
 Общий тайм-аут зависит от количества участвующих в операции чтения/записи символов и среднего времени передачи одного символа с учетом межсимвольного интервала. Если символов много, например 1000, то на общем времени выполнения операции начинают сказываться колебания времени затрачиваемого на один символ или времени межсимвольного интервала. Поэтому тайм-ауты в структуре COMMTIMEOUTS задаются двумя величинами. Таким образом формула для вычисления общего тайм-аута операции,
 например чтения, выглядит так NumOfChar * ReadTotalTimeoutMultiplier + ReadTotalTimeoutConstant, где NumOfChar это число символов запрошенных для о
перации чтения. Для операции чтения, кроме общего тайм-аута на всю операцию, задается так же тайм-аут на интервал между двумя последовательными символами. Точнее это интервал между началами двух последовательных символов. В это значение входит и время передачи самого символа. Теперь небольшой пример. ReadTotalTimeoutMultiplier = 2, ReadTotalTimeoutConstant = 1, ReadIntervalTimeout = 1, считывается 250 символов. Если операция чтения завершится за 250 * 2 + 1 = 501 миллисекунду, то будет считано все сообщение.
 Если операция чтения не завершится за 501 миллисекунду, то она все равно будет завершена. При этом будут возвращены символы, прием которых завершился до истечения тайм-аута операции. Остальные символы могут быть получены следующей операцией чтения. Если между началами двух последовательных символов пройдет более 1 миллисекунды, то операция чтения так же будет завершена.
Как и для заполнения структуры DCB, для COMMTIMEOUTS существует функция считывания установленных в системе значений. Это функция GetCommTimeouts:

Код:
BOOL GetCommTimeouts(HANDLE hFile, LPCOMMTIMEOUTS lpCommTimeouts);

Параметров этой функции, не нуждаются в описании - они достаточно очевидны, как и возвращаемые функцией значения. Под структуру, адресуемую lpCommTimeouts должна быть выделена память.
Заполнив структуру COMMTIMEOUTS можно вызывать функцию установки тайм-аутов порта. Это функция называется SetCommTimeouts:

Код:
BOOL SetCommTimeouts(HANDLE hFile, LPCOMMTIMEOUTS lpCommTimeouts);

Установку тайм-аутов можно производить как до установки параметров порта, так и после. Последовательность вызова функций SetCommState и SetCommTimeouts не имеет никакого значения. Главное, что бы все настройки были завершены до начала ввода/вывода информации.

Не всегда настройку порта можно жестко зашить в код программы. Внешние устройства могут позволять изменять параметры линии связи, чаще всего скорость обмена, которая зависит от длины соединительного кабеля. В таких случаях разумно предоставить пользователю самому задавать режимы обмена. Можно самому разработать соответствующий настроечный диалог (что и сделано в классе CComPortManager) , а можно воспользоваться стандартным, предоставляемым операционной системой, а точнее, производителем порта.
 Стандартный диалог выводится функцией CommConfigDialog, которая работает со структурой COMMCONFIG. Здесь не станем останавливаться на их описании.
Теперь познакомимся с функцией SetupComm, которая, на самом деле, совсем не то, что следует из названия.

Код:
BOOL SetupComm(HANDLE hFile, DWORD dwInQueue, DWORD dwOutQueue);

Эту функцию скорее следовало назвать SetCommQueueSize, поскольку все, что она делает, это устанавливает размеры (в байтах) очередей приема и передачи. Причем размеры рекомендуемые. В общем случае, система сама в состоянии определить требуемый размер очередей, однако Вы можете вмешаться в этот процесс. Внутренние очереди драйвера позволяют избежать потери данных, если Ваша программа не успевает их считывать, и пауз в работе программы, если она передает данные слишком быстро.
 Размер очереди выбирается немного большим максимальной длины сообщения примерно на 20%. Указанный Вами размер очереди будет принят драйвером к сведению. Но он оставляет за собой право внести коррективы или вообще отвергнуть устанавливаемое значение. В последнем случае функция завершится с ошибкой. Внешние устройства управления объектами, чаще всего подключаемые к портам, обычно обмениваются с компьютером короткими сообщениями. Соответственно и вызов функции SetupComm не требуется.
 Однако, если Ваше устройство передает или принимает блоки данных длиной в несколько тысяч байт, рекомендуется установить размеры очередей драйвера.
Давайте сделаем паузу в изучении функций настройки и получения состояния коммуникационных портов и перейдём к приему и передаче данных. Рассмотрим синхронные чтение/запись, это проще. Прием и передача данных выполняется функциями ReadFile и WriteFile, то есть теми же самыми, которые используются для работы с дисковыми файлами. Вот как выглядят прототипы этих функций:

Код:
BOOL ReadFile(HANDLE hFile, LPVOID lpBuffer, DWORD nNumOfBytesToRead,
LPDWORD lpNumOfBytesRead, LPOVERLAPPED lpOverlapped);

Код:
BOOL WriteFile(HANDLE hFile, LPVOID lpBuffer, DWORD nNumOfBytesToWrite,
LPDWORD lpNumOfBytesWritten, LPOVERLAPPED lpOverlapped);

Вот значения их параметров:
hFile   Описатель открытого файла коммуникационного порта.
lpBuffer   Адрес буфера. Для операции записи данные из этого буфера будут передаваться в порт. Для операции чтения в этот буфер будут помещаться принятые из линии данные.
nNumOfBytesToRead, nNumOfBytesToWrite   Число ожидаемых к приему или предназначенных к передаче байт.
nNumOfBytesRead, nNumOfBytesWritten   Число фактически принятых или переданных байт. Если принято или передано меньше данных, чем запрошено, то для дискового файла это свидетельствует об ошибке, а для коммуникационного порта совсем не обязательно. Причина в тайм-аутах.
lpOverlapped   Адрес структуры OVERLAPPED, используемой для асинхронных операций. Подробнее как с структурой, так и с асинхронными операциями мы познакомимся позже. Для синхронных операций данный параметр должен быть равным NULL.

Еще раз коснусь темы тайм-аутов. Если Вы не используете ни общий, ни межбайтный тайм-ауты для операции чтения и внешнее устройство прекратило передачу, то Ваша программа будет вечно ждать завершения синхронной операции. Другими словами она зависнет. Если же тайм-ауты используются, то операция чтения нормально завершится. Только количество считанных байт будет меньше количества запрошенных для чтения. Это не обязательно свидетельствует об ошибке. Например программа может по тайм-ауту определять конец очередного блока данных.
 Аналогично и для операции записи, с той лишь разницей, что неполная передача данных из буфера, скорее всего, будет свидетельствовать о проблеме во внешнем устройстве. То есть будет считаться ошибкой. Коммуникационный порт не совсем обычный файл. Например, для него нельзя выполнить операцию позиционирования файлового указателя. С другой стороны, порт позволяет управлять потоком, что нельзя делать с обычным файлом.
Познакомимся с функциями управления приемом/передачей данных через коммуникационные порты. Поскольку первой операцией, после открытия порта, является его сброс, то и начнем с функции выполняющей требуемые действия.

Код:
BOOL PurgeComm(HANDLE hFile, DWORD dwFlags);

Вызов этой функции позволяет решить две задачи: очистить очереди приема/передачи в драйвере и завершить все находящиеся в ожидании запросы ввода/вывода. Какие именно действия выполнять задается вторым параметром (значения можно комбинировать с помощью побитовой операции | (or):
PURGE_TXABORT   Немедленно прекращает все операции записи, даже если они не завершены
PURGE_RXABORT   Немедленно прекращает все операции чтения, даже если они не завершены
PURGE_TXCLEAR   Очищает очередь передачи в драйвере
PURGE_RXCLEAR   Очищает очередь приема в драйвере

Вызов этой функции нужен для отбрасывания мусора, который может находиться в приемном буфере на момент запуска программы, или как результат ошибки в работе устройства. Очистка буфера передачи и завершение операций ввода/вывода так же потребуются при ошибке, как процедура восстановления, и при завершении программы, для красивого выхода. Следует помнить, что очистка буфера передачи, как и экстренное завершение операции записи, не выполняют передачу данных находящихся в этом буфере. Данные просто отбрасываются.
 Если же передача остатка данных необходима, то перед вызовом PurgeComm следует вызвать функцию (также не станем на ней останавливаться) :

Код:
BOOL FlushFileBuffers(HANDLE hFile);

Ниже очень кратко рассмотрены ещё несколько функций ( для полного описания обратитесь к источнику материала).

Иногда требуется срочно передать символ, имеющий определенное специальное значение, а в очереди передатчика уже есть данные, которые нельзя терять. В этом случае можно воспользоваться функцией:
Код:
BOOL TransmitCommChar(HANDLE hFile, char cChar);

Данная функция передает один (и только один) внеочередной байт в линию, не смотря на наличие данных в очереди передатчика, и перед этими данными. Однако управление потоком действует. Функцию можно вызвать только синхронно. Более того, если байт экстренных данных, от предыдущего вызова этой функции, еще не передан в линию (например из-за функций управления потоком), то попытка экстренной передачи еще одного байта завершится ошибкой.
 Если Вы используете программное управление потоком, то символы приостановки и возобновления передачи (обычно CTRL-S и CTRL-Q), лучше всего передавать именно этой функцией. Последовательный канал передачи данных можно перевести в специальное состояние, называемое разрывом связи. При этом передача данных прекращается, а выходная линия переводится в состояние "0". Приемник, обнаружив, что за время необходимое для передачи стартового бита, битов данных, бита четности и стоповых битов,
 приемная линия ни разу не перешла в состояние "1", так же фиксирует у себя состояние разрыва.
Код:
BOOL SetCommBreak(HANDLE hFile);
BOOL ClearCommBreak(HANDLE hFile);

Следует заметить, что состояние разрыва линии устанавливается аппаратно. Поэтому нет другого способа возобновить прерванную, с помощью SetCommBreak, передачу данных, кроме вызова ClearCommBreak. Более тонкое управление потоком данным позволяет осуществить функция:
BOOL EscapeCommFunction(HANDLE hFile,DWORD  dwFunc);

Приостановить прием/передачу данных может и возникновение любой ошибки при установленном в TRUE поле fAbortOnError в структуре DCB использованной для настройки режимов работы коммуникационного порта. В этом случае, для восстановления нормальной работы порта, следует использовать функцию:
Код:
BOOL ClearCommError(HANDLE   hFile, LPDWORD   lpErrors,LPCOMSTAT lpStat);
Эта функция не только сбрасывает признак ошибки для соответствующего порта, но и возвращает более подробную информацию об ошибке.

***
Теперь немного об асинхронной работой с портами
(но в классе CComPortManager используется только синхронный).
Начнем с событий связанных с последовательными портами. Вы указываете системе осуществлять слежение за возникновением связанных с портом событий устанавливая маску с помощью функции

Код:
BOOL SetCommMask(HANDLE hFile, DWORD dwEvtMask);

Маска отслеживаемых событий задается вторым параметром. Можно указывать любую комбинацию следующих значений: EV_BREAK   Состояние разрыва приемной линии
EV_CTS   Изменение состояния линии CTS
EV_DSR   Изменение состояния линии DSR
EV_ERR   Ошибка обрамления, перебега или четности
EV_RING   Входящий звонок на модем (сигнал на линии RI порта)
EV_RLSD   Изменение состояния линии RLSD (DCD)
EV_RXCHAR   Символ принят и помещен в приемный буфер
EV_RXFLAG   Принят символ заданный полем EvtChar структуры DCB использованной для настройки режимов работы порта
EV_TXEMPTY   Из буфера передачи передан последний символ
Если dwEvtMask равно нулю, то отслеживание событий запрещается. Разумеется всегда можно получить текущую маску отслеживаемых событий с помощью функции

Код:
BOOL GetCommMask(HANDLE hFile, LPDWORD lpEvtMask);

Вторым параметром задается адрес переменной принимающей значение текущей установленной маски отслеживаемых событий. В дополнение к событиям, перечисленным в описании функции SetCommMask, данная функция может возвратить следующие:
EV_EVENT1   Устройство-зависимое событие
EV_EVENT2   Устройство-зависимое событие
EV_PERR   Ошибка принтера
EV_RX80FULL   Приемный буфер заполнен на 80 процентов

Эти дополнительные события используются внутри драйвера. Вы не должны переустанавливать состояние их отслеживания. Когда маска отслеживаемых событий задана, Вы можете приостановить выполнение своей программы до наступления события. При этом программа не будет занимать процессор. Это выполняется вызовом функции

Код:
BOOL WaitCommEvent(HANDLE hFile, LPDWORD lpEvtMask, LPOVERLAPPED lpOverlapped);

В переменной, адресуемой вторым параметром, не будут устанавливаться внутренние события драйвера (перечислены в описании функции GetCommMask). В единичное состояние установятся только те биты, которые соответствуют реально произошедшим событиям. Адрес структуры OVERLAPPED требуется для асинхронного ожидания (возможно и такое). Однако пока будем полагать, что порт открыт для синхронных операций, следовательно этот параметр должен быть NULL. Замечу только, что при асинхронном ожидании данная функция может завершиться с ошибкой,
 если в процессе этого ожидания будет вызвана функция SetCommMask для переустановки маски событий. Кроме того, связанное со структурой OVERLAPPED событие (объект создаваемый функцией CreateEvent, а не событие порта) должно быть с ручным сбросом. Вообще, поведение функции с ненулевым указателем на структуру OVERLAPPED аналогично поведению функций чтения и записи.

Освобождать процессор на время ожидания хорошо, но хотелось бы параллельно с вводом/выводом делать какую-либо полезную работу. Что бы это стало возможным, необходимо в качестве параметра dwFlagsAndAttributes функции CreateFile() вместо 0 указать FILE_FLAG_OVERLAPPED. Кроме того, для функций ReadFile, WriteFile и WaitCommEvent необходимо в качестве параметра lpOverlapped указывать адрес правильно инициализированной структуры OVERLAPPED. Вот как выглядит эта структура:

Код:
typedef struct _OVERLAPPED
{
DWORD  Internal;
DWORD  InternalHigh;
DWORD  Offset;
DWORD  OffsetHigh;
HANDLE hEvent;
} OVERLAPPED, *LPOVERLAPPED;

(Описания этой структуры нет в материале, откуда бралась информация, потому что (цитата):" поскольку данная статья не о файловом вводе/выводе вообще, а о работе с портами. Для наших целей, за исключением WaitCommEvent, можно просто обнулить все поля этой структуры.")
Для WaitCommEvent поле hEvent должно содержать корректный описатель объекта "событие". Что бы все стало понятно, надо разобраться с таким обязательным атрибутом параллельной работы как синхронизация. ВНИМАНИЕ!!! Дескриптор файла, в данном случае дескриптор файла порта, является синхронизирующим объектом ядра (согласно официальной документации Microsoft). Это означает, что его можно использовать в функциях ожидания событий наравне с дескрипторами событий.
Таким образом в поле hEvent в структуре OVERLAPPED можно занести NULL и ожидать освобождения дескриптора файла, а не дескриптора события. Это действительно работает в Windows NT. Однако в Windows95/98 все совсем иначе. Обсуждение ошибок, неточностей и прочих проблем документации оставим в стороне. Просто замечу, что в Windows95/98 поле hEvent должно содержать корректный дескриптор объекта event В ЛЮБОМ СЛУЧАЕ!!! Иначе функции асинхронного ввода/вывода будут работать более чем странным образом. Кроме того, мы должны ожидать освобождения именно дескриптора этого события, а не дескриптора файла.
Синхронизация нужна для упорядочения доступа к совместно используемым объектам. Предположим, что две программы одновременно пытаются изменить значение общей переменной. Каков будет результат? Скорее всего неопределенный. Что бы этого избежать требуется разрешать доступ второй программы к переменной только после того, как с ней закончила работать первая программа. Для синхронизации используются различные методы: семафоры, блокировки, события, критические секции и тому подобное.
События являются простейшими синхронизирующими объектами. Они могут находиться только в двух состояниях: установленном (событие произошло или наступило) и сброшенном (событие не произошло или не наступило). События создаются функцией CreateEvent и разрушаются функцией CloseHandle. Установить событие можно функцией SetEvent, а сбросить ResetEvent. Функции записи/чтения для файла открытого для асинхронного ввода/вывода будут немедленно возвращать управление с кодом ошибки ERROR_IO_PENDING. Это означает, что асинхронная операция успешно стартовала. Если возвращается другой код ошибки, то операция не стартовала (например из-за ошибки в параметрах).
Теперь Вы можете спокойно заниматься другой работой периодически проверяя, завершилась ли операция ввода/вывода. Эта проверка выполняется функцией

Код:
BOOL GetOverlappedResult(
HANDLE hFile,
LPOVERLAPPED lpOverlapped,
LPDWORD lpcbTransfer,
BOOL fWait
);

Параметр hFile определяет дескриптор опрашиваемого файла, lpOverlapped задает адрес структуры OVERLPPED. Третий параметр задает адрес переменной, куда будет помещено количество считанных или записанных байт. Соответствующий параметр функций ReadFile и WriteFile, хоть и ДОЛЖЕН БЫТЬ ЗАДАН НЕ НУЛЕВЫМ, не позволяет получить количество переданных байт, так как на момент возврата управления из функций чтения/записи не передано ни одного байта. Параметр fWait означает, должна ли функция GetOverlappedResult ждать завершения операции ввода/вывода. Если этот параметр равен FALSE,
 то функция немедленно вернет управление. При этом код возврата будет TRUE, если операция завершена, или FALSE, если операция не завершена. В послед случае код ошибки возвращаемой функцией GetLastError будет ERROR_IO_INCOMPLETE. Если функция GetOverlappedResult завершилась с кодом возврата FALSE, и другим кодом ошибки, то ошибка произошла именно при вызове самой функции. Если параметр fWait равен TRUE, то функция будет дожидаться завершения операции ввода-вывода. Замечу, что ожидать завершения ввода/вывода с помощью функции GetOverlappedResult не самое правильное решение.
 При работе с дисковым файлом операция завершится гарантированно, а при работе с последовательным или параллельным портом совсем не обязательно. Представьте, что Вы не настроили тайм-ауты последовательного порта, а подключенное устройство неисправно. GetOverlappedResult будет ждать вечно, так как нет способа указать максимальное время ожидания. Ждать завершения ввода/вывода лучше с помощью функций:

Код:
DWORD WaitForSingleObject(HANDLE hObject,DWORD  dwTimeot);

DWORD WaitForMultipleObjects(DWORD cObjects,LPHANDLE lpHandles,
BOOL bWaitAll, DWORD dwTimeout);

Как следует из названия, эти функции предназначены для ожидания одного или нескольких объектов. Однако следует вспомнить примечание, которое я привел к описанию структуры OVERLAPPED! Поэтому не мудрствуя лукаво будем ожидать только объекты event. Функция WaitForSingleObject ожидает только один объект задаваемый первым параметром. Вторым параметром задается максимальное время ожидания наступления события в миллисекундах. Если вместо времени указана магическая величина INFINITE, то событие будет ожидаться вечно. Функция WaitForMultipleObjects ждет несколько событий.
Первый параметр указывает сколько именно, а второй задает массив дескрипторов этих событий. Замечу, что один и тот же дескриптор нельзя указывать в этом массиве более одного раза. Третий параметр задает тип ожидания. Если он равен TRUE, то ожидается наступление всех событий. Если FALSE, то наступления любого одного из указанных. И естественно тоже можно задать максимальное время ожидания последним параметром. Если событие наступило, то функции возвращают значения от WAIT_OBJECT_0 до WAIT_OBJECT_0+cObject-1. Естественно, что WaitForSingleObject может вернуть только WAIT_OBJECT_0 (если конечно не произошло ошибки).
Если произошла ошибка, то будет возвращено WAIT_FAILED. При превышении максимального времени ожидания функции вернут WAIT_TIMEOUT. Вернусь к объектам event, которые мы собственно и используем для ожидания. Поясню, почему для наших целей требуются события с ручным сбросом. Функции ReadFile и WriteFile в асинхронном режиме первым делом сбрасывают (переводят в занятое состояние) как дескриптор файла, так и дескриптор объекта event заданный в структуре OVERLAPPED. Когда операция чтения или записи завершается система устанавливает эти дескрипторы в свободное состояние. Тут все логично.
Однако и функции WaitForSingleObject и WaitForMultipleObjects для событий с автоматическим сбросом так же выполняют их перевод в занятое состояние при вызове. Для событий с ручным сбросом этого не происходит. Теперь представьте, что операция ввода/вывода завершилась ДО вызова WaitForSingleObject. Для событий с автоматическим сбросом снова будет выполнен перевод объекта в занятое состояние. Но освобождать то его будет некому! (Более подробная информация об объектах event выходит за рамки этой статьи).


В описании работы с коммуникационным портом COM в программах для win32 использованы материалы статьи Олега Титова.



Обратите внимание на следующую удобную возможность среды VC++6.0.

(Она позволит быстро и приятно скопировать наш класс диалога из тестового проекта в любой другой).
Когда в проекте уже существует некий класс, CXx то, чтобы надо скопировать класс в другой проект, выполняем следующие действия.


1) Чаще всего весь код класса CXx расположен в двух файлах:
   Xx.h   -заголовочный файл
   Xx.cpp   -файл реализации.

в этом случае можно:
   а) просто скопировать данные файлы из папки первого проекта в папку второго, затем, открыв проект назначения, добавить файлы в дерево проекта
   б) или: найти класс в дереве классов первого проекта, нажать на класс правой кнопкой мыши и добавить в галерею классов. Затем открываем второй проект и выполняем: Project->Add To Project->Components And Controls ищем там папку с названием, как у проекта-источника, а в этой папке файл "Xx.ogx". Вставляем этот "компонент" в проект.

2) Случай, когда класс - производный от CDialog и имеет ресурс-диалог (наш случай :) ). Самый приятный вариант - выполнить пункт 1) б). Ресурс диалога скопируется автоматом, и при том корректно будут выбраны числовые значения для идентификаторов.

Если кроме всего прочего для работы класса требуются другие (дополнительные) файлы, подключаемые при помощи "#include" ,то эти файлы просто копируем из папки в папку, а затем включаем в дерево проекта.
Если в классе используются другие ресурсы (иконки, битмапы и прочее), копируем эти ресурсы перетаскиванием файлов, или через export-import (правой кнопкой мыши по ресурсу в дереве проекта)


Примечание. После копирования класса откомпилируйте проект и определите и исправьте все нестыковки (например скорее всего будет ругань на файл "имя_исходного_проекта.h" - переправить на название проекта назначения .  А в файлах, где будут не найдены идентификаторы ресурсов, возможно достаточно будет включить в начале файла строку #include "Resource.h"). Рекомендуется удалить из папки проекта назначения файл *.CLW, затем в проекте нажать Ctrl+W, ОК, ОК. Тем самым файл дерева классов будет создан заново.

______________________________
с Уважением, Алексей Журавлёв
Версия для печати
Обсудить на форуме