Конференция "WinAPI" » Реализация асинхронного(!) доступа к COM-порту [D7, WinXP]
 
  • pihter © (19.02.13 06:11) [0]
    Задача казалась довольно простой... Но, на деле, оказалась что решение задачи независимой передачи и приема на одном порту - не так проста.

    Проблема в том что функция ReadFile блокирует не только поток (ее ведь можно и вынести в отдельный) но и дескриптор открытого порта. И в это время из другого потока этого же процесса отправить данные через этот же СОМ-порт не получается (используется этот же дескриптор) пока не придет байт на вход для считывания.

    Проблема сохраняется даже при использовании Wait-функций, когда принимающий поток находится в режиме ожидания и, по идее, не должен блокировать дескриптор порта. То есть функция ReadFile вызывается только тогда, когда функция WaitCommEvent возвращает маску события и маска анализируется на предмет возникновения именно этого события (пришел байт). Но получается что функция WaitCommEvent тоже блокирует не только свой поток, но и дескриптор открытого порта (наверное), что не позволяет отправлять информацию по этому же порту из другого потока.

    Примеры кода пока постить не буду - я их тут уже сотню перепробовал, три дня бьюсь. Заранее спасибо за помощь.
  • sniknik © (19.02.13 08:05) [1]
    из того, что знаю (фискальники,барпринтеры,весы,кассы), работающего через COM-порты, проблема "множественного взаимодействия" разных программ к ним решается COM/DCOM/TCP сервером...
    т.е. производитель предоставляет "драйвер" работающий в общем стиле, и являющийся "сервером"/объектом к которому все клиенты шлют запросы на чтение/запись, работают через него, а уж он "в одно рыло", с организацией очереди, работает непосредственно с COM-портом.

    ну или предлагается "метод"/стиль работы - закрывать порт после операции, т.е. выполнил чек в кассе/1С-е обязательно закрой порты за собой, чтобы параллельно работающая программа переводов/платежей на телефоны тоже могла сделать чек... и тоже закрыть за собой.

    естественно, если кто то не "закрывает за собой", или использует метод "прямого доступа" вместе с COM/DCOM объектом (т.е. одна программа правильно другая нет) то возникают проблемы. очень популярная в ЦТО... если контора работает(/взято на поддержку) с программами разных разработчиков.
  • Вариант (19.02.13 09:44) [2]

    > pihter ©   (19.02.13 06:11)


    Ищем и Читаем  по
    CreateFile - FILE_FLAG_OVERLAPPEВ и как вытекающее во всех функциях  смотрим - а что там за параметр lpOverlapped и что такое OVERLAPPED структура.

    Литература для изучения - MSDN нужные тебе функции,
    Рихтер Дж., Кларк Д.Д. - Программирование серверных приложений
    (работа с потоками, работа с вводом/выводом и пр.),
    Последовательные интерфейсы ПК. Практика программирования (П.Агуров)
  • Вариант (19.02.13 09:56) [3]

    > Вариант   (19.02.13 09:44) [2]

    FILE_FLAG_OVERLAPPEВ=FILE_FLAG_OVERLAPPED
  • Германн © (19.02.13 12:20) [4]

    > Примеры кода пока постить не буду

    А зачем тогда вопрос задавать было?
  • pihter © (26.02.13 06:57) [5]

    > ну или предлагается "метод"/стиль работы - закрывать порт
    > после операции, т.е. выполнил чек в кассе/1С-е обязательно
    > закрой порты за собой, чтобы параллельно работающая программа
    > переводов/платежей на телефоны тоже могла сделать чек...
    >  и тоже закрыть за собой.


    У меня задача критичная ко времени. Нужна возможность постоянно отправлять и принимать данные, пока будешь открывать/закрывать после каждой буквы - данные потеряются.

    Я вобщем-то уже разобрался, решил. Но решение мне не понравилось жутко непредскажуемостью работы. Решил через открытие порта на чтение/запись в основном потоке + создание двух потокув на чтение и запись + использование wait-функций в потоке чтения, чтоб не вызывало блокировку дескриптора порта, пока функция чтения ждет входящий байт, и функция записи в соседнем потоке в это время могла записывать. Плюс обращаю внимание, если кто столкнется с такой же проблемой, на правильную работу с OVERLAPPED-структурой. При открытии порта обязательно она должна упоминаться в параметрах CreateFile там в справке написано как, плюс, при любом вызове любой API-функции, связанной с этими портами, нужно создать OVERLAPPED-структуру, забить нулями, создать событие, потом только передавать ее в API-функцию.

    В итоге у меня все заработало, но нестабильно (непонятно что с приоритетами на чтение запись в порт, хотя с приоритетами соответствующих потоков все понятно, плюс иногда куда-то теряется собитие прихода байта в порт, и, в результате, событие возникает только при приходе следующего байта, а считывается предыдущий, думаю это из-за игры приоритетов процессов). Вобщем - кошмар и на ктитически-важной боевой задаче применять такое никак нельзя.

    Решено было использовать 2 COM-порта, по отдельному процессу-серверу на каждый (чтение/запись) и общение с главным приложением через сообщения windows. 2 СОМ-порта будут каждый подключаться к COM-USB-переходнику, которые будут подключаться к USB-хабу и от него один USB-кабель в машину. Немного костыльно, но очень надежно.
  • pihter © (26.02.13 07:05) [6]

    > > Примеры кода пока постить не буду
    >
    > А зачем тогда вопрос задавать было?


    Мне нужно не чтоб все показали где у меня ошибка (еще раз - примеров перепробовал кучу, каждый постить?) а чтоб мне саму концепцию правильную описали, навроде той, что я выше написал.


    > А зачем тогда вопрос задавать было?


    А вот придет такой же парень, с такой же бедой, а тут уже написано решение - он раз, как прочитает, да как поймет в какую сторону копать, да каааак разберется сам и опыт приобретет и все такое прочее полезное. Если он точно будет знать что таким путем реализовать можно, я ведь реализовал, ему будет уверенее.
  • sniknik © (26.02.13 08:08) [7]
    > пока будешь открывать/закрывать после каждой буквы - данные потеряются.
    значит тебе бы больше подошёл "общий сервер", тем более, по позже описанному, у тебя все в одной программе. чего стоит выделить 1 поток на работу с COM? остальные обращаются с запросами на запись, а сам поток "обработчик" "генерит наружу" события от чтения (если что прочиталось)...

    OVERLAPPED это конечно хорошо, круто, но сам COM устройство последовательное.

    > подключаться к COM-USB-переходнику, которые будут подключаться к USB-хабу и от него один USB-кабель в машину.
    ээээ... нафига тогда COM? раз, как отсюда понял, работа с USB, он то как раз может работать "параллельно". ну, должен. технология новее на десятки лет.
  • Вариант (26.02.13 11:52) [8]

    > pihter ©   (26.02.13 06:57) [5]


    > Я вобщем-то уже разобрался, решил

    Судя по тому, что пишешь дальше - не разобрался до конца. Если ты открыл порт с флагом FILE_FLAG_OVERLAPPED и правильно пользуешься последующими вызовами функций WriteFile, ReadFile,WaitCommEvent, то никакой блокировки на вызовах этих функций у тебя не будет. Все работу с портом в этом случае можно организовать в одном доп. потоке или даже в основном потоке. Единственные блокирующие в этом случае поток вызовы - это вызовы ожидания событий WaitForXXX или MsgWaitForXXX и т.п.  И то блокируют только до возникновения ожидаемого события(ий), ошибки или истечения тайм-аута... Считать с порта ты действительно можешь и более одного байта, рассчитывать на получение только одного байта можно только при небольших скоростях порта и небольшим временем обработки полученного и то не факт, что твой поток получит немедленно управление, когда придет ожидаемое событие.  Для нормальной работы с портом обычно хватает нормального приоритета процесса и потока.  Концепцию работы с последовательными устройствами и работу с функциями ввода вывода можешь найти в той литературе, которую я давал выше в
    > Вариант   (19.02.13 09:44) [2]
  • Германн © (27.02.13 01:07) [9]

    > Концепцию работы с последовательными устройствами и работу
    > с функциями ввода вывода можешь найти в той литературе,
    > которую я давал выше

    До сих пор нигде ни разу не встречал в литературе подробное описание структуры OVERLAPPED. Что это такое, для чего она нужна, как она используется "внутре" Windows и какие особенности её применения в пользовательской программе. Насколько помню у Агурова по этому поводу вроде тоже почти ноль информации.
  • Вариант (27.02.13 07:53) [10]

    > Германн ©   (27.02.13 01:07) [9]

    Рихтер Дж., Кларк Д.Д. - Программирование серверных приложений, есть глава с названием "Cтруктура OVERLAPPED"
  • Вариант (27.02.13 08:08) [11]

    > Германн ©   (27.02.13 01:07) [9]

    Да и в MSDN даже с предупреждением о наиболее часто встречающейся ошибке дано описание. А примеры кода, варианты различных методик организации ввода вывода у Рихтера описаны очень хорошо, читается "легко", интересно. Легко - это не значит, что сразу все понятно (это кому как), это значит что не тянет спать.
  • Германн © (28.02.13 02:05) [12]

    > Вариант   (27.02.13 07:53) [10]
    >
    >
    > > Германн ©   (27.02.13 01:07) [9]
    >
    > Рихтер Дж., Кларк Д.Д. - Программирование серверных приложений,
    >  есть глава с названием "Cтруктура OVERLAPPED"

    Спасибо, почитаю.
  • NoUser (14.03.13 00:31) [13]
    > OVERLAPPED это конечно хорошо, круто, но сам COM устройство последовательное.
    :)
  • pihter © (22.04.13 09:01) [14]

    > значит тебе бы больше подошёл "общий сервер"

    Да, вне всякого сомнения. Я так и реализовал.


    > тем более, по позже описанному, у тебя все в одной программе

    Да. Но хотелось бы сотворить логическую прослойку по принципу: "прикладное приложение прислало моему серверу байт - он его отправил в СОМ-порт, в СОМ-порт пришел байт - сервер отправил его прикладному приложению" все организовать на технологии сообщений Windows.


    > чего стоит выделить 1 поток на работу с COM?

    Да мне не жалко даже процесс :) даже два процесса, как в итоге оказалось :)


    > ээээ... нафига тогда COM?

    Значит, я смотрю, чтоб вам было проще помочь, пора уже раскрыть мою задачу. Задача такая: своя открытая реализация телеграфного аппарата. Данные по нашей линии передаются со скоростью 50 бод. Это достаточно медленно даже для того чтоб у меня реле успевали переключать все. Данные передаются путем перемены полярности напряжения в линии с двумя стоп-битами и в пятибитной кодировке. Мне один толковый дядька подсказал что он, когда был молодым, делал эмуляцию работы телеграфа через СОМ-порт (правда там был DOS - прямая работа с контроллером) оказалось что СОМ-порт настроить на такую работу очень просто - это один из штатных режимов его работы. Таким образом моя задача свелась к реализации гальвонической развязки линии и компьютера (это я справился) и написанию вот такого сервера взаимодействия с СОМ-портом (это я тоже справился, но плохо, нужна помощь). Нужно еще написать программу-кодировшик и вообще с терминалом и возможностью хранить библиотеку телеграмм/поиска/печати на принтере и т. п. Но это все я осилю без труда, ибо уже сто раз много подобной фигни делал и имею неплохой опыт в этом. Короче - не беда. Я и с API-функциями не в первый раз встречаюсь, майкросовтовскую справку уже вдоль и поперек излазил, говорю же, сначала казалось вообще - раз плюнуть.

    сейчас решил разделить задачу на еще более мелкие - сделать сервера приема и передачи отдельно и независимо (даже работают с разными портами - бог с ним, терпимо) и сервер передачи мне удалось сделать без проблем - все работает, никаких wait-функций не потребовалось, функцию очереди выполняют сообщения windows.

    то же самое реализовал и на сервере приема, бог уже с ним, пусть плокирует хоть весь процесс, но, когда принял байт - отправляй сообщение главной программе. и все тоже работает, но через несколько байт (когда три, когда - десять) он просто перестает принимать данные на порт! примерами кода сейчас поделюсь
  • pihter © (22.04.13 09:14) [15]
    инициализация

    var
     com_port: string;
     DCB   : TDCB;
     ctm1: TCommTimeouts;



    hPort := CreateFile(PChar(com_port),
                         GENERIC_READ,
                         0, nil,
                         OPEN_EXISTING, 0, 0);
     if hPort = INVALID_HANDLE_VALUE then
     begin
       // Обнаружена ошибка, порт открыть не удалось
       WriteToRcvSrvLog(
         'Ошибка: порт '+com_port+' открыть для чтения не удалось', 2);
       critical_error := true;
     end;

     // 3. Чтение текущих настроек порта
     if not GetCommState(hPort, DCB) then
     begin
       // Обнаружена ошибка, настройки получить не удалось
       WriteToRcvSrvLog(
         'Ошибка: настройки порта '+com_port+' получить не удалось', 2);
       critical_error := true;
     end;
     // 4. Настройки:
     // Скорость обмена

     DCB.BaudRate := 50;//[скорость обмена];
     // Число бит на символ
     DCB.ByteSize := 5;//[размер "байта" при обмене по COM порту - обычно 8 бит];
     // Стоп-биты
     DCB.StopBits := 1;//[константа, определяющая кол-во стопбитов];
     // Четность
     DCB.Parity   := NoParity;//[константа, определяющая режим контроля четности];
    // DCB.Flags := 20625;
     // 5. Передача настроек
     if not SetCommState(hPort, DCB) then
     begin
       // Обнаружена ошибка, настройки установить не удалось
       WriteToRcvSrvLog(
         'Ошибка: настройки порта '+com_port+' установить не удалось', 2);
       critical_error := true;
     end;

     // 6. Настройка буферов порта (очередей ввода и вывода)
     if not SetupComm(hPort, 256, 16) then
     begin
       // Обнаружена ошибка, настройки установить не удалось
       WriteToRcvSrvLog(
         'Ошибка: настройки буферов порта '+com_port+' установить не удалось', 2);
       critical_error := true;
     end;

     // 7. Сброс буфферов и очередей
     if PurgeComm(hPort, PURGE_TXABORT or PURGE_RXABORT or PURGE_TXCLEAR
       or PURGE_RXCLEAR) then
     begin
       // порт открыт
       WriteToRcvSrvLog(
         'Порт '+com_port+' успешно открыт для чтения', 1);

       GetCommTimeouts(hport, ctm1);
       ctm1.ReadIntervalTimeout := 500;
       SetCommTimeouts(hport, ctm1);

       //сообщим главному процессу что сервер готов
       PostMessage(StrToInt(ParamStr(1)),WMAT_RECIV_SERVER_REDY,Handle,0);

       server_redy:= true;
     end
     else begin
       // Обнаружена ошибка, порт открыть не удалось
       WriteToRcvSrvLog(
         'Ошибка: порт '+com_port+' открыть не удалось', 2);
       critical_error := true;
     end;



    чтение

    begin
       // когда совсем нефиг делать сидим и ждем данных от СОМ-порта
      FillChar(Buf, 1, 0);
      FillChar(Ovr, SizeOf(ovr), 0);
      //ovr.hEvent := CreateEvent(nil, True, False, nil);
      i := 0;
      res:=false;
     // while not res do
      if not Windows.ReadFile(hport, Buf, 1, i, @ovr) then
       begin
         // ошибка
         WriteToRcvSrvLog('Ошибка чтения байта из СОМ-порта.', 1);
       end
       else begin
         // удачное чтение байта - отправляем сообщение
         WriteToRcvSrvLog('Удачное чтение байта '+IntToStr(Buf)+' из СОМ-порта.', 0);

         PostMessage(StrToInt(ParamStr(1)),WMAT_RECIV_BYTE, Buf, 0);
       end;
     end;

  • pihter © (22.04.13 09:20) [16]

    > Если ты открыл порт с флагом FILE_FLAG_OVERLAPPED и правильно
    > пользуешься последующими вызовами функций WriteFile, ReadFile,
    > WaitCommEvent, то никакой блокировки на вызовах этих функций
    > у тебя не будет


    Значит где-то неправильно делал. На код выше в этом смысле не смотрите - тут уже попытался выпилить и структуры и вэит-функции, максимально упростить и организовать прием не взирая на блокировку. И даже это удалось не полностью :(


    > рассчитывать на получение только одного байта можно только
    > при небольших скоростях порта и небольшим временем обработки
    > полученного и то не факт, что твой поток получит немедленно
    > управление, когда придет ожидаемое событие


    байты приходят очень медленно - штуки 2 в секунду. обработка, как видите, только послать сообщение. все вполне успевается.


    > можешь найти в той литературе, которую я давал выше

    вот за литературу - огромное спасибо, очень ценные книги по теме
  • MBo © (22.04.13 10:08) [17]
    Нет желания использовать готовые библиотеки типа CPort (Dejan Crnila)?
    Если хочется сделать руками, то можно там по крайней мере посмотреть работу с WaitCommEvent c  overlapped
  • pihter © (22.04.13 12:10) [18]
    Значится... докладываю вдогонку.. Нашел книгу, которую мне тут любезно присоветовали. Там все подробно описано, насчет программирования СОМ-портов. И насчет вэит-функций. И вообще - все что нужно. Дак вот, есть там, среди прочего, готовый пример. Где уже все написано. Все, как положено. Подключил я свое устройство, изменил в сорсах частоту на 50, он и отправляет и принимает через один порт - все вообще шикарно, кроме одного - событие прихода байта все равно куда-то теряется. То есть сами байты - все приходят, но байт отправленный ранее, может прочитаться только в момент прихода следующего и так далее - по цепочке. а для моей задачи критично чтоб байт немедленно читался. какие-нибудь идеи? может прямой доступ? кто пробовал?
  • pihter © (22.04.13 12:12) [19]

    > Нет желания использовать готовые библиотеки типа CPort (Dejan
    > Crnila)?

    только за, если бы те поддерживали мою нестандартуню частоту и вообще всякие настройки стопбитности и четности. если есть возможность - я руками и ногами за! исходники открыты? можно подправить, если что?


    > Если хочется сделать руками, то можно там по крайней мере
    > посмотреть работу с WaitCommEvent c  overlapped

    ох как я их уже насмотрелся, даже снились однажды - клянусь )
 
Конференция "WinAPI" » Реализация асинхронного(!) доступа к COM-порту [D7, WinXP]
Есть новые Нет новых   [134428   +40][b:0][p:0.003]