ВступлениеПорядок
запуска и работы "службы" (назовем все описываемое ниже так) 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 = nilthenbegin{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-порта устройства (модема)ifnot GetCommModemStatus(hPort, Status) thenbegin{ошибка при получении статуса модема}//ошибка, все выключаем и выходим
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);
ifnot ReadFile(hPort, MyBuff, SizeOf(MyBuff), ByteReaded, nil) thenbegin{ошибка при чтении данных}//ошибка, все закрываем и уходим
SysErrorMessage(GetLastError);
fmMain.btnStop.Click;
Exit;
end; {ошибка при чтении данных}//данные пришлиif ByteReaded > 0 thenbegin{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.
Асинхронное чтение из Com-порта: использование потоков для опроса порта и чтения данных.
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.