CloseHandle() се връща преди серийният порт действително да бъде затворен

Скубя си косата, опитвайки се да разбера кога един сериен порт приключи да се затваря, за да мога да го отворя отново. Оказва се, че CloseHandle() се връща преди портът действително да бъде отключен.

Отварям сериен порт с помощта на CreateFile(FILE_FLAG_OVERLAPPED), свързвам го с CompletionPort с помощта на CreateIoCompletionPort(), чета/пиша в него с помощта на ReadFile(), WriteFile() и го затварям с помощта на CloseHandle().

Забелязах, че ако затворя и отворя отново сериен порт достатъчно бързо, получавам ERROR_ACCESS_DENIED обратно от CreateFile(). Това се случва въпреки факта, че чакам CloseHandle() да се върне, след което чакам всички неизпълнени операции за четене/запис, свързани с този манипулатор, да се върнат от порта за завършване. Със сигурност има по-добър начин :)

Как да затворя синхронно сериен порт? Моля, без повторни цикли, sleep() или други евтини хакове.

РЕДАКТИРАНЕ: Може би това има нещо общо с използването на портове за завършване и FILE_FLAG_OVERLAPPED. Получавам обратно извикване, когато операциите за четене/запис завършат. Има ли някакъв вид обратно извикване за затваряне на порта?


person Gili    schedule 16.01.2012    source източник
comment
Може би свързано с вашия специфичен драйвер за сериен порт. Виждали ли сте това тук на SO: stackoverflow.com/questions/2948428/   -  person Simon Mourier    schedule 17.01.2012
comment
Само за да сте сигурни.... не сте използвали DuplicateHandle никъде?   -  person Ben Voigt    schedule 17.01.2012
comment
Не съм сигурен дали портът за завършване е свързан, винаги съм използвал APC обратни извиквания с ReadFileEx и WriteFileEx. Това също е по-просто, защото APC се изпълняват само тогава, когато нишката навлезе в изчакване, което може да бъде предупредено, така че няма проблеми със синхронизирането между нишките (само внимавайте за повторно влизане).   -  person Ben Voigt    schedule 17.01.2012
comment
Подозирам, че е причинено от непълна I/O заявка. Но решете проблема в основата, няма смисъл да затваряте сериен порт и да го отваряте отново.   -  person Hans Passant    schedule 17.01.2012
comment
@BenVoigt: Не, не използвам DuplicateHandle никъде.   -  person Gili    schedule 17.01.2012
comment
@SimonMourier: Използвам автентичен comport, малко вероятно е проблемът с драйвера.   -  person Gili    schedule 17.01.2012
comment
Всъщност това не е уникално за серийните портове и не мисля, че правите нещо погрешно. Виждал съм същия проблем с манипулатори на файлове. Има значително забавяне между времето, в което CloseHandle се връща, и обектът, който действително се освобождава. Подозирам, че в крайна сметка ще откриете, че някакъв евтин хак, включващ повторен цикъл и заспиване, ще бъде единственото решение.   -  person Carey Gregory    schedule 17.01.2012


Отговори (3)


Вярвам, че проблемът е в драйвера, който обслужва COM порта. Следователно - няма да има API, който да "действително затваря" COM порта.

Между другото, след като затворите манипулатора на файла, няма нужда да чакате всички оставащи I/O да завършат с грешки. По времето, когато CloseHandle връща, всички неизпълнени I/O са вече завършени/анулирани, вие просто получавате обратните извиквания асинхронно (било то чрез порт за завършване или APC опашка, без значение).

По-конкретно FTDI драйверите (тези, които емулират COM->USB) са известни като много бъгови.

Мога само да препоръчам да опитате да изчистите данните, преди да затворите манипулатора. Можете да изчакате всички I/O да завършат преди да затворите COM порта (ако това е приложимо за вашия случай). Като алтернатива можете да се обадите на SetCommMask и WaitCommEvent, за да се уверите, че няма чакащи данни за изпращане. Надяваме се, че това може да помогне.

РЕДАКТИРАНЕ:

CloseHandle незабавно (преди да се върне) отменя ли всички чакащи I/Os на манипулатора на файла?

Строго погледнато - нене.

Възможно е да има други препратки към файловия обект. Например, код на потребителски режим може да извика DuplicateHandle или драйвер на режим на ядрото може да извика ObReferenceObjectByXXXX. В такъв случай обектът, към който се отнася дръжката, не е непременно освободен.

Когато последният манипулатор е затворен, се извиква DispatchCleanup на драйвера. Той трябва да анулира всички оставащи I/Os според това.

Въпреки това, докато една нишка е в рамките на CloseHandle - теоретично можете да издадете друг I/O от друга нишка (ако имате късмет - манипулаторът все още ще бъде валиден). Докато сте в извикването на I/O функция (като WriteFile или т.н.), операционната система временно увеличава брояча на препратки към обекта. Това от своя страна може да започне друг I/O, който няма да бъде отменен директно от извикването CloseHandle.

В този случай обаче манипулаторът ще бъде затворен от O/S веднага след издаването на новия I/O, тъй като броят на препратките към обекта отново достигна 0.

Така че при такъв извратен сценарий може да възникне ситуация, при която веднага след извикване на CloseHandle не можете да отворите отново файла.

person valdo    schedule 17.01.2012
comment
Използвам серийния порт, който идва от дънната платка (използвайки вградените драйвери на Microsoft). Няма активен адаптер COM към USB. Проблемът с изтриването на данните е, че може да блокира завинаги, ако хардуерният контрол на потока е активиран. В идеалния случай бих искал да прекратя всички неизпълнени команди, да затворя порта и да се върна от моята функция, след като затварянето наистина е извършено. Всичко това е спорен въпрос, ако няма API за осигуряване на затваряне. PS: Откъде знаеш, че CloseHandle() анулира всички неуредени I/O преди да се върне? Не виждам такава гаранция в спецификацията. - person Gili; 17.01.2012
comment
Моля, вижте моята редакция. Можете също да опитате да използвате CancelIoEx. И, разбира се, не правете I/O на друга нишка през това време (надявам се, че все пак не правите това) - person valdo; 18.01.2012
comment
Добре, накрая използвах цикъл за повторен опит, когато се опитвах да отворя порта. Благодаря за справочната информация. - person Gili; 20.01.2012

Уверете се, че вашият четец и записващо устройство са се провалили (с невалиден манипулатор), след като сте издали заявката CloseHandle() (вероятно в друга нишка), преди да опитате да докоснете устройството отново. Можете да използвате това като подсказка, за да почистите цялата си обработка на io, преди да се опитате да издадете друг CreateFile. Трябва да кажа, че портовете за завършване изглеждат ненужно барокови.

person jolyon    schedule 20.09.2012
comment
Хубав трик, но ненадежден. Тъй като същата дръжка може да бъде (теоретично) използвана повторно. - person valdo; 20.09.2012
comment
Освен това може да нямате незавършени четения/записи, така че сега трябва да следите и това. - person Gili; 21.09.2012

Когато отворите COM порт в нишка, операционната система отваря друга скрита нишка, за да извърши I/O. Опитвам се да разреша и затварянето на порт. Доколкото мога да кажа, един метод, който може да работи, е: 1) Спиране на нишката, която отвори COM порта, 2) PurgeComm(), за да спре всички I/O и да изпразни всички I/O буфери за четене/запис и 3) след това опитайте за да затворите COM порта. Може да има включен waitforsingleobject, за да се определи кога нишката с COM порта действително спира. Освен ако COM портът абсолютно не трябва да бъде затворен, обмислям да оставя COM порта отворен и да оставя WinProc да обработи командата IDM_Exit, за да затвори COM порта, нишките и приложението. Не това, което исках, но вариант, с който мога да живея. Използвам връзка от USB към сериен порт и не съм сигурен какви проблеми ми причинява това. Знам, че ако използвате интерфейс USB към сериен порт, трябва да зададете пин 20 високо (DTR). Пин 20 помага за захранването на интерфейса, но осигурява само около 30 mA или така. Ако всичко това работи, ще направя актуализация на тази публикация и ще ви уведомя как, ако успея да затворя COM порта. Windows направи бъркотия на драйвера за сериен порт и изглежда е доволен да остави нещата в безпорядък!

person Chuck L.    schedule 06.07.2016