| 
 Асинхронный режим чтения из 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-порта. Далее в разделе 
глобальных переменных определим поток-переменную полученного выше типа 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-порта, пожалуй, все. 
  
  
    Прилагающийся 
пример Следуя правилам хорошего тона, прикладываю ко всему написанному 
работающий пример. Для проверки 
работоспособности примера попробуйте понабирать AT-командыВ примере используется самое доступное устройство для 
пользователей интернет - модем (на Com-порту). В качестве "примочек" я 
использовал лампочки, которые включаются (или выключаются) при изменении статуса 
модема. Можно было прикрутить лампочки-детекторы входящих-выходящих сигналов, но 
вместо них используются счетчики байтов.
 Реализация кода 
включения-выключения не самая лучшая: можно было бы использовать TImageList для 
хранения изображений лампочек. Но почему-то ??? (кто знает почему - напишите) 
использование ImageList.GetBitmap при наличии запущенного потока "подвешивает" 
приложение насмерть. Причем это происходит под Windows'98, если тоже самое 
делать под Windows'95, то все в порядке.
 
  ATZ - инициализировать модем 
  ATH - положить трубку 
  ATH1 - поднять трубку 
  ATS0=1 - включить автоподнятие трубки на первый сигнал 
  ATS0=0 - выключить автоподнятие трубки 
  ATDP_номер_телефона_интернет_провайдера - мне нравится больше 
  всего :) 
  ATDP - набор в импульсном режиме, ATDT - набор в тоновом 
  режиме  
 Да, еще. Проект написан под Delphi3, 
при использовании Delphi более свежих версий возможны ошибки 
"несовпадения типов".
 В этим случае поменяйте типы 
"ошибочных" переменных с Integer на 
Cardinal.
 Скачать проект — ComPort.zip 
(17K) |