Асинхронный режим чтения из Com-порта
Оформил: DeeCo
Автор: Терехов Александр
ВступлениеПорядок
запуска и работы "службы" (назовем все описываемое ниже так) Com-портов состоит
из нескольких достаточно хорошо описанных шагов ( см. статьи по
теме ):
- Инициализация Com-порта посредством вызова функции CreateFile.
- Установка параметров Com-порта посредством последовательного вызова
функций GetCommState и SetCommState, а также SetupComm.
- Установка параметров тайм-аутов для чтения и записи - GetCommTimeouts и
SetCommTimeouts.
- Собственно записи в Com-порт - WriteFile и чтения из него - ReadFile.
- Закрытие порта по окончанию работ CloseHandle.
Очень большой
сложности описанные выше шаги не представляют, однако реализация чтения данных
из порта в асинхронном (неблокирующем) режиме заставляет почесать затылок. Об
этом и поговорим.
Чтение из
Com-порта.
Судя по контексту справки, касающейся функции CreateFile, для
"отлова" момента поступления данных в Com-порт следует использовать функцию
WaitCommEvent. Предварительно установив маску SetCommMask на то событие, которое
хотелось бы отследить. Нужное событие наступает - вызываем функцию ReadFile для
чтения поступающих данных.
Казалось бы все в порядке, но... Вызов функции WaitCommEvent
насмерть тормозит приложение, пока какие-либо данные не поступят в Com-порт.
Можно конечно, просто взять и запустить в непрерывном цикле
ReadFile, однако приложение хотя и будет как-то шевелиться, но это шевеление
скорее всего будет напоминать предсмертные судороги.
Как выход из ситуации многие предлагают использовать потоки
(thread), забывая при этом описать как это делать :) Итак потоки.
В модуле Classes для потоков определен специальный класс
TThread. Для создания потоков специалисты рекомендуют использовать именно его, а
не создавать потоки используя BeginThread и EndThread, т.к. библиотека VCL не
является защищенной для потоков в такой реализации. Следуя советам экспертов,
для организации контроля поступающих данных в Com-порт и будем использовать
готовый класс TThread.
В раздел interface определим тип переменных
этого класса, переопределив только один метод класса - Execute, ну и
дополнительно объявим свой метод, который и займется опросом Com-порта.
Далее в разделе
глобальных переменных определим поток-переменную полученного выше типа CommThread:TCommThread; //наш поток, в котором будет работать процедура опроса порта Затем
в разделе implementation начинаем ваять.
ВНИМАНИЕ!!! К этому времени порт уже
должен быть инициализирован функцией CreateFile.
- 1. Инициализируем поток, используя метод Create.
type
//определим тип TComThread - наследника класса TThread
TCommThread = class(TThread)
private
//процедура, занимающаяся опросом порта
procedure QueryPort;
protected
//переопределим метод запуска потока
procedure Execute; override;
end;
procedure StartComThread;
//инициализация нашего потока
begin {StartComThread}
//пытаемся инициализировать поток
CommThread := TCommThread.Create(False);
//проверяем получилось или нет
if CommThread = nil then
begin {Nil}
//ошибка, все выключаем и выходим
SysErrorMessage(GetLastError);
fmMain.btnStop.Click;
Exit;
end; {Nil}
end; {StartComThread}
Куски кода взяты
из файла проекта, поэтому нажимание на кнопку btnStop главной формы fmMain -
это "примочки" примера, не обращайте внимания.
- Запускаем процедуру опроса порта в нашем потоке.
procedure TCommThread.Execute;
begin {Execute}
repeat
QueryPort; //процедура опроса порта будет производиться пока поток не будет прекращен
until Terminated;
end; {Execute}
- Реализуем асинхронные опрос порта и чтение из него данных
procedure TCommThread.QueryPort;
var
MyBuff: array[0..1023] of Char; //буфер для чтения данных
ByteReaded: Integer; //количество считанных байт
Str: string; //вспомогательная строка
Status: DWord; //статус устройства (модема)
begin {QueryPort}
//получим статус COM-порта устройства (модема)
if not GetCommModemStatus(hPort, Status) then
begin {ошибка при получении статуса модема}
//ошибка, все выключаем и выходим
SysErrorMessage(GetLastError);
fmMain.btnStop.Click;
Exit;
end; {ошибка при получении статуса модема}
//Обработаем статус устройства (модема) и будем включать(выключать) лампочки
//готовность устройства (модема) получать данные
fmMain.imgCTSOn.Visible := ((Status and MS_CTS_ON) = MS_CTS_ON);
//готовность устройства (модема) к сеансу связи
fmMain.imgDSROn.Visible := ((Status and MS_DSR_ON) = MS_DSR_ON);
//принимаются данные с линии сигнала
fmMain.imgRLSDOn.Visible := ((Status and MS_RLSD_ON) = MS_RLSD_ON);
//входящий звонок
fmMain.imgRingOn.Visible := ((Status and MS_RING_ON) = MS_RING_ON);
//читаем буфер из Com-порта
FillChar(MyBuff, SizeOf(MyBuff), #0);
if not ReadFile(hPort, MyBuff, SizeOf(MyBuff), ByteReaded, nil) then
begin {ошибка при чтении данных}
//ошибка, все закрываем и уходим
SysErrorMessage(GetLastError);
fmMain.btnStop.Click;
Exit;
end; {ошибка при чтении данных}
//данные пришли
if ByteReaded > 0 then
begin {ByteReaded>0}
//посчитаем общее количество прочитанных байтов
ReciveBytes := ReciveBytes + ByteReaded;
//преобразуем массив в строку
Str := string(MyBuff);
//отправим строку на просмотр
fmMain.Memo1.Text := fmMain.Memo1.Text + Str;
//покажем количество считанных байтов
fmMain.lbRecv.Caption := 'recv: ' + IntToStr(ReciveBytes) + ' bytes...';
end; {ByteReaded>0}
end; {QueryPort}
На этом по поводу использования потоков для считывания данных
из Com-порта, пожалуй, все.
Прилагающийся
пример
Следуя правилам хорошего тона, прикладываю ко всему написанному
работающий пример. В примере используется самое доступное устройство для
пользователей интернет - модем (на Com-порту). В качестве "примочек" я
использовал лампочки, которые включаются (или выключаются) при изменении статуса
модема. Можно было прикрутить лампочки-детекторы входящих-выходящих сигналов, но
вместо них используются счетчики байтов. Реализация кода
включения-выключения не самая лучшая: можно было бы использовать TImageList для
хранения изображений лампочек. Но почему-то ??? (кто знает почему - напишите)
использование ImageList.GetBitmap при наличии запущенного потока "подвешивает"
приложение насмерть. Причем это происходит под Windows'98, если тоже самое
делать под Windows'95, то все в порядке. Для проверки
работоспособности примера попробуйте понабирать AT-команды
- ATZ - инициализировать модем
- ATH - положить трубку
- ATH1 - поднять трубку
- ATS0=1 - включить автоподнятие трубки на первый сигнал
- ATS0=0 - выключить автоподнятие трубки
- ATDP_номер_телефона_интернет_провайдера - мне нравится больше
всего :)
- ATDP - набор в импульсном режиме, ATDT - набор в тоновом
режиме
Да, еще. Проект написан под Delphi3,
при использовании Delphi более свежих версий возможны ошибки
"несовпадения типов". В этим случае поменяйте типы
"ошибочных" переменных с Integer на
Cardinal.
Скачать проект — ComPort.zip
(17K)
|