Ще раз про прямий доступ до апаратури  h2>
 Сивцов Павло  p>
 Преамбула  h2>
 Одного разу мій знайомий попросив написати йому просту
програму - «сторожовий пес». Все, що потрібно робити - це відловити момент розмикання
або замикання зовнішнього контакту і при настанні такої події запустити
іншу програму. Працювати програма повинна під Windows XP. Завдання виглядала
елементарної. Єдине, що не хотілося робити - апаратну частину. Тобто
найкраще було б знайти таке рішення, при якому майже нічого не потрібно було
б паяти.  p>
 Досить швидко з'ясувалося, що простіше за все для
такої мети використовувати опитування станів LPT-або COM-портів. Тут і починається
найцікавіше.  p>
 LPT  h2>
 Для реалізації «сторожового пса» на LPT-порту можна
використовувати періодичний опитування стану деяких його контактів. Можна
просто виявляти стану ліній SELECTED (контакт 13), BUSY (контакт 11) і
PAPER EMPTY (контакт 12). Досить замикати/розмикати вибраний контакт з
«Землею» (контакти 18-25). Я вибрав використання BUSY - замикав контакти 11 і
23. Отже, апаратна частина виходила елементарної, тепер треба було якось
достукатися до вибраного контакту з програмної сторони. Тут-то і зустрілася
перша складність - легальних способів прямого доступу до портів в лінійці
Windows NT немає. Використовувати примочки типу gwio.sys, що дозволяють прямий доступ
до апаратури, дуже не хотілося. Робота з портом як з файлом у даному випадку
не підходить, тому що потрібно не дані читати, а опитувати стану. Тим не менше,
після тривалого вивчення MSDN, легальний доступ до деяких лініях порту було
виявлено! Спосіб цей - доступ до порту через функцію DeviceIoControl (...,
IOCTL_PAR_QUERY_INFORMATION, ...). Тут виявилася друга складність - відсутність
потрібних заголовків файлів для Delphi. Довелося самостійно портувати
ntddpar.h з DDK. Портована файл отримав назву JwaNtDdPar.pas і був
люб'язно доданий Marcel van Brakel в JEDI Windows API Library.  p>
 Невеликий приклад демонструє загальний код. Delphi 7.  P>
 Приклад коду  p>
 
  
  
 uses  p>
  
 SysUtils, JwaWinType, JwaWinNT,
  JwaWinBase, JwaNtDdPar;  p>
  
  
 ($ WARN SYMBOL_PLATFORM OFF)  p>
  
  
 function GetLptStatus: Boolean;  p>
  
 var  p>
  
 eFileHandle: THandle;  p>
  
 eInfo: TParQueryInformation;  p>
  
 eBytesReturned: DWORD;  p>
  
 begin  p>
  
// відкриємо порт  p>
  
 eFileHandle: =
  CreateFile ( 'LPT1', GENERIC_READ, 0, nil, OPEN_EXISTING, 0, 0);  p>
  
 Win32Check (eFileHandle <>
  INVALID_HANDLE_VALUE);  p>
  
 try  p>
  
// дізнаємося стан  p>
  
 Win32Check (DeviceIoControl (eFileHandle,
  IOCTL_PAR_QUERY_INFORMATION, nil, 0,  p>
  
 @ eInfo, SizeOf (eInfo),
  @ eBytesReturned, nil )); p>
  
 Result: = (Byte (eInfo.Status)
  and PARALLEL_BUSY) = 0;  p>
  
 finally  p>
  
// не забудьте закрити хендл по завершенню
  роботи  p>
  
 Win32Check (CloseHandle (eFileHandle )); p>
  
 end;  p>
  
 end;  p>
  
 
 Короткий і елегантний код, чи не так? Для вирішення
поставленого завдання досить опитувати стан порту раз-два в секунду.
В принципі, звичайно ж, краще відразу відкрити порт при старті, а закрити по
завершення.  p>
 
  
  
 ПОПЕРЕДЖЕННЯ  p>
  
 На жаль, не вдасться відкрити порт в
  режимі FILE_FLAG_OVERLAPPED, щоб потім використовувати переваги
  асинхронної роботи. Точніше, порт відкрити вдасться, не вдасться отримати подія
  при зміні статусу ліній порту.  p>
  
 Зате цей код успішно відпрацював з-під
  гостьовий облікового запису під Windows XP.  p>
  
 
 Останній нюанс - брязкіт контактів. «Брязкіт
контактів - це явище багаторазового неконтрольованого замикання та розмикання
контактів в моменти їхнього зіткнення і розбіжності ». Тривають такі перехідні
процеси в кнопках близько 10-15 мілісекунд. Тобто з великою ймовірністю ми
будемо отримувати помилкові спрацьовування нашого коду, якщо інтервал між перевірками
буде коротшим.  p>
 Сподіваюся, цей приклад роботи з LPT-портом послужить
хорошою демонстрацією того, як у багатьох випадках легко отримати легальний
доступ до апаратури без написання драйверів або обходу Hardware Abstraction
Layer. Не для того цей HAL придумували, щоб його обходити.  P>
 Доводилося читати про випадки захоплення порту спулера
друку, але на практиці таку ситуацію зустріти не вдалося. Якщо хто-небудь
зможе прояснити це питання, я буду радий.  p>
 
  
  
 ПРИМІТКА  p>
  
 До речі, в середині сторінки  http://cooler.irk.ru/cl190902.html
  викладено досить цікавий лист, в якому описується робота з портом
  в режимі IEEE_COMPATIBILITY. Такий режим дозволяє з мінімумом рухів тіла
  забезпечити повноцінний висновок даних на саморобний LPT-пристрій.  p>
 http://cooler.irk.ru/cl190902.html
  викладено досить цікавий лист, в якому описується робота з портом
  в режимі IEEE_COMPATIBILITY. Такий режим дозволяє з мінімумом рухів тіла
  забезпечити повноцінний висновок даних на саморобний LPT-пристрій.  p>
  
 
 COM
 h2>
 При використанні COM-порту завдання виявлення
зовнішнього події може бути вирішена ще простіше. Досить замикати/розмикати
контакти 7 (RTS) та 8 (CTS) у девятіконтактного роз'єму (знову нічого не
доведеться паяти) і перевіряти наявність сигналу CTS. Причому опитування можна робити
через стандартний CommApi.  p>
 Приклад коду  p>
 
  
  
 function GetComStatus: Boolean;  p>
  
 var  p>
  
 eFileHandle: THandle;  p>
  
 eStatus: DWORD;  p>
  
 begin  p>
  
// відкриємо порт  p>
  
 eFileHandle: =
  CreateFile ( 'COM1', GENERIC_READ, 0, nil, OPEN_EXISTING, 0, 0);  p>
  
 Win32Check (eFileHandle <>
  INVALID_HANDLE_VALUE);  p>
  
 try  p>
  
// дізнаємося стан  p>
  
 Win32Check (GetCommModemStatus (eFileHandle,
  eStatus )); p>
  
 Result: = (eStatus and
  MS_CTS_ON)> 0;  p>
  
 finally  p>
  
// не забудьте закрити хендл по завершенню
  роботи  p>
  
 Win32Check (CloseHandle (eFileHandle )); p>
  
 end;  p>
  
 end;  p>
  
 
 Втім, цей приклад елегантним вже не назвеш, тому що
використання COM-порту дає можливість позбутися від періодичного опитування,
використовуючи асинхронну роботу через WaitCommEvent і WaitForMultipleObjects.  p>
 Нижче наведений код прикладу. Для пояснення суті
того, що відбувається код рясно прокоментований. Але все-таки зверну увагу на
деякі нюанси:  p>
 WaitForMultipleObjects чекає нескінченно. Ніяких
періодичних опитувань - значить і ніякого споживання ресурсів. Все реалізовано
на подіях.  p>
 Немає необхідності в TerminateThread для
примусового припинення виконання потоку. Виконання може бути
«Культурно» закінчено в будь-який момент. Для цього використовується окрема подія.
 p>
 Проста реалізація неблокірующей затримки для
придушення брязкоту. Оскільки періодичний опитування ми не застосовуємо, то, щоб
позбутися від помилкових спрацьовувань програмним шляхом, потрібно почекати кілька
десятків мілісекунд, і якщо стан за цей час не змінилося, то
замикання/розмикання ланцюга відбулося. Як таймера використовується
WaitableTimer. Зверніть увагу на його теоретичну точність.  p>
 Ключовий метод  p>
 
  
  
 procedure
  TComWatchdogThread.Execute;  p>
  
 var  p>
  
// структура, що використовується для Win32
  зберігання внутрішньої інформації при  p>
  
// асинхронної роботі. Нічого крім поля
  hEvent нам від неї не потрібно  p>
  
 eOverlapped: TOverlapped;  p>
  
  
// запит очікування асинхронного події
  зміни стану порту  p>
  
 //............................................. .............................. p>
  
 procedure InitWaitCommEvent;  p>
  
 var  p>
  
 eEventMask: DWORD;  p>
  
 begin  p>
  
// помилки ERROR_IO_PENDING потрібно просто
  ігнорувати - їх наявність означає  p>
  
// тільки те, що остання операція з
  портом ще не завершена.  p>
  
// Що цікаво, не можна двічі поспіль
  викликати WaitCommEvent, тобто  p>
  
// запросив подія - значить, дочекайся його.  p>
  
 if not WaitCommEvent (FComHandle, eEventMask,
  @ eOverlapped)  p>
  
 and (GetLastError <>
  ERROR_IO_PENDING) then  p>
  
 RaiseLastOSError;  p>
  
 end;  p>
  
  
 var  p>
  
// TWOHandleArray - це просто готовий масив з 64 Хендли для  p>
  
// функції WaitForMultipleObjects. Ми використовуємо тільки 3 Хендли,  p>
  
// але для простоти скористаємося готовим
  масивом на 64, щоб  p>
  
// не зв'язуватися з ручним розподілом
  пам'яті.  p>
  
 eHandles: TWOHandleArray;  p>
  
 eTime: Int64;  p>
  
 eStatus: DWORD;  p>
  
 eStubInstalled: Boolean;  p>
  
 begin  p>
  
// заповнимо структури для асинхронної роботи  p>
  
 FillChar (eOverlapped, SizeOf (eOverlapped),
  0);  p>
  
 eOverlapped.hEvent: =
  FChangeEvent;  p>
  
 eHandles [0]: =
  eOverlapped.hEvent;  p>
  
 eHandles [1]: = FTerminateEvent;  p>
  
 eHandles [2]: = FFlutterTimer;  p>
  
// 0.5 секунди для SetWaitableTimer  p>
  
 eTime: = -5000000;  p>
  
// Нічого собі точність, чи не так?  p>
  
  
// попередньо зведений прапор очікування
  події, щоб цикл заробив  p>
  
 InitWaitCommEvent;  p>
  
  
 while not Terminated do  p>
  
 case WaitForMultipleObjects (3,
  @ eHandles, False, INFINITE) of  p>
  
// при зміні стану порту  p>
  
 WAIT_OBJECT_0:  p>
  
 begin  p>
  
// знову зведений прапор очікування події  p>
  
 InitWaitCommEvent;  p>
  
// для придушення брязкоту зробимо невелику
  затримку. Якщо стан  p>
  
// порту зміниться швидше, ніж закінчиться
  час затримки (брязкіт), то  p>
  
// таймер просто буде переведений "на
  пізніше ".  p>
  
 Win32Check (SetWaitableTimer (FFlutterTimer,
  eTime, 0, nil, nil,  p>
  
 False )); p>
  
 end;  p>
  
  
// при запиті примусового завершення
  потоку  p>
  
 WAIT_OBJECT_0 + 1:  p>
  
// негайний вихід - завершення
  виконання потоку  p>
  
 Exit;  p>
  
  
// після затримки для придушення брязкоту  p>
  
 WAIT_OBJECT_0 + 2:  p>
  
 begin  p>
  
// дізнаємося стан  p>
  
 Win32Check (GetCommModemStatus (FComHandle,
  eStatus )); p>
  
 eStubInstalled: = (eStatus and
  MS_CTS_ON)> 0;  p>
  
  
// викличемо обробник події  p>
  
 if eStubInstalled then  p>
  
 DoOnChange;  p>
  
 end;  p>
  
  
// при наглої помилку  p>
  
 WAIT_FAILED:  p>
  
// сталося страшне ... p>
  
 RaiseLastOSError;  p>
  
 end;  p>
  
 end;  p>
  
 
 Повний код прикладу наведено у доданому до статті
архіві.  p>
  Список  b>   b>  літератури  b>  p>
 JEDI Windows API
Library http://members.chello.nl/m.vanbrakel2/win32api.zip  p>
 Parallel Port
Central http://www.lvr.com/parport.htm  p>
 Serial Port Central
http://www.lvr.com/serport.htm  p>
 Лист в журнал «Cooler» про прямий доступ до LPT  http://cooler.irk.ru/cl190902.html
 p>
 http://cooler.irk.ru/cl190902.html
 p>
 Для підготовки даної роботи були використані
матеріали з сайту http://www.rsdn.ru/
 p>