Входит и выходит...Работаем с COM-портом

Николай "GorluM" Андреев

Xakep, номер #053, стр. 053-086-1


(gorlum@real.xakep.ru)

Давным-давно, во времена старого доброго DOS`а, работать с хардваром было гораздо проще. Взять, например, управление коммуникационными портами. Программы тогда использовали прерывания и общались с устройствами напрямую (это было настоящее раздолье вирмейкерам). С тех пор многое переменилось. На смену ДОСу пришли винды, а с ними и новые правила. Теперь программы, работающие в Windows, общаются с устройством не напрямую, а через специальные WinAPI функции. Вот эти сами WinAPI функции мы сегодня и будем изучать. Разберем работу с COM-портом, а в качестве примера напишем простенькую терминальную программу для модема с простым и красивым графическим интерфейсом.

Работаем как с файлом

Сама система драйверов, да и виндов в целом, позволяет работать с коммуникационными портами (не только COM, но и LPT) как с обыкновенными файлами. То есть для этого будут использоваться те же функции, что и при работе с файлами. Вот как это реализуется. Для начала взаимодействия с портом необходимо получить его хэндл при помощи функции CreateFile. Причем параметры точно такие же, как и при работе с файлами, только в поле имени файла мы ставим имя порта - "COM1", "COM2" и т.п.:

HANDLE hCom = CreateFile("COM1",GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,0,0);

Для других приложений драйвер порта получит статус "занятый", и в переменной hCom окажется HANDLE порта. Теперь мы можем работать с ним при помощи двух api-функций: ReadFile и WriteFile. Например, для того чтобы послать в порт строку buffer, необходимо сделать следующее:

WriteFile(hCom, buffer, strlen(buffer), &n, 0);

В первом параметре этой функции мы передаем хэндл порта, далее указатель на строку, объем посылаемых байт, затем указатель на переменную типа DWORD, в которую занесется количество посланных байт, и указатель на overlapped-буфер (он нам не нужен, поэтому просто передаем ноль).

А вот что необходимо сделать, чтобы прочитать из порта строку длиной в 6 символов:

ReadFile(hCom, &buffer, 6, &n, 0);

У этой функции есть один нюанс. При запуске она ждет появления заданного количества байт в порту. Если приходит меньше ожидаемого, то функция просто не вернет управления программе и будет дожидаться оставшихся байт. Чтобы убрать такие "зависания" из программы, мы можем сделать следующее: в цикле читать по одному байту из порта и тотчас же его выводить. Но в то же время у нас не получится одновременно писать в порт и считывать. А выход из сложившийся ситуации прост! Нам надо создать поток, то есть функцию, работающую параллельно с основной программой, в который мы и поместим наш цикл чтения. Для этого мы определим функцию и добавим в нее цикл:

DWORD __stdcall ReadThread(LPVOID param){

// ... тут цикл чтения

}

А уже из мейновой функции запустим при помощи WinAPI наш поток:

HANDLE hThread = CreateThread(0, 0, ReadThread, (LPVOID)0, 0, 0);

И вроде бы все замечательно: в одном потоке у нас идет чтение и вывод, в другом (главном) - мы вводим данные и посылаем их в порт. Но тут кроется еще одна проблема. Нам нельзя одновременно читать и посылать данные! То есть в то время, пока из порта ничего не пришло, и ReadFile спокойно ждет своего байтика, мы не можем послать данные. Как же быть? Просто прежде чем отправить уже введенные данные, мы килланем поток. Убьем, пошлем, а затем снова создадим. И так каждый раз, когда захотим послать что-либо в порт. Убивать поток мы будем при помощи функции TerminateThread, в первом параметре которой передадим указатель на поток (hThread), а во втором - код завершения (0). Выглядит все это так:

Содержание  Вперед на стр. 053-086-2