Вступление
Порядок запуска и работы "службы" (назовем все описываемое ниже так)
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.
|