скрыть

скрыть

  Форум  

Delphi FAQ - Часто задаваемые вопросы

| Базы данных | Графика и Игры | Интернет и Сети | Компоненты и Классы | Мультимедиа |
| ОС и Железо | Программа и Интерфейс | Рабочий стол | Синтаксис | Технологии | Файловая система |



Google  
 

Асинхронный режим чтения из Com-порта



Автор: Терехов Александр

Вступление

Порядок запуска и работы "службы" (назовем все описываемое ниже так) Com-портов состоит из нескольких достаточно хорошо описанных шагов:
  1. Инициализация Com-порта посредством вызова функции CreateFile.
  2. Установка параметров Com-порта посредством последовательного вызова функций GetCommState и SetCommState, а также SetupComm.
  3. Установка параметров тайм-аутов для чтения и записи - GetCommTimeouts и SetCommTimeouts.
  4. Собственно записи в Com-порт - WriteFile и чтения из него - ReadFile.
  5. Закрытие порта по окончанию работ 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. 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 - это "примочки" примера, не обращайте внимания.

  2. Запускаем процедуру опроса порта в нашем потоке.
    procedure TCommThread.Execute;
    begin {Execute}
      repeat
        QueryPort; //процедура опроса порта будет производиться пока поток не будет прекращен
      until Terminated;
    end; {Execute}
  3. Реализуем асинхронные опрос порта и чтение из него данных
    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)






Copyright © 2004-2016 "Delphi Sources". Delphi World FAQ




Группа ВКонтакте   Ссылка на Twitter   Группа на Facebook