Асинхронный режим чтения из Com-порта
Вступление |
Порядок запуска и работы "службы" (назовем все описываемое ниже так) 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-порта.
Type //определим тип TComThread - наследника класса TThread TCommThread = class(TThread) private //процедура, занимающаяся опросом порта Procedure QueryPort; protected //переопределим метод запуска потока Procedure Execute; override; end; |
Далее в разделе глобальных переменных определим поток-переменную полученного выше типа CommThread:TCommThread; //наш поток, в котором будет работать процедура опроса порта Затем в разделе implementation начинаем ваять.
ВНИМАНИЕ!!!
К этому времени порт уже должен быть инициализирован функцией CreateFile.
- 1. Инициализируем поток, используя метод Create.
Procedure StartComThread; //инициализация нашего потока Begin {StartComThread} //пытаемся инициализировать поток CommThread:=TCommThread.Create(False); // проверяем получилось или нет If CommThread = Nil Then Begin {Nil} //ошибка, все выключаем и выходим SysErrorMessage(GetLastError); fmMain.btnStop.Click; Exit; End; {Nil} End; {StartComThread} - Запускаем процедуру опроса порта в нашем потоке.
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.
Скачать проект — (17K)
архив обновлен
Другие небольшие статьи,
примеры и программы можете найти на
Смотрите также :