Andrzej Daniluk
USB P ra k ty c z n e p r o g r a m o w a n ie
z W in d o w s API w C++
USB dobre na w s z y s tk o — w y k o rz y s ta j jego m oc! • Standardy USB 2.0 i 3.0 oraz połączone urządzenia, czyli sprzętowa podstawa transmisji danych • Transmisja danych w standardzie USB, czyli komunikacja i współdziałanie zasobów systemowych różnych urządzeń ■ Biblioteki i programy wielowątkowe, czyli szczegółowe aspekty programowania transmisji danych w USB
Spis treści W s tę p ........................................................................................................... 7 Rozdział 1.
Standard USB ........................................................................................... 11
Środowisko fizyczne i sygnałowe USB ........................................................................... 13 USB 2.0 ......................................................................................................................13 USB 3.0 ......................................................................................................................15 Złącza Mini i M icro................................................................................................... 19 Ramki i mikroramki ........................................................................................................ 24 Transfer danych............................................................................................................... 24 Pakiety USB 2.0 .............................................................................................................. 28 Transakcje USB 2.0 ......................................................................................................... 33 Pakiety w trybie Super Speed.......................................................................................... 38 Operacje transakcyjne USB 3.0 .......................................................................................46 Porównanie standardów USB 2.0 oraz 3.0 ................................................................53 Wireless USB .................................................................................................................. 54 Podsumowanie ................................................................................................................ 56 Rozdział 2.
Informacje o urządzeniach .......................................................................57
Identyfikatory urządzenia................................................................................................ 57 Identyfikatory sprzętu................................................................................................ 58 Identyfikatory zgodności........................................................................................... 58 Ocena i selekcja pakietów sterowników....................................................................58 Klasy instalacji urządzeń................................................................................................. 58 Menedżer urządzeń.......................................................................................................... 59 Rejestr systemowy ........................................................................................................... 63 Klucz tematyczny HKEY_LOCAL_MACHINE ......................................................64 Podklucz tematyczny \C lass...................................................................................... 65 Podklucz podklucza tematycznego \Class .................................................................66 Identyfikatory GUID ................................................................................................. 67 Pliki .in f........................................................................................................................... 69 Podsumowanie................................................................................................................. 71
4
Rozdział 3.
USB. Praktyczne programowanie z Windows API w C++
Wstęp do transmisji da n ych .....................................................................73
Struktura systemu USB 2 .0 .............................................................................................. 73 Warstwa funkcjonalna............................................................................................... 73 Warstwa fizyczna ..................................................................................................... 74 Warstwa logiczna ...................................................................................................... 75 Struktura systemu USB 3 .0 .............................................................................................. 76 Potoki danych.................................................................................................................. 77 Urządzenia i deskryptory urządzeń USB .........................................................................80 Koncentratory i deskryptory koncentratorów U SB ..........................................................84 Punkty końcowe i deskryptory punktu końcowego .........................................................89 Interfejsy i deskryptory interfejsów urządzeń USB .........................................................95 Konfiguracje i deskryptory konfiguracji........................................................................ 100 Deskryptory tekstowe .................................................................................................... 104 Komunikacja programu użytkownika z urządzeniem....................................................104 Podsumowanie................................................................................................................110 Rozdział 4.
Urządzenia klasy HID ............................................................................. 111
Deskryptor raportu..........................................................................................................111 Pozycje Collection i End Collection........................................................................ 112 Rodzaje raportów .....................................................................................................113 Zawartość raportów................................................................................................. 114 Format danych..........................................................................................................115 Zakresy wartości danych......................................................................................... 115 Jednostki miar ..........................................................................................................115 Podstawowe funkcje urządzeń klasy HID ..................................................................... 116 Funkcje rodziny HidD_Xxx().................................................................................. 117 Funkcje rodziny HidP_Xxx() .................................................................................. 125 Biblioteka HID.dll ..........................................................................................................144 Podsumowanie................................................................................................................147 Rozdział 5.
Detekcja i identyfikacja urządzeń dołączonych do magistrali USB .. 149
Podstawowe zasoby systemowe .................................................................................... 151 Funkcja SetupDiGetClassDevs()............................................................................. 152 Funkcja SetupDiEnumDeviceInterfaces() ............................................................152 Struktura SP_DEVINFO_DATA ............................................................................ 153 Struktura SP_DEVICE_INTERFACE_DATA .......................................................154 Struktura SP_DEVICE_INTERFACE_DETAIL_DATA .......................................155 Funkcja SetupDiGetDeviceInterfaceDetail()........................................................... 155 Funkcja SetupDiDestroyDeviceInfoList()............................................................... 157 Detekcja interfejsów urządzeń ................................................................................ 157 Zliczanie interfejsów urządzeń ................................................................................ 161 Funkcja SetupDiGetDeviceRegistryProperty() .............................................................. 163 Struktury danych ............................................................................................................168 Moduł usbiodef.h............................................................................................................174 Moduł cfgmgr32.h..........................................................................................................176 Biblioteka Setupapi ...................................................................................................... 182 Powiadamianie o dołączaniu i odłączaniu urządzeń......................................................185 Podsumowanie................................................................................................................189
Spis treści
Rozdział 6.
5
Odblokowanie urządzenia do transmisji. Odczyt i zapis danych ........ 191
Odblokowanie urządzenia do transmisji........................................................................ 191 Funkcja CreateFile().................................................................................................192 Funkcja CloseHandle()............................................................................................ 194 Przykładowy program środowiska tekstowego ......................................................194 Odczyt danych w formie raportu ................................................................................... 198 Funkcja ReadFile()...................................................................................................199 Odczyt długości bufora danych...............................................................................203 Funkcja HidD_GetInputReport().............................................................................207 Odczyt własności przycisków .................................................................................208 Odczyt własności wartości ......................................................................................213 Aplikacja środowiska graficznego...........................................................................218 Zapis danych w formie raportu......................................................................................225 Funkcja WriteFile()................................................................................................. 225 Funkcje HidD_SetOutputReport() oraz HidD_SetFeature()................................... 226 Struktura OVERLAPPED .............................................................................................227 Funkcje xxxEx ........................................................................................................ 230 Struktura COMMTIMEOUTS.......................................................................................234 Funkcje GetCommTimeouts() i SetCommTimeouts() ........................................... 235 Funkcja DeviceIoControl()............................................................................................ 236 Rozkazy z modułu hidclass.h ..................................................................................242 Rozkazy z modułu usbioctl.h.........................................................................................245 Identyfikacja urządzeń przyłączonych do koncentratora USB ............................... 247 Struktura URB ............................................................................................................... 262 Funkcja UsbBuildGetDescriptorRequest()..............................................................267 Podsumowanie............................................................................................................... 268 Ćwiczenia...................................................................................................................... 268 Rozdział 7.
Biblioteki WinUSB oraz LibUSB ............................................................ 271
Biblioteka WinUSB ....................................................................................................... 271 Przygotowanie pakietu instalacyjnego ....................................................................272 Funkcje eksportowe biblioteki WinUSB .................................................................277 Biblioteka LibUSB ........................................................................................................ 289 Funkcje jądra biblioteki........................................................................................... 292 Funkcje do zarządzania urządzeniem libusb ...........................................................293 Funkcje realizujące transfer masowy ...................................................................... 300 Funkcje realizujące transfer przerwaniowy .............................................................301 Funkcje asynchroniczne .......................................................................................... 301 Podsumowanie .............................................................................................................. 305 Rozdział 8.
Programowanie obiektowe transmisji USB ......................................... 307
Obiektowość .................................................................................................................. 307 Wzorce projektowe........................................................................................................ 314 Singleton ................................................................................................................. 314 Interfejsy.........................................................................................................................319 Zliczanie odwołań do interfejsu .............................................................................. 326 Identyfikator interfejsu............................................................................................ 327 Komponenty w izualne................................................................................................... 336 Podsumowanie............................................................................................................... 340 Ćwiczenia.......................................................................................................................340
6
Rozdział 9.
USB. Praktyczne programowanie z Windows API w C++
Wewnętrzne struktury da n ych ...............................................................351
Program proceduralny ................................................................................................... 352 Program obiektowy ....................................................................................................... 359 Aplikacja środowiska graficznego................................................................................. 366 Podsumowanie............................................................................................................... 375 Ćwiczenia.......................................................................................................................375 Rozdział 10. Programy wielowątkowe........................................................................379
Wątki i procesy.............................................................................................................. 379 Funkcja CreateThread()................................................................................................. 381 Klasa TThread ............................................................................................................... 389 Podsumowanie............................................................................................................... 397 Ćwiczenia.......................................................................................................................397 Rozdział 11. Adaptery USB .........................................................................................401
Adaptery USB/RS 232C ................................................................................................ 401 Właściwości portu adaptera.....................................................................................402 Adaptery USB/IEEE-488 .............................................................................................. 404 Adaptery USB/Bluetooth............................................................................................... 405 Podsumowanie............................................................................................................... 413 Literatura ............................................................................................... 415 Skorowidz .............................................................................................. 417
Wstęp Początki historii standardu USB sięgają połowy lat 90. ubiegłego wieku, kiedy to kon sorcjum siedmiu wówczas wiodących firm komputerowych i komunikacyjnych (Compaq, DEC, IBM, Intel, M icrosoft, NEC i N orthern Telecom) ogłosiło jego powstanie. Od tego czasu powiększyło się ono do ponad 1000 członków zrzeszonych w tzw. forum wdrażania USB IF (ang. USB — Implementers Forum). W 1996 roku powstała oficjalna bazow a norm a USB 1.0, specyfikująca główne aspekty programowe, mechaniczne, elektryczne i kom unikacyjne standardu USB. W 1999 roku wprowadzono aktualiza cję standardu w postaci normy USB 1.1. W wersji tej usunięto pewne problemy, które ujawniły się w trakcie przemysłowego użytkowania poprzedniej. Bardziej dynamiczny rozwój standardu USB datuje się od 1999 roku, kiedy to M icro soft i Apple zaadaptowały go na potrzeby swoich systemów operacyjnych. W 2000 roku ukazała się aktualizacja USB 2.0, kompatybilna z USB 1.1, wraz ze specyfikacją ma gistrali 480 Mb/s. Ze względu na rosnące zapotrzebowanie przystosowania standardu USB do urządzeń przenośnych w 2001 roku ogłoszono suplem ent USB OTG (ang. USB On-The Go). Bazowa norma USB specyfikuje topologię gwiaździstą, w której centrum jest hub, zaś w systemie może funkcjonować tylko jed en host (komputer). Zgodnie z USB OTG hostem może być każde urządzenie. Kolejną normą USB jest Wireless USB, która opi suje zasady bezprzewodowej łączności opartej na sieciowym protokole USB. Na prze łomie lat 2008 i 2009 ogłoszono trzecią generację standardu bazowego USB 3.0. Właściwości standardu USB dotyczą urządzeń zasilanych z magistrali, automatycznie wykrywalnych, samokonfigurujących się i o dużej szybkości transmisji danych. Jednak duża prostota w użytkowaniu je st okupiona koniecznością użycia bardziej złożonego sprzętu i oprogramowania w porównaniu do starszych szeregowych protokołów trans misji danych. Interfejs USB je st dostatecznie elastyczny zarówno w zastosowaniach dla popularnych układów peryferyjnych, takich jak myszy, joysticki, klawiatury, dru karki itp., ja k i dla urządzeń specjalizowanych, takich ja k wszelkiego rodzaju aparatu ra pomiarowa i diagnostyczna oraz cyfrowo sterowane urządzenia przemysłowe.
8
USB. Praktyczne programowanie z Windows API w C++
Celem niniejszej publikacji jest przedstawienie podstaw zasad programowania transmi sji USB z wykorzystaniem zasobów systemów operacyjnych Windows oraz współist niejących bibliotek programistycznych. Książka nie je st jedynie prezentacją typów danych, funkcji czy struktur oferow anych przez systemy operacyjne W indows oraz współistniejące biblioteki, ale przede w szystkim zawiera dużo wskazówek w postaci szczegółowo przedstawionych przykładowych aplikacji. W trakcie przygotowywania niniejszego opracowania był używany kompilator Com piler 5.5 języka C++ zgodny ze standardem ANSI/ISO i generujący kod wykonywal ny dla systemów Microsoft Windows. Pakiet zawiera bibliotekę STL. C++ Compiler 5.5 je st dostępny nieodpłatnie na stronie firmy Embarcadero (http://edn.embarcadero. com/article/20633). W szystkie program y były testowane w 32-bitowych systemach operacyjnych Windows XP i Vista oraz 64-bitowych W indows 7 i 8. Korzystano też z zasobów Windows Driver K it (WDK) Version 7.1.0 oraz 8.0, oferujących dokumen tację oraz zestawy sterowników USB. W DK 7.1.0 oraz 8.0 można bezpłatnie pobrać ze stron MSDN. Sposób prezentowania informacji zawartych w podręczniku zakłada, że Czytelnik opa nował podstawy języka C/C++ w zakresie programowania strukturalnego i procedu ralnego oraz posiada podstawowe wiadomości dotyczące sposobów samodzielnej in stalacji sterowników i konfiguracji sprzętu w systemach operacyjnych Windows.
Układ książki Pod względem tematycznym książka została podzielona na jedenaście rozdziałów. Rozdział 1., „Standard USB”, zawiera opis podstawowych właściwości bazowych stan dardów USB 2.0 oraz USB 3.0. Rozdział 2., „Informacje o urządzeniach”, omawia główne zasoby systemowe udostęp niające informacje o aktualnie przyłączonych urządzeniach USB. Rozdział 3., „Wstęp do transmisji danych”, został poświęcony omówieniu głównych ele mentów systemu odpowiedzialnych za realizację transmisji danych w standardzie USB. Rozdział 4., „Urządzenia klasy H ID ”, opisuje zasoby systemowe odpowiedzialne za komunikację z urządzeniami interaktywnymi. Rozdział 5., „Detekcja i identyfikacja urządzeń dołączonych do magistrali USB”, opi suje aspekty związane z procedurami detekcji oraz identyfikacji urządzeń dołączanych do magistrali USB. Rozdział 6., „Odblokowanie urządzenia do transmisji. Odczyt i zapis danych”, omawia podstawowe zasoby systemów operacyjnych Windows w kontekście praktycznego wy korzystania ich w trakcie realizacji szeregowej transmisji danych w standardzie USB.
Wstęp
9
Rozdział 7., „Biblioteki WinUSB oraz LibUSB”, przedstawia zasoby bibliotek WinUSB oraz LibUSB w kontekście praktycznego wykorzystania ich w trakcie realizacji szere gowej transmisji danych w standardzie USB z urządzeniami różnych typów. Rozdział 8., „Programowanie obiektowe transmisji USB”, omawia aspekty związane z praktycznym wykorzystaniem wybranych zasad program owania obiektowego i zo rientowanego obiektowo w celu realizacji transmisji danych w standardzie USB. Rozdział 9., „W ewnętrzne struktury danych”, jest poświęcony zagadnieniom związa nym z praktycznymi zastosowaniami wewnętrznych struktur danych opisujących urzą dzenie USB. Rozdział 10., „Programy wielowątkowe”, dotyczy wykorzystania C++ w nowoczesnych w ielow ątkow ych system ach operacyjnych. C++ posiada szereg cech ułatwiających tworzenie aplikacji wielowątkowych, jednak ich praktyczne wykorzystanie w trakcie realizacji szeregowej transmisji danych stanowi nieraz problem dla mniej doświadczo nych programistów. Rozdział 11., „Adaptery U SB ”, opisuje adaptery rodzin USB/RS 232C, USB/GPIB oraz USB/Bluetooth, pomocne w realizowaniu transmisji danych z urządzeniami, które nie są zaopatrzone w interfejs USB.
10
USB. Praktyczne programowanie z Windows API w C++
Rozdział 1.
Standard USB Dynamiczny rozwój systemów informatycznych i coraz większa liczba urządzeń zdol nych do wym iany inform acji z kom puterem w czasie rzeczywistym skłoniły produ centów sprzętu i oprogramowania do opracowania standardu przesyłu danych, który zapewniłby prostotę przyłączania urządzeń do systemu, a zarazem zadowalającą uni wersalność i funkcjonalność obsługi. Wdrożenie uniwersalnej magistrali szeregowej U SB rozwiązało dotychczasowe problem y z ograniczoną liczbą dostępnych w kom puterze portów komunikacyjnych RS-232C i LPT oraz ze zbyt m ałą szybkością trans misji danych oferowaną przez te porty. U niw ersalność U SB zapewnia m ożliwość podłączenia do kom putera w ielu różnych urządzeń, które następnie są automatycznie wykrywane i rozpoznawane przez system, przez co instalacja sterowników i konfiguracja sprzętu odbywają się w dużym stopniu automatycznie. Obecnie wszystkie liczące się systemy operacyjne obsługują transmi sje danych w standardach USB. Jedynym liczącym się do niedawna konkurentem USB był lansowany przez Apple stan dard łącza szeregowego ogólnego przeznaczenia FireWire, umożliwiający bardzo szyb ką izochroniczną transmisję danych w czasie rzeczywistym. Standard ten jest znacznie szybszy i stabilniejszy niż USB 2.0, a jego najnowsza wersja — znana pod oznaczeniem IEEE 1394d — przewiduje wykorzystanie połączeń optycznych, co powinno umożli wić transfer danych z szybkością do 6,4 Gb/s. O dpow iedzią firm y Intel było powołanie grupy z zadaniem opracowania i promocji standardu USB 3.0, zdolnego oferować dziesięciokrotnie szybsze transfery danych niż USB 2.0 (patrz tabela 1.1) z zachowaniem kompatybilności wstecznej polegającej na pełnej obsłudze urządzeń USB 1.x i 2.0. Pierwsze urządzenia USB 3.0 pojawiły się na rynku na przełomie lat 2009/2010. Trwająca od dłuższego czasu rywalizacja pomiędzy firmami Intel i Apple doprow a dziła do wspólnego opracowania przez tych producentów 20-pinowego interfejsu no wej generacji o wymownej nazwie Thunderbolt (piorun; wcześniej używano nazwy
12
USB. Praktyczne programowanie z Windows API w C++
Tabela 1.1. Zakresy szybkości przesyłu danych obsługiwanych przez przewodowe łącza USB Szybkość transmisji danych
Zakres
Zastosowanie
Niska lub mała — LS (ang. Low Speed)
do 1,5 Mb/s
Urządzenia interaktywne: klawiatura, mysz, kontrolery gier, aparatura pomiarowa
Pełna — FS (ang. Full Speed)
do 12 Mb/s
Wysokiej klasy aparatura pomiarowa, telefonia, sygnały audio i skompresowane wideo
Wysoka — HS (ang. High Speed)
do 480 Mb/s
Obsługa dysków twardych, sygnały wizyjne, obrabiarki sterowane numerycznie
Superwysoka — SS (ang. Super Speed)
do 5 Gb/s
Obsługa dysków i urządzeń optycznych, sygnały wizyjne wysokiej rozdzielczości
kodowej Light Peak) z rozdzielonymi liniami wysyłania i odbierania danych (podob nie jak w USB 3.0). Thunderbolt jest kombinacją rozwiązań technologicznych znanych z magistrali PCI Express (transfer danych) oraz uniwersalnego interfejsu cyfrowego DisplayPort (transfer obrazu). Okablowanie interfejsu miało być pierwotnie oparte na światłowodach i umożliwiać transm isję danych z szybkością powyżej 100 Gb/s, je d nak uznano, iż rozwiązanie to będzie zbyt drogie. Aktualnie dostępny Thunderbolt ko rzysta z kabli miedzianych, co zapewnia dwukierunkowe transfery danych z prędkością do 10 Gb/s przy ograniczeniu długości kabla do ok. 6 metrów. Thunderbolt umożliwia jednoczesne połączenie z wieloma urządzeniami i w założeniu ma być następcą USB, FireW ire oraz HDMI. N a rysunku 1.1 schematycznie zestawiono najpopularniejsze obecnie standardy łączy szeregowych pod względem oferowanych przez nie szybkości transferów danych. Rysunek 1.1. Najważniejsze obecnie standardy przewodowych łączy szeregowych
N a rysunkach 1.2 - 1.4 zaprezentowano oznaczenia stosowane przez producentów urzą dzeń USB.
Rozdział 1. ♦ Standard USB
13
Rysunek 1.2. Obecnie używane oznaczenia przewodowych urządzeń USB 2.0 z podstawową i wysoką szybkością transmisji
Rysunek 1.3. Obecnie używane oznaczenia przewodowych urządzeń USB 3.0 z superwysoką szybkością transmisji Rysunek 1.4. Obecnie używane oznaczenie bezprzewodowych urządzeń USB
Środowisko fizyczne i sygnałowe USB Niniejszy podrozdział zawiera zbiór podstawowych informacji na temat okablowania, złączy oraz linii sygnałowych wykorzystywanych w bazowych standardach USB 2.0 oraz USB 3.0.
USB 2.0 N a rysunkach 1.5 - 1.9 pokazano kolejno: ♦ schemat przekroju kabla wykorzystywanego przez USB 2.0, ♦ przekrój kabla standardu USB 2.0,
14
USB. Praktyczne programowanie z Windows API w C++
Rysunek 1.5. Kabel USB 2.0
Rysunek 1.6. Przekrój kabla standardu USB 2.01
Rysunek 1.7. Standardowe złącze USB 2.0 typu A
Rysunek 1.8. Standardowe złącze USB 2.0 typu B
Rysunek 1.9. Numeracja styków złączy USB 2.0 typu A i B
1 W celu poprawienia parametrów elektrycznych oraz zwiększenia stabilność impedancji w kablach ekranowanych stosuje się ekran z folii metalowej wraz ze spiralnym ułożeniem żyły ciągłości (ang. drain wire) wokół folii.
Rozdział 1. ♦ Standard USB
15
♦ wygląd złącza typu A wykorzystywanego do podłączenia urządzeń do komputera (hosta), ♦ wygląd złącza typu B wykorzystywanego do przyłączania urządzeń peryferyjnych, ♦ numerację wyprowadzeń we wtyczkach typu A i B, których wykaz zawiera tabela 1.2. Tabela 1.2. Wykaz wyprowadzeń wykorzystywanych w złączach USB 2.0 typu A i B Numer
Oznaczenie
Opis sygnału
Kolor przewodu
1
VBUS
Przewód zasilania (maks. +5 [V])
Czerwony
2
Data- (D-)
Przewód symetrycznej skrętki sygnałowej
Biały
3
Data+ (D+)
Przewód symetrycznej skrętki sygnałowej
Zielony
4
GND
Przewód masy zasilania
Czarny
Shell
Shield
Ekran
USB 3.0 N a rysunkach 1.10 - 1.15 pokazano kolejno: ♦ schemat przekroju kabla wykorzystywanego przez USB 3.0, ♦ przekrój kabla standardu USB 3.0, ♦ wygląd złącza typu A wykorzystywanego do podłączenia urządzeń do komputera (hosta), ♦ wygląd złącza typu B wykorzystywanego do przyłączania urządzeń peryferyjnych, ♦ numerację wyprowadzeń we wtyczkach typu A i B, których wykaz zawierają tabele 1.3 i 1.4, ♦ numerację styków złącza USB 3.0 typu Powered-B, których wykaz zawiera tabela 1.5. Rysunek 1.10. Kabel USB 3.0
16
USB. Praktyczne programowanie z Windows API w C++
Rysunek 1.12. Standardowe złącze USB 3.0 typu A z niebieską częścią wewnętrzną we wtyczce. Wnętrze większości wtyczek typu A w starszych kablach USB 2.0 jest białe lub czarne
Rysunek 1.13. Standardowe złącze USB 3.0 typu B z niebieską częścią wewnętrzną we wtyczce. Wnętrze większości wtyczek typu B w starszych kablach USB 2.0 jest białe lub czarne Rysunek 1.14. Numeracja styków złączy USB 3.0 typu A i B
Rozdział 1. ♦ Standard USB
17
Tabela 1.3. Wykaz wyprowadzeń wykorzystywanych w złączach USB 3.0 typu A Numer
Oznaczenie
Opis sygnału
Kolor przewodu
1
VBUS
Przewód zasilania (maks. +5 [V])
Czerwony
2
Data- (D-)
Przewód symetrycznej skrętki sygnałowej USB 2.0
Biały
3
Data+ (D+)
Przewód symetrycznej skrętki sygnałowej USB 2.0
Zielony
4
GND
Przewód masy zasilania
Czarny
5
StdA_SSRX-
Przewód symetrycznej skrętki linii odbiorczej dla superwysokiej szybkości transmisji (ang. Super Speed Receiver)
Niebieski
6
StdA_SSRX+
Przewód symetrycznej skrętki linii odbiorczej dla superwysokiej szybkości transmisji (ang. Super Speed Receiver)
Żółty
7
GND_DRAIN
Przewód masy sygnałowej
Czarny
8
StdA_SSTX-
Przewód symetrycznej skrętki linii nadawczej dla superwysokiej szybkości transmisji (ang. Super Speed Transmitter)
Fioletowy
9
StdA_SSTX+
Przewód symetrycznej skrętki linii nadawczej dla superwysokiej szybkości transmisji (ang. Super Speed Transmitter)
Pomarańczowy
Shell
Shield
Ekran
Tabela 1.4. Wykaz wyprowadzeń wykorzystywanych w złączach USB 3.0 typu B Numer
Oznaczenie
Opis sygnału
Kolor przewodu
1
Przewód zasilania (maks. +5 [V])
Czerwony
2
VBUS Data- (D-)
Przewód symetrycznej skrętki sygnałowej USB 2.0
Biały
3
Data+ (D+)
Przewód symetrycznej skrętki sygnałowej USB 2.0
Zielony
4
GND
Przewód masy zasilania
Czarny
5
StdB_SSTX-
Przewód symetrycznej skrętki linii nadawczej dla superwysokiej szybkości transmisji (ang. Super Speed Transmitter)
Niebieski
6
StdB_SSTX+
Przewód symetrycznej skrętki linii nadawczej dla superwysokiej szybkości transmisji (ang. Super Speed Transmitter)
Żółty
7
GND_DRAIN
Przewód masy sygnałowej
Czarny
8
StdB_SSRX-
Przewód symetrycznej skrętki linii odbiorczej dla superwysokiej szybkości transmisji (ang. Super Speed Receiver)
Fioletowy
9
StdB_SSRX+
Przewód symetrycznej skrętki linii odbiorczej dla superwysokiej szybkości transmisji (ang. Super Speed Receiver)
Pomarańczowy
Shell
Shield
Ekran
18
USB. Praktyczne programowanie z Windows API w C++
Tabela 1.5. Wykaz wyprowadzeń wykorzystywanych w złączach USB 3.0 Powered-B Numer
Oznaczenie
Opis sygnału
1
VBUS
Przewód zasilania (maks. +5 [V])
2
Data- (D-)
Przewód symetrycznej skrętki sygnałowej USB 2.0
3
Data+ (D+)
Przewód symetrycznej skrętki sygnałowej USB 2.0
4
GND
Przewód masy zasilania
5
StdB_SSTX-
Przewód symetrycznej skrętki linii nadawczej dla superwysokiej szybkości transmisji (ang. Super Speed Transmitter)
6
StdB_SSTX+
Przewód symetrycznej skrętki linii nadawczej dla superwysokiej szybkości transmisji (ang. Super Speed Transmitter)
7
GND_DRAIN
Przewód masy sygnałowej
8
StdB_SSRX-
Przewód symetrycznej skrętki linii odbiorczej dla superwysokiej szybkości transmisji (ang. Super Speed Receiver)
9
StdB_SSRX+
Przewód symetrycznej skrętki linii odbiorczej dla superwysokiej szybkości transmisji (ang. Super Speed Receiver)
10
DPWR
Przewód zasilania dostarczanego z portu USB
11
DGND
Przewód masy zasilania DPWR
Shell
Shield
Ekran
W celu zapewnienia znacznie szybszego transferu danych w protokole transmisyjnym USB 3.0 wprowadzono poziom superwysokiej szybkości transferu danych SS (ang. Super Speed), w którym dane są przesyłane z szybkością do 5 Gb/s. Aby koncentrator USB 3.0 mógł zaoferować wszystkie tryby transm isji danych (z zachowaniem kom patybilności wstecznej), składa się z dw óch koncentratorów podrzędnych. Pierwszy zapewnia transfer 5 Gb/s trybu SS, drugi zaś jest odpowiedzialny za transmisję w try bach FS (ang. Full Speed) i HS (ang. High Speed). Oba wymienione koncentratory łą czą się dopiero w porcie USB. W porównaniu do USB 2.0 wtyczki interfejsu USB 3.0 m ają pięć styków więcej. Do pary przewodów D+ i D - obecnych w USB 2.0 dodano dwie pary symetrycznych linii transmisyjnych. Linie SSTX+ i SSTX- są przeznaczone do przesyłania danych z hosta do urządzenia zewnętrznego, zaś linie SSRX+ i SSR X służą do przesyłania danych w kierunku odwrotnym — od urządzenia peryferyjnego do hosta. Dodano też dodatkowe przewody masy sygnałowej GND_DRAIN. Dodanie dodatkow ych linii transm isyjnych skutkuje powiększeniem liczby styków w gniazdach i wtyczkach USB 3.0 (patrz rysunki 1.12 - 1.14 oraz tabele 1.3 i 1.4). Aby uzyskać miejsce na dodatkowe styki, gniazda i wtyczki USB 3.0 zostały nieco powięk szone — z wyjątkiem typu A, w którym dodatkowe styki znalazły się w tylnej części wtyczki, a dotychczasowy jej kształt pozostał niezmieniony. Mimo opisanych różnic standard USB 3.0 zachowuje zgodność wsteczną z USB 2.0. Oznacza to, że na przy kład starsze urządzenia USB 2.0 ze złączam i typu A można bez problemu podłączać do portu USB 3.0 typu A i odwrotnie (patrz rysunek 1.16 oraz tabela 1.9). Złącza typu Powered pozwalają na zasilanie urządzeń zewnętrznych za pom ocą kabla służącego jednocześnie do przesyłania danych. Bazowy standard Pow ered USB po zwala na zasilanie jednego portu napięciem +5 V, przy natężeniu 0,5 A. Specyfikacja
Rozdział 1. ♦ Standard USB
19
USB + Power 0.8 um ożliw ia podłączanie urządzeń w ym agających zasilania prądem o napięciu +24 V i natężeniu 5 A. Na rysunku 1.15 zaprezentowano numerację styków złącza USB 3.0 Powered-B, zaś w tabeli 1.5 przedstawiono opis wyprowadzeń wyko rzystywanych w omawianym złączu. Rysunek 1.15. Numeracja styków złącza USB 3.0 typu Powered-B
O możliwości uzyskania maksymalnej prędkości transmisji danych w standardach USB przy połączeniu różnych urządzeń decyduje element o najmniejszej oferowanej pręd kości transferu. N a przykład urządzenie USB 3.0 podłączone do hosta z kontrolerem USB 2.0 będzie przesyłało dane z prędkością nie większą, niż oferuje tryb HS interfej su USB 2.0. N a rysunku 1.16 zaprezentowano możliwe sposoby połączenia urządzeń USB 3.0 oraz 2.0. Rysunek 1.16. Możliwe warianty połączenia urządzeń USB 3.0 i USB 2.0
Złącza Mini i Micro Różne typy zm iniaturyzow anych złączy USB wykorzystuje się w przenośnych urzą dzeniach, takich jak palmtopy, tablety, telefony komórkowe, kamery czy aparaty cy frowe. N a rysunkach 1.17 i 1.18 pokazano odpowiednio wygląd złącza USB 2.0 typu
20
USB. Praktyczne programowanie z Windows API w C++
Rysunek 1.17. Złącze USB 2 0 typu Mini-A
Rysunek 1.18. Numeracja styków złączy USB 2.0 typu Mini-A i Mini-B
5*3£i
b 4 3 P I
i ę -h ^ r-K -5 ^
M in i-A
M in i-B
M ini-A oraz numerację wyprowadzeń we wtyczkach typu M ini-A i M ini-B, których wykaz zaw iera tabela 1.6. Złącza typu M ini obecnie są już przestarzałe i nowe urzą dzenia nie powinny być w nie wyposażane. Tabela 1.6. Wykaz wyprowadzeń wykorzystywanych w złączu USB 2.0 Mini/Micro A i B Numer
Oznaczenie
Opis sygnału
Kolor przewodu
1
VBUS
Przewód zasilania (maks. +5 [V])
Czerwony
2
Data- (D-)
Przewód symetrycznej skrętki sygnałowej
Biały
3
Data+ (D+)
Przewód symetrycznej skrętki sygnałowej
Zielony
4
ID
USB OTG ID do identyfikacji linii. Dla urządzenia typu master — linia połączona z masą; dla urządzenia typu slave — linia nie podłączona
5
GND
Przewód masy zasilania
Shell
Shield
Ekran
Czarny
N a rysunku 1.19 pokazano wygląd złącza USB 2.0 typu Micro-B. Rysunek 1.19. Złącze USB 2 0 typu Micro-B
Od 2007 roku standardem USB OTG (technologii umożliwiającej komunikację mię dzy dwoma urządzeniami USB na zasadzie master-slave bez pośrednictwa komputera) stały się mikrozłącza USB 2.0, dla których numeracja styków je st pokazana na rysun ku 1.20, zaś wykaz wyprowadzeń je st opisany w tabeli 1.6.
Rozdział 1. ♦ Standard USB
Numeracja styków’ złączy USB 2.0 typu Mi ero-A i Micro-B
21
I 12345" Mi C ro-A
f |— j 12345 Mi Cro-B
Wraz z upowszechnieniem się standardu USB 3.0 konstrukcja mikrozłączy uległa znacz nej modyfikacji. Na rysunkach 1.21 oraz 1.22 pokazano odpowiednio wygląd wtyczki USB 3.0 typu Micro-A/AB oraz numerację styków, których opis wyprowadzeń zawiera tabela 1.7. W arto zauważyć, że — podobnie jak w przypadku standardowych złączy USB 3.0 typu A/B — mikrozłącza USB 3.0 również umożliwiają zachowanie kompa tybilności transferu danych zgodnego z wersją 2.0. Rysunek 1.21. Wtyczka USB 3.0 typu Micro-A/AB
Tabela 1.7. Wykaz wyprowadzeń wykorzystywanych w złączu USB 3.0 Micro-A/AB Numer
Oznaczenie
Opis sygnału
Kolor przewodu
1
VBUS
Przewód zasilania (maks. +5 [V])
Czerwony
2
Data- (D-)
Przewód symetrycznej skrętki sygnałowej USB 2.0
Biały Zielony
3
Data+ (D+)
Przewód symetrycznej skrętki sygnałowej USB 2.0
4
ID
USB OTG ID do identyfikacji linii
5
GND
Przewód masy zasilania
Czarny
6
MicA_SSTX-
Przewód symetrycznej skrętki linii nadawczej dla superwysokiej szybkości transmisji (ang. Super Speed Transmitter)
Fioletowy
7
MicA_SSTX+
Przewód symetrycznej skrętki linii nadawczej dla superwysokiej szybkości transmisji (ang. Super Speed Transmitter)
Pomarańczowy
8
GND_DRAIN
Przewód masy sygnałowej
Żyła ciągłości
9
MicA_SSRX-
Przewód symetrycznej skrętki linii odbiorczej dla superwysokiej szybkości transmisji (ang. Super Speed Receiver)
Niebieski
10
MicA_SSRX+
Przewód symetrycznej skrętki linii odbiorczej dla superwysokiej szybkości transmisji (ang. Super Speed Receiver)
Żółty
Shell
Shield
Ekran
Złącze USB 3.0 typu Micro-A jest praw ie identyczne jak złącze Micro-B. Różnica po lega na tym, iż szersza część złącza typu Micro-A (patrz rysunki 1.21 i 1.22) nie posiada konstrukcji stożkowej. Zapobiega to próbom podłączenia kabla USB 3.0 z w tyczką
22
USB. Praktyczne programowanie z Windows API w C++
USB 3.0
10
U S B 2.0
1
Rysunek 1.22. Numeracja styków złączy USB 3.0 typu Micro-A. Szersza część złącza zapewnia zgodność wsteczną ze standardem USB 2.0 typu M icro-A do portu USB 3.0 Micro-B. Tego typu konstrukcja zapewnia, że urzą dzenie peryferyjne nie może funkcjonować jako host USB. Z kolei mikrozłącze USB 3.0 typu AB jest z wyglądu identyczne jak USB Micro-A. W konsekwencji zgodnie ze specyfikacją każde urządzenie USB OTG, które może funkcjonować jako host, powin no też funkcjonować jako urządzenie peryferyjne. Z tego względu odpowiedni port USB w urządzeniu powinien akceptować zarówno wtyczki typu Micro-A, jak i Micro-B (patrz tabela 1.9). N a rysunkach 1.23 oraz 1.24 pokazano odpowiednio wygląd wtyczki USB 3.0 typu Micro-B oraz numerację styków, których opis wyprowadzeń zawiera tabela 1.8.
Rysunek 1.23. Wtyczka USB 3.0 typu Micro-B. Szersza, stożkowa część zapewnia zgodność wsteczną ze standardem 2.0 (patrz rysunek 1.19) USB 3.0
10
U S B 2 .0
1
Rysunek 1.24. Numeracja styków złączy USB 3.0 typu Micro-B. Szersza, stożkowa część złącza zapewnia zgodność wsteczną ze standardem USB 2.0 Mikrozłącze USB 3.0 typu B jest z wyglądu podobne do klasycznego USB 2.0 Micro-B, ale z zestawem dodatkowych pinów w prostokątnej obudowie, odpowiedzialnych za organizację transferu w trybie Super Speed. Stożkowa konstrukcja części złącza jest wstecznie kom patybilna z USB 2.0 (patrz rysunek 1.19) i zapobiega próbom podłą czenia kabla z wtyczką USB 3.0 M icro-A do portu Micro-B. Złącze USB 3.0 Micro-B występuje zarówno w postaci męskiej (wtyczka), jak i żeńskiej (gniazdo). W tabeli 1.9 zebrano wykaz podstawowych typów wtyczek i gniazd USB z uwzględnieniem możliwości wzajemnej współpracy.
Rozdział 1. ♦ Standard USB
23
Tabela 1.8. Wykaz wyprowadzeń wykorzystywanych w złączu USB 3.0 Micro-B Numer
Oznaczenie
Opis sygnału
Kolor przewodu
1
VBUS
Przewód zasilania (maks. +5 [V])
Czerwony
2
Data- (D-)
Przewód symetrycznej skrętki sygnałowej USB 2.0
Biały
3
Data+ (D+)
Przewód symetrycznej skrętki sygnałowej USB 2.0
Zielony
4
ID
USB OTG ID do identyfikacji linii
5
GND
Przewód masy zasilania
Czarny
6
MicB_SSTX-
Przewód symetrycznej skrętki linii nadawczej dla superwysokiej szybkości transmisji (ang. Super Speed Transmitter)
Fioletowy
7
MicB_SSTX+
Przewód symetrycznej skrętki linii nadawczej dla superwysokiej szybkości transmisji (ang. Super Speed Transmitter)
Pomarańczowy
8
GND_DRAIN
Przewód masy sygnałowej
Czarny
9
MicB_SSRX-
Przewód symetrycznej skrętki linii odbiorczej dla superwysokiej szybkości transmisji (ang. Super Speed Receiver)
Niebieski
10
MicB_SSRX+
Przewód symetrycznej skrętki linii odbiorczej dla superwysokiej szybkości transmisji (ang. Super Speed Receiver)
Żółty
Shell
Shield
Ekran
Tabela 1.9. Zestawienie podstawowych typów wtyczek i gniazd standardów USB pod względem możliwości wzajemnej współpracy A :typ A B: typ B AB: typ AB M: Micro
Gniazdo
USB 2.0
A
Wtyczka USB 2.0 A
B
M-A
oe asn
M-B
B
M-A
M-B
X
X
X X
M-B
M-AB
A X
M-AB
B
M-B
X
B
A
USB 3.0
X X
X
X X
X X
X X
X
24
USB. Praktyczne programowanie z Windows API w C++
Ramki i mikroramki W systemach USB 2.0 ramka (ang. fram e) jest zdefiniowana jako czas pomiędzy dwo ma kolejnymi znacznikami SO F (ang. Start o f Frame). Początkiem ramki jest znacz nik SO F zawierający 11 bitów danych i 5 bitów cyklicznej kontroli nadmiarowej CRC (ang. Cyclic Redundancy Check), odpowiadającej za korekcję błędów i weryfikację poprawności danych. Dane są przesyłane w postaci ramek o czasie trwania 1 ms dla niskiej (LS) i pełnej (FS) szybkości transm isji lub w m ikroram kach o czasie trw ania 1/8 czasu trw ania ramki, tj. 125 ps dla wysokiej (HS) szybkości transmisji danych, tak jak obrazują to rysunki 1.25 i 1.26. Rysunek 1.25. Ramki i mikroramki w standardzie USB 2.0
|_______ 1 ms (ramka - niska i pełna szyb k o ś ć )^
j
□ □ □ □ □ □ □ □ 125
(m ikro)ram ka IM-1
SOF
,l is
(mikroramka - wysoka szybkość)
m ikro(ram ka) N
liO F
SOF
(m ikro)ram ka N+1
ISO F
SOF
IiO F
Rysunek 1.26. Szeregowa transmisja (mikro)ramek USB 2.0 W wersji USB 3.0 nie są używane znaczniki SOF, zatem nie występuje pojęcie m i kroramki. Transfer na magistrali USB 3.0 odbywa się w magistralowych przedziałach czasu (ang. bus interval) wyrażanych w jednostkach 125 ps i wyznaczanych przy superwysokiej szybkości transmisji danych (patrz rozdział 3., tabela 3.12). W celu utrzy mania kontroli nad połączeniami oraz ich synchronizacji host regularnie wysyła spe cjalne pakiety zawierające odpowiednio zdefiniowany znacznik czasu do wszystkich portów, które aktualnie nie przebywają w stanie niskiego poboru energii.
Transfer danych Transfer (transmisja) danych pomiędzy aplikacją użytkownika a zewnętrznym urządze niem USB odbywa się na zasadzie zapisu i (lub) odczytu informacji z buforów danych znajdujących się w urządzeniu. W systemie USB bufory zdefiniowane w urządzeniu są określane mianem punktów końcow ych EP (ang. endpoints). Za pośrednictwem określonego punktu końcowego użytkow nik otrzymuje dostęp do wybranej funkcjo nalności urządzenia. Każdy punkt końcowy można scharakteryzować, podając szereg jego parametrów, z których najważniejsze to: numer punktu końcowego, kierunek prze pływ u danych (IN — z punktu końcowego do komputera, O U T — z kom putera do punktu końcowego), rodzaj transferu danych i maksymalny rozm iar pakietu danych.
Rozdział 1. ♦ Standard USB
25
Każdy punkt realizuje transm isję danych tylko w jednym kierunku. W yjątkiem je st dwukierunkowy, tzw. zerowy punkt końcowy EP0, za pom ocą którego urządzenia są konfigurowane. Proces komunikacji bazuje na transmisji danych w (mikro)ramkach czasowych (125 ps lub 1 ms) lub 125-mikrosekundowych magistralowych przedziałach czasu, w których może być zdefiniowany jeden z czterech podstawow ych rodzajów transferu danych: masowy, przerwaniowy, izochroniczny i kontrolny, przy czym każdy z nich wymaga oddzielnych potoków komunikacyjnych. Komunikacyjnymi potokami strumieniowymi (ang. stream pipes) dane są przesyłane bajt po bajcie za pomocą trzech pierwszych typów transferów danych. Formaty danych są zależne od urządzenia. Poszczególne strumienie elementarne mają postać odpowied nio skonfigurowanych pakietów. Potokami strumieniowymi jest realizowany transfer jednokierunkow y. Potokami komunikatowymi (ang. message pipes) jest realizowany transfer kontrolny lub są przesyłane dane dla celów konfiguracyjnych urządzenia wykonawczego. Format rozkazów przesyłanych potokiem kom unikatow ym je st zdefiniowany w standardzie USB [15, 16]. Potokami komunikatowymi jest realizowany transfer dwukierunkowy. T ra n s fe r m asow y (ang. bulk transfer) je st w ykorzystywany w trakcie komunikacji z urządzeniami wymagającymi wymiany dużych porcji danych z szeroką tolerancją re gularności i czasu ich przekazywania. Transfer masowy jest realizowany z pełną (FS), w ysoką (HS) lub superwysoką (SS) szybkością transmisji poprzez potok masowy, je żeli inne transfery nie w ykorzystują przydzielonych im potoków. Kontrola błędów występujących w trakcie przekazu masowego polega na żądaniu ponownego przesła nia pakietu danych obarczonego błędem. Rozmiar pakietu danych (patrz tabela 1.10) je st zapisany w polu wMaxPacketSize struktury USB_ENDPOINT_DESCRIPTOR (patrz roz dział 3., tabela 3.12). W szystkie kolejne pakiety danych transferu masowego powinny mieć identyczną długość. W przypadku niedomiaru ostatni pakiet w transferze charak teryzuje się m niejszą liczbą bajtów danych. Tabela 1.10. Maksymalny rozmiar pakietu danych w standardach USB 1.x/2.0/3.0 Maksymalny rozmiar pakietu danych (bajty) Typ transferu LS
FS
HS
SS
Kontrolny
8
8, 16, 32, 64
64
512
Masowy
—
8, 16, 32, 64
512
1024
Przerwaniowy
<8
< 64
< 1024
< 1024
Izochroniczny
—
< 1023
< 1024
< 1024
W trybie z superwysoką szybkością transmisji (SS), podobnie ja k w USB 2.0, wszyst kie kolejne pakiety powinny mieć identyczną długość. W przypadku niedomiaru ostatni pakiet w transferze charakteryzuje się m niejszą liczbą bajtów danych. W jednostko wym magistralowym przedziale czasu (tj. w trakcie 125 ps) można przekazać od 1 do 16 pakietów z danymi.
26
USB. Praktyczne programowanie z Windows API w C++
W trybie SS jest dostępny protokół strumieniowy (ang. stream protocol), pozwalający na transportowanie identyfikowanego strum ienia pakietów z danymi w potoku ma sowym. Kompletne inform acje o strumieniu danych są przechowywane w strukturze USBD_STREAM_INFORMATION (Windows 8). t y p e d e f s t r u c t _USBD_STREAM_INFORMATION { U SBD_PIPE_HANDLE P ip e H a n d le ;
//d a ne z zakresu
ULONG
S tre a m I D ;
ULONG
M a x im u m T r a n s fe r S iz e ;
ULONG
P i p e F la g s ;
<0; 65535>
/ / o b e c n i e nieużywane
} USBD_STREAM_INFORMATION, *PUSBD_STREAM_INFORMATION;
Z perspektywy sterownika klienta strumienie reprezentują wiele logicznych punktów końcowych, z których każdy ma własny bufor danych oraz dokładnie taką samą cha rakterystykę. Każdy strum ień elementarny je st scharakteryzowany poprzez własny identyfikator StreamID, umożliwiający przypisanie mu określonego bufora danych. Strumień pakietów jest transportowany potokiem identyfikowanym przez PipeHandle. M aksym alny rozm iar danych, które sterownik klienta może umieścić w strumieniu, je st zapisany w polu MaximumTransferSize. N a rysunku 1.27 zobrazowano mechanizm transportowania strumienia pakietów z danymi do bufora hosta [16].
Rysunek 1.27. Idea strumieniowego transferu masowego Transmisja poprzez potoki masowe z wykorzystaniem protokołu strumieniowego umoż liwia zwiększenie liczby buforów hosta do wartości 216 (czyli 65 536) udostępnianych danemu punktowi końcowemu, tak ja k obrazowo pokazano to na rysunku 1.28. Rów nież wiele buforów danych może być skonfigurowanych w postaci strumieni transpor towanych do jednego punktu końcowego (zarówno IN, jak i OUT) [16]. Protokoły strumieniowe zostały opracowane, aby umożliwić transmisję bardzo dużych ilości danych między nadawcą i odbiorcą w czasie rzeczywistym. Dzięki temu odbiorca nie musi czekać na zakończenie transferu, żeby na przykład obejrzeć film lub odsłu chać muzykę. Odbiorca zaczyna je analizować ju ż po dotarciu do niego pierwszych pakietów z danymi. T ra n sfe r przerw aniow y (ang. interrupt transfer) jest wykorzystywany w trakcie ko munikacji z urządzeniami wymagającymi wymiany niewielkich porcji danych z dużą regularnością czasu ich przekazywania (np. urządzenia interaktywne klasy HID). Prze rwaniowy transfer danych polega na regularnym (np. co 1 ms) wysyłaniu zapytań do
Rozdział 1. ♦ Standard USB
27
Rysunek 1.28. Transmisja danych za pomocą strumieniowego transferu masowego urządzenia i ewentualnym oczekiwaniu na stosowne do zapytania odpowiedzi. Odstęp czasowy ponawiania kolejnych przekazów przerwaniowych (ang. period lub service interval) jest ustalany na podstawie inform acji zawartych w polu bInterval deskryptora punktu końcowego reprezentowanego przez strukturę USB_ENDPOINT_DESCRIPTOR. Rozm iar pakietu danych zapisany w polu wMaxPacketSize struktury USB_ENDPOINT_ DESCRIPTOR zależy od szybkości transm isji danych, tak ja k pokazano w tabeli 1.10. Wszystkie kolejne pakiety danych transferu przerwaniowego powinny mieć identycz n ą długość. W przypadku niedom iaru ostatni pakiet w transferze charakteryzuje się mniejszą liczbą bajtów danych. Przerwaniowy punkt końcowy funkcjonujący w trybie HS może obsłużyć w trakcie mikroramki maksymalnie trzy pakiety danych. T ra n sfe r izochroniczny (ang. isochronous transfer) je st wykorzystywany w trakcie jednokierunkowej komunikacji, w której wymagany jest regularny przepływ pakietów docierających do odbiornika w rów nych odstępach czasu i w tej samej kolejności, w jakiej zostały nadane (np. dane audio i wideo). W trakcie przekazu izochronicznego nie stosuje się kontroli błędów występujących podczas transmisji. Transfer izochro niczny je st wykorzystywany w komunikacji z urządzeniami realizującymi pełną (FS), w ysoką (HS) lub superwysoką (SS) szybkość transmisji danych. Rozmiar pakietu da nych zapisany w polu wMaxPacketSize struktury USB_ENDPOINT_DESCRIPTOR zależy od szybkości transmisji danych. Dla transferów prowadzonych z w ysoką częstotliwością pracy magistrali USB 2.0 deklarowane pole danych nie może przekraczać 1024 bajtów (tabela 1.10). Punkty końcowe obsługujące transfery izochroniczne prowadzone z w y soką szybkością m ogą korzystać z transmisji szerokopasmowych, obsługując w jednej mikroramce maksymalnie trzy pakiety z danymi (trzy transakcje). W urządzeniach wykorzystujących superw ysoką szybkość transm isji izochroniczny punkt końcowy może przyjąć m aksym alnie 48 pakietów z danymi o rozmiarze 1024 bajtów przesyłanych w określonym odstępie czasowym ponawiania kolejnych przeka zów. Jeżeli w trakcie tego przedziału czasu jest przesyłany jeden pakiet danych, jego rozmiar może się mieścić w granicach od 0 do 1024 bajtów. Dane na temat liczby pa kietów , które może obsłużyć izochroniczny punkt końcowy funkcjonujący w trybie z superwysoką szybkością przesyłu danych, są zapisane w polach bMaxBurst oraz Mult struktury USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR opisującej deskryptor punktu końcowego dla trybu SS.
28
USB. Praktyczne programowanie z Windows API w C++
T ran sfer kontrolny lub sterujący (ang. control transfer) jest obligatoryjnie realizowa ny przez każde urządzenie USB, niezależnie od podtrzymywanych przez nie szybkości transmisji danych, i jest adresowany do zerowego punktu końcowego (EP0) urządze nia. Za pom ocą transferu kontrolnego urządzenie jest konfigurowane i nadzorowane. Za pośrednictwem transferu kontrolnego z komputera macierzystego (hosta) do urzą dzenia wykonawczego USB są przesyłane żądania (lub wywoływane mutatory) typu SET (ustaw) i odbierane inform acje o konfiguracji lub o aktualnym stanie urządzenia, zwracane przez rozkazy lub akcesory typu GET (podaj). Transfer kontrolny dzieli się na trzy etapy: przekazywania rozkazu (tzw. etap ustawia nia), transmisji danych oraz przekazywania statusu (tzw. etap statusu). W trakcie prze kazywania rozkazu kierowanego do urządzeń HS transmitowany jest 8-bajtowy blok danych zawierający informacje o długości pola danych realizowanych na etapie prze kazywania danych. Dane przekazywane na etapie transmisji nie m ogą być dłuższe niż 8 bajtów dla trybu LS oraz 64 bajty dla trybów FS i HS, tak jak pokazano w tabeli 1.10. Maksymalny rozmiar pakietu danych, który może być odebrany lub wysłany na ma gistrale podczas operacji przekazu sterującego, jest zapisany w polu wMaxPacketSize struktury USB_ENDPOINT_DESCRIPTOR. Na etapie przekazywania statusu następuje wery fikacja w ykonania transferu kontrolnego. W trybie SS transfer kontrolny pomiędzy hostem a urządzeniem peryferyjnym jest realizowany poprzez transmisję danych o dłu gości 512 bajtów w 125-mikrosekundowym magistralowym przedziale czasu.
Pakiety USB 2.0 Typowe transakcje USB 2.0 m ogą być zbudowane z trzech rodzajów pakietów ele mentarnych. Pakiety te po uzupełnieniu o informacje um ożliwiające ich w zajem ną synchronizację stanowią kompletne transmitowane dane, tak ja k schematycznie poka zano to na rysunku 1.29. Rysunek 1.29. Główne elementy transferu danych w systemie USB 2.0
Wszystkie pakiety rozpoczynają się od pola SYNC służącego do zsynchronizowania nadajnika i odbiornika w trakcie transferu danych. Pole synchronizacji ma rozm iar 8 bitów w trybie LS i FS oraz 32 bitów w trybie HS. Ostatnie dwa bity pola synchro nizacji wskazują początek 8-bitowego pola identyfikatora pakietu PID (ang. Packet
Rozdział 1. ♦ Standard USB
29
Identifier). Pakiety elementarne dla celów transmisji danych są grupowane w pakiety wyższego poziomu identyfikowane właśnie za pom ocą PID. Dzięki posiadaniu uni kalnego identyfikatora możliwe je st rozróżnienie odpowiednich pakietów przenoszą cych dany strumień elementarny. W tabeli 1.11 zamieszczono opis identyfikatora PID. W artość identyfikatora pakietu faktycznie jest zapisana w czterech bardziej znaczących bitach, zaś pozostałe są jego uzupełnieniem. Tabela 1.11. Opis bitów identyfikatora pakietu USB 2.0 Typ
W artość PID
Znaczenie
Opis
Zapowiedź
1000b
SPLIT
Zapowiedź transakcji dzielonej w trybie HS
0100b
PING
Zapowiedź operacji kontroli dostępu
Specjalne Potwierdzenie
Zapowiedź
Dane
PRE
Pakiet preambuły w trybie LS
ERR
Błąd transakcji dzielonej HS
0010b
ACK
Akceptacja pakietu danych
1100b
1010b
NAK
Brak akceptacji pakietu; możliwa retransmisja
0110b
NYET
Brak gotowości do przyjęcia następnego pakietu danych dla operacji OUT w trybie HS
1110b
STALL
Transfer niemożliwy
0001b
OUT
Zapowiedź transferu danych w kierunku host ^ urządzenie. Urządzenie odpowiada, transmitując pakiety ACK, NAK, NYET lub STALL
1001b
IN
Zapowiedź transferu danych w kierunku urządzenie ^ host. Weryfikacja następuje poprzez pakiety NAK lub STALL. W odpowiedzi na wysłanie pakietu IN możliwa jest transmisja pakietu DATAx z potwierdzeniem ACK
0101b
SOF
Znacznik początku (mikro)ramki
1101b
SETUP
Zapowiedź transferu kontrolnego lub sterującego. Więcej informacji na temat formatu pakietu SETUP zamieszczono w rozdziale 6. (patrz listing 6.17 oraz tabele 6.5 i 6.6)
0011b
DATA0
Parzysty numer pakietu danych
1011b
DATA1
Nieparzysty numer pakietu danych
0111b
DATA2
Pakiet danych pierwszej z trzech izochronicznych transakcji typu IN w trakcie jednej mikroramki w trybie HS. Następnymi pakietami są DATA1 i DATA0
1111b
MDATA
Pakiet danych pierwszej z dwóch izochronicznych transakcji OUT w trakcie jednej mikroramki w trybie HS (następnym pakietem jest DATA1) lub jeden z dwóch pierwszych pakietów, jeżeli na mikroramkę przypadają trzy transakcje (następnym pakietem jest DATA2)
30
USB. Praktyczne programowanie z Windows API w C++
Pole AD D R (ang. address) określa adres urządzenia, do którego jest kierowany pakiet danych lub które je st źródłem pakietu. Pole jest wyspecyfikowane dla pakietów zapo wiedzi IN, OUT, SETUP, SPLIT oraz PING. Pole ENDP (ang. endpoint) ma rozmiar 4 bitów, co umożliwia jednoznaczne określe nie 16 punktów końcowych. Pole jest wyspecyfikowane dla pakietów zapowiedzi IN, OUT, SETUP oraz PING. Pole cyklicznej kontroli nadmiarowej CRC zabezpiecza integralność transmitowanych danych. Pięciobitowe pole CRC-5 je st wyspecyfikowane dla pakietów zapowiedzi IN, OUT, SETUP, SPLIT, PING oraz znacznika początku ramki SO F. Pole EO P (ang. E nd o f Packet) określa koniec transmitowanego pakietu. Pole je st wy specyfikowane dla pakietów zapowiedzi IN, OUT, SETUP oraz PING. P ak iety d an y ch (ang. data packets) są przeznaczone dla danych wysyłanych (zapi sywanych) do urządzenia lub odbieranych (odczytywanych) z urządzenia. N a rysunku 1.30 schematycznie zaprezentowano ogólny format pakietu danych USB.
Rysunek 1.30. Ogólny format pakietu danych używany dla PID: DATA0, DATA1, DATA2 oraz MDATA Pole robocze Dane pakietu danych składa się z maksymalnie 8 bajtów dla transm isji LS, 1023 bajtów (8184 bitów ) dla transm isji FS oraz 1024 bajtów (8192 bitów) dla transmisji w trybie HS. Pakiet danych zakończony jest 16-bitową sumą kontrolną. Pole robocze pakietu może nie zawierać danych, tzn. może być zerowej długości (ang. zero -length), niemniej zawsze powinno być zakończone 16-bitową sum ą kontrolną. Pakiety DATA0 oraz DATA1 są wykorzystywane w trybach LS oraz FS jako element kontroli transferu danych. W celu synchronizacji wymiany danych między nadajni kiem i odbiornikiem kolejne transakcje w ramach transferu danych są na przemian pa kietami typu DATA0 oraz D ATA1. Przekaz izochroniczny realizowany w systemie z w ysoką szybkością transferu danych posiada rozbudowany system kontrolny umożliwiający przeprowadzenie trzech trans akcji w trakcie jednej mikroramki, co pozwala na transfer z szybkością 192 Mb/s bez konieczności stosowania pakietu potwierdzenia. W trakcie wykonyw ania transakcji dla przekazów izochronicznych są wykorzystywane dodatkowe rodzaje pakietów da nych typu DATA2 oraz M DATA, tak ja k pokazano na rysunkach 1.31 oraz 1.32.
Rysunek 1.31. Transfer izochroniczny typu IN w systemie HS
Rozdział 1. ♦ Standard USB
31
Rysunek 1.32. Transfer izochroniczny typu OUT w systemie HS P ak iety zapow iedzi (ang. token packets) są elementem kontrolnym transakcji i infor m ują o jej rodzaju. Pakiety tego typu są przesyłane jedynie przez host, nigdy przez urządzenie peryferyjne. Transakcję odczytu danych z urządzenia rozpoczyna pakiet IN , który zawiera adres urządzenia ADDR, numer punktu końcowego END P, z które go należy pobrać (odczytać) dane wejściowe, oraz 5-bitową sumę kontrolną CRC-5. Pobrane dane są następnie przekazywane w pakiecie danych D ATA x, zawierającym pole danych i zakończonym polem CRC-16. Transakcje zapisu (wysłania) danych do urządzenia rozpoczyna pakiet OUT, który zawiera adres urządzenia AD D R, numer punktu końcowego ENDP, do którego należy zapisać (wysłać) dane wyjściowe, oraz pole CRC-5. W ysyłane dane są następnie przekazywane w pakiecie danych DATAx, zawierającym pole danych i zakończonym 16-bitową sumą kontrolną CRC-16. W obu przypadkach transakcje wejściowe typu IN oraz wyjściowe typu OUT są koń czone pakietem potwierdzenia A C K wysyłanym odpowiednio przez host lub urządze nie. N a rysunku 1.33 pokazano ogólny form at pakietu zapowiedzi USB 2.0. Transakcje kontrolne rozpoczyna pakiet SETUP, zawierający adres urządzenia ADDR, num er punktu końcowego ENDP, do którego należy przekazać odpowiednie polece nie (np. dane konfiguracyjne), oraz pole CRC-5. Pakiet danych transakcji kontrolnej zawiera 8-bitowy kod rozkazu wraz z określeniem typu odbiorcy.
Rysunek 1.33. Ogólnyformat pakietu zapowiedzi dla PID: SETUP, OUT, IN oraz PING Pakiet SOF jest znacznikiem początku ramki umożliwiającym jej rozpoznanie przez urządzenia FS. W trakcie transferów izochronicznych pakiet SO F jest wykorzystywa ny do inicjalizacji i synchronizacji transferu danych na początku jednej milisekundo wej ramki. Ogólny format tego pakietu je st pokazany na rysunku 1.34.
Rysunek 1.34. Budowa pakietu SOF Pakiet zapowiedzi transakcji dzielonej SPLIT może przybrać formę pakietu rozpoczę cia transakcji dzielonej SSP LIT (ang. Start SPLIT) — wówczas je st pierwszym pa kietem w ysyłanym w trakcie rozpoczęcia transakcji, lub formę pakietu zakończenia transakcji dzielonej C SP LIT (ang. Complete SPLIT) — wówczas jest pakietem w ysy łanym w trakcie kom pletow ania transakcji. Pakiety te wykorzystuje się w sytuacji,
32
USB. Praktyczne programowanie z Windows API w C++
gdy do huba (koncentratora) HS zostaje podłączone urządzenie LS lub FS. N a rysun ku 1.35 pokazano ogólną postać pakietu SPLIT, zaś w tabelach 1.12 i 1.13 zaprezen towano budowę poszczególnych jego odmian.
Rysunek 1.35. Ogólny format pakietu SPLIT Tabela 1.12. Opis pól pakietu SPLIT dla transferów kontrolnego, przerwaniowego, masowego oraz izochronicznego IN Pole
Znaczenie (m aska bitowa)
HUBADDR
Adres huba (koncentratora) jest zapisany na 7 bitach
SC
Bit identyfikujący rodzaj pakietu 0b — SSPLIT 1b — CSPLIT
PORT
Adres portu huba jest zapisany na 7 bitach
S
Bit identyfikujący tryb pracy (ang. Speed) 0b — FS (przekaz kontrolny, masowy, przerwaniowy, izochroniczny IN) 1b — LS (przekaz kontrolny lub przerwaniowy)
E
Bit nieużywany (0)
ET
Bity identyfikujące rodzaj punktu końcowego (ang. Endpoint Type) 00b — kontrolny 01b — izochroniczny IN 10b — masowy 11b — przerwaniowy
Tabela 1.13. Opis pól pakietu SPLIT dla transferu izochronicznego OUT Pole
Znaczenie (m aska bitowa)
HUBADDR
Adres huba (koncentratora) jest zapisany na 7 bitach
SC
Bit identyfikujący rodzaj pakietu 0b — SSPLIT 1b — CSPLIT
PORT
Adres portu huba jest zapisany na 7 bitach
S and E
00b — zapowiadane dane to jeden ze środkowych transmitowanych pakietów 01b — koniec danych 10b — początek danych 11b — zapowiadane dane są kompletną porcją przesyłanej informacji
ET
Bity identyfikujące rodzaj punktu końcowego (ang. Endpoint Type) 01b — izochroniczny OUT
Rozdział 1. ♦ Standard USB
33
P a k ie t p o tw ierd zen ia (ang. handshake packet) służy do potw ierdzania w ykonania transakcji przesyłu danych i oprócz pól SYNC i EOP zawiera jedynie bajt PID, tak jak pokazano na rysunku 1.36. Potwierdzenie może wystąpić w formie pozytywnej (ACK) lub negatywnej (NAK), lub może zawierać informacje o wystąpieniu błędu wewnętrz nego urządzenia (STALL). Podczas transferu przerwaniowego wystąpienie potw ier dzenia negatywnego N A K oznacza, że urządzenie nie dysponuje danymi, które mogą być przetransmitowane do hosta. W wersji USB 2.0 występuje dodatkowo potwierdze nie N YE T (ang. N ot Yet) przeznaczone dla urządzeń HS (patrz tabela 1.11). SYNC
PID
EOP
Rysunek 1.36. Format pakietu potwierdzenia dla PID: ACK, NAK, STALL oraz NYET P ak iet p ream b u ły (ang. pream ble packet) poprzedza pakiety wysyłane do urządzeń LS i nie jest zakończony znacznikiem końca pakietu EO P (ang. End o f Packet). Porty, do których są przyłączone urządzenia LS, zostają odblokowane tylko i wyłącznie w te dy, gdy host dokona detekcji pakietu preambuły.
Transakcje USB 2.0 Z definicji transakcją nazywamy zbiór operacji stanowiących pew ną całość, które po winny być wykonane w całości lub niewykonane w ogóle. Tak ja k schematycznie pokazano na rysunku 1.37, transakcje USB są uporządkowany mi sekwencjami operacji służących do wymiany informacji między hostem (kompute rem) i urządzeniem peryferyjnym. Szczegółowa postać transakcji zależy od rodzaju realizowanego transferu danych. Zapowiedź
Dane
< ------------------------
Potwierdzenie
------------------------► T ra n sa kcja
Rysunek 1.37. Ogólna postać trójfazowej transakcji USB 2.0 W ymaga się, aby pojedyncza transakcja zamknęła się w jednej ramce (lub mikroramce) — patrz rysunki 1.25 i 1.26. Podobnie jak typy transferów danych, transakcje USB można podzielić na kilka grup. T ra n s a k c je m asow e (ang. bulk transactions) są bardzo podobne do stosow anych w przekazach przerwaniowych, z tą różnicą, że nie m ają zagwarantowanego pasma transmisyjnego i wykonanie ich następuje w miarę jego udostępniania. Typowa ope racja transakcyjna typu OUT wykonywana na magistrali USB 2.0 w trakcie transferu masowego polega na przesłaniu kolejno: pakietu zapowiedzi OUT w kierunku od ho sta do urządzenia w ykonawczego (dokładniej jego wybranego punktu końcowego), pakietu z danymi (maksymalnie 1024 bajty dla trybu HS) typu D ATAx oraz pakietu
34
USB. Praktyczne programowanie z Windows API w C++
potwierdzenia A C K (w kierunku przeciwnym do przepływu danych), tak jak pokazano na rysunku 1.38.
© Host (PC)
0 R
c
D A Pole danych i 0/1
16
c R C 5
E N D P
A D D R
U T
U rządze nie USB HS
Rysunek 1.38. Trójfazowa transakcja masowa typu OUT w standardzie USB 2.0. Transakcja składa się z pakietu zapowiedzi (1), pakietu z danymi (2) oraz pakietu potwierdzenia (3). Pakiety z danymi są naprzemiennie pakietami typu DATA0 i DATA1 W standardzie USB 2.0 transakcje masowe typu IN są inicjowane przez host (kompu ter) poprzez wysłanie do punktu końcowego urządzenia pakietu zapowiedzi IN. Urzą dzenie wykonawcze transmituje do hosta żądane dane w postaci pakietu z danymi ty pu DATAx, których poprawne otrzymanie host potwierdza wysłaniem pakietu ACK, tak ja k schematycznie pokazano na rysunku 1.39.
□ Host (PC)
A T A 0/1
r. Pole danych
R C
16
C R C 5
E N D P
A D D R
I N
Urządzenie USB HS
Rysunek 1.39. Trójfazowa transakcja masowa typu IN w standardzie USB 2.0. Transakcja składa się z pakietu zapowiedzi (1), pakietu z danymi (2) oraz pakietu potwierdzenia (3). Pakiety danych są naprzemiennie pakietami typu DATA0 i DATA1 W arto pamiętać, że kolejność transferowanych pakietów jest uzależniona od typu po toku skojarzonego z danym punktem końcowym (kontrolny, przerwaniowy, masowy, izochroniczny). Niektóre punkty końcowe m ają potencjalne możliwości obsługiwania kilku typów transferu danych (dzięki tzw. alternatywnym ustaw ieniom interfejsów), jednak w ostatecznie skonfigurowanym wybranym punkcie końcowym obowiązuje tylko jed en z nich. Łącze USB 2.0 standardowo pracuje w trybie półdupleksowym (ang. half-duplex), w którym wymiana danych pomiędzy hostem a urządzeniem pery feryjnym następuje naprzemiennie — w danej chwili komunikacja może być przepro wadzana tylko w jednym kierunku. Asynchronicznie realizowany może być przekaz masowy i kontrolny, zaś synchronicznie — przerwaniowy oraz izochroniczny. Przed rozpoczęciem transakcji masowej typu O U T host może wysłać do punktu koń cowego urządzenia wykonawczego funkcjonującego w trybie HS pakiet PING. Pozy tyw na odpowiedź w formie pakietu A C K oznacza, że punkt końcowy je st skonfiguro wany do odbioru pakietu z danymi o maksymalnym rozmiarze, jaki może być zapisany w polu wMaxPacketSize struktury deskryptora punktu końcowego, tak jak pokazuje to rysunek 1.40. Jeżeli punkt końcowy urządzenia nie je st odpowiednio skonfigurowa ny, odpowiada pakietem NAK. W takiej sytuacji host powinien odczekać pewien prze dział czasu (wyrażony w mikroramkach), określony poprzez wartość wpisaną w polu
Rozdział 1. ♦ Standard USB
35
Rysunek 1.40. Pozytywnie przeprowadzona dwufazowa transakcja kontroli dostępu b In terv al deskryptora punktu końcowego, i ponowić operację kontroli dostępu PING, tak ja k zilustrowano to na rysunku 1.41. bInterval
©
A K
C R C 5
E N D P
A D D R
P 1 N G
------
C R C 5
1
E A P N D 1 D D N P R G
Urządzenie USB HS
© Rysunek 1.41. Pierwsza operacja kontroli dostępu zakończyła się niepowodzeniem. Hostponawia operację po upływie określonego czasu T ran sak cje przerw aniow e (ang. interrupt transactions) są elementami przerwanio wego transferu danych. Transfer przerwaniowy jest realizowany w warunkach wyso kiej regularności przekazywania danych, dlatego też transakcje tego typu powinny być wykonywane w sposób natychmiastowy, bez żadnej zwłoki. Konsekwencją tego faktu jest konieczność wcześniejszego przygotowania przez urządzenie danych do wysłania lub m iejsca na dane do zapisu. Prawidłowo w ykonana transakcja przerw aniow a jest kończona w zależności od stosowanego trybu transferu danych odpowiednim pakietem potwierdzenia lub odpowiednio sformatowanym pakietem danych, tak jak pokazano na rysunkach 1.42 oraz 1.43. W trakcie realizacji transakcji przerwaniowych sekwencja ponawiania kolejnych prze kazów (zarówno OUT, ja k i IN ) je st zawsze inicjow ana przez host (patrz opis pola bInterval w tabeli 3.12 prezentującej deskryptor punktu końcowego). Pakiety z da nymi są naprzemiennie pakietami typu DATA0 i DATA1.
Rysunek 1.42. Trójfazowe transakcje przerwaniowe typu OUT w standardzie USB 2.0. Dla określonego punktu końcowego transfery przerwaniowe składają się wyłącznie z transakcji zapisu (OUT) danych do urządzenia, zamykanych pakietem potwierdzenia wysyłanym przez urządzenie
36
USB. Praktyczne programowanie z Windows API w C++
Rysunek 1.43. Trójfazowe transakcje przerwaniowe typu IN w standardzie USB 2.0. Dla określonego punktu końcowego transfery przerwaniowe składają się z samych transakcji odczytu (IN) danych z urządzenia, zamykanych pakietem potwierdzenia wysyłanym przez host T ran sak cje izochroniczne (ang. isochronous transactions) wykonują się sekwencyj nie w określonym przedziale czasowym (patrz opis pola bInterval w tabeli 3.12 pre zentującej deskryptor punktu końcowego). Transakcje tego typu nie oczekują na po twierdzenia, tak ja k pokazano na rysunkach 1.44 oraz 1.45.
Rysunek 1.44. Dwufazowa transakcja izochroniczna typu IN w standardzie USB 2.0. Cechą charakterystyczną transakcji izochronicznej USB 2.0 jest brak pakietu potwierdzenia
Rysunek 1.45. Dwufazowa transakcja izochroniczna typu OUT w standardzie USB 2.0. Cechą charakterystyczną transakcji izochronicznej USB 2.0 jest brak pakietu potwierdzenia T ransakcje kontrolne (ang. control transactions) są elementami transferu kontrolnego (sterującego) i w ogólności przybierają formę trzech odrębnych transakcji: przekazy wania rozkazu (SETUP), przekazywania opcjonalnych2 danych (DATA OUT lub DATA IN) oraz przekazywania statusu wykonanych operacji (STA TU SIN lub STATUS OUT). Transfer kontrolny dla operacji wysyłania danych sterujących w kierunku urządzenia wykonawczego rozpoczyna się transakcją SETUP, która składa się z pakietu zapowie dzi SETUP, 8-bitowego pakietu danych typu DATA0 specyfikującego rozkaz dla urzą dzenia oraz pakietu potwierdzenia odsyłanego przez urządzenie w kierunku hosta. Pa kiet potwierdzenia nie zawiera pola danych. W przypadku wystąpienia błędu na etapie
2 Oznacza to, że transfer sterujący może funkcjonować bez etapu transmisji danych.
Rozdział 1. ♦ Standard USB
37
ustawiania w pakietach SETUP urządzenie nie wysyła pakietu potwierdzenia. W sytu acji, w której 8-bitowe pole danych w transakcji SETUP jest niewystarczające do prze kazania kompletnego rozkazu sterującego do urządzenia wykonawczego, rozpoczyna ny je st etap transm isji (zapisu) danych sterujących w postaci transakcji D ATA O U T. W pierwszej kolejności host transmituje w kierunku urządzenia pakiet zapowiedzi OUT, a następnie pakiet z danymi typu DATA1 (w przypadku większej ilości zapisywanych informacji pakiety z danymi są na przem ian pakietami typu DATA1/DATA0). Trans akcję zapisu danych zamyka pakiet potw ierdzenia A C K przesyłany przez urządzenie w kierunku hosta. W przypadku w ystąpienia błędu pakiet potwierdzenia w ykonania etapu zapisu danych sterujących może przybrać formę N AK lub STALL. Transfer kon trolny jest kończony etapem przekazywania statusu przez urządzenie w postaci pakietu typu DATA1 w odpowiedzi na pakiet zapowiedzi IN wysłany przez host. W większo ści przypadków pole danych pakietu typu DATA1 przekazywanego na etapie statusu je st zerowej długości (tzw. pakiet 0-Length DATA1). Transakcję STATUS IN kończy pakiet potwierdzenia A C K wysyłany przez host. W przypadku wystąpienia błędu pa kiet potw ierdzenia w ykonania etapu statusu może przybrać formę N A K lub STALL. Powyższe rozw ażania zostały zilustrowane na rysunku 1.46. Rysunek 1.47 obrazuje kolejne etapy transferu kontrolnego dla operacji odczytu danych konfiguracyjnych w y słanych przez urządzenie wykonawcze.
Rysunek 1.46. Kolejne etapy transferu kontrolnego w standardzie USB 2.0 dla operacji zapisu (wysłania) danych sterujących do urządzenia. Każdy etap składa się z transakcji trójfazowych
Rysunek 1.47. Kolejne etapy transferu kontrolnego w standardzie USB 2.0 dla operacji odczytu (odbioru) danych konfiguracyjnych wysłanych przez urządzenie. Każdy etap składa się z transakcji trójfazowych
38
USB. Praktyczne programowanie z Windows API w C++
T ra n sa k c je dzielone (ang. split transactions) składają się z dw óch części: SSPLIT — rozpoczęcia transakcji dzielonej, oraz CSPLIT — zakończenia transakcji dzielonej. Pomiędzy nimi m ogą być wykonywane inne transakcje z wysoką szybkością transferu danych. Transakcje SPLIT wykorzystuje się w sytuacji, gdy do huba HS zostaje pod łączone urządzenie LS lub FS, tak jak w sposób uproszczony pokazano to na rysunkach 1.48 i 1.49.
Rysunek 1.48. Przykład transakcji dzielonej w trakcie przerwaniowego transferu typu IN
Rysunek 1.49. Przykład transakcji dzielonej w trakcie przerwaniowego transferu typu OUT
Pakiety w trybie Super Speed Transfer danych w standardzie USB 3.0 jest realizowany poprzez transakcje, w skład których wchodzą cztery podstawowe typy pakietów: pakiety zarządzania łączem LM P (ang. Link Management Packets), pakiety transakcji TP (ang. Transaction Packets), pa kiety danych DP (ang. Data Packets) oraz pakiety izochroniczne ITP (ang. Isochronous Timestamp Packets) zawierające informację odniesienia czasowego (ang. time stamp) dla synchronizacji nadajnika (hosta) i odbiornika. P ak iety za rz ą d za n ia łączem LM P służą do bezpośredniego zarządzania łączem da nych pomiędzy wybranymi portami transmisyjnymi. P ak iety tra n sa k c ji TP wykorzystuje się do kontroli przepływu danych, konfiguracji oraz utrzym ania połączeń pom iędzy punktam i końcowym i urządzeń. Pakiety te nie zawierają pola danych. P ak iety d an y ch D P służą do bezpośredniej wymiany inform acji pomiędzy hostem a urządzeniem w ykonaw czym (w obu kierunkach). Host używa pakietu danych, aby przesłać informacje do urządzenia wykonawczego. Urządzenie peryferyjne wykorzy stuje pakiet danych, aby przekazać żądane informacje zwrotne do hosta w odpowiedzi na odebrany pakiet A C K TP. Pakiety danych składają się z dwóch części: nagłówka D PH (ang. Data Packet Header) oraz pola danych DPP (ang. Data Packet Payload) zawie-
Rozdział 1. ♦ Standard USB
39
rającego blok danych D ata zabezpieczony 32-bitową sumą kontrolną (CRC-32) uży w aną w celu zachowania integralności danych. Pakiet DP może zawierać blok danych o zerowej długości, niemniej powinien on być zawsze zabezpieczony polem CRC-32. Pakiety ITP umożliwiają izochroniczną transmisję multiemisyjną ze stałą częstotliwo ścią przepływu danych, tzn. transmisję, w której host może w sposób skorelowany cza sowo komunikować się z grupą punktów końcowych (tzw. połączenia jeden-do-wielu). Pakiety ITP nie zawierają adresów punktów końcowych ani informacji trasujących. Są inicjowane przez host i przesyłane za pośrednictw em koncentratorów do aktywnych urządzeń USB aktualnie pracujących z superw ysoką szybkością transferu danych. Pakiety ITP są wykorzystywane do wysyłania znacznika czasu w celu synchronizacji połączeń oraz utrzymania nadzoru hosta nad połączeniami izochronicznymi z urządze niami SS. Pakiety izochroniczne są transmitowane co 125 |rs. Urządzenie wykonaw cze nie udziela odpowiedzi na otrzymany pakiet ITP. W standardzie USB 3.0 pakiety ITP zastępują pakiety SO F. Urządzenia USB 3.0 m ogą pozostawać w czterech dyskretnych stanach (poza główny mi, które zostały omówione w rozdziale 5.). W stanie U0 pozostają urządzenia w trakcie realizacji aktywnego połączenia z nom inalną szybkością transferu danych (5 Gb/s), w stanie U1 urządzenie wyłącza moduły nadawczy i odbiorczy, w stanie U2 zegar taktują cy staje się nieaktywny, zaś w stanie U3 urządzenie przełącza się w stan wstrzymania (ang. suspend mode). Gdy wszystkie urządzenia zewnętrzne znajdą się w trybie niskie go poboru energii, również komputer macierzysty (host) zamyka łącze odbioru danych. N a rysunkach 1.50 i 1.51 oraz 1.53 - 1.60 pokazano odpowiednio ogólne formaty pa kietów LMP, TP, D P oraz ITP. W tabelach 1.14 - 1.17 zaprezentowano opis pól skła dowych poszczególnych pakietów.
Pakiety LMP
Rysunek 1.50. Ogólnyformat pakietu zarządzania łączem LMP [16]
Pakiety TP 31 30 29 28 27 26 25 24 23 22 21 2019 18 17 1615 14 13 12 11 10 9 Device Address Reserved
Seq Num
Reserved PP
7 6 5 4 3 2 1 0
Route String
Reserved
NumP
HE
Rsvd
Type Ept Num
D rty Rsvd
Stream 1D/Reserved
Link Control Word
Rysunek 1.51. Ogólny format pakietu transakcji ACK TP [16]
CRC 16
DWORD 0
Sub Type DWORD 2 DWORD 3
40
USB. Praktyczne programowanie z Windows API w C++
Rysunek 1.52. Ogólna koncepcja emisji pojedynczej, polegająca na implementacji mechanizmów trasowania pakietów na przykładzie 5-poziomowej fizycznej topologii gwiazdy (ale logicznej topologii magistrali) 4-portowych koncentratorów USB 3.0 [16]. Skróty US i DS oznaczają odpowiednio port górny (ang. upstream port) oraz porty dolne (ang. downstream port) huba
Rysunek 1.53. Ogólny format pakietu NRDY TP [16]
Rysunek 1.54. Ogólny format pakietu ERDY TP [16]
Rozdział 1. ♦ Standard USB
41
Rysunek 1.55. Ogólnyformat pakietu STATUS TP [16] Tabela 1.14. Zawartość pól pakietu LMP Pole
Znaczenie
SubType Specific
Zależnie od pola podtypu (SubType) pakietu LMP. Szczegółowe dane są dostępne w dokumencie [16]
SubType
0000b — zarezerwowane 0001b — (ang. Set Link Function) pole jest używane do konfigurowania funkcjonalności urządzenia pozostającego w stanie aktywnego połączenia w trybie z superwysoką szybkością transferu danych 0010b — (ang. U2 Inactivity Timeout) limit czasu bezczynności w stanie U2 0011b — (ang. Vendor Device Test) test urządzenia 0100b — (ang. Port Capability) parametry transmisyjne portu 0101b — (ang. Port Configuration) parametry konfiguracyjne portu 0110b — (ang. Port Configuration Response) odpowiedź konfiguracyjna portu od 0111b do 1111b — zarezerwowane
Type
00000b — Link Management Packet
Link Control Word
Najważniejszymi zadaniami warstwy łącza danych jest kontrola przepływu danych oraz kontrola błędów. Dwubajtowe słowo kontrolne pakietu LMP składa się z 11 bitów definiujących poziom kontroli przepływu danych oraz 5-bitowego pola cyklicznej kontroli nadmiarowej CRC-5, zabezpieczającego integralność transmitowanych danych. Szczegółowy format tego pola jest opisany w dokumencie [16]
CRC-16
16-bitowa cykliczna kontrola nadmiarowa
Tabela 1.15. Zawartość pól pakietów TP Pole
Znaczenie
Device Address
Adres urządzenia docelowego. Jest ustalany przez host w trakcie procesu enumeracji (wyliczania) urządzeń dołączanych do magistrali USB (patrz rozdział 5.)
Route String
Pole zarezerwowane do użycia przez koncentratory (huby) — zawiera informacje na temat trasowania pakietów. Opisuje sposób wyznaczenia trasy i wysyłania nią pakietów do wybranych urządzeń przyłączonych do określonych portów huba (patrz rysunek 1.52)
42
USB. Praktyczne programowanie z Windows API w C++
Tabela 1.15. Zawartość pól pakietów TP — ciąg dalszy Pole
Znaczenie
SubType
0000b — zarezerwowane 0001b — ACK Dla punktów końcowych EP IN pakiet jest wysyłany przez host, co sygnalizuje żądanie danych od urządzenia. Następny w kolejności wysłany przez host pakiet ACK TP oznacza potwierdzenie odbioru danych (potwierdzenie wykonania transakcji). Dla punktów końcowych EP OUT pakiet ACK TP jest wysyłany przez urządzenie jako potwierdzenie otrzymania od hosta pakietu danych oraz jako informacja dla hosta o liczbie buforów danych dostępnych dla odbioru kolejnych porcji informacji. 0010b — NRDY Pakiet informujący o braku gotowości do transferu danych. Wysyłając pakiet NRDY TP, nieizochroniczny punkt końcowy EP OUT urządzenia informuje host o braku możliwości umieszczenia transmitowanego pakietu danych w buforze odbiorczym. Wysyłając pakiet NRDY TP, nieizochroniczny punkt końcowy EP IN urządzenia sygnalizuje brak możliwości przesłania pakietu danych DP w odpowiedzi na wysłany przez host pakiet ACK TP. Format pakietu NRDY TP pokazano na rysunku 1.53. 0011b — ERDY Pakiet wysyłany przez urządzenie sygnalizujący gotowość do transmisji danych przez nieizochroniczny punkt końcowy. Format pakietu ERDY TP pokazano na rysunku 1.54. 0100b — STATUS Pakiet wysyłany przez host, informujący punkt końcowy o zainicjowaniu etapu statusu w trakcie transferu kontrolnego. W odpowiedzi na pakiet STATUS TP punkt końcowy przesyła pakiet ACK TP zawierający informacje konfiguracyjne. Format pakietu STa Tu S TP pokazano na rysunku 1.55. 0101b — STALL Pakiet informujący o blokadzie punktu końcowego lub o błędach w transferze kontrolnym. Pakiet jest wysyłany przez punkt końcowy. Format pakietu STALL TP pokazano na rysunku 1.56. 0110b — DEV_NOTIFICATION Pakiet przesyłany przez urządzenie, informujący host o dokonanej zmianie konfiguracji lub interfejsu urządzenia. Szczegółowy opis pól tego pakietu jest dostępny w dokumentacji [16]. 0111b — PING Pakiet jest wysyłany przez host. Zawiera zapowiedź operacji kontroli dostępu. Format pakietu PING TP pokazano na rysunku 1.57. 1000b — PINGRESPONSE Pakiet odpowiedzi urządzenia. Format pakietu PING RESPONSE TP pokazano na rysunku 1.58. od 1001b do 1111b — zarezerwowane
Rozdział 1. ♦ Standard USB
43
Tabela 1.15. Zawartość pól pakietów TP — ciąg dalszy Pole
Znaczenie
Type
00100b — Transaction Packet
Reserved (Rsvd)
Pole zarezerwowane
SeqNum
(ang. Sequence Number) numer porządkowy oczekiwanego pakietu z danymi DP. Pole cyklicznie akceptuje wartości z przedziału <0; 31>. Numerem porządkowym pakietu występującym po liczbie 31 jest SeqNum=0
NumP
(ang. Number o f Packets) pole jest wykorzystywane do określenia akceptowanych przez urządzenie odbiorcze liczby buforów dla pakietów z danymi. W każdym skonfigurowanym buforze można umieścić jeden pakiet z danymi. Wartość zapisana w polu NumP nie powinna przekraczać wartości przechowywanej w polu bMaxBurst struktury USB SUPERSPEED ENDPOINT COMPANION DESCRIPTOR (patrz rozdział 3., tabela 3.13)
HE
(ang. Host Error) bit jest używany wówczas, gdy pakiet ACK TP jest przesyłany w kierunku od hosta do urządzenia. Kiedy host ustali bit HE, bit rty również powinien być sygnalizowany, ale tylko w przypadku transferu nieizochronicznego
EptNum
(ang. Endpoint Number) pole identyfikuje punkt końcowy w obrębie urządzenia, który jest źródłem lub adresatem pakietu TP. Można skonfigurować maksymalnie 15 punktów końcowych typu IN oraz 15 typu OUT
D
(ang. Direction) bit określający kierunek transferu danych 0b — host ^ urządzenie (OUT) 1b — urządzenie ^ host (IN)
rty
(ang. Retry Data Packet) bit używany do sygnalizowania sytuacji, w których host lub urządzenie nie odebrały żądanego pakietu danych lub odebrany pakiet jest niekompletny. Urządzenie nadawcze powinno retransmitować co najmniej jeden pakiet danych, począwszy od numeru SeqNum identyfikującego jego pozycję w serii pakietów
PP
(ang. Packets Pending) bit ustalany przez host. Sygnalizuje pakiet pochodzący od punktu końcowego identyfikowanego wartościami zapisanymi w polach EptNum oraz D
Stream ID
(ang. Stream ID) identyfikator strumienia danych z zakresu <1; 65535>. Wartość 0 oznacza, że punkt końcowy nie obsługuje strumieniowego transferu masowego. Kiedy urządzenie dysponuje danymi z właściwego strumienia, transmituje w kierunku hosta pakiet ERDY TP z określonym identyfikatorem Stream ID. Host odpowiada pakietem ACK TP. Po otrzymaniu odpowiednio skonfigurowanego pakietu ACK TP urządzenie transmituje w kierunku hosta strumień pakietów z danymi DP wraz z aktualnym identyfikatorem Stream ID. Sterownik hosta wykorzystuje ten identyfikator do umieszczenia przychodzących danych w buforze odpowiedniego punktu końcowego (patrz rysunek 1.27)
Link Control Word
Dwubajtowe słowo kontrolne pakietu TP składa się z 11 bitów definiujących poziom kontroli przepływu danych oraz 5-bitowego pola cyklicznej kontroli nadmiarowej CRC-5, zabezpieczającego integralność transmitowanych danych
CRC-16
16-bitowa cykliczna kontrola nadmiarowa
44
USB. Praktyczne programowanie z Windows API w C++
Rysunek 1.56. Ogólnyformat pakietu STALL TP [16]
Rysunek 1.57. Ogólny format pakietu PING TP [16]
Rysunek 1.58. Ogólny format pakietu PINGRESPONSE TP [16]
Pakiety DP
Rysunek 1.59. Ogólnyformat pakietu danych DP [16]
Rozdział 1. ♦ Standard USB
45
Tabela 1.16. Zawartość pakietu DP Pole
Znaczenie
Device Address
Adres urządzenia docelowego. Jest ustalany przez host w trakcie procesu wyliczania urządzeń dołączanych do magistrali USB (patrz rozdział 5.)
Route String / Reserved
Pole zarezerwowane do użycia przez koncentratory (huby) — zawiera informacje na temat trasowania pakietów. Opisuje sposób wyznaczenia trasy i wysyłania nią pakietów do wybranych urządzeń przyłączonych do określonych portów huba (patrz rysunek 1.52)
Reserved/R/Rsvd
Zarezerwowane
Data Length
Rozmiar (w bajtach) pakietu z danymi DPP z wyłączeniem pola CRC-32
EOB/LPF
(ang. End of Burst/Last Packet Flag) dla nieizochronicznych punktów końcowych jednobitowe pole sygnalizuje wartość EOB — koniec serii pakietów (bit w wysokim stanie logicznym). Kiedy urządzenie jest gotowe do kontynuacji transferu, powinno wysłać do hosta pakiet ERDY TP. Dla izochronicznych punktów końcowych bit LPF w wysokim stanie logicznym jest znacznikiem ostatniego pakietu w serii pakietów przesyłanych w wyznaczonym odstępie czasowym ponawiania kolejnych przekazów
SeqNum
(ang. Sequence Number) numer porządkowy oczekiwanego pakietu z danymi. Pole cyklicznie akceptuje wartości z przedziału <0; 31>. Numerem porządkowym pakietu występującym po liczbie 31 jest SeqNum=0
D
(ang. Direction) bit określający kierunek transferu danych 0b — host ^ urządzenie (OUT) 1b — urządzenie ^ host (IN)
EptNum
(ang. Endpoint Number) pole identyfikuje punkt końcowy w obrębie urządzenia, który jest źródłem lub adresatem pakietu DP. Można skonfigurować maksymalnie 15 punktów końcowych typu IN oraz 15 typu OUT
PP
(ang. Packets Pending) bit ustalany przez host. Sygnalizuje pakiet pochodzący od punktu końcowego identyfikowanego wartościami zapisanymi w polach EptNum oraz D
S
(ang. Setup) pole ustalane przez host w celu identyfikacji, że dany DP jest pakietem SETUP zawierającym żądanie dostępu do informacji konfiguracyjnych i sterujących urządzenia
Type
01000b — Data Packet Header
Stream ID
(ang. Stream ID) identyfikator strumienia danych używany w przypadku strumieniowego transferu masowego
Link Control Word
Dwubajtowe słowo kontrolne pakietu DP składające się z 11 bitów definiujących poziom kontroli przepływu danych oraz 5-bitowego pola cyklicznej kontroli nadmiarowej CRC-5, zabezpieczającego integralność transmitowanych danych
CRC-32
32-bitowa cykliczna kontrola nadmiarowa
CRC-16
16-bitowa cykliczna kontrola nadmiarowa
46
USB. Praktyczne programowanie z Windows API w C++
Pakiety ITP
Rysunek 1.60. Ogólny format pakietu multiemisyjnego ITP [16] Tabela 1.17. Zawartość pakietu ITP Pole
Znaczenie
Isochronous Timestamp
Wartość odniesienia czasowego identyfikującego częstotliwość przepływu danych w potoku izochronicznym. Dokładny opis formatu pola jest dostępny w dokumencie [16]
Bus Interval Adjustment Control
Pole Bus Interval Adjustment Control zawiera adres urządzenia kontrolującego częstotliwość przepływu danych w systemie. Urządzenie USB 3.0 może wysłać do hosta komunikat Bus_Interval_Adjustment_Message w pakiecie DEVNOTIFICATION TP w celu skorygowania wartości 125 ps z dokładnością do ±13,333 ps. W trakcie resetowania, przyłączania lub odłączania urządzenia host wyzeruje wartość tego pola
Reserved
Zarezerwowane
Link Control Word
Dwubajtowe słowo kontrolne pakietu ITP, składające się z 11 bitów definiujących poziom kontroli przepływu danych oraz 5-bitowego pola cyklicznej kontroli nadmiarowej CRC-5, zabezpieczającego integralność transmitowanych danych
CRC-16
16-bitowa cykliczna kontrola nadmiarowa
Operacje transakcyjne USB 3.0 W USB 3.0 wprow adzono kom unikację w pełnym trybie dupleksow ym (ang. fu ll duplex), która umożliwia jednoczesną transmisję danych w dwóch kierunkach. Dzięki rozdzielonym liniom w ysyłania i odbierania SST X +/- oraz SSR X +/- (każda z linii odpow iada za transfer jednostronny) urządzenie zewnętrzne nie musi oczekiwać na przydział magistrali od hosta (kom putera m acierzystego) — zarówno urządzenie, jak i host są zdolne do jednoczesnego przesyłu danych. Transakcje w systemach z superw ysoką szybkością transm isji danych m ogą przybierać formę operacji jedno-, dwulub trójfazowych. Dwufazowe tran sak cje m asowe składają się z pakietów TP oraz DP. Wynika stąd, że główne różnice pomiędzy trybami HS i SS w przypadku masowego transferu danych polegają na tym, iż w trybie Super Speed w trakcie transmisji z komputera (hosta) do punktu końcowego na w yjściu pakiet zapowiedzi OUT jest zastąpiony odpowiednio skonfigurowanym nagłówkiem DPH, który jest przesyłany razem z pakietem zawiera jącym pole danych DPP (rysunek 1.61). W trakcie transmisji w kierunku przeciwnym
Rozdział 1. ♦ Standard USB
47
Rysunek 1.61. Dwufazowe transakcje masowe typu OUT w trakcie transmisji pojedynczych pakietów w standardzie USB 3.0 host inicjuje żądanie przekazu danych od punktu końcowego urządzenia wykonawcze go, transm itując odpowiednio skonfigurowany pakiet transakcyjny TP AC K , tak jak pokazano na rysunku 1.62.
Rysunek 1.62. Dwufazowe transakcje masowe typu IN w trakcie transmisji pojedynczych pakietów w standardzie USB 3.0. Transakcje są kończone pakietem ACK TP z ustalonym polem NumP=0, co oznacza, że host nie ma przygotowanego bufora do odbioru kolejnego pakietu składającego się z nagłówka oraz pola danych Kiedy host je st gotowy do transmisji danych potokiem masowym, wysyła w kierunku urządzenia jeden lub więcej pakietów D P, z których każdy posiada odpowiednio skonfi gurowany nagłówek D P H zawierający adres urządzenia (Device Address), numer punk tu końcowego (EptNum), kierunek transferu (D) oraz num er porządkowy (SeqNum ) pakietu z danymi DPP. Pole SeqNum w nagłówku pierwszego pakietu DP jest zawsze inicjowane wartością 0. Pole SeqNum występujące w nagłówku drugiego pakietu z da nymi je st inicjowane wartością 1, trzeciego wartością 2 itd. — aż do wartości 31. N a stępny w kolejności, 32. pakiet z danymi ponownie zawiera pole SeqNum o wartości 0. W przypadku pomyślnego odbioru pakietu z danymi urządzenie odpowiada, transmi tując pakiet transakcyjny A C K TP z polem SeqNum wskazującym na następny ocze kiwany pakiet z danymi oraz z polem NumP określającym liczbę pakietów z danymi (liczbę buforów danych), które określony punkt końcowy może obsłużyć. Standardo wo skonfigurowane urządzenie wykonawcze powinno za pomocą pakietu A C K TP po twierdzić każdy otrzymany pakiet DP (składający się nagłówka D P H oraz pola danych D PP). Transfer masowy O UT jest zakończony, kiedy host przetransmituje w kierunku urządzenia kompletne dane (całkowicie opróżni swój bufor wyjściowy) lub kiedy pole danych ostatniego pakietu DP posiada rozm iar mniejszy, niż jest to zapisane w polu wMaxPacketSize deskryptora punktu końcowego (patrz rozdział 3., tabela 3.12). Kiedy następna porcja danych je st przygotowana do transmisji, host powinien wysłać kolej ny pakiet D P z polem SeqNum zainicjowanym kolejną w artością liczbową. K iedy host je st przygotowany na odbiór danych masowych, transmituje w kierunku urządzenia wykonawczego odpowiednio skonfigurowany pakiet A C K TP, zawierający informacje o numerze porządkowym SeqNum oczekiwanego pakietu DP oraz liczbie
48
USB. Praktyczne programowanie z Windows API w C++
pakietów NumP, które spodziewa się otrzymać. Tak ja k schematycznie pokazano na rysunku 1.62, host powinien wysłać pakiet A C K TP w odpowiedzi na każdy otrzyma ny pakiet z danymi. Niemniej urządzenie nie musi wstrzymywać transm isji danych w oczekiw aniu na kolejny pakiet potwierdzenia od hosta. Poprzedni z otrzymanych przez urządzenie pakietów A C K TP zawiera wystarczające informacje na temat liczby pakietów z danymi, które host spodziewa się otrzymać. Ustalenie przez urządzenie w nagłówku DPH pakietu z danymi bitu EOB (ang. End o f Burst) w w ysokim stanie logicznym oznacza, że urządzenie wykonawcze nie posiada więcej danych, które może przetransmitować w kierunku hosta (rysunek 1.63). Z ko lei transmisja pakietu A C K TP z polem NumP o wartości 0 oznacza, że host nie będzie oczekiwał następnej porcji danych od urządzenia.
Rysunek 1.63. Dwufazowa transakcja masowa typu IN w trakcie seryjnej transmisji pakietów w standardzie USB 3.0. Host inicjuje transakcję, wysyłając odpowiednio skonfigurowany pakiet ACK TP. Urządzenie odpowiada, wysyłając serię pakietów z danymi. Ostatni pakiet danych w serii jest transmitowany z bitem EOB w wysokim stanie logicznym ustalonym w nagłówku pakietu Standard USB 3.0 um ożliwia seryjną transmisję pakietów (ang. data bursting). Jest to mechanizm znacznie poprawiający w ydajność transferu poprzez eliminację koniecz ności każdorazow ego oczekiwania na pakiet potwierdzenia po wystąpieniu pakietu z danymi. Każdy punkt końcowy skonfigurowany w trybie SS ma m ożliwość okre ślenia maksymalnej liczby pakietów, które może obsłużyć (wysłać lub odebrać) bez konieczności oczekiwania na wystąpienie pakietu potwierdzenia. M aksymalna liczba pakietów z danymi, które punkt końcow y może obsłużyć bez oczekiwania na pakiet potwierdzenia (maksymalna liczba pakietów danych przypadająca na jed n ą transak cję), jest określona w polu bMaxBurst struktury USB_SUPERSPEED_ENDPOINT_COMPANION_ DESCRIPTOR (patrz rozdział 3., tabela 3.13). Poprzez pole NumP pakietu TP A C K host ma możliwość dynamicznej konfiguracji liczby pakietów w serii w trakcie transm i sji danych. T ran sak cje izochroniczne w standardzie USB 3.0 m ogą występować w formie dw u fazowych operacji typu IN składających się z pakietów TP oraz D P i (lub) operacji jednofazow ych typu O UT składających się wyłącznie z pakietów DP. Transakcje izochroniczne w szystkich typów w ykonują się sekwencyjnie w określonym przedziale czasow ym (patrz opis pola b In terv al w tabeli 3.12 prezentującej deskryptor punktu końcowego). Transakcje tego typu nie oczekują na potwierdzenia i charakteryzują się tym, że maksymalna liczba pakietów z danymi, które izochroniczny punkt końcowy jest w stanie obsłużyć w wyznaczonym odstępie czasowym ponawiania kolejnych prze kazów, wynosi 48, tak ja k opisuje to pole Mult struktury USB_SUPERSPEED_ENDPOINT_ COMPANION_DESCRIPTOR (patrz rozdział 3., tabela 3.13). Pole danych każdego pakietu D P (za w yjątkiem ostatniego) powinno posiadać rozmiar równy zapisanemu w polu
Rozdział 1. ♦ Standard USB
49
wMaxPacketSize deskryptora punktu końcowego (patrz rozdział 3., tabela 3.12). Ostatni pakiet z danymi jest oznaczany bitem LPF (ang. Last Packet Flag) w nagłówku D P H . N a rysunkach 1.64 - 1.67 zaprezentowano ideę izochronicznego przepływu informa cji realizowanego w systemie z superwysoką szybkością transferu danych.
Rysunek 1.64. Dwufazowa transakcja izochroniczna typu IN w standardzie USB 3.0. Host inicjuje transakcję, wysyłając odpowiednio skonfigurowany pakiet ACK TP. Ostatni pakiet z danymi jest transmitowany z bitem LPF w wysokim stanie logicznym ustalonym w nagłówku pakietu
Rysunek 1.65. Jednofazowa transakcja izochroniczna typu OUT w standardzie USB 3.0. Ostatni pakiet z danymi jest transmitowany z bitem LPF w wysokim stanie logicznym ustalonym w nagłówku pakietu
Rysunek 1.66. Seryjna transmisja pakietów potokiem izochronicznym w standardzie USB 3.0. Kolejne serie pakietów z danymi są poprzedzane odpowiednio skonfigurowanymi pakietami ACK TP wysyłanymi przez host. Transakcje powinny zamknąć się w przedziale czasu określonym przez wartość wpisaną w polu bInterval deskryptora punktu końcowego
Rysunek 1.67. Operacje kontroli dostępu do punktów końcowych EP1 oraz EP2 urządzenia
50
USB. Praktyczne programowanie z Windows API w C++
T ran sak cje k o n tro ln e są elem entami przekazu kontrolnego polegającego na wyko nywaniu operacji zapisu danych sterujących do urządzenia lub odczycie żądanych da nych konfiguracyjnych pochodzących od urządzenia wykonawczego. Kolejne etapy wysyłania (zapisu) danych sterujących do urządzenia rozpoczynają się od wykonania dwufazowej transakcji polegającej na transm isji przez host pakietu SETUP DP, któ rego odbiór urządzenie potwierdza w ysłaniem pakietu A C K TP. W sytuacji, w której pole danych w pakiecie SETUP DP jest niewystarczające do przekazania kompletnego rozkazu sterującego do urządzenia wykonawczego, rozpoczynany jest etap transmisji danych sterujących w postaci co najmniej jednej dwufazowej transakcji składającej się z odpowiednio skonfigurowanych pakietów z danymi D P, których otrzymanie urządze nie potwierdza wysłaniem pakietu A C K TP. Całość operacji zapisu danych sterujących do urządzenia jest kończona dwufazową transakcją przekazywania statusu, polegającą na transm isji przez host pakietu STATU S TP, którego odbiór urządzenie potw ierdza w ysłaniem odpowiednio skonfigurowanego pakietu A C K TP. W trakcie realizacji operacji odbioru danych konfiguracyjnych pochodzących od urzą dzenia wykonawczego etap odczytu danych konfiguracyjnych składa się z co najmniej jednej dwufazowej transakcji, polegającej na transmisji przez host jednego lub większej liczby odpowiednio skonfigurow anych pakietów A C K TP zawierających informacje o pakiecie lub pakietach z danymi konfiguracyjnymi, które host spodziewa się otrzymać. N a rysunkach 1.68 i 1.69 zaprezentowano kolejne etapy przekazu kontrolnego odpo wiednio dla operacji zapisu i odczytu realizowanych w systemie z superwysoką szyb kością transferu danych.
Rysunek 1.68. Kolejne etapy przekazu kontrolnego w standardzie USB 3.0 dla operacji zapisu (wysłania) danych sterujących do urządzenia. Kolejne etapy składają się z transakcji dwufazowych T ran sak cje p rzerw aniow e w ystępują w postaci transakcji dwufazowych typu OUT oraz trójfazowych typu IN . Są one zawsze regularnie inicjowane przez host na podsta wie wartości przechowywanej w polu bInterval deskryptora punktu końcowego (patrz rozdział 3., tabela 3.12). N a rysunkach 1.70 i 1.71 zaprezentowano kolejne etapy transferu przerwaniowego reali zowanego w systemie z superwysoką szybkością transferu danych. W połączeniach USB 2.0 stosuje się czasochłonną metodę próbkowania określaną ja ko polling. Komputer m acierzysty (host) regularnie odpytuje podłączone urządzenia
Rozdział 1. ♦ Standard USB
51
E ta p u s ta w ia n ia
E ta p s ta tu s u SeqN um =4 NumP=0 -
STATUS T P >
SeqNum=3 NumP=1
SeqNum=0 Num P.1
SeqNum=0
A C K T P — A C K T P - - - - A C K T P -- S E T U P DP
ACKTP
DP
DP
SeqN um=1 NumP=1
SeqNum=0
SeqN um=3
ACKTP
Etap transm isji danych konfiguracyjnych Potwierdzenie wykonania etapu ustawiania
Potwierdzenie wykonania etapu statusu
Rysunek 1.69. Kolejne etapy przekazu kontrolnego w standardzie USB 3.0 dla operacji odczytu (odbioru) danych konfiguracyjnych wysłanych przez urządzenie. Kolejne etapy składają się z transakcji dwufazowych. Dwufazowe transakcje na etapie transmisji danych konfiguracyjnych są kończone pakietem ACK TP z ustalonym polem NumP=0
Rysunek 1.70. Dwufazowe transakcje przerwaniowe typu OUT w standardzie USB 3.0. Host inicjuje transakcję, wysyłając pakiet DP. Sekwencja ponawiania kolejnych przekazów jest zawsze inicjowana przez host
Rysunek 1.71. Trójfazowe transakcje przerwaniowe typu IN w standardzie USB 3.0. Host inicjuje transakcję, wysyłając pakiet ACK TP. Sekwencja ponawiania kolejnych przekazów jest zawsze inicjowana przez host w równych odstępach czasowych zewnętrzne w celu ustalenia ich gotowości do transmisji danych. W standardzie USB 3.0 urządzenia peryferyjne m ogą zablokować próbkowanie łącza, zgłaszając swój stan jako N R D Y (ang. N ot Ready) z m ożliw ością przełączenia się w stan niskiego poboru
52
USB. Praktyczne programowanie z Windows API w C++
energii, tak ja k pokazano to schematycznie na rysunkach 1.72 oraz 1.73. Gdy urzą dzenie jest przygotowane do wysłania danych, zgłasza sygnał ERDY (ang. Endpoint Ready) oznaczający gotowość do transm isji w sposób przedstawiony schematycznie na rysunkach 1.74 oraz 1.75.
Rysunek 1.72. Blokowanie ponawiania kolejnych przekazów przerwaniowych typu IN. Host zaprzestaje komunikacji z punktem końcowym po otrzymaniu pakietu NRDY TP
Rysunek 1.73. Blokowanie ponawiania kolejnych przekazów przerwaniowych typu OUT. Host zaprzestaje komunikacji z punktem końcowym po otrzymaniu pakietu NRDY TP
Rysunek 1.74. Wznowienie przez host transakcji przerwaniowych typu IN po odebraniu pakietu ERDY TP
Rysunek 1.75. Wznowienie przez host transakcji przerwaniowych typu OUT po odebraniu pakietu ERDY TP
Rozdział 1. ♦ Standard USB
53
Porównanie standardów USB 2.0 oraz 3.0 W tabelach 1.18 oraz 1.19 zebrano informacje odzwierciedlające najważniejsze różnice pomiędzy sposobem realizacji transferu danych w omawianych w bieżącym rozdziale standardach USB. Tabela 1.18. Porównanie formatów transferów danych w standardach USB 2.0 oraz 3.0 Pakiety Typ transferu
Transakcje
Zastosowania USB 2.0
Kontrolny
SETUP
Zapowiedź Dane
DATA (IN/OUT) (opcjonalnie)
STATUS
Masowy
Przerwaniowy
Izochroniczny
DATA (IN/OUT)
DATA (IN/OUT)
DATA (IN/OUT)
USB 3.0
Dane SETUP DP
Potwierdzenie
Transakcje ACK TP
Zapowiedź
Transakcje ACK TP (dla operacji odczytu danych)
Dane
Dane DP
Potwierdzenie
Transakcje ACK TP (dla operacji zapisu danych)
Transfer danych dla celów kontrolnych lub konfiguracyjnych odbiornika
Zapowiedź Dane
Transakcje STATUS TP
Potwierdzenie
Transakcje ACK TP
Zapowiedź
Transakcje ACK TP (dla transferu IN)
Dane
Dane DP
Potwierdzenie
Transakcje ACK TP (dla transferu OUT)
Zapowiedź
Transakcje ACK TP (dla transferu IN)
Dane
Dane DP
Potwierdzenie
Transakcje ACK TP (dla transferu OUT)
Zapowiedź
Transakcje ACK TP (dla transferu IN)
Dane
Dane DP
Ciągły transfer dużych ilości danych z szeroką tolerancją regularności i czasu ich przekazywania pomiędzy nadajnikiem a odbiornikiem Wymiana niewielkich porcji danych pomiędzy nadajnikiem a odbiornikiem z wymaganą dużą regularnością czasu ich przekazywania Regularny przepływ pakietów danych docierających do odbiornika w równych odstępach czasu i w tej samej kolejności, w jakiej zostały nadane
54
USB. Praktyczne programowanie z Windows API w C++
Tabela 1.19. Główne cechy transmisji w standardach USB 3.0 oraz 2.0 USB 3.0
USB 2.0
Dupleksowy protokół transmisji danych
Półdupleksowy protokół transmisji danych
Powiadamianie asynchroniczne (NRDY, ERDY)
Regularne odpytywane urządzeń peryferyjnych (ang. polling)
Implementacja protokołu umożliwiającego strumieniowy przekaz masowy
Brak protokołu strumieniowego
Seryjna transmisja pakietów z danymi potokiem masowym
Konieczność oczekiwania na wystąpienie pakietu potwierdzenia
Transakcje z wykorzystaniem zalet emisji pojedynczej umożliwiającej przesyłanie pakietu do pojedynczego punktu końcowego (ang. unicast transactions)
Transakcje z wykorzystaniem emisji pakietów rozgłoszeniowych poprzedzonych pakietem zapowiedzi (ang. broadcast transactions)
Zastąpienie pakietu zapowiedzi odpowiednio skonfigurowanym pakietem potwierdzenia
Przesyłanie kolejnego pakietu zapowiedzi, danych oraz potwierdzenia
Rozdzielenie procedur detekcji błędów w trakcie transmisji, odzyskiwania danych oraz kontroli przepływu danych pomiędzy warstwami protokołu i łącza danych
Warstwa protokołu danych jest odpowiedzialna za detekcję błędów, odzyskiwanie danych oraz kontrolę ich przepływu
Wireless USB Bezprzewodowe, szerokopasmowe łącze USB o krótkim zasięgu wiąże dużą prędkość przesyłu danych i prostotę użycia USB 2.0/3.0 z technologią bezprzewodową. Łącze to jest często nazywane WUSB, jednak organizacja USB Implementers Forum (USB IF) promuje nazwę Certified W ireless USB (patrz rysunek 1.4) dla odróżnienia tej tech nologii od konkurencyjnych, działających na podobnych zasadach. Standard Certified W ireless USB jest oparty na technologii o szerokim widmie emisji UWB (ang. Ultra Wide Band)3, zdolnej do przesyłania danych z prędkością 480 Mbit/s na dystansie do 3 metrów lub 110 M bit/s na odległości do 10 m. Łącze jest przystosowane do pracy w zakresie częstotliwości od 3,1 do 10,6 GHz. W standardzie Certified W ireless USB 1.1 dane m ogą być transmitowane z szybkością do 1 Gbit/s na częstotliwości powyżej 6 GHz. N a rysunku 1.76 pokazano wygląd typowego adaptera CWUSB. Rysunek 1.76. Typowy adapter CWUSB
3 Technologia UWB bazuje na szybkim wysyłaniu krótkotrwałych impulsów. Czas trwania pojedynczego impulsu jest rzędu kilkudziesięciu pikosekund.
Rozdział 1. ♦ Standard USB
55
Technologia CWUSB może być stosowana w urządzeniach peryferyjnych, które obec nie są podłączane do standardowych portów USB, np. kontrolery gier, drukarki, apa raty i kamery cyfrowe, skanery, dyski twarde oraz dyski flash. N a rysunku 1.77 zaprezentowano przykład topologii systemu, którego elementy w y m ieniają dane za pośrednictw em bezprzew odowych łączy USB. Głównymi składni kami systemu są: adapter łącza przewodowego HW A (ang. H ost Wire Adapter), pod łączany za pośrednictw em standardowego złącza USB do portu hosta (komputera), adapter urządzeń przewodowych DW A (ang. Device Wire Adapter) oraz natywne urzą dzenie CWUSB. DW A realizuje w systemie funkcje huba, do którego są podłączane przewodowe urządzenia USB, kom unikujące się bezprzewodowo za pośrednictwem HW A z wybranym hostem. Wykorzystanie DW A jest dobrym sposobem na podłącze nie istniejących przewodowych urządzeń peryferyjnych w bliskiej odległości od kom putera bez konieczności używania skomplikowanego okablowania. Należy oczywiście zauważyć, iż przedstawiona topologia znacznie się upraszcza, w przypadku gdy ele mentami systemu są wyłącznie urządzenia natywne CWUSB. N a rysunku 1.78 zapre zentowano fizyczną realizację topologii z rysunku 1.77.
Rysunek 1.77. Przykładowa topologia systemu, którego elementy wymieniają informacje za pośrednictwem bezprzewodowego łącza USB B ezprzew odow e łącze USB realizuje w szystkie typy transferu danych oferow ane przez standardowy protokół USB. Istnieją jednak pewne różnice w sposobach realiza cji transmisji pakietów oraz mechanizmach uwierzytelniania urządzeń, nadzorowania poprawności transmisji danych oraz ich zabezpieczenia przed zewnętrznym nasłuchem. Dokładny opis standardu CWUSB wykracza poza ramy tematyczne niniejszego opra cowania. Zainteresowani Czytelnicy m ogą skorzystać z kompletnej specyfikacji stan dardu CWUSB zamieszczonej na stronie organizacji USB [18].
56
USB. Praktyczne programowanie z Windows API w C++
Podsumowanie W rozdziale tym zostały zaprezentowane podstawowe wiadomości dotyczące szerego wej transmisji danych w funkcjonujących obecnie standardach USB. Tematy te zostały
Rysunek 1.78. Jeden z możliwych sposobów fizycznej konfiguracji systemu, którego elementy wymieniają informacje za pośrednictwem bezprzewodowego łącza USB. Na rysunku nie uwzględniono natywnych urządzeń CWUSB potraktow ane w sposób zwięzły, ale zupełnie wystarczający do zrozum ienia zagad nień związanych z program ową kontrolą łącza USB. W dalszej części książki — wraz z wprowadzaniem konkretnych algorytmów mogących obsługiwać komunikację USB — omówione zagadnienia będą stopniowo uzupełniane. Bardziej szczegółowe infor macje dotyczące teoretycznych podstaw standardów USB oraz CWUSB Czytelnik mo że znaleźć w bogatej literaturze przedmiotu [1, 2, 13, 15, 16, 18].
Rozdział 2.
Informacje o urządzeniach U rządzeniem nazywamy jednostkę sprzętową, która powoduje zmiany stanu systemu. Każda kolejna zmiana stanu jest spowodowana zewnętrznym oddziaływaniem na system w wyniku wystąpienia określonego zdarzenia realizowanego za pośrednictwem okre ślonej funkcji. System operacyjny komunikuje się z urządzeniami, wykorzystując spe cjalistyczne oprogramowanie zwane sterow nikam i. Kiedy system operacyjny w ykry w a nowe urządzenie, rozpoznaje jego typ, znajduje pasujący sterownik i instaluje go. W procesie kontroli instalacji i konfiguracji urządzeń system operacyjny W indows używa albo identyfikatorów urządzenia występujących pod postacią odpowiednich łań cuchów znaków, albo klas instalacji urządzenia.
Identyfikatory urządzenia Gdy system wykrywa nowe urządzenie, wysyła do niego zapytanie, aby otrzymać łań cuch znaków, który je identyfikuje. Urządzenia m ogą mieć wiele tego rodzaju identy fikatorów przydzielonych przez producenta i um ieszczonych w pliku .inf, będącym częścią pakietu sterowników. W trakcie instalacji urządzenia system operacyjny od czytuje jego identyfikator, porównuje go z identyfikatorami zamieszczonymi w pakie tach sterowników, następnie wybiera najbardziej odpowiedni pakiet i na tej podstawie ostatecznie konfiguruje urządzenie. Identyfikatory urządzeń mogą występować pod postacią rozbudowanych łańcuchów znaków. Ze w zględu na szczegółowość zaw artych informacji m ogą odpowiadać do kładnie wybranemu modelowi urządzenia (identyfikatory sprzętu), ale m ogą też opisy wać grupę urządzeń (identyfikatory kompatybilności).
58
USB. Praktyczne programowanie z Windows API w C++
Identyfikatory sprzętu Tego rodzaju identyfikatory charakteryzują się najw yższą zgodnością między urzą dzeniem a pakietem sterowników. Pierwszy łańcuch znaków na liście identyfikatorów sprzętu (ang. hardware ID) jest właściwym identyfikatorem urządzenia, dokładnie opi sującym jego markę, model i wersję. Pozostałe identyfikatory sprzętu m ogą opisywać dane urządzenie w sposób mniej precyzyjny. Mechanizm ten pozwala systemowi W in dows na użycie sterownika dla innej wersji sprzętu, jeżeli sterownik dla danej wersji nie jest aktualnie dostępny.
Identyfikatory zgodności System operacyjny używa identyfikatorów kompatybilności lub zgodności (ang. com patible IDs), w sytuacji gdy nie może odszukać sterownika pasującego do odczyta nego identyfikatora wybranego urządzenia ani żadnego innego pasującego identyfi katora sprzętu. Jeżeli sterownik zostanie dopasowany jedynie na podstawie kryterium kom patybilności, zazwyczaj będą dostępne najbardziej podstawowe funkcje danego urządzenia.
Ocena i selekcja pakietów sterowników Kiedy system operacyjny rozpozna nowe urządzenie, wysyła do niego zapytanie i na podstaw ie otrzymanej odpowiedzi przystępuje do wyszukania najlepiej pasującego sterownika. W trakcie tego wyszukiwania przeglądane są różne pakiety sterowników, którym automatycznie przydzielana je st odpowiednia ranga w zależności od stopnia dopasowania odczytanego identyfikatora sprzętu lub kompatybilności. Ranga określa stopień zgodności sterownika z urządzeniem. Im niższa ranga, tym lepszy jest stopień zgodności. Ranga zerowa określa najlepszą m ożliwą zgodność sterownika z urządze niem. Lepszy zakres zgodności sterownika z odczytanym identyfikatorem urządzenia skutkuje niższą rangą w porów naniu ze stopniem zgodności otrzymanym na podsta wie innych identyfikatorów sprzętu. Podobnie zgodność z identyfikatorem sprzętu oznacza lepszą rangę w porównaniu ze zgodnością z którymś z identyfikatorów kom patybilności. System operacyjny zawsze dąży do tego, aby instalowany był sterownik z najniższą rangą.
Klasy instalacji urządzeń Szybkie upowszechnianie się aplikacji korzystających z portu USB zmotywowało or ganizację USB do sprecyzowania pewnego standardu dla urządzeń, które mają podobne charakterystyki, poprzez połączenie ich w grupy nazwane klasami. Tego rodzaju spe cyfikacja pozwala na tworzenie rodzajowych sterowników, nadających się do użytku
Rozdział 2. ♦ Informacje o urządzeniach
59
z różnymi urządzeniami tej samej klasy. Jedno urządzenie może należeć do kilku klas. Dzięki wprowadzeniu podziału urządzeń na klasy producent może przydzielić do urzą dzenia klasę instalacji w pakiecie sterowników urządzenia. Klasa instalacji grupuje urządzenia (a dokładniej interfejsy przez nie realizowane), które są instalowane i kon figurowane przez system operacyjny w ten sam sposób. Klasy urządzeń USB zazwyczaj są opisywane odrębnymi standardami zawierającymi szczegółowe informacje o atrybutach i sposobie uzyskiwania dostępu do interfejsów realizow anych przez daną klasę. Inform acje takie są zawarte w specyficznych dla każdej klasy deskryptorach, którymi posługują się sterowniki odpowiednich urządzeń. Klasa urządzenia definiuje też zestawy funkcji właściwych dla danej klasy urządzeń, formaty przekazywanych danych, instrukcje sterujące i informacje statusowe. W tabeli 2.1 zaprezentowano ogólny opis dla obecnie istniejących klas urządzeń USB.
Menedżer urządzeń M enedżer urządzeń udostępnia informacje na tem at zainstalowanych urządzeń oraz wygodny graficzny interfejs ułatwiający wykonywanie operacji, służący do: ♦ określania, czy sprzęt zainstalowany na komputerze działa prawidłowo, ♦ zmieniania konfiguracji sprzętu, ♦ identyfikowania sterowników urządzeń, które zostały załadowane dla poszczególnych urządzeń, a także uzyskiwania informacji dotyczących każdego sterownika, ♦ zmieniania zaawansowanych ustawień i właściwości urządzeń, ♦ instalowania zaktualizowanych sterowników urządzeń, ♦ wyłączania, włączania i odinstalowywania urządzeń, ♦ przywracania poprzedniej wersji sterowników. N a rysunku 2.1 pokazano wygląd okna M enedżer urządzeń, które jest dostępne w sys temie Windows 7. M enu W idok/Urządzenia według typów grupuje urządzenia zgodnie z ich funkcjami w systemie. Wybierając opcję Właściwości (rysunki 2.2 i 2.3), użytkow nik m a m ożliwość zapo znania się z typem, lokalizacją i num erem urządzenia, jego producentem, numerem funkcji urządzenia w systemie, identyfikatorem GUID klasy urządzeń, szczegółami pliku sterownika i wieloma innymi danymi.
60
USB. Praktyczne programowanie z Windows API w C++
Tabela 2.1. Klasy urządzeń Klasa
Użycie
Opis
Przykłady
00h
Urządzenie lub interfejs
Klasa bazowa
Kod klasy bazowej jest przechowywany w deskryptorze interfejsu (patrz rozdział 3., tabela 3.16)
01h
Interfejs
Urządzenia audio
Klasa grupująca urządzenia audio definiuje standardowe mechanizmy kontroli i przesyłania strumieni danych reprezentujących zakodowany cyfrowo dźwięk, np. głośniki, mikrofony, karty dźwiękowe
02h
Urządzenie oraz interfejs
Klasa urządzeń komunikacyjnych USB Communications Device Class (USB CDC)
Modemy, adaptery Wi-Fi, adaptery Ethernet. Klasa może być też stosowana dla urządzeń przemysłowych CNC (ang. Computerized Numerical Control), takich jak różnego rodzaju obrabiarki numeryczne, wykorzystujących standardowy port szeregowy. Za pomocą adapterów można podłączyć urządzenie zaopatrzone w standardowy port szeregowy do portu USB komputera sterującego pracą urządzenia CNC
03h
Interfejs
Urządzenia HID
Urządzenia interfejsu użytkownika. Klasa opisuje grupę urządzeń peryferyjnych obsługiwanych bezpośrednio przez użytkownika i służących do wprowadzania różnego typu danych do komputera (klawiatura, mysz, joystick, urządzenia pomiarowe). Niektóre urządzenia HID umożliwiają ponadto obustronną wymianę danych z aplikacją nadzorującą ich pracę. Urządzenia HID komunikują się z komputerem macierzystym (hostem) poprzez struktury danych, zwane raportami, z wykorzystaniem kontrolnych lub przerwaniowych transferów danych. Klasa HID jest szczególną grupą urządzeń, które mogą być samodzielnie programowane z poziomu API systemu operacyjnego. Z tego względu coraz większa liczba producentów urządzeń do tej pory nieuznawanych za standardowe udostępnia sprzęt (np. pomiarowy lub diagnostyczny) zgodny z klasą HID
Rozdział 2. ♦ Informacje o urządzeniach
61
Tabela 2.1. Klasy urządzeń — ciąg dalszy Klasa
Użycie
Opis
Przykłady
05h
Interfejs
Fizyczny interfejs urządzeń PID (ang. USB Physical Interface Device)
Klasa definiuje standardy konfiguracji oraz struktury fizycznego interfejsu urządzeń i może być traktowana jako rozszerzenie klasy HID dla urządzeń, z którymi użytkownik może się komunikować manualnie w czasie rzeczywistym
06h
Interfejs
Urządzenia służące do obrazowania (ang. imaging)
Skanery, kamery internetowe
07h
Interfejs
Urządzenia drukujące
Drukarki laserowe i atramentowe, komputerowo sterowane obrabiarki numeryczne CNC, np. wycinarki laserowe, plazmowe, wodne
08h
Interfejs
Urządzenia służące do archiwizowania danych (ang. mass storage)
Pamięci typu flash, karty pamięci, dyski zewnętrzne, odtwarzacze cyfrowe, aparaty i kamery cyfrowe
09h
Urządzenie
Koncentratory USB
Koncentratory (huby) USB
10h
Urządzenie
Urządzenia audio/wideo
Klasa grupująca urządzenia audio/wideo definiuje standardowe mechanizmy kontroli i przesyłania strumieni danych reprezentujących zakodowany cyfrowo dźwięk i obraz, np. kamery i aparaty cyfrowe
0Ah
Interfejs
Urządzenia zgodne z USB CDC
Urządzenia zgodne z USB CDC
0Bh
Interfejs
Urządzenia interfejsu kart chipowych (ang. smart card)
Czytniki USB kart chipowych
0Dh
Interfejs
Systemy zabezpieczeń
Czytniki linii papilarnych
0Eh
Interfejs
Urządzenia wideo
Klasa grupująca urządzenia wideo definiuje standardowe mechanizmy kontroli i przesyłania strumieni danych reprezentujących zakodowany cyfrowo obraz, np. kamery internetowe
0Fh
Interfejs
Urządzenia monitorujące parametry życiowe
Pulsometry, ciśnieniomierze, termometry cyfrowe
DCh
Urządzenie oraz interfejs
Urządzenia diagnostyczne
Urządzenie testujące zgodne z USB
E0h
Interfejs
Kontrolery bezprzewodowe
Adaptery Bluetooth
EFh
Urządzenie oraz interfejs
Różne
Urządzenia oparte na technologii ActiveSync
62
USB. Praktyczne programowanie z Windows API w C++
Tabela 2.1. Klasy urządzeń — ciąg dalszy Klasa
Użycie
Opis
Przykłady
FEh
Interfejs
Specyficzne dla aplikacji
IrDA, USB TMC (ang. Test and Measurement Class), USB DFU (ang. Device Firmware Upgrade)
FFh
Urządzenie oraz interfejs
Specyficzne dla producenta
Specyficzne dla producenta
Rysunek 2.1. Menedżer urządzeń
A
Menedżer urządzeń
Plik
Akcja
Widok
Pomoc
« 4 1 i h | B I □ a j !& -
: ANDRZEJ-PC E - ^ Baterie E)-*Si Karty graficzne Karty sieciowe @ -ffl Klawiatury El ® Komputer E l - Kontrolery dźwięku, wideo i gier E l- d El ^ EJ - ÿ E l Ł<
Kontrolery IDE ATA/ATAPI Kontrolery magazynu Kontrolery uniwersalnej magistrali szeregowej Monitory
M Mysz i inne urządzenia wskazujące 0 Odbiorniki radiowe Bluetooth E) ^ Porty (COM i LPT} O Procesory El-ï—a Stacje dysków s ..^| Stacje dysków CD-ROM/DVD El
Urządzenia interfejsu HID Eł-jjP Urządzenia systemowe
Rysunek 2.2. Ogólne właściwości przykładowego uniwersalnego kontrolera gier
A
Menedżer urządzeń
Plik
Akcja
Widok
Pomoc
»■ »Im IB IB si
tb
B-^ ft ANDRZEJ-PC f f l- ijl Baterie Ei-ljjj Karty graficzne El ^ Karty sieciowe E H ® Klawiatury EH ® Komputer El-^j Kontrol ery dźwięku, wi deo i gier B K a Kontrol ery IDE ATA/ATAPI a - C - Kontrolery magazynu El- ^ Kontrolery uniwersalnej magistrali szeregowej a t Monitory E l - f l Mysz i inne urządzenia wskazujące E) © Odbiorniki radiowe Bluetooth EH1? r Porty (COM i LPT) E i- O Procesory El-i—a Stacje dysków l+l-cjj Stacje dysków CD-RQM/DVD Urządzenia interfejsu HID Urządzenie USB inter Urządzenie USB inter i i- fe, Urządzenie USB inter i Urządzenie USB inter Ś - ® Urządzenia systemowe i
i
:
Otwiera arkusz właściwości dla bieżą
Aktualizuj oprogramowanie sterownika.., Wyłącz
Odinstaluj Skanuj w poszukiwaniu zmian sprzętu W łaściw ości
Rozdział 2. ♦ Informacje o urządzeniach
63
Rysunek 2.3. Karta Szczegóły z właściwościami uniwersalnego kontrolera gier
Rejestr systemowy Rejestr jest systemową bazą danych, zawierającą wszystkie informacje o aktualnej kon figuracji sprzętowej. Stanowi również pew ną odmianę pliku konfiguracyjnego zain stalowanych aplikacji. Rejestr jest dość skomplikowaną strukturą danych, zawierającą wiele poziomów kluczy tematycznych, podkluczy i związanych z nimi wartości. Rejestr dzieli zapisane informacje na dwie podstawowe kategorie: związane ze sprzętem i apli kacjami (ang. computer-specific) oraz określone przez użytkownika (ang. user-specific). Pięć głównych kluczy tematycznych może zawierać wiele podkluczy i podkluczy za gnieżdżonych. Klucze należące do kategorii computer-specific to: ♦ HKEY_LOCAL_MACHINE — zawiera informacje o komputerze oraz informacje dotyczące sprzętu i oprogramowania; ♦ HKEY_CURRENT_CONFIG — zawiera informacje o bieżącej konfiguracji sprzętowej; ♦ HKEY_CLASSES_ROOT — zawiera informacje na temat interfejsu użytkownika oraz obiektów OLE. Klucze należące do kategorii user-specific to: ♦ HKEYJJSERS — zawiera ustawienia przypisane użytkownikom dotyczące obszaru roboczego aplikacji; ♦ HKEYj CJRRENTj JSER — zaw iera inform acje o aktualnym użytkowniku lub użytkownikach.
64
USB. Praktyczne programowanie z Windows API w C++
Klucz tematyczny HKEY_LOCAL_MACHINE Wszystkie parametry określające dane urządzenie reprezentowane w swojej klasie in stalacji (np. HID) można odczytać, korzystając z edytora rejestrów, na przykład za pośrednictwem podklucza \Enum klucza tematycznego HKEY_LOCAL_MACHINE\SYSTEM\ CurrentControlSet\Enum\. Podklucz \Enum\USB\ zawiera informacje na temat identy fikatorów producenta VID (ang. Vendor ID) i produktu PID (ang. Product ID) zainsta lowanego w systemie urządzenia USB, tak ja k pokazano na rysunku 2.4 i w tabeli 2.2. ^-E d y lo .n jK tm
-
M
Ulubione Pomoc VtlMíK ¡APIIl.OKH Narwa Typ J¿ VII) DlCIłfłPII) Al51)1 Sltt>omyslna] REG_SZ j i VID 0FCE3.P1D D1» 1®»)Capabilities REG_DWORD J¿ Vid Ol.-rifP.d
d.oi€c»p>d.(nin»M..oi ClassGUID REG_SZ J¿ V.d OfcrAPd dl MAM, OS S ] CompatiblelDs REG_MULTI_SZ J í Vid OkedtP.d dlM iiM i 01 REG_DW ORD jk I ConfigFlags J¿ Vid Oł.^rlrPd illI litM , 08 REG_SZ S ] DeviceDesc J¿ Vid.OfcrdfPid.dlMAMi.Ofl REG_SZ S Driver J¿ VID OFCE3.P3D £209 SlHardwarelD REG_MULTI_SZ J i VII) 10DSAPII5 m i s S i Locationlnformation REG_SZ J¿ VID.I^mPID.OlOS J i VII) 1¿>/APII) 010 SAMI 01) REG_SZ S i Mfg J¡ VID 1207&P1D 0103&MJ 01 SParentldPrefix REG_SZ VII) 1SO/APII) 01bS REG_SZ S Service J i vn).rs n » p ii:.ii» o J i vio n rrA P io sioo j í Vil) loOAAPlÜ OOC3 J i vil) 16H/APII) S7V J i VII).18I)1AI>!I).;0A« J i VID 1FA3&PID 1C06 J¿ VII) »KAAPII3 010? I. fKł1/lKj:iS. lACd)1 - J i hW)04;iie(v0M ( J¿ VID 223A&PE) 0100 t J¿ VII) I I I I APII) «Wl |. J i VII>_l 111APII)_S061AMI.0I) t J i vio rrrrAPio w iA M io i ______________■ fc INIlgRIMI________________ Komputer\HKEY_LOCAL_MACHINE\SY'STEM\CurrentControlSet\Enum\USB\VID_22BA8íPID_0108\68£L7bdd3el&08£L
^
Plik Edycja Widok
I. t ( l [. t I p I ( I (. t ( l P t ( p t ( ¿
Dane (wartość nie ustalona] 0x00000034 (132) WDCIass {745al7aO-74d3-lldO-b6fe-00a0c90f57da) USB\Class_033tSubClass_008tProt_00USB\Class_03ASubClass_0 0x00000000 (0) @input.inf,%hid,devicedesc%;Urzadienie USB interfejsu HID {745al7 aO-74d3-U dO-b6fe-00a0c90f57da}\0131 USB\VID_22BAfiłPID_01088iREV_0313 USB\VID_22BA8tPID_0108 Port*0001.Hub_*0008 @input.inf,%stdmfg%;(Standardowe urządzenia systemowe] 7&4558750&0 HidUsb
Rysunek 2.4. Widok edytora rejestru z wpisami dla podklucza tematycznego \Enum\USB\ Tabela 2.2. Specyfikacja podklucza \Enum\USB Nazwa
Opis
Podstawowe źródło danych
Class
Klasa instalacji urządzeń
Plik .inf oraz defguid.h
ClassGUID
Identyfikator GUID klasy instalacji urządzeń
Plik .inf oraz defguid.h
DeviceDesc
Opis urządzenia
Plik .inf
HardwareID
Łańcuch znaków zawierający identyfikatory producenta i produktu
Deskryptor urządzenia
CompatibleIDs
Łańcuch znaków zawierający identyfikatory klasy (opcjonalnie podklasy) instalacji urządzenia
Deskryptor urządzenia i deskryptor interfejsu
Mfg
Nazwa producenta
Plik .inf
Driver
Nazwa podklucza podklucza tematycznego Class
Rejestr systemowy C urrentC ontrolSet\C ontrol\C lass\
Location Information
USB Device lub łańcuch iProduct
Sterownik magistrali lub deskryptor łańcuchowy
Service
Podklucz Service
Rejestr systemowy HKLM\System\ C urrentC ontrolSet\Services\
Rozdział 2. ♦ Informacje o urządzeniach
65
Każdy podklucz podklucza tematycznego \Enum\JSB\ zawiera z kolei podklucz \Device Parameters, w którym jest zapisana nazwa symboliczna (ang. symbolic name) urządze nia USB, tak ja k pokazano na rysunku 2.5.
Rysunek 2.5. Odczyt nazwy symbolicznej urządzenia USB
Podklucz tematyczny \Class Wszystkie parametry określające klasę instalacji urządzeń można odczytać za pośred nictwem podklucza HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\, tak ja k pokazano na rysunku 2.6.
Rysunek 2.6. Widok edytora rejestru z wpisami dla identyfikatorów GUID klas instalacji
ee
USB. Praktyczne programowanie z Windows API w C++
Podklucz podklucza tematycznego \Class W swojej klasie instalacji każde urządzenie posiada podklucz opisujący jego sterow nik. Podklucz podklucza tematycznego \C lass często jest określany mianem podklucza \D riv er (ang. driver key). N a rysunku 2.7 pokazano przykładowe zapisy występujące w podkluczu \D river, a w tabeli 2.3 przedstawiono jego typową zawartość.
Rysunek 2.7. Podklucze opisujące sterowniki urządzenia Tabela 2.3. Specyfikacja podklucza \Driver Nazwa
Opis
Podstawowe źródło danych
DriverDate
Data utworzenia sterownika
Plik .inf
DriverDesc
Opis sterownika
Plik .inf
DriverVersion
Wersja sterownika
Plik .inf
InfPath
Nazwa pliku .inf
Nazwa pliku .inf
InfSection
Nazwa sterownika w sekcji DDInstal
Plik .inf
InfSectionEx
Dodatkowe rozszerzenia używane w pliku .inf
Plik .inf
MathingDeviceID
Identyfikator sterownika
Deskryptor urządzenia i plik .inf
ProviderName
Dostawca sterownika
Plik .inf, łańcuch znaków określający dostawcę
Rozdział 2. ♦ Informacje o urządzeniach
67
Identyfikatory GUID Pełną realizację interfejsu urządzenia przez klasę instalacji urządzeń uzyskuje się po przez wydobycie identyfikatora interfejsu. Typ strukturowy GUID przechowuje global nie unikatowy identyfikator GUID (ang. Globally Unique Identifier). System operacyjny W indows, generując (na podstawie aktualnej daty i czasu) nowy identyfikator GUID, gwarantuje, że będzie on unikatowy w skali w szystkich identyfikatorów tego typu wygenerowanych na całym świecie. System operacyjny generuje automatycznie nowy identyfikator GUID przed rozpoczęciem procesu instalacji sterownika dla wybranego urządzenia. W ten sposób GUID identyfikuje interfejs realizowany przez każdą klasę instalacji urządzeń. Podczas uruchamiania Windows buduje strukturę drzewa z GUID dla wszystkich wykrytych urządzeń. Oprócz GUID dla interfejsu, który realizuje klasa instalacji samego urządzenia, Windows może umieścić w drzewie GUID klasę instalacji magistrali, do której podłączone je st urządzenie. W tabeli 2.4 zebrano wartości iden tyfikatorów GUID kilkunastu podstaw ow ych klas urządzeń. Wartości tych identyfika torów można odczytać z rejestru systemowego (patrz rysunek 2.6). Tabela 2.4. Identyfikatory GUID podstawowych klas urządzeń według nazewnictwa występującego w rejestrze systemowym Klasa
GUID
Opis urządzeń
CDROM
4D36E965-E325-11CE-BFC1-08002BE10318
Sterowniki napędów CD/DVD/ Blu-ray
DiskDrive
4D36E967-E325-11CE-BFC1-08002BE10318
Dyski twarde
Display
4D36E968-E325-11CE-BFC1-08002BE10318
Adaptery wideo
FDC
4D36E969-E325-11CE-BFC1-08002BE10318
Kontrolery urządzeń wymiennych
FloppyDisk
4D36E980-E325-11CE-BFC1-08002BE10318
Dyski wymienne
HDC
4D36E96A-E325-11CE-BFC1-08002BE10318
Sterowniki dysków twardych
HIDClass
745A17A0-74D3-11D0-B6FE-00A0C90F57DA
Urządzenia klasy HID
1394
6BDD1FC1-810F-11D0-BEC7-08002BE2092F
Sterowniki kontrolera hosta IEEE 1394
Image
6BDD1FC6-810F-11D0-BEC7-08002BE2092F
Kamery i skanery
Keyboard
4D36E96B-E325-11CE-BFC1-08002BE10318
Klawiatury
Modem
4D36E96D-E325-11CE-BFC1-08002BE10318
Modemy
Mouse
4D36E96F-E325-11CE-BFC1-08002BE10318
Myszy oraz inne urządzenia wskazujące
Media
4D36E96C-E325-11CE-BFC1-08002BE10318
Kontrolery dźwięku, wideo i gier
Net
4D36E972-E325-11CE-BFC1-08002BE10318
Adaptery sieciowe
Ports
4D36E978-E325-11CE-BFC1-08002BE10318
Porty równoległe i szeregowe
SCSIAdapter
4D36E97B-E325-11CE-BFC1-08002BE10318
Kontrolery SCSI (SAS) i RAID
System
4D36E97D-E325-11CE-BFC1-08002BE10318
Magistrale systemowe, mostki itp.
USB
36FC9E60-C465-11CF-8056-444553540000
Kontrolery hosta USB oraz głównych hubów
68
USB. Praktyczne programowanie z Windows API w C++
Każde urządzenie zawiera informacje na tem at identyfikatora producenta VID, przy pisanego zgodnie z zaleceniami standardu USB, oraz identyfikatora produktu PID, samodzielnie określanego przez wytwórcę urządzenia (patrz rozdział 3., tabela 3.3). Wartości tych identyfikatorów są odczytywane w procesie wyliczania urządzeń do łączanych do magistrali USB. Dzięki temu w trakcie detekcji i identyfikacji urządzeń podłączanych do magistrali USB system operacyjny może dobrać sterownik, który najlepiej pasuje do urządzenia. Globalnie unikatowy identyfikator GUID identyfikuje interfejs, jaki sterownik klasy urządzeń udostępnia warstwie aplikacji. Listę podłą czonych urządzeń, których sterownik ma interfejs identyfikowany poprzez określony GUID, uzyskuje się za pomocą funkcji SetupDiGetClassDevs(), SetupDiEnumDevice In te rfac e s() oraz SetupD iG etD eviceInterfaceD etail(), wydobywających dal sze szczegółowe informacje na tem at podłączonych urządzeń, tak jak zaprezento wano w rozdziale 5. W deklaracji interfejsu klasy urządzenia identyfikator GUID jest zapisywany w nawia sach klamrowych, na przykład: { D C 6 B F 4 3 B -D C C A -4 C 7 B - 9 9 7 E -E 7 B 6 6 1 E E 4 E 8 0 }
W celu przekształcenia identyfikatora GUID na łańcuch znaków można wykorzystać funkcję StringFromGUID2() z modułu windows.h, tak ja k pokazano na listingu 2.1. Listing 2.1. Przekształcanie identyfikatora GUID na łańcuch znaków //
--------------------------------------------------------------------
# i n c l u d e # in c lu d e u s i n g n a m es p ac e s t d ; OLECHAR b u f f e r [ 1 2 8 ] ; s t a t i c GUID GUID j DE VINTE RFACE j USB j HOSTj CONTROLLER = {0x3abf6f2d,
0x71c4,
0x462a,
{0x8a,
0x92,
0x1e,
0x68,
0x61,
0xe6,
0 xaf,
0x27}};
\
i n t m a in () { S tr in g F r o m G U I D 2 ( G U I D j D E V I N T E R F A C E j U S B j H O S T _ C O N T R O L L E R , b u ffer, p rin tf("% ls \n ",
s iz e o f( b u ffe r ) ) ;
b u ffer);
system ("PA JSE"); return }
//
0;
--------------------------------------------------------------------
W podkatalogu instalacyjnym W DK ...\inc\api\devguid.h znajdują się identyfikatory GUID dla urządzeń PnP.
Rozdział 2. ♦ Informacje o urządzeniach
69
Pliki .inf Pliki skryptowe systemu Windows z rozszerzeniem .in f zawierają wszystkie informa cje wymagane przez system operacyjny do zainstalow ania sterowników urządzenia i stworzenia dla niego optymalnej konfiguracji. Pliki te można odszukać w katalogu W indow s\Inf Każdy producent urządzenia, dla którego W indows nie posiada pliku .in f ani pliku sterownika, jest zobowiązany do udostępnienia wymaganych przez sprzęt sterowników wraz z odpowiednim plikiem inf. Z reguły zawartość katalogu z plikam i informacyjnymi jest ukryta przed użytkowni kiem. W celu wyedytowania żądanego pliku należy w menu Organizuj wybrać Opcje folderów i wyszukiwania, a następnie w karcie Widok zaznaczyć i zastosować pozycję Pokaż ukryte pliki i foldery w sposób, jaki pokazano na rysunkach 2.8 i 2.9. Rysunek 2.8. Menu Organizuj/ Opcje folderów i wyszukiwania w systemie Windows 7
■
- |&
► K o m p u te r » Dysk loka ln y
Nawy folder Wytnij Kopiuj □
Wklej Cofnij Wykonaj ponownie Zaznacz wszystko
E
Układ
^
Usuń
Opcje folderów i wyszukiwania
Zmień nazwę Usuń właściwości Właściwości Zamknij
Rysunek 2.9. Karta Widok okna Opcje folderów w systemie Windows 7
70
USB. Praktyczne programowanie z Windows API w C++
Posługując się systemem Windows 8, należy otworzyć eksplorator plików. Wybieramy w nim K om puter/D ysk C, a następnie rozwijamy Widok i wybieramy Opcje, tak ja k pokazano na rysunku 2.10. E
0
Okienko podgl ądu
[TB Okienko szczegółów ■
gj| Bardzo duże ikony |E| Duże ikony ||§ Małe ikony Kafelki
§ § Lista §1 Zawartość
11
[□] Grupuj według w
I I Pola wyboru elementów
f t l Dodaj kolum ny
I I Rozszerzenia nazw plików rU-i Ukryte elementy Ukl
m Dopasuj rozmiar wszystkich k Bieżący widok
2
Pokazywanie/ukiywanie
Rysunek 2.10. Menu Widok eksploratora plików w systemie Windows 8 Następnie klikamy Zmień opcje folderów i wyszukiwania i w karcie Widok wybieramy Pokaż ukryte pliki, fold ery i dyski (rysunek 2.11). Rysunek 2.11. Karta Widok okna Opcje folderów w systemie Windows 8
W ewnętrzna struktura plików .in f składa się z sekcji oddzielonych nagłówkami z na zw ą sekcji umieszczoną w nawiasach klamrowych. Z kolei każda sekcja może zawie rać szereg wpisów w postaci: o p c ja = p a r a m e t ry
Każda z opcji może zawierać kilka parametrów oddzielonych przecinkami. Jeżeli nazwy opcji są parametrami, ich kolejność ma znaczenie. Komentarze są oznaczone znakiem ; (średnik). Nazwy zmiennych są ograniczone znakami %(procent), np. %DeviceName%. Liczba sekcji w pliku nie je st ściśle określona. Poniżej wymieniono kilka najczęściej występujących elementów plików .inf. Version — sekcja tworzy nagłówek identyfikujący plik .inf. Zawiera m.in. informacje o klasie instalacji urządzenia, jej identyfikatorze GUID, wersji sterownika i producencie oraz nazwę pliku katalogowego sterownika urządzenia. Manufacturer — sekcja zawiera listę producentów urządzeń oraz innych odwołań do w łaściwych sekcji.
Rozdział 2. ♦ Informacje o urządzeniach
71
Model — sekcja tworzy listę urządzeń danego producenta i odwołań do związanych z po szczególnymi urządzeniami sekcji. Dla każdego urządzenia powinny być podane iden tyfikatory VID oraz PID (i ewentualnie identyfikatory urządzeń kompatybilnych), np.: ; ========== M a n u fa c tu r e r /M o d e l s s e c t i o n s =========== [M anufacturer] %Manuf act ure rN am e% = S t a n d a r d , N T a m d 6 4 [Standard.NTam d64] %DeviceName% = U S B _ I n s t a l l ,
U S B\ V ID _0 22 B A& P ID _0 10 8& R EV _0 31 3
Różne postaci sekcji DDInstall tw orzą listy opisujące sposób instalacji pojedynczego urządzenia lub klasy urządzeń uprzednio określonych w sekcji Version. Sekcja SourceD isksFiles określa pliki wchodzącego w skład katalogu instalacyjnego urządzenia. Z kolei w sekcji SourceDiskNames identyfikowane są dyski z instalowanymi plikam i wymienionymi w sekcji SourceDisksFiles. Sekcja S trin g s zawiera łańcuchy znaków przypisane odpowiednim zmiennym zdefi niowanym we wcześniejszych sekcjach. Więcej informacji na temat struktury plików .in f można znaleźć w rozdziale 7. oraz na stronach MSDN pod hasłem IN F File Sections and Directives (http://msdn.microsoft. com/en-us/library/ff547433(v=vs.85). aspx).
Podsumowanie Niniejszy rozdział zawiera krótkie wprowadzenie do w ybranych elementów systemu operacyjnego umożliwiających kontrolę i identyfikację zainstalowanego sprzętu. M e nedżer urządzeń i rejestr systemowy są dwoma podstawowymi narzędziami służącymi do szybkiej identyfikacji podłączonych i zainstalowanych w systemie urządzeń pery feryjnych. W szystkie zapisane w menedżerze i rejestrze informacje na tem at intere sujących nas urządzeń m ożna samodzielnie — w sposób programowy — odzyskać, korzystając z odpow iednich warstw API (ang. Application Program m ing Interface) systemu operacyjnego.
72
USB. Praktyczne programowanie z Windows API w C++
Rozdział 3.
Wstęp do transmisji danych R ozdział je s t pośw ięcony teoretycznym podstaw om organizacji transm isji danych w systemie USB. W pierwszej kolejności zostanie omówiona struktura systemu USB, a następnie zostaną przedstawione struktury danych odpowiadające poszczególnym składnikom systemu. Forma prezentowanych wiadomości odzwierciedla punkt widze nia programisty i nie uwzględnia innych aspektów związanych z fizyczną budową elek tronicznych układów składających się na system USB oraz sposobów transmitowania przez nie sygnałów elektrycznych.
Struktura systemu USB 2.0 Stosowany w standardzie USB 2.0 opis zależności między sprzętem i oprogramowa niem w ogólnych zarysach odzwierciedla model wzorcowy ISO OSI (ang. Open Sys tem Interconnection Reference M odel). Proces kom unikacji zgodny z tym modelem można wstępnie podzielić na trzy główne warstwy ze względu na sposób wymiany in formacji między nimi, tak ja k pokazano na rysunku 3.1.
Warstwa funkcjonalna Rozpatruj ąc warstwę funkcjonalną systemu, zaniedbujemy jego fizyczne i logiczne aspekty, tak ja k schematycznie pokazano na rysunku 3.2, skonstruowanym zgodnie z zasadą reifikacji danych.
Uwaga
Diagram pokazany na rysunku 3.2 powinien zostać zinterpretowany przez progra mistę w następujący sposób: klasa urządzenia reprezentuje wiedzę o realnie ist niejących w systemie urządzeniach USB określonego typu, zaś interfejs — wiedzę o istnieniu klasy urządzeń.
74
USB. Praktyczne programowanie z Windows API w C++
Rysunek 3.1. Uproszczony schemat warstwowych powiązań między sprzętem i oprogramowaniem
Program użytkownika
« c a li»
«dass» Klasa urządzenia
«interface» Interfejs urządzenia <
+Lista funkcji))
1 -...... - -
-Lista atrybutów +Lista funkcji))
Rysunek 3.2. Abstrakcyjne przedstawienie funkcjonalnej konfiguracji systemu USB. Interfejs opisuje funkcjonalność klasy urządzeń Z poziomu programu użytkow nika (klienta) sposób obsługi urządzeń jest niezależny od przestrzeni adresowej, w której zostały umiejscowione. Każde urządzenie USB na leży do określonej klasy urządzeń z dokładnie opisaną listą atrybutów i listą funkcji, których wykonanie zapewnia konstrukcja urządzenia. Lista funkcji, które urządzenie może zrealizować za pośrednictw em punktów końcowych, je st zdefiniow ana w od powiednim interfejsie. Urządzenie może realizować wiele interfejsów właściwych dla danej konfiguracji. Program użytkownika wykonywany na komputerze macierzystym za pośrednictwem oprogramowania systemowego odwołuje się do funkcji realizowanej przez urządzenie wykonawcze. Głównymi składnikami oprogramowania systemowego USB są sterow niki klienta, sterowniki USB i sterownik kontrolera hosta.
Warstwa fizyczna W arstw a fizyczna zapew nia transm isję danych między urządzeniami systemu USB i kom puterem m acierzystym (hostem). Z reguły ma ona w ielopoziom ow ą strukturę topologii gwiaździstej, z kom puterem jako elem entem centralnym i koncentratorami (hubami) w centrum każdej gwiazdy peryferyjnej. Oznacza to, iż każdy hub może two rzyć fizyczną topologię gwiazdy, ale logiczną topologię magistrali (patrz rozdział 1., rysunek 1.52).
Rozdział 3. ♦ Wstęp do transmisji danych
75
N a rysunku 3.3 pokazano typowy diagram wdrożenia służący do zobrazowania fizycznej architektury systemu. Podstawowym elementem diagramu wdrożenia jest węzeł (ang. node). W zależności od tego, co chcemy przekazać, można używać wielu oznaczeń dla węzłów. W przedstaw ieniu architektury sprzętowej podstaw ow ą rolę odgryw ają « p r o c e s s o r » , który może wykonać kod programu lub komponentu, urządzenie w y konawcze « d e v ic e » , które jest przyłączone do węzła i współpracuje z programem (np. drukarka, skaner), oraz urządzenie programowalne i wykonawcze « d e v ic e /p ro c esso r» , które nie tylko współpracuje z programem uruchomionym na hoście, ale również samo może być programowalne (np. przyrządy pomiarowe). Koncentratory również można oznaczać za pom ocą stereotypu « d e v i c e » , jednak w tym przypadku należy zadbać o odpowiednie nazewnictwo takiego węzła.
Rysunek 3.3. Fizyczna konfiguracja urządzeń USB Najważniejszymi elementami sprzętowymi hosta są kontroler hosta i główny hub. Kon troler hosta to specjalizowany kontroler komunikacyjny wykonujący kolejne transfery danych w postaci transakcji. Główny hub je st koncentratorem znajdującym się w e wnątrz hosta i udostępnia minimum dwa porty USB.
Warstwa logiczna N a rysunku 3.3 pokazano układ połączeń w warstwie fizycznej systemu USB, który bywa niekiedy dosyć skomplikowany. Warto jednak zwrócić uwagę na fakt, iż w w ar stwie logicznej kom puter łączy się z każdym urządzeniem w taki sposób, jakby były one bezpośrednio podłączone do m agistrali USB. Sytuację tę ilustruje rysunek 3.4. Logiczne urządzenie USB jest zbiorem punktów końcowych, z którymi komputer w y mienia dane.
76
USB. Praktyczne programowanie z Windows API w C++
« d e v ic e » Urządzenie USB
/
« d e v ic e » Urządzenie USB
« d e v ic e » Urządzenie USB
/
« p ro c e s so r»
Kom puter (host-kontroler USB + główny hub)
/ « d e v ic e » Urządzenie USB
/ « d e v ic e » Urządzenie U SB
/
/
/ « d e v ic e » Urządzenie USB
/
Rysunek 3.4. Logiczna topologia magistrali USB W każdym urządzeniu jest zdefiniowany przynajmniej jeden bufor danych, tzw. zero wy punkt końcowy EP0 (ang. endpoint zero), za pom ocą którego urządzenie jest kon figurowane. Urządzenia wykonawcze zawierają dodatkowe punkty końcowe zgrupo wane w interfejsy.
Struktura systemu USB 3.0 Standard USB 2.0 jest opisywany w formie trójwarstwowej architektury składającej się z warstw: fizycznej, logicznej oraz funkcjonalnej. Zadaniem oprogramowania in terfejsu łącza szeregowego, potocznie określanego mianem SIE (ang. System Interface Engine), jest pośredniczenie w w ym ianie informacji pomiędzy warstwami fizyczną, logiczną oraz funkcjonalną. W arstwa funkcjonalna zawiera opis sytemu z punktu w i dzenia programów uruchamianych na komputerze macierzystym (hoście) i odwołują cych się do funkcji pełnionych przez urządzenie wykonawcze (peryferyjne). Struktura opisu systemu USB 3.0 zasadniczo różni się od poprzednika. W arstwa fi zyczna (ang. physical layer) określa charakterystyki elektryczne sygnałów w trybie Super Speed — tzn. wskazuje, w jaki sposób informacje są zabezpieczone i kodowane, oraz generuje specjalne sekwencje sygnałowe wykorzystywane przez inne warstwy. W arstw a łącza (ang. link layer) je st odpow iedzialna za generowanie pakietów oraz stworzenie i utrzymanie bezpiecznego kanału transmisyjnego pomiędzy hostem a urzą dzeniem peryferyjnym z zapewnieniem praw idłowości transferu pakietów oraz kon troli przepływu danych. W arstwa protokołu (ang. protocol layer) zarządza przepływem danych między urzą dzeniam i oraz określa sposób w ykorzystyw ania różnych struktur pakietów w trybie z superwysoką szybkością transmisji danych (patrz rozdział 1.). N a rysunku 3.5 zaprezentowano ogólne modele warstwowe stosowane w standardach USB 2.0 oraz 3.0, zaś na rysunku 3.6 — model połączenia dla urządzeń USB działa jących w trybie z superwysoką szybkością transm isji danych za pośrednictwem kon centratora (huba).
Rozdział 3. ♦ Wstęp do transmisji danych
77
Rysunek 3.5. Modele architektury USB 2.0 oraz USB 3.0 z oznaczonymi liniami sygnałowymi. Warstwy fizyczną, łącza i protokołu USB 3.0 oznaczono odpowiednio skrótami PHY, LINK oraz PROTOCOL
Rysunek 3.6. Realizacja połączenia w trybie Super Speed dla urządzeń USB 3.0. Warstwyfizyczną, łącza i protokołu USB 3.0 oznaczono odpowiednio skrótami PHY, LINK oraz PROTOCOL. Na rysunku pominięto elementy odpowiedzialne za transmisję w trybach LS/FS/HS
Potoki danych Potok (ang. pipe) jest pojęciem abstrakcyjnym. Służy on do opisu transmisji między de klarowanym w pamięci komputera buforem danych a punktem końcowym zdefiniowa nym w zewnętrznym urządzeniu USB i określającym jego funkcjonalność. Ponieważ USB jest interfejsem szeregowym, punkt końcowy jest właściwie kolejką FIFO sekwen cyjnie opróżnianą i (lub) w ypełnianą przez nadawane i (lub) odbierane bajty danych. N a rysunku 3.7 w sposób poglądowy zobrazowano ideę przepływu danych w syste mie USB. Zaznaczono przykładowy dwukierunkowy punkt końcowy EP0 i dwa przy kładowe punkty jednokierunkowe.
78
USB. Praktyczne programowanie z Windows API w C++
Domyślny (zerowy) potok kontrolny
Bufor IN OUT
------- ►
. -
EPO
----
■4 <>
Proqram użytkow nika
<>
Bufot IN
■4-------
EP1 IN
<> Bufor OUT
------- ►
E P n OUT
Rysunek 3.7. Diagram współpracy obrazujący jeden z możliwych wariantów transmisji danych w systemie USB. Wszystkie obiekty oznaczono jako anonimowe. Potokami strumieniowymi jest realizowany transfer jednokierunkowy, zaś potokami kontrolnymi (komunikatowymi) — transfer dwukierunkowy W trakcie konfiguracji urządzenia stos sterowników USB tworzy potok (po stronie hosta) dla każdego punktu końcowego zdefiniowanego w interfejsie podstawowym oraz interfejsach alternatywnych urządzenia. Potok jest kanałem komunikacyjnym utworzo nym pomiędzy sterownikiem kontrolera hosta a punktem końcowym urządzenia. Dla sterowników klienta potok jest logiczną abstrakcją punktu końcowego. Aby rozpocząć transmisję w wybranym trybie (masowym, izochronicznym, przerwaniowym lub kon trolnym), odpowiedni sterownik musi uzyskać identyfikator określonego potoku.
Uwaga
W dokumentacji USB pojęcia „potok" (ang. pipe) oraz „punkt końcowy" (ang. end point) są często traktowane jako synonimy i używane zamiennie. Wynika to z faktu, iż pomiędzy punktem końcowym i potokiem zawsze występuje relacja typu jeden-do-jednego. Podanie identyfikatora potoku jest równoznaczne z określeniem odpo wiadającego mu identyfikatora punktu końcowego. W celu uniknięcia dwuznaczności powinno się używać określenia punkt końcowy potoku (masowego, izochronicznego, przerwaniowego lub kontrolnego). System operacyjny udostępnia dw a podstawowe typy danych, w których zawarte są właściwości potoków. W tabelach 3.1 i 3.2 zamieszczono odpowiednie opisy.
Tabela 3.1. Specyfikacja typu wyliczeniowego USBD PIPE TYPE zdefiniowanego w module usb.h Element typu wyliczeniowego
Znaczenie
UsbdPipeTypeControl
Potok sterujący (kontrolny); do potoku będą dostarczane dane konfiguracyjne
UsbdPipeTypelsochronous
Potok obsługuje przekaz izochroniczny
UsbdPipeTypeBulk
Potok obsługuje transmisję masową
UsbdPipeTypeInterrupt
Potok obsługuje przekaz przerwaniowy
Rozdział 3. ♦ Wstęp do transmisji danych
79
Windows Driver K it (WDK) definiuje ten typ jako: typedef enum _USBD_PIPE_TYPE { UsbdPipeTypeControl = 0, UsbdPipeTypeIsochronous = 1, UsbdPipeTypeBulk = 2, UsbdPipeTypeInterrupt = 3 } USBD_PIPE_TYPE; Definicja ta tworzy nowe słowo kluczowe USBD_PIPE_TYPE (typ wyliczeniowy). Tabela 3.2. Specyfikacja struktury USBDPIPEINFORMATION Typ
Element struktury
Znaczenie
USHORT
MaximumPacketSize
Określa maksymalny rozmiar transmitowanego pakietu. Deklarowany rozmiar nie powinien być większy niż wartość pola wMaxPacketSize zapisanego w deskryptorze punktu końcowego
UCHAR
EndpointAddress
Adres punktu końcowego
ULONG
Interval
Przedział czasu w trakcie odpytywania łącza przez host. Odpowiada wartości zapisanej w polu bInterval struktury opisującej deskryptor punktu końcowego
USB_PIPE_TYPE
PipeType
Typ potoku komunikacyjnego
USB_PIPE_HANDLE
PipeHandle
Identyfikator potoku. Identyfikator ten jest generowany przez sterownik kontrolera hosta
ULONG
MaximumTransferSize
Maksymalny rozmiar transmitowanych danych. W systemach operacyjnych Windows XP oraz późniejszych pole MaximumTransferSize nie jest używane i sterowniki klienta je ignorują. W celu ustalenia maksymalnego rozmiaru transmitowanych danych sterowniki klienta w systemach XP/Vista/7/8 używają pola wMaxPacketSize deskryptora punktu końcowego
ULONG
PipeFlags
Znacznik bitowy wykorzystywany przez sterownik portu do określenia typu potoku komunikacyjnego
W DK definiuje tę strukturę w module usb.h jako: typedef struct _USBD_PIPE_INFORMATION { USHORT MaximumPacketSize; UCHAR EndpointAddress; UCHAR Interval; USBD_PIPE_TYPE PipeType; USBD_PIPE_HANDLE PipeHandle; ULONG MaximumTransferSize; ULONG PipeFlags; } USBD_PIPE_INFORMATION, *PUSBD_PIPE_INFORMATION; D efinicja ta tworzy dwa nowe słowa kluczowe: USBD_PIPE_INFORMATION (struktura) i PUSBD_PIPE_INFORMATION (wskaźnik do struktury). Struktura ta je st z kolei w ykorzy stywana (agreguje) w strukturze USB_DEVICE_INFORMATION zdefiniowanej w module hubbusif.h.
80
USB. Praktyczne programowanie z Windows API w C++
Urządzenia i deskryptory urządzeń USB Pod względem logicznym urządzenie USB jest opisywane jako zbiór określonych kon figuracji. Urządzenia m ogą być rozmaicie konfigurowane, przy czym po skonfiguro w aniu sprzętu (patrz rozdział 5., rysunki 5.1 oraz 5.2) tylko jedna z dostępnych kon figuracji może być aktywna. Z kolei każdą konfigurację można traktować jako zbiór interfejsów. Oznacza to, że każda konfiguracja może mieć kilka interfejsów, z których wszystkie m ogą być aktywne w tym samym czasie, co pozwala na dostęp do różnych funkcji urządzenia poprzez różne sterowniki (patrz rozdział 5., rysunek 5.8). Każdy interfejs stanowi zaś zestaw dobrze zdefiniowanych punktów końcowych realizujących ściśle określone funkcjonalności urządzenia. Należy przy tym pamiętać, że zerowy punkt końcowy nie je s t powiązany z konkretnym interfejsem — je s t „ własnością ” urzą dzenia. N a rysunku 3.8 zobrazowano schematycznie strukturę logiczną złożonego urzą dzenia USB. Rysunek 3.8. Struktura logiczna urządzenia USB. Numeracje konfiguracji rozpoczynają się od jedynki, zaś interfejsów od zera
Standard USB umożliwia komunikację z wieloma urządzeniami, przy czym każde z nich może się charakteryzować odm iennym i właściwościam i kom unikacyjnymi. Z tego względu każde urządzenie USB ma możliwość udostępniania informacji o sposobach komunikacji oraz liczbie dostępnych konfiguracji, interfejsów i punktów końcowych. Tego rodzaju dane są przechowywane w strukturach danych zawierających opisy urzą dzeń, konfiguracji, interfejsów oraz punktów końcowych. Struktury te agregują inne struktury, które z kolei zawierają opis odpowiadających im deskryptorów, tak jak sche matycznie pokazano na rysunku 3.9. Dane przechowywane w deskryptorach można
Rozdział 3. ♦ Wstęp do transmisji danych
81
<ï U S B _C 0N FIG U R A T10N _D E S C R IP T 0R
«C ppStm ct» USB IN TER FAC E DESCRIPTOR
«C ppStm ct» U SB_END PO INT_D ESC RIPTO R
Rysunek 3.9. Struktury agregujących podstawowych deskryptorów służących do opisu urządzeń USB z punktu widzenia programisty. Agregujące klasyfikatory odzwierciedlają podstawową logiczną strukturę urządzeń USB. Każde urządzenie USB 3.0 jest też urządzeniem USB 2.0, które z kolei spełnia wszystkie kryteria właściwe dla podstawowego urządzenia USB 1.1. Na rysunku pominięto oznaczenie struktury opisującej deskryptor tekstowy USB STRING DESCRIPTOR, która nie jest powiązana z żadną inną strukturą systemu wydobyć w trakcie procesu detekcji i identyfikacji urządzeń dołączonych do magistrali USB. W modułach usb100.h oraz usbspec.h (Windows 8) znajdują się makrodefinicje, za pom ocą których można określić żądany typ deskryptora: / / USB 1.1
USB_DEVICE_DESCRIPTOR_TYPE USB_CONFIGURATION_DESCRIPTOR_TYPE USB_STRING_DESCRIPTOR_TYPE USB_INTERFACE_DESCRIPTOR_TYPE USB_ENDPOINT_DESCRIPTOR_TYPE
0x01 0x02 0x03 0x04 0x05
/ / USB 2 . 0
USB_DEVICE_QUALIFIER_DESCRIPTOR_TYPE USB_OTHER_SPEED_CONFIGURATION_DESCRIPTOR_TYPE USB_INTERFACE_POWER_DESCRIPTOR_TYPE
0x06 0x07 0x08
/ / USB 3 . 0
USB_OTG_DESCRIPTOR_TYPE USB_DEBUG_DESCRIPTOR_TYPE USB_INTERFACE_ASSOCIATION_DESCRIPTOR_TYPE USB_BOS_DESCRIPTOR_TYPE USB_DEVICE_CAPABILITY_DESCRIPTOR_TYPE USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR_TYPE
0x09 0x0A 0x0B 0x0F 0x10 0x30
Podstawowe informacje na temat urządzeń USB są zapisane w polach struktury USB DEVICE, której zasoby zostały pokazane w tabeli 3.3.
Wskazówka
Każde urządzenie USB charakteryzuje się przynajmniej jednym deskryptorem kon figuracji i jednym deskryptorem interfejsu. Pobierając deskryptor urządzenia, można odczytać informacje na jego temat. W przypadku pobrania deskryptora konfiguracji zwracane są informacje opisujące konfigurację urządzenia, deskryptory interfejsów związanych z daną konfiguracją oraz deskryptory wszystkich punktów końcowych dla każdego interfejsu.
82
USB. Praktyczne programowanie z Windows API w C++
Tabela 3.3. Specyfikacja struktury USBDEVICEDESCRIPTOR Typ
Element struktury
Znaczenie
UCHAR
bLength
Liczba bajtów w deskryptorze urządzenia (długość deskryptora)
UCHAR
bDescriptorType
Typ deskryptora. Wartość USB DEVICE DESCRIPTOR TYPE określa deskryptor urządzenia
USHORT
bcdUSB
Liczba zapisana w kodzie BCD określająca numer wersji USB
UCHAR
bDeviceClass
Kod klasy, do której należy urządzenie USB (patrz rozdział 2., tabela 2.1)
UCHAR
bDeviceSubClass
Kod podklasy związany z kodem klasy urządzenia USB (szczegółowe informacje są dostępne w dokumentacji USB [15,16])
UCHAR
bDeviceProtocol
Kod protokołu komunikacyjnego urządzenia. Z reguły jest on związany z klasą (lub podklasą) urządzenia (szczegółowe informacje są dostępne w dokumentacji USB)
UCHAR
bMaxPacketSizeO
Maksymalny rozmiar pakietu akceptowanego przez zerowy punkt końcowy. Dla urządzeń pracujących w trybie LS maksymalny rozmiar pakietu wynosi 8 bajtów, dla urządzeń FS — 8, 16, 32 lub 64 bajty, dla urządzeń HS — 64 bajty. Dla urządzeń USB pracujących z superwysoką szybkością transmisji danych maksymalny rozmiar pakietu wynosi 2^bMaxPacketSize0, gdzie bMaxPacketSizeO powinno być równe 9, co w wyniku daje maksymalny rozmiar pakietu z danymi sterującymi równy 512 bajtów
USHORT
idVendor
Identyfikator VID producenta urządzenia. Jest przypisany zgodnie z zaleceniami standardu USB
USHORT
idProduct
Identyfikator produktu PID. Jest przypisany przez producenta urządzenia
USHORT
bcdDevice
Określa numer wersji urządzenia w kodzie BCD
UCHAR
iManufacturer
Przechowuje indeks łańcucha znaków określającego producenta urządzenia
UCHAR
iProduct
Indeks łańcucha znaków opisującego urządzenie
UCHAR
iSerialNumber
Indeks łańcucha znaków określającego numer seryjny urządzenia
UCHAR
bNumConfigurations
Określa liczbę możliwych konfiguracji urządzenia USB
Gdy macierzysty komputer wyśle żądanie, wszystkie istniejące w systemie urządzenia USB powinny udzielać wyczerpujących informacji o swoich możliwościach komuni kacyjnych i konfiguracyjnych. Poprzez bufor danych komputera potokiem zerowym są przesyłane komunikaty z żądaniami, a w kierunku przeciwnym informacje o liczbie konfiguracji dostępnych w urządzeniu. W systemie operacyjnym elementy deskryptora urządzenia są reprezentowane w polach struktury USB_DEVICE_DESCRIPTOR, której zasoby przedstawia tabela 3.3.
Rozdział 3. ♦ Wstęp do transmisji danych
Wskazówka
83
Kod BCD, czyli dziesiętny zakodowany dwójkowo (ang. Binary-Coded Decimal), re prezentuje sposób zapisu liczb polegający na dwójkowym zakodowaniu kolejnych cyfr dziesiętnych na czterech bitach (tzw. półbajtach). Kod ten je st obecnie szero ko stosowany w urządzeniach elektronicznych. W DK definiuje tę strukturę w module usb100.h jako: typedef struct _USB_DEVICE_DESCRIPTOR { UCHAR bLength; UCHAR bDescriptorType; USHORT bcdUSB; UCHAR bDeviceClass; UCHAR bDeviceSubClass; UCHAR bDeviceProtocol; UCHAR bMaxPacketSize0; USHORT idVendor; USHORT idProduct; USHORT bcdDevice; UCHAR iManufacturer; UCHAR iProduct; UCHAR iSerialNumber; UCHAR bNumConfigurations; } USB_DEVICE_DESCRIPTOR, *PUSB_DEVICE_DESCRIPTOR ; D efinicja ta tworzy dwa nowe słowa kluczowe: USB_DEVICE_DESCRIPTOR (struktura) i PUSB_DEVICE_DESCRIPTOR (wskaźnik do struktury). Elementy typu w yliczeniowego USB_DEVICE_SPEED przechow ują informacje na temat stałych definiujących tryby transmisji danych. Zasoby typu wyliczeniowego przedsta w ia tabela 3.4.
Tabela 3.4. Specyfikacja typu wyliczeniowego USB_DEVICE_SPEED Element typu wyliczeniowego
Znaczenie
W artość
UsbLowSpeed
Transfer w trybie LS USB 1.1
0
UsbFullSpeed
Transfer w trybie FS USB 1.1
1
UsbHighSpeed
Transfer w trybie HS USB 2.0
2
UsbSuperSpeed
Transfer w trybie SS USB 3.0
3
W DK definiuje ten typ w module usbspec.h jako: typedef enum { UsbLowSpeed = 0, UsbFullSpeed = 1, UsbHighSpeed = 2, UsbSuperSpeed = 3 } USB_DEVICE_SPEED; Definicja ta tworzy nowe słowo kluczowe USB_DEVICE_SPEED.
84
USB. Praktyczne programowanie z Windows API w C++
Kiedy oprogramowanie hosta wykryje nowy sprzęt, sterownik magistrali USB tworzy reprezentujący go fizyczny obiekt urządzenia PDO (ang. Physical Device Object). Dla każdego urządzenia jest tworzony odrębny PDO. Identyfikator sprzętu (ang. hardware ID) dla określonego PDO ma następującą postać: USB\VID_xxxx&PID_yyyy gdzie wartości xxxx oraz yyyy są odczytywane bezpośrednio z pól idVendor oraz idProduct deskryptora urządzenia. Sterownik klasy urządzeń oraz sterowniki dostarczane przez producenta tworzą od powiednio operacyjne obiekty urządzeń FDO (ang. Functional Device Objects) oraz opcjonalnie filtrujące obiekty urządzeń Filter DO (ang. Filter Device Objects) repre zentujące każdy aktualnie dostępny port USB.
Koncentratory i deskryptory koncentratorów USB N a rysunkach 3.10 oraz 3.11 pokazano odpowiednio schemat koncentratora USB 3.0 oraz ogólną topologię systemu pozw alającą na podłączenie zarówno urządzeń USB 3.0, ja k i 2.0. Hub pracujący w trybie z superwysoką szybkością transmisji składa się z dwóch logicznych koncentratorów: USB 2.0 oraz 3.0, i może zawierać do 15 portów wyjściowych. Koncentratory w systemie USB 3.0 m ogą być konfigurowane w formie maksymalnie 5-poziomowej fizycznej topologii gwiazdy, umożliwiającej podłączenie do głównego huba i zaadresowanie maksymalnie 127 urządzeń.
Rysunek 3.10. Koncentrator USB 3.0 [16]. Na rysunku pominięto oznaczenia przewodów zasilającego i masowych Zaprezentowany w tabeli 3.5 typ wyliczeniowy USB_HUB_TYPE definiuje stałe identyfi kujące koncentratory USB. Informacje te m ogą być wykorzystywane podczas wywo łania działającego w trybie jądra rozkazu IOCTL_USB_GET_HUB_INFORMATION_EX.
Rozdział 3. ♦ Wstęp do transmisji danych
85
Rysunek 3.11. Ogólna topologia systemu USB 3.0 składającego się z hosta oraz dwóch podłączonych za pośrednictwem huba urządzeń wykonawczych realizujących odmienne funkcjonalności [16] Tabela 3.5. Specyfikacja typu wyliczeniowego U SBH U BTYPE Element typu wyliczeniowego
Znaczenie
W artość
UsbRootHub
Główny koncentrator
1
Usb20Hub
Deskryptor huba (patrz tabela 3.6)
2
Usb30Hub
Deskryptor huba (patrz tabela 3.8)
3
W DK definiuje ten typ w module usbioctl.h (Windows 8) jako: typedef enum _USB_HUB_TYPE { UsbRootHub = 1, Usb20Hub = 2, Usb30Hub =3 } USB_HUB_TYPE; Definicja ta tworzy nowe słowo kluczowe USB_HUB_TYPE. W systemie operacyjnym elementy deskryptora huba funkcjonującego w trybach LS, HS oraz FS są reprezentowane w polach struktury USB_HUB_DESCRIPTOR, której zasoby przedstawia tabela 3.6.
86
USB. Praktyczne programowanie z Windows API w C++
Tabela 3.6. Specyfikacja struktury USB HUB DESCRIPTOR Typ
Element struktury
Znaczenie
UCHAR
bDescriptorLength
Liczba bajtów w deskryptorze (długość deskryptora)
UCHAR
bDescriptorType
Określa typ deskryptora. Należy wpisać wartość 0x29
USHORT
bNumberOfPorts
Liczba portów (dolnych) USB udostępniana przez koncentrator
UCHAR
wHubCharacteristics
Charakterystyka koncentratora. Szczegóły znajdują się w dokumentacji USB
UCHAR
bPowerOnToPowerGood
Czas określony w 2-milisekundowych odstępach, po którym jest ustalane napięcie w porcie. Czas jest liczony od momentu przyłączenia zasilania do portu. Więcej informacji na ten temat można znaleźć w specyfikacji USB
USHORT
bHubControlCurrent
Określa maksymalny pobór prądu w jednostkach [mA] wnoszony przez układ sterujący huba
USHORT
bRemoveAndPowerMask[64]
Aktualnie nieużywane w systemach Windows 7 i 8
W DK definiuje tę strukturę w module usb100.h w następujący sposób: typedef struct _USB_HUB_DESCRIPTOR { UCHAR bDescriptorLength; UCHAR bDescriptorType; UCHAR bNumberOfPorts; USHORT wHubCharacteristics; UCHAR bPowerOnToPowerGood; UCHAR bHubControlCurrent; UCHAR bRemoveAndPowerMask[64]; } USB_HUB_DESCRIPTOR, *PUSB_HUB_DESCRIPTOR; Definicja ta tworzy nowe słowa kluczowe: USB_HUB_DESCRIPTOR (struktura) i PUSB_HUB_ DESCRIPTOR (wskaźnik do struktury). Zaprezentowana w tabeli 3.7 struktura USB_HUB_INFORMATION zawiera informacje w y korzystywane podczas wywołania działającego w trybie jądra rozkazu IOCTL_USB_GET_ HUB INFORMATION. Tabela 3.7. Specyfikacja struktury USBHUBINFORMATION Typ
Element struktury
Znaczenie
USB_HUB_DESCRIPTOR
HubDescriptor
Deskryptor koncentratora USB
BOOLEAN
HubIsBusPowered
Znacznik określający rodzaj zasilania huba. Wartość TRUE oznacza, że hub jest zasilany z magistrali USB
W DK definiuje tę strukturę w module usbioctl.h w następujący sposób: typedef struct _USB_HUB_INFORMATION { USB_HUB_DESCRIPTOR HubDescriptor; BOOLEAN HubIsBusPowered; } USB_HUB_INFORMATION, *PUSB_HUB_INFORMATION;
Rozdział 3. ♦ Wstęp do transmisji danych
87
D efinicja ta tworzy nowe słowa kluczowe: USB_HUB_INFORMATION (struktura) i PUSB_ HUB_INFORMATION (wskaźnik do struktury). Elementy deskryptora huba funkcjonującego w trybie z superwysoką szybkością trans misji danych są reprezentowane w polach struktury USB_30_HUB_DESCRIPTOR, której za soby przedstawia tabela 3.8. Tabela 3.8. Specyfikacja struktury USB 30 HUB DESCRIPTOR Typ
Element struktury
Znaczenie
UCHAR
bLength
Liczba bajtów w deskryptorze urządzenia (długość deskryptora)
UCHAR
bDescriptorType
Określa typ deskryptora. W trybie SS należy wpisać wartość 0x2A
USHORT
bNumberOfPorts
Liczba portów (dolnych) USB udostępniana przez koncentrator
UCHAR
wHubCharacteristics
Charakterystyka huba. Szczegóły znajdują się w dokumentacji USB
UCHAR
bPowerOnToPowerGood
Czas określony w 2-milisekundowych odstępach, po którym jest ustalane napięcie zasilania w porcie. Oznacza to czas, po którym host uzyska dostęp do portu. Więcej informacji na ten temat można znaleźć w specyfikacji USB
USHORT
bHubControlCurrent
Określa maksymalny pobór prądu w jednostkach [mA]
USHORT
bHubHdrDecLat
Maksymalne opóźnienie w dekodowaniu nagłówka pakietu
USHORT
wHubDelay
Średnie opóźnienie transmisji wprowadzone przez hub wyrażone w jednostkach [ms]
USHORT
DeviceRemovable
Określa, czy urządzenie peryferyjne jest podłączone na stałe do portu huba (jest nieusuwalne) lub czy dopuszcza się możliwość jego odłączania
W DK definiuje tę strukturę w module usbspec.h (Windows 8) w następujący sposób: typedef struct _USB_30_HUB_DESCRIPTOR { UCHAR bLength; UCHAR bDescriptorType; UCHAR bNumberOfPorts; USHORT wHubCharacteristics; UCHAR bPowerOnToPowerGood; UCHAR bHubControlCurrent; UCHAR bHubHdrDecLat; USHORT wHubDelay; USHORT DeviceRemovable; } USB_30_HUB_DESCRIPTOR, *PUSB_30_HUB_DESCRIPTOR; Definicja ta tworzy nowe słowa kluczowe: USB_30_HUB_DESCRIPTOR (struktura) i PUSB_ 30_HUB_DESCRIPTOR (wskaźnik do struktury). Zaprezentow ana w tabeli 3.9 struktura USB_HUB_INFORMATION_EX zawiera informacje wykorzystywane podczas wywołania działającego w trybie jądra rozkazu IOCTL_USB_ GET HUB INFORMATION EX.
88
USB. Praktyczne programowanie z Windows API w C++
Tabela 3.9. Specyfikacja struktury USBHUBINFORM ATIONEX Typ
Element struktury
Znaczenie
USB_HUB_TYPE
HubType
Typ powinien określać główny hub 2.0 lub 3.0
USHORT
HighestPortNumber
Maksymalna liczba portów oferowanych przez koncentrator. Numeracja portów rozpoczyna się od wartości 1
USB_HUB_DESCRIPTOR
UsbHubDescriptor
Deskryptor koncentratora USB 1.x/2.0
USB_30_HUB_DESCRIPTOR
Usb30HubDescriptor
Deskryptor koncentratora USB 3.0
W DK definiuje tę strukturę w module usbioctl.h w następujący sposób: typedef struct _USB_HUB_INFORMATION_EX { USB_HUB_TYPE HubType; USHORT HighestPortNumber; union { USB_HUB_DESCRIPTOR UsbHubDescriptor; USB_30_HUB_DESCRIPTOR Usb30HubDescriptor; } u; } USB_HUB_INFORMATION_EX, *PUSB_HUB_INFORMATION_EX; Definicja ta tworzy nowe słowa kluczowe: USB_HUB_INFORMATION_EX (struktura) i PUSB_ HUB_INFORMATION_EX (wskaźnik do struktury). W polach unii USB_HUB_CAP_FLAGS są przechowywane informacje na tem at param e trów transferu danych oferowanych przez koncentrator USB, tak jak zaprezentowano w tabeli 3.10. Tabela 3.10. Specyfikacja unii USB HUB CAPFLAGS Typ
Element struktury
Znaczenie
ULONG
ul
Maska bitowa reprezentująca właściwości koncentratora
ULONG
HubIsHighSpeedCapable
TRUE — jeżeli koncentrator oferuje możliwość pracy w trybie HS
ULONG
HubIsHighSpeed
TRUE — jeżeli koncentrator jest skonfigurowany w trybie HS
ULONG
HubIsMultiTtCapable
TRUE — jeżeli koncentrator może wykonać wiele transakcji jednocześnie
ULONG
HubIsMultiTt
TRUE — jeżeli koncentrator jest skonfigurowany do wykonywania wielu transakcji jednocześnie
ULONG
HubIsRoot
TRUE — dla głównego koncentratora
ULONG
HubIsArmedWakeOnConnect
TRUE — koncentrator rozpoczyna transmisję w momencie podłączenia urządzenia
ULONG
HubIsBusPowered
FALSE — koncentrator nie jest zasilany z magistrali USB (ma własne zasilanie)
ULONG
ReservedMBZ
Zarezerwowane
W DK definiuje tę unię w module usbioctl.h w następujący sposób:
Rozdział 3. ♦ Wstęp do transmisji danych
89
typedef union _USB_HUB_CAP_FLAGS { ULONG ul; struct { ULONG HubIsHighSpeedCapabl e :1; ULONG HubIsHighSpeed :1; ULONG HubIsMultiTtCapable :1; ULONG HubIsMultiTt :1; ULONG HubIsRoot :1; ULONG HubIsArmedWakeOnConnect :1; ULONG HubIsBusPowered :1; ULONG ReservedMBZ :25; }; } USB_HUB_CAP_FLAGS, *PUSB_HUB_CAP_FLAGS; Definicja ta tworzy nowe słowa kluczowe: USB_HUB_CAP_FLAGS (unia) i PUSB_HUB_CAP_ FLAGS (wskaźnik do unii).
Punkty końcowe i deskryptory punktu końcowego Każde urządzenie dołączone do magistrali USB może być traktowane jako zbiór nieza leżnie wybieranych punktów końcowych (elementów, które są źródłem lub odbiorni kiem danych w urządzeniu). Wszystkie punkty końcowe realizują odpowiednie funkcje urządzenia. Każde urządzenie posiada zerowy punkt końcowy (EP0), za pośrednic tw em którego jest inicjowane i konfigurowane. Punkt EP0 jest konfigurowany bezpo średnio po dołączeniu urządzenia do magistrali USB i włączeniu zasilania. Tym samym automatycznie zostaje utworzony prowadzący do urządzenia domyślny (zerowy) po tok kontrolny wykorzystywany przez system operacyjny do przesyłania danych iden tyfikacyjnych i konfiguracyjnych (danych sterujących). Urządzenia wykonawcze transmitujące dane z niską szybkością zawierają dwa dodat kow e punkty końcowe. Przyrządy kom unikujące się z kom puterem przy wyższych szybkościach transmisji m ogą zawierać do 31 punktów końcowych. System rezerwuje dostęp do zerowego punktu końcowego EP0 domyślnego potoku kontrolnego, tak że realnie można skonfigurować 15 punktów końcowych EP IN oraz 15 typu EP OUT. Program uruchomiony na macierzystym komputerze (hoście) i obsługujący wybrane ze wnętrzne urządzenie wykonawcze komunikuje się z nim za pośrednictwem jego punk tów końcowych. Opis wszystkich punktów końcowych jest przechowywany w polach odpowiednich struktur (tabele 3.11 i 3.12). Charakterystyka punktów końcowych za wiera m.in. informacje na tem at adresu punktu końcowego, opisu kierunku przepływu danych, rozmiaru pakietu danych (pola roboczego), jakie urządzenie jest w stanie ode brać lub wysłać, oraz różnego rodzaju parametrów transmisji. Dane na temat punktów końcowych urządzeń USB są zapisywane w polach struktury USB_ENDPOINT, której opis zaprezentowano w tabeli 3.11.
90
USB. Praktyczne programowanie z Windows API w C++
Tabela 3.11. Specyfikacja struktury USB ENDPOINT Typ
Element struktury
Znaczenie
DWORD
dwCount
Rozmiar struktury w bajtach
USB_ENDPOINT_DESCRIPTOR
D escriptor
Wskaźnik do struktury zawierającej elementy deskryptora punktu końcowego
LPCVOID
lpvExtended
Wskaźnik ogólny do danych niebędących częścią opisu deskryptora punktu końcowego
W DK definiuje tę strukturę w module usbtypes.h jako: typedef struct _USB_ENDPOINT { const DWORD dwCount; const USB_ENDPOINT_DESCRIPTOR Descriptor; const LPCVOID lpvExtended; } USB_ENDPOINT, *PUSB_ENDPOINT, *LPUSB_ENDPOINT; D efinicja ta tworzy trzy nowe słowa kluczowe: USB_ENDPOINT (struktura) oraz PUSB_ ENDPOINT i LPUSB_ENDPOINT (wskaźniki do struktury). W tabeli 3.12 przedstawiono znaczenie elementów struktury deskryptora punktu koń cowego. Tabela 3.12. Specyfikacja struktury USBENDPOINTDESCRIPTOR Typ
Element struktury
Znaczenie
UCHAR
bLength
Rozmiar struktury w bajtach
UCHAR
bDescriptorType
Określa typ deskryptora USB_ENDPOINT_DESCRIPTOR_TYPE
UCHAR
bEndpointAddress
Adres i własności punktu końcowego (patrz rysunki 3.12 oraz 3.13). Cztery młodsze bity słowa D3 - D0 identyfikują numer punktu końcowego. Ósmy bit D7 określa kierunek transferu danych: 0b — host ^ urządzenie (OUT), 1b — urządzenie ^ host (IN). W przypadku kontrolnych punktów końcowych bit D7 jest ignorowany. W module usb100.h znajdują się makrodefinicje, za pomocą których można określić kierunek przepływu danych: U S B _ E N D P O IN T _ D IR E C T IO N _ M A S K
0x80
U S B _ E N D P O IN T _ D IR E C T IO N _ O U T (a d d r)
/ / l u b { \ ((a d d r)
& U S B _ E N D P O IN T _ D IR E C T IO N _ M A S K ))
U S B _ E N D P O IN T _ D IR E C T IO N _ IN (a d d r) / / lu b ( ( a d d r )
UCHAR
bmAttributes
& U S B _ E N D P O IN T _ D IR E C T IO N _ M A S K )
Bity D1 - D0 opisują podstawowe atrybuty punktu końcowego: 00b — punkt końcowy potoku kontrolnego, 01b — punkt końcowy potoku izochronicznego, 10b — punkt końcowy potoku masowego, 11b — punkt końcowy potoku przerwaniowego. Dla punktu końcowego potoku przerwaniowego bity D3 - D2 pozostają zarezerwowane. Bity D5 - D4 określają sposób wykorzystania punktu końcowego:
Rozdział 3. ♦ Wstęp do transmisji danych
91
Tabela 3.12. Specyfikacja struktury USB ENDPOINT DESCRIPTOR — ciąg dalszy Typ
Element struktury
Znaczenie
00b — transfer periodyczny, 01b — asynchroniczne powiadamianie, 10b — zarezerwowane, 11b — zarezerwowane. Dla punktu końcowego potoku izochronicznego bity D3 - D2 określają typ synchronizacji: 00b — brak synchronizacji, 01b — transfer asynchroniczny, 10b — transfer adaptacyjny, 11b — transfer synchroniczny. Bity D5 - D4 opisują sposób wykorzystania punktu końcowego: 00b — punkt końcowy dla transmisji danych, 01b — sprzężenie zwrotne, 10b — sprzężenie zwrotne dla danych, 11b — zarezerwowane. Dla masowych oraz kontrolnych punktów końcowych bity D5 - D2 pozostają wyzerowane. Dla wszystkich typów punktów końcowych bity D7 - D6 pozostają wyzerowane. W module usb100.h znajdują się makrodefinicje, za pomocą których można określić podstawowe atrybuty punktu końcowego (bity D1 - D0 bmAttributes): TYPE
MASK
0x03
E N D P O IN T > y p e ; C O N T R O L
0x00
E N D P O IN T
0x01
TYPE
E N D P O IN T
TYPE
IS O C H R O N O U S _ i
E N D P O IN T " t y p e "
CÛ
CÛ CÛ CÛ CÛ CQ
00 00 00 00 00
E N D P O IN T
0x02
IN TE R R U P T
0x03
W module usbspec.h (Windows 8) znajdują się makrodefinicje, za pomocą których można określić dodatkowe atrybuty punktu końcowego (bity D7 - D2 bmAttri butes): U SB _ E N D P O IN T _ T Y P E _ B U LK _ R E S E R V E D _ M A S K
0xFC
U S B _ E N D P O IN T _ T Y P E _ C O N T R O L _ R E S E R V E D _ M A S K
0xFC
U S B _ 2 0 _ E N D P O IN T _ T Y P E _ IN T E R R U P T _ R E S E R V E D _ M A S K
0xFC
U S B _ 3 0 _ E N D P O IN T _ T Y P E _ IN T E R R U P T _ R E S E R V E D _ M A S K
0xCC
U S B _ E N D P O IN T _ T Y P E _ IS O C H R O N O U S _ R E S E R V E D _ M A S K
0xC0
U S B _ 3 0 _ E N D P O IN T _ T Y P E _ IN T E R R U P T _ U S A G E _ M A S K
0x30
U S B _ 3 0 _ E N D P O IN T _ T Y P E _ IN T E R R U P T _ U S A G E _ P E R IO D IC
0x00
U S B _ 3 0 _ E N D P O IN T _ T Y P E _ IN T E R R U P T _ U S A G E _ N O T IF IC A T IO N
0x10
U S B _ 3 0 _ E N D P O IN T _ T Y P E _ IN T E R R U P T _ U S A G E _ R E S E R V E D 1 0
0x20
U S B _ 3 0 _ E N D P O IN T _ T Y P E _ IN T E R R U P T _ U S A G E _ R E S E R V E D 1 1
0x30
U S B _ 3 0 _ E N D P O IN T _ T Y P E _ IN T E R R U P T _ U S A G E (b m A ttr)
/ / l u b ( b m A t t r & U S B _ 3 0 _ E N D P O IN T _ T Y P E _ IN T E R R U P T _ U S A G E _ M A S K ) U S B _ E N D P O IN T _ T Y P E _ IS O C H R O N O U S _ S Y N C H R O N IZ A T IO N _ M A S K
0x0C
U S B _ E N D P O IN T _ T Y P E _ IS O C H R O N O U S _ S Y N C H R O N IZ A T IO N _ N O _ S Y N C H R O N IZ A T IO N
0x00
U S B _ E N D P O I N T _ T Y P E _ I S O C H R O N O U S _ S Y N C H R O N IZ A T I O N _ A S Y N C H R O N O U S
0x04
U S B _ E N D P O IN T _ T Y P E _ IS O C H R O N O U S _ S Y N C H R O N IZ A T IO N _ A D A P T IV E
0x08
USB
0x0C
E N D P O IN T
TYPE
IS O C H R O N O U S
S Y N C H R O N IZ A T IO N
SYN CH RON OU S
92
USB. Praktyczne programowanie z Windows API w C++
Tabela 3.12. Specyfikacja struktury USBENDPOINTDESCRIPTOR — ciąg dalszy Typ
Element struktury
Znaczenie U S B _ E N D P O IN T _ T Y P E _ IS O C H R O N O U S _ S Y N C H R O N IZ A T IO N (b m A ttr) / / lu b ( b m A t t r
& U S B _ E N D P O IN T _ T Y P E _ IS O C H R O N O U S _ S Y N C H R O N IZ A T IO N _ M A S K )
U S B _ E N D P O IN T _ T Y P E _ IS O C H R O N O U S _ U S A G E _ M A S K 0 x 3 0 U S B _ E N D P O IN T _ T Y P E _ IS O C H R O N O U S _ U S A G E _ D A T A _ E N D O IN T
0x00
U S B _ E N D P O IN T _ T Y P E _ IS O C H R O N O U S _ U S A G E _ F E E D B A C K _ E N D P O IN T
0x10
U S B _ E N D P O IN T _ T Y P E _ IS O C H R O N O U S _ U S A G E _ IM P L IC IT _ F E E D B A C K _ D A T A _ E N D P O IN T
0x20
U S B _ E N D P O IN T _ T Y P E _ IS O C H R O N O U S _ U S A G E _ R E S E R V E D 0 x 3 0 U S B _ E N D P O IN T _ T Y P E _ IS O C H R O N O U S _ U S A G E (b m A ttr) / / lu b ( b m A t t r
USHORT
wMaxPacketSize
& U S B _E N D P O IN T _ T Y P E _ IS O C H R O N O U S _ U S A G E _ M A S K )
Maksymalny rozmiar pakietu danych, który może być odebrany lub wysłany przez punkt końcowy podczas transmisji (patrz rozdział 1., tabela 1.10). Dla wszystkich rodzajów punktów końcowych funkcjonujących w trybach FS oraz HS bity D10 - D0 określają maksymalny rozmiar pakietu z danymi. Dla przerwaniowych oraz izochronicznych punktów końcowych pracujących w trybie HS bity D12 - D11 określają liczbę możliwych transakcji w trakcie jednej mikroramki: 00b — jedna transakcja w trakcie mikroramki, 01b — dwie transakcje w trakcie mikroramki, 10b — trzy transakcje w trakcie mikroramki, 11b — zarezerwowane. Bity D15 - D13 pozostają w niskim stanie logicznym. Dla przekazów kontrolnych realizowanych w trybie z superwysoką szybkością transferu danych rozmiar pakietu z danymi wynosi 512 bajtów. Dla przekazów masowych rozmiar pakietu z danymi wynosi 1024 bajty. Dla przekazów przerwaniowych oraz izochronicznych w trybie SS maksymalny rozmiar pakietu powinien wynosić 1024 bajty, pod warunkiem że do pola bMaxBurst (patrz tabela 3.13) wpisano wartość niezerową. Jeżeli pole bMaxBurst zawiera wartość 0, rozmiar pakietu danych można określić w szerokim przedziale od 0 do 1024 bajtów dla izochronicznych punktów końcowych oraz od 1 do 1024 bajtów dla przerwaniowych punktów końcowych
UCHAR
bInterval
Parametr określający odstęp czasowy ponawiania kolejnych przekazów w trakcie transmisji pomiędzy kolejnymi odpytywaniami punktu końcowego. Wyrażony jest w milisekundach dla trybów LS i FS, mikroramkach dla trybu HS lub mikrosekundach dla trybu SS. Dla przerwaniowych punktów końcowych pracujących w trybie FS wartość bInterval można wyznaczyć w przedziale <1; 255>. Dla izochronicznych punktów końcowych pracujących w trybach FS oraz HS wartość bInterval jest wyznaczana w przedziale <1; 16>. Dla przerwaniowych punktów końcowych pracujących w trybie HS wartość bInterval można wyznaczyć w przedziale <1; 16>. Dla izochronicznych punktów końcowych FS i HS oraz przerwaniowych punktów końcowych HS uogólnieniem jest następująca formuła:
Rozdział 3. ♦ Wstęp do transmisji danych
93
Tabela 3.12. Specyfikacja struktury USB ENDPOINT DESCRIPTOR — ciąg dalszy Typ
Elementstruktury
UCHAR
bInterval
Znaczenie
odstęp czasowy ponawiania kolejnych przekazów: ServiceInterval=2~(bInterval-1)xF, gdzie F = 1 ms dla urządzeń FS oraz F = 125 |is dla urządzeń HS. Dla bInterval = 1 odstęp czasowy ponawiania kolejnych przekazów jest równy czasowi trwania jednej ramki dla trybów LS i FS lub jednej mikroramki dla trybu HS. Dla masowych i kontrolnych punktów końcowych OUT pracujących w trybie HS parametr bInterval mieszczący się w przedziale <0; 255> powinien wyznaczać maksymalny odstęp czasowy (maksymalną liczbę mikroramek) pomiędzy odebraniem przez host pakietu NAK a wysłaniem kolejnego pakietu PING, jeżeli w odpowiedzi na pierwszy wysłany pakiet urządzenie odpowiedziało pakietem NAK. Wartość 0 oznacza, że punkt końcowy nie będzie wykorzystywał pakietu NAK. Dla wszystkich rodzajów transferów kontrolnych i masowych w trybach LS i FS oraz dla transferu kontrolnego IN oraz masowego IN w trybie HS parametr bInterval jest ignorowany. Dla izochronicznych oraz przerwaniowych punktów końcowych pracujących w trybie SS wartość bInterval mieści się w przedziale <1; 16>, przy czym uogólnieniem jest formuła: odstęp czasowy ponawiania kolejnych przekazów: ServiceInterval=2^(bInterval-1)x125ps.Dla bInterval = 1 odstęp czasowy ponawiania kolejnych przekazów jest równy jednostkowemu magistralowemu przedziałowi czasu wyznaczanemu przy superwysokiej szybkości transferu danych. Dla wszystkich rodzajów transferów kontrolnych i masowych w trybie SS parametr bInterval jest ignorowany
W DK definiuje tę strukturę w module usb100.h jako: typedef struct _USB_ENDPOINT_DESCRIPTOR { UCHAR bLength; UCHAR bDescriptorType; UCHAR bEndpointAddress; UCHAR bmAttributes; USHORT wMaxPacketSize; UCHAR bInterval; } USB_ENDPOINT_DESCRIPTOR, *PUSB_ENDPOINT_DESCRIPTOR; Definicja ta tworzy dwa nowe słowa kluczowe: USB_ENDPOINT_DESCRIPTOR (struktura) i PUSB_ENDPOINT_DESCRIPTOR (wskaźnik do struktury). D7 Kierunek
D6
D5
D4
Zarezerwowane (0)
D3
D2
Dl
DO
Numer punktu końcowego
Rysunek 3.12. Format pola bEndpointAddress struktury USBENDPOINTDESCRIPTOR. W trakcie wyznaczania adresu zerowego punktu końcowego EP0 bit D7 jest ignorowany
94
USB. Praktyczne programowanie z Windows API w C++
Rysunek 3.13. Idea wykorzystania pola bEndpointAddress struktury USB_ENDPOINT_DESCRIPTOR w trakcie komunikacji pomiędzy hostem a złożonym urządzeniem peryferyjnym. Adres urządzenia (Device Address) jest ustalany przez host w trakcie procesu enumeracji urządzeń dołączanych do magistrali USB (patrz rozdział 5.). Na rysunku zobrazowano podstawowy model zakładający, że każdy interfejs w ramach konfiguracji realizuje odrębny zestaw funkcji urządzenia USB Opisane w tabeli 3.13 pola struktury USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR są wykorzystywane przez sterowniki klienta do pobierania deskryptora urządzenia funkcjonującego w trybie z superwysoką szybkością transmisji danych. WDK definiuje tę strukturę w module usbspec.h (Windows 8) jako: typedef struct _USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR { UCHAR bLength; UCHAR bDescriptorType; UCHAR bMaxBurst; union { UCHAR AsUchar; struct { UCHAR MaxStreams :5; UCHAR Reservedl :3; } Bulk; struct { UCHAR Mult :2; UCHAR Reserved2 :6; } Isochronous; } bmAttributes; USHORT wBytesPerInterval; } USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR, *PUSB SUPERSPEED ENDPOINT COMPANION DESCRIPTOR;
Rozdział 3. ♦ Wstęp do transmisji danych
95
Tabela 3.13. Specyfikacja struktury USBSUPERSPEEDENDPOINTCOMPANIONDESCRIPTOR Typ
Element struktury
Znaczenie
UCHAR
bLength
Długość deskryptora w bajtach
UCHAR
bDescriptorType
Typ deskryptora. Należy wpisać USB SUPERSPEED ENDPOINT COMPANION_DESCRIPTOR
UCHAR
bMaxBurst
Maksymalna liczba pakietów <0; 15>, które punkt końcowy może odebrać lub wysłać bez konieczności oczekiwania na wystąpienie pakietu potwierdzenia. Wartość 0 oznacza, że punkt końcowy może odebrać lub wysłać jeden pakiet danych, zaś wartość 15 oznacza, że liczba pakietów z danymi przypadających na transakcję wynosi 16 (dla masowych oraz izochronicznych punktów końcowych). Dla przerwaniowych punktów końcowych wartość bMaxBurst nie może przekroczyć 2. Host ma możliwość dynamicznej zmiany wartości bMaxBurst w trakcie transferu. Dla kontrolnych punktów końcowych wartość bMaxBurst powinna być ustalona jako 0
UCHAR
AsUchar
Rozmiar struktur Bulk oraz Isochronous
UCHAR
MaxStreams
Wartość z zakresu <0; 16>, określająca maksymalną liczbę strumieni, które może obsłużyć punkt końcowy w trakcie transferu masowego. Wartość 0 oznacza, iż punkt końcowy nie obsługuje transferu strumieniowego. Dla wartości z przedziału od 1 do 16 liczba strumieni, które może obsłużyć punkt końcowy, wynosi 2^MaxStreams
UCHAR
Reservedl
Zarezerwowane
UCHAR
Mult
Maksymalna liczba pakietów = (bMaxBurst*(Mult+1)), które punkt końcowy może odebrać lub wysłać w wyznaczonym odstępie czasowym ponawiania kolejnych przekazów izochronicznych. Liczbowa wartość wpisana do pola Mult nie może przekraczać 2
UCHAR
Reserved2
Zarezerwowane
USHORT
wBytesPerInterval
Maksymalna liczba bajtów danych, które mogą być przetransmitowane do punktu końcowego lub poprzez ten punkt w określonym odstępie czasowym ponawiania kolejnych przekazów przerwaniowych lub izochronicznych
Definicja ta tworzy dwa nowe słowa kluczowe: USB_SUPERSPEED_ENDPOINT_COMPANION_ DESCRIPTOR (struktura) i PUSB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR (wskaźnik do struktury).
Interfejsy i deskryptory interfejsów urządzeń USB Każdy interfejs posiada własny deskryptor interfejsu (ang. interface descriptor), który m.in. określa liczbę źródeł danych (punktów końcowych) w urządzeniu wykonawczym.
96
USB. Praktyczne programowanie z Windows API w C++
M ożna zatem powiedzieć, że interfejs, będąc zbiorem określonych punktów końco wych, jest odpowiedzialny za funkcjonalność urządzenia. Jedno urządzenie może reali zować wiele różnych interfejsów. System operacyjny przechowuje informacje na temat deskryptora interfejsu w polach struktury USB_INTERFACE_DESCRIPTOR, której zasoby zostały opisane w tabeli 3.14. Tabela 3.14. Specyfikacja struktury USBINTERFACEDESCRIPTOR Typ
Element struktury
Znaczenie
UCHAR
bLength
Rozmiar struktury w bajtach
UCHAR
bDescriptorType
Określa typ deskryptora USB INTERFACE DESCRIPTOR TYPE
UCHAR
bInterfaceNumber
Identyfikuje indeks w tablicy interfejsów danej konfiguracji (numer interfejsu)
UCHAR
bA lternateS etting
Określa alternatywne ustawienia interfejsu identyfikowanego przez indeks w tablicy interfejsów danej konfiguracji. Alternatywne ustawienia interfejsu zawierają ten sam zestaw punktów końcowych właściwych dla interfejsu podstawowego, jednak z odmiennie zdefiniowanymi ich funkcjonalnościami, np. punkt końcowy potoku izochronicznego zdefiniowany w ramach interfejsu podstawowego może być punktem końcowym potoku masowego w alternatywnym ustawieniu tego interfejsu
UCHAR
bNumEndpoints
Liczba punktów końcowych (z wyłączeniem wartości domyślnej dla EP0)
UCHAR
bInterfaceC lass
Przechowuje kody klas bazowych zgodnych ze specyfikacją USB. Jeśli pole ma wartość FFH, urządzenie jest klasy opracowanej przez producenta
UCHAR
blnterfaceSubC lass
Przechowuje kody klas pochodnych związanych z klasami bazowymi określonymi przez bInterfaceC lass
UCHAR
bInterfaceProtocol
Określa kod protokołu dla danej klasy (i podklasy) urządzeń zgodnych ze standardem USB. Jeśli pole ma wartość FFH, urządzenie wykorzystuje protokół opracowany przez producenta
UCHAR
iIn te rfa c e
Przechowuje indeks deskryptora tekstowego opisującego dany interfejs. Wartość domyślna jest ustalona jako 0x1
W DK definiuje tę strukturę w module usbtypes.h jako: typedef struct _USB_INTERFACE_DESCRIPTOR { UCHAR bLength; UCHAR bDescriptorType; UCHAR bInterfaceNumber; UCHAR bAlternateSetting; UCHAR bNumEndpoints; UCHAR bInterfaceClass; UCHAR bInterfaceSubClass; UCHAR bInterfaceProtocol; UCHAR iInterface; } USB_INTERFACE_DESCRIPTOR, *PUSB_INTERFACE_DESCRIPTOR;
Rozdział 3. ♦ Wstęp do transmisji danych
97
Definicja ta tworzy dwa nowe słowa kluczowe: USB_INTERFACE_DESCRIPTOR (struktura) i PUSB_INTERFACE_DESCRIPTOR (wskaźnik do struktury). Zasoby struktur przechowujących w swoich polach informacje o deskryptorze inter fejsu urządzenia i deskryptorze punktu końcowego są wykorzystywane w strukturze USB_INTERFACE, zaprezentowanej w tabeli 3.15. Tabela 3.15. Specyfikacja struktury USB INTERFACE Typ
Element struktury
Znaczenie
DWORD
dwCount
Rozmiar struktury w bajtach
USB_INTERFACE_DESCRIPTOR
D escriptor
Wskaźnik do struktury zawierającej opis deskryptora interfejsu
LPCVOID (void*)
lpvExtended
Wskaźnik do danych, które są atrybutami nieopisującymi deskryptora interfejsu
LPCUSB_ENDPOINT
lpEndpoints
Wskaźnik do struktury zawierającej opis deskryptora punktu końcowego
W DK definiuje tę strukturę w module usbtypes.h jako: typedef struct _USB_INTERFACE { const DWORD dwCount; const USB_INTERFACE_DESCRIPTOR Descriptor; const LPCVOID lpvExtended; const LPCUSB_ENDPOINT lpEndpoints; } USB_INTERFACE, *PUSB_INTERFACE, *LPUSB_INTERFACE; Definicja ta tworzy trzy nowe słowa kluczowe: USB_INTERFACE (struktura) oraz PUSB_ INTERFACE i LPUSB_INTERFACE (wskaźniki do struktury). W polach struktury USBD_INTERFACE_INFORMATION (tabela 3.16) są przechowywane in formacje na temat interfejsu odpowiadającego danej konfiguracji urządzenia USB. Tabela 3.16. Specyfikacja struktury USBD INTERFACE INFORMATION Typ
Element struktury
Znaczenie
USHORT
bLength
Rozmiar struktury w bajtach
UCHAR
InterfaceNumber
Identyfikuje indeks w tablicy interfejsów danej konfiguracji
UCHAR
A ltern ateS ettin g
Określa alternatywne ustawienia interfejsu identyfikowanego przez indeks w tablicy interfejsów danej konfiguracji
UCHAR
Class
Przechowuje kody klas bazowych zgodnych ze specyfikacją USB
UCHAR
SubClass
Przechowuje kody klas pochodnych zgodnych ze specyfikacją USB
UCHAR
Protocol
Identyfikator protokołu odpowiadający danemu interfejsowi
98
USB. Praktyczne programowanie z Windows API w C++
Tabela 3.16. Specyfikacja struktury USBDINTERFACEINFORMATION — ciąg dalszy Typ
Element struktury
Znaczenie
UCHAR
Reserved
Zarezerwowane
USBD_INTERFACE_HANDLE
InterfaceH andle
Identyfikator używany przez kontroler hosta w celu uzyskania dostępu do interfejsu
ULONG
NumberOfPipes
Liczba punktów końcowych dla danego interfejsu
USBD PIPE INFORMATION
Pipes[1]
Wskaźnik do pierwszego elementu w tablicy struktur USBD PIPE INFORMATION
W DK definiuje tę strukturę w module usb.h w następujący sposób: typedef struct _USBD_INTERFACE_INFORMATION { USHORT Length; UCHAR InterfaceNumber; UCHAR AlternateSetting; UCHAR Class; UCHAR SubClass; UCHAR Protocol; UCHAR Reserved; USBD_INTERFACE_HANDLE InterfaceHandle; ULONG NumberOfPipes; USBD_PIPE_INFORMATION Pipes[1]; } USBD_INTERFACE_INFORMATION, *PUSBD_INTERFACE_INFORMATION Definicja ta tworzy dwa nowe słowa kluczowe: USBD_INTERFACE_INFORMATION (struk tura) oraz PUSBD_INTERFACE_INFORMATION (wskaźnik do struktury). Urządzenie scharakteryzowane wieloma interfejsami MI (ang. Multiple Interfaces) je st opisywane w dokumentacji USB jako urządzenie złożone. Interfejsy urządzenia złożonego mogą być częścią określonej konfiguracji lub występować niezależnie. Każdy niezależnie funkcjonujący interfejs urządzenia złożonego je st traktowany jako urządzenie potomne na stosie urządzeń USB, dla którego odpowiedni sterownik USB tworzy fizyczny obiekt urządzenia PDO. Dla każdego skonfigurowanego inter fejsu jest tworzony odrębny PDO. Identyfikator sprzętu (ang. hardware ID) dla inter fejsu PDO urządzenia złożonego ma następującą postać: USB\VID_v(4)&PID_p(4)&MI_z(2) gdzie: v(4) je st czterocyfrowym identyfikatorem producenta VID przypisanym zgodnie z zaleceniami standardu USB, p(4) je st czterocyfrowym identyfikatorem produktu PID przypisanym przez pro ducenta urządzenia, z(2) je s t numerem interfejsu przechowywanym w polu bInterfaceNumber deskryptora interfejsu. Jeżeli oprogramowanie systemowe nie jest w stanie przypisać do urządzenia właści wego sterownika klienta, ładowany jest nadrzędny rodzajowy sterownik Usbccgp.sys. Sterownik ten generuje identyfikatory zgodności (ang. compatible IDs), korzystając z zasobów deskryptora interfejsu:
Rozdział 3. ♦ Wstęp do transmisji danych
99
USB\CLASS_d(2)&SUBCLASS_s(2)&PROT_p(2), USB\CLASS_d(2)&SUBCLASS_s(2), USB\CLASS_d(2) gdzie: d(2) je st kodem klasy bazowej (bInterfaceC lass), s(2) je st kodem klasy pochodnej (bInterfaceSubClass), p(2) je st kodem protokołu (bInterfaceProtocol). Podstawowy model konfiguracji pokazany na rysunku 3.13 zakłada relacje typu jeden-do-jednego, występujące między interfejsami a funkcjami zdefiniowanymi w ramach urządzenia. Jednak wraz z upowszechnieniem się standardu USB pojawiły się specy fikacje klas instalacji, w których zdefiniowano funkcje urządzeń realizujących kilka interfejsów. Programowa obsługa tego typu rozwiązania powinna umożliwiać zarów no wykorzystanie osobnego sterownika dla każdej funkcji urządzenia, jak i realizację kilku interfejsów przez pojedynczy sterownik. Zdefiniowany w dokumencie [16] deskryptor przypisania interfejsów IAD (ang. Interface Association Descriptor) opisuje zasady jednoczesnej realizacji więcej niż dw óch interfejsów przez funkcje zdefinio wane w urządzeniu. W tabeli 3.17 zamieszczono specyfikację tego deskryptora, zaś na rysunku 3.14 zobrazowano model konfiguracji, w którym funkcje urządzenia reali zują wiele interfejsów jednocześnie. Tabela 3.17. Specyfikacja struktury USB INTERFACE ASSOCIATION DESCRIPTOR Typ
Element struktury
Znaczenie
UCHAR
bLength
Rozmiar struktury w bajtach
UCHAR
bDescriptorType
Określa typ deskryptora USB INTERFACE ASSOCIATION DESCRIPTOR_TYPE
UCHAR
b F irs tIn te rfa c e
Numer pierwszego interfejsu właściwego danej funkcji urządzenia
UCHAR
bInterfaceCount
Liczba kolejnych interfejsów właściwych danej funkcji urządzenia
UCHAR
bFunctionClass
Kod klasy przypisany przez USB IF. Jeśli pole ma wartość FFH, urządzenie jest klasy opracowanej przez producenta
UCHAR
bFunctionSubClass
Kod podklasy klasy przypisany przez USB IF
UCHAR
bFunctionProtocol
Kod protokołu dla danej klasy (i podklasy) urządzeń zgodnych ze standardem USB
UCHAR
iFunction
Indeks deskryptora znakowego opisującego daną funkcję
W DK definiuje tę strukturę w module usb100.h w następujący sposób: typedef struct _USB_INTERFACE_ASSOCIATION_DESCRIPTOR { UCHAR bLength; UCHAR bDescriptorType; UCHAR bFirstInterface; UCHAR bInterfaceCount; UCHAR bFunctionClass; UCHAR bFunctionSubClass; UCHAR bFunctionProtocol; UCHAR iFunction; } USB_INTERFACE_ASSOCIATION_DESCRIPTOR, *PUSB_INTERFACE_ASSOCIATION_DESCRIPTOR;
100
USB. Praktyczne programowanie z Windows API w C++
Rysunek 3.14. Model konfiguracji urządzenia USB z deskryptorem przypisania interfejsów Definicja ta tworzy dwa nowe słowa kluczowe: USB_INTERFACE_ASSOCIATION_DESCRIPTOR (struktura) oraz PUSB_INTERFACE_ASSOCIATION_DESCRIPTOR (wskaźnik do struktury).
Konfiguracje i deskryptory konfiguracji Urządzenia USB m ogą być rozmaicie konfigurowane, przy czym każdej konfiguracji można przypisać odpow iednią liczbę interfejsów. Zbiór interfejsów, które m ogą być jednocześnie obsłużone, tworzy konfigurację urządzenia. Urządzenia USB m ogą mieć wiele konfiguracji, jednak w danym momencie tylko jedna z nich może być aktywna. Urządzenia klasy HID są z reguły opisywane za pom ocą jednej konfiguracji. Zaprezentowane w tabeli 3.18 zasoby struktury USB_CONFIGURATION zawierają informa cje na tem at różnych parametrów konfiguracyjnych urządzenia. WDK definiuje tę strukturę w module usbtypes.h jako: typedef struct _USB_CONFIGURATION { const DWORD dwCount; const USB_CONFIGURATION_DESCRIPTOR Descriptor; const LPCVOID lpvExtended; const DWORD dwNumInterfaces; const LPCUSB_INTERFACE lpInterfaces; } USB_CONFIGURATION, * PUSB_CONFIGURATION, * LPUSB_CONFIGURATION;
Rozdział 3. ♦ Wstęp do transmisji danych
101
Tabela 3.18. Specyfikacja struktury USB CONFIGURATION Typ
Element struktury
Znaczenie
DWORD
dwCount
Rozmiar struktury w bajtach (długość deskryptora)
USB_CONFIGURATION_DESCRIPTOR
D escriptor
Wskaźnik do struktury USB_CONFIGURATION_DESCRIPTOR
LPCVOID
lpvExtended
Wskaźnik do danych aktualnie niebędących elementami deskryptora konfiguracji
DWORD
dwNumInterfaces
Liczba interfejsów odpowiadająca danej konfiguracji
LPCUSB_INTERFACE
lp In te rfa c e s
Wskaźnik do struktury USB INTERFACE
Definicja ta tworzy trzy nowe słowa kluczowe: USB CONFIGURATION (struktura), PUSB CONFIGURATION i LPUSB CONFIGURATION (wskaźniki do struktury). Z kolei każda konfiguracja je st opisana przez odrębny deskryptor konfiguracji (ang. configuration descriptor), zawierający m.in. informację o konfiguracyjnych własno ściach urządzeń w systemie. W systemie operacyjnym elementy deskryptora konfigu racji są reprezentowane w polach struktury USB CONFIGURATION DESCRIPTOR, której za soby są przedstawione w tabeli 3.19. Tabela 3.19. Specyfikacja struktury USB CONFIGURATION DESCRIPTOR Typ
Element struktury
Znaczenie
UCHAR
bLength
Rozmiar struktury w bajtach
UCHAR
bDescriptorType
Stała o wartości USB_CONFIGURATION_DESCRIPTOR_TYPE, określająca typ deskryptora
USHORT
wTotalLength
Pole struktury zawiera informacje o łącznej długości deskryptorów konfiguracji (reprezentowanych przez macierzystą strukturę), interfejsów (reprezentowanych przez strukturę USB INTERFACE DESCRIPTOR), punktów końcowych (reprezentowanych przez strukturę USB_ENDPOINT_DESCRIPTOR) oraz dane specyficzne dla klasy urządzenia i (lub) producenta
UCHAR
bNumInterfaces
Określa liczbę interfejsów przypisanych danej konfiguracji
bConfigurationValue
Liczba umożliwiająca wybór danej konfiguracji w standardzie USB 1.0
UCHAR
iC onfiguration
Indeks deskryptora tekstowego opisującego daną konfigurację
UCHAR
bmAttributes
Ośmiobitowe pole przechowujące charakterystykę urządzenia. Pięć pierwszych bitów słowa jest zarezerwowanych i ustawionych w niskim stanie logicznym. Jeżeli piąty bit jest ustawiony na 1, w urządzeniu dostępna jest funkcja zdalnej konfiguracji. Ustalenie wysokiej wartości logicznej dla szóstego bitu informuje, że urządzenie może mieć zarówno własne, jak i zewnętrzne źródło zasilania. Ustalony bit siódmy informuje, że urządzenie jest zasilane z magistrali USB
UCHAR
102
USB. Praktyczne programowanie z Windows API w C++
Tabela 3.19. Specyfikacja struktury USBCONFIGURATIONDESCRIPTOR — ciąg dalszy Typ
Element struktury
Znaczenie
UCHAR
MaxPower
Określa maksymalny pobór prądu zasilania [mA]. Wartości są określane z krokiem 2 mA dla urządzeń USB 2.0 lub 8 mA dla urządzeń USB 3.0. Pole to jest używane, jeżeli urządzenie jest zasilane za pośrednictwem magistrali USB
W DK definiuje tę strukturę w module usb100.h jako: typedef struct { UCHAR bLength; UCHAR bDescriptorType; USHORT wTotalLength; UCHAR bNumInterfaces; UCHAR bConfigurationValue; UCHAR iConfiguration; UCHAR bmAttributes; UCHAR MaxPower; } USB_CONFIGURATION_DESCRIPTOR; Definicja ta tworzy nowe słowo kluczowe USB_CONFIGURATION_DESCRIPTOR (struktura). Zasoby struktur przechowujących w swoich polach informacje o deskryptorze urządze nia i konfiguracji są wykorzystywane w strukturze USB_DEVICE, której zasoby zostały po kazane w tabeli 3.20. Wskaźnik do tej struktury zwraca funkcja LPGET_DEVICE_INFO(): typedef LPCUSB_DEVICE (* LPGET_DEVICE_INFO)( USB HANDLE hDevice — gdzie hDevice jest identyfikatorem urządzenia USB. Tabela 3.20. Specyfikacja struktury USB_DEVICE Typ
Element struktury
Znaczenie
DWORD
dwCount
Rozmiar struktury w bajtach
USB_DEVICE_DESCRIPTOR
D escriptor
Wskaźnik do struktury USB_DEVICE_DESCRIPTOR
LPCUSB_CONFIGURATION
lpConfigs
Wskaźnik do tablicy struktur USB_CONFIGURATION
LPCUSB CONFIGURATION
lpActiveConfig
Wskaźnik do aktualnie używanej (aktywnej) struktury USB_CONFIGURATION
W DK definiuje tę strukturę w module usbtypes.h jako: typedef struct _USB_DEVICE j const DWORD dwCount; const USB_DEVICE_DESCRIPTOR Descriptor; const LPCUSB_CONFIGURATION lpConfigs; const LPCUSB_CONFIGURATION lpActiveConfig; j USB_DEVICE, *PUSB_DEVICE, *LPUSB_DEVICE; Definicja ta tworzy trzy nowe słowa kluczowe: USB_DEVICE (struktura) oraz PUSB_DEVICE i LPUSB_DEVICE (wskaźniki do struktury).
Rozdział 3. ♦ Wstęp do transmisji danych
103
Zaprezentow ana w tabeli 3.21 struktura HUB_DEVICE_CONFIG_INFO zawiera inform a cje wykorzystywane podczas w yw ołania działającego w trybie jądra rozkazu IOCTL_ INTERNAL_USB_GET_DEVICE_CONFIG_INFO, umożliwiającego uzyskanie informacji na te m at aktualnie podłączonych do koncentratora urządzeń peryferyjnych. Tabela 3.21. Specyfikacja struktury HUB DEVICE CONFIG INFO Typ
Element struktury
Znaczenie
ULONG
Version
Numer wersji. Należy wpisać 1
ULONG
Length
Rozmiar struktury
USB_HUB_CAP_FLAGS
HubFlags
Określa właściwości huba zapisane w unii USB_HUB_CAP_FLAGS
USB_ID_STRING
HardwareIds
Informacje na temat identyfikatora sprzętu PnP zapisanego w polach struktury USB ID STRING
USB_ID_STRING
CompatibleIds
Informacje na temat identyfikatora zgodności sprzętu PnP zapisanego w polach struktury USB_ID_STRING
USB_ID_STRING
DeviceDescription
Opis urządzenia zapisany w polach struktury USB_ID_STRING
W DK definiuje tę strukturę w module usbioctl.h w następujący sposób: typedef struct _HUB_DEVICE_CONFIG_INFO { ULONG Version; ULONG Length; USB_HUB_CAP_FLAGS HubFlags; USB_ID_STRING Hardwarelds; USB_ID_STRING Compatiblelds; USB_ID_STRING DeviceDescription; } HUB_DEVICE_CONFIG_INFO, *PHUB_DEVICE_CONFIG_INFO; Definicja ta tworzy nowe słowa kluczowe: HUB_DEVICE_CONFIG_INFO (struktura) i PHUB_ DEVICE_CONFIG_INFO (wskaźnik do struktury). W tabeli 3.22 zaprezentowano opis pól struktury USB_ID_STRING. Tabela 3.22. Specyfikacja struktury USB_ID_STRING Typ
Element struktury
Znaczenie
USHORT
LanguageId
Identyfikator języka użytego do opisu urządzenia [17]
LONG
LengthInBytes
Długość łańcucha znaków zawierającego tekst opisu identyfikatorów urządzenia
PWCHAR
Buffer
Wskaźnik do bufora danych zawierającego tekst opisu identyfikatorów urządzenia
W DK definiuje tę strukturę w module usbioctl.h w następujący sposób: typedef struct _USB_ID_STRING { USHORT LanguageId; ULONG LengthInBytes; PWCHAR Buffer; } USB_ID_STRING, *PUSB_ID_STRING;
10 4
USB. Praktyczne programowanie z Windows API w C++
Definicja ta tworzy dwa nowe słowa kluczowe: USB_ID_STRING (struktura) oraz PUSB ID_STRING (wskaźnik do struktury).
Deskryptory tekstowe Deskryptory urządzeń, konfiguracji oraz interfejsów m ogą zawierać odniesienia do deskryptorów tekstowych (zwanych też łańcuchowymi), przechowujących informacje w form ie opisowej w postaci łańcuchów znaków zapisanych w form acie Unicode. W tabeli 3.23 przedstawiono opis pól struktury deskryptora tekstowego. Tabela 3.23. Specyfikacja struktury USBSTRINGDESCRIPTOR Typ
Element struktury
Znaczenie
UCHAR
bLength
Długość deskryptora w bajtach
UCHAR
bDescriptorType
Typ deskryptora. Należy wpisać USB STRING DESCRIPTOR TYPE
WCHAR
bString[1]
Wskaźnik do bufora danych zawierającego właściwy tekst
W DK definiuje tę strukturę w module usb100.h jako: typedef struct _USB_STRING_DESCRIPTOR { UCHAR bLength; UCHAR bDescriptorType; WCHAR bString[1]; } USB_STRING_DESCRIPTOR, *PUSB_STRING_DESCRIPTOR; D efinicja ta tworzy dwa nowe słowa kluczowe: USB_STRING_DESCRIPTOR (struktura) i PUSB_STRING_DESCRIPTOR (wskaźnik do struktury).
Komunikacja programu użytkownika z urządzeniem W systemach operacyjnych Windows programy użytkownika (aplikacje), aby nawią zać komunikację z zewnętrznym urządzeniem przyłączonym do portu USB, powinny uzyskać dostęp do odpowiednich sterowników tego urządzenia. Sterowniki urządze nia stanow ią element pośredniczący między w arstw ą funkcjonalną i sprzętem. Apli kacje kom unikują się ze sterownikami urządzenia za pom ocą odpowiedniej warstwy komunikacyjnej (zbioru funkcji) systemu operacyjnego. Ze względu na to, że Windows chroni dostęp do przestrzeni adresowej wejścia-wyjścia (I/O), dla aplikacji niezbędne stają się sterowniki umożliwiające dostęp do fragmentu lub całej przestrzeni I/O. Z punktu widzenia podsystemu PnP wyróżniamy trzy głów ne typy sterowników:
Rozdział 3. ♦ Wstęp do transmisji danych
105
♦ Sterownik (lub stos sterowników) m agistrali danych (ang. bus driver) obsługujący kontroler magistrali1. ♦ Sterownik operacyjny (ang. function driver) lub klienta (ang. client driver). Jest to główny sterownik urządzenia, udostępniający użytkownikowi odpowiedni interfejs operacyjny. Sterownik operacyjny jest niezbędny, chyba że urządzenie je st standardowym urządzeniem przyłączonym do magistrali (w tym przypadku operacje I/O są wykonywane przez sterownik magistrali i jej dowolne sterowniki filtrujące). Sterownik klienta urządzenia z reguły ma postać pary: sterownik klasy - ministerownik (ang. minidriver). W takich parach sterownik klasy oferuje funkcje wymagane przez wszystkie urządzenia danego typu, zaś ministerownik oferuje funkcje specyficzne dla określonego urządzenia. Sterowniki klienta m ogą być — w zależności od potrzeb — urucham iane w trybie użytkow nika lub w trybie jądra. W tabeli 3.24 zaprezentowano zestawienie najw ażniejszych systemowych sterowników właściwych dla określonych klas urządzeń. Tabela 3.24. Najważniejsze systemowe sterowniki klas urządzeń Klasa
Sterownik klasy
Audio
Usbaudio.sys
Communications Device Class (CDC)
Usb8023.sys
Bluetooth
Bthusb.sys
Imaging
Usbscan.sys
Hub Device
Usbhub.sys
Human In te rfa c e Device (HID)
Hidclass.sys Hidusb.sys
Mass Storage
Usbstor.sys
Media T ransfer Protocol Devices
Wpdusb.sys
P rin te r
Usbprint.sys
Smart Card
Usbccid.sys
♦ Sterowniki filtrujące (ang. filte r drivers), których głównym zadaniem jest sortowanie żądań I/O skierowanych do magistrali (ang. bus filter driver), klasy urządzeń (ang. class filte r driver) lub konkretnego urządzenia (ang. device filte r driver). Sterowniki m ogą być tworzone zgodnie z modelami KMDF (ang. Kernel-Mode Driver Framework), UM DF (ang. User-Mode D river Framework) lub W DM (ang. Windows D river M odel). W systemach operacyjnych Windows program może być uruchomiony w dwóch try bach: użytkownika i jądra systemu. Każdy z nich charakteryzuje się innym sposobem uzyskiwania dostępu do pamięci i pozostałych zasobów komputera. Aplikacje zawsze
1 Każdy typ magistrali obecny w komputerze posiada własne sterowniki.
106
USB. Praktyczne programowanie z Windows API w C++
są uruchamiane w trybie użytkownika, natomiast sterowniki portu w trybie jądra, tak ja k pokazano na rysunku 3.15.
Rysunek 3.15. Szczegółowy model warstwowy sterowników USB w systemie Windows 8. Zawartość biblioteki WinUSB jest omawiana w rozdziale 7. Na rysunku pominięto informacje na temat biblioteki HID, ponieważ zostanie ona dokładnie przedstawiona w rozdziale 4. Na rysunku pominięto też informacje na temat omawianej w rozdziale 7. biblioteki LibUSB, gdyż nie jest ona częścią zasobów systemu operacyjnego W arstwa sterowników uruchamianych w trybie jądra i znajdująca się powyżej stosu sterowników USB um ożliw ia kom unikację między sterownikami klienta i sprzętem. Tak zorganizowane środowisko komunikacji powoduje, że program użytkownika nie może uzyskać bezpośredniego dostępu do portu USB — dostęp do portu uzyskuje po przez sterownik przypisany do urządzenia. D la urządzenia złożonego (scharakteryzowanego w ielom a interfejsami), dla którego nie został przydzielony standardowy sterownik klasy, system operacyjny ładuje nad rzędny rodzajowy sterownik USB (Usbccgp.sys), który je st umiejscawiany pomiędzy w arstw ą sterowników klienta a sterownikiem głównego huba. Jeżeli nie funkcjonuje mechanizm IAD, nadrzędny rodzajowy sterownik tworzy odrębne fizyczne obiekty urządzeń PDO dla każdego interfejsu urządzenia złożonego. Sterowniki klienta umiej scawiane powyżej nadrzędnego rodzajowego sterownika tw orzą operacyjne obiekty urządzeń FDO oraz opcjonalnie filtrujące obiekty urządzeń Filter DO.
Rozdział 3. ♦ Wstęp do transmisji danych
107
Dostarczany przez system operacyjny sterownik Usbd.sys (ang. Universal Serial Bus Driver) zapewnia dostęp do biblioteki funkcji eksportowych wykorzystywanych przez sterowniki klienta. Za pośrednictwem Usbd.sys sterownik klienta kieruje do stosu ste rowników USB żądania URB (patrz opis struktury URB w rozdziale 6.), które dotyczą w yboru określonej konfiguracji urządzenia, jego interfejsu itp. Biblioteka Usbdex.lib udostępnia funkcje eksportowe przetwarzające żądania URB zgodnie z wymaganiami systemu Windows 8. Stos sterowników USB 2.0 składa się ze: ♦ sterownika głównego huba (usbhub.sys) dającego dostęp do wszystkich koncentratorów funkcjonujących w systemie, ♦ stosu sterowników kontrolera hosta (ang. host controller driver). Z kolei stos sterowników kontrolera hosta (zwanego też niekiedy sterownikiem portu — ang. p o rt driver) składa się z: ♦ sterownika portu (usbport.sys), ♦ jednego, dw óch lub trzech ministerowników (ang. m iniport drivers; usbuhci.sys, usbohci.sys lub usbehci.sys) umożliwiających komunikację z jednym z trzech typów kontrolerów: UHCI, OHCI lub EHCI. Sterowniki kontrolera hosta poprzez nadzorowanie przesyłania pakietów w (mikro)ramkach są odpowiedzialne za komunikację między aplikacjami USB a wybranym proto kołem transportowym. N a początku każdej (mikro)ramki sterownik kontrolera hosta USB 1.x/2.0 generuje pakiet SO F będący znacznikiem początku (mikro)ramki. W ob rębie każdej (mikro)ramki są przesyłane pakiety z danymi albo w kierunku od hosta do urządzenia peryferyjnego (OUT), albo w kierunku przeciwnym (IN). Transfery są zawsze inicjowane przez jednostkę nadrzędną (host). Korzystając ze sterowników kon trolera hosta, aplikacje USB uzyskują bezpośredni dostęp do urządzeń bez konieczno ści znajomości sprzętowej implementacji warstwy transportowej. W standardach USB 1.x/2.0 istnieją trzy specyfikacje opisujące interfejs dostępu do urządzeń: UHCI (ang. Universal H ost Controller Interface), OHCI (ang. Open Host Controller Interface) oraz EHCI (ang. Enhanced H ost Controller Interface). Specy fikacja UHCI opisuje interfejs dostępu do urządzeń USB wprowadzony przez firmę Intel dla standardu USB 1.x. Opracowany przez Compaq, M icrosoft i National Semi conductor OHCI jest otwartym standardem określającym sposób współpracy kompu tera (hosta) z urządzeniami USB pracującymi z niskimi (LS) oraz pełnymi (FS) szyb kościam i transm isji danych. Z kolei ulepszony interfejs EHCI dostępu do urządzeń USB został wprowadzony przez firmy Compaq i M icrosoft dla magistrali USB 2.0. EHCI był standardem mającym zastąpić OHCI z jednoczesnym zapewnieniem zacho wania kompatybilności wstecznej. N a potrzeby standardu USB 3.0 Intel opracował rozszerzony interfejs dostępu do urzą dzeń xHCI (ang. eXtensible H ost Controller Interface). xHCI poprzez umożliwienie obsługi zarów no urządzeń USB 3.0, ja k i 2.0 zapew nia dużo w iększe m ożliwości w porów naniu z poprzednikam i. Głównymi zaletam i xHCI są: wprowadzony model programowania w pełni odzwierciedlający nowy standard USB, możliwość realizacji
108
USB. Praktyczne programowanie z Windows API w C++
seryjnej transmisji pakietów i strumieniowego transferu masowego oraz ograniczenie zużycia pam ięci operacyjnej, w czasie kiedy urządzenia pozostają w stanie niskiego poboru energii. Stos sterowników USB 3.0 składa się z: ♦ sterownika głównego huba (Usbhub3.sys), ♦ sterownika kontrolera hosta (Usbxhci.sys), ♦ rozszerzenia ( Ucx01000.sys), które poza bieżącymi zadaniami jest przewidziane do obsługi innych przyszłościowych typów sterowników klasy xHCI. Do głównych zadań sterownika huba należy zarządzanie koncentratorami USB oraz ich portami, wykonywanie procedur wyliczania (enumeracji) urządzeń przyłączanych do portów huba oraz kreowanie fizycznych obiektów urządzeń PDO dla przyłączone go sprzętu. Sterownik kontrolera hosta dostarcza ogólny interfejs dla urządzeń USB 3.0 i je st odpowiedzialny przede wszystkim za planowanie przepływu transferów da nych w systemie. Do najważniejszych zadań sterownika Ucx01000.sys należy dostar czanie ogólnego interfejsu dla sterownika huba oraz odpowiednie kolejkowanie żądań kierowanych do sterownika kontrolera hosta. Szczegółowe informacje na temat stosu sterowników w systemie Windows 8 są dostępne z poziomu okna Menedżer urządzeń, tak jak zaprezentowano na rysunkach 3.16 - 3.18. Rysunek 3.16. Kontrolery uniwersalnej magistrali szeregowej w systemie Windows 8
Menedżer urządzeń Plik
Akcja
u d > j
W idok
-
n
E f l
Pomoc
Kontrolery IDE ATA/ATAPI
A
<$-Kontrolery magazynu P Kontrolery uniwersalnej magistrali szeregowej P Etron USB 3.0 extensible Host Controller - 0100 (Microsoft) ^ Etron USB 3.0 extensible Host Controller - 0100 (Microsoft) ^ Generic USB Hub ^ Generic USB Hub ^ Główny koncentrator USB ^ Główny koncentrator USB P Główny koncentrator USB (xHCI) P Główny koncentrator USB (xHCI) P Rozszerzony kontroler hosta 1 USB2 mikroukładu z serii Intel(R) C600/X79 — 1D26 P Rozszerzony kontroler hosta 2 USB2 mikroukładu z serii Intel(R) C600/X79 — 1D2D P Urządzenie kompozytowe USB P Urządzenie pamięci masowej USB
[> Ę h Monitory
V
Sterownik klienta komunikuje się ze sterownikami USB poprzez wysyłanie pewnych zgłoszeń w postaci pakietów IRPs (ang. I/O Request Packets). Przed rozpoczęciem po łączenia z programem użytkownika odpowiedni sterownik tworzy reprezentujący go obiekt urządzenia2 i rejestruje swój identyfikator, który daje możliwość rozpoznania 2
W systemie operacyjnym urządzenie reprezentowane jest przez egzemplarz struktury danych DEVICE_OBJECT zwanej obiektem urządzenia (ang. Device Object). Obiekt urządzenia reprezentuje logiczne, wirtualne lub fizyczne urządzenie, dla którego odpowiedni sterownik przechwytuje właściwe zgłoszenia I/O. Każdy sterownik reprezentuje jeden lub więcej obiektów urządzenia.
Rozdział 3. ♦ Wstęp do transmisji danych
109
Rysunek 3.18. Stos sterowników USB 3.0 go przez program y kontrolne, ja k również rejestruje zestaw funkcji w ywoływanych w momencie, gdy program użytkownika chce nawiązać komunikację ze sterownikiem. Jako parametr do każdej z takich funkcji system przekazuje odpowiednią strukturę IRP, która zawiera kod wykonywanej operacji, bufor z danymi wyjściowymi (ang. output buffer), jakie są przekazywane do sterownika, oraz bufor danych wejściowych (ang. input buffer), w którym sterownik umieszcza odpowiedź. Przetwarzaniem poszczegól nych IRP na transakcje (w ram ach ramki, mikroramki lub magistralowego przedziału czasu), których rozmiar jest dostosowany do rozmiarów odpowiedniego bufora danych (punktu końcowego) urządzenia, zajmuje się sterownik kontrolera hosta.
110
USB. Praktyczne programowanie z Windows API w C++
Podsumowanie W niniejszym rozdziale omówiono najważniejsze pojęcia służące do opisu transmisji danych w standardzie USB. Przedstawiono najważniejsze struktury danych przecho wujące informacje o organizacji oraz najistotniejszych parametrach systemu USB. W ie le praktycznych sposobów wykorzystania omówionych zasobów systemowych zosta nie przedstawionych w dalszej części książki.
Rozdział 4.
Urządzenia klasy HID Do opisu urządzeń klasy HID są wykorzystywane omówione w rozdziale 3. standardo we deskryptory urządzeń, konfiguracji, interfejsu, punktu końcowego oraz łańcuchowe. Oprócz standardow ych klasa HID im plementuje deskryptor właściwy dla urządzeń HID, deskryptor raportu oraz deskryptor fizyczny.
Deskryptor raportu U rządzenia klasy HID kom unikują się z komputerem, wykorzystując kontrolne lub przerwaniowe transfery danych. Informacje są przesyłane poprzez potoki (dostarcza ne do potoku) w specjalnej form ie zwanej raportam i. Ich formaty są zdefiniowane w deskryptorach raportów. D eskryptor raportu z kolei może mieć albo prostą formę sekwencji bajtów reprezentujących jed n ą wielkość, albo złożoną formę reprezentującą wiele param etrów o różnym znaczeniu. N a rysunku 4.1 pokazano budowę przykła dowego deskryptora raportu dla myszy. D eskryptor ten został wygenerowany przez program narzędziowy HID Descriptor Tool, który można bezpłatnie pobrać ze strony: http://www.usb.org/developers/hidpage/. Każdy deskryptor raportu składa się z kilku głównych elementów, których znaczenie omówiono poniżej. W specyfikacji urządzeń klasy HID w ystępują predefiniowane grupy urządzeń w y szczególnione na pozycji Usage Page identyfikującej odpow iednią przestrzeń nazw w deskryptorze raportu. Każda z predefiniowanych grup urządzeń jest reprezentowa na przez dane typu USAGE lub PUSAGE, których definicja wygląda następująco: t y p e d e f USHORT USAGE, *PUSAGE;
Pozycja Usage zaw iera identyfikator urządzenia prostego lub elementu urządzenia związanego z konkretną jego funkcją w ram ach urządzenia złożonego. N a przykład liczba 0x06 występująca na pozycji Usage oznacza klawiaturę, która w rzeczywistości jest zintegrowanym urządzeniem wskazującym, stanowiącym element składowy stan dardowego komputera stacjonarnego Usage Page (Generic Desktop).
112
USB. Praktyczne programowanie z Windows API w C++
Rysunek 4.1. Przykładowy deskryptor raportu dla myszy
tKtHID Descriptor Tool(DT) - D:\usb 3.0\HidView\MSDEV\Projects\test\mouse.hid File
Edit
Parse Descriptor
Ia
I — ll
A b o ut
HID Items
usxse— l :±.g e _ p a g e
USAGE_MINIMUM USAGE.MAXIMUM DESIGNATORSNDEX DESIGNATOR_MINIMUM D ESIGNATOR S AXIMUM 5TRING_INDEX 5TRING_MINIMUM 5TRING_MAXIMUM COLLECTION END_COLLECTION INPUT OUTPUT FEATURE LOGICAL_MI NIM UM LOGICAL_MAXIMUM PHYSICAL_MI NIM UM PHYSICAL_MAXIMUM UNIT_EXPONENT UNIT REPORT_SIZE REPORT_ID REPORT_COU NT
Report Descriptor USAGE_PAGE ( G e n e r ic D e sk tc P) USAGE (Mouse) COL L ECTION ( A p p li c a t i on) USAGE ( P o in t e r ) COLLECTION ( P h y s ic a l) U5AGE_PAGE ( B u t to n ) U5AGE_MIMIMUM ( B u tto n 1) U5AGE_MAXIMUM ( B u tto n 3) LOGIC AL_M I NIM UM (0) LOGICAL_MAXIMUM (1) REPORT_COU NT (3) REPORT_SIZE (1) INPUT ( D a t a , V a r ,A b s) REPORT_COUNT (1) REPORT_SIZE (5) INPUT ( C n s t .V a r ,A b s ) USAGE_PAGE ( G e n e r ic D e sk to p ) USAGE (X) USAGE (Y) LOGICAL_MI NIM UM ( -1 2 7 ) LOGICAL_MAXIMUM (127) REPORT_SIZE (8) REPORT_COU NT (2) INPUT ( D a t a , V a r ,R e l) END_COLLECTION END_COLLECTION
05 09 A1 09 A1 05 19 29 15 25 95 75 81 95 75 81 05 09 09 15 25 75 95 81 CO CO
01 02 01 01 00 09 01 03 00 01 03 01 02 01 05 03 01 30 31 81 7F 08 02 06
Manual E n t r y C le a r D e s c r ip t o r
/ / Usages f r o m G en eri c Desktop Page (0x01) # d e f i n e HI D_ USAGE_GENERIC_POINTER
((USAGE) 0x01 )
# d e f i n e HID_USAGE_GENERIC_MOUSE
((USAGE) 0x02 )
# d e f i n e H I D_ USA GE _GE NER IC _J OYS TI CK
((USAGE) 0x04 )
# d e f i n e HID_USAGE_GENERIC_GAMEPAD
((USAGE) 0x05 )
# d e f i n e HID_USAGE_GENERIC_KEYBOARD
( ( u SA G e ) 0x06 )
# d e f i n e HID_USAGE_GENERIC_KEYPAD
((USAGE) 0x07 )
# d e f i n e HID_USAGE_GENERIC_SYSTEM_CTL
((USAGE) 0x80 )
//...
Z kolei głównymi elementami każdej klawiatury są różnego rodzaju przyciski: / / Keybo ard/Keypad Page //... # d e f i n e HID_USAGE_KEYBOARD_ aA
((USAGE)
0x04 )
# d e f i n e HID_USAGE_KEYBOARD_ zZ
((USAGE)
0x1D)
/ / Numbers # d e f i n e HID_USAGE_KEYBOARD_ ONE
((USAGE)
0x 1E)
# d e f i n e HID_USAGE_KEYBOARD_ ZERO
((USAGE)
0x27 )
# d e f i n e HID_USAGE_KEYBOARD_ LCTRL
((USAGE)
0x E0)
# d e f i n e HID_USAGE_KEYBOARD_ LSHFT
((USAGE)
0x E1)
/ / M o d i f i e r Keys
//...
M oduł hidusage.h i dokumentacja USB dla urządzeń klasy HID [10] zawierają szcze gółowe kody wszystkich urządzeń.
Pozycje Collection i End Collection Elementy składowe deskryptora raportu są wyszczególniane na odpowiednich pozy cjach, ograniczonych klauzulami Collection i E nd Collection, gdzie identyfikowane są związki pomiędzy co najmniej dwoma typami danych (Input, Output lub Future), jakie
Rozdział 4. ♦ Urządzenia klasy HID
113
wykorzystuje urządzenie. N a przykład najprostszy lokalizator, jakim jest mysz, może być opisany jako zbiór dwóch, czterech lub pięciu zestawów danych (x, y, przycisk 1, przycisk 2, przycisk 3). Postać deskryptora raportu w sposób abstrakcyjny odzwier ciedla strukturę warstw ow ą systemu USB, może być zatem przedstawiona w postaci trzech głów nych struktur danych opisujących poszczególne warstwy: funkcjonalną lub aplikacji, fizyczną i logiczną. Dodatkowo producent urządzenia może zdefiniować w łasną warstwę, tak ja k pokazano w tabeli 4.1. Tabela 4.1. Struktura warstwowa deskryptora raportu Pozycja
W artość
Opis struktury danych
Collection (1Ah)
00h
Fizyczna
01h
Aplikacji
02h
Logiczna
03h - 7Fh
Zarezerwowane
80h - FFh
Zdefiniowana przez producenta
End Collection (C0h)
Brak
W arstwa aplikacji zawiera funkcje danego urządzenia wspólne dla aplikacji. W arstwa fizyczna zawiera pozycje reprezentujące opis struktur danych, które mogą być transmi towane do lub odbierane z pojedynczego punktu końcowego urządzenia. N a przykład lokalizator i przyciski wykorzystywane przez mysz są uwzględnione w warstwie fizycznej deskryptora raportu (patrz rysunek 4.1). N a pozycjach opisujących elementy warstwy fizycznej odnajdujemy m.in. informacje na temat sposobów działania elementów skła dowych urządzenia oraz wszelkie dane na temat raportów wejściowych, wyjściowych i konfiguracyjnych, za pom ocą których można się komunikować z urządzeniem. W arstwa logiczna przechowuje struktury danych pośredniczące między warstwami apli kacji i fizyczną. Elementami pośredniczącymi są na przykład bufory danych i rozmia ry buforów danych. Tak zdefiniowane warstwy deskryptora mogą być zagnieżdżone.
Rodzaje raportów Przesłanie danych sterujących do urządzenia wykonawczego USB jest równoznaczne z w ysłaniem tzw. raportu wyjściowego (ang. output report). Zawiera on dane, które opisują aktualny stan urządzenia. Odczyt (zapis) danych pochodzących z urządzenia jest równoznaczny z odebraniem raportu wejściowego (ang. input report). Dane służące do konfigurowania urządzeń m ają postać raportów konfiguracyjnych (ang. future reports). Jeżeli do obsługi urządzenia niezbędne je st wysłanie lub odebranie więcej niż jednego raportu tego samego typu, każdy raport pow inien mieć unikatowy identyfikator (ang. report ID ) zapisany w bajcie zerowym.
11 4
USB. Praktyczne programowanie z Windows API w C++
Zawartość raportów Sposób kom unikacji aplikacji użytkow nika z urządzeniem powinien odzwierciedlać fizyczną naturę elementów sterujących przyrządem. Elementy sterujące lub kontrolne (ang. control), za pośrednictwem których użytkownik obsługuje dane urządzenie, mogą się charakteryzować dyskretnym lub ciągłym sposobem działania. Elementami sterującymi o dyskretnym sposobie działania są przyciski (ang. buttons), które m ogą pozostawać w dwóch dyskretnych stanach logicznych: naciśniętym (wy soki, tru e lub ON) i nienaciśniętym (niski, fa lse lub OFF). Służą one do wydawania poleceń. Najprostszymi przykładami występowania takich elementów w realnym urzą dzeniu są przyciski Caps Lock, Shift i Tab na klawiaturze komputera. W przypadku myszy dyskretne sterowanie polega na naciśnięciu i szybkim zwolnieniu prawego lub lewego przycisku (gdy mysz jest nieruchoma). Przykładem elementów kontrolnych o ciągłym sposobie działania są diody LED, a ste rujących — wszelkiego rodzaju lokalizatory służące do przekazywania komputerowi inform acji w postaci złożonego zestawu w artości (ang. value) o zmianie położenia (współrzędnych X , Y , Z) lokalizatora, zgodnie z prawoskrętnym układem współrzęd nych (rysunek 4.2).
Programy komputerowe współpracujące z lokalizatorami pow odują wyświetlenie na ekranie znaku śledzenia lub odpowiednie przetwarzanie w skazań i zmienianie jego położenia (lub wskazań) zgodnie z ruchami lokalizatora. Najprostszymi przykładami występowania takich elementów w realnych urządzeniach są wszelkiego typu manipu latory lub potencjometry. N a rysunkach 4.3 i 4.4 pokazano odpowiednio uproszczoną i szczegółową budowę ra portu wejściowego dla hipotetycznego lokalizatora używanego do wykonywania ope racji przeciągania (przesuwania wybranego elementu z jednoczesnym przytrzymaniem przycisku). Raport taki będzie się składać z opcjonalnego identyfikatora raportu (za pisanego w bajcie zerowym), dwóch zestawów ciągłych wartości określających współ rzędne lokalizatora: Value X, Value Y, oraz z dyskretnych wartości przypisanych je d nemu z przycisków Button.
Rozdział 4. ♦ Urządzenia klasy HID
Raport ID
115
W artość X
W artość Y
Przycisk
Rysunek 4.3. Uproszczona budowa raportu wejściowego dla nieskomplikowanego lokalizatora Rysunek 4.4. Przykład szczegółowej postaci raportu wejściowego
0
Baj t 0
7
Bajt1
15
Ba| 2
23
Bajt 3
31
Format danych W deskryptorze raportu rozm iar i form at danych opisują dwie pozycje. N a pozycji R eport Size specyfikuje się liczbę bitów (rozmiar) przeznaczonych dla danego pola w raporcie wyjściowym, wejściowym lub konfiguracyjnym. Wartość na pozycji Report Count określa liczbę pól w raporcie. Jeżeli na przykład raport zawiera trzy jednobajtowe (8-bitowe) pola, to Report Size=8 i Report Count=3.
Zakresy wartości danych Zakres wartości danych, które może zawierać raport, jest ściśle określony poprzez zde finiowanie ich maksymalnej i minimalnej wartości logicznej (ang. logical m aximum/ minimum). Posługiwanie się jednostkam i logicznymi powoduje, że dane są niezależne od przyjętego systemu miar. Załóżmy, że hipotetyczny przyrząd może mierzyć napię cie prądu w granicach 0 - 100 V z krokiem 0,2 V. Zakres zmienności danych wyrażo nych w jednostkach logicznych wyniesie wówczas <0, 500>. M oże zaistnieć sytuacja, w której urządzenie USB posiada wiele elementów sterują cych o dyskretnym sposobie działania (wiele przycisków), z których kilka w danej chwili może się znajdować w w ysokim stanie logicznym ON. D la każdego takiego elem entu je st definiowana unikalna wartość maksim um fizycznego, odpowiadająca maksimum logicznemu. D la elementów sterujących o dyskretnym sposobie działania wartości maksimów fizycznych definiuje się w formie binarnej, dlatego też elementy kontrolne tego typu są źródłem wartości binarnych.
Jednostki miar W deskryptorze raportu pozycje Physical M inimum (minimum fizyczne), Physical M aximum (maksimum fizyczne), Unit Exponent (postać wykładnicza o podstawie 10) oraz Unit (jednostki) określają jednostki, w jakich są wyrażane dane zawarte w raporcie.
116
USB. Praktyczne programowanie z Windows API w C++
W przykładzie z hipotetycznym przyrządem, który może mierzyć napięcie prądu w gra nicach od 0 do 100 V z krokiem 0,2 V, zakres zmienności danych wyrażonych w je d nostkach fizycznych wyniesie <0, 100>. Pozycja Unit określa jednostki, w jakich są wyrażane dane po tym, ja k zostały przekonwertowane za pom ocą pozycji Physical i Unit Exponent. Specyfikacja HID defi niuje kody dla sześciu podstaw ow ych jednostek miar: długości, masy, czasu, tem pe ratury, natężenia prądu, intensywności światła, tak jak pokazano w tabeli 4.2. Tabela 4.2. Specyfikacja formatu zapisu jednostek miar zawartych w raporcie System miar (półbajt = 0) Numer półbajtu
Wielkość mierzona
i
Żaden (0)
Liniowy SI (1)
Kątowy SI (2)
Liniowy angielski (3)
Kątowy angielski (4)
Długość
Brak
Centymetr
Radian
Cal
Stopień
2
Masa
Brak
Gram
3
Czas
Brak
Sekunda
4
Temperatura
Brak
Kelvin
5
Natężenie prądu
Brak
Amper
ó
Natężenie światła
Brak
Kandela
l
Zarezerwowane
Brak
Stopień Fahrenheita
Podstawowe funkcje urządzeń klasy HID Tak jak schematycznie pokazano na rysunku 4.5, transfer danych (raportów) pomiędzy urządzeniem a zainstalowanym na komputerze oprogramowaniem właściwym dla urzą dzeń klasy HID może być realizowany dwojako. Raporty HID mogą być transportowa ne domyślnym potokiem kontrolnym lub odpowiednio skonfigurowanymi potokami przerwaniowymi. W trakcie transferu kontrolnego punkt końcowy hosta (komputera) inicjuje przekaz danych, korzystając z funkcji API, takich jak: HidD_SetOutputReport(), HidD_GetInputReport(), HidD_GetFeatureReport() oraz HidD_SetFeatureReport(). Funkcje HidD_SetOutputReport() oraz HidD_GetInputReport() um ożliwiają aplikacji wysyłanie oraz odbieranie odpowiednio raportów wyjściowych OUT oraz wejściowych IN. Parametry funkcji HidD_SetOutputReport() zawierają identyfikator urządzenia HID, bufor z raportem wyjściowym oraz liczbę transmitowanych bajtów danych. Podobnie aplikacja użytkownika, wywołując funkcję HidD_GetInputReport(), wymaga znajomo ści identyfikatora urządzenia wykonawczego, określenia bufora wejściowego, w którym będzie przechowywany odebrany raport, oraz liczby pakietów, które aplikacja spodzie w a się otrzymać. Funkcje HidD_GetFeatureReport() oraz HidD_SetFeatureReport() służą do transmitowania raportów konfiguracyjnych.
Rozdział 4. ♦ Urządzenia klasy HID
117
Rysunek 4.5. Sposób komunikacji komputera z urządzeniem wykonawczym klasy HID Raporty HID m ogą być również przesyłane potokami przerwaniowymi. W sytuacji kie dy wybrany przerwaniowy punkt końcowy je st skonfigurowany w trybie IN lub OUT, należy skorzystać odpowiednio z usług funkcji ReadFile() oraz W riteF ile(), tak jak zostanie to zaprezentowane w rozdziale 6. W tym podrozdziale zostaną przedstawione podstawowe, niezarezerwowane dla sys temu funkcje rodzin HidD_Xxx() oraz HidP_Xxx() , pomagające w w ymianie danych kontrolnych między aplikacją użytkow nika uruchom ioną na komputerze a sterowni kami reprezentującymi zewnętrzne urządzenie wykonawcze klasy HID.
Funkcje rodziny HidD_Xxx() Funkcje dające dostęp do sprzętowo niezależnych sterowników urządzeń (ang. Hard ware Independent Device Driver) um ożliwiają wykorzystanie kodów źródłowych po zwalających na stworzenie programowalnego interfejsu urządzeń. W przypadku więk szości aplikacji nadmiernie szczegółowa znajomość urządzenia jest zbyteczna, dlatego też funkcje tej rodziny przechowują tyle informacji o sprzęcie, ile można.
Funkcja HidD_GetAttributes() Funkcja HidD_GetAttributes() pobiera atrybuty urządzenia klasy HID. BOOLEAN Hi d D _ G e t A t t r i b u t e s ( IN HANDLE
H id D e v ice O b je c t,
OUT PHIDD_ATTRIBUTES
A ttrib u te s
);
Param etr wejściowy HidDeviceObject jest identyfikatorem urządzenia klasy HID. Pa rametrem wyjściowym jest wskaźnik A ttrib u tes do struktury HIDD_ATTRIBUTES, której zasoby są przedstawione w tabeli 4.3. Udostępniają one informacje na temat produktu i producenta danego urządzenia.
118
USB. Praktyczne programowanie z Windows API w C++
Tabela 4.3. Specyfikacja struktury HIDD ATTRIBUTES Typ
Element struktury
Znaczenie
ULONG
Size
Rozmiar struktury w bajtach
USHORT
VendorID
Określa identyfikator producenta urządzenia HID
USHORT
ProductID
Określa identyfikator danego urządzenia HID
USHORT
VersionNumber
Określa numer wersji urządzenia HID
W DK definiuje tę strukturę jako: t y p e d e f s t r u c t _HIDD_ ATT RIB UTE S { ULONG USHORT
S iz e ; VendorID ;
USHORT
Pro ductID ;
USHORT
V e rs io n N u m b e r;
} HIDD _AT TR IBU TES , *PHIDD_ ATT RIB UT ES;
Definicja ta tworzy dwa nowe słowa kluczowe: HIDD_ATTRIBUTES (struktura) i PHIDD_ ATTRIBUTES (wskaźnik do struktury). W przypadku prawidłowego w ykonania funkcja HidD_GetAttributes() zwraca w ar tość praw dziw ą (TRUE), w przeciw nym razie — fałsz (FALSE). Użycie funkcji HidD_ G e tA ttrib u te s() w aplikacji użytkow nika je st opcjonalne dla urządzeń klasy HID. Sterowniki działające w trybie jąd ra systemu zam iast funkcji HidD_GetAttributes() używaj ą rozkazu IOCTL_HID_GET_COLLECTION_INFORMATION.
Funkcja HidD_GetFeature() Poprzez parametr ReportBuffer funkcja HidD_GetFeature() pobiera informacje na te mat właściwości konfiguracyjnych urządzenia HID. BOOLEAN H id D _ G e tF e a tu re ( IN HANDLE OUT PVOID IN ULONG
H id D e v ice O b je c t, R e portBu ffer, R e portBufferLength
);
HidDeviceObject jest identyfikatorem urządzenia. Wskaźnik ogólny ReportBuffer wska zuje bufor danych, poprzez który są przekazywane dane konfiguracyjne w postaci ra portów. Jeżeli obsługa urządzenia wymaga identyfikatora raportu, zawartość pierwsze go bajtu bufora powinna być niezerowa; w razie potrzeby do pierwszego bajtu bufora należy wpisać NULL (wartość domyślna). Param etr wejściowy ReportBufferLength określa rozmiar bufora w bajtach. W trakcie określania rozmiarów bufora danych na leży zwrócić uwagę na możliwość występowania identyfikatora raportu, co powinno skutkow ać pow iększeniem rozm iaru bufora o je d e n bajt. Jeżeli liczba w ysyłanych bajtów jest większa od podanej, to urządzenie odsyła tylko tyle bajtów, ile określono w parametrze ReportBufferLength. Prawidłowo wykonana funkcja HidD_GetFeature() zwraca wartość TRUE.
Rozdział 4. ♦ Urządzenia klasy HID
119
Użycie funkcji HidD_GetFeature() w aplikacji użytkownika nie jest obligatoryjne dla urządzeń klasy HID. Sterow niki działające w trybie jąd ra system u zam iast funkcji HidD_GetFeature() używ ają rozkazu IOCTL_HID_GET_FEATURE.
Funkcja HidD_SetFeature() Funkcja umożliwia włączenie określonej właściwości konfiguracyjnej urządzenia po przez wysłanie odpowiedniego raportu. BOOLEAN H id D _ S e tF e a tu re ( IN HANDLE
H id D e v ice O b je c t,
IN PVOID
R e portBu ffer,
IN ULONG
R e portBufferLength
);
Parametr wejściowy HidDeviceObject jest identyfikatorem urządzenia. Wskaźnik ogól ny ReportBuffer wskazuje bufor danych wyjściowych. Jeżeli dane m ają identyfikator, pierwszy bajt bufora powinien zawierać wartość niebędącą wskaźnikiem pustym. Pa rametr wejściowy ReportBufferLength określa rozmiar w bajtach bufora danych (dłu gość wysyłanego raportu). W trakcie określania rozmiaru bufora danych należy zwró cić uwagę na możliwość występowania identyfikatora raportu, co powinno skutkować powiększeniem rozmiaru bufora o jed en bajt ((PUCHAR)ReportBuffer + 1). Praw idłow o w ykonana funkcja zw raca w artość prawdziwą. U życie funkcji HidD_ S etFeature() w aplikacji użytkow nika je st opcjonalne dla urządzeń klasy HID. Ste rowniki działające w trybie jądra systemu zamiast funkcji HidD_SetFeature() używają rozkazu IOCTL_HID_SET_FEATURE.
Funkcja HidD_GetHidGuid() Z a pośrednictwem parametru HidGuid funkcja HidD_GetHidGuid() zwraca identyfika tor GUID interfejsu, jaki sterownik urządzenia klasy HID udostępnia warstwie aplikacji. VOID H id D _ G e tH id G u id ( OUT LPGUID
H id G u id
);
W skaźnik HidGuid wskazuje strukturę GUID. Użycie funkcji HidD_GetHidGuid() w apli kacji użytkownika jest obligatoryjne dla urządzeń klasy HID.
Funkcja HidD_GetInputReport() Funkcja umożliwia odczyt (zapis) danych z urządzenia w postaci raportu. BOOLEAN
s td ca ll
H id D _ G e tIn p u tR e p o r t( IN HANDLE IN OUT IN ULONG );
H id D e v ice O b je c t,
PVOID
R e portBu ffer,
R e portBufferLength
120
USB. Praktyczne programowanie z Windows API w C++
Param etr w ejściow y HidDeviceObject je s t identyfikatorem urządzenia klasy HID. Wskaźnik ogólny ReportBuffer wskazuje bufor danych wejściowych. Jeżeli dane ma j ą identyfikator, pierwszy b ajt bufora pow inien zawierać wartość niebędącą w skaź nikiem pustym. Param etr wejściowy ReportBufferLength określa rozm iar w bajtach bufora danych (długość raportu). W trakcie określania bufora danych należy zwrócić uwagę na możliwość w ystępow ania identyfikatora raportu, co powinno skutkować powiększeniem rozmiaru bufora o jed en bajt ( (PUCHAR)ReportBuffer + 1). Jeżeli licz ba wysyłanych bajtów jest większa od podanej, to urządzenie odsyła tylko tyle bajtów, ile określono w parametrze ReportBufferLength. Praw idłow o w ykonana funkcja zw raca w artość prawdziwą. U życie funkcji HidD_ GetInputReport() w aplikacji użytkownika jest obligatoryjne dla urządzeń klasy HID. Sterowniki działające w trybie jądra systemu zamiast funkcji HidD_GetInputReport() używ ają rozkazu IOCTL_HID_GET_INPUT_REPORT.
Funkcja HidD_SetOutputReport() Funkcja um ożliwia wysłanie do urządzenia danych w postaci raportu. BOOLEAN
s td ca ll
H id D _ S etO u tp u tR ep ort( IN HANDLE
H id D e v ice O b je c t,
IN PVOID
R e portBu ffer,
IN ULONG
R e portBufferLength
);
Param etr w ejściow y HidDeviceObject je s t identyfikatorem urządzenia klasy HID. Wskaźnik ogólny ReportBuffer wskazuje bufor danych wyjściowych. Jeżeli dane ma j ą identyfikator, pierwszy bajt bufora powinien zawierać wartość niebędącą wskaźni kiem pustym. Parametr wejściowy ReportBufferLength określa rozmiar w bajtach bu fora danych dla raportu. W trakcie określania rozmiaru bufora należy zwrócić uwagę na możliwość w ystępow ania identyfikatora raportu, co powinno skutkować powięk szeniem rozmiaru bufora o jeden bajt ((PUCHAR)ReportBuffer + 1). Praw idłow o w ykonana funkcja zw raca w artość prawdziw ą. U życie funkcji HidD_ SetO utputReport() w aplikacji użytkow nika je st opcjonalne. Sterowniki działające w trybie ją d ra system u zam iast funkcji HidD_SetOutputReport() używ ają rozkazu IOCTL_HID_SET_OUTPUT_REPORT.
Funkcja HidD_SetNumInputBuffers() Funkcja um ożliw ia powiadom ienie sterownika urządzenia identyfikowanego przez HidDeviceObject o maksymalnej liczbie buforów dla raportów wejściowych. BOOLEAN H idD _SetN um InputBuffers( IN HANDLE
H id D e v ice O b je c t,
OUT ULONG
N um berBuffers
);
Rozdział 4. ♦ Urządzenia klasy HID
121
Prawidłowo wykonana funkcja zwraca wartość TRUE, w przeciwnym wypadku należy spodziewać się, że do NumberBuffers została przypisana niewłaściwa liczba. Standar dowo sterowniki urządzeń klasy HID um ożliw iają pobranie przynajmniej dwóch ra portów wejściowych. W W indows 2000 maksym alna liczba buforów wejściowych wynosi 200, a sterowniki instalowane w systemach Windows XP, Vista, 7 oraz 8 mogą się posługiwać 512 buforami wejściowymi (wartością domyślną jest liczba 32). Użycie funkcji HidD_SetNumInputBuffers() w aplikacji użytkownika nie jest obligato ryjne dla urządzeń klasy HID. Sterowniki działające w trybie jądra systemu zamiast funkcji HidD_SetNumInputBuffers() używ ają rozkazu IOCTL_SET_NUM_DEVICE_INPUT_ BUFFERS.
Funkcja HidD_GetNumInputBuffers() Funkcja um ożliwia określenie (pobranie) liczby aktualnie dostępnych buforów dla ra portów wejściowych. BOOLEAN H id D _ G e tN u m In p u tB u ffe rs( IN HANDLE
H id D e v ice O b je c t,
OUT PULONG
N um berBuffers
);
Param etr w ejściow y HidDeviceObject je s t identyfikatorem urządzenia klasy HID. W skaźnik NumberBuffers wskazuje dane typu unsigned long. Użycie funkcji HidD_ GetNumInputBuffers() w aplikacji użytkow nika nie je st obligatoryjne dla urządzeń klasy HID. Sterowniki działające w trybie jądra systemu zam iast tej funkcji używają rozkazu IOCTL_GET_NUM_DEVICE_INPUT_BUFFERS.
Funkcja HidD_GetManufacturerString() Funkcja umożliwia pobranie łańcucha znaków zakończonego zerowym ogranicznikiem, który identyfikuje producenta urządzenia określonego przez HidDeviceObject. BOOLEAN H id D _ G e tM a n u fa ctu re rS trin g ( IN HANDLE OUT PVOID IN ULONG
H id D e v ice O b je c t, B u ffe r, BufferLength
);
W skaźnik ogólny Buffer wskazuje bufor danych, w którym zostanie umieszczony od czytany łańcuch znaków typu wchar_t. Parametr BufferLength określa rozmiar (w baj tach) bufora danych. Jeżeli rozmiar bufora został określony niewłaściwie (jest mniej szy niż wymagany), funkcja zwróci wartość FALSE. Długość łańcucha identyfikującego producenta urządzenia USB może wynosić maksymalnie 126 znaków typu wchar_t (z wyłączeniem zerowego ogranicznika, tzn. znaku NULL). Użycie funkcji HidD_GetManufacturerString() w aplikacji użytkownika jest opcjonal ne dla urządzeń klasy HID. Sterowniki działające w trybie jądra systemu zamiast tej funkcji używ ają rozkazu IOCTL_HID_GET_MANUFACTURER_STRING.
122
USB. Praktyczne programowanie z Windows API w C++
Funkcja HidD_GetProductString() Funkcja pobiera łańcuch znaków zakończony zerowym ogranicznikiem, który identy fikuje urządzenie określone przez HidDeviceObject. BOOLEAN Hi d D _ G e t P r o d u c t S t r i n g ( IN HANDLE OUT PVOID IN ULONG
H id D e v ice O b je c t, B u ffe r, BufferLength
);
W skaźnik ogólny Buffer wskazuje bufor danych, w którym zostanie umieszczony od czytany łańcuch znaków typu wchar_t. Parametr BufferLength określa rozmiar (w baj tach) bufora danych. Jeżeli rozmiar bufora został określony niewłaściwie (jest mniej szy niż wymagany), funkcja zwróci wartość FALSE. Długość łańcucha identyfikującego urządzenie USB może wynosić maksymalnie 126 znaków typu wchar_t (z wyłączeniem zerowego ogranicznika, tzn. znaku NULL). Użycie funkcji HidD_GetProductString() w aplikacji użytkownika nie je st obligatoryj ne dla urządzeń klasy HID. Sterowniki działające w trybie jąd ra systemu zamiast tej funkcji używ ają rozkazu IOCTL_HID_GET_PRODUCT_STRING.
Funkcja HidD_GetSerialNumberString() Funkcja pobiera łańcuch znaków identyfikujący num er seryjny urządzenia określane go przez HidDeviceObject. BOOLEAN Hi d D _ G e t S e r i a l N u m b e r S t r i n g ( IN HANDLE OUT PVOID IN ULONG
H id D e v ice O b je c t, B u ffe r, BufferLength
);
W skaźnik ogólny Buffer wskazuje bufor danych, w którym zostanie umieszczony od czytany łańcuch znaków typu wchar_t. Parametr BufferLength określa rozmiar (w baj tach) bufora danych. Jeżeli rozmiar bufora został określony niewłaściwie (jest mniej szy niż wymagany), funkcja zwróci wartość FALSE. Długość łańcucha identyfikującego numer seryjny urządzenia USB może wynosić maksymalnie 126 znaków typu wchar_t (z wyłączeniem zerowego ogranicznika, tzn. znaku NULL). Użycie funkcji HidD_GetSerialNumberString() w aplikacji użytkownika nie jest obli gatoryjne dla urządzeń klasy HID. Sterowniki działające w trybie jądra systemu za miast tej funkcji używ ają rozkazu IOCTL_HID_GET_SERIALNUMBER_STRING.
Funkcja HidD_GetIndexedString() Funkcja pobiera indeks do łańcucha znaków, który określa urządzenie identyfikowane przez HidDeviceObject.
Rozdział 4. ♦ Urządzenia klasy HID
123
BOOLEAN H id D _ G e tIn d e x e d S trin g ( IN HANDLE IN ULONG OUT PVOID IN ULONG )
H id D e v ice O b je c t, S trin g In d e x, B u ffe r, BufferLength
;
Param etr StringIndex określa numer indeksu. W skaźnik ogólny Buffer wskazuje bu for danych, w którym zostanie umieszczony odczytany łańcuch znaków typu wchar_t. Param etr BufferLength określa rozmiar (w bajtach) bufora danych. Jeżeli rozmiar bu fora został określony niewłaściwie (jest mniejszy niż wymagany), funkcja zwróci war tość FALSE. Użycie funkcji HidD_GetIndexedString() w aplikacji użytkownika nie jest obligatoryj ne dla urządzeń klasy HID. Sterowniki działające w trybie jąd ra systemu zamiast tej funkcji używ ają rozkazu IOCTL_HID_GET_INDEXED_STRING.
Funkcja HidD_FlushQueue() Funkcja usuwa wszystkie nieprzetworzone raporty aktualnie znajdujące się w buforze wejściowym urządzenia identyfikowanego przez HidDeviceObject. BOOLEAN Hid D _ Flu sh Q u e u e ( IN HANDLE
H id D e v ice O b je c t
);
Jeżeli z bufora wejściowego urządzenia zostały usunięte wszystkie raporty, funkcja HidD_FlushQueue() zwraca wartość TRUE. Użycie omawianej funkcji w aplikacji użyt kownika je st opcjonalne dla urządzeń klasy HID. Sterowniki działające w trybie jądra systemu zamiast niej używ ają rozkazu IOCTL_HID_FLUSH_QUEUE.
Funkcja HidD_GetPreparsedData() Funkcja um ożliw ia pobranie w skaźnika do bufora danych zawierającego informacje o możliwościach eksploatacyjnych urządzenia identyfikowanego przez HidDeviceObject. Inform acje takie są przechowywane w polach odpowiedniej struktury (deskryptora), której użycie je st całkowicie zarezerwowane dla systemu. BOOLEAN H id D _ G e tP re p a rs e d D a ta ( IN HANDLE
H id D e v ice O b je c t,
OUT PHIDP_PREPARSED_DATA
*Preparsed D ata
);
Param etr PreparsedData wskazuje strukturę HIDP_PREPARSED_DATA, przechowującą da ne na tem at urządzenia. Użytkownik nie ma m ożliwości bezpośredniego odwołania się do tego wskaźnika. Dane wskazywane przez PreparsedData są dostępne pośrednio za pom ocą rodziny funkcji HidP_Xxx(), dla których jest on parametrem wejściowym.
12 4
USB. Praktyczne programowanie z Windows API w C++
Prawidłowo wykonana funkcja zwraca wartość TRUE. Użycie funkcji HidD_GetPreparsedData() w aplikacji użytkownika nie jest obligatoryjne dla urządzeń klasy HID. Ste rowniki działające w trybie jąd ra systemu zam iast niej używ ają rozkazu IOCTL_HID_ GET_COLLECTION_DESCRIPTOR.
Funkcja HidD_FreePreparsedData() Funkcja zwalnia zasoby struktury HIDP_PREPARSED_DATA. BOOLEAN H id D _ F r e e P re p a rs e d D a t a ( IN PHIDP_PREPARSED_DATA
P reparsedD ata
);
Prawidłowo wykonana funkcja zwraca wartość TRUE. Użycie funkcji HidD_FreePreparsedData() w aplikacji użytkownika nie je st obligatoryjne dla urządzeń klasy HID.
Funkcja HidD_GetPhysicalDescriptor() Deskryptor fizyczny określa format danych dla elementów sterujących pracą urządze nia. Pozwala zidentyfikować, w jaki sposób użytkownik fizycznie wchodzi w interak cję z urządzeniem. Funkcja HidD_GetPhysicalDescriptor() pobiera deskryptor fizyczny urządzenia identyfikowanego przez HidDeviceObject. BOOLEAN H id D _ G e tP h y si c a l D e s c r i p t o r ( IN HANDLE OUT PVOID IN ULONG
H id D e v ice O b je c t, B u ffe r, BufferLength
);
W skaźnik ogólny Buffer wskazuje bufor danych, w którym zostanie umieszczony de skryptor fizyczny. Parametr BufferLength określa rozmiar (w bajtach) bufora danych. Użycie funkcji HidD_GetPhysicalDescriptor() w aplikacji użytkownika jest opcjonal ne dla urządzeń klasy HID. Sterowniki działające w trybie jąd ra systemu zamiast tej funkcji używ ają rozkazu IOCTL_GET_PHYSICAL_DESCRIPTOR. N a rysunku 4.6 pokazano umiejscowienie deskryptora fizycznego w stosie deskryptorów USB.
Funkcja HidD_GetMsGenreDescriptor() Funkcja pobiera deskryptor M icrosoftu dla urządzenia identyfikowanego przez para metr HidDeviceObject. BOOLEAN
s td c a ll
H id D _ G e tM sG e n re D e s crip to r
(
IN HANDLE H i d D e v i c e O b j e c t , OUT PVOID B u f f e r , IN );
ULONG B u f f e r L e n g t h
Rozdział 4. ♦ Urządzenia klasy HID
125
Rysunek 4.6. Umiejscowienie deskryptora fizycznego w stosie deskryptorów USB
Funkcja ta może być wykorzystywana w systemach operacyjnych, począwszy od W in dows XP.
Funkcje rodziny HidP_Xxx() Funkcje rodziny HidP (ang. Human Interface Device Protocol) stanowią warstwę trans portową dla raportów, którym i posługują się urządzenia klasy HID. Poniżej omówio no kilkanaście najczęściej wykorzystywanych funkcji tej grupy.
Funkcja HidP_GetButtons() Funkcja HidP_GetButtons() je st synonimem funkcji HidP_GetUsages(). # d e fin e H id P _ G e tB u t to n s ( R ty , H id P _ G e tU sa g e s(R ty,
UP a, UP a,
LC o, LC o,
U Li, U Li,
U Le , U Le ,
P pd , P pd ,
Re p, Re p,
RLe)
\
RLe)
Funkcja HidP_GetButtonsEx() Funkcja HidP_GetButtonsEx() jest synonimem funkcji HidP_GetUsagesEx().
Funkcja HidP_GetButtonCaps() Funkcja zwraca wskaźnik ButtonCaps do tablicy struktur HIDP_BUTTON_CAPS przecho wującej inform acje na tem at właściw ości danych dyskretnych występujących w w y branym typie raportu. NTSTATUS
std ca ll
H id P_G etB utton C a ps( IN HIDP_REPORT_TYPE
ReportType,
OUT PHIDP_BUTTON_CAPS IN OUT PULONG
IN PHIDP_PREPARSED_DATA );
ButtonCaps,
ButtonCapsLength, P reparsedD ata
126
USB. Praktyczne programowanie z Windows API w C++
Param etr wejściowy ReportType określa rodzaj raportu. Typy raportów są przechowy wane w elementach typu wyliczeniowego HIDP_REPORT_TYPE (patrz tabela 4.4). Jeżeli ButtonCapsLength je st użyty jako param etr w ejściow y, to wskazuje rozm iar bufora danych. Jeżeli je s t użyty jak o param etr wyjściowy, wskazuje liczbę danych aktual nie zw racanych przez funkcję. W skaźnik PreparsedData w skazuje strukturę HIDP_ PREPARSED_DATA. / / -------------------------------------------------------------------H id D _ G e tP re p a rs e d D a ta (h id D e v ice O b je ct, H id P_G etC aps(prep arsedD ata, PULONG
&preparsedD ata)
& c a p a b ilitie s ) ;
buttonCapsLength;
HIDP_BUTTON_CAPS * b u t t o n C a p s = new \ H ID P _ B U T T O N _ C A P S [ca p a b ilitie s.N u m b e rIn p u tB u tto n C a p s]; H id P _ G e tB u tto n C a p s (H id P _ In p u t, fo r ( U S H O R T i
= 0;
buttonCaps,
buttonCapsLength,
i< c a p a b ilitie s .N u m b e rI n p u tB u tto n C a p s ;i+ + )
p r in tf( " B u tto n C a p s [ % d ] .U s a g e P a g e % x\n\n", if ( b u tto n C a p s [ i] .Is R a n g e )
i,
preparsedD ata); {
b u tto n C a p s [i].U sa g e P a g e );
{
p r i n t f ( " U s a g e s m i n , max % d . . % d \ n \ n " ,
b u tto n C a p s [i].R a n g e .U s a g e M in ,
b u t t o n C a p s [ i ] .R an ge .U sa ge M a x); p r in tf( " D a ta
i n d e x m i n , max % d . . % d \ n \ n " ,
b u tto n C a p s [i].R a n g e .D a ta In d e x M in , butto n C a p s[i].R a n g e .D a ta In d e xM a x); }
//... / / -----------Tabela 4.4. Specyfikacja typu wyliczeniowego HIDP REPORT TYPE Element
Znaczenie
HidP Input
Raport wej ściowy
HidP Output
Raport wyj ściowy
HidP Feature
Raport konfiguracyjny
Prawidłowo wykonana funkcja HidP_GetButtonCaps() zwraca wartość o statusie HIDP_ STATUS_SUCCESS. WDK definiuje ten typ następująco: t y p e d e f enum _H IDP_R EPOR T_TYPE { H id P _ In p u t, H id P _ O u tp u t, H id P _ F e a tu re } HIDP_REPORT_TYPE;
Definicja ta tworzy nowe słowo kluczowe HIDP_REPORT_TYPE (typ wyliczeniowy).
Funkcja HidP_GetCaps() Poprzez parametr wyjściowy Capabil it ie s funkcja przekazuje wskaźnik do elemen tów struktury HIDP_CAPS (patrz tabela 4.5) przechowującej informacje na temat właści wości wybranego urządzenia.
Rozdział 4. ♦ Urządzenia klasy HID
127
Tabela 4.5. Specyfikacja struktury HIDP CAPS Typ
Element struktury
Znaczenie
USAGE
Usage
Identyfikator urządzenia związanego z konkretną jego funkcją w ramach grupy urządzeń, na przykład Generic Desktop: P ointer 01h, Mouse 02h, Jo ystick 04h itp.
USAGE
UsagePage
Identyfikator grupy urządzeń w systemie
USHORT
InputReportByteLength
Maksymalny rozmiar w bajtach raportów wejściowych IN z uwzględnieniem identyfikatora raportu
USHORT
OutputReportByteLength
Maksymalny rozmiar w bajtach raportów wyjściowych OUT z uwzględnieniem identyfikatora raportu
USAGE
FeatureReportByteLength
Maksymalny rozmiar w bajtach raportów konfiguracyjnych z uwzględnieniem identyfikatora raportu
USAGE
Reserved[17]
Zarezerwowane
USHORT
NumberLinkCollectionNodes
Liczba egzemplarzy struktur HIDP LINK COLLECTION NODE zwracanych przez funkcję HidP GetLinkCollectionNodes()
USHORT
NumberInputButtonCaps
Liczba egzemplarzy struktur HIDP BUTTON CAPS dla danych wejściowych zwracanych przez funkcję HidP GetButtonCaps()
USAGE
NumberInputValueCaps
Liczba egzemplarzy struktur HIDP VALUE CAPS dla danych wejściowych zwracanych przez funkcję HidP GetValueCaps()
USAGE
NumberInputDataIndices
Liczba indeksowanych danych dyskretnych i ciągłych dla wszystkich raportów wejściowych
USHORT
NumberOutputButtonCaps
Liczba egzemplarzy struktur HIDP_BUTTON_CAPS dla danych wyjściowych zwracanych przez funkcję HidP GetButtonCaps()
USHORT
NumberOutputValueCaps
Liczba egzemplarzy struktur HIDP_VALUE_CAPS dla danych wyjściowych zwracanych przez funkcję HidP GetValueCaps()
USAGE
NumberOutputDataIndices
Liczba indeksowanych danych dyskretnych i ciągłych dla wszystkich raportów wyjściowych
USAGE
NumberFeatureButtonCaps
Liczba egzemplarzy struktur HIDP BUTTON CAPS dla danych konfiguracyjnych zwracanych przez funkcję HidP GetButtonCaps()
USHORT
NumberFeatureValueCaps
Liczba egzemplarzy struktur HIDP VALUE CAPS dla danych konfiguracyjnych zwracanych przez funkcję HidP GetValueCaps()
USHORT
NumberFeatureDataIndices
Liczba indeksowanych danych dyskretnych i ciągłych dla wszystkich raportów konfiguracyjnych
128
USB. Praktyczne programowanie z Windows API w C++
NTSTATUS
std ca ll
H id P _G etC aps( IN PHIDP_PREPARSED_DATA P r e p a r s e d D a t a , OUT PHIDP_CAPS C a p a b i l i t i e s );
Param etr wejściowy wskazuje strukturę HIDP_PREPARSED_DATA. W DK definiuje tę strukturę jako: t y p e d e f s t r u c t _ H ID P_C AP S { USAGE U sa g e ; USAGE U s a g e P a g e ; USHORT I n p u t R e p o r t B y t e L e n g t h ; USHORT O u t p u t R e p o r t B y t e L e n g t h ; USHORT F e a t u r e R e p o r t B y t e L e n g t h ; USHORT R e s e r v e d [ 1 7 ] ; USHORT N u m b e r L i n k C o l l e c t i o n N o d e s ; USHORT N u m b e r I n p u t B u t t o n C a p s ; USHORT N u m b e r I n p u t V a l u e C a p s ; USHORT N u m b e r I n p u t D a t a I n d i c e s ; USHORT N u m b e r O u t p u t B u t t o n C a p s ; USHORT N u m b e r O u t p u t V a l u e C a p s ; USHORT N u m b e r O u t p u t D a t a I n d i c e s ; USHORT N u m b e r F e a t u r e B u t t o n C a p s ; USHORT N u m b e r F e a t u r e V a l u e C a p s ; USHORT N u m b e r F e a t u r e D a t a I n d i c e s ; } HID P_ CA PS ,
* PH ID P_ CA PS;
D efinicja ta tworzy dw a nowe słowa kluczowe: HIDP_CAPS (struktura) i PHIDP_CAPS (wskaźnik do struktury). / / -------------------------------------------------------------------# d e f i n e H I D_USAGE_PAGE_GENERIC
((USAGE)
# d e f i n e H I D_ USA GE _GE NER IC _J OYS TI CK
( ( u SA G e ) 0x04 )
0x 01 )
//h id u s a g e .h
//... if(H id D _ G e tP re p a rse d D a ta (h id D e v ic e O b je c t, H id P _ G e tC a p s (p re p a rs e d D a ta , if( c a p a b ilitie s .U s a g e P a g e
& preparsedD ata)){
& c a p a b ilitie s );
== HID_USAGE_PAGE_GENERIC &&
c a p a b i l i t i e s . U s a g e == H ID _U SA GE _G EN ER IC _J O YS TI CK ){ p r in tf(" Z n a le z io n o
jo y s tic k
HID\n\n");
p r in t f ( " U s a g e = % d \ n U s a g e P a g e = % d \ n I n p u t R e p o r t B y t e L e n g t h = % d \ n " "O utpu tR epo rtB yteLe n gth = % d \n Featu reR ep ortB yteLen gth = % d\n " "Num berInputButtonCaps=% d\nNum berInputValueCaps=% d\n\n", c a p a b ilitie s .U s a g e ,c a p a b ilitie s .U s a g e P a g e , c a p a b ilitie s .I n p u tR e p o rtB y te L e n g th , c a p a b ilitie s .O u tp u tR e p o r tB y te L e n g th , c a p a b i l i t i e s . FeatureR eportByteLength, c a p a b ilitie s .N u m b e rIn p u tB u tto n C a p s , c a p a b ilitie s .N u m b e rIn p u tV a lu e C a p s); }
//... / / --------------------------------------------------------------------
Prawidłowo wykonana funkcja HidP_GetCaps() zwraca wartość o statusie HIDP_STATUS_ SUCCESS.
Rozdział 4. ♦ Urządzenia klasy HID
129
Funkcja HidP_GetData() Z a pośrednictwem wskaźnika DataList funkcja wydobywa tablicę struktur: t y p e d e f s t r u c t _HIDP_DATA { USHORT
DataIndex;
USHORT u n io n
Reserved; {
ULONG
/ / w artości
RawValue;
BOOLEAN On; / / p r z y c i s k i w s t a n i e ON ( t r u e ) }; } HIDP_DATA, *PHIDP_DATA;
— z których każda identyfikuje indeks danych i stan aktywnych (ON) wartości binarnych (przycisków) lub indeks oraz wartości elementów sterujących o charakterze ciągłym. NTSTATUS
std ca ll
H idP_G etD ata( IN HIDP_REPORT_TYPE
ReportType,
OUT PHIDP_DATA
D a ta List,
IN OUT PULONG
D ataLength,
IN PHIDP_PREPARSED_DATA IN PCHAR
Report,
IN ULONG
ReportLength
PreparsedD ata,
);
Param etr ReportType wskazuje typ wyliczeniowy zawierający dane na tem at rodza ju raportu. W skaźnik Report wskazuje bufor danych z raportem odpowiedniego ty pu. Użyty jako param etr wejściowy wskaźnik DataLength wskazuje daną zawierającą rozmiar tablicy struktur. Wymagany rozmiar tablicy można określić za pom ocą funk cji HidP_MaxDataListLength(). Użyty jako parametr wyjściowy wskaźnik DataLength wskazuje daną zawierającą liczbę elementów o charakterze dyskretnym w stanie ON i liczbę elementów o charakterze wartości ciągłych. W skaźnik PreparsedData w ska zuje strukturę HIDP_PREPARSED_DATA. Param etr ReportLength określa rozmiar raportu (w bajtach) wskazywanego przez Report. Pow inien być równy długości raportu w y specyfikowanego przez funkcję HidP_GetCaps(). //
--------------------------------------------------------------------
PHIDP_DATA D a t a L i s t ;
//... i n t m a in ()
{
//... D a ta L ist
= new H I D P _ D A T A [ d a t a L i s t L e n g t h ] ;
w h ile ( tru e )
{ / / c y k l i c z n y o d c z y t r a p o r t u w ejści ow eg o
m e m s e t(in p u tR e p o rtB u ffe r, R e a d F ile (/ * ...* / ,
0x00,
& num berO fBytesRead, p rin tf("% d
%d
%d
ca p a b ilitie s .In p u tR e p o rtB y te L e n g th );
in p u tR e p o rtB u ffe r,
%d
%d
ca p a b ilitie s .In p u tR e p o rtB y te L e n g th ,
N U LL ); %d % d \n ",
in p u tR e p o rtB u ffe r[0 ],in p u tR e p o rtB u ffe r[1 ],in p u tR e p o rtB u ffe r[2 ], i n p u tR e p o r tB u ffe r[3 ],in p u tR e p o rtB u ffe r[4 ],in p u tR e p o rtB u ffe r[5 ], i n p u tR e p o r tB u ffe r[ 6 ] );
HidP_GetData(HidP_Input, DataList, &dataListLength, preparsedData, inputReportBuffer, capabilities.InputReportByteLength);
130
USB. Praktyczne programowanie z Windows API w C++
p rin tf("D a ta In d e x = % d \n ",D a ta L is t[5 ].D a ta In d e x ); p r in t f( " R a w V a lu e = % d \ n " ,D a t a L is t [ 5 ] .Raw Value); p r in tf("\n ");
1 1 ...
//
--------------------------------------------------------------------
Prawidłowo wykonana funkcja HidP_GetData() zwraca wartość o statusie HIDP_STATUS_ SUCCESS.
Funkcja HidP_GetExtendedAttributes() Z a pośrednictwem wskaźnika A ttrib u tes funkcja zwraca rozszerzone atrybuty elemen tu kontrolnego urządzenia HID. NTSTATUS
std ca ll
H id P _ G e tE x te n d e d A ttrib u te s ( IN HIDP_REPORT_TYPE R e p o r t T y p e , IN USHORT D a t a I n d e x , IN PHIDP_PREPARSED_DATA P r e p a r s e d D a t a , OUT PHIDP_EXTENDED_ATTRIBUTES A t t r i b u t e s , PULONG L e n g t h A t t r i b u t e s );
Param etr ReportType wskazuje typ wyliczeniowy zawierający dane na temat rodzaju raportu. Parametr DataIndex jest elementem struktury HIDP_DATA. Wskaźnik Preparsed Data wskazuje strukturę HIDP_PREPARSED_DATA. N a wejściu LengthA ttributes powinien wskazywać daną o wartości większej lub równej sizeof(HIDP_EXTENDED_ATTRIBUTES). N a wyjściu parametr LengthA ttributes określa rozmiar bufora danych (w bajtach). Prawidłowo wykonana funkcja HidP_GetExtendedAttributes() zwraca wartość o sta tusie HIDP_STATUS_SUCCESS.
Funkcja HidP_GetLinkCollectionNodes() Abstrakcyjna kolekcja łączy (ang. link collection) w ramach raportu wprowadza orga nizacyjną hierarchię porządkującą elementy kontrolne urządzenia w odpowiednich gru pach. Każde takie abstrakcyjne łącze jest reprezentowane przez strukturę HIDP_LINK_ COLLECTION_NODE. Na przykład w konsolach gier typu gam epad pojęcia „łącze warstw” używa się do rozróżnienia przycisków obsługiwanych przez praw ą lub lew ą dłoń gra cza. Funkcja HidP_GetLinkCollectionNodes() wydobywa dane przechowywane przez pola struktury HIDP_LINK_COLLECTION_NODE. NTSTATUS H id P _ G e tL in k C o lle c tio n N o d e s( OUT PHI DP_LINK_COLLECTION_NODE IN OUT PULONG
L in k C o lle c tio n N o d e s,
L in k C o lle c t io n N o d e s L e n g t h ,
IN PHIDP_PREPARSED_DATA
P reparsedD ata
);
W skaźnik LinkCollectionNodes wskazuje tablicę struktur. t y p e d e f s t r u c t _HID P_LINK_C OLLEC TIO N_NO DE USAGE
L in kU sa g e ;
{
Rozdział 4. ♦ Urządzenia klasy HID
USAGE
131
L in kU sage Page;
USHORT
Parent;
USHORT
N u m b e rO fC h ild re n ;
USHORT
N e x tS ib lin g ;
USHORT
F irs tC h ild ;
ULONG
C o lle c tio n T y p e :
ULONG
I s A lia s :
8;
1;
ULONG
Reserved:
PVOID
U serContext;
23;
} HIDP_LINK _C OLL ECT IO N_ NOD E,
*PHIDP_LINK _CO LLECT ION _NO DE ;
Użyty jako wejściowy param etr LinkCollectionNodesLength określa rozmiar tablicy, a na wyjściu wskazuje liczbę elementów tablicy. Rozm iar bufora może zostać okre ślony za pom ocą pola NumberLinkCollectionNodes struktury HIDP_CAPS. W skaźnik PreparsedData wskazuje strukturę HIDP_PREPARSED_DATA. //
--------------------------------------------------------------------
PHIDP_LINK_COLLECTION_NODE l i n k C o l l e c t i o n N o d e s ;
//... in t m a in ( ) {
//... PULONG
lin k C o lle c tio n N o d e s L e n g th ;
lin k C o lle c tio n N o d e s
= new \
H ID P _ L IN K _C O L L EC T IO N _ N O D E[ca pa bilitie s.N u m b e rLin kC o l le c t io n N o d e s ] ; H id P _ G e tL in k C o lle c tio n N o d e s (lin k C o lle c tio n N o d e s , lin k C o lle c tio n N o d e s L e n g th , preparsedD ata); p rin tf("LinkU sag e= % d \nLinkU sagePag e= % d\nParent= % d \n" "N u m b e rO fC h ild re n = % d \n C o lle ctio n T yp e = % d \n \n ", lin k C o lle c tio n N o d e s- > L in k U s a g e , lin k C o lle c tio n N o d e s - > L in k U s a g e P a g e , lin k C o lle c tio n N o d e s - > P a re n t , lin k C o lle c tio n N o d e s - > N u m b e rO fC h ild re n , lin k C o lle c tio n N o d e s - > C o lle c tio n T y p e ,/ * ...* / );
//...
//
--------------------------------------------------------------------
Prawidłowo wykonana funkcja HidP_GetLinkCollectionNodes() zwraca wartość o sta tusie HIDP_STATUS_SUCCESS.
Funkcja HidP_GetScaledUsageValue() Poprzez param etr UsageValue funkcja w ydobywa w ybraną i przeskalow aną wartość będącą elementem raportu dla urządzenia z danej grupy urządzeń. Dane są ekstrapolowane w sposób liniowy między wartościami fizycznego m aksimum (minimum) i lo gicznego m aksim um (minimum). W artość logiczna je st zwracana przez urządzenie, a fizyczna przez funkcję. NTSTATUS
std ca ll
H id P _ G e tS c a le d U sa g e V a lu e ( IN HIDP_REPORT_TYPE IN USAGE IN USHORT IN USAGE
ReportType,
UsagePage, L in k C o lle c tio n Usage,
OPT IONAL,
132
USB. Praktyczne programowanie z Windows API w C++
OUT PLONG
U sageValue,
IN PHIDP_PREPARSED_DATA IN PCHAR
Report,
IN ULONG
ReportLength
PreparsedD ata,
);
Wskaźnik ReportType wskazuje typ wyliczeniowy HIDP_REPORT_TYPE. Parametr UsagePage je st identyfikatorem grupy urządzeń. Opcjonalny param etr L inkC ollection określa kryterium w yszukiw ania w obrębie deskryptora. W artość zero oznacza urządzenia klasy HID. W skaźnik Report wskazuje konkretny typ raportu, którego długość określa ReportLength. Poprawnie wykonana funkcja HidP_GetScaledUsageValue() zwraca wartość o statusie HIDP_STATUS_SUCCESS. D la nieprawidłowo określonej wartości logicznej lub fizycznej funkcja zwraca HIDP_STATUS_BAD_LOG_PHY_VALUES.
Funkcja HidP_GetSpecificButtonCaps() Funkcja wydobywa wartości binarne, które są częścią jednego z raportów: HidP_Input, HidP_Output lub HidP_Feature. NTSTATUS
s td ca ll
H id P_G etS peci f ic B u t to n C a p s ( IN HIDP_REPORT_TYPE IN USAGE IN USHORT IN USAGE
ReportType,
UsagePage, L in k C o lle c tio n , Usage,
OUT PHIDP_BUTTON_CAPS IN OUT PULONG
ButtonCaps,
ButtonCapsLength,
IN PHIDP_PREPARSED_DATA
P reparsedD ata
);
Niezerowy param etr UsagePage pełni funkcję kryterium wyszukiwania dla danej grupy urządzeń. Param etr LinkC ollection określa kryterium w yszukiwania w obrębie deskryptora. Parametr wejściowy Usage pełni funkcję kryterium wyszukiwania dla w y branego urządzenia z grupy urządzeń. Wskaźnik ButtonCaps wskazuje tablicę struktur HIDP_BUTTON_CAPS, poprzez które zostanie zwrócony rezultat wykonania funkcji. Wskaź nik ButtonCapsLength wskazuje daną zawierającą rozmiar bufora danych. //
--------------------------------------------------------------------
PULONG
buttonCapsLength;
HIDP_BUTTON_CAPS * b u t t o n C a p s = new \ H ID P _ B U T T O N _ C A P S [ca p a b ilitie s.N u m b e rIn p u tB u tto n C a p s]; USAGE U sa g e P a g e = HID_USAGE_PAGE_BUTTON; USHORT L i n k C o l l e c t i o n
= O;
USAGE Us a g e = O; H id P _ G e tS p e c ific B u tto n C a p s (H id P _ I n p u t, buttonCaps, fo r ( U S H O R T i
= O;
UsagePage,
L in k C o lle c tio n ,
buttonCapsLength,
i< c a p a b ilitie s .N u m b e rI n p u tB u tto n C a p s ;i+ + )
p r in tf( " B u tto n C a p s [ % d ] .U s a g e P a g e % x\n\n", if ( b u tto n C a p s [ i] .Is R a n g e )
i,
Usage,
preparsedD ata); j
b u tto n C a p s[i].U sa g e P a g e );
j
p r i n t f ( " U s a g e s m i n , max % d . . % d \ n \ n " ,
b u tto n C a p s [i].R a n g e .U s a g e M in ,
button C ap s[i].R an g e.U sag eM ax);
Rozdział 4. ♦ Urządzenia klasy HID
p r in tf( " D a ta
133
i n d e x m i n , max % d . . % d \ n \ n " ,
b u tto n C a p s [i].R a n g e .D a ta In d e x M in ,
b u t t o n C a p s [ i ] .R ange.D ataIndexM ax); } //
//
...
--------------------------------------------------------------------
Poprawnie w yw ołana funkcja HidP_GetSpecificButtonCaps() zwraca wartości o sta tusie HIDP_STATUS_SUCCESS. Jej wywołanie z wartościam i zerowymi dla UsagePage, Usage i LinkCollection jest tożsame z użyciem funkcji HidP_ButtonCaps().
Funkcja HidP_GetSpecificValueCaps() Funkcja w ydobyw a z raportu w łaściwość tablicow ą wartości o charakterze ciągłym, spełniającą wybrane kryteria wyszukiwania. NTSTATUS
std ca ll
H id P_G etS peci f ic V a lu e C a p s ( IN HIDP_REPORT_TYPE IN USAGE IN USHORT IN USAGE
ReportType,
UsagePage, L in k C o lle c tio n , Usage,
OUT PHI DP_ VALUE_CAPS IN OUT PULONG
V a lu e C a p s,
V a lu eC a psL en gth ,
IN PHIDP_PREPARSED_DATA
P reparsedD ata
);
Niezerowy param etr UsagePage pełni funkcję kryterium wyszukiwania dla danej grupy urządzeń. Parametr LinkCollection określa kolejne kryterium wyszukiwania w obrębie deskryptora. Parametr wejściowy Usage pełni funkcję kryterium wyszukiwania dla wy branego urządzenia należącego do grupy urządzeń. Wskaźnik ValueCaps wskazuje tablicę struktur PHIDP_VALUE_CAPS, poprzez które zostanie zwrócony rezultat wykonania funk cji. W skaźnik ValueCapsLength wskazuje daną zaw ierającą rozmiar bufora danych. //
--------------------------------------------------------------------
# d e f i n e H I D_USAGE_PAGE_LED
(( USAGE)
//h id u s a g e .h
0x08)
//... PULONG
v a lu eC a p sL en g th ;
HI DP_ VALUE_CAPS * v a l u e C a p s = new \ H ID P _ V A L U E _ C A P S [ca p a b ilitie s.N u m b e rO u tp u tV a lu e C a p s]; USAGE U sa g e P a g e = HID_USAGE_PAGE_LED; USHORT L i n k C o l l e c t i o n
= 0;
USAGE Us a g e = 0; H id P _ G e tS p e c ificV a lu e C a p s (H id P _ O u tp u t,U sa g e P a g e , va lu e C a p s, fo r ( U S H O R T i
= 0;
i,
{
va lu e C a p s[i].U sa g e P a g e );
p r i n t f ( " V a l u e C a p s [ % d ] . P h y s i c a l M i n % d \n ",
i,
v a lu e C a p s [i].P h y s ic a lM in );
p r i n t f ( " V a l u e C a p s [ % d ] . P h y s i c a l M a x % d \n ",
i,
v a lu e C a p s [i].P h y s ic a lM a x );
p r i n t f ( " V a l u e C a p s [ % d ] . B i t S i z e %d\n",
i,
v a lu e C a p s [ i].B itS iz e ) ;
p r in t f ( " V a lu e C a p s [ % d ] . R e p o r t C o u n t % d\n\n",
//
Usage,
preparsedD ata);
i< c a p a b ilitie s .N u m b e rO u tp u tV a lu e C a p s ;i+ + )
p r in t f ( " V a lu e C a p s [ % d ] . U s a g e P a g e %x\n",
}
L in k C o lle c tio n ,
v a lu e C a p sL e n g th ,
i,
v a lu e C a p s [i].R e p o rtC o u n t);
--------------------------------------------------------------------
Poprawnie użyta funkcja HidP_GetSpecificValueCaps() zwraca wartość o statusie HIDP STATUS SUCCESS.
13 4
USB. Praktyczne programowanie z Windows API w C++
Funkcja HidP_GetUsageValue() Funkcja wydobywa dane z raportu spełniającego wybrane kryteria wyszukiwania. NTSTATUS
std ca ll
H id P_G etU sageValue( IN HIDP_REPORT_TYPE IN USAGE IN USHORT IN USAGE OUT PULONG
ReportType,
UsagePage, L in k C o lle c tio n , Usage, U sageValue,
IN PHIDP_PREPARSED_DATA IN PCHAR
Report,
IN ULONG
ReportLength
PreparsedD ata,
);
Wskaźnik ReportType wskazuje typ wyliczeniowy HIDP_REPORT_TYPE. Parametr UsagePage je st identyfikatorem grupy urządzeń. Parametr LinkCollection określa kryterium w y szukiwania. W skaźnik Report wskazuje konkretny typ raportu, który zawiera żądaną wartość, a jego rozmiar określa ReportLength. W skaźnik UsageValue wskazuje bufor danych wyjściowych. Prawidłowo wykonana funkcja HidP_GetUsageValue() zwraca wartość o statusie HIDP_ STATUS_SUCCESS.
Funkcja HidP_GetUsageValueArray() Funkcja wydobywa dane z raportu składającego się z więcej niż jednego pola. NTSTATUS
std ca ll
H id P _ G etU sa g e V a lu e A rra y ( IN HIDP_REPORT_TYPE IN USAGE IN USHORT IN USAGE
ReportType,
UsagePage, L in k C o lle c tio n
OPT IONAL,
Usage,
OUT PCHAR
U sageValue,
IN USHORT
U sag eValueB yteLeng th,
IN PHIDP_PREPARSED_DATA IN PCHAR
Report,
IN ULONG
ReportLength
PreparsedD ata,
);
W skaźnik ReportType wskazuje typ raportu; może być HidP_Output lub HidP_Feature. Param etr UsageValue wskazuje tablicę znaków, w której zostaną umieszczone odczy tane dane. Żądana liczba bitów może zostać określona jako wynik mnożenia wartości pól BitSize i ReportCount struktury HIDP_VALUE_CAPS. Parametr UsageValueByteLength określa rozmiar bufora danych. W pierwszym przybliżeniu rozmiar ten może być równy iloczynowi wartości pól B itSize i ReportCount struktury HIDP_VALUE_CAPS. Prawidłowo wykonana funkcja HidP_GetUsageValueArray() zwraca wartość o statusie HIDP STATUS SUCCESS.
Rozdział 4. ♦ Urządzenia klasy HID
135
Funkcja HidP_GetUsages() Funkcja zwraca liczbę dyskretnych elementów sterujących pozostających w stanie ON. NTSTATUS
std ca ll
H id P _G etU sag e s( IN HIDP_REPORT_TYPE R e p o r t T y p e , IN USAGE U s a g e P a g e , IN USHORT L i n k C o l l e c t i o n , OUT USAGE* U s a g e L i s t , IN OUT ULONG* U s a g e L e n g t h , IN PHIDP_PREPARSED_DATA P r e p a r s e d D a t a , IN PCHAR R e p o r t , IN ULONG R e p o r t L e n g t h );
Wskaźnik UsageLi s t wskazuje bufor danych, poprzez który funkcja zwraca wynik dzia łania. Użyty jako param etr wejściowy UsageLength określa rozmiar bufora danych, a użyty jako param etr wyjściowy podaje liczbę elementów w stanie O N . W skaźnik Report wskazuje rodzaj raportu (HidP_Input lub HidP_Feature), a ReportLength okre śla jego długość. Aplikacje działające w trybie użytkownika i aplikacje działające w trybie jądra syste mu powinny się posługiwać funkcją HidP_MaxUsageListLength() do określenia maksy malnej liczby elementów sterujących o charakterze dyskretnym, występujących w ra mach określonego typu raportu. W celu prawidłowego określenia wartości UsageLength można użyć funkcji HidP_GetButtons() z wyzerowanym argumentem UsageList. Ste rowniki urządzeń używ ają pola XXXReportByteLength struktury HIDP_CAPS do określe nia długości raportu. / / -------------------------------------------------------------------USAGE * U s a g e L i s t = new U S A G E [ u s a g e L i s t L e n g t h ] ;
//... R e a d F ile (/ * ...* / ,
in p u tR e p o rtB u ffe r,
& num berO fBytesRead,
c a p a b ilitie s .I n p u tR e p o rtB y te L e n g th , N U L L );
//... H id P _G etU sage s(H idP_ Inp ut,
0,
0,
U sa g e L ist,
preparsedD ata,
& u sa g e L istL e n g th ,
in p u tR e p o rtB u ffe r,
c a p a b ilitie s .In p u tR e p o rtB y te L e n g th ); p rin tf( "L ic z b a
w ciśn ię ty ch
p rzy cis k ó w
(w s t a n i e ON)=%d\n " ,
u s a g e L is tL e n g th );
//... / / --------------------------------------------------------------------
Popraw nie użyta funkcja HidP_GetUsages() zwraca wartość o statusie HIDP_STATUS_ SUCCESS.
Funkcja HidP_GetUsagesEx() Funkcja wydobywa identyfikatory UsagePage i Usage dla wszystkich dyskretnych ele mentów sterujących urządzenia pozostających w stanie ON .
136
USB. Praktyczne programowanie z Windows API w C++
NTSTATUS
std ca ll
H id P_G etU sage sE x ( IN HIDP_REPORT_TYPE R e p o r t T y p e , IN USHORT L i n k C o l l e c t i o n
OPTIONAL,
IN OUT PUSAGE_AND_PAGE B u t t o n L i s t , IN OUT ULONG * U s a g e L e n g t h , IN PHIDP_PREPARSED_DATA P r e p a r s e d D a t a , IN PCHAR R e p o r t , IN ULONG R e p o r t L e n g t h );
W skaźnik ReportType wskazuje typ wyliczeniowy HIDP_REPORT_TYPE. Wskaźnik But tonL ist wskazuje tablicę struktur: t y p e d e f s t r u c t _USAGE_AND_PAGE { USAGE U sa g e; USAGE U s a g e P a g e ; } USAGE_AND_PAGE,
* PUSAG E_AND_PAG E;
— poprzez k tó rą funkcja zw raca identyfikatory Usage i UsagePage dla przycisków w stanie O N . Użyty jako parametr wejściowy wskaźnik UsageLength wskazuje rozmiar tablicy struktur, a jako param etr wyjściowy — liczbę elementów w stanie O N w ra porcie danych. Maksymalny rozm iar tablicy może zostać określony za pom ocą funk cji HidP_MaxUsageListLength(). W skaźnik PreparsedData w skazuje strukturę HID_ PREPARSED_DATA. W skaźnik Report wskazuje raport danych o długości ReportLength. / / -------------------------------------------------------------------USAGE_AND_PAGE * U s a g e s = new U S A G E _ A N D _ P A G E [ u s a g e L i s t L e n g t h ] ; w h ile ( tru e )
{ / / c y k l i c z n y o d c z y t danych z wybranego u r z ą d z e n i a
m e m s e t(in p u tR e p o rtB u ffe r, R e a d F ile (/* ...* /,
0x00,
ca p a b ilitie s .In p u tR e p o rtB y te L e n g th );
in p u tR e p o rtB u ffe r,
& num berO fBytesRead,
c a p a b ilitie s .I n p u tR e p o rtB y te L e n g th ,
N U L L );
if(n u m b e rO fB y te sR e a d = = ca p a b ilitie s.In p u tR e p o rtB y te L e n g th ) { fo r ( U S H O R T
i= 0 ;
p rin tf("% d
i< c a p a b ilitie s . In p u tR e p o r tB y te L e n g th ; ",
i++)
in p u tR e p o rtB u ffe r [i]);
p r in tf("\n "); ULONG v a l i d U s a g e s = u s a g e L i s t L e n g t h ; H id P _ G e tU sa g e sE x (H id P _ In p u t,
0,
preparsedD ata,
Usages,
& v a lid U s a g e s,
in p u tR e p o rtB u ffe r,
c a p a b ilitie s .In p u tR e p o rtB y te L e n g th );
/ / s t a n a k t u a l n i e używanych p r z y c i s k ó w p r i n t f ( " Z b i ó r p rz y c is k ó w :\n fo r ( U L O N G i = 0 ;
");
i< v a lid U s a g e s ;
p r in tf("U sa g e P a g e = % d
i++)
{
U sa g e (W ciśn ię ty p r z y c is k
U sa g e s[i].U sa g e P a g e ,
nr)=%d\n
",
U sa g e s[i].U sa g e );
}
//... }
//
--------------------------------------------------------------------
Prawidłowo użyta funkcja HidP_GetUsagesEx() zwraca wartość o statusie HIDP_STATUS_ SUCCESS.
Rozdział 4. ♦ Urządzenia klasy HID
137
Funkcja HidP_GetValueCaps() Funkcja zw raca w skaźnik ValueCaps do struktury HIDP_VALUE_CAPS przechowującej informacje na tem at właściw ości w artości niebinarnych (o charakterze ciągłym), b ę dących elementami wyspecyfikowanego raportu urządzeń klasy HID. NTSTATUS H id P _ G e tV a lu e C a p s ( IN HIDP_REPORT_TYPE OUT PHI DP_ VALUE_CAPS IN OUT PULONG
ReportType, V a lu e C a p s,
V a lu e C a psL e n gth ,
IN PHIDP_PREPARSED_DATA
Pre parsedD ata
);
Param etr wejściowy ReportType określa rodzaj raportu. Typy raportów są przechowy wane w elementach typu wyliczeniowego HIDP_REPORT_TYPE (patrz tabela 4.5). Jeżeli ValueCapsLength jest użyty jako param etr wejściowy, to wskazuje rozmiar bufora da nych; a jeżeli jako parametr wyjściowy — liczbę danych aktualnie zwracanych przez funkcję. Parametr Val ueCapsLength może być określany przez pole NumberXxxValueCaps struktury HID_CAPS. Wskaźnik PreparsedData wskazuje strukturę HIDP_PREPARSED_DATA. / / -------------------------------------------------------------------if(H id D _ G e tP re p a rse d D a ta (h id D e v ic e O b je c t, H id P_G etC aps(prep arsedD ata,
& preparsedD ata)){
& c a p a b ili t i e s ) ;
//... PULONG
V a lu e C a p s L e n g th ;
HI DP_ VALUE_CAPS * v a l u e C a p s = new \ H ID P _ V A L U E _ C A P S [ca p a b ilitie s.N u m b e rO u tp u tV a lu e C a p s]; H id P _ G e tV a lu e C a p s (H id P _ O u tp u t , for ( U S H O R T
i
= 0;
V a lu e C a ps,
V a lu e C a p sL e n g th ,
i< c a p a b ilitie s .N u m b e rO u tp u tV a lu e C a p s ;i+ + )
p r in t f ( " V a lu e C a p s [ % d ] . U s a g e P a g e %x\n", p r i n t f ( " V a l u e C a p s [ % d ] . R e p o r t I D % x\n",
i, i,
preparsedD ata); {
v a lu e C a p s [i].U s a g e P a g e ); v a lu e C a p s[i].R e p o rtID );
p r i n t f ( " V a l u e C a p s [ % d ] . P h y s i c a l M i n %d\n",
i,
v a lu e C a p s [i].P h y s ic a lM in );
p r i n t f ( " V a l u e C a p s [ % d ] . P h y s i c a l M a x %d\n",
i,
v a lu e C a p s [i].P h y s ic a lM a x );
p r i n t f ( " V a l u e C a p s [ % d ] . B i t S i z e % d \n ",
i,
p r i n t f ( " V a l u e C a p s [ % d ] . R e p o r t C o u n t %d\n", p r in t f ( " V a lu e C a p s [ % d ] . U n it s E x p % d\n\n",
v a lu e C a p s [ i].B itS iz e ) ; i, i,
v a lu e C a p s [i].R e p o rtC o u n t); v a lu e C a p s [ i] .U n its E x p );
//... }
//... / / --------------------------------------------------------------------
Popraw nie w yw ołana funkcja HidP_GetValueCaps() zwraca wartość o statusie HIDP_ STATUS_SUCCESS.
Funkcja HidP_InitializeReportForID() Funkcja inicjalizuje raport dla urządzeń klasy HID. Oznacza to, że wszystkie dane b ę dące elementami raportu (zamieszczone w odpowiednich polach raportu) zostaną w y zerowane albo zostanie im przypisany pusty wskaźnik NULL. NTSTATUS
std ca ll
H id P _ In itia liz e R e p o rtF o rID ( IN HIDP_REPORT_TYPE
ReportType,
138
USB. Praktyczne programowanie z Windows API w C++
IN UCHAR
Re portID ,
IN PHIDP_PREPARSED_DATA IN OUT PCHAR IN ULONG
PreparsedD ata,
Report,
ReportLength
);
Param etr wejściowy ReportType określa typ raportu wskazywanego przez w skaźnik Report. Param etr wejściowy ReportID określa identyfikator raportu. W skaźnik PreparsedData wskazuje strukturę HIDP_PREPARSED_DATA. Parametr ReportLength określa rozmiar w bajtach raportu wskazywanego przez Report. Prawidłowo użyta funkcja H idP_InitializeR eportForID () zwraca wartość o statusie HIDP_STATUS_SUCCESS.
Funkcja HidP_MaxDataListLength() Funkcja wydobywa maksymalny rozmiar tablicy struktur HIDP_DATA zwracanych przez funkcję HidP_GetData() dla wyspecyfikowanego rodzaju raportu identyfikowanego przez parametr ReportType. ULONG H i d P _ M a x D a t a L i s t L e n g t h ( HIDP_REPORT_TYPE R e p o r t T y p e , PHIDP_PREPARSED_DATA P r e p a r s e d D a t a );
//
--------------------------------------------------------------------
ULONG d a t a L i s t L e n g t h
=
H id P _ M a x D a ta L is tL e n g th ( H id P _ I n p u t, p rin tf("M a x D a ta L istL e n g th = % d \n ",
//
preparsedD ata);
d a ta L istL e n g th );
--------------------------------------------------------------------
Funkcja HidP_MaxUsageListLength() Funkcja zwraca maksymalną liczbę elementów urządzenia (np. przycisków) z prede finiowanej grupy urządzeń UsagePage, posługujących się określonym przez parametr ReportType typem raportu. ULONG H id P _ M a xU sa g e L istL e n g th ( IN HIDP_REPORT_TYPE
ReportType,
IN USAGE
OPT IONAL,
UsagePage
IN PHIDP_PREPARSED_DATA
P reparsedD ata
);
Wartość parametru UsagePage stanowi kryterium wyszukiwania. Jeżeli UsagePage=HID_ USAGE_PAGE_UNDEFINED, funkcja zwróci liczbę przycisków w urządzeniu obsługujących dany typ raportu. //
-----------------------------------------------------------
# d e f i n e HID_USAGE_PAGE_BUTTON
((USAGE)
//... USAGE U sa g e P a g e = HID_USAGE_PAGE_BUTTON;
0x 09 )
//h id u s a g e .h
Rozdział 4. ♦ Urządzenia klasy HID
139
ULONG u s a g e L i s t L e n g t h = H id P _ M a x U s a g e L is tL e n g th (H id P _ In p u t, p rin tf("\n % d ",
//
UsagePage,
preparsedD ata);
u sa g e L is tLe n g th );
-----------------------------------------------------------
Funkcja HidP_SetData() Funkcja umieszcza w raporcie identyfikowanym przez ReportType wyspecyfikowany zbiór danych. NTSTATUS
std ca ll
H id P _S e tD a ta ( IN HIDP_REPORT_TYPE
ReportType,
IN PHIDP_DATA
D a ta L ist,
IN OUT PULONG
D ataLength,
IN PHIDP_PREPARSED_DATA IN OUT PCHAR IN ULONG
PreparsedD ata,
Report,
ReportLength
);
Wskaźnik DataList wskazuje tablicę struktur HIDP_DATA. Parametr wejściowy DataLength określa liczbę elementów w tablicy wskazywanej przez DataList. Parametr ReportLength określa rozmiar (długość) w bajtach raportu wskazywanego przez Report. Rozmiar ra portu powinien odpowiadać wartości zwracanej przez funkcję HidP_GetCaps(). Poprawnie użyta funkcja HidP_SetData() zwraca wartość o statusie HIDP_STATUS_SUCCESS.
Funkcja HidP_SetButtons() Funkcja HidP_SetButtons() je st synonimem funkcji HidP_SetUsages(). # d e fin e H id P _ S e t B u t t o n s ( R ty , H id P _ S etU sag es(R ty ,
Up, Up,
Lco, Lco,
U Li, U Li,
U Le , U Le ,
P pd , P pd ,
Re p, Re p,
Rle)
\
Rle)
Funkcja HidP_SetScaledUsageValue() Funkcja przekształca wybraną wielkość fizyczną UsageValue na odpowiadającą jej w ar tość logiczną i umieszcza w wyspecyfikowanym przez ReportType raporcie. NTSTATUS
std ca ll
H id P _ S e tS c a le d U s a g e V a lu e ( IN HIDP_REPORT_TYPE IN USAGE IN USHORT IN USAGE IN LONG
ReportType,
UsagePage, L in k C o lle c tio n U sageValue,
IN PHIDP_PREPARSED_DATA IN OUT PCHAR IN ULONG
OPT IONAL,
Usage, PreparsedD ata,
Report,
ReportLength
);
Poprawnie użyta funkcja HidP_SetScaledUsageValue() zwraca wartość o statusie HIDP_ STATUS_SUCCESS.
140
USB. Praktyczne programowanie z Windows API w C++
Funkcja HidP_SetUsageValue() W raporcie w yspecyfikow anym przez ReportType funkcja umieszcza określone w ar tości, korzystając z pól struktury HIDP_VALUE_CAPS. NTSTATUS
std ca ll
H id P_S etU sa geV a lu e( IN HIDP_REPORT_TYPE R e p o r t T y p e , IN USAGE U s a g e P a g e , IN USHORT L i n k C o l l e c t i o n , IN USAGE U s a g e , IN ULONG U s a g e V a l u e , IN PHIDP_PREPARSED_DATA P r e p a r s e d D a t a , IN OUT PCHAR R e p o r t , IN ULONG R e p o r t L e n g t h );
Poprawnie użyta funkcja HidP_SetUsageValue() zwraca wartość o statusie HIDP_STATUS_ SUCCESS.
Funkcja HidP_SetUsageValueArray() Funkcja umieszcza w buforze wskazywanym przez UsageValue (o rozmiarze UsageValueByteLength) tablicę danych przeznaczonych dla danego typu raportu HidP_Output lub HidP_Feature. Raport powinien się składać z więcej niż jednego pola. NTSTATUS
std ca ll
H id P _ S e tU s a g e V a lu e A rra y ( HIDP_REPORT_TYPE R e p o r t T y p e , USAGE U s a g e P a g e , USHORT L i n k C o l l e c t i o n , USAGE U s a g e , PCHAR U s a g e V a l u e , USHORT U s a g e V a l u e B y t e L e n g t h , PHIDP_PREPARSED_DATA P r e p a r s e d D a t a , PCHAR R e p o r t , ULONG R e p o r t L e n g t h );
Poprawnie użyta funkcja HidP_SetUsageValueArray zwraca wartość o statusie HIDP_ STATUS_SUCCESS.
Funkcja HidP_SetUsages() Funkcja um ieszcza w raporcie wartości odpowiadające stanowi O N dla elementów kontrolnych lub sterujących urządzenia. NTSTATUS
std ca ll
H id P _ S e tU sa g e s( IN HIDP_REPORT_TYPE R e p o r t T y p e , IN USAGE U s a g e P a g e , IN USHORT L i n k C o l l e c t i o n , IN OUT PUSAGE U s a g e L i s t , IN OUT PULONG U s a g e L e n g t h , IN PHIDP_PREPARSED_DATA P r e p a r s e d D a t a ,
Rozdział 4. ♦ Urządzenia klasy HID
141
IN OUT PCHAR R e p o r t , IN ULONG R e p o r t L e n g t h );
Param etr ReportType określa typ raportu (HidP_Output lub HidP_Feature). W skaźnik Report wskazuje bufor danych z raportem. Parametr ReportLength określa w bajtach długość raportu, która powinna być równa analogicznej wartości zwracanej przez funk cję HidP_GetCaps(). Wskaźnik UsageList wskazuje tablicę danych typu USAGE. Aplikacje działające w trybie użytkownika i aplikacje działające w trybie jądra systemu powinny się posługiwać funkcją HidP_MaxUsageListLength() do określenia maksymalnej liczby elementów sterujących lub kontrolnych występujących w określonym typie raportu. //
--------------------------------------------------------------------
//... # d e f i n e HID_USAGE_PAGE_LED
(( USAGE) 0 x 0 8 )
# d e f i n e HID_USAGE_LED_NUM_LOCK
(( USAGE) 0x 01 )
# d e f i n e HID_USAGE_LED_CAPS_LOCK
(( USAGE) 0x 02 )
# d e f i n e HID_USAGE_LED_SCROLL_LOCK
(( USAGE) 0x 03 )
//h id u s a g e .h
//... c h a r * o u t p u t R e p o r t B u f f e r = new c h a r [ c a p a b i l i t i e s . O u t p u t R e p o r t B y t e L e n g t h ] ; assert(outputR eportBuffer!= N U LL); m em set(ou tpu tR eportBu ffer,
0,
c a p a b ilitie s .O u tp u tR e p o rtB y te L e n g th );
//... USAGE U s a g e L i s t [ u s a g e L i s t L e n g t h ] ; USAGE Usag e1 = HID_USAGE_LED_NUM_LOCK; USAGE Usa g e2 = HID_USAGE_LED_CAPS_LOCK; USAGE Usa g e3 = HID_USAGE_LED_SCROLL_LOCK; USAGE U sa g e4 = / * . . . * / ;
//... U sag eList[0 ]
= Usage1;
U sag eList[1 ]
= Usage2;
U sag eList[2 ]
= Usage3;
U sag eList[3 ]
= Usage4;
//... ULONG U s a g e L e n g t h = 0; if(U s a g e 1 != 0 ) Us a g e L e n g th + + ; if(U s a g e 2 != 0 ) Us a g e L e n g th + + ; if(U s a g e 3 != 0 ) Us a g e L e n g th + + ; if(U s a g e 4 != 0 ) Us a g e L e n g th + + ;
//... H id P _ S e tU sa g e s(H id P _ O u tp u t,
HID_USAGE_PAGE_LED,
&UsageLength,
preparsedD ata,
0,
U sa g e List,
ou tp utR ep ortB u ffer,
c a p a b ilitie s .O u tp u tR e p o r tB y te L e n g th );
//... DWORD n u m b e r O f B y t e s W r i t e = 0; W rite F ile ( / * ...* /,
& outputR epo rtB uffer,
c a p a b ilitie s .O u tp u tR e p o r tB y te L e n g th , & num berO fB ytesW rite,
N U LL ) )
//... / / --------------------------------------------------------------------
142
USB. Praktyczne programowanie z Windows API w C++
Popraw nie użyta funkcja HidP_SetUsages() zwraca wartość o statusie HIDP_STATUS_ SUCCESS.
Funkcja HidP_UnsetUsages() Funkcja umieszcza wartość binarną w stanie OFF w raporcie wyspecyfikowanym przez param etr ReportType. NTSTATUS
std ca ll
H id P _U n se tU sa ge s( IN HIDP_REPORT_TYPE R e p o r t T y p e , IN USAGE U s a g e P a g e , IN USHORT L i n k C o l l e c t i o n
OPTIONAL,
IN OUT PUSAGE U s a g e L i s t , IN OUT PULONG U s a g e L e n g t h , IN PHIDP_PREPARSED_DATA P r e p a r s e d D a t a , IN OUT PCHAR R e p o r t , IN ULONG R e p o r t L e n g t h );
Użyty jako parametr wejściowy wskaźnik Report wskazuje raport danych (Output lub Feature); użyty jako parametr wyjściowy wskazuje typ raportu. Parametr ReportLength określa w bajtach długość raportu, która pow inna być równa analogicznej wartości zwracanej przez funkcję HidP_GetCaps(). Poprawnie użyta funkcja HidP_UnsetUsages() zwraca wartość o statusie HIDP_STATUS_ SUCCESS.
Funkcja HidP_UsageListDifference() Funkcja wydobywa różnice między zawartościami dwóch tablic przechowujących w ła ściwości elementów kontrolnych urządzenia. W ten sposób można porównać zmiany stanu przycisków sterujących podczas kolejnych wyw ołań takich funkcji ja k HidP_ GetUsagesEx() lub HidP_GetButtons() w trakcie odczytu raportu wejściowego. NTSTATUS
std ca ll
H id P _ U s a g e L is tD iffe r e n c e ( IN PUSAGE
P re v io u sU s a g e L is t,
IN PUSAGE
C u rre n tU sa g e L ist,
OUT PUSAGE
B re a kU sa ge List,
OUT PUSAGE
M a k e U s a g e L is t,
IN ULONG
U sag eListLen g th
);
Wskaźnik PreviousUsageList wskazuje tablicę danych, której elementy funkcja porówna z elementami tablicy wskazywanej przez CurrentUsageList. Parametr BreakUsageList wskazuje bufor danych, który na wyjściu zawiera listę PreviousUsageList. Wskaźnik MakeUsageList wskazuje bufor danych, który zawiera listę CurrentUsageList. Parametr UsageListLength określa rozmiar tablic danych. / / -------------------------------------------------------------------USAGE_AND_PAGE * U s a g e s = new U S A G E _ A N D _ P A G E [ u s a g e L i s t L e n g t h ] ; c o n s t ULONG m a x P r e v i o u s U s a g e s = 14; USAGE p r e v i o u s U s a g e s [ m a x P r e v i o u s U s a g e s ] ;
Rozdział 4. ♦ Urządzenia klasy HID
w h ile ( tru e )
143
{ / / c y k l i c z n y o d c z y t r a p o r t u w ejści ow eg o
m e m s e t(in p u tR e p o rtB u ffe r,
0x00,
ca p a b ilitie s .In p u tR e p o rtB y te L e n g th );
R e a d F ile (/ * ...* / ) ; if(n u m b e rO fB y te s R e a d = = c a p a b ilitie s .In p u tR e p o rtB y te L e n g th ){ fo r ( U S H O R T i= 0 ; p rin tf("% d
i< c a p a b ilitie s .In p u tR e p o rtB y te L e n g th ; ",
i++)
in p u tR e p o rtB u ffe r [i]);
pri n tf("\n "); ULONG v a l i d U s a g e s
= u s a g e L is tL e n g th ;
H id P _ G e tU sa g e sE x (H id P _ In p u t,
0,
preparsedD ata,
Usages,
& v a lid U s a g e s,
in p u tR e p o rtB u ffe r ,
c a p a b ilitie s .In p u tR e p o rtB y te L e n g th ); USAGE * c u r r e n t U s a g e s = new U S A G E [ m a x P r e v i o u s U s a g e s ] ; USAGE * b r e a k U s a g e s = new U S A G E [ m a x P r e v i o u s U s a g e s ] ; USAGE *m a k eU sa g es = new U S A G E [ m a x P r e v i o u s U s a g e s ] ;
/ / s t a n a k t u a l n i e używanych p r z y c i s k ó w m em set(currentUsages,
0,
s iz e o f( c u rr e n tU s a g e s ) ) ;
p r i n t f ( " Z b i ó r p rz y c is k ó w : fo r ( U L O N G i= 0 ; p r in tf(
");
i< v a lid U s a g e s ;
"UsagePage=%d
i++)
{
Usage=%d\n " ,
U sag es[i].U sag e P ag e,
U sa g e s[i].U sa g e ); cu rre n tU sa g e s[i]
= U sag e s[i].U sa ge ;
} H id P _ U s a g e L is tD i ffe r e n c e ( p re v io u s U s a g e s ,
currentU sages,
b r e a kU s a g e s , m akeUsages, p r in tf( " B re a k L is t: f o r ( U L O N G i= 0 ;
");
i< u s a g e L is tL e n g th ;
if(b re a k U sa g e s[i]= = 0 ) p rin tf("% d } p rin tf("M a k e L is t: f o r ( U L O N G i= 0 ;
",
i++) {
break;
b r e a k U s a g e s [i]);
");
i< u s a g e L is tL e n g th ;
if( m a k e U s a g e s [i] = = 0 ) p rin tf("% d
u s a g e L is tL e n g th );
i++) {
break;
", m a k e U s a g e s [i]);
} p r in tf( " \ n \ n " ) ;
/ / z a p i s p o p r z e d n i c h u s ta w ie ń m em cp y(p re vio u s U sa g e s,
currentU sages,
d e le te
[]
currentU sages;
d e le te
[]
breakUsages;
d e le te
[] m a k e U s a g e s ;
u sa g e L is tL e n g th * s iz e o f(U S A G E ));
}
//... / / --------------------------------------------------------------------
Poprawnie użyta funkcja HidP_UsageListDifference() zwraca wartość o statusie HIDP_ STATUS_SUCCESS.
Funkcja HidP_UsageAndPageListDifference() Funkcja ta jest analogiczna do HidP_UsageListDifference(), z tą różnicą, że jej cztery pierwsze argumenty wskazują tablicę struktur USAGE_AND_PAGE.
14 4
USB. Praktyczne programowanie z Windows API w C++
NTSTATUS
std ca ll
H id P _ U s a g e A n d P a g e L is tD i f f e r e n c e ( IN PUSAGE_AND_PAGE
P re v io u sU s a g e L is t,
IN PUSAGE_AND_PAGE
C u rre n tU sa g e L ist,
OUT PUSAGE_AND_PAGE
B re a kU sa ge List,
OUT PUSAGE_AND_PAGE
M a k e U s a g e L is t,
IN ULONG
U sag eListLen g th
);
Popraw nie w ykonana funkcja HidP_UsageAndPageListDifference() zwraca wartość o statusie HIDP STATUS SUCCESS.
Biblioteka HID.dll W szystkie aplikacje tworzone w środowisku W indows m ają tzw. nierozwiązane od niesienia do funkcji — często nazywa się je importowanymi wywołaniami bibliotek. Poszczególne biblioteki dołączane dynamicznie (popularne DLL-e) zawierają progra my potrzebne do w ykonania wywoływanej funkcji i w momencie instalacji z reguły stają się częścią API systemu operacyjnego. Znajdujące się w jądrze systemu pliki .lib i .dll są wykonywalnymi modułami zawie rającymi definicje eksportowanych zmiennych i funkcji, a w szczególności funkcji ste rowników urządzeń. Biblioteki dołączane w czasie wykonywania programu (ang. run -time dynamic linking) wym agają w yw ołań funkcji LoadLibrary(), GetProcAddress() i F reeL ibrary(). Funkcja LoadLibrary() odwzorowuje wskazany moduł wykonawczy (plik .dll lub .exe) na przestrzeń adresową wykonywanego procesu. HINSTANCE L o a d L i b r a r y ( I N
LPCTSTR l p L i b F i l e N a m e ) ;
Param etr lpLibFileName jest w skaźnikiem do ciągu znaków zakończonych zerowym ogranicznikiem, zawierającym nazwę modułu wykonawczego (plik .dll lub .exe). Po dana nazwa dotyczy pliku modułu i nie jest związana z nazwą własną przechowywaną w module bibliotecznym. Funkcja zgłosi błąd w postaci pustego wskaźnika, jeśli po dana nazwa zawiera ścieżkę dostępu do pliku, w którym żądany moduł nie występuje. Jeżeli nie podano ścieżki dostępu do pliku i pominięto rozszerzenie nazwy pliku, to dom yślnie dołączanym rozszerzeniem je st .dll. W przypadku powodzenia wartością zwracaną przez funkcję je st identyfikator modułu. Funkcja LoadLibrary() je st używana głównie w celu dynamicznego odwzorowywania modułów .dll i pobierania ich identyfikatorów, które z kolei są używane przez funkcję: FARPROC G e t P r o c A d d r e s s ( I N HMODULE h M o d u l e , I N
LPCSTR l p P r o c N a m e ) ;
do pobierania adresów funkcji składowych bibliotek .dll. Warto zdawać sobie sprawę z faktu, iż identyfikatory modułów nie są zmiennymi globalnymi. Oznacza to, że w y wołanie funkcji LoadLibrary() w jednym procesie nie może być wykorzystane do okre ślania identyfikatora używanego w procesach potomnych.
Rozdział 4. ♦ Urządzenia klasy HID
145
Do zwalniania identyfikatora przydzielonego za pom ocą funkcji LoadLibrary() służy funkcja FreeLibrary(). W tabeli 4.6 zamieszczono zestawienie funkcji eksportowych dostępnych z poziomu biblioteki HID.dll, zaś na rysunku 4.7 pokazano umiejscowienie omawianej biblioteki na stosie sterowników Windows HID. Tabela 4.6. Podstawowe funkcje eksportowe biblioteki HID.dll Nazwa funkcji eksportowej
Moduł
Dostępność
HidD FlushQueue()
hidsdi.h
Dostępna
HidD FreePreparsedData()
hidsdi.h
Dostępna
HidD G etA ttributes()
hidsdi.h
Dostępna
HidD G etConfiguration()
hidsdi.h
Zarezerwowana do użytku systemowego
HidD GetFeature()
hidsdi.h
Dostępna
HidD GetHidGuid()
hidsdi.h
Dostępna
HidD GetIndexedString()
hidsdi.h
Dostępna
HidD GetInputReport()
hidsdi.h
Dostępna
HidD GetM anufacturerString()
hidsdi.h
Dostępna
HidD GetMsGenreDescriptor()
hidsdi.h
Zarezerwowana do użytku systemowego
HidD GetNumInputBuffers()
hidsdi.h
Dostępna
HidD G etPhysicalD escriptor()
hidsdi.h
Dostępna
HidD GetPreparsedData()
hidsdi.h
Dostępna
HidD G etProductString()
hidsdi.h
Dostępna
HidD GetSerialNumberString()
hidsdi.h
Dostępna
HidD SetC onfiguration()
hidsdi.h
Zarezerwowana do użytku systemowego
HidD SetFeature()
hidsdi.h
Dostępna
HidD SetNumInputBuffers()
hidsdi.h
Dostępna
HidD SetOutputReport()
hidsdi.h
Dostępna
HidP FreeC ollectionD escription()
hidpddi.h
Zarezerwowana do użytku systemowego
HidP GetButtonCaps()
hidpi.h
Dostępna
HidP GetCaps()
hidpi.h, hidparse lib.lib
Dostępna
HidP G etC ollectionD escription()
hidpddi.h
Zarezerwowana do użytku systemowego
HidP GetData()
hidpi.h
Dostępna
HidP G etExtendedA ttributes()
hidpi.h, hidparse_lib.lib
Dostępna
HidP GetLinkCollectionNodes()
hidpi.h
Dostępna
HidP GetScaledUsageValue()
hidpi.h
Dostępna
146
USB. Praktyczne programowanie z Windows API w C++
Tabela 4.6. Podstawowe funkcje eksportowe biblioteki HID.dll — ciąg dalszy Nazwa funkcji eksportowej
Moduł
Dostępność
HidP GetSpecificButtonCaps()
hidpi.h, hidparse lib.lib
Dostępna
HidP GetSpecificValueCaps()
hidpi.h, hidparse lib.lib
Dostępna
HidP GetUsageValue()
hidpi.h, hidparse lib.lib
Dostępna
HidP GetUsageValueArray()
hidpi.h, hidparse lib.lib
Dostępna
HidP GetUsages()
hidpi.h, hidparse lib.lib
Dostępna
HidP GetUsagesEx()
hidpi.h, hidparse lib.lib
Dostępna
HidP GetValueCaps()
hidpi.h
Dostępna
HidP InitializeR eportF orID ()
hidpi.h
Dostępna
HidP MaxDataListLength()
hidpi.h, hidparse lib.lib
Dostępna
HidP MaxUsageListLength()
hidpi.h
Dostępna
HidP SetData()
hidpi.h, hdparse_lib.lib
Dostępna
HidP SetScaledUsageValue()
hidpi.h
Dostępna
HidP SetUsageValue()
hidpi.h, hidparse lib.lib
Dostępna
HidP SetUsageValueArray()
hidpi.h, hidparse lib.lib
Dostępna
HidP SetUsages()
hidpi.h, hidparse lib.lib
Dostępna
HidP SysPowerCaps
hidpddi.h
Zarezerwowana do użytku systemowego
HidP SysPowerEvent()
hidpddi.h
Zarezerwowana do użytku systemowego
HidP TranslateUsagesToI8042ScanCodes()
hidpddi.h
Zarezerwowana do użytku systemowego
HidP UnsetUsages()
hidpi.h, hidparse lib.lib
Dostępna
HidP UsageAndPageListDifference()
hidpi.h
Dostępna
HidP U sageListD ifference()
hidpi.h
Dostępna
W tabeli 4.7 zaprezentowano zestawienie możliwości wykorzystania omawianych za sobów biblioteki HID.dll w oprogramowaniu uruchamianym w trybie użytkownika lub w trybie jądra. Biblioteka HID.dll znajduje się w katalogu \System32\ lub SySW OW 64\. Tabela 4.7. Sposoby wykorzystania funkcji biblioteki HID.dll Sterowniki
Aplikacje
Tryb użytkownika
HidD_Xxx()
HidD_Xxx(), HidP_Xxx()
Tryb jądra
HidD Xxx() lub rozkazy IOCTL HID xxx()
Rozdział 4. ♦ Urządzenia klasy HID
147
Rysunek 4.7. Umiejscowienie biblioteki HID.dll na stosie sterowników Windows HID
Podsumowanie W niniejszym rozdziale zaprezentowano główne założenia transm isji danych z urzą dzeniam i klasy HID. Przedstawiono najważniejsze struktury danych przechowujące informacje o organizacji oraz najistotniejszych parametrach urządzeń klasy HID. Omó wiono zasoby biblioteki H ID .dll oraz zaprezentowano formaty danych transmitowa nych przez łącze USB. Rozdział zawiera też opis podstawowych funkcji pomocnych w samodzielnej realizacji transm isji danych z urządzeniam i klasy HID. W iele prak tycznych sposobów wykorzystania omówionych zasobów systemowych zostanie przed staw ionych w dalszej części książki. Dodatkowe informacje na tem at sposobów ko munikacji z urządzeniami klasy HID można znaleźć w książkach Janet Axelson [1, 2] i Chrisa Canta [5].
148
USB. Praktyczne programowanie z Windows API w C++
Rozdział 5.
Detekcja i identyfikacja urządzeń dołączonych do magistrali USB Urządzenia USB są automatycznie wykrywane przez system operacyjny po ich pod łączeniu i włączeniu zasilania. Kiedy w systemie pojawi się nowy sprzęt, aktywowane są procedury jego detekcji i identyfikacji. Zespół tego typu operacji często jest okre ślany jako wyliczanie lub enumeracja urządzeń (ang. enumeration). Rozpoczęcie pro cesu enum eracji powoduje przejście urządzenia między podstawowym i stanami, jak pokazano na rysunku 5.1.
Rysunek 5.1. Podstawowe stany urządzenia w trakcie enumeracji
150
USB. Praktyczne programowanie z Windows API w C++
Z a pośrednictwem kilkunastu czynności, z których najważniejsze zostały przedstawio ne poniżej, system operacyjny wykonuje enumerację urządzenia w ramach poszcze gólnych stanów.
Rysunek 5.2. Szczegółowy diagram czynności dla procesu enumeracji urządzeń dołączanych do magistrali USB ♦ Użytkownik przyłącza urządzenie do portu USB hosta (macierzystego komputera) lub huba (koncentratora) — urządzenie pozostaje w stanie przyłączenia (ang. attached state). ♦ Po odblokowaniu linii zasilającej urządzenie przechodzi w stan zasilania (ang. powered state). ♦ Po sprawdzeniu stanu linii zasilających oprogramowanie hosta przystępuje do konfigurowania nowego sprzętu. ♦ Hub poprzez testowanie stanu linii sygnałowych sprawdza, z ja k ą prędkością przesyłu danych urządzenie może pracować. Informacja zostaje przekazana do hosta w odpowiedzi na wysłany przez niego rozkaz GET_STATUS. ♦ Kiedy nowe urządzenie zostaje rozpoznane, kontroler hosta wysyła do huba rozkaz SET_FEATURE. Port zostaje zresetowany (przez 10 ms linie sygnałowe pozostają w niskim stanie logicznym). ♦ Poprzez dalsze testowanie aktualnego stanu linii sygnałowych host sprawdza, czy urządzenie pracujące z pełną szybkością przesyłu danych może pracować też z szybkością wysoką. ♦ Ponownie wysyłając rozkaz GET_STATUS, host sprawdza, czy urządzenie pozostaje w stanie Reset. Jeżeli nie, zostaje utworzony potok zerowy przeznaczony do celów konfiguracji urządzenia. Urządzeniu zostaje przypisany domyślny adres 00h, po czym przechodzi ono do stanu domyślnego (ang. default state). ♦ Host wysyła rozkaz GET_DESCRI PTOR, aby otrzymać informacje o maksymalnym rozmiarze pakietu danych, który może być transmitowany potokiem domyślnym. R ozkaz ten je st kierowany do zerowego punktu końcowego EP0 urządzenia. O program owanie hosta może identyfikować w danym czasie tylko jedno urządzenie, zatem tylko jedno urządzenie w danym czasie może pozostawać z adresem 00h.
Rozdział 5. ♦ Detekcja i identyfikacja urządzeń dołączonych do magistrali USB
151
♦ W następnej kolejności za pośrednictwem rozkazu SET_ADDRESS urządzeniu je st przypisywany unikatowy adres — urządzenie przechodzi do stanu adresowania (ang. addressed state). Nowy adres pozostaje aktualny, dopóki urządzenie jest przyłączone do portu USB. W momencie odłączenia urządzenia port jest resetowany. ♦ W dalszej kolejności za pośrednictw em na nowo adresowanego rozkazu GET_DESCRI PTOR oprogramowanie hosta pobiera kompletne deskryptory urządzenia (patrz rysunek 3.9). ♦ Po odczytaniu deskryptorów urządzenia oprogramowanie hosta wyszukuje dla urządzenia najlepiej pasujący sterownik i zapisuje odpowiednie informacje (Vendor ID, Product ID, ...) w pliku .inf. ♦ Sterownik urządzenia wysyła rozkaz SET_CONFIGURATION w celu ostatecznego skonfigurowania nowego sprzętu. Od tego momentu urządzenie pozostaje w stanie skonfigurowania (ang. configured state)1.
Podstawowe zasoby systemowe K om pilator C++ w module setupapi.h udostępnia szereg użytecznych funkcji i struk tur, które w znakomity sposób um ożliw iają sam odzielne przeprow adzenie detekcji i identyfikacji ścieżek dostępu do interfejsów oferowanych przez sterownik(i) urządzeń aktualnie dołączonych do magistrali USB. W tym podrozdziale zostały przedstawione najważniejsze z nich. W dalszej części książki ze względu na zwięzłość sformułowań poprzez interfejs urzą dzenia będziemy rozum ieli interfejs, ja k i sterownik urządzenia udostępnia warstwie aplikacji.
Uwaga
Windows Driver Kit je st w pełni kompatybilny jedynie z kompilatorami VC++. W de finicjach struktur i funkcji WDK w sposób niejednolity używa dla typów zmiennych rozszerzeń IN lu b in w celu oznaczenia parametrów wejściowych, OUT lu b out dla oznaczenia parametrów wyjściowych lu b opt dla oznaczenia parametrów opcjonalnych. Możliwe je st również występowanie oznaczeń będących kombinacją tych parametrów, n p . inout lu b in opt. Niektóre kompilatory C++ mogą zgła szać błędy w trakcie kompilacji modułów zawierających tego rodzaju oznaczenia w deklaracjach zmiennych. W przypadku napotkania przez kompilator problemów z używanymi przez WDK rozszerzeniami należy podjąć próbę zmiany ustawień opcji używanego kompilatora C++; można również bez jakiejkolwiek szkody dla oprogra mowania opisane wyżej elementy, które nie są rozpoznawane przez kompilator, sa modzielnie usunąć z odpowiednich plików nagłówkowych.
1 Jeżeli w trakcie transmisji urządzenie USB 2.0 przez 3 ms nie wykrywa znacznika początku ramki danych SOF, przechodzi do stanu zawieszenia (ang. suspended state).
152
USB. Praktyczne programowanie z Windows API w C++
Funkcja SetupDiGetClassDevs() Funkcja zwraca identyfikator klasy podłączonych urządzeń, których lista i opis kon figuracji znajduje się w rejestrze system owym w kluczu HKEY_LOCAL_MACHINE (patrz rozdział 2.). HDEVINFO S etu p D iG e tC la s sD e v s( IN LPGUID
C la s s G u id ,
IN PCTSTR
Enum erator,
IN HWND IN DWORD
hwndParent,
OPTIONAL OPTIONAL OPTIONAL
F la g s
);
Parametr ClassGuid wskazuje strukturę GUID klasy urządzeń. Użycie tego parametru jest opcjonalne. Aplikacje użytkownika m ogą pobierać adres identyfikatora GUID dla klasy urządzeń HID za pom ocą funkcji HidD_GetHidGuid(). Wskaźnik Enumerator wskazuje łańcuch znaków (zakończony zerowym ogranicznikiem), przechowujący dane konkret nych urządzeń (patrz rozdział 2., rysunek 2.4). Użycie tego param etru w programie jest opcjonalne. Jeżeli w skaźnikow i przypiszemy wartość NULL, funkcja zwróci listę urządzeń typu PnP (ang. Plug and Play). Opcjonalnie wykorzystywany identyfikator hwndParent wskazuje okno odpowiedzialne za interakcję z otrzymanym zestawem urzą dzeń. Znacznik Flags przyjmuje postać bitowej alternatywy wybranego zestawu nastę pujących stałych symbolicznych: ♦ DIGCF_ALLCLASSES — określa listę wszystkich zainstalowanych w systemie urządzeń; ♦ DIGCF_DEVICEINTERFACE — określa listę zainstalowanych urządzeń z danym interfejsem; ♦ DIGCF_DEFAULT — zwraca listę urządzeń z domyślnym interfejsem; ♦ DIGCF_PRESENT — określa urządzenia aktualnie dostępne w systemie; ♦ DIGCF_PROFILE — określa listę urządzeń będących częścią aktualnego zestawu sprzętowego. Jeżeli wykonanie funkcji nie powiedzie się, zwracana jest wartość INVALID_HANDLE_VALUE. K od ewentualnego błędu m ożna zdiagnozow ać za pom ocą funkcji G etL astE rro r(). Szczegółowe kody błędów są dostępne na stronie http://m sdn.m icrosoft.com /en-us/ library/windows/desktop/m s681382(v=vs.85).aspx.
Funkcja SetupDiEnumDeviceInterfaces() Funkcja wyszukuje interfejsy urządzeń identyfikowanych przez wskaźnik DeviceInfoSet zwracany przez funkcję SetupDiGetClassDevs(). WINSETUPAPI
BOOL WINAPI
S e tu p D iE n u m D e v ice In te rfa ce s ( IN HDEVINFO
D e vice In fo S e t,
Rozdział 5. ♦ Detekcja i identyfikacja urządzeń dołączonych do magistrali USB
IN PSP_DEVINFO_DATA IN LPGUID
D e vice In fo D a ta ,
1S3
OPTIONAL
In te rfa c e C la s s G u id ,
IN DWORD
M e m b e rI n d e x ,
OUT PS P_ DE VI CE_ IN TER FA CE_ DAT A
D e vice In te rfa ce D a ta
);
W skaźnik DeviceInfoData wskazuje strukturę SP_DEVINFO_DATA (patrz tabela 5.1), co umożliwia ograniczenie przeszukiwań istniejących urządzeń. Opcjonalnie funkcji moż na przekazać pusty wskaźnik. W takim wypadku funkcję należy wywoływać cyklicz nie, tak aby przeszukała w szystkie interfejsy udostępniane przez sterownik danego urządzenia. W skaźnik InterfaceC lassG uid wskazuje strukturę GUID. Param etr w ej ściowy MemberIndex je st numerem odpytywanego interfejsu. Jego wartości zaczynają się od 0 (zerowy indeks pierwszego interfejsu — interfejsu domyślnego). Jeżeli funk cja jest wywoływana w pętli cyklicznie, przy każdym wywołaniu należy odpowiednio zwiększyć wartość MemberIndex. Jeżeli SetupDiEnumDeviceInterfaces() zwróci w ar tość FALSE oraz funkcja G etL astE rror() zwróci ERROR_NO_MORE_ITEMS, oznacza to, że nie znaleziono interfejsu o podanym indeksie. Wskaźnik D eviceInterfaceD ata wska zuje strukturę SP_DEVICE_INTERFACE_DATA (patrz tabela 5.2), której rozmiar należy w pi sać do pola cbSize: d e v ic e In te rfa c e D a ta .c b S iz e
= size o f(SP_D EVICE_IN TE RFACE_D ATA);
Struktura SP_DEVINFO_DATA W polach struktury są przechowywane informacje na tem at egzemplarza urządzenia należącego do klasy urządzeń USB. W tabeli 5.1 zamieszczono jej opis. Tabela 5.1. Specyfikacja struktury SP DEVINFO DATA Typ
Element struktury
Znaczenie
DWORD
cbSize
Rozmiar struktury w bajtach
GUID
ClassGuid
Identyfikator GUID klasy urządzeń
DWORD
DevInst
Identyfikator wewnętrznej struktury opisującej urządzenie w systemie
ULONG_PTR
Reserved
Zarezerwowane
W indows Driver K it (WDK) definiuje tę strukturę jako: t y p e d e f s t r u c t _SP _DEVINFO_DATA { DWORD GUID DWORD
c b S iz e ; C la s s G u id ; D evInst;
ULONG_PTR
Reserved;
} SP _D EVINFO_DATA,
*PSP_DEVINFO_DATA;
D efinicja ta tworzy dw a nowe słowa kluczowe: SP_DEVINFO_DATA (struktura) i PSP DEVINFO_DATA (wskaźnik do struktury).
15 4
Uwaga
USB. Praktyczne programowanie z Windows API w C++
Funkcje rodziny SetupDiXx(), używając struktury SP_DEVINFO_DATA jako parametru, automatycznie sprawdzają poprawność określenia jej rozmiaru. Aktualny rozmiar struktury należy wskazać za pomocą operatora size o f() i wpisać do pola cbSize. Jeżeli rozmiar struktury w ogóle nie zostanie określony lub zostanie określony nie prawidłowo, to w przypadku użycia struktury jako parametru wejściowego IN zostanie wygenerowany błąd ERROR_INVALID_PARAMETER, natomiast przy korzystaniu ze struk tury jako parametru wyjściowego OUT zostanie wygenerowany błąd ERROR_INVALID_ USER BUFFER.
Struktura SP_DEVICE_INTERFACE_DATA Zasoby struktury SP_DEVICE_INTERFACE_DATA zaprezentowane w tabeli 5.2 przechowu j ą dane interfejsu należącego do klasy urządzeń USB. Tabela 5.2. Specyfikacja struktury SP DEVICE INTERFACE DATA Typ
Element struktury
Znaczenie
DWORD
cbSize
Rozmiar struktury w bajtach
GUID
InterfaceClassG uid
Identyfikator GUID interfejsu klasy urządzeń
DWORD
Flags
Znaczniki interfejsu. Wartość SPINT ACTIVE oznacza, że interfejs jest aktualnie dostępny. Wartość SPINT DEFAULT oznacza domyślny interfejs dla klasy urządzeń. Wartość SPINT REMOVED określa usunięty interfejs
ULONG_PTR
Reserved
Parametr zarezerwowany i aktualnie nieużywany
W DK definiuje tę strukturę jako: t y p e d e f s t r u c t _SP _D E VI CE _ IN TE R FA CE _D A TA { DWORD c b S i z e ; GUID
In te rfa c e C la s s G u id ;
DWORD F l a g s ; ULONG_PTR
Reserved;
} S P_ DE V IC E_ IN TE RF A CE _ DA TA ,
*P SP _D EV IC E _I NT ER FA CE _D A TA ;
Definicja ta tworzy dwa nowe słowa kluczowe: SP_DEVICE_INTERFACE_DATA (struktura) i PSP_DEVICE_INTERFACE_DATA (wskaźnik do struktury).
Uwaga
Funkcje zdefiniowane w module setupapi.h, używając struktury SP_DEVICE_INTERFACE_ DATAjako parametru, automatycznie sprawdzają poprawność określenia jej rozmiaru. Aktualny rozmiar struktury należy wskazać za pomocą operatora sizeo f() i wpisać do pola cbSize. Jeżeli rozmiar struktury w ogóle nie zostanie określony lub zostanie określony nieprawidłowo, system wygeneruje błąd ERROR_INVALID_USER_BUFFER.
Rozdział 5. ♦ Detekcja i identyfikacja urządzeń dołączonych do magistrali USB
1SS
Struktura SP_DEVICE_INTERFACE_DETAIL_DATA Struktura SP_DEVICE_INTERFACE_DETAIL_DATA zawiera informacje o postaci ścieżki do stępu do interfejsu wybranego urządzenia USB. W tabeli 5.3 przedstawiono znaczenie poszczególnych pól tej struktury. Tabela S.3. Specyfikacja struktury SP DEVICE INTERFACE DETAIL DATA Typ
Element struktury
Znaczenie
DWORD
cbSize
Rozmiar struktury w bajtach
TCHAR
DevicePath[ANYSIZE ARRAY]
Łańcuch znaków zakończony zerowym ogranicznikiem (tzw. NULL — ang. terminated string), zawierający pełną nazwę symboliczną urządzenia (ścieżkę dostępu do interfejsu udostępnianego przez sterownik urządzenia). Parametr ten jest wykorzystywany przez funkcję C reateF ile()
W DK definiuje tę strukturę jako: t y p e d e f s t r u c t _ S P _ D E VI CE _ I N TE R FA CE _D ET A I L_ D AT A { DWORD
c b S iz e ;
TCHAR
DevicePath[A N Y SIZE_A R RA Y];
} SP _ D EV I C E_ I N T E R F A C E_ D ET A I L _D A T A ,
* P S P _ D EV IC E _I N TE R FA CE _D ET AI L _D A TA ;
D efinicja ta tworzy dw a nowe słowa kluczowe: SP_DEVICE_INTERFACE_DETAIL_DATA (struktura) i PSP_DEVICE_INTERFACE_DETAIL_DATA (wskaźnik do struktury).
Uwaga
Niekiedy ścieżkę dostępu do interfejsu urządzenia utożsamia się z jego nazwą sym boliczną, którą można odczytać z rejestru systemowego (patrz rozdział 2.). Chociaż te dwa łańcuchy znaków mają bardzo podobną postać, to jednak mogą się różnić długością, dlatego w programach bezpieczniej jest posługiwać się kompletnymi da nymi zapisanymi w polu DevicePath struktury SP_DEVICE_INTERFACE_DETAIL_DATA.
Funkcja SetupDiGetDeviceInterfaceDetail() Funkcja zwraca szczegółowe informacje na temat interfejsu urządzenia. WINSETUPAPI
BOOL WINAPI
S e tu p D iG e tD e v ic e In te rfa c e D e ta il( IN HDEVINFO
D e vice In fo S e t,
IN PS P_ DE VI CE_ IN TER FA CE_ DAT A
D e vice In te rfa ce D a ta ,
OUT PS P_ DE V IC E_ IN TE RF A CE _ DE TA IL _ DA TA IN DWORD OUT PDWORD
R e q u ire d S iz e ,
OUT PSP_DEVINFO_DATA );
D e v ic e In te rfa c e D e ta ilD a ta ,
D e v ic e In te rfa c e D e ta ilD a ta S iz e , OPTIONAL
D e vice In fo D a ta
OPTIONAL
OPTIONAL
156
USB. Praktyczne programowanie z Windows API w C++
W skaźnik DeviceInfoSet je st zwracany przez funkcję SetupDiGetClassDevs(). Para metr D eviceInterfaceD ata wskazuje strukturę SP_DEVICE_INTERFACE_DATA. Wskaźnik D eviceInterfaceD etailD ata wskazuje strukturę SP_DEVICE_INTERFACE_DETAIL_DATA (patrz tabela 5.3); opcjonalnie zamiast niego do funkcji może być przekazana wartość NULL. W przypadku jaw nego wskazania struktury SP_DEVICE_INTERFACE_DETAIL_DATA w skaźnik pow inien być poprawnie zainicjowany, a jej pole cbSize musi być praw i dłowo określone. W przeciw nym razie kom pilator zgłosi błędy naruszenia pamięci, podobnie ja k na rysunkach 5.3 i 5.4.
Rysunek 5.3. Błąd naruszenia pamięci dla nieprawidłowo zainicjowanego wskaźnika do struktury SP DEVICE INTERFACE DETAIL DATA
Rysunek 5.4. Błąd naruszenia pamięci dla nieprawidłowo określonego rozmiaru pola cbSize struktury SP_DEVICE_INTERFACE_DETAIL_DATA Argument DeviceInterfaceDetailDataSize funkcji SetupDiGetDeviceInterfaceDetail() ma wartość zerową, jeżeli DeviceInterfaceDetailData=NULL; w przeciwnym razie okre śla rozm iar bufora: (offsetof(SP_DEVICE_INTERFACE_DETAIL_DATA, DevicePath) + sizeof(TCHAR)). Param etr RequiredSize je st wskaźnikiem do danej typu DWORD, której przypisuje się żądany rozmiar bufora wskazywanego przez DeviceInterfaceDetailData. Param etr DeviceInfoData je st wskaźnikiem do bufora danych przechowującego infor macje na tem at interfejsu udostępnianego przez sterownik urządzenia. Jeżeli w skaź nikowi nie przypisano wartości NULL, rozm iar danych pow inien zostać określony za pom ocą operatora s iz e o f ( ) : DeviceInfoData.cbSize=sizeof(SP_DEVINFO_DATA).
Rozdział 5. ♦ Detekcja i identyfikacja urządzeń dołączonych do magistrali USB
157
Funkcja SetupDiDestroyDeviceInfoList() Funkcja usuw a w szystkie zaalokowane zasoby zawierające informacje o urządzeniu i zwalnia przydzieloną im pamięć. Kolejne urządzenia podłączane do systemu mogą korzystać ze zwolnionych zasobów. WINSETUPAPI
BOOL WINAPI
S e tu p D iD e s tr o y D e v ic e I n fo L is t( IN HDEVINFO
D e vice In fo S e t
Wskaźnik DeviceInfoSet jest zwracany przez funkcję SetupDiGetClassDevs(). W przy padku prawidłowego zwolnienia zasobów funkcja zwraca wartość TRUE, w przeciwnym razie wartość FALSE. Kod wystąpienia błędu jest zwracany przez funkcję GetLastError().
Detekcja interfejsów urządzeń N a rysunku 5.5 w postaci diagramu czynności przedstawiono ogólną sieć działań, za pom ocą których można programowo samodzielnie wykonać procedurę detekcji urzą dzeń klasy HID aktualnie podłączonych do systemu, co w efekcie powinno skutkować odzyskaniem pełnych nazw symbolicznych (pełnych ścieżek dostępu do interfejsów) urządzeń zapisanych w polu DevicePath struktury SP_DEVICE_INTERFACE_DETAIL_DATA. HidD_GetHidGuid(..)
SetupDiGetClassDevs(...)
Rysunek 5.5. Ogólny diagram czynności dla operacji wstępnej enumeracji urządzeń klasy HID
158
USB. Praktyczne programowanie z Windows API w C++
Czynności (w znaczeniu nadawanym przez UML) mogą być interpretowane w zależ ności od wybranej perspektywy: jako zestaw pojedynczych metod (z perspektywy projektowej) lub jako zadanie do wykonania, i to zarówno przez człowieka, jak i przez komputer (z perspektywy pojęciowej). Diagramów czynności można używać do opi su metod rozwiązywania problemów wyrażonych w postaci skończonej sekwencji kroków — to je st ich cel. Obsługują one wszystkie standardowe konstrukcje stero wania wymagane do opisania algorytmów [14]. W pierwszej kolejności należy odczytać postać identyfikatora GUID interfejsu klasy urządzeń występujących w systemie. Wskaźnik deviceInfoSet wskaże dane zawiera jące informacje na temat wszystkich zainstalowanych i aktualnie dostępnych (przyłą czonych) urządzeń danej klasy. Następnie wyszukiwane są interfejsy poszczególnych urządzeń. Poprzez odczytanie zawartości pola DevicePath struktury SP_DEVICE_INTERFACE_ DETAIL_DATA wydobywana jest pełna ścieżka dostępu DevicePath do interfejsu istniejące go urządzenia USB. N a koniec dotychczas używane przez program zasoby są zwalniane. Niektóre z dostępnych kompilatorów języka C++ mogą niewłaściwie obliczać rozmiar struktur (za pomocą operatora sizeo f()). Błędne obliczenie rozmiaru którejkolwiek z używanych struktur będzie niezmiennie skutkować błędami w trakcie uruchamia nia programu. W takich sytuacjach należy zadbać o właściwe ustalenie opcji kompi latora na podstawie jego dokumentacji. Stosowana tu konstrukcja: #pragma o p t i o n
push -a1
11 ... #pragma o p t i o n
pop
odpowiada opisanej sytuacji. Inne przykłady rozwiązania tego typu problemów moż na znaleźć w artykule dostępnym pod adresem : http://support.codegear.com / article/35751. N a listingu 5.1 zamieszczono kod modułu projektu będącego uszczegółowioną imple m entacją diagramu z rysunku 5.5. Listing 5.1. Kod modułu usb_R5_1.cpp jako przykład zaprogramowania wstępnej enumeracji urządzeń na podstawie identyfikatora GUID klasy urządzeń # i n c l u d e #pragma o p t i o n
p u s h -a 1
# i n c lu d e < s e tu p a p i> #pragma o p t i o n
pop
# in c lu d e # in c lu d e u s i n g n a m es p ac e s t d ; v o id d is p la y E r r o r ( c o n s t
c h a r * msg){
c o u t << msg << e n d l ; system ("PAUSE"); e x it(0 ); };
//
--------------------------------------------------------------------
t e m p l a t e < c l a s s T>
Rozdział 5. ♦ Detekcja i identyfikacja urządzeń dołączonych do magistrali USB
in lin e
159
v o i d r e l e a s e M e m o r y ( T &x)
{ a ssert(x d e le te
!= N U L L ) ;
[]
x;
x = NULL; }
//
--------------------------------------------------------------------
GUID c l a s s G u i d ; HMODULE h H i d L i b ; DWORD / *
un sig n e d lo n g lu b ULONG * / m e m b e r l n d e x = 0;
DWORD d e v i c e l n t e r f a c e D e t a i l D a t a S i z e ; DWORD r e q u i r e d S i z e ; HDEVINFO d e v i c e l n f o S e t ; S P_ DE V IC E_I NT ER FA CE _D A TA d e v i c e l n t e r f a c e D a t a ; PSP_ DE V IC E_ IN TE RF A CE _ DE TA IL _ DA TA d e v i c e l n t e r f a c e D e t a i l D a t a = NULL; in t m a in ( ) {
¡¡Odwzorow anie i d e n t y f i k a t o r a b i b l i o t e k i H I D . d l l w p r z e s t r z e n i / / ad re so w e j głównego pro c e s u h H id L ib = L o a d L ib ra ry ("C :\\W in d o w s \\sy s te m 3 2 \\H ID .D L L "); if
(!h H id L ib ) d is p la y E rro r("B łą d
d o łą c ze n ia b i b lio t e k i
H ID .D LL.");
/ / P o b r a n i e adre su f u n k c j i e k s p o r to w e j HidD_G etHidGu id() v o id
(
/* (v o id
s td c a ll
* H id D _ G e tH id G u id )(O U T
std ca ll* /(FA R P R O C & )
LPGUID H i d G u i d ) ;
H id D _ G e tH id G u id = G e t P r o c A d d r e s s ( h H id L ib , "HidD_G etH idGui d " ) ;
if
(!H id D _ G e tH id G u id ){ F re e L ib ra ry ( h H id L ib ) ; d is p la y E rro r("N ie
zn a le zio n o
i d e n t y f ik a t o r a G U ID .");
}
/ /W y w o ła n ie f u n k c j i H idD _G etH idGuid( ) H id D _ G e tH id G u id (& c la s s G u id );
/ / P r o c e d u r y e n u m e r a c ji ur ządzeń d e v ic e ln f o S e t = S e tu p D iG e tC la s s D e v s (& c la s s G u id , DIGCF_PRESENT if
(d e v ice ln fo S e t
NULL,
== INVALID_HANDLE_VALUE)
d is p la y E rro r("N ie
z id e n ty fik o w a n o podłączonych
d e v ice ln te rfa c e D a ta .cb S ize
urząd zeń.\n");
= size o f(SP_D EVICE _IN TE RFACE_D ATA);
w h ile (S e tu p D iE n u m D e v ic e In te rfa c e s (d e v ic e In fo S e t, m em berlndex, me mberInde x++;
NULL,
| D I G C F _ I N T E R F A C E D E V I C E );
NU LL,
& c la s s G u id ,
&devi c e l n t e r f a c e D a t a ) ) {
/ / i n k r e m e n t a c j a numeru i n t e r f e j s u
S e tu p D iG e tD e v ic e In te rfa c e D e ta il(d e v i c e ln fo S e t, NULL ,
0,
& d e v ice In te rface D a ta ,
& d e v ic e In te rfa c e D e ta ilD a ta S iz e ,
NULL);
d e v i c e l n t e r f a c e D e t a i l D a t a = (PS P_ DE V IC E_ IN T ER F A CE _ DE T A IL _D A TA ) new D W O R D [ d e v i c e I n t e r f a c e D e t a i l D a t a S i z e ] ; d e v i c e l n t e r f a c e D e t a i l D ata-> c b S iz e= s ize o f(S P _ D E V IC E _ IN T E R F A C E _ D E T A IL_ D A T A ); if
(!S e tu p D iG e tD e v ic e In te rfa c e D e ta il(d e v ic e ln fo S e t, d e v ic e ln te rfa c e D e ta ilD a ta , & re q u ire d S iz e ,
&devi c e ln t e r f a c e D a t a ,
d e v ic e ln te rfa c e D e ta ilD a ta S iz e ,
NULL)){
re le a s e M e m o r y (d e v ic e In te r fa c e D e ta ilD a ta );
160
USB. Praktyczne programowanie z Windows API w C++
//S e tu p D iD e stro y D e v ic e In fo L ist(d e vic e In fo S e t); / / d i s p l a y E r r o r ( " N i e można p o b ra ć i n f o r m a c j i o i n t e r f e j s i e . \ n " ) ; }
/ / d e v i c e I n t e r f a c e D e t a i l D a t a - > D e v i c e P a t h j e s t łączem sym bolic zn ym / / do i n t e r f e j s u u r z ą d z e n i a c o u t << d e v i c e I n t e r f a c e D e t a i l D a t a - > D e v i c e P a t h << e n d l ; releaseM em ory(d evi c e I n t e r f a c e D e t a ilD a t a ) ; };
/ /k o n ie c w hile
S e tu p D iD e s tro y D e v ic e In fo L is t(d e v ic e In fo S e t); F re e L ib ra ry ( h H id L ib ) ; c o u t << e n d l ; system ("PAUSE"); return
0;
}
/ / --------------------------------------------------------------------------------------------------------
Różne odmiany wskaźnika do funkcji FARPROC są zdefiniowane w module windef.h. W Linuksie FARPROC należy zastąpić wskaźnikiem ogólnym void*. Windows Driver Kit stosuje konwencję s td c a ll, co zapewnia, że funkcja zostanie wywołana zgod nie z wymogami systemu operacyjnego. Oznacza to, że w funkcji wywołującej liczba i typ argumentów muszą być poprawne. Funkcje i typy danych definiowane w zaso bach WDK API często korzystają z następujących makrodefinicji: # d e f i n e DDKAPI
std ca ll
lub # d e f i n e DDKA PI_PTR _ _ s t d c a l l *
W tego typu konwencjach deklaracja wskaźnika do funkcji może zostać zapisana na jeden z dwóch sposobów: v o id
(DDKAPI * H i d D _ G e t H i d G u i d ) ( O U T
LPGUID H i d G u i d ) ;
v o id
(DD KA PI _P TR H i d D _ G e t H i d G u i d ) ( O U T
lub LPGUID H i d G u i d ) ;
N a rysunku 5.6 przedstawiono działający program z listingu 5.1. Wynik działania progra mu należy porównać z odpowiednimi zapisami w edytorze rejestrów (patrz rysunek 2.5). E:\LISB.Pralctyczne p ro g ra m o w a n ie \R o z d z ia ł 5\R5_1\proj_USB_R5_1 .exe
. i a#vid_lc4f&pid_0002&mi_01&col01#8&121b4c0f&0&0000#{4dle55b2-fl6f-llcf-88cb-001111000030} \\?\hid#vid_lc4f&pi d_0002&ni_01&col02#8&121b4c0f&0&0001#{4dle55b2-fl6f-llcf-88cb-001111000030} \\?\hi d#vidjD9da&pid_OOOa#7&36552396&0&0000#{4dle55b2-fl6f-llcf-88cb-001111000030} \ \ ? \ hid#vid_lc4f&pi d_0002&mi _00#8&3 5f2 8 9dl&0&0000#{4dle 5 5b 2 - f16 f - llcf-8 8 c b - 001111000030} Press any key to continue
Rysunek 5.6. Aplikacja proj_USB_R5_1 w trakcie działania Odczytane ścieżki dostępu (łącza symboliczne) do poszczególnych interfejsów urządzeń klasy HID m ogą być przekazane do funkcji C re a te F ile () w celu otrzymania identyfi katora pliku sterownika urządzenia wykonawczego USB, które jest aktualnie dostępne w systemie.
Rozdział 5. ♦ Detekcja i identyfikacja urządzeń dołączonych do magistrali USB
161
Zliczanie interfejsów urządzeń Dokonując niewielkiej modyfikacji poprzednio prezentowanego algorytmu, można zbudować uniwersalną funkcję searchInterfaceH idDevices(), która dodatkowo zlicza interfejsy aktualnie podłączonych do systemu urządzeń klasy HID. Listing 5.2 zawiera odpowiedni przykład będący modyfikacją kodu z listingu 5.1. Listing 5.2. Kod modułu usb_R5_2.cpp # i n c l u d e #pragma o p t i o n
p u s h -a 1
# i n c lu d e < s e tu p a p i> #pragma o p t i o n
pop
# in c lu d e # in c lu d e u s i n g n a m es p ac e s t d ; v o id d is p la y E r r o r ( c o n s t
c h a r * msg){
c o u t << msg << e n d l ; system ("PAUSE"); e x it(0 ); };
I I -------------------------------------------------------------------t e m p l a t e < c l a s s T> in lin e
v o i d r e l e a s e M e m o r y ( T &x)
{ assert(x de le te
[]
!= N U L L ) ; x;
x = NULL; }
I I -------------------------------------------------------------------in t
search In te rfa ce H id D e vice s()
{ HMODULE h H i d L i b ; HDEVINFO d e v i c e I n f o S e t ; SP _INT E RF AC E_ DE V IC E_ DA TA d e v i c e I n t e r f a c e D a t a ; DWORD
m em b e rI n d e x = 0;
GUID c l a s s G u i d ; P S P_ DE V IC E_ IN TE RF AC E_ DE TA IL _ DA TA d e v i c e I n t e r f a c e D e t a i l D a t a = NULL; DWORD r e q u i r e d S i z e = 0; DWORD d e v i c e I n t e r f a c e D e t a i l D a t a S i z e = 0; DWORD s e a r c h M a x D e v i c e = 100; bool
do ne = f a l s e ;
v o id
(
s td c a ll
* H id D _ G e tH id G u id )(O U T
LPGUID H i d G u i d ) ;
h H id L ib = L o a d L ib ra ry ("C :\\W in d o w s \\sy s te m 3 2 \\H ID .D L L "); if
(!h H id L ib ) d is p la y E rro r("B łą d
d o łą c ze n ia b i b lio t e k i
H ID .D LL.");
(FARPROC&) HidD_GetHidGuid = GetProcAddress(hHidLib, "HidD_GetHidGuid"); if
(!H id D _ G e tH id G u id ){ F re e L ib ra ry ( h H id L ib ) ;
162
USB. Praktyczne programowanie z Windows API w C++
d is p la y E rr o r(" N ie
zn a le zio n o
id e n ty fik a to r a
G U ID .");
} H id D _ G e tH id G u id d e v ice ln fo S e t
(& cla ssG u i d);
= S e tu p D iG e tC la s s D e v s (& c la s s G u id ,
N ULL ,
(DIGCF_PRESENT
NULL,
| DIGCF_DEVICEINTERFACE));
d e v i c e I n t e r f a c e D a t a . c b S i z e = s izeo f(SP_IN TE R F A C E_D EV IC E_D A TA ); w h ile (!d o n e ) fo r(;
{
m em b e rI n d e x < s e a r c h M a x D e v i c e ; memberIndex++)
{
if(S e tu p D iE n u m D e v i c e I n t e r f a c e s ( d e v i c e I n f o S e t , 0 , & c la s s G u i d , m em berIndex,&devi c e I n t e r f a c e D a t a ) )
{
S e tu p D iG e t D e v ic e I n te rfa c e D e t a il(d e v ic e In fo S e t,& d e v ic e In te rfa c e D a ta , N U L L ,0 ,& d e v ic e In te rfa c e D e ta ilD a ta S ize , N U LL ) ; re q u ire d S iz e = d e v ic e I n te rfa c e D e ta ilD a ta S iz e ; d e v i c e I n t e r f a c e D e t a i l D a t a = ( P S P _ D E V I C E _ IN T E R F A C E _ D E T A IL _ D A T A ) \ new D W O R D [ d e v i c e I n t e r f a c e D e t a i l D a t a S i z e ] ; if ( d e v i ce In te rfa c e D e ta ilD a ta )
{
devi c e In te rfa c e D e ta ilD a ta -> c b S iz e = \ sizeo f(SP_IN TE RF ACE_D EV ICE_D ETAIL_D AT A); } e lse
{
S etu p D iD e stro y D e v i c e I n f o L i s t ( d e v i c e I n fo S e t) ; releaseM em ory(d evi c e I n t e r f a c e D e t a ilD a t a ) ; re turn
0;
} if(!S e tu p D iG e tD e v i c e In te rfa c e D e ta il( d e v ic e I n fo S e t, & d e v ic e I n t e r f a c e D a t a ,d e v i c e I n t e r f a c e D e t a i l Data, r e q u ire d S iz e ,& d e v ic e In te rfa c e D e ta ilD a ta S iz e ,N U L L )){ S etu p D iD e stro y D e v i c e I n f o L i s t ( d e v i c e I n fo S e t) ; releaseM em ory(d evi c e I n t e r f a c e D e t a ilD a t a ) ; re turn
0;
} } e ls e
{ i f(ERROR_NO_MORE_ITEMS == G e t L a s t E r r o r ( ) ) { do ne = TRUE; break; }
} c o u t << d e v i c e I n t e r f a c e D e t a i l D a t a - > D e v i c e P a t h << e n d l ; releaseM em ory(d evi c e I n t e r f a c e D e t a i lD a t a ) ; } } S etu p D iD e stro y D e v i c e I n f o L i s t ( d e v i c e I n fo S e t) ; F re e L ib ra ry ( h H id L ib ) ; r e t u r n m em b e rI n d e x; }
//
--------------------------------------------------------------------
in t m a in ( ) { c o u t << " \ n L i c z b a
in te rfe js ó w
urządzeń
<< s e a r c h I n t e r f a c e H i d D e v i c e s ( ) c o u t << e n d l ;
k l a s y HID w s y s t e m i e = " \
<< e n d l ;
Rozdział 5. ♦ Detekcja i identyfikacja urządzeń dołączonych do magistrali USB
163
system ("PAUSE"); return }
//
0;
--------------------
Funkcja SetupDiGetDeviceRegistryProperty() Zdefiniowana w module setupapi.h funkcja SetupDiGetDeviceRegistryProperty() w y dobywa właściwości zainstalow anych urządzeń PnP. Właściwości te można również odczytać za pom ocą edytora rejestru (patrz rozdział 2., rysunki 2.4 i 2.5). WINSETUPAPI
BOOL WINAPI
S e tu p D iG e tD e v ic e R e g is try P ro p e rty ( IN HDEVINFO
D e vice In fo S e t,
IN PSP_DEVINFO_DATA IN DWORD OUT PDWORD OUT PBYTE IN DWORD OUT PDWORD
D e vice In fo D a ta ,
Pro perty, PropertyRegD ataType,
OPTIONAL
P ro pe rtyB u ffe r, P ro p e rty B u ffe rS ize , R e q u ire d S iz e
OPTIONAL
);
W skaźnik DeviceInfoSet jest zwracany przez funkcję SetupDiGetClassDevs(). Parametr DeviceInfoData wskazuje strukturę SP_DEVINFO_DATA. Parametr Property identyfikuje właściwość urządzenia PnP, którą aktualnie chcemy odczytać. Jest on reprezentowany przez odpowiednie stałe symboliczne, których podzbiór został wykorzystany w kodzie z listingu 5.3. Kompletny zestaw predefiniowanych stałych symbolicznych można odna leźć w plikach pomocy. Opcjonalnie używany wskaźnik PropertyRegDataType wskazu je typ danej zawierającej właściwość urządzenia. Parametr PropertyBuffer wskazuje bufor danych, poprzez który odczytamy właściwości urządzenia. Jeżeli PropertyBuffer przypiszemy wartość NULL, a parametrowi PropertyBufferSize wartość zero, funkcja zwróci żądany rozmiar bufora danych przechowywany w zmiennej RequiredSize. Pa ram etr PropertyB ufferSize określa w bajtach rozm iar bufora wskazywanego przez PropertyBuffer. Listing 5.3. Kod modułu usb_R5_3.cpp # i n c l u d e #pragma o p t i o n
p u s h -a 1
# i n c lu d e < s e tu p a p i> #pragma o p t i o n
pop
# in c lu d e # in c lu d e # in c lu d e < c str in g > u s i n g n a m es p ac e s t d ; v o id d is p la y E r r o r ( c o n s t
c h a r * msg){
16 4
USB. Praktyczne programowanie z Windows API w C++
c o u t << msg << e n d l ; system ("PAUSE"); e x it(O ); };
//
--------------------------------------------------------------------
t e m p l a t e < c l a s s T> in lin e
v o i d r e l e a s e M e m o r y ( T &x)
{ assert(x de le te
!= N U L L ) ;
[]
x;
x = NULL; }
//
--------------------------------------------------------------------
GUID c l a s s G u i d ; HMODULE h H i d L i b ; DWORD / * u n s i g n e d l o n g * / m em be rI n de x = O; DWORD d e v i c e I n t e r f a c e D e t a i l D a t a S i z e ; DWORD r e q u i r e d S i z e ; HDEVINEO d e v i c e I n f o S e t ; S P_ DE V IC E_I NTE RE ACE _D ATA d e v i c e I n t e r f a c e D a t a ; PS P_ DEV IC E_ IN TE RE AC E_ DE TA IL _D A TA d e v i c e I n t e r f a c e D e t a i l D a t a = NULL; SP_DEVINEO_DATA d e v i c e I n f o D a t a ;
//
--------------------------------------------------------------------
s trin g
g e t R e g is try P ro p e rty S trin g (H D E V IN E O d e v ic e I n fo S e t, PSP_DEVINEO_DATA d e v i c e I n f o D a t a ,
DWORD p r o p e r t y )
{ DWORD p r o p e r t y B u f f e r S i z e = O;
//DWORD pr op erty R egD a ta Typ e = 0; c h a r * p r o p e r t y B u f f e r = NULL; S e tu p D iG e tD e v ic e R e g is try P ro p e rty (d e v i c e In fo S e t,
devi ceInfoD ata,
/*& propertyRegD ataType*/N U LL,
property, NULL,
O,
& p ro p e rty B u ffe rS iz e );
/ / a lo k o w a n ie p a m i ę c i d l a b u f o r a danych p r o p e r t y B u f f e r = new c h a r [ ( p r o p e r t y B u f f e r S i z e * s i z e o f ( T C H A R ) ) ] ; bool
r e s u lt= S e tu p D iG e tD e v ic e R e g is try P ro p e rty (d e v i c e In fo S e t,
de v ice In fo D a ta ,
property,/*& propertyR egD ataType*/N ULL, p ropertyB u ffe r,
p ro p e rty B u ffe rS iz e ,
N ULL); if( !r e s u lt) re le a se M e m o ry(p ro p e rty B u ffe r); return }
//
p ro pe rtyB u ffe r;
--------------------------------------------------------------------
in t m a in ( ) { v o id
(
s td c a ll
* H id D _ G e tH id G u id )(O U T
LPGUID H i d G u i d ) ;
h H id L ib = L o a d L ib ra ry ("C :\\W in d o w s \\sy s te m 3 2 \\H ID .D L L "); if
(!h H id L ib ) d is p la y E rro r("B łą d
d o łą c ze n ia b i b lio t e k i
H ID .D LL.");
(EARPROC&) HidD_GetHidGuid = GetProcAddress(hHidLib, "HidD_GetHidGuid"); if
(!H id D _ G e tH id G u id ){
Rozdział 5. ♦ Detekcja i identyfikacja urządzeń dołączonych do magistrali USB
165
F re e L ib ra ry ( h H id L ib ) ; d is p la y E rro r("N ie
zn a le zio n o
i d e n t y f ik a t o r a G U ID .");
} H id D _ G e tH id G u id (& c l a s s G u i d ) ; d e v ic e I n fo S e t = S e tu p D iG e tC la s s D e v s (& c la s s G u id , DIGCF_PRESENT if
NULL,
NULL,
| D I G C F _ I N T E R F A C E D E V I C E );
( d e v i c e I n f o S e t == INVALID_HANDLE_VALUE) d is p la y E rro r("N ie
z id e n ty fik o w a n o podłączonych
d e v ic e In te rfa c e D a ta .c b S iz e
urząd zeń.\n");
= size o f(SP_D EVICE _IN TE RFACE_D ATA);
w h ile (S e tu p D iE n u m D e v ic e In te rfa c e s (d e v ic e In fo S e t, memberIndex,
NU LL,
& c la s s G u id ,
& d e v ice In te rfa c e D a ta )){
me mberInde x++; / / i n k r e m e n t a c j a numeru i n t e r f e j s u S e tu p D iG e tD e v ic e In te rfa c e D e ta il(d e v i ce In fo S e t, NU LL,
0,
& d e v ice In te rface D a ta ,
& d e v ic e In te rfa c e D e ta ilD a ta S iz e ,
NULL);
d e v i c e I n t e r f a c e D e t a i l D a t a = (PS P_ DE V IC E_ IN T ER F AC E_ DE T AI L _D AT A ) new D W O R D [ d e v i c e I n t e r f a c e D e t a i l D a t a S i z e ] ; d e v i c e I n t e r f a c e D e t a i l D ata-> c b S iz e= s ize o f(S P _ D E V IC E _ IN T E R F A C E _ D E T A IL_ D A T A ); d e v i c e I n f o D a t a . c b S i z e = size o f(S P _ D E V IN F O _ D A T A ); if
(!S e tu p D iG e tD e v ic e In te rfa c e D e ta il(d e v ice In fo S e t, d e v ic e In te rfa c e D e ta ilD a ta , & re q u ire d S iz e ,
&devi c e I n t e r f a c e D a t a ,
d e v ic e In te rfa c e D e ta ilD a ta S iz e ,
&devi c e I n f o D a t a ) ) {
r e le a s e M e m o r y (d e v ic e In te r fa c e D e ta ilD a ta ); S e tu p D iD e s tr o y D e v ic e In fo L is t(d e v ic e In fo S e t);
//d is p la y E rro r
( " N i e można p o b ra ć i n f o r m a c j i o i n t e r f e j s i e . \ n " ) ;
}
/ / c o u t << d e v i c e I n t e r f a c e D e t a i l D a t a - > D e v i c e P a t h << e n d l ; c o u t << " \ n C l a s s D e s c r :
"< < g e tR e g istry P ro p e rty S trin g (d e v ic e In fo S e t, & d eviceIn fo D ata,
c o u t << " \ n C l a s s G U I D :
S P D R P _ C L A S S );
"< < g e tR e g istry P ro p e rty S trin g (d e v ic e In fo S e t, & d e vice In fo D ata ,
c o u t << " \ n C o m p a t i b i l e I D s :
SP DR P_ CL A S S GU ID );
"< < g e tR e g istry P ro p e rty S trin g (d e v ic e In fo S e t, & d e vice In fo D ata ,
S PD R P_ CO M PA TI B LE ID S );
c o u t << " \ n C o n f i g F l a g s :
"< < g e tR e g istry P ro p e rty S trin g (d e v ic e In fo S e t,
c o u t << " \ n D e v i c e D e s c r :
"< < g e tR e g istry P ro p e rty S trin g (d e v ic e In fo S e t,
& d e vice In fo D ata , & d e vice In fo D ata , c o u t << " \ n D r i v e r :
SPDRP_DEVICEDESC);
"< < g e tR e g istry P ro p e rty S trin g (d e v ic e In fo S e t, & d e vice In fo D ata ,
/ / c o u t << " \n F rie n d ly N a m e : // c o u t << " \ n H a r d w a r e I D :
SPDRP_DRIVER);
"< < g e tR e g is try P ro p e rty S trin g (d e v ic e In fo S e t, & d e v i c e I n f o D a t a , SPDRP_FRIENDLYNAME);
"< < g e tR e g istry P ro p e rty S trin g (d e v ic e In fo S e t, & d e vice In fo D ata ,
c o u t << " \ n M f g :
S P D R P _ C O N FI G F LA G S ) ;
SPDRP_HARDWAREID);
"< < g e tR e g istry P ro p e rty S trin g (d e v ic e In fo S e t, & d e vice In fo D ata ,
SPDRP_MFG);
c o u t << " \ n E n u m e r a t o r N a m e :
"< < g e tR e g istry P ro p e rty S trin g (d e v ic e In fo S e t,
c o u t << " \ n P h y s D e v O b j N a m e :
"< < g e tR e g istry P ro p e rty S trin g (d e v ic e In fo S e t,
& d e vice In fo D ata , & d e vice In fo D ata ,
SPDRP_ENUMERATOR_NAME);
S P D R P_ PH YS IC A L_ DE V IC E_ O BJ E CT _N AM E) ;
166
USB. Praktyczne programowanie z Windows API w C++
c o u t << e n d l ; releaseM em ory(d evi c e I n t e r f a c e D e t a ilD a t a ) ; }; / / k o n ie c w h i l e S e tu p D iD e str o y D e v ic e In fo L is t(d e v ic e In fo S e t) ; E r e e L ib ra ry (h H id L ib ); c o u t << e n d l ; system ("PAUSE"); return }
//
O;
-------------------------------------------------------
Używanie funkcji SetupDiGetDeviceRegistryProperty() z reguły nie ogranicza się do pojedynczego wywołania. W pierw szym wywołaniu określamy wymagany rozm iar bufora danych, a w następnym odczytujem y żądane w łaściw ości zainstalow anego w systemie urządzenia PnP. Pokazano to na listingu 5.3 w ciele przykładowej funkcji getR egistryPropertyString() wydobywającej niektóre właściwości łańcuchowe sprzę tu zgodnego z klasą HID. Rysunek 5.7 przedstawia wynik działania programu cyklicz nie wywołującego funkcję getR egistryPropertyString() w celu odczytania wybranych właściwości łańcuchowych urządzeń zainstalowanych w systemie. Rysunek 5.7. Aplikacja proj_USB_R5_3 w trakcie działania
U S B .Pra ktyczn e p ro g ra m o w a n ie \R o z d z ia ł 5\R... a s s u e s c r : m u ^ ia s s assGUID: {745a l7 a O - 7 4 d 3 - lld O m pati b i l e l D s : n fig F la g s : v ic e D e s c r : U r z ą d z e n ie s t e r u j ą i v e r : { 7 4 5 a l7 a 0 - 7 4 d 3 - lld 0 - b 6 t rd w a re lD : HID\VID_1C4F&PID_00 g: M ic r o s o f t um eratorNam e: HID y s DevObj Name: \ D e v i ce\0000005 a s s D e s c r : H ID C la ss assGUID: { 7 4 5 a l7 a 0 - 7 4 d 3 - lld 0 m pati b i l e l D s : n fig F la g s : v i c e D e s c r : Ur z ą d z e n i e zg o d ne i v e r : { 7 4 5 a l7 a 0 - 7 4 d 3 - lld 0 - b 6 f rd w a re lD : HID\VID_1C4F&PID_00 g: {Standardow e u r z ą d z e n ia s y umeratorNam e: HID y s DevObj Name: \ D e v i ce\0000005 a s s D e s c r : Mouse assGUID: { 4 d 3 6 e 9 6 f- e 3 2 5 - llc e mpati b i l e l D s : n fig F la g s : v ic e D e s c r : M ysz zg o dn a z HID i v e r : { 4 d 3 6 e 9 6 f - e 3 2 5 - llc e - b f c rd w a re lD : HID\VID_09DA&PID_00 g: M ic r o s o f t umeratorNam e: HID y s DevObjName: \ D e v i ce\0000005 as s Des c r : K eyb oard assGUID: { 4 d 3 6 e 9 6 b -e 3 2 5 - llc e m pati b i l e l D s : n fig F la g s: v/i ceDes c r : U r z ą d z e n ie k la w i a t
Rozdział 5. ♦ Detekcja i identyfikacja urządzeń dołączonych do magistrali USB
167
W rejestrze system owym elem entam i podkluczy tem atycznych są dwa typy danych: łańcuchowe (oznaczone jako REG_SZ lub REG_MULTI_SZ) i liczbowe (oznaczone jako REG_DWORD). Testując kod z listingu 5.3, możemy zauważyć, że funkcja getRegistryPropertyString() w ydobyw a jedynie właściw ości łańcuchowe urządzenia. Aby odzyskać właściwość liczbową, należy zmodyfikować tę funkcję lub, co jest dużo bardziej pożyteczne, zbu dować jej odmianę, która będzie zwracała dane typu DWORD. N a listingu 5.4 zamiesz czono odpowiedni przykład funkcji getRegistryPropertyDWORD(), wydobywającej w ła ściwości liczbowe urządzenia zapisane w rejestrze systemowym. Listing 5.4. Kodfunkcji getRegistryPropertyDWORD() wraz z jej przykładowym wywołaniem 1 1 ... SP_DEVINFO_DATA d e v i c e I n f o D a t a ;
11 --------------------------------------------------------------------
DWORD g e t R e g i s t r y P r o p e r t y D W O R D ( H D E V I N F O d e v i c e I n f o S e t , PSP_DEVINFO_DATA d e v i c e I n f o D a t a ,
DWORD p r o p e r t y )
{ DWORD p r o p e r t y B u f f e r S i z e = 0; DWORD p r o p e r t y R e g D a t a T y p e = 0; DWORD * p r o p e r t y B u f f e r = 0; bool
re s u lt;
S e tu p D iG e tD e v ic e R e g is try P ro p e rty (d e v i c e In fo S e t,
devi ceInfoD ata,
&propertyRegD ataType,
NU LL,
property,
0,
& p ro p e rty B u ffe rS iz e );
l / a l o k o w a n i e p a m i ę c i d l a b u f o r a danych p r o p e r t y B u f f e r = new D W O R D [ ( p r o p e r t y B u f f e r S i z e * s i z e o f ( D W O R D ) ) ] ; r e s u lt
= S e tu p D iG e tD e v ic e R e g is try P ro p e rty (d e v ice In fo S e t,
de v ice In fo D a ta ,
p r o p e r t y , & propertyR egD ataType, (ch ar* )pro pertyB u ffer,
s iz e o f(p ro p e rty B u ffe r),
NULL); if( !r e s u lt) re le a se M e m o ry(p ro p e rty B u ffe r); return
*propertyB uffer;
}
11 -------------------------------------------------------------------in t m a in ( ) {
1 1 ... c o u t < < " \ n C a p a b i l i t i e s " << g e t R e g i s t r y P r o p e r t y D W O R D ( d e v i c e I n f o S e t , & d e vice In fo D ata ,
SPDRP_CAPABILITIES);
1 1 ... }
11--------------------------------------------------------------------
168
USB. Praktyczne programowanie z Windows API w C++
Struktury danych Elementy opisu urządzeń USB, które są dostępne w systemie, są często przechowywa ne w polach odpowiednio skonstruowanych struktur danych znajdujących się w jednej przestrzeni nazw. N a listingu 5.4 pokazano przykładową strukturę DEVICE_DATA: t y p e d e f s t r u c t _DEVI CE_ DA TA {
/ / i d e n t y f i k a t o r sprzętu / / ł ą c z e s y m b o lic z n e
TCHAR
*Hardw areId;
TCHAR
*Path;
DWORD
D e vice In sta n ce ;
} D E V I C E _ D A T A , *PDEVICE_DATA;
/ / --------------------------------------------------------------------------------------------------------
Jej pola przechowują przykładowe dane zawierające identyfikator sprzętu (HardwareId), ścieżkę dostępu do interfejsu urządzenia (Path) oraz DeviceInstance, który będzie lo kalizował element DevInst struktury USB_DEFINFO_DATA. Zawartość struktury DEVICE_ DATA pozwala wstępnie zidentyfikować urządzenie. Aby w pełni wykorzystać tak skonstruowany typ danych, należy zadeklarować tablicę wskaźników do struktur o rozmiarze nie mniejszym niż rzeczywista liczba interfejsów urządzeń USB w systemie: PDEVICE_DATA d e v i c e L i s t ;
//... d e v i c e L i s t = (P D EV IC E_ DA TA) new \ D E V IC E _D A T A [((m em berIn d ex+ 1)*sizeof(D E V IC E _D A T A ))];
Warto pamiętać, że w przypadku błędnego zaalokowania pamięci dla tablicy struktur kom pilator zgłosi błąd naruszenia pamięci. Po prawidłowym zaalokowaniu tablicy struktur określamy za pomocą funkcji s tr le n ( ) aktualną długość ścieżki dostępu do interfejsu urządzenia: s i z e _ t n Le n = s t r l e n ( d e v i c e I n t e r f a c e D e t a i l D a t a - > D e v i c e P a t h )
+ 1;
oraz tworzymy tablicę ścieżek: d e v i c e L i s t [ m e m b e r I n d e x ] . P a t h = new T C H A R [ ( n L e n * s i z e o f ( T C H A R ) ) ] ;
Ścieżka dostępu do interfejsu urządzenia zostaje przekopiow ana za pom ocą funkcji strncpy() do pola Path elementu (o indeksie memberIndex) tablicy struktur DEVICE_DATA. strn c p y (d e v ic e L is t[m e m b e rIn d e x ].P a th , d e v ice In te rfa ce D e ta ilD a ta -> D e v ice P a th ,
nLen);
O d tego momentu program dysponuje indeksowaną ścieżką dostępu do interfejsu urzą dzenia, którą można wykorzystać na przykład w funkcji uzyskującej dostęp do pliku sterownika urządzenia: C re a te F ile (d e v i c e L is t[ 2 ] .P a th ,
...);
Sposoby wypełniania pozostałych elementów struktury DEVICE_DATA zaprezentowano w kodzie z listingu 5.5, zawierającym funkcję setGetHidDeviceData(). N a rysunku 5.8 pokazano wynik działania omawianego programu.
Rozdział 5. ♦ Detekcja i identyfikacja urządzeń dołączonych do magistrali USB
Listing S.S. Kod modułu usb_R5_4.cpp # i n c l u d e #pragma o p t i o n
p u s h -a 1
# i n c lu d e < s e tu p a p i> #pragma o p t i o n
pop
# in c lu d e # in c lu d e # in c lu d e < c str in g > u s i n g n a m es p ac e s t d ; v o id d is p la y E r r o r ( c o n s t
c h a r * msg){
c o u t << msg << e n d l ; system ("PAUSE"); e x it(0 ); };
I I -------------------------------------------------------------------------------------------------------t e m p l a t e < c l a s s T> in lin e
v o i d r e l e a s e M e m o r y ( T &x)
{ assert(x de le te
[]
!= N U L L ) ; x;
x = NULL; }
I I -------------------------------------------------------------------------------------------------------t y p e d e f s t r u c t _DEVI CE_ DA TA { TCHAR
*H ardwareId;
I I łą c z e s y m b o lic z n e
TCHAR
*Path;
DWORD
D e vice In sta n ce ;
} DE VIC E_DATA,
*PDEVICE_DATA;
I I -------------------------------------------------------------------------------------------------------in t
se tG etH id D e viceD ata()
{ PDEVICE_DATA d e v i c e L i s t ; DWORD p r o p e r t y B u f f e r S i z e = 0; c h a r * p r o p e r t y B u f f e r = NULL; SP_DEVINFO_DATA d e v i c e I n f o D a t a ; HMODULE h H i d L i b ; HDEVINFO d e v i c e I n f o S e t ; SP _INT E RF AC E_ DE V IC E_ DA TA d e v i c e I n t e r f a c e D a t a ; DWORD
m em b e rI n d e x = 0;
GUID c l a s s G u i d ; P S P_ DE V IC E_ IN TE RF AC E_ DE TA IL _ DA TA d e v i c e I n t e r f a c e D e t a i l D a t a = NULL; DWORD r e q u i r e d S i z e = 0; DWORD d e v i c e I n t e r f a c e D e t a i l D a t a S i z e = 0; DWORD s e a r c h M a x D e v i c e = 100; bool
do n e = f a l s e ;
v o id
(
s td c a ll
IIm ak symalna l i c z b a i n t e r f e j s ó w ur ządzeń
* H id D _ G e tH id G u id )(O U T
LPGUID H i d G u i d ) ;
h H id L ib = L o a d L ib ra ry ("C :\\W in d o w s \\sy s te m 3 2 \\H ID .D L L "); if
(!h H id L ib ) d is p la y E rro r("B łą d
d o łą c ze n ia b i b lio t e k i
H ID .D LL.");
(FARPROC&) HidD_GetHidGuid = GetProcAddress(hHidLib, "HidD_GetHidGuid");
16e
170
USB. Praktyczne programowanie z Windows API w C++
if
(!H id D _ G etH id G u id ){ F re e L ib ra ry ( h H id L ib ) ; d is p la y E rr o r(" N ie
zn a le zio n o
id e n ty fik a to r a
G U ID .");
} H id D _ G e tH id G u id
(& cla ssG u i d);
d e v ic e I n fo S e t = S e tu p D iG e tC la s s D e v s (& c la s s G u id ,
N ULL ,
( d IGCF_PRESENT
NULL,
| DIGCF_DEVICEINTERFACE));
d e v i c e I n t e r f a c e D a t a . c b S i z e = s izeo f(SP_IN TE R F A C E_D EV IC E_D A TA ); w h ile (!d o n e )
{
d e v ic e L is t fo r(;
= new D E V I C E _ D A T A [ ( ( m e m b e r I n d e x + 1 ) * s i z e o f ( D E V I C E _ D A T A ) ) ] ;
m em b e rI n d e x < s e a r c h M a x D e v i c e ; memberIndex++)
{
if(S e tu p D iE n u m D e v i c e I n t e r f a c e s ( d e v i c e I n f o S e t , 0 , & c la s s G u i d , m em berInde x,& d eviceInterfaceD ata))
{
S e tu p D iG e tD e v ic e In te rfa c e D e ta il(d e v ic e In fo S e t,& d e v ic e In te rfa c e D a ta , N U L L ,0 ,& d e v ic e In te rfa c e D e ta ilD a ta S ize , N U LL ) ; re q u ire d S iz e = d e v ic e I n te rfa c e D e ta ilD a ta S iz e ; d e v i c e I n t e r f a c e D e t a i l D a t a = ( P S P _ D E V I C E _ IN T E R F A C E _ D E T A IL _ D A T A ) \ new D W O R D [ d e v i c e I n t e r f a c e D e t a i l D a t a S i z e ] ; if ( d e v i ce In te rfa c e D e ta ilD a ta )
{
devi c e In te rfa c e D e ta ilD a ta -> c b S iz e = \ sizeo f(SP_IN TE RF ACE_D EV ICE_D ETAIL_D AT A); } e lse
{
S etu p D iD e stro y D e v i c e I n f o L i s t ( d e v i c e I n fo S e t) ; re le a s e M e m o r y (d e v ic e In te r fa c e D e ta ilD a ta ); re turn
0;
} d e v i c e I n f o D a t a . c b S i z e = size o f(S P _ D E V IN F O _ D A T A ); if
( !S e tu p D iG e tD e v ic e In te rfa c e D e ta il(d e v ic e In fo S e t, & d e v ice In te rface D a ta ,
d e v ic e In te rfa c e D e ta ilD a ta ,
d e v ic e In te rfa c e D e ta ilD a ta S iz e , & re q u ire d S iz e ,
& d eviceIn fo D ata))
{
S etu p D iD e stro y D e v i c e I n f o L i s t ( d e v i c e I n fo S e t) ; releaseM em ory(d evi c e I n t e r f a c e D e t a i lD a t a ) ; re turn
0;
} s i z e _ t n Le n = s t r l e n ( d e v i c e I n t e r f a c e D e t a i l D a t a - > D e v i c e P a t h )
+ 1;
d e v i c e L i s t [ m e m b e r I n d e x ] . P a t h = new T C H A R [ ( n L e n * s i z e o f ( T C H A R ) ) ] ; strn c p y (d e v ic e L is t[m e m b e rIn d e x ].P a th , d e v ice In te rfa ce D e ta ilD a ta -> D e v ice P a th , c o u t < < "\n D e v ic e L ist["< < m e m b e rIn d e x < < "].P a th :
nLen);
\ n " <<
d e v i c e L i s t [ m e m b e r I n d e x ] . P a t h << e n d l ; d e v ic e L is t[m e m b e rIn d e x ].D e v ic e In s ta n c e = d e v ic e In fo D a ta .D e v In s t; S e tu p D iG e tD e v ic e R e g is try P ro p e rty (d e v i c e In fo S e t,
& d eviceIn foD ata,
SPDRP_HARDWAREID, NU LL, & p ro p e rty B u ffe r S ize );
NU LL,
0,
Rozdział 5. ♦ Detekcja i identyfikacja urządzeń dołączonych do magistrali USB
11 a lo kow an ie p a m i ę c i d l a b u f o r a danych p r o p e r t y B u f f e r = new c h a r [ ( p r o p e r t y B u f f e r S i z e * s i z e o f ( T C H A R ) ) ] ; S e tu p D iG e tD e v ic e R e g is try P ro p e rty (d e v i c e In fo S e t,
& d eviceIn fo D ata,
SPDRP_HARDWAREID,NULL, propertyB u ffe r,
p ro p e rty B u ffe rS iz e ,
NULL); d e v ice L is t[m e m b e rIn d e x ].H a rd w a re Id
= p ro pertyB u ffe r;
c o u t < < "\n D e vic e Lis t["< < m e m b e rIn d e x< < "].H a rd w a re Id :
\ n " <<
d e v i c e L i s t [ m e m b e r I n d e x ] . H a r d w a r e I d << e n d l ; } e ls e
{ i f(ERROR_NO_MORE_ITEMS == G e t L a s t E r r o r ( ) ) { do ne = TRUE; break; }
} re le a se M e m o ry(p ro p e rty B u ffe r); releaseM em ory(d evi c e I n t e r f a c e D e t a i lD a t a ) ; releaseM em ory(d evi ce L ist[m e m b e rIn d e x ]. P a t h ) ; } releaseM em ory(d evi c e L i s t ) ; } S etu p D iD e stro y D e v i c e I n fo L i s t ( d e v i c e I n fo S e t) ; F re e L ib ra ry ( h H id L ib ) ; r e t u r n m em b e rI n d e x; }
11 -------------------------------------------------------------------in t m a in ( ) { c o u t << s e t G e t H i d D e v i c e D a t a ( )
<< e n d l ;
c o u t << e n d l ; system ("PAUSE"); return
0;
}
11 --------------------------------------------------------------------
Rysunek 5.8. Aplikacja proj_USB_R5_4 w trakcie działania
U S B ,P ra kty czn e p rog ra m o w a r ie \ R o z d z i a ł 5\R5_4\proj_USB_R5_4.e.., HID \V ID_1C4F&PID_0002&REV_0110SM I _ 0 1 & C o lO l Devi c e L i s t [ 1 ] . P a t h : V \ ? \ h i d#vi d _ l c 4 f 4 p i d_0002Am _ 0 1 4 c o l 02#84121b4c0f 4040001# {4dle5 5 b 2 - f 1 6 f - l l c f - 8 8 c b -001111000030} Devi c e L i s t [ 1 ] . H a r d w a r e ld : HI D\VI D_1C4 F&PI D_0002&REV_0110£M I_01& Col 02 Devi c e L i s t [2] . P a th : W ? \ h i d#vi d_09da&pi d_000a#7&365 5239&&0&0000#{4dle5 5 b 2 - f 1 6 f - l l c f - 8 8 c b - 00111100003 0} Devi c e L i s t [ 2 ] . H a r d w a r e ld : HI D\VI D_09DA5tPI D_000A&REV_0017 Devi c e L i s t [ 3 ] . P a t h : V \ ? \ h i d # v id _ lc 4 f & p i d_0002£mi _00#8435 f 289dl&0& 0000# {4dle5 5 b2- f 1 6 f - l l c f - 8 8 c b - 00111 1000030} Devi c e L i s t [3] . H a r d w a r e ld : HID\VID_1C4F&PID_0002&REV_0110SM I_00 4 P re ss any key t o
c o n t in u e
.
.
.
171
172
USB. Praktyczne programowanie z Windows API w C++
N a rysunku 5.9 zaprezentow ano sposób uzyskiw ania dostępu do urządzenia USB. W pierwszej kolejności należy określić ścieżkę dostępu do właściwego dla danej kon figuracji interfejsu, ja k i sterownik udostępnia w arstwie aplikacji, po czym uzyskać identyfikator pliku sterownika urządzenia. Rysunek 5.9. Uzyskiwanie dostępu do złożonego urządzenia USB funkcjonującego w podstawowym modelu konfiguracji (por. rysunek 3.13)
Program użytkownika uzyskuje dostęp do urządzenia poprzez jego interfejs dostar czany przez obiekty PDO i FDO. Dla każdego zidentyfikowanego przez system portu USB oraz dla każdego zidentyfikowanego i przyłączonego urządzenia określony ste rownik tworzy odpowiedni obiekt urządzenia, interfejs, wewnętrzną nazwę obiektu fizycznego (PhysDevObjName — patrz rysunek 5.7) oraz nazwę reprezentującą łącze symboliczne do pliku sterownika (patrz rysunek 5.8). Gdy program ista zna num er oraz identyfikator GUID żądanego interfejsu urządzenia, może wykorzystać pokazaną na listingu 5.6 funkcję wydobywającą pełną ścieżkę do stępu do interfejsu, jaki sterownik urządzenia udostępnia warstwie aplikacji. Listing 5.6. Jeden ze sposobów uzyskiwania pełnej ścieżki wystąpienia obiektu urządzenia na podstawie znajomości numeru oraz identyfikatora GUID interfejsu urządzenia # in clu d e < in itg u id >
II... / / -------------------------------------------------------------------D E FIN E_GUID(devInterfaceGUIDConstant, 0x88,
0xcb,
0x4d1e55b2,
0x00,
0x11,
0 xf1 6f,
0x11,
0x11cf,
0x00,
0x00,
\ 0x30);
GUID d e v I n t e r f a c e G U I D = d e v I n t e r f a c e G U I D C o n s t a n t ;
//
--------------------------------------------------------------------
char* g e tD e v ice P a th (L P G U ID d e v In te rfa ce G U ID ,
/* U I N T
in te rfa c e In d e x * /)
{
HDEVINFO deviceInfoSet; SP_DEVICE_INTERFACE_DATA deviceInterfaceData; PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData = NULL;
Rozdział 5. ♦ Detekcja i identyfikacja urządzeń dołączonych do magistrali USB
DWORD r e q u i r e d S i z e ,
d e v i c e I n t e r f a c e D e t a i l D a t a S i z e = 0;
BOOL b R e s u l t ; d e v ic e I n fo S e t = S e tu p D iG e tC la s sD e v s(d e v In te rfa ce G U ID , N U LL , NU LL, ( DI G C F _ P R E S E N T | DIGCF_DEVICEINTERFACE)); i f ( d e v i c e I n f o S e t == INVALID_HANDLE_VALUE)
{
//b łą d e x it( l) ; } d e v ic e In te rfa c e D a ta .c b S iz e
= size o f(SP_D EVICE _IN TE RFACE_D ATA);
b R e su lt = S e tu p D iE n u m D e v ic e In te rfa c e s (d e v ic e In fo S e t,
NULL,
devInterfaceG U ID , 1,
/ * i n t e r f a c e I n d e x numer i n t e r f e j s u * /
& d e v ice In te rfa ce D a ta ); i f ( b R e s u l t == FALSE)
{
//b łą d S e tu p D iD e s tr o y D e v ic e In fo L is t(d e v ic e In fo S e t); e x it(1 ); } S e tu p D iG e t D e v ic e I n te rfa c e D e t a il(d e v ic e In fo S e t,& d e v ic e In te rfa c e D a ta , N U L L ,0 ,& d e v ic e In te rfa c e D e ta ilD a ta S ize , N U LL ) ; d e v i c e I n t e r f a c e D e t a i l D a t a = (PS P_ DE V IC E_ IN T ER F A CE _ DE T A IL _D A TA ) new D W O R D [ d e v i c e I n t e r f a c e D e t a i l D a t a S i z e ] ; i f ( d e v i c e I n t e r f a c e D e t a i l D a t a == NULL)
{
S e tu p D iD e s tro y D e v ic e In fo L is t(d e v ic e In fo S e t);
/ / b ł ą d a l o k a c j i p a m ię c i e x it(1 ); } d e v i c e I n t e r f a c e D e t a i l D a t a - > c b S i z e = sizeo f(SP_D EV ICE _IN TE RF ACE_D ETAIL_D AT A); bR e sult = S e tu p D iG e tD e v ic e In te rfa c e D e ta il(d e v ic e In fo S e t, & d e v ice In te rface D a ta , devi c e In te rfa c e D e ta ilD a ta , devi c e In te r fa c e D e ta ilD a t a S i ze, & re q u ire d S iz e ,N U L L ); i f ( b R e s u l t == FALSE)
{
//b łą d S e tu p D iD e s tro y D e v ic e In fo L is t(d e v ic e In fo S e t); d e le te
[]
d e v ic e I n te rfa c e D e ta ilD a ta ;
e x it(1 ); } return }
//
d e v ic e In te rfa c e D e ta ilD a ta -> D e v ic e P a th ;
--------------------------------------------------------------------
i n t m ain () { c o u t << g e t D e v i c e P a t h ( & d e v I n t e r f a c e G U I D ,
/* ...* /);
devO bject = C r e a t e F ile ( g e tD e v ic e P a th ( & d e v I n te r fa c e G U I D ) , / * .. .* /);
/ / P a t r z r o z d z i a ł 6. //... }
//
--------------------------------------------------------------------
173
17 4
USB. Praktyczne programowanie z Windows API w C++
Moduł usbiodef.h Dotychczas zostały omówione procedury detekcji i identyfikacji urządzeń klasy HID aktualnie podłączonych do magistrali USB. Warto pamiętać, że w zasobach WDK moż na odszukać użyteczny m oduł usbiodef.h, w którym m.in. zdefiniowanych jest wiele identyfikatorów GUID, za pom ocą których uzyskuje się ścieżki dostępu do interfejsów wszystkich urządzeń USB aktualnie dostępnych w systemie. Jeżeli w przypadku urządzeń HID zdecydujemy się posługiwać identyfikatorem GUID_ DEVINTERFACE_HID, w kodzie programu należy zrezygnować z funkcji HidD_GetHidGuid(), tak jak pokazano na listingu 5.7. N a rysunku 5.10 zaprezentowano program w trakcie działania. Listing 5.7. Kod modułu usb_R5_5.cpp # i n c l u d e #pragma o p t i o n
p u s h -a 1
# i n c lu d e < s e tu p a p i> #pragma o p t i o n
pop
# in c lu d e # in c lu d e u s i n g n a m es p a ce s t d ; v o id d is p la y E r r o r ( c o n s t
c h a r * msg){
c o u t << msg << e n d l ; system ("PAUSE"); e x it(0 ); };
//
--------------------------------------------------------------------
t e m p l a t e < c l a s s T> in lin e
v o i d r e l e a s e M e m o r y ( T &x)
{ assert(x
!= N U L L ) ;
d e le t e [ ] x; x = NULL; }
//
--------------------------------------------------------------------
/ *A5DCBF10-6530-11D2-901F-00C04FB951ED * / s ta tic
GUID GUID_DEVINTER FACE _U SB_ DEV ICE =
{0x A5 DC B F1 0 ,
0x6530,
0x11D2,
{0x90,
0x1F,
0x00,
0 xC 0 ,
0x4F,
0xB9,
0x51,
0xED}};
/*3ABF6F2D-71C4-462a-8A92-1E6861E6AF27*/ s ta tic
GUID GUID_DEVINTERFACE_USB_HOST_CONTROLLER =
{0x3abf6f2d,
0x71c4,
0x462a,
{0x8a,
0x92,
0x1e,
\
0x68,
0x61,
0xe6,
0 xaf,
0x27}};
/ *F18A0E88-C30C-11D0-8815-00A0C906BED8*/ s ta tic
GUID GUID_DEVINTERFACE_USB_HUB =
{0xf18a0e88,
0xc30c,
0x11 d0 ,{0x88 ,
0x15,
0x00,
\
0xa0,
0xc9,
0x06,
0xbe,
0xd8}};
Rozdział 5. ♦ Detekcja i identyfikacja urządzeń dołączonych do magistrali USB
175
/ *4D1E55B2-F16F-11CF-88CB-001111000 030 */ s ta tic
GUID GU ID_ DE VINTERFACE_HID =
{ 0 x 4D 1 E5 5B 2 ,
0xF16F,
0x11CF,
{0x88, 0x11,
0 xC B , 0x11,
0x00, 0x00,
\ 0x00,
0x30}};
/ / -------------------------------------------------------------------------------------------------------DWORD m em be rI n de x = 0; DWORD d e v i c e I n t e r f a c e D e t a i l D a t a S i z e ; DWORD r e q u i r e d S i z e ; HDEVINFO d e v i c e I n f o S e t ; S P_ DE V IC E_I NT ER FA CE _D A TA d e v i c e I n t e r f a c e D a t a ; PS P_ DE V IC E_ IN TE RF AC E_ DE TA IL _ DA TA d e v i c e I n t e r f a c e D e t a i l D a t a = NULL; i n t m a in (){ d e v i c e I n f o S e t = S e t u p D iG e t C la s s D e v s ( & G U I D _ D E V I N T E R F A C E _ U S B _ D E V I C E , N ULL , if
NU LL,
DIGCF_PRESENT
| DIGCF_INTERFACEDEVICE);
( d e v i c e I n f o S e t == INVALID_HANDLE_VALUE) d is p la y E rro r("N ie
z id e n ty fik o w a n o podłączonych
d e v ic e In te rfa c e D a ta .c b S iz e
urząd zeń.\n");
= size o f(SP_D EVICE _IN TE RFACE_D ATA);
w h ile (S e tu p D iE n u m D e v ic e In te rfa c e s (d e v ic e In fo S e t,
NULL,
& GU ID_ DE VIN TERFACE_USB_DEVICE, memberIndex,
&devi c e I n t e r f a c e D a t a ) ) {
me mberInde x++; / / i n k r e m e n t a c j a numeru i n t e r f e j s u S e tu p D iG e tD e v ic e In te rfa c e D e ta il(d e v i ce In fo S e t, NU LL,
0,
& d e v ice In te rface D a ta ,
& d e v ic e In te rfa c e D e ta ilD a ta S iz e ,
NULL);
d e v i c e I n t e r f a c e D e t a i l D a t a = (PS P_ DE V IC E_ IN T ER F AC E_ DE T AI L _D AT A ) new D W O R D [ d e v i c e I n t e r f a c e D e t a i l D a t a S i z e ] ; d e v ice In te rfa ce D e ta ilD a ta -> cb S ize = size o f(S P _ D E V IC E _ IN T E R F A C E _ D E T A IL _ D A T A ); if
( !S e tu p D iG e tD e v ic e In te rfa c e D e ta il( d e v ic e In fo S e t, d e v ic e In te rfa c e D e ta ilD a ta , & re q u ire d S iz e ,
&devi c e I n t e r f a c e D a t a ,
d e v ic e In te rfa c e D e ta ilD a ta S iz e ,
NULL)){
r e le a s e M e m o r y (d e v ic e In te r fa c e D e ta ilD a ta ); S e tu p D iD e s tr o y D e v ic e In fo L is t(d e v ic e In fo S e t);
//d is p la y E rro r
( " N i e można p o b ra ć i n f o r m a c j i o i n t e r f e j s i e . \ n " ) ;
} c o u t << d e v i c e I n t e r f a c e D e t a i l D a t a - > D e v i c e P a t h << e n d l ; releaseM em ory(d evi c e I n t e r f a c e D e t a ilD a t a ) ;
} ; / / k o n i e c w hile S e tu p D iD e s tro y D e v ic e In fo L is t(d e v ic e In fo S e t); c o u t << e n d l ; system ("PAUSE"); return
0;
}
/ / --------------------------------------------------------------------------------------------------------
176
USB. Praktyczne programowanie z Windows API w C++
U SB. Praktyczne pragram ow anie\Razdział 5\R5_5\Proj_USB_R5_5.exe ? \ u sb # vi d_09da&pi d_000a#&£rdc785 a a & 0 & 3 # | a 5 d c b tlO - 6 5 3 0 - lld 2 - 9 0 1 t- 0 0 c 0 4 tb 9 5 1 e „ \ \ ? \ u s b # v i d _ lc 4 f& p i d_0002#6&dc785 aa&0&4# {a5dcbf1 0 -6 5 3 0 - lld 2 - 9 0 1 f - 0 0 c 0 4 f b 9 5 1 e d } \ \ ? \ u sb # vi d_0781&pi d_5 5 3 0#2 004 35149118e7a2 Od el# {a5 d c b f 1 0 - 6 5 3 0 - lld 2 - 9 0 1 f - 0 0 c0 4 fb 9 5 l e d } P re s s any key t o
c o n t in u e
.
Rysunek 5.10. Detekcja wszystkich urządzeń aktualnie podłączonych do magistrali USB z GUID DEVINTERFACE USB DEVICE
Moduł cfgmgr32.h Menedżer konfiguracji oferuje szereg niezwykle użytecznych i prostych w wykorzysta niu funkcji rodziny CM_xxx(), które są pomocne w szybkim zdiagnozowaniu aktualnie dostępnych w systemie urządzeń. W niniejszym podrozdziale zostaną przedstawione te najbardziej podstawowe. W celu wykorzystania zasobów menedżera do identyfika cji urządzeń USB w pierwszej kolejności należy określić interesującą nas klasę insta lacji urządzeń w funkcji SetupDiGetClassDevs() oraz odpowiednio wywołać żądaną funkcję menedżera. N a listingu 5.8 zaprezentowano przykład wykorzystania funkcji: CMAPI CONFIGRET WINAPI C M _ G e t _ D e v N o d e _ R e g i s t r y _ P r o p e r t y ( IN
DEVINST d n D e v I n s t ,
IN
ULONG u l P r o p e r t y ,
INOUT
PULONG p u l R e g D a t a T y p e ,
INOUT
PVOID B u f f e r ,
INOUT
PULONG p u l L e n g t h ,
IN
ULONG u l F l a g s
);
w celu szybkiego odczytania informacji o zainstalowanych i aktualnie dostępnych urzą dzeniach na podstawie zapisów rejestru systemowego. Pierwszym parametrem funkcji je st pole typu DEVINST. DEVINST jest wewnętrzną strukturą danych reprezentującą urzą dzenie w systemie. D la urządzeń USB pierw szym param etrem wejściow ym funkcji CM_Get_DevNode_Registry_Property() będzie pole DevInst struktury SP_DEVINFO_DATA przechowującej informacje na temat egzemplarza urządzenia należącego do klasy urzą dzeń USB. Param etr wejściowy ulProperty identyfikuje właściwość urządzenia, którą aktualnie chcemy odczytać. Jest on reprezentowany przez odpowiednie stałe symbo liczne CM_DRP_xxx, określające żądane w łaściw ości instalacyjne urządzenia zapisane w rejestrze system ow ym (patrz rozdział 2.). W artości CM_DRP_xxx są zdefiniow ane w module cfgmgr32.h w następujący sposób: CM_DRP_DEVICEDESC
( 0 x 0 0 0 0 0 0 0 1 ) //D e v ic e D e s c REG_SZ
CM_DRP_HARDWAREID
( 0 x 0 0 0 0 0 0 0 2 ) / / H a r d w a r e l D REG_MULTI_SZ
pr operty (R W )
//property(R W ) CM_DRP_COMPATIBLEIDS
( 0 x 0 0 0 0 0 0 0 3 ) / / C o m p a t i b l e l D s REG_MULTI_SZ
//property(R W ) CM_DRP_UNUSED0
(0 x 0 0 0 0 0 0 0 4 ) //u n u s e d
CM_DRP_SERVICE
(0 x 0 0 0 0 0 0 0 5 ) / / S e r v i c e REG_SZ pr operty (R W )
CM_DRP_UNUSED1
(0 x 0 0 0 0 0 0 0 6 ) //u n u s e d
CM_DRP_UNUSED2
(0 x 0 0 0 0 0 0 0 7 ) //u n u s e d
CM_DRP_CLASS
( 0 x 0 0 0 0 0 0 0 8 ) / / C l a s s REG_SZ pr op erty (R W )
Rozdział 5. ♦ Detekcja i identyfikacja urządzeń dołączonych do magistrali USB
CM_DRP_CLASSGUI D
(0x00 00 00 09 )
CM_DRP_DRIVER
(0x0000000A)
CM_DRP_CONFIGFLAGS
(0x0000000B)
CM_DRP_MFG
(0x0000000C)
CM_DRP_FRIENDLYNAME
(0x0000000D)
CM DRP LOCATION INFORMATION
(0x00 00 00 0E )
CM_DRP_PHYSICAL_DEVICE_OBJECT_NAME ( 0 x 0 0 0 0 0 0 0 F ) CM DRP C A P A B I L I T I E S
(0x00000010)
CM_DRP_UI_NUMBER
(0x00000011)
CM DRP U P PE R FI L TE R S
(0x00000012)
177
/ClassGUID REG_SZ p r operty (R W ) / D r i v e r REG_SZ p r operty (R W ) / C o n f i g F l a g s REG_DWORD /p r o p e rty (R W ) /M fg REG_SZ p r operty (R W ) /F rie n d ly N a m e REG_SZ p r operty (R W ) / L o c a t i o n I n f o r m a t i o n REG_SZ /p r o p e rty (R W ) /P h y s ic a lD e v ic e O b je c tN a m e REG_SZ /p ro p e rty(R ) / C a p a b i l i t i e s REG_DWORD /p ro p e rty(R ) /UiNumber REG_DWORD p r o p e r t y ( R ) / U p p e r F i l t e r s REG_MULTI_SZ /p r o p e rty (R W )
Opcjonalny wskaźnik pulRegDataType wskazuje wartość NULL, a w skaźnik Buffer — bufor danych przechowujący łańcuch znaków identyfikujący egzemplarz urządzenia aktualnie przyłączonego do magistrali USB. Wskaźnik pul Length określa długość bufora danych. Znacznik ulFlags nie ma istotnego znaczenia i może zawierać wartość NULL. Prawidłowo wykonane funkcje menedżera konfiguracji zwracają wartość CR_SUCCESS. W programach x64 zam iast CM_Get_DevNode_Registry_Property() powinno się uży wać funkcji SetupDiGetDeviceRegistryProperty(), tak jak pokazano w dalszej części niniejszego podrozdziału. Listing 5.8. Określenie typów urządzeń USB aktualnie zainstalowanych w systemie # i n c l u d e #pragma o p t i o n
p u s h -a 1
# i n c lu d e < s e tu p a p i> #pragma o p t i o n
pop
# in c lu d e # in clu d e
"D :\\W IN D D K \\7 6 0 0 .1 6 3 8 5 .1 \\in c\\a p i\\c fg m g r3 2 .h "
u s i n g n a m es p a ce s t d ;
/ / -------------------------------------------------------------------------------------------------------/ / W y ś w i e t l a l i s t ę ur ządzeń USB z a i n s t a l o w a n y c h w s y s te m i e s trin g
g e tD e v ic e D e s crip tio n (D E V IN S T
devInst)
{ c h a r /*TCHAR*/ b u f f e r [ 1 0 2 3 ] ; ULONG b u f f e r L e n ; b u fferLen if
= s iz e o f( b u ffe r ) ;
((devInst
!= 0)
&& ( C M _ G e t _ D e v N o d e _ R e g i s t r y _ P r o p e r t y ( d e v I n s t ,
CM_DRP_DEVICEDESC / * C M _ D R P _ C L A S S * / ,
NU LL,
b u ffer,
& bu fferLen ,
== CR_SU CCE SS )) return
bu ffer;
};
/ / -------------------------------------------------------------------------------------------------------v o id p rin tU S B D e v ic e s () { DWORD m em b e rI n d e x =0; HDEVINFO d e v i c e I n f o S e t ; SP_DEVINFO_DATA d e v i c e I n f o D a t a ; d e v ic e I n f o S e t = S e tu p D iG e tC la s sD e v s
(N ULL ,
"USB",
D I G C F _ P R E S E N T | D I G C F _ A L L C L A S S E S );
NULL,
0)
178
USB. Praktyczne programowanie z Windows API w C++
if
( d e v i c e I n f o S e t == INVALID_HANDLE_VALUE) return;
f o r ( m e m b e r I n d e x = 0;
; memberIndex++)
d e v ic e In fo D a ta .c b S ize
= s iz e o f
{
(d e v ice In fo D a ta );
i f ( ! S e t u p D i E n u m D e v i c e I n f o ( d e v i c e I n f o S e t , m e m b e rI n d e x , & d eviceIn fo D ata)) break; c o u t << g e t D e v i c e D e s c r i p t i o n ( d e v i c e I n f o D a t a . D e v I n s t )
<< e n d l ;
} return; }
//
--------------------------------------------------------------------
i n t m ain () { c o u t << " R o d z a j
k o n t r o l e r a USB: \ n " ;
p rin tU S B D e v i c e s ( ) ; c o u t << e n d l ; system ("PAUSE"); return }
//
0;
--------------------------------------------------------------------
N a rysunkach 5.11 oraz 5.12 zaprezentowano wynik działania programu z listingu 5.: odpowiednio w systemach Windows 7 z USB 2.0 oraz Windows 8 z USB 3.0. u Menedżer urządzeń Plik
Akcja
Widok
Pomoc
1 Im I□ IH h I* IÊ o* itś ^ Kontrolery uniwersalnej magistrali szeregowej ÿ Generic USB Hub ÿ Generic USB Hub ••^ Główny koncentrator USB ••^ Główny koncentrator USB ••p Standardowy rozszerzony kontroler hosta PCI do USB ••p Standardowy rozszerzony kontroler hosta PCI do USB - ^ Urządzenie kompozytowe USB iÿ Urządzenie kompozytowe USB ••iÿ Urządzenie pamięci masowej USB >-:i_| Modemy , Bal Monitory
USB.Praktyczne programowanie k o n t r o l e r a U SB : k o n c e n t r a t o r USB k o n c e n t r a t o r USB e n i e k o m p o z y t o w e USB e n i e w id e o USB e n i e w e j ś c i o w e USB e n i e p a m i ę c i m a s o w e j USB c B lu e t o o c h A d a p te r c U SB H ub c U SB H ub e n i e k o m p o z y to w e USB e n i e w e j ś c i o w e USB e n i e w e j ś c i o w e USB A b y k o n ty n u o w a ć ,
n a c is n ij
d o w o ln y k l a w i s z
.
Q Mysz i inne urządzenia wskazujące Q Mysz zgodna z HID Q Mysz zgodna z HID Q Synaptics PS/2 Port TouchPad | Odbiorniki radiowe Bluetooth Q Generic Bluetooth Adapter 0 Moduł wyliczający Bluetooth firmy Microsoft £ 3 Procesory Stacje dysków Stacje dysków CD-ROM/DVD Urządzenia do obrazowania Urządzenia interfe su HID Urządzenie we ściowe USB ^ Urządzenie we ściowe USB •ifcs Urządzenie we ściowe USB
Rysunek 5.11. Porównanie działania aplikacji projektu proj_USB_R5_6 z informacjami przechowywanymi w menedżerze urządzeń Windows 7 z USB 2.0
Rozdział 5. ♦ Detekcja i identyfikacja urządzeń dołączonych do magistrali USB
179
M enedżer urządzeń Plik
Akcja
Widok
Pomoc
^ Kontrolery uniwersalnej magistrali szeregowej Etron USB 3.0 extensible Host Controller - 0100 (Microsoft) Etron USB 3.0 extensible Host Controller - 0100 (Microsoft) Generic USB Hub Generic USB Hub Główny koncentrator USB Główny koncentrator USB Główny koncentrator USB (xHCI) Główny koncentrator USB (xHCl) Rozszerzony kontroler hosta 1 USB2 mikroukładu z serii Intel(R) C600/X79 — 1D26 Rozszerzony kontroler hosta 2 USB2 mikroukładu z serii Intel(R) C600/X79 — 1D2D Urządzenie kompozytowe USB Urządzenie pamięci masowej USB
Rysunek 5.12. Porównanie działania aplikacji projektu proj_USB_R5_6 z informacjami przechowywanymi w menedżerze urządzeń Windows 8 z USB 3.0 W trakcie tw orzenia oprogram ow ania dla urządzeń USB często zachodzi potrzeba szybkiego odtworzenia drzewa urządzeń zainstalowanych w systemie oraz odczytania odpowiednich identyfikatorów VID oraz PID. Czynność tę można wykonać bez zna jom ości właściwych identyfikatorów GUID klas urządzeń. Menedżer konfiguracji ofe ruje funkcję: CMAPI CONFIGRET WINAPI CM _Get_Device_ID ( IN
DEVINST d n D e v I n s t ,
OUT PWSTR B u f f e r , IN
ULONG B u f f e r L e n ,
OUT ULONG u l F l a g s );
przechowującą identyfikatory egzemplarzy aktualnie zainstalowanych w systemie urzą dzeń. D la urządzeń USB pierwszym parametrem funkcji będzie pole DevInst struktu ry SP_DEVINFO_DATA przechowującej informacje na temat egzemplarza urządzenia na leżącego do klasy urządzeń USB. Wskaźnik Buffer wskazuje bufor danych zawierający łańcuch znaków opisujący egzem plarz urządzenia aktualnie przyłączonego do magi strali USB. Parametr BufferLength określa długość bufora danych. Znacznik ulFlags nie m a istotnego znaczenia i może zawierać wartość NULL, tak jak pokazano na listin gu 5.9. Programy użytkownika m ogą dodatkowo korzystać z usług funkcji: CMAPI CONFIGRET WINAPI C M _G e t_ C h ild( OUT
PDEVINST p d n D e v I n s t ,
IN
DEVINST d n D e v I n s t
IN
ULONG u l F l a g s
,
);
przechowującej identyfikator urządzenia potomnego w drzewie urządzeń. Listing 5.9. Kod modułu usb_R5_7.cpp # i n c l u d e #pragma o p t i o n
p u s h -a 1
# i n c lu d e < s e tu p a p i> #pragma o p t i o n
pop
lSO
USB. Praktyczne programowanie z Windows API w C++
#include #include "D:\\WINDDK\\7600.16385.1\\inc\\api\\cfgmgr32.h" using namespace std; I I -------------------------------------------------------------------HDEVINFO deviceInfoSet; DEVINST devInstChild; SP_DEVINFO_DATA deviceInfoData; CONFIGRET configRet; char /*TCHAR*/ buffer[MAX_DEVICE_ID_LEN]; DWORD /*ULONG*/ propertyBufferSize = 0; DWORD property; char *propertyBuffer = NULL; DWORD propertyRegDataType; I I -------------------------------------------------------------------char* printPrperty() { SetupDiGetDeviceRegistryProperty(deviceInfoSet, &deviceInfoData, property, NULL, NULL, 0, &propertyBufferSize); propertyBuffer = new char[(propertyBufferSize * sizeof(char/*TCHAR*/))]; i f (SetupDiGetDeviceRegistryProperty(deviceInfoSet, &deviceInfoData, SPDRP_DEVICEDESC, &propertyRegDataType, propertyBuffer, propertyBufferSize, &propertyBufferSize)) return propertyBuffer; } I I — Wyświetla drzewo urządzeń USB oraz odczytuje ich identyfikatory VID i PID— void printUSBDevices() { deviceInfoSet = SetupDiGetClassDevs(NULL, "USB", NULL, DIGCF_PRESENT | DIGCF_ALLCLASSES); i f(deviceInfoSet == INVALID_HANDLE_VALUE) return; for(DWORD memberIndex = 0; ; memberIndex++) { deviceInfoData.cbSize = sizeof (deviceInfoData); if(!SetupDiEnumDeviceInfo(deviceInfoSet, memberIndex, &deviceInfoData)) break; configRet = CM_Get_Device_ID(deviceInfoData.DevInst, buffer, MAX_PATH, 0); if (configRet == CR_SUCCESS) { printf("\n%s\n", pri ntPrperty()); printf("%s\n", buffer); delete [] propertyBuffer; } configRet = CM_Get_Child(&devInstChild, deviceInfoData.DevInst, 0); if(configRet == CR_SUCCESS) { configRet = CM_Get_Device_ID (devInstChild, buffer, MAX_PATH, 0); if(configRet == CR_SUCCESS){ IIprintf(" %s\n", printProperty()); printf(" %s\n", buffer); IIdelete [J propertyBuffer; } configRet = CM_Get_Child(&devInstChild, devInstChild, 0);
Rozdział 5. ♦ Detekcja i identyfikacja urządzeń dołączonych do magistrali USB
i f ( c o n f i g R e t == CR_SUCCESS)
{
c o n f ig R e t = C M _G et_ D e v ice _ ID (d e v In stC h ild , i f ( c o n f i g R e t == CR_SUCCESS)
/ / p r in tf(" p r in tf( "
181
b u ffer,
MAX_PATH, 0 ) ;
{
% s\n ", p r i n t P r o p e r t y ( ) ) ; %s\n",
b uffer);
//d e le te [ ] pro p e rtyB u ffe r; } } e ls e
{
c o n tin u e ; } } e lse
{
co n tin u e ; } } return; }
//
--------------------------------------------------------------------
i n t m ain () { p rin tU S B D e v ic e s(); c o u t << e n d l ; system ("PA U SE"); return }
//
0;
--------------------------------------------------------------------
N a rysunku 5.13 zaprezentowano rezultat działania programu wyświetlającego drzewo aktualnie dostępnych w systemie urządzeń USB. Rysunek 5.13. Detekcja ścieżek wystąpień wszystkich obiektów urządzeń aktualnie podłączonych do magistrali USB
USB.Praktyczne programowanie\Rozdział 5\R5_Apr... Głów ny k o n c e n t r a t o r USB (xrtC I) U5B\ROOT_HUB 30\5&25 82B61A&0&0 U r z ą d z e n ie p a m ię c i masowej USB U5B\VID_0781&PID_5 5 30\2 004 3 5149118E7A20DE1 U r z ą d z e n ie kompozytowe USB U5B\VID_1C4F&PID_0002\6&DC 785AA&0&4 USB\VID_1C4F&PID_0002£M I_00\7& 1C 3 01AE4&0&0000 HID\VID_1C4F£PID_0002iW I_00\8&35F289D1&0&0000 Głów ny k o n c e n t r a t o r USB U5B\ROOT_HUB20\4&1143D803&0 U5B\VID_8087&PI D_0024\5 &80170F4&0&1 USB\VID_0781&PID_5 5 30\200435149118E7A20DE1 U r z ą d z e n ie w e jś c io w e USB USB\VID_1C4F&PID_0002&MI_00\7&1C 3 01AE4&0&0000 HID\VID_1C4F&PID_0002&MI_00\8&35 F289D1&0&0000 U r a d z e n i e w e jś c io w e USB U5B\VID_1C4F&PID_0002&MI_01\7&1C301AE4&0&0001 H ID\VID_1C4 F&PI D_0002&M I_01&COL01\8&121B4COF&0&0000 Głów ny k o n c e n t r a t o r USB U5B\ROOT_HUB20\4&1EEA0231&0 U5B\VID_8087&PID_0024\5&2CC4586D&0&1 USB\VID_09DA&PID_000A\6&DC 785AA&0&3 Głów ny k o n c e n t r a t o r USB (x tfC I) U5B\ROOT_HUB3O\5&10CAD5A2&O&O G e n e r ic USB Hub U5B\VID_8087&PID_0024\5&80170F4&0&1 USB\VID_0781&PID_55 30\2 004 3514 9118E7A20DE1 G e n e r ic USB Hub USB\VID_8087&PID_0024\5&2CC4586D&0&1 U5B\VID_09DA&PID_000A\6&DC 785AA&0&3 HID\VID_09DA&PID_000A\7&3655 2 396&0&0000 U rz ą d z e n ie w e jś c io w e USB USB\VID_09DA&PID_000A\6&DC 785AA&0&3 HID\VID_09DA&PID_000A\7&365 5 23 96&0&0000 P r e s s a n y k e y t o c o n t in u e . . . _
182
USB. Praktyczne programowanie z Windows API w C++
Zasoby menedżera konfiguracji um ożliw iają samodzielne odtworzenie drzewa urzą dzeń. Hierarchiczne drzewo urządzeń zawiera dane o dostępnym w systemie sprzęcie i zostaje utworzone przez system operacyjny na podstawie inform acji otrzymanych z poszczególnych sterowników. Każdy w ęzeł drzewa reprezentuje fizyczny obiekt urządzenia. W celu dokładnego zapoznania się z zasobami sprzętowymi można sko rzystać z opcji Urządzenia według połączeń z menu Widok menedżera urządzeń.
Biblioteka Setupapi Biblioteka Setupapi zawiera m.in. funkcje (będące odpowiednikami funkcji z modułu setupapi.h) związane z instalacją urządzeń. W tabeli 5.4 przedstawiono funkcje po mocne w wyszukaniu konkretnego urządzenia lub klasy urządzeń w systemie i pobie rające szczegółowe informacje o interfejsie dostępne z poziom u biblioteki Setupapi wraz z ich odpowiednikami z modułu setupapi.h. Tabela 5.4. Podstawowe funkcje eksportowane z biblioteki Setupapi.dll Setupapi.h
Setupapi.dll
SetupDiGetClassDevs()
SetupDiGetClassDevsA()
SetupDiEnumDeviceInterfaces()
SetupDiEnumDeviceInterfaces()
SetupDiDestroyDeviceInfoList()
SetupDiDestroyDeviceInfoList()
SetupD iG etD eviceInterfaceD etail()
SetupDiGetDeviceInterfaceDetailA()
SetupDiGetDeviceRegistryProperty()
SetupDiGetDeviceRegistryPropertyA()
Bibliotekę Setupapi można łączyć z programem w sposób statyczny, korzystając z mo dułu Setupapi.lib lub dynamicznie wykorzystując Setupapi.dll. Należy zwrócić uwagę na fakt, że niektóre (starsze) kom pilatory C++ m ogą niewłaściwie odwzorowywać identyfikator biblioteki łączonej statycznie w przestrzeni adresowej głównego procesu (programu wykonawczego). Dlatego też dużo bezpieczniejszym sposobem w ykorzy stania zasobów Setupapi jest dynamiczne odwzorowywanie identyfikatora Setupapi.dll w przestrzeni adresowej głównego procesu. N a listingu 5.10 zaprezentowano kod programu posługującego się funkcjami ekspor towanymi z biblioteki Setupapi.dll. K od ten jest również ilustracją kolejnego sposobu importowania funkcji z biblioteki dołączanej dynamicznie. Listing 5.10. Kod modułu usb_R5_8.cpp # i n c l u d e #pragma o p t i o n
push -a 1
# i n c lu d e < s e tu p a p i> #pragma o p t i o n
pop
# in c lu d e # in c lu d e u s i n g n a m es p a ce s t d ;
Rozdział 5. ♦ Detekcja i identyfikacja urządzeń dołączonych do magistrali USB
v o id d is p la y E r r o r ( c o n s t
c h a r * msg){
c o u t << msg << e n d l ; system ("PAUSE"); e x it(0 ); };
I I -------------------------------------------------------------------------------------------------------t e m p l a t e < c l a s s T> in lin e
v o i d r e l e a s e M e m o r y ( T &x)
{ assert(x de le te
!= N U L L ) ;
[]
x;
x = NULL; }
I I -------------------------------------------------------------------------------------------------------I * A5DCBF10-6530-11D2-901F-00C04FB951ED * I s ta tic
GUID GUID_DEVINTER FACE _U SB_ DEV ICE =
{0x A5 DC B F1 0 ,
0x6530,
0x11D2,
{0x90,
0x1F,
0x00,
0 xC 0 ,
0x4F,
0xB9,
0x51,
0xED}};
I * 3ABF6F2D-71C4-462a-8A92-1E6861E6AF27 * I s ta tic
GUID GUID_DEVINTERFACE_USB_HOST_CONTROLLER =
{0x3abf6f2d,
0x71c4,
0x462a,
{0x8a,
0x92,
0x1e,
\
0x68,
0x61,
0xe6,
0 xaf,
0x27}};
I * F18A0E88-C30C-11D0-8815-00A0C906BED8 * I s ta tic
GUID GUID_DEVINTERFACE_USB_HUB =
{0xf18a0e88,
0xc30c,
0x11 d0 ,{0x88 ,
0x15,
0x00,
\
0xa0,
0xc9,
0x06,
0xbe,
0xd8}};
I I -------------------------------------------------------------------------------------------------------DWORD m em be rI n de x = 0; DWORD d e v i c e I n t e r f a c e D e t a i l D a t a S i z e ; DWORD r e q u i r e d S i z e ; HMODULE h S e t u p A p i ; HDEVINFO d e v i c e I n f o S e t ; S P_ DE V IC E_I NT ER FA CE _D A TA d e v i c e I n t e r f a c e D a t a ; PS P_ DE V IC E_ IN TE RF AC E_ DE TA IL _ DA TA d e v i c e I n t e r f a c e D e t a i l D a t a = NULL; in t m a in ( ) { ty p e d e f v o id *
(
s td c a ll
* p S e tu p D iG e tC la ssD e vs)
(IN LPGUID
C la s s G u id ,
IN PCTSTR IN HWND
Enum erator, hwndParent,
IN DWORD ty p e d e f bool
(
OPTIONAL OPTIONAL
F la g s );
s td c a ll*
p S e tu p D iE n u m D e v ic e In te rfa c e s)
( i n HDEVINFO
D e vice In fo S e t,
IN PSP_DEVIN FO_DATA IN LPGUID IN DWORD
OPTIONAL
D e vice In fo D a ta ,
MemberIndex,
OUT PS P_ DE VI CE_ IN TER FA CE_ DAT A ty p e d e f bool ty p e d e f bool
OPTIONAL
In te rfa c e C la s s G u id , D e vic e In te rfa c e D a ta );
(
s td c a ll
* p S e tu p D iD e s tr o y D e v ic e In fo L is t)(IN
(
s td c a ll
* p S e tu p D iG e tD e v ic e In te rfa c e D e ta il)
(IN HDEVINFO
D e vice In fo S e t,
v o id * );
183
18 4
USB. Praktyczne programowanie z Windows API w C++
IN PS P_ DE VI CE_ IN TER FA CE_ DAT A
D e vice In te rfa ce D a ta ,
OUT P S P_ DE V IC E_ IN TE RF A CE _ DE TA IL _ DA TA D e v i c e I n t e r f a c e D e t a i l D a t a , IN DWORD
D e v ic e I n te rfa c e D e ta ilD a ta S iz e ,
OUT PDWORD
R e q u ire d S iz e ,
OUT PSP_DEVINFO_DATA hSetupApi if
OPTIONAL
D e vice In fo D a ta
O PT IO NA L );
= L o a d L ib ra ry ("C :\\W in d o w s \\S y s te m 3 2 \\S E T U P A P I.D L L ");
(!hSetupA pi) d is p la y E rro r("B łą d
d o łą c ze n ia b i b lio t e k i
S ET U PA PI.D LL.");
p S e t u p D i G e t C l a s s D e v s S e t u p D i G e t C l a s s D e v s A = NULL; S e tu p D iG e tC la s sD e v sA = (p S e tu p D iG e tC la s s D e v s ) G e tP ro c A d d re s s ( h S e tu p A p i, "S e tu p D iG e tC la s sD e v sA "); p S e t u p D i E n u m D e v i c e I n t e r f a c e s S e t u p D i E n u m D e v i c e I n t e r f a c e s = NULL; SetupD iE num D evi c e I n t e r f a c e s
= (pSetupDi En u m D e vice In te rfa ce s )
G e tP ro cA d d re s s(h S e tu p A p i,
"Setup D i E n u m D e v ic e In te rfa c e s ");
p S e t u p D i D e s t r o y D e v i c e I n f o L i s t S e t u p D i D e s t r o y D e v i c e I n f o L i s t = NULL; S e tu p D iD e s tro y D e v i c e I n f o L i s t = (pS e tu p D iD e s tro y D e v i c e I n f o L i s t ) G e tP ro cA d d re s s(h S e tu p A p i,
"S e tu p D iD e s tro y D e v ic e In fo L is t");
p S e tu p D iG e tD e v ic e In te rfa c e D e ta il
S e t u p D i G e t D e v i c e I n t e r f a c e D e t a i l A = NULL;
S e t u p D i G e t D e v i c e I n t e r f a c e D e t a i l A = (p S e tu p D iG e tD e v i c e I n t e r f a c e D e t a i l ) G e tP ro cA d d re s s(h S e tu p A p i, if
(!S e tu p D iG e tC la ss D e v s A
||
" S e tu p D iG e tD e v ic e In te rfa c e D e ta ilA " );
!S etu p D iE n u m D e vice In te rfa ce s
!S e tu p D iG e tD e v ic e In te rfa c e D e ta ilA
||
||
!S e tu p D iD e s tr o y D e v ic e In fo L is t)
{
F re e L ib ra ry (h S e tu p A p i); d is p la y E rr o r(" N ie
zn a le zio n o
fu n k cji
e ksp o rto w ych .");
} d e v i c e I n f o S e t = S e t u p D iG e t C la s s D e v s A ( & G U I D _ D E V I N T E R F A C E _ U S B _ H U B , N ULL , if
NU LL,
DIGCF_PRESENT
| DI GC F _ I N T E R F A C E D E V I C E );
( d e v i c e I n f o S e t == INVALID_HANDLE_VALUE) d is p la y E rro r("N ie
z id e n ty fik o w a n o podłączonych
d e v ic e In te rfa c e D a ta .c b S iz e
urząd zeń.\n");
= size o f(SP_D EVICE _IN TE RFACE_D ATA);
w h ile (S e tu p D iE n u m D e v ic e In te rfa c e s (d e v ic e In fo S e t,
NULL,
&GUID _DEVINTERFACE_USB_HUB, memberIndex,
&devi c e I n t e r f a c e D a t a ) ) {
me mberInde x++; I I i n k r e m e n t a c j a numeru i n t e r f e j s u S e tu p D iG e tD e v ic e In te rfa c e D e ta ilA (d e v ic e In fo S e t, NU LL,
0,
& d e v ice In te rfac e D a ta ,
& d e v ic e In te rfa c e D e ta ilD a ta S iz e ,
NULL);
d e v i c e I n t e r f a c e D e t a i l D a t a = (PS P_ DE V IC E_ IN T ER F AC E_ DE T AI L _D AT A ) new D W O R D [ d e v i c e I n t e r f a c e D e t a i l D a t a S i z e ] ; d e v ice In te rfa ce D e ta ilD a ta -> cb S ize = size o f(S P _ D E V IC E _ IN T E R F A C E _ D E T A IL_ D A T A ); if
(!S e tu p D iG e tD e v ic e In te rfa c e D e ta ilA (d e v i ce In fo S e t, d e v ic e In te rfa c e D e ta ilD a ta , & re q u ire d S iz e ,
NULL)){
r e le a s e M e m o r y (d e v ic e In te r fa c e D e ta ilD a ta ); S e tu p D iD e s tr o y D e v ic e In fo L is t(d e v ic e In fo S e t); }
& d e v ice In te rface D a ta ,
d e v ic e In te rfa c e D e ta ilD a ta S iz e ,
Rozdział 5. ♦ Detekcja i identyfikacja urządzeń dołączonych do magistrali USB
185
c o u t << d e v i c e I n t e r f a c e D e t a i l D a t a - > D e v i c e P a t h << e n d l ; releaseM em ory(d evi c e ln t e r f a c e D e t a ilD a t a ) ; };
I I k o n i e c whi le
S e tu p D iD e s tro y D e v ic e In fo L is t(d e v ic e In fo S e t); c o u t << e n d l ; F re e L ib ra ry (h S e tu p A p i); system ("PAUSE"); return
0;
}
I I --------------------------------------------------------------------------------------------------------
Powiadamianie o dołączaniu i odłączaniu urządzeń Podłączanie urządzeń LS i FS pow oduje zmiany napięcia odpow iednio na liniach D - i D+ (urządzenia HS oraz SS podłącza się tak samo ja k FS). Każde urządzenie HS jest na początku traktowane ja k urządzenie FS. Dopiero w czasie konfiguracji hub HS sprawdza, czy z tym urządzeniem m ożliw a je st kom unikacja z w ysoką szybkością transmisji danych. W podobny sposób jest wykrywane odłączenie urządzenia, z tym że podczas odłączania napięcie na odpowiedniej linii danych maleje do zera, co powodu je zablokowanie portu i sygnalizację zdarzenia w rejestrach statusowych portu i huba. W trakcie działania programu obsługującego zewnętrzne urządzenie USB istnieje moż liwość powiadamiania użytkownika o dołączaniu lub odłączaniu urządzenia. W celu zaprogramowania tego typu funkcjonalności aplikacji w pierwszej kolejności powinni śmy uzyskać interesujący nas identyfikator GUID klasy urządzeń. Następnie należy od powiednio wypełnić (patrz tabela 5.5) pola struktury DEV_BROADCAST_DEVICEINTERFACE z modułu D bt.h: t y p e d e f s t r u c t _DEV_BROADCAST_DEVICEINTERFACE { DWORD d b c c _ s i z e ; DWORD d b c c _ d e v i c e t y p e ; DWORD d b c c _ r e s e r v e d ; GUID d b c c _ c l a s s g u i d ; TCHAR d b c c _ n a m e [ 1 ] ; } DEV_BROADCAST_DEVICEINTERFACE *PDEV_BROADCAST_DEVICEINTERFACE;
Tabela 5.5. Specyfikacja struktury DEV BROADCAST DEVICEINTERFACE Typ
Element struktury
Znaczenie
DWORD
dbcc size
Rozmiar struktury w bajtach plus długość łańcucha znaków wpisanego w polu dbcc name (jeżeli jest używane)
DWORD
dbcc devicetype
DBT_DEVTYP_DEVICEINTERFACE
DWORD
dbcc reserved
Zarezerwowane
GUID
dbcc classguid
Identyfikator GUID klasy urządzeń
TCHAR
dbcc name
Łańcuch znaków (zakończony zerowym ogranicznikiem) określający nazwę urządzenia
186
USB. Praktyczne programowanie z Windows API w C++
Kolejnym krokiem jest odpowiednie zarejestrowanie powiadomienia poprzez zadekla row aną w module windows.h funkcję: HDEVNOTIFY WINAPI IN
R e g is te rD e v ic e N o tific a tio n (
HANDLE h R e c i p i e n t ,
IN
LPVOID N o t i f i c a t i o n F i l t e r ,
IN
DWORD F l a g s
);
Parametr hRecipient jest identyfikatorem obiektu (np. okna w programie), który otrzy ma powiadomienie. Param etr N o tific a tio n F ilte r zawiera informacje o urządzeniach, których ma dotyczyć powiadomienie. Znacznik Flags przechowuje informacje o odbiorcy powiadomienia. Jeżeli odbiorcą powiadomienia jest okno, znacznik Flags przyjmuje wartość DEVICE_NOTIFY_WINDOW_HANDLE. W przypadku gdy do Flags zostanie wpisana wartość DEVICE_NOTIFY_ALL_INTERFACE_CLASSES, powiadomienia będą dotyczyć inter fejsów wszystkich klas urządzeń (parametr dbcc_classguid jest wówczas ignorowany). N a listingu 5.11 zaprezentowano kod um ożliwiający realizację powiadomień o dołą czaniu lub odłączaniu urządzeń klasy HID w trakcie działania programu. Odpowied nie kom unikaty są przetw arzane w pętli w ywołań funkcji API SDK GetMessage(), TranslateM essage() oraz DispatchMessage() i wyświetlane bezpośrednio na pulpicie (program posługuje się funkcją WinMain()). Powiadomienia o zmianie stanu dołącza nego lub odłączanego urządzenia USB są generowane w postaci standardowych kom u nikatów WM_DEVICECHANGE w funkcji API SDK: LRESULT CALLBAC K WindowProc(HWND hwnd, WPARAM wParam,
UINT uMsg, LPARAM l P a r a m ) ;
W ysyła ona kom unikat identyfikowany przez uMsg do okna identyfikowanego przez param etr hwnd. Listing 5.11. Kod modułu usb_R5_9.cpp # in c lu d e # i n c l u d e # i n c l u d e s ta tic
GUID GUID_DEVINTER FACE _U SB_ DEV ICE =
{0x A5 DC B F1 0 ,
0x6530,
0x11D2,
{0x90,
0x1F,
0x00,
0 xC 0 ,
0x4F,
0xB9,
0x51,
0xED}};
/ / -------------------------------------------------------------------bool
h i d D e v i c e N o t i f y ( H W N D hwnd, GUID GUID_DEV INT ER FAC E_ HID, HDEVNOTIFY * h i d D e v i c e N o t i f y )
{ DEV_BROADCAST_DEVICEINTERFACE N o t i f i c a t i o n F i l t e r ; Zero M em ory(& N otifi c a t i o n F i l t e r ,
s iz e o f( N o tifi c a tio n F ilte r) ) ;
N o t i f i c a t i o n F i l t e r . d b c c _ s i z e = s iz e o f ( D E V _ B R O A D C A S T _ D E V I C E I N T E R F A C E ) ; N o t if i c a tio n F ilte r.d b c c _ d e v ic e ty p e N o t if i c a tio n F ilte r.d b c c _ c la s s g u id * h id D e v ic e N o tify
= DBT_DEVTYP_ DE VICE INT ER FA CE; = GUID_DEV INT ER FAC E_ USB _D EV ICE;
= R e g is te rD e v ic e N o tific a tio n (h w n d ,
& N o tific a tio n F ilte r,
DEVIC E_NOTIFY_WINDOW_HANDLE); if ( ! h i d D e v i c e N o tify )
Rozdział 5. ♦ Detekcja i identyfikacja urządzeń dołączonych do magistrali USB
}
//
re turn
fa lse ;
return
true;
187
--------------------------------------------------------------
LRESULT CALLBAC K WindowProc(HWND hwnd,
UINT uMsg, WPARAM wParam,
LPARAM l P a r a m )
{ s w itc h (u M sg )
{
c a s e WM_DEVICECHANGE: M essageBox(NULL,
" J e d n o z u r z ą d z e ń USB z o s t a ł o o d ł ą c z o n e / p r z y ł ą c z o n e ! " ,
"Uw aga!",
MB_ICONEXCLAMATION
| MB_OK);
break; d e fa u lt: return } re turn }
//
D e f W in d o w P r o c ( h w n d ,
uMsg, wParam,
lP a ra m );
0;
--------------------------------------------------------------
i n t WINAPI W i n M a in ( H I N S T A N C E h I n s t a n c e ,
HINSTANCE,
LPS TR,
in t)
{ HWND hWnd; WNDCLASSEX w n d C l a s s E x ; HDEVNOTIFY h D e v i c e N o t i f y ; MSG Msg; w n d C la s s E x .c b S iz e
= sizeof(W NDC LASSEX);
w n d C la ssE x .style
= 0;
w n d C la ss E x .lp fn W n d P ro c
= W indowProc;
w n d C la s s E x .c b C ls E x tra
= 0;
w ndC lassEx.cb W ndExtra
= 0;
w n d C la ssE x .h In sta n ce
= h Instance;
w n d C la ss E x .lp sz C la s sN a m e = " D e v ic e N o t if y T e s t " ; i f(!R e g is te rC la s sE x (& w n d C l a ssEx)){ M essageBox(NULL,
"B łą d r e j e s t r a c j i
M B JCO NE XCL AM ATI ON return
okna!",
"B łą d !",
| MB_OK);
0;
} hWnd = C r e a t e W i n d o w E x ( W S J X _ C L I E N T E D G E , " D e v i c e N o t i f y T e s t " , " CW_USEDEFAULT, CW_USEDEFAULT, 0 , hInstance,
0,
",WS_OVERLAPPEDWINDOW, NU LL,
NULL,
NULL);
i f( hW nd == NULL ){ M essageBox(NULL,
"B łą d u tw o rze n ia o k n a !",
M B JCO NE XCL AM ATI ON return
"B łą d !",
| MB_OK);
0;
} if(!h id D e v ic e N o tify (N U L L , M e ssageBox(N ULL,
"B łą d !", return
GUID_D EVI NTE RFA CE_ USB _D EV IC E,
"B łą d w yko n a n ia f u n k c j i M B _ I CONEXCLAMATION
& hD e vice N o tify)){
h id D e v ic e N o tify ().",
| MB_OK);
1;
} M e ssageBox(N ULL,
"Funkcja h id D e v ic e N o tify ()
" U w a g a ! " , MBJ CO NE XC LA MA TI O N w h ile(G etM essage (& M sg ,
NULL,
T ra n s la te M e s sa g e (& M s g );
0,
0)
> 0 ){
wykonana p o m y ś ln ie . " ,
| MB _ OK );
188
USB. Praktyczne programowanie z Windows API w C++
DispatchM essage(& M sg); } r e t u r n M s g. w Pa ra m ; }
//
-------------------------------------
Analiza kodu pozwala zauważyć, że program zostanie uruchomiony w głównym w ąt ku systemu operacyjnego i pozostanie tam rezydentny (będzie cyklicznie odbierał ko munikaty od sterowników zgodnie z m odelem warstwowym systemu USB), dopóki nie zostanie przez użytkownika usunięty z pamięci lub dopóki system operacyjny nie zostanie zrestartowany, tak jak pokazano na rysunku 5.14. Rysunek 5.14. Komunikaty 0 odłączaniu 1przyłączaniu urządzeń
Uwaga!
A
Funkcja hidDeviceNotifyQ wykonana pomyślnie,
OK
Niedogodności związane z działaniem i obsługą programu opartego na kodzie z listingu 5.10 można rozwiązać w prosty sposób, który polega na zaprogramowaniu interfejsu użytkownika w standardowej funkcji main(), tak jak pokazano na listingu 5.12. Na ry sunku 5.15 zobrazowano wynik działania tak zmodyfikowanego kodu podczas testów polegających na przyłączaniu i odłączaniu urządzeń USB w trakcie działania programu. Listing 5.12. Fragment kodu modułu usb_R5_10.cpp //
--------------------------------------------------------------
i n t m ain () { c o n s t c h a r * c o n s t classN am e = " D e v i c e N o t i f y T e s t " ; HDEVNOTIFY h D e v i c e N o t i f y ; WNDCLASS w i n c l
= {0 } ;
w in c l. h I n s t a n c e = G etM oduleH andle(0); w in c l. lp s z C la s s N a m e = classNam e; w i n c l . l p f n W n d P r o c = W in d o w P r o c ; if
(!R e g is te rC la s s (& w in c l))
{
DWORD e r r o r = G e t L a s t E r r o r ( ) ; c o u t << " B ł ę d n e w y k o n a n i e R e g i s t e r C l a s s ( ) , return
b ł ą d = " << e r r o r << e n d l ;
1;
} HWND hwnd = C r e a t e W i n d o w E x ( 0 , 0,
classN am e, 0,
0,
0,
0,
classN am e, 0,
0,
0,
0);
Rozdział 5. ♦ Detekcja i identyfikacja urządzeń dołączonych do magistrali USB
if
( !h w n d)
189
{
DWORD e r r o r = G e t L a s t E r r o r ( ) ; c o u t << " B ł ę d n e w y k o n a n i e C r e a t e W i n d o w E x ( ) , return
} if(!h id D e v ic e N o tify (N U L L ,
GUID_D EV IN TER FAC E_ USB _D EV IC E,
c o u t << " B ł ą d w y k o n a n i a f u n k c j i return
b ł ą d = " << e r r o r << e n d l ;
1; & hD e vice N o tify)){
h id D e v ic e N o tify ().\ n ";
1;
} c o u t << " F u n k c j a h i d D e v i c e N o t i f y ( )
w y k on an a p o m y ś l n i e . \ n " ;
c o u t << " O c z e k i w a n i e n a p o w i a d a m i a n i e o o d ł ą c z a n i u / " "p rzy łą cza n iu
u rzą d ze n ia .\n "
"Aby z a k o ń c z y ć program , fo r
(;;)
n a ciśn ij
C t r l + C \ n " << e n d l ;
{
MSG msg; BOOL b R e t = G e t M e s s a g e ( & m s g , if
( ( b R e t == 0)
||
hwnd, 0 ,
0);
( b R e t == - 1 ) )
break; T ra n s la te M e s s a g e (& m s g ); D ispatchM essage(&m sg); } return }
//
0;
--------------------------------------------------------------
USB.Praktyczne programowanie\Rozdział 5\R5_10\proj_USB_R5_1... F u n k c j a h i d D e v i c e N o t i f y O w y k o n a n a p om ysł m e . ' c z e k i w a n ie n a p o w ia d a m ia n ie o o d l a c z a n i u / p r z y l a c z a n i u b y z a k o n c z y c p r o g r a m ,n a c i ś n i j C t r l + C
u r z ą d z e n ia ,
Rysunek 5.15. Diagnozowanie zdarzeń polegających na przyłączaniu lub odłączaniu urządzeń USB
Podsumowanie W niniejszym rozdziale zostały omówione podstawowe zasoby systemowe służące do detekcji oraz identyfikacji interfejsów urządzeń dołączonych do magistrali USB. Za prezentowano przykłady praktycznego wykorzystania omawianych funkcji i struktur. Konstrukcje przykładowych programów starano się przedstawić w sposób na tyle przej rzysty, by Czytelnik nie miał żadnych problemów z samodzielną ich modyfikacją i do stosowaniem do swoich wymagań sprzętowych i programowych. Starano się również zadbać o czytelność kodu poprzez stosowanie oryginalnego nazewnictwa dla zmien nych i funkcji, które wiernie odzwierciedla ich rolę w programie.
190
USB. Praktyczne programowanie z Windows API w C++
Rozdział 6.
Odblokowanie urządzenia do transmisji. Odczyt i zapis danych Odczytanie ścieżek dostępu do interfejsów urządzeń aktualnie dostępnych w systemie nie oznacza, że można swobodnie korzystać z portu USB. Programy działające w try bie użytkownika zawsze inicjują połączenia z zewnętrznymi urządzeniami i za każdym razem powinny uzyskiwać dostęp do pliku sterownika urządzenia. Ten rozdział zapo zna nas z praktycznymi sposobami nawiązywania połączenia z zewnętrznym urządze niem USB. Przedstawione algorytmy będą łatwe do testowania i zostały zaprojektowane zgodnie z następującą regułą: „Kod je st łatwy do testowania, jeżeli można go przete stować niezależnie od pozostałych fragmentów tworzonego programu”.
Odblokowanie urządzenia do transmisji Przed rozpoczęciem korzystania z urządzenia przyłączonego do portu USB należy o tym fakcie poinformować system operacyjny. Czynność tę określa się jako otwieranie (lub odblokowywanie) urządzenia do transmisji. Jednak zanim zaczniemy wykonywać ja kiekolwiek czynności, system operacyjny musi zidentyfikować sterownik urządzenia aktualnie przyłączonego do portu USB. W przypadku uzyskania dostępu do sterownika urządzenia system operacyjny przekazuje do aplikacji jego identyfikator. W e wszyst kich operacjach wejścia-wyjścia zam iast szczegółowej ścieżki dostępu do interfejsu urządzenia USB używa się właśnie jego identyfikatora.
192
USB. Praktyczne programowanie z Windows API w C++
Funkcja CreateFile() Jest to funkcja służąca do otw arcia pliku reprezentującego urządzenie (sterow nik). Zw raca 32- lub 64-bitowy identyfikator urządzenia przechowywany we właściwości HANDLE, do którego będą adresowane wszystkie komunikaty i raporty. Składnia C reateF ile() wygląda następująco: HANDLE WINAPI C r e a t e F i l e ( I N
LPCTSTR l p F i l e N a m e ,
DWORD d w D e s i r e d A c c e s s ,
IN DWORD d w S h a re M od e, IN LPSEC U RI TY _A TT RI B U TE S l p S e c u r i t y A t t r i b u t e s , IN DWORD d w C r e a t i o n D i s p o s i t i o n , IN DWORD d w F l a g s A n d A t t r i b u t e s , IN HANDLE h T e m p l a t e F i l e ) ;
Pierwszy parametr, lpFileName, jest wskaźnikiem do zadeklarowanego ciągu znaków zakończonego zerowym ogranicznikiem (dokładniej: do pierwszego znaku tego łań cucha), w którym będzie przechowywana pełna nazwa symboliczna urządzenia (patrz rozdział 5., tabela 5.3). Jeżeli lpFileName reprezentuje nazwę, to w standardzie ANSI wartość domyślna dłu gości łańcucha jest ograniczona do MAX_PATH znaków. Wykorzystując rozszerzoną wer sję tej funkcji (standard Unicode), można pokonać ograniczenie co do długości oma wianego ciągu znaków, posługując się konwencją nazewnictwa UNC (ang. Universal Nam ing Convention). Param etr dwDesiredAccess zawiera specyfikację rodzaju dostępu do obiektu. Może sta nowić kombinację następujących wartości: ♦ GENERIC_ALL — przyznanie aplikacji praw do zapisu, odczytu i urucham iania pliku; ♦ GENERIC_EXECUTE — dostęp do uruchamiania pliku; ♦ GENERIC_READ — dostęp do odczytu z pliku lub urządzenia; ♦ GENERIC_WRITE — dostęp do zapisu do pliku lub urządzenia; ♦ 0 — bez dostępu, na przykład tylko tworzenie nowego pliku. Param etr dwShareMode w yszczególnia, w ja k i sposób dany obiekt (lub plik) może być współdzielony: ♦ 0 — obiekt nie może być współdzielony (ang. exclusive access); ♦ FILE_SHARE_DELETE — współdzielenie z operacjami usuwania; ♦ FILE_SHARE_READ — tryb współdzielenia z operacjami czytania; ♦ FILE_SHARE_WRITE — tryb współdzielenia z operacjami zapisu. Opcjonalnie używany parametr lp S ecu rity A ttrib u tes je st wskaźnikiem do struktury SECURITY_ATTRIBUTES zawierającej deskryptor zabezpieczeń obiektu i określającej, czy zwracany identyfikator jest dziedziczony.
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
t y p e d e f s t r u c t _SE CUR IT Y_ A TT RI BU TE S DWORD
193
{
nLength;
LPVOID l p S e c u r i t y D e s c r i p t o r ; BOOL
b In h e ritH a n d le ;
} SEC U RI TY _ A TT RI B U TE S ;
nLength to rozmiar struktury w bajtach, a lpS ecurityD escriptor jest wskaźnikiem do deskryptora zabezpieczeń obiektu. Jeżeli ustalono NULL, zostanie wybrana wartość domyślna. Pole bInheritH andle pozw ala określić, czy zwracany przez C reateF ile() identyfikator jest dziedziczony przy tw orzeniu nowego procesu. W artość TRUE ozna cza, że nowy proces dziedziczy ten identyfikator. Param etr w ejściow y dw C reationD isposition funkcji C re a te F ile () określa rodzaje operacji wykonywanych na pliku i może być reprezentowany przez następujące stałe symboliczne: ♦ CREATE_NEW— utworzenie nowego pliku; funkcja nie będzie wykonana pomyślnie, jeżeli plik ju ż istnieje; ♦ CREATE_ALWAYS — utworzenie nowego pliku niezależnie od tego, czy ten ju ż istnieje; jeżeli plik istnieje, nowy zostanie zapisany na istniejącym; ♦ OPEN_EXISTING — otwarcie istniejącego pliku; jeżeli plik nie istnieje, funkcja nie zostanie wykonana pomyślnie; ♦ OPEN_ALWAYS — otwarcie istniejącego pliku; jeżeli taki nie istnieje, zostanie stworzony identycznie ja k za pom ocą CREATE_NEW; ♦ TRUNCATE_EXISTING — tuż po otw arciu plik zostaje okrojony do rozmiaru 0 bajtów, ale wym agane je st w cześniejsze jego utworzenie przynajmniej z rodzajem dostępu GENERIC_WRITE; funkcja nie będzie wykonana pomyślnie, jeżeli plik nie istnieje. P aram etr w ejściow y dwFlagsA ndA ttributes funkcji C re a te F ile () określa atrybuty i znaczniki wykorzystywane podczas operacji na plikach. Atrybuty m ogą być repre zentowane przez następujące stałe symboliczne: ♦ FILE_ATTRIBUTE_OFFLINE — dane zawarte w pliku nie są bezpośrednio udostępniane; ♦ FILE_ATTRIBUTE_READONLY — plik tylko do odczytu; ♦ FILE_ATTRIBUTE_SYSTEM — plik jest częścią systemu operacyjnego lub jest używany wyłącznie przez system operacyjny; ♦ FILE_ATTRIBUTE_TEMPORARY — plik jest używany do czasowego przechowywania danych i powinien być usunięty, jeżeli nie jest wykorzystywany. Znaczniki są reprezentowane następująco: ♦ FILE_FLAG_WRITE_THROUGH — zawartość pliku zostaje zapisana pośrednio poprzez bufor;
19 4
USB. Praktyczne programowanie z Windows API w C++
♦ FILE_FLAG_OPEN_NO_RECALL — używane dane powinny pozostać na zdalnym dysku; ♦ FILE_FLAG_OPEN_REPARSE_POINT — pozwala na podłączenie do lokalnego, pustego katalogu na partycji NTFS dowolnego katalogu znajdującego się na lokalnym lub zdalnym dysku; ♦ FILE_FLAG_OVERLAPPED — stosowany je st w przypadku asynchronicznych operacji w ykonyw anych przez dłuższy czas przez funkcje ReadFile(), W riteF ile(), ConnectNamedPipe() i TransactNamedPipe(); w tym kontekście musi nastąpić odwołanie do struktury OVERLAPPED zawierającej informacje używane w asynchronicznych operacjach wejścia-wyjścia. Prawidłowo w yw ołana funkcja C re a te F ile () zwraca identyfikator pliku sterownika urządzenia. Jeżeli plik został ju ż otwarty przed wywołaniem funkcji z CREATE_ALWAYS lub OPEN_ALWAYS, przypisanym i do dw CreationD isposition, funkcja G etLastError() zw róci wartość ERROR_ALREADY_EXISTS. Jeżeli plik sterownika nie istnieje, GetLastE rror() zwraca 0. W przypadku gdy funkcja C reateF ile() nie została wykonana po myślnie, należy oczekiwać wartości INVALID_HANDLE_VALUE.
Uwaga
W Windows 98 dostęp do pliku sterownika urządzenia HID (np. pliku sterownika klawiatury) mógł w tym samym czasie uzyskać więcej niż jeden program. Należy być świadomym faktu, że późniejsze wersje systemu Windows uniemożliwiają aplika cjom użytkownika uzyskiwanie swobodnego dostępu do wielu standardowych ste rowników, np. sterowników klawiatury czy myszy. Sterowniki tego typu odblokowują urządzenia w trybie zakazu współdzielenia (ang. exclusive access). Więcej informa cji na ten tem at należy szukać w dokumentacji systemu operacyjnego.
Funkcja CloseHandle() Przed zakończeniem działania program u należy zam knąć otw arty plik sterow nika urządzenia USB i zwolnić obszar pam ięci przydzielony na jego identyfikator, korzy stając z funkcji: BOOL WINAPI C l o s e H a n d l e ( I N
HANDLE h O b j e c t ) ;
Param etr wejściowy hObject jest zwracany przez funkcję C reateF ile() i w pełni iden tyfikuje aktualnie używany sterownik urządzenia.
Przykładowy program środowiska tekstowego Aby odczytać kom pletną listę atrybutów urządzeń, w pełni zidentyfikować ich produ centów i pobrać inne istotne informacje, w pierwszej kolejności należy pobrać ścieżki dostępu do interfejsów (patrz rozdział 5.) i odblokować aktualnie dostępne w systemie sterowniki urządzeń USB. N a rysunku 6.1 pokazano uogólniony diagram czynności dla operacji otw arcia do transm isji aktualnie dostępnych urządzeń USB. Praw idło we pobranie identyfikatora hidDeviceObject zwracanego przez funkcję C reateF ile()
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
195
je st równoznaczne z odblokowaniem wybranego urządzenia. Po odblokowaniu z urzą dzeniem można się komunikować na różne sposoby. W omawianym przykładzie od aktualnie dostępnych urządzeń klasy HID poprzez bufor danych są pobierane dane na tem at ich identyfikatorów VID/PID oraz num eru w ersji i producenta (funkcje HidD_ G etA ttrib u tes(), HidD_GetProductString(), HidD_GetManufacturerString()). Ponad to program pobiera deskryptory urządzenia: fizyczny i określony przez Microsoft (funk cje HidD_GetPhysicalDescriptor() i HidD_GetMsGenreDescriptor()). Po odczytaniu interesujących nas informacji uprzednio odblokowane sterowniki urządzeń USB zo stają zamknięte, a pozostałe zasoby zaalokowane przez program są zwalniane. GetHidGuid(...)
Setu pDi G etCI as sDevs (_
[devicelnfoSet == INVALID_HANDLE_VALUEj ^ disp|ayError(
| ^ SetupDiEnumDevicelnterfaces(...p^ <
W [FALSE]
D
memberlndex++
(i
SetupDiGetDevicelnterfaceDetail(.
(?
[FALSE]
( devicelnterfaceDetailData->DevicePathJ^_
^CreateFile(devicelnterfaceDetailData->DevicePath)^
[hidDeviceObject != INVALID HANDLE VALUE] ^H idD_G etAttributes(...); HidD_GetProductString();
.7^
{^CIoseHandle(hidDeviceObject)^)
SetupDiDestroyDevicelnfoList(...M _
*S>
Rysunek 6.1. Diagram czynności dla operacji enumeracji z wykorzystaniem funkcji CreateFile() N a listingu 6.1 zamieszczono kod modułu usb_R6 1.cpp będącego uszczegółowioną implementacją diagram u z rysunku 6.1. Rysunek 6.2 przedstawia program w trakcie działania.
196
USB. Praktyczne programowanie z Windows API w C++
Listing 6.1. Odblokowanie i odczytanie podstawowych informacji o urządzeniach klasy HID # i n c l u d e #pragma o p t i o n
p u s h -a 1
# i n c lu d e < s e tu p a p i> #pragma o p t i o n
pop
# in c lu d e # in c lu d e # d e fin e b u ff e rL e n g th
256
u s i n g n a m es p ac e s t d ; v o id d is p la y E r r o r ( c o n s t
c h a r * msg){
c o u t << msg << e n d l ; system ("PAUSE"); e x it(0 ); };
/ / -------------------------------------------------------------------t e m p l a t e < c l a s s T> in lin e
v o i d r e l e a s e M e m o r y ( T &x)
{ assert(x de le te
[]
!= N U L L ) ; x;
x = NULL; }
/ / -------------------------------------------------------------------GUID c l a s s G u i d ; HMODULE h H i d L i b ; DWORD m em be rI n de x = 0; DWORD d e v i c e I n t e r f a c e D e t a i l D a t a S i z e ; DWORD r e q u i r e d S i z e ; HDEVINFO d e v i c e I n f o S e t ; S P_ DE V IC E_I NT ER FA CE _D A TA d e v i c e I n t e r f a c e D a t a ; PS P_ DE V IC E_ IN TE RF AC E_ DE TA IL _ DA TA d e v i c e I n t e r f a c e D e t a i l D a t a = NULL;
/ / -------------------------------------------------------------------t y p e d e f s t r u c t _H ID D_ATTRIBUTES { ULONG S i z e ; USHORT V e n d o r I D ; USHORT P r o d u c t I D ; USHORT V e r s i o n N u m b e r ; } HIDD _AT TR IBU TES , *PHIDD_ ATT RIB UT ES;
/ / -------------------------------------------------------------------HANDLE
h i d D e v i c e O b j e c t = IN VALID_HANDLE_VALUE;
HIDD_ATTRIBUTES h i d d A t t r i b u t e s ; w char_t b u ff e r [ b u ff e r L e n g th ] ; i n t m ain () { v o id
(__ s t d c a l l * H i d D _ G e t H i d G u i d ) ( O U T
bool
(__ s t d c a l l * H i d D _ G e t A t t r i b u t e s ) ( I N
LPGUID H i d G u i d ) ; HANDLE H i d D e v i c e O b j e c t ,
OUT PHIDD_ATTRIBUTES A t t r i b u t e s ) ; bool
(__ s t d c a l l * H i d D _ G e t P r o d u c t S t r i n g ) ( I N
HANDLE H i d D e v i c e O b j e c t ,
OUT PVOID IN ULONG bool
(__ s t d c a l l * H i d D _ G e t M a n u f a c t u r e r S t r i n g ) ( I N
B u ffe r, B ufferLeng th);
HANDLE H i d D e v i c e O b j e c t ,
OUT PVOID
B u ffe r,
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
197
IN ULONG BufferLength); bool ( stdcall *HidD_GetPhysicalDescriptor)(IN HANDLE HidDeviceObject, OUT PVOID Buffer, IN ULONG BufferLength); bool ( stdcall *HidD_GetMsGenreDescriptor)(IN HANDLE HidDeviceObject, OUT PVOID Buffer, IN ULONG BufferLength); hHidLib = LoadLibrary("C:\\Windows\\SySWOW64\\HID.DLL"); if (IhHidLib) displayError("Błąd dołączenia biblioteki HID.DLL."); (FARPROC&) HidD_GetHidGuid=GetProcAddress(hHidLib, "HidD_GetHidGuid"); (FARPROC&) HidD_GetAttributes=GetProcAddress(hHidLib, "HidD_GetAttributes") (FARPROC&) HidD_GetProductString=GetProcAddress(hHi dLib, "HidD_GetProductString"); (FARPROC&) HidD_GetManufacturerString=GetProcAddress(hHidLib, "HidD_GetManufacturerString"); (FARPROC&) HidD_GetPhysicalDescriptor=GetProcAddress(hHidLib, "HidD_GetPhysicalDescriptor"); (FARPROC&) HidD_GetMsGenreDescriptor=GetProcAddress(hHidLib, "Hi dD_GetMsGenreDescriptor"); if (!HidD_GetHidGuid || !HidD_GetAttributes || !HidD_GetProductString || !HidD_GetManufacturerString || !HidD_GetPhysicalDescriptor || !HidD_GetMsGenreDescriptor){ FreeLibrary(hHidLib); displayError("Nie znaleziono żadnych funkcji eksportowych.\n"); } HidD_GetHidGuid(&classGuid); deviceInfoSet = SetupDiGetClassDevs(&classGuid, NULL, NULL, DIGCF_PRESENT | DIGCF_INTERFACEDEVICE); if (deviceInfoSet == INVALID_HANDLE_VALUE){ FreeLibrary(hHidLib); displayError("Nie zidentyfikowano podłączonych urządzeń.\n"); devicelnterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); hiddAttributes.Size = sizeof(HIDD_ATTRIBUTES); while(SetupDiEnumDeviceInterfaces(deviceInfoSet, NULL, &classGuid, memberIndex, &devi ceInterfaceData)){ memberIndex++; //inkrementacja numeru interfejsu SetupDiGetDeviceInterfaceDetail(devi ceInfoSet, &deviceInterfaceData, NULL, 0, &deviceInterfaceDetailDataSize, NULL); deviceInterfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA) new DWORD[deviceInterfaceDetailDataSize]; deviceInterfaceDetailData->cbSize=sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); if (!SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &devi ceInterfaceData, deviceInterfaceDetailData, devi ceInterfaceDetailDataSi ze, &requiredSize, NULL)){ releaseMemory(devi ceInterfaceDetailData); SetupDiDestroyDevi ceInfoList(devi ceInfoSet); displayError("Nie można pobrać informacji o in terfejsie.\n"); }
198
USB. Praktyczne programowanie z Windows API w C++
h id D e v ic e O b je c t= C re a te F ile ( d e v ic e In te r fa c e D e ta ilD a ta -> D e v ic e P a th , GENERIC_READ,
FIL E_ SH ARE _RE AD,
N ULL , O PE N _ EX I S TI N G , if ( h id D e v ic e O b je c t
!= IN VAL ID_HANDLE_VALUE)
0,
NULL ) ;
{
c o u t << "\ n" < < d e v i c e I n t e r f a c e D e t a i l D a t a - > D e v i c e P a t h H id D _ G e tM a n u fa c tu re rS trin g (h id D e v ic e O b je c t, p r in t f ( " P r o d u c e n t = % ls\n ",
b u ffer,
<< " \ n " ; b u fferLen g th );
b u ffer);
H id D _ G e tA ttr ib u te s (h id D e v ic e O b je c t,
& h id d A ttrib u te s ) ;
p r in t f ( " V I D / P I D / w e r s j a = % x /% x /% x \n ",h id d A ttrib u te s .V e n d o rID , h id d A ttrib u te s .P ro d u c tID , h i d d A t t r i b u t e s . V e r s io n N u m b e r); H id D _ G e tP ro d u c tS trin g (h id D e v ic e O b je c t, p r in t f( " U r z ^ d z e n ie = % ls\n ",
b u ffer,
H id D _ G e tP h y s ic a lD e s c rip to r (h id D e v ic e O b je c t, p r in t f ( " D e s k r y p t o r f iz y c z n y = % ls\n ",
= % ls\n ",
b u ffer,
b u fferLen g th );
b uffer);
H id D _ G e tM sG e n re D e s c rip to r (h id D e v i c e O b je c t, p rin tf(" D e s k ry p to r M icro so ft
bu fferLen gth );
b uffer);
b u ffer,
b u fferLen g th );
b u ffer);
C lo s e H a n d le (h id D e v ic e O b je c t); } releaseM em ory(d evi c e I n t e r f a c e D e t a ilD a t a ) ;
};llkoniec while S e tu p D iD e s tro y D e v ic e In fo L is t(d e v ic e In fo S e t); F re e L ib ra ry ( h H id L ib ) ; c o u t << e n d l ; system ("PAUSE"); return } ||
0;
--------------------------------------------------------------
E:\U SB.Praktyczne p ro g ra m o w a n ie \R o z d z ia ł 6\R6_i\proj_U SB_R6_1 .exe d#vi d _ lc 4 f& p i d_t>002&mi_01&col 02#8&121b4c0f&0&0001#{4dle5 5 b 2 - f l 6 f - l l c f - 8 8 c b - 001111000030} P ro d u c e n t = V ID /PID/wers j a = lc 4 f/ 2 / 1 1 0 U r z ą d z e n ie = USB K eyko ard D e s k r y p to r f i z y c z n y = D e s k r y p to r M i c r o s o f t = P r e s s any k e y t o
c o n t in u e
.
Rysunek 6.2. Aplikacja proj_USB_R6_1 w trakcie działania
Odczyt danych w form ie raportu Aplikacje użytkownika m ogą korzystać z funkcji HidD_GetXxx() w celu odczytywania (pobrania) raportów w ejściow ych i konfiguracyjnych pochodzących z aktualnie do stępnego w systemie urządzenia. Niemniej funkcji tych powinno się używać jedynie do odczytywania aktualnego stanu urządzenia. Należy zwrócić uwagę na fakt, że wiele urządzeń może nieprawidłowo odpowiadać na cykliczne żądania wysyłane przez funk
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
199
cje HidD_GetXxxx(). W tabeli 6.1 zamieszczono wykaz funkcji, którymi aplikacje użyt kownika m ogą się bezpiecznie posługiwać w trakcie transferu danych z urządzeń przy łączonych do magistrali USB. Tabela 6.1. Funkcje używane podczas transferu raportów wejściowych Typ raportu
Funkcje API WDK/SDK
Typ transferu danych
Wejściowy IN (Input)
HidD GetInputReport()
Kontrolny z rozkazem Get Report
ReadFile()
Przerwaniowy IN
HidD GetFeature()
Kontrolny z rozkazem Get_Report
Konfiguracyjny IN (Feature IN)
Funkcja ReadFile() Zasadniczą częścią kodu realizującego cykliczny odczyt danych pochodzących z ze wnętrznego urządzenia USB będzie zdefiniowana w Windows SDK API funkcja: BOOL R e a d F i l e ( I N
HANDLE
hCommDev,
OUT LPVOID IN DWORD
lp B u ffe r, nNum berOfBytesToRead,
OUT LPDWORD
lp N um berO fBytesRead ,
IN OUT LPOVERLAPPED
lp O v e rla p p e d );
Użycie jej w program ie zapewni odczytanie w szystkich danych przychodzących do urządzenia identyfikowanego przez hCommDev. Parametr lpBuffer jest wskaźnikiem do bufo ra danych, przez który będziemy odczytywać wszystkie informacje, nNumberOfBytesToRead określa liczbę bajtów do odebrania, a lpNumberOfBytesRead wskazuje liczbę rzeczywi ście odebranych bajtów. Pole danych odbieranego pakietu może posiadać rozmiar mniejszy, niż je st to zapisane w polu wMaxPacketSize deskryptora punktu końcow e go — wówczas taki pakiet jest ostatni w serii. Aby kontrolować liczbę bajtów, które zo stały rzeczywiście przeczytane i alokowane w buforze wejściowym odbiornika, funkcja umieszcza j ą w zmiennej lpNumberOfBytesRead, stanowiącej przedostatni parametr. Wskaźnik lpOverlapped zostanie tymczasowo zignorowany. Analizując rysunek 6.2, bez trudu zauważymy, że w zaprezentow anym tu systemie jednym z dostępnych aktualnie urządzeń USB klasy HID je st uniwersalny kontroler gier. Ze względu na to, że zapewne większość Czytelników posiada tego typu (lub po dobne) urządzenie, właśnie je wybierzemy do dalszych testów. Listing 6.2 prezentuje kompletny kod m odułu program u odbierającego z łącza USB raport w ysyłany przez uniwersalny kontroler gier. Z szeregu dostępnych w systemie urządzeń do transmisji danych zostanie wybrane tylko jedno. Wyboru dokonamy w bar dzo prosty sposób, mianowicie na podstawie odczytanego wcześniej VID urządzenia1. Z a pom ocą funkcji s t r s t r ( ) z m odułu cstring.h sprawdzimy, czy ciąg znaków okre ślający identyfikator VID występuje w ścieżce dostępu do interfejsu urządzenia:
1 Jeżeli w systemie występuje więcej urządzeń o zgodnych identyfikatorach VID, to w programie w celu jednoznacznego określenia urządzenia wykonawczego należy również wykorzystać identyfikator PID.
200
USB. Praktyczne programowanie z Windows API w C++
if (NULL != strstr(deviceInterfaceDetailData->DevicePath, "vid_22ba")){ 11... } W omawianym przykładzie zakładamy ponadto, że celem jest synchroniczne i cykliczne odbieranie raportów wejściowych w trybie przerwaniowego transferu danych, tak jak pokazano na listingu 6.2. Koniec cyklicznego odczytu danych nastąpi w momencie, gdy użytkownik naciśnie na panelu sterowniczym urządzenia przycisk Select (przycisk o indeksie 11 — patrz rysunek 6.4). W testowanym urządzeniu naciśnięcie tego przy cisku powoduje umieszczenie w szóstym bajcie (polu) raportu dziesiętnej wartości 64. Zerowy bajt raportu zawiera jego identyfikator (RaportID ). N a rysunku 6.3 zaprezen towano omawiany program w trakcie działania. Listing 6.2. Kod modułu usb_R6_2.cpp #include #pragma option push -a1 #include #pragma option pop #include #include #define bufferLength 32 using namespace std; void displayError(const char* msg){ cout << msg << endl; system("PAUSE"); exit(0); }; II
--------------------------------------------------------------
template inline void releaseMemory(T &x) { assert(x != NULL); delete [] x; x = NULL; } II
--------------------------------------------------------------
GUID classGuid; HMODULE hHidLib; DWORD memberIndex = 0; DWORD devi ceInterfaceDetailDataSize; HDEVINFO deviceInfoSet; SP_DEVICE_INTERFACE_DATA deviceInterfaceData; PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData = NULL; HANDLE hidDeviceObject = INVALID_HANDLE_VALUE; BYTE inputReportBuffer[bufferLength]; IIbufor danych wejściowych DWORD numberOfBytesRead = 0; int main() {
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
201
void ( stdcall *HidD_GetHidGuid)(OUT LPGUID HidGuid); bool ( stdcall*HidD_GetNumInputBuffers)(IN HANDLE HidDeviceObject, OUT PULONG NumberBuffers); hHidLib = LoadLibrary("C:\\Windows\\System32\\HID.DLL"); if (!hHidLib) displayError("Błąd dołączenia biblioteki HID.DLL."); (EARPROC&) Hi dD_GetHidGuid=GetProcAddress(hHidLib, "HidD_GetHi dGuid"); (EARPROC&) Hi dD_GetNumInputBuffers=GetProcAddress(hHidLib, "HidD_GetNumInputBuffers"); if (!HidD_GetHidGuid){ EreeLibrary(hHidLib); displayError("Nie znaleziono żadnych funkcji eksportowych.\n"); } HidD_GetHidGuid(&cl assGuid) ; deviceInfoSet = SetupDiGetClassDevs(&classGuid, NULL, NULL, DIGCE_PRESENT j DIGCE_INTEREACEDEVICE); if (deviceInfoSet == INVALID_HANDLE_VALUE){ EreeLibrary(hHidLib); displayError("Nie zidentyfikowano podłączonych urządzeń.\n"); } deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTEREACE_DATA); while(SetupDiEnumDeviceInterfaces(deviceInfoSet, NULL, &classGuid, memberIndex, &devi ceInterfaceData)){ memberIndex++; llinkrementacja numeru interfejsu SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &devi ceInterfaceData, NULL, 0, &deviceInterfaceDetailDataSize, NULL); deviceInterfaceDetailData = (PSP_DEVICE_INTEREACE_DETAIL_DATA) new DWORD[deviceInterfaceDetailDataSize]; deviceInterfaceDetail Data->cbSize=sizeof(SP_DEVICE_INTEREACE_DETAIL_DATA); if (!SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &devi ceInterfaceData, deviceInterfaceDetailData, devi ceInterfaceDetailDataSi ze, NULL, NULL)){ releaseMemory(devi ceInterfaceDetailData); SetupDiDestroyDevi ceInfoList(devi ceInfoSet); displayError("Nie można pobrać informacji o in terfejsie.\n"); } if (NULL != strstr(deviceInterfaceDetailData->DevicePath, "vid_22ba")){ cout << "\n"<< deviceInterfaceDetailData->DevicePath << "\n"; hidDeviceObject=CreateEile(deviceInterfaceDetailData->DevicePath, GENERIC_READ, EILE_SHARE_READ, NULL,OPEN_EXISTING,0,NULL); if(hidDevi ceObject==INVALID_HANDLE_VALUE) displayError("Nie można otworzyć urządzenia do transm isji."); else break; } releaseMemory(devi ceInterfaceDetailData); };llkoniec while
202
USB. Praktyczne programowanie z Windows API w C++
SetupDiDestroyDeviceInfoList(deviceInfoSet); ULONG numberBuffers; //pobranie maks. długości raportu wejściowego HidD_GetNumInputBuffers(hidDevi ceObject, &numberBuffers); printf("liczba buforów wejściowych = %d\n",numberBuffers); while(true) { //cykliczny odczyt danych memset(&inputReportBuffer, 0x00, sizeof(inputReportBuffer)); ReadFile(hidDevi ceObject, inputReportBuffer, sizeof(inputReportBuffer), &numberOfBytesRead, NULL); printf("%d %d %d %d %d %d %d %d\n", inputReportBuffer[0], inputReportBuffer[1], inputReportBuffer[2], inputReportBuffer[3], inputReportBuffer[4], inputReportBuffer[5], inputReportBuffer[6], inputReportBuffer[7]); //Odczyt raportu wejściowego kończy naciśnięcie przycisku Select / (11) //uniwersalnego kontrolera gier if(inputReportBuffer[6]==64) break; } CloseHandle(hidDevi ceObject); FreeLibrary(hHidLib); cout << endl; system("PAUSE"); return 0; } / / --------------------------------------------------------------------
Rysunek 6.3. Aplikacja proj_USB_R6_2 w trakcie cyklicznego odczytywania raportów pochodzących z uniwersalnego kontrolera gier
USB.Praktyczne programowame\Rozdział 6\R6_2\proj_USB_R6_2.exe V \ ? \ h id # v i d_ 2 2ba&pi d_010S#8&5 9 f 5 d 85 &M0000#{4 d le 5 5 b2 - f 1 6 f - l l c f - 8 8 c b - 001111000030 l i c z b a b u f o r ow w e jśc io w y ch = 32 0 129 127 128 128 0 2 0 0 129 127 128 128 0 0 0 0 129 127 128 128 0 8 0 0 129 127 128 128 0 0 0 0 129 127 128 128 0 32 0 0 129 127 128 128 0 0 0 0 129 127 128 128 0 32 0 0 129 127 128 128 0 0 0 0 129 127 128 128 5 0 0 0 129 127 128 128 0 0 0 7 0 0 0 129 127 128 128 0 129 127 128 128 0 0 0 0 129 127 128 128 1 0 0 0 129 127 128 128 0 0 0 0 129 127 128 128 0 16 0 0 129 127 128 128 0 0 0 0 129 127 128 128 64 0 0 0 129 127 128 128 0 0 0 0 129 127 128 128 16 0 0 0 129 127 128 128 0 0 0 0 129 127 128 128 0 128 0 0 129 127 128 128 0 0 0 64 0 129 127 128 128 0 0 Aby kontynuow ać,
n a c iś n ij
dow oln y k la w is z .
.
. _
W ynik działania aplikacji proj_USB_R6_2 można w prosty sposób zweryfikować, po równując go z dostępnymi z poziomu Panelu sterowania (rysunek 6.4) właściwościa mi uniwersalnego kontrolera gier. N a rysunku 6.5 pokazano form at raportu wejścio w ego, za pom ocą którego urządzenie (a dokładniej jego sterownik) kom unikuje się z aplikacj ą użytkownika. W arto zw rócić uw agę, że konstruując bardziej zaaw ansow ane aplikacje, możemy w trakcie odczytu zaw artości bufora w ejściow ego cyklicznie w yw oływać funkcję
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
203
Rysunek 6.4. Właściwości uniwersalnego kontrolera gier Raport ID Wartość X Wartość Y Wartość 2
Obrót (rot)
Przycisk
Przycisk
Rysunek 6.5. Format raportu wejściowego dla typowego uniwersalnego kontrolera gier HidP_GetUsagesEx() (patrz rozdział 4.) w celu określenia numeru aktualnie naciśnię tego przycisku znajdującego się na panelu sterowniczym urządzenia. W takiej sytuacji nie trzeba znać zawartości raportu wejściowego, aby w odpowiedzi na naciśnięcie w y branego przycisku program wykonał jakąś akcję, na przykład zakończył odczyt danych.
Odczyt długości bufora danych W kodzie z listingu 6.2 rozmiar bufora danych wejściowych w funkcji ReadFile() zo stał ustalony arbitralnie. Istnieją jednak sytuacje, w których takie postępowanie może się okazać mało efektywne. Projektując oprogramowanie przeznaczone do współpra cy z konkretną klasą urządzeń, zawsze można skorzystać z zasobów struktur HIDP_CAPS, HIDP_PREPARSED_DATA i rodzin współpracujących funkcji. W obecnie omawianym przy kładzie aktualny rozmiar bufora danych wejściowych jest odczytywany poprzez pole InputReportByteLength struktury HIDP_CAPS. Warto zwrócić uwagę, że ten sposób okre ślania rozmiaru bufora danych wymaga użycia przynajmniej funkcji: HidP_GetCaps(), HidD_GetPreparsedData() i HidD_FreePreparsedData(), tak ja k zaprezentowano na li stingu 6.3.
20 4
USB. Praktyczne programowanie z Windows API w C++
Listing 6.3. Kod modułu usb_R6_3.cpp # i n c l u d e #pragma o p t i o n
p u s h -a 1
# i n c lu d e < s e tu p a p i> #pragma o p t i o n
pop
# in c lu d e # in c lu d e u s i n g n a m es p ac e s t d ; v o id d is p la y E r r o r ( c o n s t
c h a r * msg){
c o u t << msg << e n d l ; system ("PAUSE"); e x it(0 ); };
/ / ------------------------------------------------------------------t e m p l a t e < c l a s s T> in lin e
v o i d r e l e a s e M e m o r y ( T &x)
{ assert(x de le te
!= N U L L ) ;
[]
x;
x = NULL; }
/ / ------------------------------------------------------------------t y p e d e f USHORT USAGE, *PUSAGE; t y p e d e f s t r u c t _HIDP_PREPARSED_DATA *PHIDP_PREPARSED_DATA; PHIDP_PREPARSED_DATA p r e p a r s e d D a t a ; t y p e d e f s t r u c t _ HI D P_C A PS { USAGE U sa g e ; USAGE U s a g e P a g e ; USHORT I n p u t R e p o r t B y t e L e n g t h ; USHORT O u t p u t R e p o r t B y t e L e n g t h ; USHORT F e a t u r e R e p o r t B y t e L e n g t h ; USHORT R e s e r v e d [ 1 7 ] ; USHORT N u m b e r L i n k C o l l e c t i o n N o d e s ; USHORT N u m b e r I n p u t B u t t o n C a p s ; USHORT N u m b e r I n p u t V a l u e C a p s ; USHORT N u m b e r I n p u t D a t a I n d i c e s ; USHORT N u m b e r O u t p u t B u t t o n C a p s ; USHORT N u m b e r O u t p u t V a l u e C a p s ; USHORT N u m b e r O u t p u t D a t a I n d i c e s ; USHORT N u m b e r F e a t u r e B u t t o n C a p s ; USHORT N u m b e r F e a t u r e V a l u e C a p s ; USHORT N u m b e r F e a t u r e D a t a I n d i c e s ; } HID P_ CA PS ,
* PH ID P_ CA PS;
/ / ------------------------------------------------------------------t y p e d e f s t r u c t _HID P_LINK_C OLLEC TIO N_NO DE USAGE
L in kU sa g e ;
USAGE
L in kU sage Page;
USHORT
Parent;
USHORT
N u m b e rO fC h ild re n ;
USHORT
N e x tS ib lin g ;
USHORT
{
F irs tC h ild ;
ULONG
C o lle c tio n T y p e :
ULONG
I s A lia s :
8;
1;
ULONG
Reserved:
PVOID
U serContext;
23;
} HIDP_LINK _C OLL ECT IO N_ NOD E,
*PHIDP_LINK _CO LLECT ION _NO DE ;
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
205
I I --------------------------------------------------------------------------------------------
HIDP_CAPS capabilities; PHIDP_LINK_COLLECTION_NODE linkCollectionNodes; GUID classGuid; HMODULE hHidLib; DWORD memberIndex = 0; DWORD devi ceInterfaceDetailDataSize; HDEVINFO deviceInfoSet; SP_DEVICE_INTERFACE_DATA deviceInterfaceData; PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData = NULL; HANDLE hidDeviceObject = INVALID_HANDLE_VALUE; BYTE *inputReportBuffer; //bufor danych wejściowych DWORD numberOfBytesRead = 0; int main() { void (_stdcall *HidD_GetHidGuid)(OUT LPGUID HidGuid); long (_stdcall* HidP_GetCaps)(IN PHIDP_PREPARSED_DATA PreparsedData, OUT PHIDP_CAPS Capabilities); long ( stdcall* HidP_GetLinkCollectionNodes)(OUT PHIDP_LINK_COLLECTION_NODE\ LinkCollectionNodes, IN OUT PULONG LinkCollectionNodesLength, IN PHIDP_PREPARSED_DATA PreparsedData); bool (_stdcall* HidD_GetPreparsedData)(IN HANDLE HidDeviceObject, OUT PHIDP_PREPARSED_DATA *PreparsedData); bool (_stdcall* HidD_FreePreparsedData)(IN PHIDP_PREPARSED_DATA PreparsedData); hHidLib = LoadLibrary("C:\\Windows\\System32\\HID.DLL"); if (!hHidLib) displayError("Błąd dołączenia biblioteki HID.DLL."); (FARPROC&) Hi dD_GetHidGuid=GetProcAddress(hHidLib, "HidD_GetHi dGuid"); (FARPROC&) Hi dP_GetCaps=GetProcAddress(hHidLib, "HidP_GetCaps"); (FARPROC&) Hi dD_GetPreparsedData=GetProcAddress(hHidLib, "HidD_GetPreparsedData"); (FARPROC&) HidD_FreePreparsedData=GetProcAddress(hHidLib, "HidD_FreePreparsedData"); (FARPROC&) Hi dP_GetLinkCollectionNodes=GetProcAddress(hHidLib, "HidP_GetLinkCollectionNodes") ; if (!HidD_GetHidGuid || !HidP_GetCaps || !HidD_GetPreparsedData || !HidD_FreePreparsedData || !HidP_GetLinkCollectionNodes){ FreeLibrary(hHidLib); displayError("Nie znaleziono żadnych funkcji eksportowych.\n"); } HidD_GetHidGuid(&cl assGuid); deviceInfoSet = SetupDiGetClassDevs(&classGuid, NULL, NULL, DIGCF_PRESENT | DIGCF_INTERFACEDEVICE);
206
USB. Praktyczne programowanie z Windows API w C++
if (deviceInfoSet == INVALID_HANDLE_VALUE){ FreeLibrary(hHidLib); displayError("Nie zidentyfikowano podłączonych urządzeń.\n"); } deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); while(SetupDiEnumDeviceInterfaces(deviceInfoSet, NULL, &classGuid, memberIndex, &devi ceInterfaceData)){ memberIndex++; //inkrementacja numeru interfejsu SetupDiGetDeviceInterfaceDetail(devi ceInfoSet, &deviceInterfaceData, NULL, 0, &deviceInterfaceDetailDataSize, NULL); deviceInterfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA) new DWORD[deviceInterfaceDetailDataSize]; deviceInterfaceDetailData->cbSize=sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); if (!SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &devi ceInterfaceData, deviceInterfaceDetailData, devi ceInterfaceDetailDataSi ze, NULL, NULL)){ releaseMemory(devi ceInterfaceDetailData); SetupDiDestroyDevi ceInfoList(devi ceInfoSet); displayError("Nie można pobrać informacji o in terfejsie.\n"); } if (NULL != strstr(deviceInterfaceDetailData->DevicePath, "vid_22ba")){ cout << "\n"<< deviceInterfaceDetailData->DevicePath << "\n"; hidDeviceObject=CreateFile(deviceInterfaceDetailData->DevicePath, GENERIC_READ, FILE_SHARE_READ, NULL,OPEN_EXISTING,0,NULL); if(hi dDeviceObject==INVALID_HANDLE_VALUE) displayError("Nie można otworzyć urządzenia do transm isji."); else break; } releaseMemory(devi ceInterfaceDetailData); }; //koniec while SetupDiDestroyDeviceInfoList(deviceInfoSet); if(HidD_GetPreparsedData(hidDeviceObject, &preparsedData)){ HidP_GetCaps(preparsedData, &capabilities); printf("Usage=%d\nUsagePage=%d\nInputReportByteLength=%d\n" "OutputReportByteLength=%d\nFeatureReportByteLength=%d\n" "NumberInputButtonCaps=%d\nNumberInputValueCaps=%d\n\n", capabilities.Usage,capabilities.UsagePage, capabilities.InputReportByteLength, capabilities.OutputReportByteLength, capabilities.FeatureReportByteLength, capabilities.NumberInputButtonCaps, capabilities.NumberInputValueCaps); PULONG linkCollectionNodesLength; linkCollectionNodes = new \ HIDP_LINK_COLLECTION_NODE[capabilities.NumberLinkCollectionNodes]; HidP_GetLinkCollectionNodes(linkCollectionNodes, linkCollectionNodesLength, preparsedData);
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
207
printf("LinkUsage=%d\nLinkUsagePage=%d\nParent=%d\n" "NumberOfChildren=%d\nCollectionType=%d\n\n", linkCollectionNodes->LinkUsage, linkCollectionNodes->LinkUsagePage, linkCollectionNodes->Parent, linkCollectionNodes->NumberOfChildren, linkCollectionNodes->CollectionType); inputReportBuffer = new BYTE[capabilities.InputReportByteLength]; while(true) { l/cykliczny odczyt danych z wybranego urządzenia memset(inputReportBuffer, 0x00, capabilities.InputReportByteLength); ReadFile(hidDeviceObject, inputReportBuffer, capabilities.InputReportByteLength, &numberOfBytesRead, NULL); if(numberOfBytesRead==capabilities.InputReportByteLength){ for(USHORT i=0; i
W trakcie testow ania pow yższego kodu łatwo zauważymy, że program dodatkowo odczytuje wybrane właściwości aktualnie testowanego urządzenia. Sposób wykorzy stania funkcji HidP_GetLinkCollectionNodes() do pobierania wybranych pól struktury HIDP_LINK_COLLECTION_NODE pokazuje rysunek 6.6.
Funkcja HidD_GetInputReport() W przypadku gdy testowane urządzenie mogłoby się posługiwać kontrolnym transfe rem danych dla raportów wejściowych (tabela 6.1), w celu odczytania aktualnej po staci raportu wejściowego można użyć funkcji HidD_GetInputReport(). Pokazuje to fragment kodu z listingu 6.4.
208
USB. Praktyczne programowanie z Windows API w C++
Rysunek 6.6. Aplikacja proj_USB_R6_3 w trakcie działania
USB.Praktyczne program owanie\Rozdział 6\R6_3\proj_USB_R6_3.exe \ \ ? \ h i d # vid_22ba& pid_0108# 845 9 f 5 d85&0&0000#{4dle5 5 b 2 - f l 6 f -1 1 c f - 8 8 c b - 0 0 1 1 1 1 000030} Usage=4 U sa ge P a ge = l I n p u t R e p o r t B y te L en g t h=7 O u tp u tR e p o rtB y te L e n g th = 5 F e a t u r e R e p o r t B y t eL en g t h=0 NumberI n p u t B u t to n C a p s = l NumberI n p u t V a lueCaps=5 L in k U sa g e = 4 L i nHdJsagePage=l P a re n t= 0 N u m b erO fC h i1d re n = l C o l i e c t i onType= l 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
129 129 129 129 129 129 129 130 129 129 129 129 129 129 130 129 129
127 127 127 127 127 127 127 127 127 127 127 127 127 127 127 127 127
128 128 128 128 128 128 128 128 128 128 128 128 128 128 128 128 128
128 128 128 128 128 128 128 128 128 128 128 128 128 128 128 128 128
A b y k o n ty n u o w a ć ,
32 0 0 0 128 0 0 0 32 0 0 0 16 0 16 0 16 0 0 0 64 0 0 0 16 0 0 0 0 0 0 0 0 64 n a c iś n ij
d o w o ln y k la w is z
Listing 6.4. Przykład wykorzystania funkcji HidD_GetInputReport() / / -------------------------------------------------------------------BYTE *inputReportBuffer; //bufor danych wejściowych //... bool ( stdcall *HidD_GetInputReport)(IN HANDLE HidDeviceObject, IN OUT PVOID ReportBuffer, IN ULONG ReportBufferLength); //... (FARPROC&) HidD_GetInputReport=GetProcAddress(hHidLib, "HidD_GetInputReport"); //... inputReportBuffer = new BYTE[capabilities.InputReportByteLength]; HidD_GetInputReport(hidDeviceObject, inputReportBuffer, capabilities.InputReportByteLength); printf("%d %d %d %d %d %d %d\n", inputReportBuffer[0], inputR eportB uffer[1],//....); / / --------------------------------------------------------------------
Odczyt własności przycisków N a listingu 6.5 pokazano przykład wykorzystania funkcji HidP_GetButtonCaps() i struk tury HIDP_BUTTON_CAPS przechowującej informacje na temat własności elementów typu Button dla raportu wejściowego HidP_Input, którym aktualnie posługuje się dostępne w systemie urządzenie USB. Przykład ten może być traktowany jako uzupełnienie kodu z listingu 6.4. N a rysunku 6.7 pokazano wynik działania omawianego kodu.
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
Listing 6.5. Przykład wykorzystania funkcji HidP_GetButtonCaps() #include #pragma option push -a1 #include #pragma option pop #include #include #define HID_USAGE_PAGE_BUTTON ((USAGE) 0x09) #define HID_USAGE_PAGE_GENERIC ((usage) 0x0l) / / hidusage.h #define HID_USAGE_GENERIC_JOYSTICK ((USAGE) 0x04) using namespace std; void displayError(const char* msg){ cout << msg << endl; system("PAUSE"); exit(0); }; / / -------------------------------------------------------------------template inline void releaseMemory(T &x) { assert(x != NULL); delete [] x; x = NULL; } / / -------------------------------------------------------------------typedef USHORT USAGE, *PUSAGE; typedef struct _HIDP_PREPARSED_DATA *PHIDP_PREPARSED_DATA; / / typedef unsigned int NTSTATUS; PHIDP_PREPARSED_DATA preparsedData; typedef struct _HIDP_CAPS { USAGE Usage; USAGE UsagePage; USHORT InputReportByteLength; USHORT OutputReportByteLength; USHORT FeatureReportByteLength; USHORT Reserved[17]; USHORT NumberLinkCollectionNodes; USHORT NumberInputButtonCaps; USHORT NumberInputValueCaps; USHORT NumberInputDataIndices; USHORT NumberOutputButtonCaps; USHORT NumberOutputValueCaps; USHORT NumberOutputDataIndices; USHORT NumberFeatureButtonCaps; USHORT NumberFeatureValueCaps; USHORT NumberFeatureDataIndices; } HIDP_CAPS, *PHIDP_CAPS; / / -------------------------------------------------------------------HIDP_CAPS capabilities; GUID classGuid; HMODULE hHidLib;
209
210
USB. Praktyczne programowanie z Windows API w C++
DWORD memberIndex = 0; DWORD devi ceInterfaceDetailDataSize; HDEVINFO deviceInfoSet; SP_DEVICE_INTERFACE_DATA deviceInterfaceData; PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData = NULL; HANDLE hidDeviceObject = INVALID_HANDLE_VALUE;
U --------------------------------------------------------------
typedef enum _HIDP_REPORT_TYPE { //hidpi.h HidP_Input, HidP_Output, HidP_Feature } HIDP_REPORT_TYPE; / / -------------------------------------------------------------------typedef struct _HIDP_BUTTON_CAPS { USAGE UsagePage; UCHAR ReportID; BOOLEAN IsAlias; USHORT BitField; USHORT LinkCollection; USAGE LinkUsage; USAGE LinkUsagePage; BOOLEAN IsRange; BOOLEAN IsStringRange; BOOLEAN IsDesignatorRange; BOOLEAN IsAbsolute; ULONG Reserved[10]; union { struct { USAGE UsageMin, UsageMax; USHORT StringMin, StringMax; USHORT DesignatorMin, DesignatorMax; USHORT DataIndexMin, DataIndexMax; } Range; struct { USAGE Usage, Reserved1; USHORT StringIndex, Reserved2; USHORT DesignatorIndex, Reserved3; USHORT DataIndex, Reserved4; } NotRange; }; } HIDP_BUTTON_CAPS, *PHIDP_BUTTON_CAPS; / / -------------------------------------------------------------------int main() { unsigned int ( stdcall *HidP_GetButtonCaps)(IN HIDP_REPORT_TYPE ReportType, OUT PHIDP_BUTTON_CAPS ButtonCaps,IN OUT PULONG ButtonCapsLength, IN PHIDP_PREPARSED_DATA PreparsedData); void
(_stdcall *HidD_GetHidGuid)(OUT LPGUID HidGuid);
long
(_stdcall* HidP_GetCaps)(IN PHIDP_PREPARSED_DATA PreparsedData, OUT PHIDP_CAPS Capabilities);
bool
(_stdcall* HidD_GetPreparsedData)(IN HANDLE HidDeviceObject, OUT PHIDP_PREPARSED_DATA *PreparsedData);
bool
(_stdcall* HidD_FreePreparsedData)(IN PHIDP_PREPARSED_DATA PreparsedData);
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
211
hHidLib = LoadLibrary("C:\\Windows\\System32\\HID.DLL"); if (IhHidLib) displayError("Błąd dołączenia biblioteki HID.DLL."); (FARPROC&) Hi dD_GetHidGuid=GetProcAddress(hHidLib, "HidD_GetHi dGuid"); (FARPROC&) Hi dP_GetCaps=GetProcAddress(hHidLib, "HidP_GetCaps"); (FARPROC&) Hi dD_GetPreparsedData=GetProcAddress(hHidLib, "HidD_GetPreparsedData"); (FARPROC&) Hi dD_FreePreparsedData=GetProcAddress(hHidLib, "HidD_FreePreparsedData"); (FARPROC&) Hi dP_GetButtonCaps=GetProcAddress(hHidLib, "HidP_GetButtonCaps"); if (!HidD_GetHidGuid || !HidP_GetCaps || !HidD_GetPreparsedData || !HidP_GetButtonCaps || !HidD_FreePreparsedData){ FreeLibrary(hHidLib); displayError("Nie znaleziono żadnych funkcji eksportowych.\n"); } HidD_GetHidGuid(&classGuid); devicelnfoSet = SetupDiGetClassDevs(&classGuid, NULL, NULL, DIGCF_PRESENT | DIGCF_INTERFACEDEVICE); if (deviceInfoSet == INVALID_HANDLE_VALUE){ FreeLibrary(hHidLib); displayError("Nie zidentyfikowano podłączonych urządzeń.\n"); } deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); while(SetupDiEnumDeviceInterfaces(deviceInfoSet, NULL, &classGuid, memberIndex, &devi ceInterfaceData)){ memberIndex++; //inkrementacja numeru interfejsu SetupDiGetDeviceInterfaceDetail(devi ceInfoSet, &deviceInterfaceData, NULL, 0, &deviceInterfaceDetailDataSize, NULL); deviceInterfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA) new DWORD[deviceInterfaceDetailDataSize]; deviceInterfaceDetailData->cbSize=sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); if (!SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &devi ceInterfaceData, deviceInterfaceDetailData, devi ceInterfaceDetailDataSi ze, NULL, NULL)){ releaseMemory(devi ceInterfaceDetailData); SetupDiDestroyDevi ceInfoList(deviceInfoSet); displayError("Nie można pobrać informacji o in terfejsie.\n"); } if (NULL != strstr(deviceInterfaceDetailData->DevicePath, "vid_22ba")){ cout << "\n"<< deviceInterfaceDetailData->DevicePath << "\n"; hidDeviceObject=CreateFile(deviceInterfaceDetailData->DevicePath, GENERIC_READ, FILE_SHARE_READ, NULL,OPEN_EXISTING,0,NULL); if(hidDeviceObject==INVALID_HANDLE_VALUE) displayError("Nie można otworzyć urządzenia do transm isji."); else break; } releaseMemory(devi ceInterfaceDetailData);
212
USB. Praktyczne programowanie z Windows API w C++
}; //koniec while SetupDiDestroyDeviceInfoList(deviceInfoSet); if(HidD_GetPreparsedData(hidDeviceObject, &preparsedData)){ HidP_GetCaps(preparsedData, &capabilities); PULONG buttonCapsLength; HIDP_BUTTON_CAPS *buttonCaps = new \ HIDP_BUTTON_CAPS[capabilities.NumberInputButtonCaps]; HidP_GetButtonCaps(HidP_Input, buttonCaps, buttonCapsLength, preparsedData); for(USHORT i = 0; i Button Page\n"); break; /* */ default : p rin tf(" ..." ); } switch (buttonCaps[i].LinkUsage) { case HID_USAGE_GENERIC_JOYSTICK: printf("\Link Usage -> Generic Joystick Page\n"); break; /* */ default : p rin tf(" ..." ); } switch (buttonCaps[i].LinkUsagePage) { case HID_USAGE_PAGE_GENERIC: printf("\Link Usage Page -> Generic Desktop Page\n"); break; /* */ default : p rin tf(" ..." ); } } else printf("Not Range Usage%d\n\n", buttonCaps[i].NotRange.Usage); }
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
213
releaseMemory(buttonCaps); } FreeLibrary(hHidLib); cout << endl; system("PAUSE"); return 0; } / / ------------------------------------
Rysunek 6.7. Odczyt wybranych własności elementów sterujących o charakterze dyskretnym
USŁPraktyczne piogramawanie\Fta£dział 6VR(j 3\proj JS 3 R6 3aeKe
[ □
| Ig]
k \ ? \ h i d# vid_22ba&pid_0108# 8&59f5 d85&0&0000#{4dle5 5 b 2 - f l 6 f - l l c f - 8 8 c b - 001111000030} B u tto n C a p s 0 UsagePage 9 B u tto n C a p s 0 R eportID = x0 B u tto n C a p s 0 .I s A l i a s = x0 B u tto n C a p s 0 B i t F i e l d = 2 B u tto n C a p s 0 L in k C o lle c t io n = x0 B u tto n C a p s 0 .L in k U sa g e = x4 B u tto n C a p s 0 LinkU sag ePag e= 0x1 B u tto n C a p s 0 IsRange= 1
. . . . . .
Usages m in , max 1 . . 1 2 p a t a in d e x m in , max 5 .. 1 6 Usage Page -> B u tto n Page L in k Usage -> G e n e r ic J o y s t i c k Page L in k Usage Page -> G e n e r ic D e sk to p Page A by ko ntynu ow ać, n a c i ś n i j
dow oln y k la w is z .
.
. _
Odczyt własności wartości Kod z listingu 6.6 to przykład wykorzystania funkcji HidP_GetValueCaps() i struktury HIDP_VALUE_CAPS przechowującej informacje na temat własności elementów typu Value dla raportu wyjściowego HidP_Output, którym aktualnie posługuje się dostępne w sys temie urządzenie USB. Przykład ten może być traktowany jako uzupełnienie kodu z li stingu 6.4. N a rysunku 6.8 pokazano wynik działania omawianego kodu. Listing 6.6. Przykład wykorzystania funkcji HidP_GetValueCaps() #include #pragma option push -a1 #include #pragma option pop #include #include #define HID_USAGE_PAGE_GENERIC ((USAGE) 0x01) //hidusage.h #define HID_USAGE_PAGE_LED ((uSAGe) 0x08) #define HID_USAGE_GENERIC_JOYSTICK (( uSAGe) 0x04) using namespace std; void displayError(const char* msg){ cout << msg << endl; system("PAUSE"); exit(0); }; / / -------------------------------------------------------------------template
21 4
USB. Praktyczne programowanie z Windows API w C++
inline void releaseMemory(T &x) { assert(x != NULL); delete [] x; x = NULL; } //
--------------------------------------------------------------
typedef USHORT USAGE, *PUSAGE; typedef struct _HIDP_PREPARSED_DATA *PHIDP_PREPARSED_DATA; //typedef unsigned int NTSTATUS; PHIDP_PREPARSED_DATA preparsedData; typedef struct _HIDP_CAPS { //hidpi.h USAGE Usage; USAGE UsagePage; USHORT InputReportByteLength; USHORT OutputReportByteLength; USHORT FeatureReportByteLength; USHORT Reserved[17]; USHORT NumberLinkCollectionNodes; USHORT NumberInputButtonCaps; USHORT NumberInputValueCaps; USHORT NumberInputDataIndices; USHORT NumberOutputButtonCaps; USHORT NumberOutputValueCaps; USHORT NumberOutputDataIndices; USHORT NumberFeatureButtonCaps; USHORT NumberFeatureValueCaps; USHORT NumberFeatureDataIndices; } HIDP_CAPS, *PHIDP_CAPS; HIDP_CAPS capabilities; GUID classGuid; HMODULE hHidLib; DWORD memberIndex = 0; DWORD devi ceInterfaceDetailDataSize; HDEVINFO deviceInfoSet; SP_DEVICE_INTERFACE_DATA deviceInterfaceData; PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData = NULL; HANDLE hidDeviceObject = INVALID_HANDLE_VALUE; / / -------------------------------------------------------------------typedef enum _HIDP_REPORT_TYPE { //hidpi.h HidP_Input, HidP_Output, HidP_Feature } HIDP_REPORT_TYPE; / / -------------------------------------------------------------------typedef struct _HIDP_VALUE_CAPS { //hidpi.h USAGE UsagePage; UCHAR ReportID; BOOLEAN IsAlias; USHORT BitField; USHORT LinkCollection; USAGE LinkUsage; USAGE LinkUsagePage; BOOLEAN IsRange; BOOLEAN IsStringRange; BOOLEAN IsDesignatorRange;
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
215
BOOLEAN IsAbsolute; BOOLEAN HasNull; UCHAR Reserved; USHORT BitSize; USHORT ReportCount; USHORT Reserved2[5]; ULONG UnitsExp; ULONG Units; LONG LogicalMin, LogicalMax; LONG PhysicalMin, PhysicalMax; union { struct { USAGE UsageMin, UsageMax; USHORT StringMin, StringMax; USHORT DesignatorMin, DesignatorMax; USHORT DataIndexMin, DataIndexMax; } Range; struct { USAGE Usage, Reserved1; USHORT StringIndex, Reserved2; USHORT DesignatorIndex, Reserved3; USHORT DataIndex, Reserved4; } NotRange; }; } HIDP_VALUE_CAPS, * PHIDP_VALUE_CAPS;
U -------------------------------------------------------------int main() { unsigned int ( stdcall *HidP_GetValueCaps)(IN HIDP_REPORT_TYPE ReportType, OUT PHIDP_VALUE_CAPS ValueCaps,IN OUT PULONG ValueCapsLength, IN PHIDP_PREPARSED_DATA PreparsedData); void (_stdcall *HidD_GetHidGuid)(OUT LPGUID HidGuid); long (_stdcall* HidP_GetCaps)(IN PHIDP_PREPARSED_DATA PreparsedData, OUT PHIDP_CAPS Capabilities); bool (_stdcall* HidD_GetPreparsedData)(IN HANDLE HidDeviceObject, OUT PHIDP_PREPARSED_DATA *PreparsedData); bool (_stdcall* HidD_FreePreparsedData)(IN PHIDP_PREPARSED_DATA PreparsedData); hHidLib = LoadLibrary("C:\\Windows\\System32\\HID.DLL"); i f (!hHidLib) displayError("Błąd dołączenia biblioteki HID.DLL."); (FARPROC&) Hi dD_GetHidGuid=GetProcAddress(hHidLib, "HidD_GetHi dGuid"); (FARPROC&) Hi dP_GetCaps=GetProcAddress(hHidLib, "HidP_GetCaps"); (FARPROC&) Hi dD_GetPreparsedData=GetProcAddress(hHidLib, "HidD_GetPreparsedData"); (FARPROC&) Hi dD_FreePreparsedData=GetProcAddress(hHidLib, "HidD_FreePreparsedData"); (FARPROC&) Hi dP_GetValueCaps=GetProcAddress(hHidLib, "HidP_GetValueCaps"); if (!HidD_GetHidGuid || !HidP_GetCaps || !HidD_GetPreparsedData || !HidD_FreePreparsedData || !HidP_GetValueCaps){ FreeLibrary(hHidLib); displayError("Nie znaleziono żadnych funkcji eksportowych.\n"); }
216
USB. Praktyczne programowanie z Windows API w C++
HidD_GetHidGuid(&classGuid); deviceInfoSet = SetupDiGetClassDevs(&classGuid, NULL, NULL, DIGCF_PRESENT | DIGCF_INTERFACEDEVICE); if (deviceInfoSet == INVALID_HANDLE_VALUE){ FreeLibrary(hHidLib); displayError("Nie zidentyfikowano podłączonych urządzeń.\n"); } deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); while(SetupDiEnumDeviceInterfaces(deviceInfoSet, NULL, &classGuid, memberIndex, &devi ceInterfaceData)){ memberIndex++; //inkrementacja numeru interfejsu SetupDiGetDeviceInterfaceDetail(devi ceInfoSet, &deviceInterfaceData, NULL, 0, &deviceInterfaceDetailDataSize, NULL); deviceInterfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA) new DWORD[deviceInterfaceDetailDataSize]; deviceInterfaceDetailData->cbSize=sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); if (!SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &devi ceInterfaceData, deviceInterfaceDetailData, devi ceInterfaceDetailDataSi ze, NULL, NULL)){ releaseMemory(devi ceInterfaceDetailData); SetupDiDestroyDevi ceInfoList(devi ceInfoSet); displayError("Nie można pobrać informacji o in terfejsie.\n"); } if (NULL != strstr(deviceInterfaceDetailData->DevicePath, "vid_22ba")){ cout << "\n"<< deviceInterfaceDetailData->DevicePath << "\n"; hidDeviceObject=CreateFile(deviceInterfaceDetailData->DevicePath, GENERIC_READ, FILE_SHARE_READ, NULL,OPEN_EXISTING,0,NULL); if(hidDeviceObject==INVALID_HANDLE_VALUE) displayError("Nie można otworzyć urządzenia do transm isji."); else break; } releaseMemory(devi ceInterfaceDetailData); }; //koniec while SetupDiDestroyDeviceInfoList(deviceInfoSet); if(HidD_GetPreparsedData(hidDeviceObject, &preparsedData)){ HidP_GetCaps(preparsedData, &capabilities); PULONG valueCapsLength; HIDP_VALUE_CAPS *valueCaps = new \ HIDP_VALUE_CAPS[capabili ties.NumberOutputValueCaps]; HidP_GetValueCaps(HidP_Output, valueCaps, valueCapsLength, preparsedData); for(USHORT i = 0; i
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
p rin tf( " V a lu e C a p s [ % d ] .L in k U s a g e =
x% x\n",
p rin tf( " V a lu e C a p s [ % d ] .L in k U s a g e P a g e =
i,
v a lu e C a p s[i].L in k U s a g e );
0x% x\n",
i,
va lu e C a p s[i].L in k U s a g e P a g e ); p r in t f ( " V a lu e C a p s [ % d ] . I s R a n g e = %d\n",
i,
v a lu e C a p s[i].IsR a n g e );
p r i n t f ( " V a l u e C a p s [ % d ] . I s S t r i n g R a n g e = %d\n",
i,
v a lu e C a p s [i].Is S trin g R a n g e ); p r i n t f ( " V a l u e C a p s [ % d ] . I s D e s i g n a t o r R a n g e = % d \n ",
i,
v a lu e C a p s [i].Is D e s ig n a to rR a n g e ); p r i n t f ( " V a l u e C a p s [ % d ] . I s A b s o l u t e = %d\n",
i,
p r in t f ( " V a l u e C a p s [ % d ] . H a s N u l l = %d\n",
i,
v a lu e C a p s [i].H a s N u ll);
v a lu e C a p s [i].Is A b s o lu te );
p r i n t f ( " V a l u e C a p s [ % d ] . B i t S i z e = %d\n",
i,
v a lu e C a p s [ i].B itS iz e ) ;
p r i n t f ( " V a l u e C a p s [ % d ] . R e p o r t C o u n t = % d \n ",
i,
p r i n t f ( " V a l u e C a p s [ % d ] . U n i t s E x p = % d \n ",
i,
v a lu e C a p s [ i].U n its E x p ) ;
p r i n t f ( " V a l u e C a p s [ % d ] . U n i t s = % d \n ",
v a lu e C a p s [i].U n its );
i,
v a lu e C a p s[i].R e p o rtC o u n t);
p r i n t f ( " V a l u e C a p s [ % d ] . L o g i c a l M i n = %d\n",
i,
v a lu e C a p s [i].L o g ic a lM in );
p r i n t f ( " V a lu e C a p s [ % d ] . L o g i c a lM a x = %d\n",
i,
v a lu e C a p s [i].L o g ic a lM a x );
p r i n t f ( " V a l u e C a p s [ % d ] . P h y s i c a l M i n = % d \n ",
i,
v a lu e C a p s [i].P h y s ic a lM in );
p r i n t f ( " V a l u e C a p s [ % d ] . P h y s i c a l M a x = % d \n ",
i,
v a lu e C a p s [i].P h y s ic a lM a x );
p r i n t f ( " V a l u e C a p s [ % d ] . R a n g e = % d \n ",
i,
v a lu e C a p s[i].R a n g e );
p r i n t f ( " V a l u e C a p s [ % d ] . R a n g e . U s a g e M i n = % d \n ",
i,
v a lu e C a p s[i].R a n g e .U sa g e M in ); p r i n t f ( " V a l u e C a p s [ % d ] . R a n g e . U s a g e M a x = % d \n ",
i,
v a lu e C a p s [ i].R a n g e .U s a g e M a x ) ; p r i n t f ( " V a l u e C a p s [ % d ] . R a n g e . S t r i n g M i n = % d \n ",
i,
v a lu e C a p s[i].R a n g e .S trin g M in ); p r i n t f ( " V a l u e C a p s [ % d ] . R a n g e . S t r i n g M a x = % d \n ",
i,
v a lu e C a p s[i].R a n g e .S trin g M a x ); p r in t f ( " V a lu e C a p s [ % d ] . R a n g e . D e s i g n a t o r M in = %d\n",
i,
val u e C a p s [i].R a n g e .D e s ig n a to rM in ); p r in t f ( " V a lu e C a p s [ % d ] . R a n g e . D e s i g n a t o r M a x = %d\n",
i,
val u e C a p s [i].R a n g e .D e s ig n a to rM a x ); p r i n t f ( " V a l u e C a p s [ % d ] . R a n g e . D a t a I n d e x M i n = % d \n ",
i,
v a lu e C a p s [i].R a n g e .D a ta In d e x M in ); p r i n t f ( " V a l u e C a p s [ % d ] . R a n g e . D a t a I n d e x M a x = % d \n ",
i,
v a lu e C a p s[i].R a n g e .D a ta In d e x M a x ); p r i n t f ( " V a l u e C a p s [ % d ] . N o t R a n g e = % d \n ",
i,
va lu e C a p s[i].N o tR a n g e );
p r in tf("V a lu e C a p s[% d ].N o tR a n g e .U s a g e = x% x\n\n",
i,
v a lu e C a p s[i].N o tR a n g e .U sa g e ); s w itc h
(v a lu e C a p s[i].U s a g e P a g e )
{
c a s e HID_USAGE_PAGE_LED: p r in tf(" \ U s a g e
Pag e -> LEDs P a g e \ n " ) ;
break;
/*
*/
d e fa u lt
:
p r in tf( " ..." ) ; } sw itc h
( v a lu e C a p s[i].L in k U s a g e )
{
c a s e H I D_ US AGE _GE NER IC _J OYS TI CK: p rin tf(" \L in k
U sa g e -> G e n e r i c J o y s t i c k
Page\n");
break;
/*
*/
d e fa u lt
:
p r in tf( " ..." ) ; } sw itc h
( v a lu e C a p s [i].L in k U s a g e P a g e )
{
c a s e HID_USAGE_PAGE_GENERIC: p rin tf(" \L in k
U sa g e Pag e -> G e n e r i c D e s k t o p P a g e \ n \ n " ) ;
217
218
USB. Praktyczne programowanie z Windows API w C++
break;
/*
*/
d e fa u lt
:
p r in tf( " ..." ) ; } } re le a se M e m o ry(v a lu e C a p s) ; H id D _ F r e e P re p a rs e d D a t a ( p re p a r s e d D a ta ) ; C lo se H a n d le (h id D e v i c e O b je c t ) ; } F re e L ib ra ry ( h H id L ib ) ; c o u t << e n d l ; system ("PAUSE"); return
0;
}
/ / --------------------------------------------------
R y su n e k
6 .8 .
Odczyt wybranych własności elementu sterującego o charakterze ciągłym
11 11
|IS:i.l'Mk:yi.'n:- [ ;xj r
';\R6
1
lk:[ liii itj. -«-
I
I^
|\\?\hi d # vid_22ba*pi d_0108#8&59f 5d85&0&0000#{4dle5 5 b 2 - fl6 f- llc f- 8 8 c b - 0 0 1 1 1 1 0 0 0 0 3 0 } V al ueCaps [ O ] . UsagePage= x8 V al ueCaps . ReportID= xO V al ueCaps .I s A l i a s = x0 . B i t F i e ld = 130 V al ueCaps . L in k C o lle c t io n = x0 V al ueCaps V al ueCaps . L inkU sag e= x4 . LinkUsagePage= 0x1 V al ueCaps V al ueCaps . IsRange= 0 V al ueCaps . I sS trin g R a n g e = 0 V al ueCaps . IsD esig n a to rR a n g e = 0 V al ueCaps . I s A b s o lu te = 1 V al ueCaps .H a s N u ll= 0 V al ueCaps . B it S iz e = 8 V al ueCaps . R e p o rte o u nt= 1 V al ueCaps . U n itsE x p = O V al ueCaps .U n it s = 0 V al ueCaps . L o g i ca lM in = 0 V al ueCaps .L o g ic a lM a x = 255 V al ueCaps . P h y s ic a lM in = 0 . P h y sic a lM a x = 255 V al ueCaps . Range= 4390979 V al ueCaps . R a n g e .UsageMin= 67 V al ueCaps V al ueCaps . R a n g e .UsageMax= 67 V al ueCaps . R a n g e .S trin g M in = 0 V al ueCaps . R a n g e .StringM ax= 0 V al ueCaps . R a n g e .D e sig n a to rM in = ( . R a n g e .D e si gnatorMax= ( V al ueCaps . R a n g e .DataIndexM in= 0 V al ueCaps . R a n g e .DataIndexMax= 0 V al ueCaps V al ueCaps . NotRange= 4390979 . NotRange.Usage= x43 V al ueCaps Usage Page -> LEDs Page L inie Usage -> G e n e r ic J o y s t ic k Page L in k Usage Page -> G e n e r ic D e sktop Page
Aplikacja środowiska graficznego Przykładem aplikacji środowiska graficznego, która wykorzystuje omówione do tej pory zasoby systemu operacyjnego w zakresie transmisji danych w standardzie USB, j e s t proj_U SB_R6_4. D ziałanie aplikacji polega na odczytaniu raportu wejściowego pochodzącego z przykładowego urządzenia, jakim jest uniwersalny kontroler gier, oraz wizualizacji danych zawartych w w ybranych polach tego raportu. Struktura logiczna omawianego projektu je st pokazana na rysunku 6.9. W ygląd głównego form ularza aplikacji w trakcie uzyskiwania dostępu do urządzenia, odczytu danych oraz zamyka nia portu USB do transmisji zaprezentowano na rysunkach 6.10a - 6.10c. N a listin gach 6.7 i 6.8 zamieszczono kody głównych modułów projektu.
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
«CppStruct>> HI DP C A P S
+UsagePage « C p p T y p e d e f» USAGE
+lnputReportByteLength: USHORT +OutputReportByteLength: USHORT +FeatureReportByteLength: USHORT +Reserved: USHORT +NumberLmkCollectionNodes: USHORT «CppTypedef>> « C p p S y n o n y m » +NumberlnputButtonCaps: USHORT ...............................> +NumberlnputValueCaps: USHORT PHIDP C A P S +NumberlnputDatalndices: USHORT +NumberOutputButtonCaps: USHORT +NumberOutputValueCaps: USHORT +NumberOutputDatalndices: USHORT « C p p T y p e d e f» +NumberFeatureButtonCaps: USHORT PUSAGE +NumberFeatureValueCaps: USHORT +NumberFeatureDatalndices: USHORT
> « C p p S tr u c t » HIDD ATTRIBUTES +Size: ULONG +VendorlD: USHORT +ProductlD: USHORT +VersionNumber: USHORT
«CppSynonym>> « C p p T y p e d e f» PHIDD ATTRIBUTES
« C p p T y p e d e f» HIDP C A P S
-capabilities
« C p p Syn onym »
« C p p T y p e d e f» -hiddAttributes HIDD_ATTRIBUTES < _____________
219
« C p p T y p e d e f» PHIDP P R E P A R SED DATA « Cp p Syn o nym » -preparsedData «CppStruct>> HIDP P R E P A R S ED DATA
T F o rm l «published»-Button1: TButton « p u b lish e d » - T rackBarl : TTrackBar « p u b lish ed»-ProgressBar1: TProgressBar «published»-Label1: TLabel «published»-Label2: TLabel «published»-M em o1: TMemo -classGuid: GUID -hHidLib: H MODULE -memberlndex: DW ORD - -devicelnterfaceDetailDataSize: DW ORD -deviceInfoSet: HDEVINFO -devicelnterfaceData: SP_DEVICE_INTERFACE_DATA -deviceInterfaceDetailData: PSP_DEVICE_INTERFACE_DETAIL_DATA -hidDeviceObject: HANDLE -inputReportBuffer: BYTE -numberOfBytesRead: DW ORD -points: TPoint <
Rysunek 6.9. Struktura logiczna aplikacjiproj_USB_R6_4
Rysunek 6.10a. Aplikacjaproj_USB_R6_4 w trakcie uzyskiwania dostępu do urządzenia
220
USB. Praktyczne programowanie z Windows API w C++
Rysunek 6.10b. Aplikacja proj_USB_R6_4 w trakcie odczytu danych z urządzenia
Rysunek 6.10c. Aplikacja proj_USB_R6_4 w trakcie zamykania portu do transmisji danych Listing 6.7. Definicja klasy TForm1 aplikacjiproj_USB_R6_4 zawarta w module usb_R6_4.h / / -------------------------------------------------------------------class TForm1 : public TForm { published://IDE-managed Components TButton *Button1; TTrackBar *TrackBar1; TProgressBar *ProgressBar1; TLabel *Label1; TLabel *Label2; TMemo *Memo1;
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
221
void fastcall Button1Click(TObject *Sender); void fastcall FormClose(TObject *Sender, TCloseAction &Action); private: //Deklaracje użytkownika HIDD_ATTRIBUTES hiddAttributes; HIDP_CAPS capabilities; PHIDP_PREPARSED_DATA preparsedData; GUID classGuid; HMODULE hHidLib; DWORD memberIndex; DWORD devi ceInterfaceDetailDataSize; HDEVINFO deviceInfoSet; SP_DEVICE_INTERFACE_DATA deviceInterfaceData; PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData; HANDLE hidDeviceObject; BYTE inputReportBuffer[bufferLength]; //bufor danych wejściowych DWORD numberOfBytesRead; TPoint points[5]; void fastcall displayError(const char* msg); public: //Deklaracje użytkownika void fastcall showCapabilitiesDevice(TObject *Sender); fastcall TForm1(TComponent* Owner); }; / / -------------------------------------------------------------------Listing 6.8. Kod głównego modułu usb_R6_4.cpp / / -------------------------------------------------------------------#include #include #pragma hdrstop #include "usb_R6_4.h" #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; void ( stdcall *HidD_GetHidGuid)(OUT LPGUID HidGuid); bool ( stdcall *HidD_GetAttributes)(IN HANDLE HidDeviceObject, OUT PHIDD_ATTRIBUTES Attributes); long ( stdcall* HidP_GetCaps)(IN PHIDP_PREPARSED_DATA PreparsedData, OUT PHIDP_CAPS Capabilities); bool ( stdcall* HidD_GetPreparsedData)(IN HANDLE HidDeviceObject, OUT PHIDP_PREPARSED_DATA *PreparsedData); bool ( stdcall* HidD_FreePreparsedData)(IN PHIDP_PREPARSED_DATA PreparsedData); / / -------------------------------------------------------------------fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { memberIndex = 0; deviceInterfaceDetailData = NULL; numberOfBytesRead = 0; hidDeviceObject = INVALID_HANDLE_VALUE; hHidLib = LoadLibrary("C:\\Windows\\System32\\HID.DLL"); if (!hHidLib) displayError("Błąd dołączenia biblioteki HID.DLL.");
222
USB. Praktyczne programowanie z Windows API w C++
(FARPROC&) HidD_GetHidGuid=GetProcAddress(hHidLib, "HidD_GetHidGuid"); (FARPROC&) HidD_GetAttributes=GetProcAddress(hHidLib, "HidD_GetAttributes") (FARPROC&) HidP_GetCaps=GetProcAddress(hHidLib, "HidP_GetCaps"); (FARPROC&) HidD_GetPreparsedData=GetProcAddress(hHidLib, "HidD_GetPreparsedData"); (FARPROC&) HidD_FreePreparsedData=GetProcAddress(hHidLib, "HidD_FreePreparsedData"); if (!HidD_GetHidGuid) { FreeLibrary(hHidLib); displayError("Nie znaleziono żadnych funkcji eksportowych.\n"); } Canvas->Pen->Mode = pmNotXor; Canvas->Pen->Color = clRed; Canvas->Pen->Width = 3; } / / -------------------------------------------------------------------void fastcall TForm1::displayError(const char* msg){ ShowMessage(msg); exit(0); }; / / -------------------------------------------------------------------void fastcall TForm1::Button1Click(TObject *Sender) { HidD_GetHidGuid(&classGuid); deviceInfoSet = SetupDiGetClassDevs(&classGuid, NULL, NULL, DIGCF_PRESENT | DIGCF_INTERFACEDEVICE); if (deviceInfoSet == INVALID_HANDLE_VALUE){ FreeLibrary(hHidLib); displayError("Nie zidentyfikowano podłączonych urządzeń.\n"); } deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); while(SetupDiEnumDeviceInterfaces(deviceInfoSet, NULL, &classGuid, memberIndex, &deviceInterfaceData)){ memberIndex++; //inkrementacja numeru interfejsu SetupDiGetDeviceInterfaceDetail(devi ceInfoSet, &deviceInterfaceData, NULL, 0, &deviceInterfaceDetailDataSize, NULL); deviceInterfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA) new DWORD[deviceInterfaceDetailDataSize]; deviceInterfaceDetailData->cbSize=sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); if (!SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &deviceInterfaceData, deviceInterfaceDetailData, devi ceInterfaceDetailDataSi ze, NULL, NULL)){ releaseMemory(devi ceInterfaceDetailData); SetupDiDestroyDevi ceInfoList(devi ceInfoSet); FreeLibrary(hHidLib); displayError("Nie można pobrać informacji o in terfejsie.\n"); } if (NULL != strstr(deviceInterfaceDetailData->DevicePath, "vid_22ba")){ ShowMessage(deviceInterfaceDetail Data->DevicePath); hidDeviceObject=CreateFile(deviceInterfaceDetailData->DevicePath, GENERIC_READ,0,NULL,OPEN_EXISTING,0,NULL);
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
223
if(hidDevi ceObject==INVALID_HANDLE_VALUE) displayError("Nie można otworzyć urządzenia do transm isji."); else break; } releaseMemory(devi ceInterfaceDetailData); };//koniec while SetupDiDestroyDeviceInfoList(deviceInfoSet); showCapabili tiesDevice(this); bool marker=false; while(true) { ReadFile(hidDeviceObject, inputReportBuffer, sizeof(inputReportBuffer), &numberOfBytesRead, NULL); if (marker==true) Canvas->Polyline(points,3); //zamazuje trójkąt marker=true; points[0].x = 2*inputReportBuffer[1]+ClientHeight/2; points[0].y = 270; points[1].x = 2*inputReportBuffer[1]+75+ClientHeight/2; points[1].y = 270; points[2].x = 2*inputReportBuffer[1]+37+ClientHeight/2; points[2].y = 310; points[3].x = 2*inputReportBuffer[1]+ClientHeight/2; points[3].y = 270; / / rysuje trójkąt Canvas->Polyline(points,3); TrackBar1->Position = inputReportBuffer[1]; ProgressBar1->Position = inputReportBuffer[1]; if(inputReportBuffer[6]==64 || hidDeviceObject == INVALID_HANDLE_VALUE){ ShowMessage("Odczyt zakończony. Możesz bezpiecznie" " zamknąć aplikację."); break; } }//koniec while } / / -------------------------------------------------------------------void fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action) { if(hidDeviceObject != INVALID_HANDLE_VALUE) CloseHandle(hidDeviceObject); FreeLibrary(hHidLib); Action = caFree; } / / -------------------------------------------------------------------void fastcall TForm1::showCapabilitiesDevice(TObject *Sender) { HidD_GetAttributes(hidDeviceObject, &hiddAttributes); AnsiString S = S.Format("VID-PID-Wersja = %x-%x-%x", OPENARRAY(TVarRec,(hiddAttributes.VendorID, hiddAttributes.ProductID, hiddAttributes.Versi onNumber))); Memo1->Lines->Add(S); HidD_GetPreparsedData(hidDeviceObject, &preparsedData); HidP_GetCaps(preparsedData, &capabilities);
22 4
USB. Praktyczne programowanie z Windows API w C++
A n s i S t r i n g S1 = S 1 . F o r m a t ( " U s a g e = % d UsagePage=%d\ InputR eportByteLength= % d O utputR eportB yte Leng th= % d \ Fe a tu re R e p o r tB y te L e n g th = % d N um berInputButtonCaps=% d\ N u m b e r In p u t V a l u e C a p s = % d N u m b e r I n p u t D a t a I n d i c e s = % d \ N u m b e r O u tp u tB u t to n C a p s = % d N u m b e r O u tp u tV a l u e C a p s = % d \ N u m b e r O u tp u tB u t to n C a p s = % d N u m b e r O u tp u tV a l u e C a p s = % d \ Num berOutputDataIndices=% d NumberFeatureButtonCaps=% d\ NumberFeatureValueCaps=% d N um b e rFe a tu re D a ta In d ice s = % d ", O P E N A R R A Y (T V a rR e c ,(c a p a b ilitie s.U s a g e ,
c a p a b ilitie s .U s a g e P a g e ,
ca p a b ilitie s .In p u tR e p o rtB y te L e n g th , c a p a b ilitie s .O u tp u tR e p o r tB y te L e n g th , c a p a b ilitie s .F e a tu re R e p o rt B y te L e n g t h , c a p a b ilitie s .N u m b e rIn p u tB u tto n C a p s, c a p a b ilitie s .N u m b e rIn p u tV a lu e C a p s, c a p a b ilitie s .N u m b e rIn p u tD a ta In d i ces, ca p a b ilitie s .N u m b e rO u tp u tB u tto n C a p s , c a p a b ilitie s .N u m b e rO u tp u tV a lu e C a p s , ca p a b ilitie s .N u m b e rO u tp u tB u tto n C a p s , c a p a b ilitie s .N u m b e rO u tp u tV a lu e C a p s , c a p a b i l i t i e s .N u m be rO u tpu tD a ta In d ice s, ca p a b ilitie s .N u m b e rF e a tu re B u tto n C a p s , c a p a b i l i t i e s . N u m b e r F e a t u r e V a l ueCaps, c a p a b ilitie s .N u m b e rF e a tu re D a ta In d ic e s ))); Mem o 1-> Lines-> Add(S1 ); H id D _ F r e e P re p a rs e d D a ta ( p re p a r s e d D a ta ) ; }
/ / ---------------------------------------------------------------------------W skład klasy form ularza aplikacji w chodzą struktury HIDD_ATTRIBUTES, HIDP_CAPS, HIDP_PREPARSED_DATA, GUID, SP_DEVICE_INTERFACE_DATA, SP_DEVICE_INTERFACE_DETAIL_ DATA oraz klasy znanych komponentów wizualnych. Obsługę zdarzenia polegającego na identyfikacji i odblokowaniu do transmisji sterownika wybranego urządzenia zapew nia komponent klasy TButton. Dodatkowo z funkcją obsługi zdarzenia przycisku Buttonl je st skojarzona funkcja showCapabilitiesDevice(), za pomocą której w obszarze kom ponentu klasy TMemo są wyświetlane podstawowe właściwości testowanego urządzenia. Zakres zmienności danych zawartych w jednym z pól raportu wejściowego (odpowia dających ciągłym zmianom położenia drążka lub kierownicy kontrolera gier) jest w i zualizowany poprzez odpowiednie zachowanie się własności Position komponentów klas TProgressBar i TTrackBar (w aplikacjach komponenty te zazwyczaj spełniają funk cję elementów sterujących o charakterze ciągłym). Dodatkowo aktualna wartość prze chowywana w jednym z pól raportu wejściowego jest wykorzystywana do lokalizowania w obrębie formularza samodzielnie zaprogramowanego trójkąta będącego przykłado w ą (i bardzo uproszczoną) emulacją znaku śledzenia, jakim posługuje się oprogramo wanie standardowych lokalizatorów. Koniec odczytu danych jest sygnalizowany poprzez naciśnięcie na panelu sterowni czym kontrolera gier przycisku Select (w niektórych urządzeniach oznaczonego nu merem 11), co powoduje umieszczenie w odpowiednim polu (bajcie) raportu wejścio wego dyskretnej dziesiętnej wartości 64.
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
225
Zapis danych w form ie raportu Aplikacje użytkownika m ogą używać funkcji HidD_SetXxx() w celu zapisywania (wy syłania) raportów wyjściowych i konfiguracyjnych do aktualnie dostępnego w systemie urządzenia. Niemniej funkcji tych powinno się używać jedynie do ustalania aktualnego stanu urządzenia. Należy zwrócić uwagę na fakt, że wiele urządzeń może nieprawidło wo reagować na cykliczne polecenia wysyłane przez funkcje HidD_SetXxx(). W tabeli 6.2 zamieszczono wykaz funkcji, którymi aplikacje użytkownika m ogą się bezpiecznie posługiwać w trakcie transferu danych do urządzeń przyłączonych do magistrali USB. Tabela 6.2. Funkcje używane podczas transferu raportów wyjściowych Typ raportu
Funkcje API WDK/SDK
Typ transferu danych
Wyjściowy OUT (Output)
HidD SetOutputReport()
Kontrolny z rozkazem Set Report
W riteF ile()
Przerwaniowy OUT, ewentualnie kontrolny z rozkazem Set_Report
HidD SetFeature()
Kontrolny z rozkazem Set_Report
Konfiguracyjny OUT (Feature OUT)
Funkcja WriteFile() Zasadniczą częścią kodu wysyłającego dane do urządzenia USB będzie zdefiniowana w Windows SDK API funkcja: BOOL W r i t e F i l e ( I N
HANDLE
hCommDev,
IN LPCVOID IN DWORD
lp B u ffe r, n N um berO fBytesT oW rite,
OUT LPDWORD
lp N u m b erO fB y tesW ritten ,
IN OUT LPOVERLAPPED
lp O v e rla p p e d );
Ogólnie rzecz biorąc, może ona zapisywać dane do dowolnego urządzenia (pliku) je d noznacznie wskazanego przez identyfikator hCommDev. W przypadku transmisji danych może być z pow odzeniem stosowana zarówno do jej w ariantu przerwaniowego, ja k i kontrolnego (patrz tabela 6.2). Funkcja ta zapisuje dane do obszaru pamięci (bufora danych) identyfikowanego przez w skaźnik lpB uffer. D eklaracja LPCVOID lpB uffer odpowiada klasycznej deklaracji wskaźnika ogólnego (adresowego) stałej, czyli const void *Buffer. Rozm iar bufora ustala się w zależności od potrzeb, zasobów pamięci kom putera i pojemności bufora danych urządzenia zewnętrznego. Następny parametr — nNumberOfBytesToWrite — określa liczbę bajtów do wysłania, a wskaźnik lpNumberO fBytesW ritten w skazuje liczbę rzeczyw iście w ysłanych bajtów . Pole danych transmitowanego pakietu może posiadać rozmiar mniejszy, niż jest to zapisane w polu wMaxPacketSize deskryptora punktu końcow ego. W ówczas taki pakiet je s t ostatni w serii. Aby kontrolować liczbę rzeczywiście wysłanych bajtów, funkcja umieszcza ją w zmiennej lpNumberOfBytesWritten, stanowiącej przedostatni parametr. Ostatni pa rametr lpOverlapped je st wskaźnikiem struktury OVERLAPPED. Zawiera ona informacje
226
USB. Praktyczne programowanie z Windows API w C++
o dodatkowych metodach kontroli transmisji, polegających na sygnalizowaniu aktual nego położenia pozycji wskaźnika transmitowanego pliku. Większość elementów tej struktury je st zarezerwowana przez system operacyjny. Jeżeli jednak chcielibyśmy z niej skorzystać, należałoby w funkcji C reateF ile() przypisać parametrowi dwFlagsAndAttributes znacznik FILE_FLAG_OVERLAPPED. W tym przykładzie zignorujemy wskaź nik lpOverlapped, przypisując mu NULL (do struktury OVERLAPPED powrócimy jeszcze za chwilę). Na listingu 6.9 zaprezentowano fragment kodu modułu programu zapisującego przykła dowy raport wyjściowy do urządzenia USB identyfikowanego przez hidDeviceObject. Listing 6.9. Przykład obsługi raportu wyjściowego zapomocąfunkcji WriteFile() / / -------------------------------------------------------------------char* outputReportBuffer = new char[capabilities.OutputReportByteLength]; assert(outputReportBuffer != NULL); memset(outputReportBuffer, 0x00, capabilities.OutputReportByteLength); //... outputReportBuffer[0] = 0x00; //Raport ID outputReportBuffer[1] = /* ...* /; outputReportBuffer[2] = /* ...* /; //... printf("Raport wyjściowy: "); for(ULONG i=1; iDevicePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,OPEN_EXISTING, 0, NULL); //... DWORD numberOfBytesWrite = 0; if(!WriteFile(hidDeviceObject, &outputReportBuffer, capabilities.OutputReportByteLength, &numberOfBytesWrite, NULL)) printf("Błąd wysłania raportu wyjściowego %d\n", GetLastError()); else if(numberOfBytesWri te==capabilities.OutputReportByteLength) printf("Raport wyjściowy został wysłany.\n"); else printf("Błędna liczba wysłanych bajtów: %d\n",numberOfBytesWrite); releaseMemory(outputReportBuffer); //... / / --------------------------------------------------------------------
Funkcje HidD_SetOutputReport() oraz HidD_SetFeature() W przypadku gdy testowane urządzenie mogłoby się posługiwać kontrolnym transfe rem danych dla raportów w yjściow ych (patrz tabela 6.2), w celu wysłania aktualnej postaci raportu wyjściowego lub konfiguracyjnego OUT można użyć odpowiednio funk
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
227
cji HidD_SetOutputReport() i (lub) HidD_SetFeature(). Przykładowy sposób jej wyko rzystania pokazuje kod z listingu 6.10. Listing 6.10. Przykład wykorzystania funkcji HidD_SetOutputReport() oraz HidD_SetFeature() / / -------------------------------------------------------------------int main() { //... outputReportBuffer[0] = 0x00; //Raport ID outputReportBuffer[1] = /* ...* /; outputReportBuffer[2] = /* ...* / outFeatureReportBuffer[0] = 0x00; //Raport ID outFeatureReportBuffer[1] = /* ...* /; outFeatureReportBuffer[2] = /* ...* /; bool ( stdcall *HidD_SetOutputReport)(IN HANDLE HidDeviceObject, IN OUT PVOID ReportBuffer, IN ULONG ReportBufferLength); bool ( stdcall *HidD_SetFeatureReport)(IN HANDLE HidDeviceObject, IN OUT PVOID ReportBuffer, IN ULONG ReportBufferLength); //... (FARPROC&) HidD_SetOutputReport=GetProcAddress(hHidLib, "HidD_SetOutputReport"); (FARPROC&) HidD_GetFeatureReport=GetProcAddress(hHidLib, "HidD_SetFeatureReport"); //... HidD_SetOutputReport(hidDeviceObject, outputReportBuffer, capabilities.OutputReportByteLength); //... HidD_SetFeatureReport(hidDeviceObject, outFeatureReportBuffer, capabilities.FeatureReportByteLength); //... / / --------------------------------------------------------------------
Struktura OVERLAPPED W dotychczas omawianych przykładach urządzenie USB pozostawało odblokowane w standardowym trybie działania synchronicznego. Aby odblokować urządzenie dla asynchronicznych operacji odczytu i zapisu danych, należy utworzyć obiekt na bazie struktury kontrolującej asynchroniczne operacje I/O (wejścia-wyjścia): typedef struct _OVERLAPPED { DWORD Internal; DWORD InternalHigh; DWORD Offset; DWORD OffsetHigh; HANDLE hEvent; } OVERLAPPED;
228
USB. Praktyczne programowanie z Windows API w C++
Znaczenie poszczególnych pól tej struktury je st następujące: ♦ Internal — zarezerwowane dla systemu operacyjnego. Staje się istotne, gdy funkcja GetOverlappedResul t ( ) zwróci rezultat asynchronicznych operacji I/O dla pliku, potoku lub urządzenia zewnętrznego. ♦ InternalH igh — zarezerwowane dla systemu. Specyfikuje długość przesyłanych danych. Staje się istotne, gdy funkcja GetOverlappedResult() zwraca wartość TRUE. ♦ O ffset — określa w skaźnik położenia pliku przeznaczonego do transferu. Traktowany jest jako przesunięcie wyznaczone w stosunku do początku pliku. ♦ OffsetHigh — bardziej znacząca część przesunięcia. ♦ hEvent — określenie sposobu oznaczenia końca transferu danych. Jako jeden z argumentów funkcji C reateF ile() powinien zostać użyty znacznik FILE_ FLAG_OVERLAPPED: h id D e v ic e O b je c t= C re a te F ile (d e v ic e In te r fa c e D e ta ilD a ta - > D e v ic e P a th , GENERIC_READ | GENERIC_WRITE, FILE_SHA RE _RE AD NULL,
| FILE_ SH ARE _WR ITE,
OP EN _ EX IS TI N G,
FILE _F L AG _ O VE RL AP PE D ,
N ULL);
D la każdego żądania wykonania asynchronicznej operacji przez funkcje DeviceIoCont r o l ( ) , ReadFile() i W riteF ile() należy używać osobnego obiektu struktury, przeka zywanego do tych funkcji jako ostatni argument (patrz listing nieco dalej). W trakcie wykonywania asynchronicznych operacji I/O zdarzają się sytuacje, w któ rych powinno się diagnozować czasy przeterminowania dla odczytu i (lub) zapisu da nych pochodzących z łącza USB. Windows SDK API definiuje funkcję często wyko rzystywaną w tym celu: DWORD W a i t F o r S i n g l e O b j e c t ( I N
HANDLE h E v e n t ,
IN DWORD d w M i l l i s e c o n d s ) ;
W zależności od kontekstu użycia omawianej funkcji, dwMilliseconds określa w mili sekundach czas przeterminowania (ang. time out) albo czas oczekiwania na zdarzenie (ang. break time). Funkcja zwraca czas, który upłynął, nawet jeżeli stan obiektu nie jest w żaden sposób sygnalizowany. Jeżeli param etr ten je st równy zero, funkcja natych miast testuje stan obiektu. W przypadku gdy zostanie przypisana mu wartość INFINITE (nieskończoność), stan obiektu nie będzie testowany. Param etr hEvent jest identyfikatorem określonego obiektu zdarzenia. Z reguły należy mu przydzielić odpowiednią wartość, korzystając z funkcji SDK API: HANDLE C r e a t e E v e n t ( I N OUT L PS EC URI TY_ ATT RIB UT ES l p E v e n t A t t r i b u t e s , IN BOOL b M a n u a l R e s e t , IN BOOL b I n i t i a l S t a t e , IN OUT LPCTSTR lp N a m e ) ;
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
229
Param etr lp E v e n tA ttrib u te s je s t w skaźnikiem do struktury zabezpieczeń obiektu SECURITY_ATTRIBUTES, określającej, czy zwracany identyfikator może być dziedziczo ny przez procesy potomne. Jeżeli przypiszemy mu wartość NULL, identyfikator ten nie będzie dziedziczony. Param etr bManualReset określa, czy i kiedy występuje automa tyczne lub ręczne zwolnienie stworzonego obiektu zdarzenia. Jeżeli przypiszemy mu wartość FALSE, Windows automatycznie zwolni obiekt w przypadku zakończenia da nego procesu lub wystąpienia zdarzenia. Parametr lpName je st wskaźnikiem do łańcu cha liczącego co najwyżej MAX_PATH znaków i zakończonego zerowym ogranicznikiem. Łańcuch ten określa konkretną nazwę obiektu zdarzenia. Funkcja WaitForSingleObject() w przypadku niepomyślnego wykonania zwraca war tość WAIT_FAILED. Jeżeli zostanie w ykonana pomyślnie, należy się spodziewać nastę pujących wartości: ♦ WAIT_ABANDONED — wyspecyfikowany jest obiekt wzajemnego wykluczania (ang. mutex), tj. sekcji krytycznej współdzielonej przez wiele procesów, który nie został zwolniony przez dany wątek; ♦ WAIT_OBJECT_0 — sygnalizowany je st aktualny stan wyspecyfikowanego obiektu; ♦ WAIT_TIMEOUT — czas przeterminowania wybranej operacji upłynął i aktualny stan obiektu nie będzie sygnalizowany. Jeżeli w funkcji CreateEvent() parametrowi bManual Reset zostanie przypisana wartość TRUE, należy skorzystać z funkcji Windows SDK API: BOOL R e s e t E v e n t ( I N HANDLE h E v e n t ) ;
Funkcja ta uniemożliwia sygnalizowanie stanu obiektu zdarzenia. N a listingu 6.11 zaprezentowano przykładową funkcję readUSBReport(), która w spo sób asynchroniczny kontroluje operację pobierania raportów z zewnętrznego urządze nia USB. Funkcję tę można wywoływać cyklicznie. Listing 6.11. Fragment kodu modułu usb_R6_5.cpp, ilustrujący wykorzystanie struktury OVERLAPPED w trakcie pobierania raportu wejściowego / / ---------------------------------------------------------------------------bool
readUSBReport(HANDLE,
v o id * ,
USHORT i n t p u t R e p o r t B y t e L e n g t h )
{ DWORD r e s u l t
= 0;
DWORD n u m b e r O f B y t e s R e a d = 0; OVERLAPPED * o v e r l a p p e d = NULL; i f ( o v e r l a p p e d == NULL) { o v e r l a p p e d = new OVERLAPPED; o verlappe d-> hEvent = C reateEvent(N U LL,
TRUE, TRUE,
"");
o v e r l a p p e d - > O f f s e t = 0; o v e rla p p e d -> O ffs e tH ig h
= 0;
} if(!R e a d F ile (h id D e v ic e O b je c t,
in p u tR e p o rtB u ffe r,
& num berO fBytesRead, if(G e tL a s tE rro r()
ove rla p p e d ))
== ERROR_IO_PENDING)
{
{
in tp u tR e p o rtB y te L e n g th ,
230
USB. Praktyczne programowanie z Windows API w C++
result = WaitForSingleObject(overlapped->hEvent, 100); if(resu lt == WAIT_TIMEOUT) { CancelIo(hidDeviceObject); return false; } else if(resu lt == WAIT_FAILED){ displayError("Błąd odczytu danych."); return false; } GetOverlappedResult(hi dDeviceObject, overlapped, &numberOfBytesRead, FALSE); } else displayError("Błąd odczytu danych."); } ResetEvent(overlapped->hEvent); if(numberOfBytesRead==capabilities.InputReportByteLength){ for(USHORT i=0; i
--------------------------------------------------------------
Funkcje xxxEx Funkcje W riteFile() i ReadFile() mogą być wykorzystywane zarówno w trakcie trans misji asynchronicznej, ja k i synchronicznej. Transmisja asynchroniczna może być trak tow ana jako szczególna metoda przesyłania danych. Z tego względu W indows API udostępnia grupę funkcji przeznaczonych do realizacji tylko i wyłącznie asynchronicz nego trybu przesyłania danych.
Funkcja WriteFileEx() BOOL WriteFileEx(IN HANDLE hFile, IN LPCVOID lpBuffer, IN DWORD nNumberOfBytesToWrite, IN OUT LPOVERLAPPED lpOverlapped, IN LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine ); Funkcja W riteFileEx() asynchronicznie zapisuje (wysyła) dane do pliku identyfikowa nego przez hFile. Identyfikator ten jest zwracany poprzez funkcję C reateF ile() i po w inien być przydzielony z atrybutami FILE_FLAG_OVERLAPPED oraz GENERIC_WRITE. Pa rametr nNumberOfBytesToWrite określa liczbę bajtów przeznaczoną do zapisu (wysłania). W trakcie wykonywania standardowych operacji plikowych koniec wysyłanego pliku można określić, posługując się funkcją SetEndOfFile(HANDLE hFile).
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
231
Funkcja G etLastError() zwraca ERROR_INVALID_USER_BUFFER, gdy operacja zapisu do pliku została w ykonana nieprawidłowo, lub ERROR_NOT_ENOUGH_MEMORY, gdy napotka no zbyt wiele procesów jednocześnie żądających wykonania asynchronicznych opera cji wejścia-wyjścia. W celu przerwania zapisu do pliku można użyć funkcji CancelIo (HANDLE hFile).
Funkcja ReadFileEx() BOOL R e a d F i l e E x ( I N
HANDLE h F i l e ,
OUT LPVOID l p B u f f e r , IN DWORD n N u m b e r O f B y t e s T o R e a d , IN OUT LPOVERLAPPED l p O v e r l a p p e d , IN LPOVERLAPPED_COMPLETION_ROUTINE l p C o m p l e t i o n R o u t i n e ) ;
Funkcja ReadFileEx() asynchronicznie odczytuje (pobiera) dane z pliku identyfiko wanego przez hFile. Identyfikator ten je st zwracany poprzez funkcję C reateF ile() i powinien być przydzielony z atrybutami FILE_FLAG_OVERLAPPED oraz GENERIC_READ. Parametr nNumberOfBytesToRead określa liczbę bajtów przeznaczoną do odczytu (ode brania). W przypadku wykonywania standardowych operacji na pliku koniec odbiera nego pliku można określić, posługując się funkcją SetEndOfFile(HANDLE hFile). Funkcja GetLastError() zwraca ERROR_INVALID_USER_BUFFER, gdy operacja odczytu z pli ku została wykonana nieprawidłowo, lub ERROR_NOT_ENOUGH_MEMORY, gdy napotkano zbyt wiele procesów jednocześnie żądających wykonania asynchronicznych operacji wejściaw yjścia. W celu przerwania odczytu można użyć funkcji CancelIo(HANDLE h F ile ). Jeżeli w trakcie odczytu okaże się, że rozmiar bufora danych wejściowych jest zbyt mały, funkcja ReadFileEx() zwróci FALSE, a GetLastError() — wartość ERROR_INSUFFICIENT_ BUFFER.
Funkcja FileIOCompletionRoutine() W odróżnieniu od W riteFile() i ReadFile(), funkcje W riteFileEx() i ReadFileEx() nie posługują się jawnie zaimplementowanym mechanizmem ochrony wysyłanych i odbie ranych danych. Zamiast tego wykorzystują wskaźnik lpCompletionRoutine do funkcji: VOID CALLBAC K F i l e I O C o m p l e t i o n R o u t i n e ( I N
DWORD d w E r r o r C o d e ,
IN DWORD d w N u m b e r O f B y t e s T r a n s f e r r e d , IN LPOVERLAPPED l p O v e r l a p p e d ) ;
Parametr dwNumberOfBytesTransferred jest liczbą transferowanych bajtów (w przypadku wystąpienia błędów w transmisji param etr ten jest równy zero). Param etr dwErrorCode reprezentuje status w ykonania operacji w ejścia-w yjścia i może przyjąć następujące wartości: ♦ 0 — operacja wejścia-wyjścia została wykonana prawidłowo; ♦ ERROR_HANDLE_EOF — funkcja ReadFileEx() próbuje odczytać dane zawarte poza znacznikiem końca pliku (EOF).
232
USB. Praktyczne programowanie z Windows API w C++
Funkcja WaitForSingleObjectEx() DWORD W a i t F o r S i n g l e O b j e c t E x ( I N
HANDLE h H a n d l e ,
IN DWORD d w M i l l i s e c o n d s , IN BOOL b A l e r t a b l e ) ;
W zależności od kontekstu użycia omawianej funkcji, dwMilliseconds określa w mili sekundach czas przeterminowania (ang. Time Out) lub czas oczekiwania na zdarzenie (ang. Break Time). Funkcja zwraca czas, który upłynął, nawet jeżeli stan obiektu nie jest w żaden sposób sygnalizowany. Jeżeli param etr ten je st równy zero, funkcja natych miast testuje stan obiektu. W przypadku gdy zostanie przypisana mu wartość INFINITE (nieskończoność), stan obiektu nie będzie testowany. Parametr hHandle jest identyfi katorem określonego obiektu (wątek, muteks, semafor, proces, zdarzenie). Parametro w i bA lertable należy przypisać wartość TRUE, jeżeli wykonywane w wątku operacje wejścia-wyjścia bezwzględnie powinny się zakończyć powodzeniem.
Funkcja SleepEx() DWORD S l e e p E x ( I N DWORD d w M i l l i s e c o n d s , IN BOOL b A l e r t a b l e ) ;
Jeżeli param etr bA lertable przyjmuje wartość FALSE, funkcja SleepEx() wstrzymuje działanie bieżącego wątku na czas określony parametrem dwMilliseconds. W przypad ku gdy param etr bA lertable przyjmuje wartość TRUE, a funkcje ReadFileEx(), W riteFileEx() i SleepEx() są wywoływane w tym samym wątku, SleepEx() kończy dzia łanie albo w momencie upłynięcia czasu dwMilliseconds, albo po wykonaniu funkcji FileIOCompletionRoutine(). N a listingu 6.12 zaprezentowano jed en z m ożliwych sposobów w ykorzystania oma wianych funkcji. W programie głównym funkcje te są używane pośrednio poprzez w y wołanie readUSBReportEx(). Listing 6.12. Fragment kodu programu asynchronicznie pobierającego z potoku raport wejściowy //... BYTE i n p u t R e p o r t B u f f e r [ 7 ] ;
//bufor danych wejściowych
DWORD s t a t u s = ERROR_SUCCESS;
/ / -------------------------------------------------------------------v o i d CALLBAC K F i l e I O C o m p l e t i o n R o u t i n e ( c o n s t DWORD e r r o r C o d e , c o n s t DWORD n u m b e r O f B y t e s T r a n s f e r r e d , OVERLAPPED* o v e r l a p p e d ) { s ta tu s = errorCode; i f ( E R R O R _ S U C C E S S == s t a t u s )
{
a sse rt(size o f(in p u tR e p o rtB u ffe r)
== n u m b e r O f B y t e s T r a n s f e r r e d ) ;
//do bufora z potoku je st asynchronicznie pobierany raport wejściowy if
( !R e a d F ile E x (h id D e v ic e O b je c t,
& in p u tR e p o rtB u ffe r,
s iz e o f(in p u tR e p o rtB u ffe r), F ile IO C o m p le tio n R o u tin e ))
ove rla p p e d ,
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
statu s = G e tL a stE rro r(); } } //
--------------------------------------------------------------
DWORD r e a d U S B R e p o r t E x ( H A N D L E ,
v o id * )
{ OVERLAPPED * o v e r l a p p e d = NULL; i f ( o v e r l a p p e d == NULL) { o v e r l a p p e d = new OVERLAPPED; o v e r l a p p e d - > O f f s e t = 0; o v e rla p p e d -> O ffs e tH ig h
= 0;
} if( !R e a d F ile E x ( h id D e v i ce O b ject,
in p u tR e p o rtB u ffe r ,
s iz e o f ( in p u t R e p o r t B u f f e r ) , o verlappe d, F ile lO C o m p le tio n R o u tin e ))
{
statu s = G e tL a stE rro r(); } e ls e
{ w h il e ( E R R O R _ S U C C E S S == s t a t u s )
{
S le e p E x (IN F IN IT E ,T R U E ); p r i n t f ( " % d %d %d %d %d %d % d \n ",
in p u tR e p o rtB u ffe r [ 0 ] ,
in p u tR e p o rtB u ffe r [1 ],
in p u tR e p o rtB u ffe r [2 ],
in p u tR e p o rtB u ffe r [3 ],
in p u tR e p o rtB u ffe r [4 ],
in p u tR e p o rtB u ffe r [5 ],
in p u t R e p o r t B u f f e r [ 6 ] );
if(in p u tR e p o rtB u ffe r[6 ]= = 6 4 ) break; } } re le a s e M e m o ry (o v e rla p p e d ); return
statu s;
}
/ / -------------------------------------------------------------------i n t m ain () {
//... w h ile (S e tu p D iE n u m D e v ic e In te rfa c e s (d e v ic e In fo S e t, m em berlndex,
NU LL,
& c la s s G u id ,
&devi c e I n t e r f a c e D a t a ) ) {
//... };//koniec while S e tu p D iD e s tro y D e v ic e In fo L is t(d e v ic e In fo S e t); read U S B R e p o rtE x(h id D eviceO b ject,
in p u tR e p o rtB u ffe r);
C lo s e H a n d le ( h id D e v i c e O b je c t ); F re e L ib ra ry ( h H id L ib ) ; c o u t << e n d l ; system ("PAUSE"); return }
0;
/ / --------------------------------------------------------------------
233
23 4
USB. Praktyczne programowanie z Windows API w C++
Struktura COMMTIMEOUTS Zasoby struktury COMMTIMEOUTS są przedstawione w tabeli 6.3. Udostępniają one infor macje o czasach przeterminowania transmisji w trakcie przesyłania danych (ang. time -out o f transmission). W trakcie transmisji asynchronicznej COMMTIMEOUTS determinuje zachowanie takich funkcji jak ReadFile(), W riteFile(), ReadFileEx() i W riteFileEx(). Tabela 6.3. Informacje zawarte w strukturze COMMTIMEOUTS Typ
Element struktury
Opis
DWORD
ReadIntervalTimeoutOkreśla maksymalny czas (w milisekundach) między pojawieniem się na linii komunikacyjnej dwóch znaków. W trakcie wykonywania ReadFile() czas jest liczony od momentu pojawienia się pierwszego znaku. Jeżeli przedział czasu między nadejściem dwóch znaków przekracza tę wartość, oznacza to, że operacja ReadFile() jest zakończona. Wartość 0 oznacza, że nie ustalono wymaganego okresu między nadejściem dwóch kolejnych znaków. Przypisanie wartości MAXDWORDpowoduje, że czytany znak jest pobierany z bufora natychmiast, gdy się tam pojawi
DWORD
ReadTotalTimeoutM ultiplier
Określa mnożnik (w milisekundach) użyty do obliczenia całkowitego przedziału czasu (przeterminowania) dla operacji czytania (odbioru). Dla wszystkich takich operacji wartość ta jest mnożona przez liczbę bajtów przewidzianych do odebrania z dysku lub łącza komunikacyjnego
DWORD
ReadTotalTimeoutConstant
Określa stałą (w milisekundach) użytą do obliczania czasu przeterminowania operacji czytania. Dla wszystkich takich operacji wartość ta jest dodawana do ReadTotal Tim eoutM ultiplier i do oczekiwanej liczby nadchodzących bajtów
DWORD
W riteTotalTim eoutM ultiplier
Określa mnożnik (w milisekundach) użyty do obliczenia całkowitego przedziału czasu (przeterminowania) dla operacji zapisywania (wysyłania). Dla wszystkich takich operacji wartość ta jest mnożona przez liczbę bajtów przewidzianych do wysłania (zapisywania). Wartość 0 oznacza, że nie ustalono czasu przeterminowania dla operacji zapisu na dysku lub do łącza komunikacyjnego
DWORD
WriteTotalTimeoutConstant
Określa stałą (w milisekundach) użytą do obliczania czasu przeterminowania operacji wysyłania. Dla wszystkich takich operacji wartość ta jest dodawana do W riteTotalT im eoutM ultiplier oraz do oczekiwanej liczby wysyłanych bajtów. Wartość 0 oznacza, że nie ustalono czasu przeterminowania dla operacji zapisu (wysyłania)
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
235
Windows SDK API definiuje tę strukturę jako: typedef struct _COMMTIMEOUTS { DWORD ReadIntervalTimeout; DWORD ReadTotalTimeoutMultiplier; DWORD ReadTotalTimeoutConstant; DWORD Wri teTotalTimeoutMultiplier; DWORD WriteTotalTimeoutConstant; } COMMTIMEOUTS,*LPCOMMTIMEOUTS; Definicja ta tworzy dwa nowe słowa kluczowe: COMMTIMEOUTS (struktura) i LPCOMMTIMEOUTS (wskaźnik do struktury).
Funkcje GetCommTimeouts() i SetCommTimeouts() Aktualne parametry przeterm inow ania operacji zapisu i odczytu, na przykład z portu komunikacyjnego, odczytamy za pom ocą funkcji: BOOL GetCommTimeouts(HANDLE hCommDev, LPCOMMTIMEOUTS lpCommTimeouts); W łasne ustawienia wpiszemy, korzystając z: BOOL SetCommTimeouts(HANDLE hCommDev, LPCOMMTIMEOUTS lpCommTimeouts); W obu przypadkach lpCommTimeouts jest wskaźnikiem struktury opisanej w tabeli 6.3. Najprostszym sposobem użycia przedstawionych powyżej instrukcji jest fragment ko du z listingu 6.13. Listing 6.13. Przykładowe wykorzystanie struktury COMMTIMEOUTS //... COMMTIMEOUTS commTimeouts; //... / / -------------------------------------------------------------------bool setCommTimeouts(ULONG ReadIntervalTimeout, ULONG ReadTotalT imeoutMul tip lie r, ULONG ReadTotalTimeoutConstant, ULONG Wri teTotalTimeoutMultiplier, ULONG Wri teTotalTimeoutConstant) { if(GetCommTimeouts(hidDevi ceObject, &commTimeouts)==0) return false; commTimeouts.ReadIntervalT imeout=ReadIntervalTimeout; commTimeouts.ReadTotalT imeoutConstant=ReadTotal TimeoutConstant; commTimeouts.ReadTotalT imeoutMultiplier= ReadTotalTimeoutMultipl ier; commTimeouts.WriteTotalTimeoutConstant=WriteTotalT imeoutConstant; commTimeouts.WriteTotalTimeoutMultiplier= WriteTotalTimeoutMul tip lie r; if(SetCommTimeouts(hidDeviceObject, &commTimeouts) == 0){ cout << "Błąd wykonania funkcji SetCommTimeouts().\n"; CloseHandle(hidDeviceObject);
236
USB. Praktyczne programowanie z Windows API w C++
return } return
fa lse ;
tru e;
} //
--------------------------------------------------------------
i n t m ain () {
//... if(H id D _ G e tP re p a rs e d D a ta (h i d D e v ice O b je ct,
& preparsedD ata)){
//... //Upewnij się, że ewentualne czasy przeterminowania zostały zmodyfikowane //zgodnie z obsługiwanymi przez urządzenie wartościami. setCommTimeouts(MAXDWORD, 0 ,
0,
re a d U S B R e p o rt(h id D e vice O b je ct,
0,
0 );
in p u tR e p o rtB u ffe r,
c a p a b ilitie s .In p u tR e p o rtB y te L e n g th );
//... }
/ / --------------------------------------------------------------------
Powyższe zapisy oznaczają, że dane powinny być pobierane z bufora wejściowego na tychm iast po tym, ja k się w nim pojawią.
Funkcja DeviceIoControl() Programy (lub aplikacje) działające w trybie użytkownika zawsze inicjują połączenia z zewnętrznymi urządzeniami. Poprzez funkcję C reateFile() uzyskuje się identyfikator pliku reprezentującego wybrane urządzenie (sterownik). Następnie można wykorzystać funkcje W riteFile(), ReadFile() lub HidD_SetOutputReport() i HidD_GetInputReport(), służące odpowiednio do przerwaniowego lub kontrolnego zapisu i odczytu danych. Należy jednak zwrócić uwagę, że oprócz wymienionych wcześniej zawsze można po służyć się zdefiniowaną w jądrze systemu funkcją ogólnego przeznaczenia: BOOL WINAPI D e v i c e I o C o n t r o l ( I N
HANDLE h D e v i c e ,
IN DWORD d w I o C o n t r o l C o d e , IN LPVOID l p I n B u f f e r , IN DWORD n I n B u f f e r S i z e , OUT LPVOID l p O u t B u f f e r , IN DWORD n O u t B u f f e r S i z e , OUT LPDWORD l p B y t e s R e t u r n e d , IN OUT LPOVERLAPPED l p O v e r l a p p e d ) ;
Znaczenie parametrów tej funkcji jest następujące: ♦ hDevice — jest identyfikatorem urządzenia wykonującego daną operację; identyfikator ten je st zwracany przez funkcję C re a te F ile (); ♦ dwIoControlCode — reprezentuje unikalny kod kontrolny IOCTL_Xxx (ang. I/O control codes) zdefiniowanej przez użytkownika operacji wejścia-wyjścia (I/O);
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
237
♦ lpInB uffer — wskaźnik do bufora danych wejściowych, który zawiera informacje potrzebne do przeprowadzenia wybranej operacji; ♦ nInBufferSize — rozmiar bufora danych wejściowych; ♦ lpOutBuffer — wskaźnik do bufora danych wyjściowych, który zawiera informacje zwrócone podczas wykonywania danej operacji; ♦ nOutBufferSize — rozmiar bufora danych wyjściowych; ♦ lpBytesReturned — wskaźnik do danej typu DWORD, zawierający liczbę bajtów zwróconych do lpOutBuffer; ♦ lpOverlapped — wskaźnik do struktury OVERLAPPED. Funkcja DeviceIoControl() w przypadku prawidłowego w ykonania zwraca wartość niezerową. Kod błędu wykonania można odzyskać za pom ocą funkcji G etL astE rror(). Funkcja DeviceIoControl() wysyła rozkazy z odpowiednim kodem IOCTL_Xxx do ste rownika urządzenia, który przekazuje je do samego urządzenia (patrz rozdział 3.). K o dy kontrolne, których format jest pokazany na rysunku 6.11, są tworzone za pomocą makrodefinicji CTL_CODE: # d e fin e CTL_COD E(DeviceType, ((D eviceType)
<< 16)
F u n c tio n ,
| ((Access)
Method, A c c e s s )
<< 14)
| ((F u n c tio n )
( << 2)
| ( M e th od )
)
31
16 D e v ic e type
15 A cce ss
14 13
2 1 Function
0 M ethod
Rysunek 6.11. Format rozkazu IOCTL_Xxx Znaczenie poszczególnych elementów pokazanych na rysunku 6.11 i zaadaptowanych w kodzie pokazanym na listingu 6.14 je st następujące: ♦ Device type — definiuje typ urządzenia, reprezentowany przez odpowiednią stałą symboliczną, do którego będą wysyłane rozkazy; ♦ Access — zawiera rodzaj dostępu do pliku sterownika urządzenia; ♦ Function — zawiera kod funkcji dla kategorii urządzeń; kody z zakresu 0 - 2047 są zarezerwowane przez system operacyjny; ♦ Method — określa sposoby buforowania danych (lub brak buforowania). Listing 6.14. Kod modułu usb_R6_6.cpp # i n c l u d e #pragma o p t i o n
push -a 1
# i n c lu d e < s e tu p a p i> #pragma o p t i o n
pop
# in c lu d e # in c lu d e
238
USB. Praktyczne programowanie z Windows API w C++
# d e fin e b u ff e rL e n g th
126
# d e f i n e H I D _ C T L _ C O D E ( id )
\
CTL_CODE ( F IL E_ D E VI CE _ KE YB O A R D , # d e f i n e H I D _ B U F F E R _ C T L_ C O D E ( id )
CTL_CODE ( F IL E_ D E VI CE _ KE YB O A R D , # d e f i n e H I D _ I N _ C T L _ C O D E ( id )
(id ),
METHOD_NEITHER,
(id ),
METHOD_BUFFERED,
(id ),
METHOD_IN_DIRECT,
(id ),
METHOD_OUT_DIRECT,
F IL E_ ANY _A CCE SS )
\ F IL E_ ANY _A CCE SS )
\
CTL_CODE ( F IL E_ D E VI CE _ KE YB O A R D , # d e f i n e HID _ O UT _ CT L _C OD E( id )
F IL E_ ANY _A CCE SS )
\
CTL_CODE ( F IL E_ D E VI CE _ KE YB O A R D ,
F IL E_ ANY _A CCE SS )
# d e f i n e I O CT L _G ET _PH YS IC AL _D ESC RIP TOR
HID_OUT_CTL_CODE(102)
# d e f i n e IOCTL_HID_FLUSH_QUEUE
HID_ CTL _CO DE (10 1)
# d e f i n e IOCTL_HID_G ET_ COLLE CT ION_D ESC RIPTO R
HID_ CTL_COD e ( i 00)
# d e f i n e IOCTL_HID_GET_COLLECTION_INFORMATION
HI D_ BUF FE R_ CTL _C ODE (10 6)
# d e f i n e IOCTL_HID_GET_FEATURE
HID_OUT_CTL_CODE(100)
# d e f i n e IOCTL_HID_GET_HARDWARE_ID
HID_OUT_CTL_COD e ( i 03)
# d e f i n e IO CTL_HID_GET_INDEXED_STRING
HID_OUT_CTL_COD e ( i 20)
# d e f i n e IOCTL_HID_GET_INPUT_REPORT
HID_OUT_CTL_COD e ( i 04)
# d e f i n e IOCTL_HID_GET_MANUFACTURER_STRING
HID_OUT_CTL_COD e ( i 10)
# d e f i n e IOCTL_GET_NUM_DEVICE_INPUT_BUFFERS
HI D_ BUF FE R_ CTL _C ODE (10 4)
# d e f i n e IOCTL_HID_GET_POLL_FREQUENCY_MSEC
HID_BUFF ER _C TL_C OD e ( i 02)
# d e f i n e IOCTL_HID_GET_PRODUCT_STRING
HID_OUT_CTL_CODE(111)
# d e f i n e IOCTL_HID_GET_SERIALNUMBER_STRING
HID_OUT_CTL_COD e ( i 12)
# d e f i n e IO CTL _HID_S ET_FEATURE
HID _ IN _C TL _C OD E( 1 00 )
# d e f i n e IO CTL _SE T_ NUM_DEVICE_INPUT_BUFFERS
HI D_ BUF FE R_ CTL _C ODE (10 5)
# d e f i n e IOCTL_HID_SET_OUTPUT_REPORT
HID _ IN _C TL _C OD E( 1 01 )
# d e f i n e IOCTL_HI D_SET_POLL_FREQUENCY_MSEC
HI D_ BUF FE R_ CTL _C ODE (10 3)
# d e f i n e IOCTL_HID_GET_DRIVER_CONFIG
HI D_ BUF FE R_ CTL _C ODE (10 0)
# d e f i n e IOCTL_HID_ SET _DRIV ER _CON FIG
hid_ buffer _ c tl_co d e( i
# d e f i n e IOCTL_HID_GET_MS_GENRE_DESCRIPTOR
HID_OUT_CTL_CODE(121)
u s i n g n a m es p a ce s t d ; v o id d is p la y E r r o r ( c o n s t
char* m sg)i
c o u t << msg << e n d l ; system ("PAUSE"); e x it(0 ); j;
/ / -------------------------------------------------t e m p l a t e < c l a s s T> in lin e
v o i d r e l e a s e M e m o r y ( T &x)
i assert(x de le te
[]
!= N U L L ) ; x;
x = NULL; j
/ / -------------------------------------------------GUID c l a s s G u i d ; HMODULE h H i d L i b ; DWORD m em be rI n de x = 0; DWORD d e v i c e I n t e r f a c e D e t a i l D a t a S i z e ; DWORD r e q u i r e d S i z e ; HDEVINFO d e v i c e I n f o S e t ;
0i )
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
SP_DEVICE_INTERFACE_DATA deviceInterfaceData; PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData = NULL; / / -------------------------------------------------------------------typedef struct _HIDD_ATTRIBUTES { ULONG Size; USHORT VendorID; USHORT ProductID; USHORT VersionNumber; } HIDD_ATTRIBUTES, *PHIDD_ATTRIBUTES; / / -------------------------------------------------------------------typedef struct _HID_COLLECTION_INFORMATION { //hidclass.h ULONG DescriptorSize; BOOLEAN Polled; UCHAR Reserved1[1]; USHORT VendorID; USHORT ProductID; USHORT VersionNumber; } HID_COLLECTION_INFORMATION, *PHID_COLLECTION_INFORMATION; / / -------------------------------------------------------------------HANDLE hidDeviceObject = INVALID_HANDLE_VALUE; HIDD_ATTRIBUTES hiddAttributes; HID_COLLECTION_INFORMATION collectionInformation; wchar_t buffer[bufferLength]; DWORD lpBytesReturned = 0; / / -------------------------------------------------------------------typedef USHORT USAGE, *PUSAGE; typedef struct _HIDP_PREPARSED_DATA *PHIDP_PREPARSED_DATA; PHIDP_PREPARSED_DATA preparsedData; typedef struct _HIDP_CAPS { USAGE Usage; USAGE UsagePage; USHORT InputReportByteLength; USHORT OutputReportByteLength; USHORT FeatureReportByteLength; USHORT Reserved[17]; USHORT NumberLinkCollectionNodes; USHORT NumberInputButtonCaps; USHORT NumberInputValueCaps; USHORT NumberInputDataIndices; USHORT NumberOutputButtonCaps; USHORT NumberOutputValueCaps; USHORT NumberOutputDataIndices; USHORT NumberFeatureButtonCaps; USHORT NumberFeatureValueCaps; USHORT NumberFeatureDataIndices; } HIDP_CAPS, *PHIDP_CAPS; HIDP_CAPS capabilities; / / -------------------------------------------------------------------int main() { long ( stdcall* HidP_GetCaps)(IN PHIDP_PREPARSED_DATA PreparsedData, OUT PHIDP_CAPS Capabilities); void (
stdcall *HidD_GetHidGuid)(OUT LPGUID HidGuid);
239
240
USB. Praktyczne programowanie z Windows API w C++
hHidLib = LoadLibrary("C:\\Windows\\System32\\HID.DLL"); if (IhHidLib) displayError("Błąd dołączenia biblioteki HID.DLL."); (FARPROC&) HidD_GetHidGuid=GetProcAddress(hHidLib, "HidD_GetHidGuid"); (FARPROC&) HidP_GetCaps=GetProcAddress(hHidLib, "HidP_GetCaps"); if (!HidD_GetHidGuid || !HidP_GetCaps){ FreeLibrary(hHidLib); displayError("Nie znaleziono żadnych funkcji eksportowych.\n"); } HidD_GetHidGuid(&cl assGuid); devicelnfoSet = SetupDiGetClassDevs(&classGuid, NULL, NULL, DIGCF_PRESENT | DIGCF_INTERFACEDEVICE); if (deviceInfoSet == INVALID_HANDLE_VALUE){ FreeLibrary(hHidLib); displayError("Nie zidentyfikowano podłączonych urządzeń.\n"); } deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); hiddAttributes.Size = sizeof(HIDD_ATTRIBUTES); while(SetupDiEnumDeviceInterfaces(deviceInfoSet, NULL, &classGuid, memberIndex, &devi ceInterfaceData)){ memberIndex++; //inkrementacja numeru interfejsu SetupDiGetDeviceInterfaceDetail(devi ceInfoSet, &deviceInterfaceData, NULL, 0, &deviceInterfaceDetailDataSize, NULL); deviceInterfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA) new DWORD[deviceInterfaceDetailDataSize]; deviceInterfaceDetailData->cbSize=sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); if (!SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &devi ceInterfaceData, deviceInterfaceDetailData, devi ceInterfaceDetailDataSi ze, &requiredSize, NULL)){ releaseMemory(devi ceInterfaceDetailData); SetupDiDestroyDevi ceInfoList(devi ceInfoSet); displayError("Nie można pobrać informacji o in terfejsie.\n"); } hidDeviceObject=CreateFile(deviceInterfaceDetailData->DevicePath, GENERIC_READ | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL ); if(hidDeviceObject != INVALID_HANDLE_VALUE) { //cout << "\n"<< deviceInterfaceDetailData->DevicePath << "\n"; DeviceIoControl(hidDeviceObject, IOCTL_HID_GET_MANUFACTURER_STRING, NULL,0,buffer,sizeof(buffer),&lpBytesReturned, NULL); printf("Producent = %ls\n", buffer); DeviceIoControl(hidDeviceObject, IOCTL_HID_GET_COLLECTION_INFORMATION, NULL,0,&collectionInformation, sizeof(HID_COLLECTION_INFORMATION), &lpBytesReturned, NULL); hiddAttributes.VendorID = collectionInformation.VendorID;
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
241
h id d A ttr ib u te s .P r o d u c t ID = c o lle c tio n In fo rm a tio n .P ro d u c tI D ; h i d d A t t r i b u t e s . V e r s i onNumber = c o l l e c t i o n I n f o r m a t i o n . V e r s i o n N u m b e r ; p r in t f ( " V I D / P I D / w e r s j a = % x /% x /% x \n " ,h id d A ttrib u te s .V e n d o rID , h id d A ttrib u te s .P ro d u c tID , h i d d A t t r i b u t e s . V e r s io n N u m b e r);
/*if(!DeviceIoControl(hidDeviceObject, IOCTL_HID_GET_COLLECTION_INFORMATION, NULL,0,&collectionInformation, sizeof(HID_COLLECTION_INFORMATION), &lpBytesReturned, NULL)) break; */ p r e p a r s e d D a t a = ( PH ID P_ PR EPA RSE D_ DA TA) \ new D W O R D [ c o l l e c t i o n I n f o r m a t i o n . D e s c r i p t o r S i z e ] ; D e v ic e Io C o n tro l(h id D e v ic e O b je c t,
IOCTL_HID_G ET_ COLLE CT ION_ DE SC RIPT OR,
N U L L ,0 ,p re p a r s e d D a ta ,c o lle c t io n I n fo rm a tio n .D e s c rip to rS iz e , & lp B y te s R e tu rn e d , H id P_G etC aps(prep arsedD ata,
NULL);
& c a p a b ilitie s ) ;
p rin tf("U s a g e = % x\n U s a g e P a g e = % x\n In p u tR e p o rtB yte Le n g th = % d \n " "O u tpu tR epo rtB yteLe n gth = % d \n Fea tu reR ep ortB yteLen gth = % d\n " "N u m b e rLin k C o lle ctio n N o d e s = % d \n " "N u m b e r I n p u t B u t t o n C a p s = % d \ n N u m b e r I n p u t V a l u e C a p s = % d \ n \ n " , c a p a b ilitie s .U s a g e ,
c a p a b ilitie s .U s a g e P a g e ,
ca p a b ilitie s .In p u tR e p o rtB y te L e n g th , c a p a b ilitie s .O u tp u tR e p o r tB y te L e n g th , c a p a b ilitie s .F e a tu re R e p o rt B y te L e n g t h , c a p a b ilitie s .N u m b e rL in k C o lle c tio n N o d e s , c a p a b ilitie s .N u m b e rIn p u tB u tto n C a p s, c a p a b ilit ie s . N u m b e r I n p u t V a l ueCaps); re leaseM em o ry(p rep arsed D ata); C lo s e H a n d le (h id D e v ic e O b je c t); } releaseM em ory(d evi c e I n t e r f a c e D e t a ilD a t a ) ;
};//koniec while S e tu p D iD e s tro y D e v ic e In fo L is t(d e v ic e In fo S e t); F re e L ib ra ry ( h H id L ib ) ; c o u t << e n d l ; system ("PAUSE"); return
0;
}
/ / -------------------------------------------------------------------Podobnie ja k W riteF ile() i R eadFile(), funkcja D eviceIoControl() może pracować w dwóch trybach: ♦ synchronicznym — odpowiedź sterownika urządzenia można odczytać natychmiast po zakończeniu działania D eviceIoControl(); ♦ asynchronicznym — sterownik urządzenia odpowie w stosownym czasie. W tym przypadku należy zdefiniować zdarzenie hEvent, które wystąpi w trakcie odpowiedzi sterownika (listing 6.9).
242
USB. Praktyczne programowanie z Windows API w C++
N a listingu 6.14 zaprezentowano przykłady wykorzystania funkcji DeviceIoControl() pracującej w trybie synchronicznym z rozkazami IOCTL_HID_GET_MANUFACTURER_STRING, IOCTL_HID_GET_COLLECTION_INFORMATION oraz IOCTL_HID_GET_COLLECTION_DESCRIPTOR (patrz rozdział 4.). W ynik działania programu obrazuje rysunek 6.12. Rysunek 6.12. Wynik działania aplikacji proj_USB_R6_6
U S B .P raktyczne p ro g ra m o w a n ... P r o d u c e n t = T e c h n o lo g y I n n o v a t io n V I D / P I D / w e rs ja = 22 ba/1 08/31 3 Usage=4 UsagePage= l I n p u tR e p o rtB y te L e n g th -7 O u t p u t R e p o rtB y te L e n g th = 5 F e a t u r e R e p o r tB y te L e n g th = 0 N um berLi n k C o l1 e c t i onNodes=2 N u m b erIn p u tB u t to n C a p s = l N u m b e r ln p u t V a lueCaps=5
A b y k o n ty n u o w a ć ,
n aci ś n ij
d o w o ln y k l a w i s z
.
Rozkazy z modułu hidclass.h Windows Driver Kit w module hidclass.h udostępnia szereg standardowych rozkazów umożliwiających wykonywanie z niskiego poziomu operacji wejścia-wyjścia na urzą dzeniach klasy HID. N a rysunku 6.13 przedstawiono kilka z nich. Rysunek 6.13. Standardowe rozkazy IOCTL_Xxx dostępne z poziomu modułu hidclass.h
L isting 6.15 przedstaw ia przykład zdefiniow anej w module usb_R6_7.cpp funkcji g e tC o lle c tio n D e sc rip to r() pracującej w trybie synchronicznym, za pom ocą której przy wykorzystaniu rozkazu IOCTL_HID_GET_COLLECTION_INFORMATION można odczytać wszystkie zasoby przechowywane aktualnie w elementach struktury HID_COLLECTION_ INFORMATION. N a rysunku 6.14 pokazano wynik działania programu. Listing 6.15. Jeden ze sposobów odczytania zawartości struktury HID COLLECTION INFORMATION # i n c l u d e #pragma o p t i o n
p u s h -a 1
# i n c lu d e < s e tu p a p i> #pragma o p t i o n
pop
# in c lu d e # in c lu d e # in clu d e
" C : \\W IN D D K \\7 6 0 0 .1 6 3 8 5 .1 \\in c \\d d k \\h id c la s s .h "
u s i n g n a m es p a ce s t d ; //
--------------------------------------------------------------
v o id d is p la y E r r o r ( c o n s t c o u t << msg << e n d l ; system ("PAUSE"); e x it(0 );
c h a r * msg){
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
]; / / ---------------------------------------------------------------------------template inline void releaseMemory(T &x) j assert(x != NULL); delete [] x; x = NULL; ] / / ---------------------------------------------------------------------------GUID classGuid; HMODULE hHidLib; DWORD memberIndex = 0; DWORD devi ceInterfaceDetailDataSize; DWORD requiredSize; HDEVINFO deviceInfoSet; SP_DEVICE_INTERFACE_DATA deviceInterfaceData; PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData = NULL; / / ---------------------------------------------------------------------------HANDLE hidDeviceObject = INVALID_HANDLE_VALUE; PHID_COLLECTION_INFORMATION collectionInformation; DWORD lpBytesReturned = 0; / / ---------------------------------------------------------------------------PHID_COLLECTION_INFORMATION getCollectionDescriptor() j PHID_COLLECTION_INFORMATION descriptor; descriptor = new \ HID_COLLECTION_INFORMATION[(sizeof(HID_COLLECTION_INFORMATION))]; descriptor->DescriptorSize = sizeof(HID_COLLECTION_INFORMATION); DWORD lpBytesReturned=O; bool result; result = DeviceIoControl(hidDeviceObject, IOCTL_HID_GET_COLLECTION_INFORMATION, descriptor,descriptor->DescriptorSize, descriptor, descriptor->DescriptorSize, &lpBytesReturned, NULL); if(!resu lt) releaseMemory(descriptor); return result ? descriptor : NULL; ] / / ---------------------------------------------------------------------------int main() j void ( stdcall *HidD_GetHidGuid)(OUT LPGUID HidGuid); hHidLib = LoadLibrary("C:\\Windows\\System32\\HID.DLL"); if (!hHidLib) displayError("Błąd dołączenia biblioteki HID.DLL."); (FARPROC&) HidD_GetHidGuid=GetProcAddress(hHidLib, "HidD_GetHidGuid"); if (!HidD_GetHidGuid){ FreeLibrary(hHidLib); displayError("Nie znaleziono żadnych \ funkcji eksportowych.\n"); ] HidD_GetHidGuid(&cl assGuid);
243
24 4
USB. Praktyczne programowanie z Windows API w C++
deviceInfoSet = SetupDiGetClassDevs(&classGuid, NULL, NULL, DIGCF_PRESENT | DIGCF_INTERFACEDEVICE); if (deviceInfoSet == INVALID_HANDLE_VALUE){ FreeLibrary(hHidLib); displayError("Nie zidentyfikowano podłączonych urządzeń.\n"); } deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); while(SetupDiEnumDeviceInterfaces(deviceInfoSet, NULL, &classGuid, memberIndex, &deviceInterfaceData)){ memberIndex++; //inkrementacja numeru interfejsu SetupDiGetDeviceInterfaceDetail(devi ceInfoSet,&devi ceInterfaceData, NULL,0,&deviceInterfaceDetailDataSize,NULL); deviceInterfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA) new DWORD[deviceInterfaceDetailDataSize]; deviceInterfaceDetailData->cbSize=sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); if (!SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &deviceInterfaceData, deviceInterfaceDetailData, devi ceInterfaceDetailDataSi ze, &requiredSize, NULL)){ releaseMemory(devi ceInterfaceDetailData); SetupDiDestroyDevi ceInfoList(devi ceInfoSet); displayError("Nie można pobrać informacji o in terfejsie.\n"); } hidDeviceObject=CreateFile(deviceInterfaceDetailData->DevicePath, GENERIC_READ | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL ); if(hidDeviceObject != INVALID_HANDLE_VALUE) { cout << "\n"<< deviceInterfaceDetailData->DevicePath << "\n"; PHID_COLLECTION_INFORMATION getCollectionDesc = \ getCollectionDescriptor(); if(getCollectionDesc) { printf("Zawartość deskryptora\n"); printf("DescriptorSize: %u\n",getCollectionDesc->DescriptorSize); printf("Polled: %d\n",getCollectionDesc->Polled); printf("VendorID: 0x%x\n",getCollectionDesc->VendorID); printf("ProductID: 0x%x\n",getCollectionDesc->ProductID); printf("VersionNumber: 0x%x\n",getCollectionDesc->VersionNumber); getCollectionDesc = NULL; } CloseHandle(hidDeviceObject); } releaseMemory(devi ceInterfaceDetailData); };//koniec while SetupDiDestroyDeviceInfoList(deviceInfoSet); FreeLibrary(hHidLib); cout << endl; system("PAUSE"); return 0; } / / --------------------------------------------------------------------
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
Rysunek 6.14. Aplikacja proj_USB_R6_7 w trakcie działania
245
U SB.Praktyczne programowanie\Rozdzia+ 6\R6_7\proj_USB_R6_7.exe
K\?\hid#vid_22ba&pid_0108#8A59f5d85&0&0000#{4dle55b2-fl6f-llcf-88cb-001111000030} ¡Z a w a rto ść d e s k r y p t o r a Des c r i p t o r S i z e : 1220 P o lle d : 0 Y e n d o r lD : Ox22ba P ro d u ctID : 0x108 Y e r s io n N u m b e r : 0x313 A b y k o n ty n u o w a ć ,
naci ś n ij
d ow o ln y k l a w is z
• • • _
Rozkazy z modułu usbioctl.h M oduł usbioctl.h zawiera rozkazy, za pom ocą których można odczytać kompletne in form acje o w szystkich urządzeniach przyłączonych do m agistrali USB. N a listingu 6.16 zaprezentowano nieskomplikowany przykład praktycznego wykorzystania w apli kacji użytkownika dwóch rozkazów: IOCTL_GET_HCD_DRIVERKEY_NAME oraz IOCTL_USB_ GET_ROOT_HUB_NAME, identyfikujących standardowe rozszerzone kontrolery PCI do USB, zaś na rysunku 6.15 pokazano program w trakcie działania. Listing 6.16. Identyfikacja standardowego rozszerzonego kontrolera PCI do USB #include #pragma option push -a1 #include #pragma option pop #include #include "D:\\WINDDK\\7600.16385.1\\inc\\api\\usbioctl.h" using namespace std; //
--------------------------------------------------------------
void getRootHubName(HANDLE devHandle) { USB_ROOT_HUB_NAME rootHubName; PUSB_ROOT_HUB_NAME pRootHubName; DWORD outBufferSize, bytesReturned; if (DeviceIoControl(devHandle, IOCTL_USB_GET_ROOT_HUB_NAME, &rootHubName, sizeof(rootHubName), &rootHubName, sizeof(rootHubName), &outBufferSize, NULL)) { outBufferSize = rootHubName.ActualLength; pRootHubName = new USB_ROOT_HUB_NAME[outBufferSize]; if (DeviceIoControl (devHandle, IOCTL_USB_GET_ROOT_HUB_NAME, &rootHubName, sizeof(rootHubName), pRootHubName, outBufferSize, &bytesReturned, NULL)) { printf("\nRoot Hub USB:\n%ls\n",&pRootHubName->RootHubName[0]); }; delete [] pRootHubName; }; return; };
/ / ---------------------------------------------------------------
246
USB. Praktyczne programowanie z Windows API w C++
void getHCDDriverKeyName(HANDLE devHandle) { USB_HCD_DRIVERKEY_NAME driverKeyName; PUSB_HCD_DRIVERKEY_NAME pDriverKeyName; DWORD outBufferSize, bytesReturned; if (DeviceIoControl(devHandle, IOCTL_GET_HCD_DRIVERKEY_NAME, &driverKeyName, sizeof(driverKeyName), &driverKeyName, sizeof(driverKeyName), &outBufferSize, NULL)) { outBufferSize = driverKeyName.ActualLength; pDriverKeyName = new USB_HCD_DRIVERKEY_NAME[outBufferSize]; if (DeviceIoControl(devHandle, IOCTL_GET_HCD_DRIVERKEY_NAME, &driverKeyName, sizeof(driverKeyName), pDriverKeyName, outBufferSize, &bytesReturned, NULL)) { printf("\nIdentyfikacja standardowego rozszerzonego kontrolera PCI do USB: \n%ls\n",&pDriverKeyName->DriverKeyName[0]); } delete [] pDriverKeyName; // int main() { HANDLE HCDHandle; //HCD - Host Controller Device char HCDName[] = "\\\\.\\HCD0"; UINT i = 0; do { HCDName[7] = i + '0 '; HCDHandle = CreateFile(HCDName, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0 , NULL); if (HCDHandle != INVALID_HANDLE_VALUE) { getRootHubName(HCDHandle); getHCDDriverKeyName(HCDHandle); CloseHandle(HCDHandl e); } i++; } while(HCDHandle != INVALID_HANDLE_VALUE); system("PAUSE"); return 0; } / / --------------------------------------------------------------------
Rysunek 6.15. Aplikacja proj_USB_R6_8 w trakcie działania
U SB.Praktyczne p ro g ra m o w a n ie \R o z d z ia ł 6\R 6_8\proj_USB_R6_8.exe i D R o o t Hub USB: U SB # RO O T_H U B2O # 4& 381724a2& O # {fl8a0e88-c3O c-lld0-8815-O O aO c9O 6bed8} I d e n t y f i k a c j a sta n d a rd o w e g o r o z s z e r z o n e g o k o n t r o l e r a PCI do USB: { 3 6 f c 9 e 6 0 - c 4 6 5 - l l c f -805 6-44 45 5 35 40000}\0001 R o o t Hub USB: U S B # R 0 0 T _ H U B 2 0 # 4 & lf9 8 d 6 6 d & 0 # { fl8 a 0 e 8 8 -c 3 0 c-lld 0 -8 8 1 5 -0 0 a 0 c9 0 6 b e d 8 } I d e n t y f i k a c j a sta n d a rd o w e g o r o z s z e r z o n e g o k o n t r o l e r a PCI do USB: { 3 6 f c 9 e 6 0 - c 4 6 5 - l l c f - 8 0 5 6 -4 4 4 5 5 35 40000}\0000 A b y k o n ty n u o w a ć , n a c i ś n i j d o w o ln y k l a w i s z • • • _
1^
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
247
Identyfikacja urządzeń przyłączonych do koncentratora USB N a rysunku 6.16 zaprezentowano przykład skonfigurowanego przez autora testowego systemu składającego się z komputera jako jednostki centralnej oraz (przyłączonych za pośrednictw em 4-portowego koncentratora USB) trzech urządzeń: kamery cyfro wej, miernika uniwersalnego (podłączonego za pom ocą adaptera USB/RS 232C) oraz standardowej klawiatury. Rysunek 6.16. Fizyczna konfiguracja systemu, w ramach którego funkcjonuje program z listingu 6.17
Listing 6.17. Identyfikacja urządzeń podłączonych do koncentratora USB #include #include #pragma option push -a1 #include #pragma option pop #include #include "D:\\WINDDK\\7600.16385.1\\inc\\api\\usbioctl.h" //#include "D:\\Program Files\\Windows K its\\8.0\\include\\shared\\usbioctl.h" using namespace std; / / --------------------------------------------------------------void displayError(const char* msg){ cout << msg << endl; system("PAUSE"); exit(0); }; / / -------------------------------------------------------------------void getDSPortConnectionIndex(HANDLE devHandle, UCHAR connectionlndex)
248
USB. Praktyczne programowanie z Windows API w C++
{ USB_NODE_CONNECTION_NAME connectionName; PUSB_NODE_CONNECTION_NAME pConnectionName; ULONG outBufferSize = 0; ULONG bytesReturned; bool success; memset(&connectionName, 0, sizeof(connectionName)); connectionName.Connectionlndex = connectionlndex; success = DeviceIoControl(devHandle, IOCTL_USB_GET_NODE_CONNECTION_NAME, &connectionName, sizeof(connectionName), &connectionName, sizeof(connectionName), &outBufferSize, NULL); if (success) { outBufferSize = connectionName.ActualLength; pConnectionName = new USB_NODE_CONNECTION_NAME[outBufferSize]; pConnectionName->ConnectionIndex = connectionIndex; if (DeviceIoControl(devHandle, IOCTL_USB_GET_NODE_CONNECTION_NAME, pConnectionName, outBufferSize, pConnectionName, outBufferSize, &bytesReturned, NULL)) { printf("USB_NODE_CONNECTION_NAME.ConnectionIndex: %d\n", pConnectionName->ConnectionIndex); printf("USB_NODE_CONNECTION_NAME.NodeName: %ls\n", pConnectionName->NodeName[0]); }; delete [] pConnectionName; }; }; //
--------------------------------------------------------------
USHORT getNumberOfPorts(HANDLE devHandle) { ULONG bytesReturned; bool success; USHORT nDSPorts = 0; //Liczba dostępnych portów huba USB_NODE_INFORMATION nodeInformation; success = DeviceIoControl(devHandle, IOCTL_USB_GET_NODE_INFORMATION, &nodeInformation, sizeof(nodeInformation), &nodeInformation, sizeof(nodeInformation), &bytesReturned, NULL); if ((success) && (nodeInformation.NodeType == UsbHub)) nDSPorts = nodeInformation.u.HubInformation.\ HubDescri ptor.bNumberOfPorts; return nDSPorts; } / / -------------------------------------------------------------------void getDSPortData(HANDLE devHandle, UCHAR connectionIndex) { ULONG bytesReturned; bool success; PUSB_NODE_CONNECTION_INFORMATION pConnectionInformation = NULL; ULONG nBytes = sizeof(USB_NODE_CONNECTION_INFORMATION) + sizeof(USB_PIPE_INFO) * 30; //15 EP IN + 15 EP OUT pConnectionInformation = new USB_NODE_CONNECTION_INFORMATION[nBytes];
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
pConnectionInformation->ConnectionIndex = connectionIndex; success = DeviceIoControl(devHandle, IOCTL_USB_GET_NODE_CONNECTION_INFORMATION, pConnectionInformation, nBytes, pConnectionInformation, nBytes, &bytesReturned, NULL); if (¡success) { delete [] pConnectionInformation; return; } //patrz rozdział 3., tabela 3.3 printf(" \n Length: %d\n", pConnectionInformation->DeviceDescriptor.bLength); printf(" DescriptorType: %2.2x\n", pConnectionInformation->DeviceDescriptor.bDescriptorType); printf(" MaxPacketSizeO: %d\n", pConnectionInformation->DeviceDescriptor.bMaxPacketSize0); printf(" DeviceClass: %2.2x\n", pConnectionInformation->DeviceDescriptor.bDeviceClass); printf(" DeviceSubClass: %2.2x\n", pConnectionInformation->DeviceDescriptor.bDeviceSubClass); printf(" DeviceProtocol: %2.2x\n", pConnectionInformation->DeviceDescriptor.bDeviceProtocol); printf(" dProduct: %2.2x\n", pConnectionInformation->DeviceDescriptor.idProduct); printf(" dVendor: %2.2x\n", pConnectionInformation->DeviceDescriptor.idVendor); printf(" NumConfigurations: %2.2x\n", pConnectionInformation->DeviceDescriptor.bNumConfigurations); printf(" Manufacturer: %2.2x\n", pConnectionInformation->DeviceDescriptor.iManufacturer); printf(" SerialNumber: %2.2x\n", pConnectionInformation->DeviceDescriptor.iSerialNumber); printf(" Device is LS: %d\n", pConnectionInformation->LowSpeed); //patrz rozdział 3., tabela 3.12 printf(" EndpointAddress: %2.2x\n", pConnectionInformation->PipeList->EndpointDescriptor.bEndpointAddress); printf(" bInterval: %d\n", pConnectionInformation->PipeList->EndpointDescriptor.bInterval); printf(" bmAttributes %2.2x\n", pConnectionInformation->PipeList->EndpointDescriptor.bmAttributes); delete [] pConnectionInformation; } / / -------------------------------------------------------------------void getStringDescriptor(HANDLE devHandle, ULONG connectionIndex, UCHAR descriptorIndex) { PUSB_DESCRIPTOR_REQUEST pStringDescRequest = NULL; bool success; ULONG nBytes; ULONG bytesReturned; LANGID LanguageID = 0x0409; //English (United States) //Language Identifiers (LANGIDs) 3/29/00 Version 1.0, USB IF 2000 UCHAR
stringDescReqBuf[sizeof(USB_DESCRIPTOR_REQUEST) + MAXIMUM_USB_STRING_LENGTH];
249
250
USB. Praktyczne programowanie z Windows API w C++
nBytes = sizeof(stringDescReqBuf); pStringDescRequest = (PUSB_DESCRIPTOR_REQUEST)stringDescReqBuf; memset(pStringDescRequest, 0, nBytes); pStringDescRequest->ConnectionIndex = connectionIndex; pStringDescRequest->SetupPacket.bmRequest = 0x80; pStringDescRequest->SetupPacket.bRequest = USB_REQUEST_GET_DESCRIPTOR; pStringDescRequest->SetupPacket.wVal ue = USB_DESCRIPTOR_MAKE_TYPE_AND_INDEX(USB_STRING_DESCRIPTOR_TYPE, descriptorIndex); //(USB_STRING_DESCRIPTOR_TYPE << 8) | descriptorIndex; pStringDescRequest->SetupPacket.wIndex=LanguageID; pStringDescRequest->SetupPacket.wLength=(USHORT)(nBytes sizeof(USB_DESCRIPTOR_REQUEST)); success = DeviceIoControl(devHandle, IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION, pStringDescRequest, nBytes, pStringDescRequest, nBytes, &bytesReturned, NULL); if (!success) { return; } printf("\nDeskryptor łańcuchowy urządzenia: %ls\nprzyłączonego do portu: %d\n", &pStringDescRequest->Data[2], connectionIndex); return; } / / -------------------------------------------------------------------PUSB_STRING_DESCRIPTOR getStringDescriptor1(HANDLE devHandle, ULONG connectionIndex, UCHAR descriptorIndex) { PUSB_DESCRIPTOR_REQUEST stringDescRequest = NULL; PUSB_STRING_DESCRIPTOR stringDescriptor = NULL; PUSB_STRING_DESCRIPTOR stringDescNode = NULL; bool success; ULONG nBytes; ULONG bytesReturned; LANGID LanguageID = 0x0409; UCHAR
stringDescReqBuf[sizeof(USB_DESCRIPTOR_REQUEST) + MAXIMUM_USB_STRING_LENGTH];
nBytes = sizeof(stringDescReqBuf); stringDescRequest = (PUSB_DESCRIPTOR_REQUEST)stringDescReqBuf; stringDescriptor = (PUSB_STRING_DESCRIPTOR)(stringDescRequest+1); memset(stringDescRequest, 0, nBytes); stringDescRequest->ConnectionIndex = connectionIndex; stringDescRequest->SetupPacket.wValue=(USB_STRING_DESCRIPTOR_TYPE<<8) | descriptorIndex; stringDescRequest->SetupPacket.wIndex=LanguageID; stringDescRequest->SetupPacket.wLength=(USHORT)(nBytes sizeof(USB_DESCRIPTOR_REQUEST)); success = DeviceIoControl(devHandle, IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION,
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
stringDescRequest, nBytes, stringDescRequest, nBytes, &bytesReturned, NULL); if (¡success) { return NULL; } stringDescNode = new USB_STRING_DESCRIPTOR[(sizeof(USB_STRING_DESCRIPTOR)+ stringDescriptor->bLength)]; if (stringDescNode == NULL) { return NULL; } memcpy(stringDescNode, stringDescriptor, stringDescriptor->bLength); return stringDescNode; } / / -------------------------------------------------------------------PUSB_CONFIGURATION_DESCRIPTOR cfgDescriptor(HANDLE devHandle, ULONG connectionIndex, UCHAR descriptorIndex) { PUSB_DESCRIPTOR_REQUEST stringDescRequest = NULL; PUSB_CONFIGURATION_DESCRIPTOR cfgDesc = NULL, cfgDescNode = NULL; bool success; ULONG nBytes; ULONG bytesReturned; UCHAR
stringDescReqBuf[sizeof(USB_DESCRIPTOR_REQUEST) + sizeof(uSB_CONFIGURATION_DESCRIPTOR)];
nBytes = sizeof(stringDescReqBuf); stringDescRequest = (PUSB_DESCRIPTOR_REQUEST)stringDescReqBuf; cfgDesc= (PUSB_CONFIGURATION_DESCRIPTOR)(stringDescRequest+1); memset(stringDescRequest, 0, nBytes); stringDescRequest->ConnectionIndex = connectionIndex; stringDescRequest->SetupPacket.bRequest=USB_REQUEST_GET_CONFIGURATION; stringDescRequest->SetupPacket.wValue= USB_DESCRIPTOR_MAKE_TYPE_AND_INDEX(USB_CONFIGURATION_DESCRIPTOR_TYPE, descriptorlndex); stringDescRequest->SetupPacket.wIndex=0; stringDescRequest->SetupPacket.wLength=(USHORT)(nBytes sizeof(USB_DESCRIPTOR_REQUEST)); success = DeviceIoControl(devHandle, IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION, stringDescRequest, nBytes, stringDescRequest, nBytes, &bytesReturned, NULL); if (¡success) { return NULL; } cfgDescNode = new USB_CONFIGURATION_DESCRIPTOR[ (sizeof(USB_CONFIGURATION_DESCRIPTOR)+cfgDesc->bLength)]; if (cfgDescNode == NULL) { return NULL; } memcpy(cfgDescNode, cfgDesc, cfgDesc->bLength); return cfgDescNode; }
/ / --------------------------------------------------------------------
251
252
USB. Praktyczne programowanie z Windows API w C++
HANDLE openDevice(const GUID& devGUID, const char* vid) { DWORD memberlndex = 0; DWORD devi celnterfaceDetailDataSize; DWORD requiredSize; HDEVINFO devicelnfoSet; SP_DEVICE_INTERFACE_DATA devicelnterfaceData; PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData = NULL; HANDLE devObject = INVALID_HANDLE_VALUE; deviceInfoSet = SetupDiGetClassDevs(&devGUID, NULL, NULL, DIGCF_PRESENT | DIGCF_INTERFACEDEVICE); if (deviceInfoSet == INVALID_HANDLE_VALUE) displayError("Nie zidentyfikowano podłączonych urządzeń.\n"); deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); while(SetupDiEnumDeviceInterfaces(deviceInfoSet, NULL, &devGUID, memberIndex, &deviceInterfaceData)){ memberIndex++; //inkrementacja numeru interfejsu SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &deviceInterfaceData, NULL, 0, &deviceInterfaceDetailDataSize, NULL); deviceInterfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA) new DWORD[deviceInterfaceDetailDataSize]; deviceInterfaceDetailData->cbSize=sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); if (!SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &devi ceInterfaceData, deviceInterfaceDetailData, deviceInterfaceDetailDataSize, &requiredSize, NULL)){ delete [] deviceInterfaceDetailData; SetupDiDestroyDeviceInfoList(deviceInfoSet); } if (NULL != strstr(deviceInterfaceDetailData->DevicePath, vid)){ cout << "\n"<< deviceInterfaceDetailData->DevicePath << "\n"; devObject=CreateFile(deviceInterfaceDetailData->DevicePath, GENERIC_READ, FILE_SHARE_READ, NULL,OPEN_EXISTING,0, NULL); if(devObject==INVALID_HANDLE_VALUE) displayError("Nie można otworzyć urządzenia do transm isji."); else break; } delete [] deviceInterfaceDetailData; };//koniec while SetupDiDestroyDeviceInfoList(deviceInfoSet); return devObject; } / / -------------------------------------------------------------------int main() { const char* VID = "vid_03eb"; //Identyfikatory huba const char* PID = "pid_0902"; HANDLE deviceObject = INVALID_HANDLE_VALUE; deviceObject = openDevice(GUID_DEVINTERFACE_USB_HUB, VID); USHORT nDSPorts = getNumberOfPorts(deviceObject); for (int i = 1; i <= nDSPorts; i++) { getDSPortConnectionIndex(deviceObject, i);
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
253
} printf("\nLiczba portów huba = %d\n",nDSPorts); for (int i = 1; i<= nDSPorts; i++) { /* PUSB_STRING_DESCRIPTOR getStrDesc = getStringDescriptor1(deviceObject, i, 2); i f (getStrDesc) { printf("\nDeskryptor łańcuchowy urządzenia: %ls\nprzyłączonego do portu: %d\n", &getStrDesc->bString, i); getStrDesc = NULL; } */ /* PUSB_CONFIGURATION_DESCRIPTOR getCfgDesc = cfgDescriptor(deviceObject, i, 2); i f (getCfgDesc) { printf("\n=====Deskryptor konfiguracji====="); //patrz rozdział 3., tabela 3.19 printf("\nNumInterfaces: %d", getCfgDesc->bNumInterfaces); printf("\nConfigurationValue: %d", getCfgDesc->bConfigurationValue); printf("\nConfiguration: %d", getCfgDesc->iConfiguration); printf("\nDescriptorType: %d", getCfgDesc->bDescriptorType); printf("\nMaxPower: %d\n", getCfgDesc->MaxPower); getCfgDesc = NULL; } */ getStringDescriptor(deviceObject, i, 2); getDSPortData(deviceObject, i); } cout <
25 4
USB. Praktyczne programowanie z Windows API w C++
opisane w rozdziale 3. (patrz tabela 3.7). Jednoelementowa struktura USB_MI_PARENT_ INFORMATION przechowuje w polu ULONG NumberOfInterfaces liczbę interfejsów urzą dzenia złożonego. N a rysunku 6.17 zaprezentowano logiczną strukturę typów danych wykorzystywanych przez rozkaz IOCTL_USB_GET_NODE_INFORMATION.
Rysunek 6.17. Logiczna struktura typów danych wykorzystywanych przez rozkaz IOCTL_USB_GET_ NODEINFORMATION Zaprezentow ana na listingu 6.17 funkcja getNumberOfPorts() odczytuje liczbę aktu alnie dostępnych portów huba. W następnej kolejności program pow inien otrzymać informację o tym, czy i do któ rych portów huba zostały przyłączone zewnętrzne urządzenia wykonawcze. W tym celu aplikacje używ ają rozkazu IOCTL_USB_GET_NODE_CONNECTION_NAME, za pomocą któ rego można odczytać wszystkie aktualnie zapisane zasoby w elementach struktury: typedef struct _USB_NODE_CONNECTION_NAME { ULONG ConnectionIndex; ULONG ActualLength; WCHAR NodeName[1]; } USB_NODE_CONNECTION_NAME, *PUSB_NODE_CONNECTION_NAME; — gdzie pole ConnectionIndex przechowuje kolejne wartości liczbowe (począwszy od 1) przypisane do poszczególnych portów huba. Jeżeli ConnectionIndex = 0 oznacza to, że do określonego portu koncentratora jest aktualnie dołączone aktywne urządzenie. Pole ActualLength określa długość łańcucha znaków reprezentującego łącze symbo liczne identyfikujące dołączony hub. Tablica NodeName przechowuje łańcuch znaków w formacie Unicode, reprezentujący łącze symboliczne, które identyfikuje przyłączo ne do portu huba urządzenie. Jeżeli do huba nie przyłączono urządzeń lub do urządze nia (urządzeń) nie są przypisane łącza symboliczne, lub gdy przyłączone urządzenie
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
255
nie jest hubem (hubem na poziomie 0+n), zerowy indeks tablicy NodeName[0] zawiera wartość NULL. Zaprezentow ana na listingu 6.17 funkcja getDSPortConnectionIndex() odczytuje stan aktualnie dostępnych portów huba. Bardzo przydatną informacją bywa odczytanie wybranego indeksu deskryptora łańcu chowego dla każdego z przyłączonych urządzeń2. W tym celu programy używ ają roz kazu IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION oraz struktury danych: typedef struct _USB_DESCRIPTOR_REQUEST { ULONG ConnectionIndex; struct { UCHAR bmRequest; //bmRequestType zgodnie z dokumentacją USB [15, 16] UCHAR bRequest; USHORT wValue; USHORT wIndex; USHORT wLength; } SetupPacket; UCHAR Data[]; } USB_DESCRIPTOR_REQUEST, *PUSB_DESCRIPTOR_REQUEST; — gdzie dla standardowych urządzeń USB pola bmRequest (oznaczanego jako bmRequestType w dokumentacjach [15, 16]) oraz bRequest powinny domyślnie przechowywać wartości odpowiednio 0x80 oraz 0x06 (patrz tabela 6.5). W polu bmRequest są zapisane informacje o kierunku transferu, typie żądania oraz jego adresacie. Dokładny opis for matu pola bmRequest został zamieszczony w rozdziale 7. w tabeli 7.3. Pole bRequest za wiera kod rozkazu. Pole wValue przechowuje typ deskryptora, który może być jednym z: USB_DEVICE_DESCRIPTOR_TYPE (0x01), USB_CONFIGURATION_DESCRIPTOR_TYPE (0x02), USB_STRING_DESCRIPTOR_TYPE (0x03), USB_INTERFACE_DESCRIPTOR_TYPE (0x04) lub USB_ ENDPOINT_DESCRIPTOR_TYPE (0x05). W bardziej znaczącym bajcie pole wVal ue przechowu je typ deskryptora, zaś w mniej znaczącym — indeks deskryptora. Indeks deskryptora służy do wyboru określonego deskryptora (konfiguracji lub łańcuchowego), w przypad ku gdy urządzenie implementuje kilka deskryptorów tego samego typu. Do wypełnia nia pola wValue można użyć zdefiniowanej w module usb100.h makrodefinicji: USB_DESCRIPTOR_MAKE_TYPE_AND_INDEX(d, i), — gdzie d jest typem deskryptora, zaś i jego indeksem. W przypadku w yboru USB_CONFIGURATION_DESCRIPTOR_TYPE w tablicy Data zostanie um ieszczony nie tylko deskryptor konfiguracji, ale też wszystkie powiązane z nim (agregujące) deskryptory interfejsów i punktów końcowych oraz ewentualnie deskryptory producenta i specyficzne dla klasy urządzenia (patrz rozdział 3., rysunek 3.9). Z tego powodu należy zadbać o odpowiednie ustalenie rozmiaru bufora danych w roz kazie IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION, gdyż w przeciwnym wypad ku rozkaz ten nie będzie wykonany pomyślnie. D la deskryptorów łańcuchow ych pole wIndex przechowuje identyfikator języka [17], w którym jest zapisana informacja o urządzeniu, lub jest równe zero dla innych typów deskryptorów. W skaźnik Data wskazuje bufor danych zawierający opis deskryptora. 2 Niektóre urządzenia mogą nie zawierać deskryptorów łańcuchowych.
256
USB. Praktyczne programowanie z Windows API w C++
Pole wLength określa rozmiar pola danych w drugiej fazie przekazu kontrolnego (patrz rozdział 1.). W tabelach 6.4 i 6.5 przedstawiono odpowiednio opisy standardowych i właściwych dla urządzeń klasy HID rozkazów struktury USB_DESCRIPTOR_REQUEST. Tabela 6.4. Opis standardowych rozkazów struktury SetupPacket oraz pola Data struktury USB_DESCRIPTOR_REQUEST bmRequest
bRequest
wValue
wIndex
wLength
Data
00000000b (0x00) 00000001b (0x01) 00000010b (0x02)
USB REQUEST CLEAR FEATURE (0x01)
Selektor właściwości. Może przyjąć jedną z następujących wartości: ENDPOINT_HALT — 0 adresatemjest punkt końcowy FUNCTION_SUSPEND— 0 adresatemjest interfejs U1_ENABLE — 48
0 Numer interfejsu Numer punktu końcowego
0
Nie zawiera danych
0
1
Numer konfiguracji
0 lub ID języka opisu deskryptora łańcuchowego (patrz listing 6.17) Numer interfejsu
Długość deskryptora
Zawartość deskryptora
1
Numer alternatyw nego ustawienia interfejsu Interfejs urządzenia lub status punktu końcowego
U2_ENABLE — 49 LTM_ENABLE — 50 adresatemjest urządzenie 0
10000000b (0x80)
USB REQUEST GET CONFIGURATION (0x08)
10000000b (0x80)
USB REQUEST GET DESCRIPTOR (0x06)
Typ deskryptora (bardziej znaczący bajt) oraz indeks deskryptora (mniej znaczący bajt)
10000001b (0x81)
USB REQUEST GET INTERFACE (0x0A)
0
00000000b (0x00) 00000001b (0x01) 00000010b (0x02)
USB REQUEST GET_STATUS (0x00)
0
0 Numer interfejsu Numer punktu końcowego
2
00000000b (0x00)
USB REQUEST SET_ADDRESS (0x05)
Adres urządzenia
0
0
Nie zawiera danych
00000000b (0x00)
USB_REQUEST_ SET CONFIGURATION (0x09)
Numer konfiguracji
0
0
Nie zawiera danych
00000000b (0x00)
USB_REQUEST_ SET DESCRIPTOR (0x07)
Typ deskryptora (bardziej znaczący bajt) oraz indeks deskryptora (mniej znaczący bajt)
0 lub ID języka opisu deskryptora łańcuchowego
Długość deskryptora
Zawartość deskryptora
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
257
Tabela 6.4. Opis standardowych rozkazów struktury SetupPacket oraz pola Data struktury USB_DESCRIPTOR_REQUEST — ciąg dalszy bmRequest
bRequest
wValue
wIndex
wLength
Data
00000000b (0x00) 00000001b (0x01) 00000010b (0x02)
USB REQUEST SET FEATURE (0x03)
Selektor właściwości
0 Numer interfejsu Numer punktu końcowego
0
Nie zawiera danych
00000001b (0x02)
USB_REQUEST_ SET INTERFACE (0x0B)
Ustawienia alternatywne
Numer interfejsu
0
Nie zawiera danych
10000010b (0x82)
USB_REQUEST_ SYNC_FRAME (0x0C)
0
Numer punktu końcowego
2
Numer ramki
00000000b
USB_REQUEST_ SET_SEL (0x30)
0
0
6
00000000b
USB_REQUEST_ IZOCH DELAY (0x31)
Opóźnienie [ns] w transmisji potokiem izochronicznym USB 3.0, liczone od momentu wysłania pakietu przez host do momentu jego otrzymania przez urządzenie
0
0
Wartości czasu [ps], po których urządzenie USB 3.0 przechodzi do stanów Ul oraz U2 Nie zawiera danych
Tabela 6.5. Opis specyficznych dla klasy HID rozkazów struktury SetupPacket oraz pola Data struktury USB_DESCRIPTOR_REQUEST bmRequest
bRequest
wValue
wIndex
wLength
Data
10100001b (0xA1)
GET_REPORT (0x01)
Numer interfejsu
Długość raportu
Zawartość raportu
00100001b (0x21)
SET_REPORT (0x09)
Numer interfejsu
Długość raportu
Zawartość raportu
10100001b (0xA1)
GET_IDLE (0x02)
Typ raportu (bardziej znaczący bajt) oraz ID raportu (mniej znaczący bajt) Typ raportu (bardziej znaczący bajt) oraz ID raportu (mniej znaczący bajt) 0 (bardziej znaczący bajt) oraz ID raportu (mniej znaczący bajt)
Numer interfejsu
1
00100001b (0x21)
SET_IDLE (0x0A)
Czas trwania stanu braku aktywności (bardziej znaczący bajt) oraz ID raportu (mniej znaczący bajt)
Numer interfejsu
0
Czas trwania stanu braku aktywności Nie zawiera danych
258
USB. Praktyczne programowanie z Windows API w C++
Tabela 6.5. Opis specyficznych dla klasy HID rozkazów struktury SetupPacket oraz pola Data struktury USB_DESCRIPTOR_REQUEST — ciąg dalszy bmRequest
bRequest
wValue
wIndex
wLength
Data
10100001b (0xA1)
GET PROTOCOL (0x03)
0
Numer interfejsu
1
00100001b (0x21)
SET PROTOCOL (0x0B)
0 — protokół bootujący 1 — protokół raportu
Numer interfejsu
0
0— protokół bootujący 1— protokół raportu Nie zawiera danych
10000001b (0x81)
GET DESCRIPTOR (0x06)
Zawartość deskryptora
SET DESCRIPTOR (0x07)
0 lub ID języka opisu deskryptora łańcuchowego 0 lub ID j ęzyka opisu deskryptora łańcuchowego
Długość deskryptora
00000001b (0x01)
Typ deskryptora (bardziej znaczący bajt) oraz indeks deskryptora (mniej znaczący bajt) Typ deskryptora (bardziej znaczący bajt) oraz indeks deskryptora (mniej znaczący bajt)
Długość deskryptora
Zawartość deskryptora
N a listingu 6.17 zaprezentowano trójargum entow ą funkcję g e tS trin g D e sc rip to r(), która pobiera wybrany indeks deskryptora urządzenia USB. W materiałach dołączonych do książki, które można pobrać z witryny wydawnictwa Helion (moduł usb_R6_9.cpp), zamieszczono dodatkowo przykładow ą funkcję g e tS trin g D e sc rip to r1 (), która od czytuje deskryptor urządzenia umieszczony w polu bString omawianej w rozdziale 3. struktury USB_STRING_DESCRIPTOR. K olejną z zaprezentow anych w kodzie z listingu 6.17 funkcji jest getDSPortData(), która korzystając z rozkazu IOCTL_USB_GET_NODE_CONNECTION_INFORMATION, pobiera in formacje zapisane w deskryptorze urządzenia przyłączonego do portu huba (rysunek 6.18). Deskryptor urządzenia jest elementem struktury: typedef struct _USB_NODE_CONNECTION_INFORMATION { ULONG ConnectionIndex; USB_DEVICE_DESCRIPTOR DeviceDescriptor; UCHAR CurrentConfigurationValue; BOOLEAN LowSpeed; BOOLEAN DeviceIsHub; USHORT DeviceAddress; ULONG NumberOfOpenPipes; USB_CONNECTION_STATUS ConnectionStatus; USB_PIPE_INFO PipeList[]; } USB_NODE_CONNECTION_INFORMATION, *PUSB_NODE_CONNECTION_INFORMATION; — gdzie pole ConnectionIndex specyfikuje num er portu huba, do którego je st przy łączone urządzenie wykonawcze, a D eviceD escriptor je st deskryptorem urządzenia (patrz rozdział 3., tabela 3.3). Pole CurrentConfigurationValue jest wykorzystywane przez rozkaz USB_REQUEST_SET_CONFIGURATION w celu określenia aktualnej konfigura cji urządzenia wykonawczego. Znacznik LowSpeed określa, czy przyłączone do portu urządzenie funkcjonuje w trybie LS (TRUE), czy też je st w stanie transm itować dane
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
□ la w e rs ji U S B _ N O D E _ C O N N E C T IO N JN F O R M A T IO N _ E X L
« C p p S tru c t» U SB_NODE_CONNECTION_l WFORMATION +Connectionlndex: ULONG +D eviceD e s cri pto r: U SB_D EVI CE_DESCRI PTOR +CurrentConfigura1ionValue: U CHAR +LowSpeed: BOOLEAN +DevicelsHub: BOOLEAN +DeviceAddress: USHORT +N urn be rOfOpen Pipes: ULONG +ConnectionStatus: USB_CONNECTIQN_STATUS +PipeList: USB_PIPEJNFO
« e n u m e r a tio n » U SB_C ONNECTION_STATU S -•-NoDevi ce Connected +DeviceConnected -•-DeviceFaNedEnumeration -t-DeviceGeneralFailure -t-DeviceCau s e dOve rcu rre nt -•-DeviceNotEnoughPower +DeviceNotEnoughBandwidth -•-DeviceHubNestedToo Deeply -t-DevicelnLegacyHub ■^DeviceEnumerating -•-DeviceReset
259
« C p p S tru c t» USB DEVICE SPEED +UsbLowSpeed +UsbFullSpeed +UsbHighSpeed +UsbSuperSpeed
« C p p S tru c t» USB DEVICE DESCRIPTOR +bLength: UCHAR +bDescriptorType: UCHAR +bcdUSB: USHORT +bDeviceClass: UCHAR +bDeviceSubClass: UCHAR +bDevi ce Protocol: UCHAR +bMaxPacketSizeO: UCHAR +idVendor: USHORT +idProduct: USHORT +bcdDevice: USHORT +iManufacturer: UCHAR +i Pro du et: UCHAR +iSerialNumber: UCHAR +bNumConfigurations: UCHAR
« C p p S tru c t» USB_PIPE_INFO ^EndpointDescriptor: USB_ENDPOINT_DESCRIPTOR < ^Schedule Offset: ULONG
« C p p S tru c t» U SB_E NDPOI HT_DE SCRI PTOR +bLength: UCHAR +bDescriptorType: UCHAR +bEndpointAddress: UCHAR +bmAttributes: UCHAR +wMaxPacketSize: USHORT +blnterval: UCHAR
Rysunek 6.18. Logiczna struktura typów danych wykorzystywanych przez rozkaz IOCTL_USB_GET_ NODE_CONNECTION_INFORMATION(_EX) z większym i prędkościam i (FALSE)3. Znacznik DeviceIsHub określa, czy przyłączone do portu huba urządzenie je st koncentratorem (TRUE), czy też standardowym urządze niem wykonawczym (FALSE). Pole DeviceAddress przechowuje adres urządzenia przy łączonego do portu o indeksie ConnectionIndex. NumberOfOpenPipes jest liczbą poto ków przypisanych do portu, a ConnectionStatus jest elementem typu wyliczeniowego USB_CONNECTION_STATUS, który przechowuje status przyłączonego do portu huba urzą dzenia. W skaźnik PipeList wskazuje tablicę struktur: typedef struct _USB_PIPE_INFO { USB_ENDPOINT_DESCRIPTOR EndpointDescriptor; ULONG ScheduleOffset; } USB_PIPE_INFO, *PUSB_PIPE_INFO; przechowującą deskryptor punktu końcowego (patrz rozdział 3., tabela 3.12) oraz pa rametr ScheduleOffset, który jest wykorzystywany przez sterownik portu do harmonogramowania odstępu czasowego ponawiania kolejnych przekazów izochronicznych i przerwaniowych. N a listingu 6.17 znajduje się także dwuargumentowa funkcja openDevice(), która uzy skuje dostęp do pokazanego na rysunku 6.16 huba. Argumentami tej funkcji powinny być: identyfikator GUID określający koncentratory USB (GUID_DEVINTERFACE_USB_HUB) 3 Istnieje rozszerzona wersja _EX omawianej struktury. Jedyna różnica pomiędzy strukturami USB_ NODE_CONNECTION_INFORMATION oraz USB_NODE_CONNECTION_INFORMATION_EX polega na tym, że pole LowSpeed występujące w pierwszej z nich zostało zastąpione polem Speed typu USB_DEVICE_SPEED (patrz rozdział 3., tabela 3.4).
260
USB. Praktyczne programowanie z Windows API w C++
oraz numer VID i (lub) PID przyłączonego do komputera huba. N a rysunku 6.19 za prezentowano omawiany program w trakcie działania. Rysunek 6.19. Aplikacja projektu proj_USB_R6_9 w trakcie działania
5 prog ramowanie\Rozdział 6 \\?\u sb# vi d_03eb&pi d_0902#6d9617a0b&0&2#{f18a0e88-c 3 0 c - ll d 0-8815 - 00a0c906bed8} U5B_NODE_CONNECTION_NAM E . Con ne c t i onI n d e x : 0 U5B_NODE_CONNECTION_NAM E . Con n e c t i onInd e x : 0 U5B_NODE_CONNECTION_NAME. Con ne c t i onInd e x : 0 U5B_l*)DE_CONNECTION_NAM E . Con n e c t i on I n d e x : 4 L ic z b a portow huba = 4 D e sk ry p to r łań cu ch ow y u r z ą d z e n ia : Samsung D i g i t a l Camera p rz y łą c z o n e g o do p o r t u : 1 L e n g th : 18 D e s c r ip t o r T y p e : Dl M a x P a cke tS izeO : 64 D e v iceCl a s s : 00 Devi ceSu bCl as s : 00 Devi c e P r o t o c o l: 00 d P r o d u c t: 1006 dV e n d o r: lf a b N u m C o n fig u ra tio n s : 02 M a n u fa ctu re r: 01 5er i a l Number: 03 D e v ic e i s L S : 0 E n d p o in tA d d re s s : b ln t e r v a l: bmAtt r i b ut es
02 0 40
D e sk ry p to r łań cu ch ow y u r z ą d z e n ia : U 5 B -S e ria l C o n t r o ll e r p rz y łą c z o n e g o do p o r t u : 2 Length : 18 D e s c r ip to rT y p e : 01 M a x P a ck e tS ize O : 64 Devi ce C l a s s : 00 Devi c e S u b C la s s : 00 Devi c e P r o t o c o l: 00 d P r o d u c t: 2303 dV e n d o r: 67b NumConfi g u r a t i o n s : 01 M a n u fa ctu re r : 01 5 e r i a l Number : 00 D e v ic e i s LS: 0 E n d p o in tA d d re s s : b ln t e r v a l: bmAtt r i b ut es
03 0 0a
D e sk ry p to r łań cu ch ow y u r z ą d z e n ia : p rz y łą c z o n e g o do p o r t u : 3 Length : 18 D e s c r ip to rT y p e : 01 M a xPacketS i ze O : 8 Devi c e C l a s s : 00 Devi ceSubCl a s s : 00 Devi c e P r o t o c o l: 00 d P r o d u c t: 8081 dV e n d o r: ffff NumConfi g u r a t i o n s : 01 M a n u fa ctu re r : 02 5 e r i a l Number : 00 D e v ic e i s LS: 1 E n d p o in tA d d re s s : b ln t e r v a l: bmAtt r i b ut es
X H - 0 0 1
03 2 08
D e sk ry p to r łań cu ch ow y u r z ą d z e n ia : p rz y łą c z o n e g o do p o r t u : 4
Jeżeli konfiguracja systemu przewiduje występowanie zarówno koncentratorów USB 2.0, ja k i USB 3.0, należy wykorzystać om ów ioną w rozdziale 3. (patrz tabela 3.9) strukturę USB_HUB_INFORMATION_EX oraz rozkaz IOCTL_USB_GET_HUB_INFORMATION_EX. Sposób postępowania został zaprezentowany w poniższym fragmencie kodu w funk cji getH ubInform ation(). N a rysunku 6.20 przedstawiono strukturę logiczną typów danych wykorzystywanych przez rozkaz IOCTL_USB_GET_HUB_INFORMATION_EX. #include "D:\\Program Files\\Windows Kits\\8.0\\include\\shared\\usbspec.h" //
--------------------------------------------------------------
void getHubInformation(HANDLE devHandle) { ULONG bytesReturned; bool success;
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
« e n u m e r a tio n » USB_HUB_TYPE
« C p p S tr u c t» U SB_HUB_I NFORMATION_EX O +HubType: USB_HUB_TYPE +H ighestPortNumber: U SHORT
261
------------------ +U sbRootH ub +Usb2QHub -HJsb30Hub
« C p p S tr u c t» USB_HUB_DESCRIPTOR ■i-bDescnptorLength: UCHAR ^bDescriptorType: UCHAR ^bNum berO fPorts: UCHAR ^wHubCharacterlstics: USHORT ^bPowerOnToPowerGood: UCHAR ł-bHubControlCurrent: UCHAR ^bRemoveAndPowerMask: UCHAR
« C p p S tr u c t» U SB_3€_HUB_DESCRIPTOR +bl_ength: UCHAR +bDescriptorType: UCHAR +bNumberOfPorts: UCHAR +w HubCharacteristics: U SHORT +bPowerOnToPowerGood: UCHAR +bHubC ontrolCurrent: UCHAR +bHubHdrDecLat: UCHAR +wHubDelay: U SHORT +Devi ce Removable: U SHORT
Rysunek 6.20. Logiczna struktura typów danych wykorzystywanych przez rozkaz IOCTL_USB_GET_ HUB_INFORMATION_EX w systemie Windows 8 z USB 3.0 USB_HUB_INFORMATION_EX hubInformation; success = DeviceIoControl(devHandle, IOCTL_USB_GET_HUB_INFORMATION_EX, &hubInformation, sizeof(hubInformation), &hubInformation, sizeof(hubInformation), &bytesReturned, NULL); if (success) { printf("NumberOfPorts hub USB 2.0: %d\n", hubInformation.u.UsbHubDescriptor.bNumberOfPorts); printf("DescriptorType hub USB 2.0: %2.2x\n", hubInformation.u.UsbHubDescriptor.bDescriptorType); printf("PowerOnToPowerGood hub USB 2.0: %u\n", hubInformation.u.UsbHubDescriptor.bPowerOnToPowerGood); printf("NumberOfPorts hub USB 3.0: %d\n", hubInformation.u.UsbHub30Descriptor.bNumberOfPorts); //... printf("HubControlCurrent: %u\n", hubInformation.HighestPortNumber); } else return; } / / -------------------------------------------------------------------W katalogu instalacyjnym W DK \7600.16385.1\...\Debuggers znajduje się aplikacja usbview.exe, z pom ocą której można w bardzo wygodny sposób otrzymać kompletne informacje na tem at aktualnie przyłączonych do kom putera urządzeń USB. Program ten może też stanowić dobry punkt odniesienia dla samodzielnie tworzonego oprogramowania.
262
USB. Praktyczne programowanie z Windows API w C++
Struktura URB Zdefiniow ana w zasobach W DK w module usb.h struktura URB (ang. USB Request Block) definiuje formaty dla w szystkich możliwych rozkazów, które m ogą być wysy łane do urządzenia USB za pośrednictwem sterownika kontrolera hosta. W tabeli 6.6 przedstawiono specyfikację tej struktury. Tabela 6.6. Specyfikacja struktury URB Typ strukturalny
Element unii
Znaczenie
_URB_HEADER
UrbHeader
Struktura przechowuje podstawowe informacje na temat żądań wysyłanych do sterownika kontrolera hosta: struct _URB_HEADER { USHORT Length ; USHORT Function ; USBD_STATUS Status ; } ; Najważniejszymi elementami tej struktury są pola Length, określające jej rozmiar, i Function, specyfikujące kod żądania o samokomentującym się nazewnictwie: URB_FUNCTION_SELECT_CONFIGURATION, URB_FUNCTION_SELECT_INTERFACE, URB_FUNCTION_ABORT_PIPE, URB_FUNCTION_GET_CURRENT_FRAME_NUMBER, URB_FUNCTION_CONTROL_TRANSFER, URB_FUNCTION_CONTROL_TRANSFER_EX, URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER, URB_FUNCTION_ISOCH_TRANSFER, URB_FUNCTION_RESET_PIPE, URB_FUNCTION_SYNC_RESET_PIPE, URB_FUNCTION_SYNC_CLEAR_STALL, URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE, URB_FUNCTION_GET_DESCRIPTOR_FROM_ENDPOINT, URB_FUNCTION_SET_DESCRIPTOR_TO_DEVICE, URB_FUNCTION_SET_DESCRIPTOR_TO_ENDPOINT, URB_FUNCTION_SET_FEATURE_TO_DEVICE, URB_FUNCTION_SET_FEATURE_TO_INTERFACE, URB_FUNCTION_SET_FEATURE_TO_ENDPOINT, URB_FUNCTION_SET_FEATURE_TO_OTHER, URB_FUNCTION_CLEAR_FEATURE_TO_DEVICE, URB_FUNCTION_CLEAR_FEATURE_TO_INTERFACE,
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
263
Tabela 6.6. Specyfikacja struktury URB — ciąg dalszy Typ strukturalny
Element unii
Znaczenie URB_FUNCTION_CLEAR_FEATURE_TO_ENDPOINT, URB_FUNCTION_CLEAR_FEATURE_TO_OTHER, URB_FUNCTION_GET_STATUS_FROM_DEVICE, URB_FUNCTION_GET_STATUS_FROM_INTERFACE, URB_FUNCTION_GET_STATUS_FROM_ENDPOINT, URB_FUNCTION_GET_STATUS_FROM_OTHER, URB_FUNCTION_VENDOR_DEVICE, URB_FUNCTION_VENDOR_INTERFACE, URB_FUNCTION_VENDOR_ENDPOINT, URB_FUNCTION_VENDOR_OTHER, URB_FUNCTION_CLASS_DEVICE, URB_FUNCTION_CLASS_INTERFACE, URB_FUNCTION_CLASS_ENDPOINT, URB_FUNCTION_CLASS_OTHER, URB_FUNCTION_GET_CONFIGURATION, URB_FUNCTION_GET_INTERFACE
_URB_SELECT_ INTERFACE
UrbSelectInterface
_URB_SELECT_ CONFIGURATION
UrbSelectConfiguration
_URB_PIPE_REQUEST
UrbPipeRequest
Specyfikuje format rozkazów dla urządzenia USB: struct _URB_SELECT_INTERFACE { struct _URB_HEADER Hdr; USBD_CONFIGURATION_HANDLE ConfigurationHandle ; USBD_INTERFACE_INFORMATION Interface ; } ; Określa format rozkazów konfiguracyjnych dla urządzenia USB: struct _URB_SELECT_CONFIGURATION { struct _URB_HEADER Hdr; PUSB_CONFIGURATION_DESCRIPTOR Configurati onDescriptor; USBD_CONFIGURATION_HANDLE ConfigurationHandle; USBD_INTERFACE_INFORMATION Interface; } Określa format rozkazów służących do resetowania zawartości potoku, który zawiera pakiet z informacjami o wystąpieniu błędu wewnętrznego urządzenia w przypadku transferu masowego lub przerwaniowego: struct _URB_PIPE_REQUEST { struct _URB_HEADER Hdr; USBD_PIPE_HANDLE PipeHandle ; } ;
264
USB. Praktyczne programowanie z Windows API w C++
Tabela 6.6. Specyfikacja struktury URB — ciąg dalszy
Typ strukturalny
Element unii
Znaczenie
_URB_FRAME_LENGTH_ CONTROL _URB_GET_FRAME_ LENGTH _URB_SET_FRAME_ LENGTH
UrbFrameLengthControl
Obecnie nieużywane
UrbGetFrameLength
Obecnie nieużywane
UrbSetFrameLength
Obecnie nieużywane
_URB_GET_CURRENT_ FRAME NUMBER
UrbGetCurrentFrame ^Number
_URB_CONTROL_ TRANSFER
UrbControl Transfer
Przechowuje aktualny numer ramki: struct _URB_GET_CURRENT_FRAME_NUMBER { struct _URB_HEADER Hdr; ULONG FrameNumber ; } Definiuje format rozkazów dla danych przesyłanych za pomocą transferu kontrolnego: struct _URB_CONTROL_TRANSFER { struct _URB_HEADER Hdr; USBD_PIPE_HANDLE PipeHandle; ULONG TransferFlags; ULONG TransferBufferLength; PVOID TransferBuffer; PMDL TransferBufferMDL; struct URB *UrbLink;
_URB_BULK_OR_ INTERRUPT_TRANSFER
URB ISOCH TRANSFER
UrbBulkOrInterrupt ^Transfer
UrbIsochronousTransfer
UCHAR SetupPacket[8]; } ; Definiuje format rozkazów dla danych wysyłanych za pomocą transferu masowego lub przerwaniowego oraz dla danych odbieranych przy użyciu transferu masowego: struct _URB_BULK_OR_INTERRUPT_TRANSFER { struct _URB_HEADER Hdr; USBD_PIPE_HANDLE PipeHandle; ULONG TransferFlags; ULONG TransferBufferLength; PVOID TransferBuffer; PMDL TransferBufferMDL; struct URB *UrbLink;
Definiuje format rozkazów dla danych wysyłanych lub odbieranych za pomocą transferu izochronicznego: struct _URB_ISOCH_TRANSFER { struct _URB_HEADER Hdr; USBD_PIPE_HANDLE PipeHandle; ULONG TransferFlags; ULONG TransferBufferLength; PVOID TransferBuffer; PMDL TransferBufferMDL; ULONG StartFrame;
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
265
Tabela 6.6. Specyfikacja struktury URB — ciąg dalszy Typ strukturalny
Element unii
Znaczenie ULONG NumberOfPackets; ULONG ErrorCount; USBD_ISO_PACKET_DESCRIPTOR IsoPacket[1]; ];
_URB_CONTROL_ DESCRIPTOR_REQUEST
UrbControlDescriptor ^Request
Elementy struktury umożliwiają pobranie lub odczytanie deskryptora urządzenia USB: struct _URB_CONTROL_DESCRIPTOR_REQUEST { struct _URB_HEADER Hdr; ULONG PVOID PMDL struct
TransferBufferLength ; TransferBuffer ; TransferBufferMDL ; _URB *UrbLink ;
UCHAR Index ; UCHAR DescriptorType ; USHORT LanguageId ; ] ;. .. _URB_CONTROL_GET_ STATUS_REQUEST
UrbControlGetStatus ^Request
Umożliwia pobranie statusu urządzenia, punktu końcowego, interfejsu lub innego elementu urządzenia zdefiniowanego przez użytkownika: struct _URB_CONTROL_GET_STATUS_REQUEST { struct _URB_HEADER Hdr; ULONG TransferBufferLength ; PVOID TransferBuffer ; PMDL TransferBufferMDL ; struct _URB *UrbLink ; USHORT Index ; ] ;. ..
_URB_CONTROL_ FEATURE_REQUEST
UrbControlFeature ^Request
Umożliwia ustalenie lub wyzerowanie danych konfiguracyjnych urządzenia, punktu końcowego lub interfejsu: struct _URB_CONTROL_FEATURE_REQUEST { struct _URB_HEADER Hdr; struct _URB *UrbLink ; USHORT FeatureSelector ; USHORT Index ; ] ;. ..
_URB_CONTROL_VENDOR_ UrbControlVendorClass OR_CLASS_REQUEST ^Request
Określa zdefiniowany przez producenta lub właściwy dla klasy urządzenia format rozkazów dla żądań wysyłanych do urządzenia, punktu końcowego lub interfejsu:
266
USB. Praktyczne programowanie z Windows API w C++
Tabela 6.6. Specyfikacja struktury URB — ciąg dalszy
Typ strukturalny
Element unii
Znaczenie struct _URB_CONTROL_VENDOR_OR_CLASS_REQUEST { struct _URB_HEADER Hdr; ULONG TransferFlags ; ULONG TransferBufferLength ; PVOID TransferBuffer ; PMDL TransferBufferMDL ; struct _URB *UrbLink ; UCHAR UCHAR USHORT USHORT
_URB_CONTROL_GET_ INTERFACE_REQUEST
UrbControlGetInterface ^Request
RequestTypeReservedBits; Request; Value; Index;
Umożliwia pobranie interfejsu dla aktualnej konfiguracji urządzenia: struct _URB_CONTROL_GET_INTERFACE_REQUEST { struct _URB_HEADER s; ULONG TransferBufferLength ; PVOID TransferBuffer ; PMDL TransferBufferMDL ; struct _URB *UrbLink ; USHORT Interface;
_URB_CONTROL_GET_ CONFIGURATION_ REQUEST
UrbControlGetConfigu ^ r a ti onRequest
} ; Umożliwia pobranie aktualnej konfiguracji urządzenia: struct _URB_CONTROL_GET_CONFIGURATION_REQUEST { struct _URB_HEADER Hdr; ULONG TransferBufferLength; PVOID TransferBuffer; PMDL TransferBufferMDL; struct URB *UrbLink;
W DK definiuje tę strukturę jako: typedef struct _URB { union { struct _URB_HEADER UrbHeader; struct _URB_SELECT_INTERFACE UrbSelectInterface; struct _URB_SELECT_CONFIGURATION UrbSelectConfiguration; struct _URB_PIPE_REQUEST UrbPipeRequest; struct _URB_FRAME_LENGTH_CONTROL UrbFrameLengthControl; struct _URB_GET_FRAME_LENGTH UrbGetFrameLength;
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
267
struct _URB_SET_FRAME_LENGTH UrbSetFrameLength; struct _URB_GET_CURRENT_FRAME_NUMBER UrbGetCurrentFrameNumber; struct _URB_CONTROL_TRANSFER UrbControlTransfer; struct _URB_BULK_OR_INTERRUPT_TRANSFER UrbBulkOrInterruptTransfer; struct _URB_ISOCH_TRANSFER UrbIsochronousTransfer; struct _URB_CONTROL_DESCRIPTOR_REQUEST UrbControlDescriptorRequest; struct _URB_CONTROL_GET_STATUS_REQUEST UrbControlGetStatusRequest; struct _URB_CONTROL_FEATURE_REQUEST UrbControlFeatureRequest; struct _URB_CONTROL_VENDOR_OR_CLASS_REQUEST UrbControlVendorClassRequest; struct _URB_CONTROL_GET_INTERFACE_REQUEST UrbControlGetInterfaceRequest; struct _URB_CONTROL_GET_CONFIGURATION_REQUEST UrbControlGetConfigurationRequest; } } URB, *PURB ; D efinicja ta tworzy dw a nowe słowa kluczowe: URB (struktura) i PURB (wskaźnik do struktury).
Funkcja UsbBuildGetDescriptorRequest() Zdefiniow ana w module usbdlib.h (WDK) funkcja UsbBuildGetDescriptorRequest() formatuje elementy struktury URB z param etram i umożliwiającymi pobranie ze ste rownika kontrolera hosta informacji na temat deskryptorów urządzeń USB. VOID UsbBuildGetDescriptorRequest( IN OUT PURB Urb, IN USHORT Length, IN UCHAR DescriptorType, IN UCHAR Index, IN USHORT LanguageId, IN PVOID TransferBuffer OPTIONAL, IN PMDL TransferBufferMDL OPTIONAL, IN ULONG TransferBufferLength, IN PURB Link OPTIONAL ); Wskaźnik Urb wskazuje strukturę URB. Parametr Length określa w bajtach rozmiar struk tury. Param etr DescriptorType określa je d n ą z następujących wartości: USB_DEVICE_ DESCRIPTOR_TYPE (deskryptory urządzenia, interfejsu, punktu końcowego), USB_CONFIGURATION_DESCRIPTOR_TYPE (deskryptory konfiguracji) lub USB_STRING_DESCRIPTOR_TYPE. Jeżeli do DescriptorType zostanie wpisany USB_STRING_DESCRIPTOR_TYPE, to LanguageId określa identyfikator języka, w którym jest zapisany deskryptor. Parametr Index okre śla indeks pobieranego deskryptora. Wskaźnik TransferBuffer wskazuje bufor, w któ rym zostaną umieszczone dane deskryptora. Jeżeli TransferBuffer = NULL, to wskaźnik TransferBufferMDL pow inien w skazywać strukturę M DL (ang. M em ory Descriptor List). Parametr TransferBufferLength określa rozmiar buforów wskazywanych przez TransferB uffer lub TransferBufferMDL. Wskaźnik Link powinien wskazywać wartość pustą NULL. O praktycznych aspektach wykorzystania struktury URB i funkcji UsbBuildGetDescriptorR equest() można przeczytać w artykule Kosty Koemana [12].
268
USB. Praktyczne programowanie z Windows API w C++
Podsumowanie Niniejszy rozdział należy traktować jako rozwinięcie dw óch poprzednich. Zawarto w nim opis praktycznych metod wykorzystywania w działających programach zaso bów systemowych, które są odpowiedzialne za transmisję danych w standardzie USB. Dotyczy to zarówno urządzeń klasy HID (z wykorzystaniem funkcji eksportowych biblioteki H ID ), ja k i innych rodzajów urządzeń (z wykorzystaniem rozkazów IOCTL). O m awiane kody zostały przedstawione w form ach strukturalnych i proceduralnych w ten sposób, aby Czytelnicy niezaznajomieni z zasadami programowania zorientowa nego obiektowo mogli je bez trudu wykorzystać. Przedstawione algorytmy są podatne na wszelkiego rodzaju modyfikacje i uzupełnienia — w zależności od własnych potrzeb i aktualnych wymagań.
Ćwiczenia N a listingu 6.18 zaprezentowano nieskomplikowany przykład kodu programu wyko rzystującego funkcję W riteFile() w celu wysłania do standardowej drukarki USB (ste rownik usbprint.sys) polecenia wydrukowania łańcucha znaków. Listing 6.18. Przykład drukowania łańcucha znaków #include #include #include #pragma option push -a1 #include #pragma option pop #define MAX_BUFFER_SIZE 64 using namespace std; / / --------------------------------------------------------------HANDLE openDevice(const GUID& devGUID, const char* vid) { / / Patrz funkcja openDevice() - listing 6.17 return devObject; } / / -------------------------------------------------------------------const GUID GUID_DEVINTERFACE_USBPRINT = {0x28d78fad,0x5a12,0x11D1,0xae,0x5b,0x00,0x00,0xf8,0x03,0xa8,0xc2}; / / -------------------------------------------------------------------int main() { const char* VID = "vid_0482"; //Identyfikatory drukarki const char* PID = "pid_03c5"; HANDLE deviceObject = INVALID_HANDLE_VALUE; DWORD numberOfBytesWritten = 0; char bufferOut[MAX_BUFFER_SIZE]; deviceObject = openDevice(GUID_DEVINTERFACE_USBPRINT, VID);
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych
269
cout <
Ćwiczenie 6.1 Odczytaj deskryptory posiadanej drukarki.
Ćwiczenie 6.2 Zmodyfikuj kod z listingu 6.18, aby umożliwić wydrukowanie zawartości pliku tek stowego.
270
USB. Praktyczne programowanie z Windows API w C++
Rozdział 7.
Biblioteki WinUSB oraz LibUSB Producenci urządzeń USB pow inni standardowo wraz ze sprzętem dostarczać odpo wiednie sterowniki, które pozw olą użytkownikowi na uzyskanie swobodnego dostępu do urządzenia wykonawczego. Tego typu sterowniki były tworzone standardowo w ar chitekturze W DM (ang. Windows D river M odel). W DM zapewnia wspólny zestaw usług dla programistów piszących sterowniki dla większości klas urządzeń, które są przeznaczone do działania w różnych systemach operacyjnych Windows. Sterowniki tego typu udostępniają żądany interfejs, który aplikacja klienta wykorzystuje w celu uzyska nia identyfikatora sterownika najbardziej pasującego do urządzenia. Z kolei identyfi kator ten je st wykorzystywany przez funkcje API, takie jak ReadFile(), W riteFile() czy DeviceIoControl(). Tego rodzaju podejście byw a stosowane wówczas, gdy oprogramowywane są złożone, wielofunkcyjne urządzenia, mogące występować w syste mie w postaci wielu obiektów, z którym i aplikacje m ogą się komunikować zarówno w trybie synchronicznym, ja k i asynchronicznym.
Biblioteka WinUSB B iblioteka W indows USB (WinUSB) oferuje ogólny sterow nik dla urządzeń, które nie w ym agają standardow ego sterow nika klienta. A rchitektura WinUSB składa się ze sterownika działającego w trybie jąd ra winusb.sys, który je st uzupełnieniem ist niejących sterowników tworzonych (począwszy od Windows XP) w technologii WDF (ang. Windows D river Foundation), oraz biblioteki winusb.dll łączonej z programem w trybie użytkownika. WDF obejmuje biblioteki sterow ników wykorzystywanych w trybie jądra KMDF (ang. Kernel-Mode D river Framework) oraz w trybie użytkow nika UM DF (ang. User-M ode D river Fram ework). Dodatkowe biblioteki w spółinstalatorów WinUSB oraz KM DF (tzw. co-installers) są w łączone do pakietu instala cyjnego W DK znajdującego się w katalogu \Redist\W inusb. Sterownik w inusb.sys instaluje się w katalogu Windows\System32\drivers\ (jako plik ukryty), zaś biblioteka winusb.dll w katalogach Windows\System32\ oraz Windows\SysWOW64\. W tabelach
272
USB. Praktyczne programowanie z Windows API w C++
7.1 oraz 7.2 zaprezentowano używane przez systemy operacyjne wersje sterowników KM DF oraz współinstalatorów. Tabela 7.1. Wersje biblioteki KMDF używane przez systemy operacyjne Wersja biblioteki KMDF 1.0
1.1
1.5
1.7
1.9
1.11
—
—
—
—
V
V
Windows 7
—
—
—
—
V
V
Windows Vista
—
—
V
V
V
V
Windows XP
V
V
V
V
V
V
Windows 8
Tabela 7.2. Zgodność współinstalatorów WinUSB oraz KMDF Współinstalator WinUSB
Wersja biblioteki KMDF
Współinstalator KMDF
Winusbcoinstaller.dll
1.5 lub późniejsza
Wdfcoinstaller01005.dll Wdfcoinstaller01007.dll Wdfcoinstaller01009.dll WDFUpdate_0100X.dll
Winusbcoinstaller2.dll
1.9 lub późniejsza
Wdfcoinstaller01009.dll WDFUpdate_01009.dll
Winusbcoinstaller2.dll
1.11
WdfCoInstaller01011.dll
Przygotowanie pakietu instalacyjnego Aby winusb.sys stał się sterow nikiem funkcyjnym danego urządzenia, należy sam o dzielnie stworzyć katalog instalacyjny, zawierający właściwe dla używanej wersji syste mu biblioteki współinstalatorów oraz odpowiedni plik .inf, tak jak pokazano na rysunku 7.4. Plik .in f pow inien zawierać poprawne identyfikatory VID oraz PID pochodzące z deskryptora urządzenia oraz samodzielnie wygenerowany przez użytkownika (np. za pom ocą kombinacji Ctrl+Shift+G)1 identyfikator DeviceInterfaceGUIDs, który jest wykorzystywany do identyfikacji odpowiedniego interfejsu. N a listingu 7.1 pokazano zawartość przykładowego pliku .in f instalującego urządzenie klasy HID2 jako urządze nie winusb w 64-bitowym systemie Windows 7. Listing 7.1. Zawartość przykładowego pliku .inf dla urządzenia winusb [Version] Signature = "$Windows NT$" Class = HIDClass ClassGUID={745A17A0-74D3-11D0-B6FE-00A0C90F57DA} ; patrz rozdział 2., tabela 2.4 Provider = %ProviderName% DriverVer=09/18/2012,1.0.0 1 Microsoft Visual Studio generuje GUIDza pomocą narzędzia Tools\Create GUID. 2 Zaproponowany wybór klasy instalacji urządzenia należy traktować wyłącznie w kategoriach poglądowych. Czytelnicy dla swoich urządzeń mogą wybierać inne klasy lub nawet definiować własne.
Rozdziat 7. ♦ Biblioteki WinUSB oraz LibUSB
CatalogFile=myusbdevice.cat ; ========== Manufacturer/Models sections =========== [Manufacturer] %ProviderName% = MyDevice_WinUSB, NTamd64 [MyDevi ce_WinUSB.NTamd64] %USB\MyDevice.DeviceDesc% = USB_Install, USB\VID_22BA&PID_0108 ; ============== Installation ======================= [USB_Install] Include=winusb.inf Needs=WINUSB.NT [USB_Install.Services] Include=winusb.inf AddService=WinUSB,0x00000002,WinUSB ServiceInstall [WinUSB_ServiceInstall] DisplayName = %WinUSB_SvcDesc% ServiceType = 1 StartType =3 ErrorControl = 1 ServiceBinary = %12%\WinUSB.sys [USB_Install.Wdf] KmdfService=WINUSB, WinUsb_Install UmdfServiceOrder=WINUSB [WinUSB_Install] KmdfLibraryVersion=1.9 [USB_Install.HW] AddReg=Dev_AddReg [Dev_AddReg] HKR,,DeviceInterfaceGUIDs,0x10000,"{45CA71ED-CE1C-44F2-82DE-87D8D8FF6C1E}" [USB_Install.CoInstall ers] AddReg=CoInstallers_AddReg CopyFiles=CoInstallers_CopyFil es [CoInstallers_AddReg] HKR,,CoInstallers32,0x00010000,"WinUSBCoInstaller2.dll","WUDFUpdate_01009.dll", "WdfCoInstaller01009.dll, WdfCoInstaller" [CoInstallers_CopyFiles] WinUSBCoInstall er2.dll WdfCoInstaller01009.dl l WUDFUpdate_01009.dll [DestinationDirs] CoInstallers_CopyFiles=11 ; ========== Source Media Section [SourceDisksNames.amd64] 1 = %DISK_NAME%,,,\amd64 [SourceDisksFiles.amd64] WinUSBCoInstaller2.dll=1 WdfCoInstaller01009.dl l=1 WUDFUpdate_01009.dl l=1
273
27 4
USB. Praktyczne programowanie z Windows API w C++
; =================Copy Files Section============== [_CopyFiles_sys] winusb.sys ; ==============Destination Directories============ [DestinationDirs] DefaultDestDir = 12 ; %SystemRoot%\system32\drivers _CopyFiles_sys = 12 ; =================== Strings ===================== [Strings] ProviderName="Praktyczne programowanie" USB\MyDevi ce.DeviceDesc="WinUSB Device" WinUSB_SvcDesc="WinUSB Device" DISK_NAME="My Install Disk" W celu dostosowania zawartości pliku .in f do konkretnych potrzeb należy określić kla sę instalacji urządzenia, wpisać odpowiednie wartości identyfikatorów VID, PID i in terfejsu GUID urządzenia oraz wskazać posiadane wersje bibliotek współinstalatorów. Szczegółowe informacje na temat plików .in f dla urządzeń winusb są dostępne na stro nie M SD N : http://msdn. microsoft.com/en-us/library/windows/hardware/ff540283(v= vs.85).aspxpod hasłem WinUSB (Winusb.sys) Installation. Tworząc własne pakiety instalacyjne przeznaczone do użycia w 64-bitowych syste m ach operacyjnych, warto pamiętać, że systemy te charakteryzują się wbudowanymi mechanizmami kontroli zabezpieczeń, które m.in. wym agają posługiwania się podpi sanymi cyfrowo sterownikami. Bardzo często zdarza się, że użytkownicy, którzy mają jed en niepodpisany sterownik, próbują całościowo wyłączać systemowe mechanizmy kontroli zabezpieczeń. Należy być świadomym faktu, że Windows Driver K it oferuje w szystkie niezbędne narzędzia służące do cyfrowego podpisyw ania sterowników. W tym celu można użyć programów narzędziowych: ♦ inf2cat.exe — tworzy plik .cat dla katalogu sterownika, ♦ makecert.exe — generuje odpowiedni certyfikat, ♦ certmgr.exe — m enedżer zarządzający certyfikatami, ♦ signtool.exe — umożliwia podpisanie pliku .cat. N a rysunku 7.1 pokazano przykład utw orzenia pliku .cat. Trzeba zwrócić uwagę, że nazw a tego pliku powinna być wcześniej poprawnie określona w sekcji [V ersion] pli ku .in f (patrz listing 7.1). N a rysunku 7.2 zaprezentowano sekwencję poleceń tworzących magazyn certyfikatów, umieszczenie w nim wygenerowanego certyfikatu oraz podpisanie pliku .cat. Szczegóły utworzonego certyfikatu można obejrzeć, korzystając z menedżera certmgr, tak ja k zaprezentowano na rysunku 7.3. Dodatkowe wiadomości na temat metod podpisywania sterowników są dostępne w pli kach pomocy M SDN oraz WDK właściwych dla używanych wersji systemu operacyj nego. N a rysunku 7.4 pokazano zawartość typowego katalogu instalacyjnego urządze nia winusb w 64-bitowym systemie operacyjnym.
Rozdział 7. ♦ Biblioteki WinUSB oraz LibUSB
SS ¿drn'^'strator: W iersz polecenia
275
|
D:\WinDDK\7600.16385.l\bin\selfsign>inf2cat /driver : D:\WinUSBDevice / o s : 7_X64
-
Signability test complete. Errors : None Warni ngs: None Catalog generation complete. D:\Wi nUSBDevi ce\myusbdevi c e .cat D : \Wi nDDK\7600.16 38 5 .l\bi n\selfsi gn>_
Rysunek 7.1. Tworzenie pliku .cat dla katalogu instalacyjnego urządzenia w 64-bitowym systemie Windows 7. W Windows 8 należy wykorzystać znacznik os:8_X64 j Administrator: W iersz polecenia
D:\Wi n USB Devi cexnake cert -r -pe Succeeded
-55
myCertStore -n f,CN=myTestCertfr myTestCert.cer
D:\WinUSBDevice>certmgr /c /add myTestCert.cer /s root CertMgr Succeeded D:\WinU5BDevice>signtool sign /s myCertStore /n myTestCert nyUSBDevice.cat Done Adding Additional Store Successfully signed: myusbdevice.cat D :\Wi nUSBDevi ce>_
Rysunek 7.2. Utworzenie przykładowego magazynu certyfikatów myCertStore, przygotowanie i umieszczenie w nim certyfikatu myTestCert.cer oraz podpisanie pliku myUSBDevice.cat w 64-bitowym systemie Windows 7. W Windows 8 czynności te przebiegają tak samo
Rysunek 7.3. Szczegółowe informacje na temat wygenerowanego przykładowego certyfikatu
276
USB. Praktyczne programowanie z Windows API w C++
Rysunek 7.4. Zawartość typowego katalogu instalacyjnego urządzenia winusb w 64-bitowym systemie operacyjnym. Nazwa podkatalogu amd64 odnosi się jedynie do określenia stosowanej 64-bitowej architektury. Dla 64-bitowych komponentów Microsoft Windows stosuje zamiennie oznaczenia x64 oraz amd64 Po prawidłowym skonfigurowaniu zawartości pakietu instalacyjnego urządzenie na leży podłączyć i samodzielnie zainstalować. Poprawnie zainstalowany i gotowy do użycia sprzęt pow inien być widoczny w menedżerze urządzeń, tak ja k pokazano na rysunku 7.5. Rysunek 7.6 przedstaw ia zawartość typowego stosu sterowników urzą dzenia winusb. Rysunek 7.5. Prawidłowo zainstalowane w systemie urządzenie winusb
^
ia
Menedżer urządzeń Plik
Akcja
Widok
I^
MM
Pomoc
i ï i ï a l b H I ® lj t \ *
4â
'I Kontrolery uniwersalnej magistrali szeregowej
ę Generic USB Hub Ę Generic USB Hub
*
^ Genet»: USB Hub
9 Główny koncentrator USB 9
Główny koncentrator USB
■ Standardowy rozszerzony kontroler K o sił P H do IJSB ^ Standardowy rozszerzony kontroler Kosta PCI do USB 9
Urządzerie kompozytowe USB
> -A U Modemy > -JEI Monitory >
Mysz i inne urządzenia wskazujące Odbiorniki radiowe Bluetooth
>
|p
[>
[>J
Procesory
ą Stacje dysków
=
Stacje dysków CD-RQM/DVD
> -2^ Urządzenia do obrazowania J -ÔS L rządzenia interfejsu HID i
H *
!
Urządzenie wejściowe USB L [jj§ W inUSBDevicel
i
Jrządzenie wejściowe USB
t> •j]ÇJ Urządzenia systemowe
T
Rozdział 7. ♦ Biblioteki WinUSB oraz LibUSB
277
Rysunek 7.6. Stos sterowników urządzenia winusb
Biblioteka winusb.dll implementuje zestaw funkcji eksportowych, umożliwiający pro gramiście bezpośrednie wykonywanie operacji wejścia-wyjścia bez konieczności jaw nego posługiwania się funkcjami oraz rozkazami opisanymi w rozdziale 6. Prezento wane w dalszej części podrozdziału funkcje opakowują rozkazy używane na poziomie jądra systemu i przekazują je bezpośrednio do sterownika winusb.sys w celu dalszego przetwarzania. Sterownik winusb.sys tworzy w trybie jądra operacyjny obiekt urządze nia FDO, tak jak obrazowo pokazano na rysunku 7.7. Rysunek 7.7. Model warstwowy sterowników urządzenia winusb
tryb użytkow nika
tryb jądra
Funkcje eksportowe biblioteki WinUSB Zasoby biblioteki są dostępne w module winusb.h oraz bibliotekach winusb.dll i winusb. lib. Bibliotekę winusb.dll można łączyć z programem w trybie użytkownika w sposób statyczny, korzystając z modułu winusb.lib, lub dynamiczny, wykorzystując winusb.dll. Należy zwrócić uwagę na fakt, że niektóre popularne kompilatory C++ mogą niewłaści wie odwzorowywać identyfikator biblioteki łączonej statycznie winusb.lib w przestrzeni
278
USB. Praktyczne programowanie z Windows API w C++
adresowej głównego procesu (programu wykonawczego). Z tego powodu bezpiecz niejszym sposobem wykorzystania zasobów WinUSB jest często dynamiczne odwzo rowywanie identyfikatora winusb.dll w przestrzeni adresowej głównego procesu oraz pobieranie funkcji eksportowych biblioteki. Prawidłowo wykonane funkcje eksportowe biblioteki winusb.dll zwracają wartość TRUE. Kody możliwych błędów powinny być diagnozowane za pomocą funkcji G etLastError().
Uwaga
Biblioteki WinUSB nie należy używać w celu uzyskania dostępu do sprzętu, który jest instalowany w systemie jako urządzenie przenośne lub pamięć masowa. Urządzenia przenośne często instalują się w systemie właśnie jako urządzenia winusb. Począw szy od Windows 7, większość urządzeń USB funkcjonujących zgodnie z protokołem MTP (ang. Media Transfer Protocof) wykorzystuje bibliotekę WinUSB. Aby uzyskać dostęp do tego typu sprzętu, wystarczy posłużyć się standardowymi funkcjami API SDK służącymi do obsługi dysków i plików.
Funkcja WinUsb_Initialize() BOOL stdcall WinUsb_Initialize( IN HANDLE DeviceHandle, OUT PWINUSB_INTERFACE_HANDLE InterfaceHandle ); Funkcja inicjalizuje wewnętrzne struktury biblioteki WinUSB oraz pobiera identyfi kator InterfaceH andle pierwszego interfejsu (interfejsu podstawowego) urządzenia identyfikowanego przez DeviceHandle. Identyfikator DeviceHandle sterownika urzą dzenia wykonawczego jest zwracany przez funkcję C reateF ile(). W iększość zasobów biblioteki WinUSB je s t w yw oływ ana asynchronicznie, dlatego też należy pamiętać o ustaleniu w funkcji C reateF ile() znacznika FILE_FLAG_OVERLAPPED. N a listingu 7.2 zaprezentowano jeden z możliwych sposobów dynamicznego łączenia biblioteki winusb.dll z programem głównym, odczytania adresów funkcji eksportowych W inU sb_Initialize() i WinUsb_Free() oraz ich wywołania w programie głównym. Listing 7.2. Inicjalizacja biblioteki WinUSB #include #include "C:\\WinDDK\\7600.16385.1\\inc\\ddk\winusb.h" #include "C:\\WinDDK\\7600.16385.1\\inc\\api\\Usb100.h" #include #include #include #pragma option push -a1 #include #pragma option pop using namespace std; //Identyfikator GUID interfejsu urządzenia powinien być poprawnie //wygenerowany i odczytany z właściwego pliku .in f
Rozdział 7. ♦ Biblioteki WinUSB oraz LibUSB
279
DEFINE_GUID(InterfaceClassGuidConstant, 0x45CA71ED, 0xCE1C, 0x44F2, 0x82,\ 0xDE, 0x87, 0xD8, 0xD8, 0xFF, 0x6C, 0x1E); GUID InterfaceClassGuid = InterfaceClassGuidConstant; 11... HMODULE hWinUSBLib; WINUSB_INTERFACE_HANDLE interfaceHandle = INVALID_HANDLE_VALUE; HANDLE deviceHandle = INVALID_HANDLE_VALUE; BOOL bResult; 11-------------------------------------------------------------------------int main(){ 11Odwzorowanie identyfikatora hWinUSBLib biblioteki winusb.dll w 11przestrzeni adresowej głównego procesu hWinUSBLib = LoadLibrary("C:\\Windows\\System32\\WinUSB.DLL"); if (!hWinUSBLib) displayError("Błąd dołączenia biblioteki WinUSB.DLL."); BOOL ( stdcall *WinUsb_Initialize)(IN HANDLE DeviceHandle, OUT PWINUSB_INTERFACE_HANDLE InterfaceHandle); BOOL ( stdcall *WinUsb_Free)(IN WINUSB_INTERFACE_HANDLE InterfaceHandle); 11Pobranie adresów funkcji eksportowych (FARPROC&) WinUsb_Initiali ze=GetProcAddress(hWinUSBLib, "WinUsb_Initialize"); (FARPROC&) WinUsb_Free=GetProcAddress(hWinUSBLib, "WinUsb_Free"); if (! WinUsb_Initialize || ! WinUsb_Free){ FreeLibrary(hWinUSBLib); displayError("Nie znaleziono żadnych funkcji eksportowych.\n"); } 11---------------------------------------------------------------------deviceInfoSet = SetupDiGetClassDevs(&InterfaceClassGuid, NULL, NULL, DIGCF_PRESENT | DIGCF_INTERFACEDEVICE); if (deviceInfoSet == INVALID_HANDLE_VALUE) displayError("Nie zidentyfikowano podłączonych urządzeń.\n"); deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); while(SetupDiEnumDeviceInterfaces(deviceInfoSet, NULL, &InterfaceClassGuid, memberIndex, &devi ceInterfaceData)){ 11Procedury enumeracji dołączonych urządzeń - patrz diagram z rysunku 5.1 11... } 11---------------------------------------------------------------------11Wybranie właściwej ścieżki dostępu DevicePath do interfejsu urządzenia 11i pobranie identyfikatora deviceHandle pliku sterownika urządzenia 11wykonawczego deviceHandle = CreateFile(deviceInterfaceDetailData->DevicePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL |FILE_FLAG_OVERLAPPED, NULL); if(deviceHandle != INVALID_HANDLE_VALUE) { 11Wywołanie funkcji WinUsb_Initialize() z aktualnymi parametrami bResult = WinUsb_Initialize(deviceHandle, &interfaceHandle); if(!bResult) printf("Błąd in icjalizacji WinUsb_Initialize()%d.\n", GetLastError()); }
11...
280
USB. Praktyczne programowanie z Windows API w C++
system("PAUSE"); CloseHandle(devi ceHandle); WinUsb_Free(interfaceHandle); return 0; } / / ------------------------------------
Funkcja WinUsb_Free() BOOL stdcall WinUsb_Free( IN WINUSB_INTERFACE_HANDLE InterfaceHandle ); Funkcja zwalnia identyfikator interfejsu przydzielony uprzednio za pomocą WinUsb_Init i a li z e ( ) lub kolejne identyfikatory wskazane przez WinUsb_GetAssociatedInterface().
Funkcja WinUsb_AbortPipe() BOOL stdcall WinUsb_AbortPipe( IN WINUSB_INTERFACE_HANDLE InterfaceHandle, IN UCHAR PipeID ); W ywoływana asynchronicznie funkcja przerywa (anuluje) transfer danych realizowa ny do punktu końcowego urządzenia wykonawczego. Parametr wejściowy In te rfa c e Handle je st identyfikatorem interfejsu dla podstawowej konfiguracji urządzenia wyko nawczego. Parametr ten je st zwracany przez funkcję W inU sb_Initialize(). Param etr wejściowy PipeID je st identyfikatorem punktu końcowego dla danych konfiguracyj nych, identyfikujących i kontrolnych urządzenia. Param etr zawiera 7-bitowy adres punktu końcowego oraz b it identyfikujący kierunek transferu danych. PipeID odpo wiada zaw artości pola bEndpointAddress w deskryptorze punktu końcowego (patrz rozdział 3., tabela 3.12).
Funkcja WinUsb_ControlTransfer() BOOL stdcall WinUsb_ControlTransfer( IN WINUSB INTERFACE HANDLE InterfaceHandle IN WINUSB SETUP PACKET SetupPacket, OUT PUCHAR Buffer, IN ULONG BufferLength, OUT PULONG LengthTransferred, IN LPOVERLAPPED Overlapped ); Funkcja realizuje kontrolny transfer danych. Param etr wejściowy InterfaceH andle identyfikuje interfejs dla danej konfiguracji. Identyfikator pierwszego interfejsu w kon figuracji (interfejsu podstawowego) powinien być uzyskany przez wywołanie funkcji W inUsb_Initial iz e (). Identyfikatory alternatywnych interfejsów są zwracane przez funkcję W inUsb_GetAssociatedInterface(). Parametr SetupPacket jest egzemplarzem struktury WINUSB_SETUP_PACKET opisanej w tabeli 7.3.
Rozdział 7. ♦ Biblioteki WinUSB oraz LibUSB
281
Tabela 7.3. Zawartość struktury WINUSB_SETUP_PACKET Typ
Element struktury
Opis
UCHAR
RequestType
Typrozkazu(żądania) jest zapisanyw poszczególnych bitach słowa: D7: kierunek transferu danych 0b — host ^ urządzenie (OUT) 1b — urządzenie ^ host (IN) D6 - D5: typ żądania 00b — standardowe 01b — właściwe danej klasie urządzeń 10b — producenta 11b — zarezerwowane D4 - D0: adresat 000000b — urządzenie 000001b — interfejs 000010b — punkt końcowy 000011b — inne W module usb100.h zdefiniowano makrodefinicje pomocne w określaniu zawartości pola RequestType (bmRequest według WDK lub bmRequestType zgodnie z dokumentacją USB): //kierunek transferu BMREQUEST_HOST_TO_DEVICE 0 BMREQUEST_DEVICE_TO_HOST 1 //typ żądania BMREQUEST_STANDARD 0 BMREQUEST_CLASS 1 BMREQUEST_VENDOR 2 //adresat BMREQUEST_TO_DEVICE 0 BMREQUEST_TO_INTERFACE 1 BMREQUEST_TO_ENDPOINT 2 BMREQUEST_TO_OTHER 3
UCHAR
Request
Kod (numer) rozkazu: USB_REQUEST_CLEAR_FEATURE (0x01) — wyzerowanie określonej właściwości interfejsu lub punktu końcowego; USB_REQUEST_GET_CONFIGURATION (0x08) — pobranie aktualnej konfiguracji urządzenia; USB_REQUEST_GET_DESCRIPTOR (0x06) — pobranie aktualnego deskryptora urządzenia lub konfiguracji; USB_REQUEST_GET_INTERFACE (0x0A) — pobranie aktualnego ustawienia interfejsu; USB_REQUEST_GET_STATUS (0x00) — pobranie statusu urządzenia, interfejsu lub punktu końcowego; USB_REQUEST_SET_ADDRESS (0x05) — wybór adresu urządzenia; USB_REQUEST_SET_CONFIGURATION (0x09) — wybór konfiguracji;
282
USB. Praktyczne programowanie z Windows API w C++
Tabela 7.3. Zawartość struktury WINUSBSETUPPACKET — ciąg dalszy Typ
Element struktury
Opis USB_REQUEST_SET_DESCRIPTOR (0x07) — uaktualnienie lub dodanie nowego deskryptora; USB REQUEST SET FEATURE (0x03) — ustalenie określonej właściwości interfejsu lub punktu końcowego; USB REQUEST SET INTERFACE (0x0B) — wybór ustawienia interfejsu; USB REQUEST SYNC FRAME (0x0C) — odczyt numeru ramki synchronizacyjnej
USHORT
Value
Zawartość pola zależy od rozkazu — patrz rozdział 6., tabele 6.4 i 6.5
USHORT
Index
Zawartość pola zależy od typu adresata rozkazu (patrz rysunki 7.8 oraz 7.9). Szczegółowe informacje wykorzystywane przez strukturę USB DESCRIPTOR REQUEST są przedstawione w rozdziale 6. w tabelach 6.4 i 6.5
USHORT
Length
Pole określa długość danych przesyłanych w trakcie transferu kontrolnego w drugiej fazie przekazu (DATA OUT lub DATA IN) — patrz rozdział 1., rysunki 1.46 - 1.47 oraz 1.68 - 1.69 D7 Kierunek
D6
D5
D4
Zarezerwowane (0)
D3
D2
D1
DO
Numer punktu końcowego
D15
D8 Zarezerwowane (0)
Rysunek 7.8. Format pola Index struktury WINUSBSETUPPACKET, w przypadku gdy adresatem rozkazu jest punkt końcowy w ramach interfejsu
Rysunek 7.9. Format pola Index struktury WINUSBSETUPPACKET, w przypadku gdy adresatem rozkazu jest interfejs w ramach danej konfiguracji Param etr wyjściowy Buffer wskazuje bufor zawierający transferowane dane. Parametr BufferLength zawiera liczbę transferowanych bajtów z wyłączeniem pakietu SETUP. Liczba transferowanych bajtów nie powinna być większa od rozmiaru bufora danych. Opcjonalny wskaźnik LengthTransferred wskazuje aktualną liczbę transferowanych baj tów (może wskazywać wartość NULL), zaś wskaźnik Overlapped — strukturę OVERLAPPED.
Funkcja WinUsb_FlushPipe() BOOL stdcall WinUsb_FlushPipe( IN WINUSB_INTERFACE_HANDLE InterfaceHandle, IN UCHAR PipeID );
Rozdział 7. ♦ Biblioteki WinUSB oraz LibUSB
283
W ywoływana asynchronicznie funkcja usuw a dane z bufora punktu końcowego. Zna czenie jej parametrów jest identyczne jak w przypadku WinUsb_AbortPipe().
Funkcja WinUsb_GetAssociatedInterface() BOOL IN IN OUT );
stdcall WinUsb_GetAssociatedInterface( WINUSB_INTERFACE_HANDLE InterfaceHandle, UCHAR AssociatedInterfaceIndex, PWINUSB_INTERFACE_HANDLE AssociatedInterfaceHandle
W ywoływana asynchronicznie funkcja odzyskuje kolejne identyfikatory interfejsów właściwe danej konfiguracji. Inaczej rzecz ujmując, funkcja kontynuuje proces wyszu kiwania identyfikatorów interfejsów rozpoczęty przez wywołanie W inUsb_Initialize(). Param etr InterfaceH andle wskazuje identyfikator pierwszego interfejsu (interfejsu podstawowego) pobranego za pom ocą W inUsb_Initial iz e (). Parametr A ssociated InterfaceIndex przechowuje indeks identyfikatora aktualnego interfejsu. Wskaźnik A ssociatedInterfaceH andle wskazuje identyfikator kolejnego interfejsu właściwego danej konfiguracji urządzenia wykonawczego. Kiedy identyfikator nie je st używany, powinien zostać zwolniony za pom ocą funkcji WinUsb_Free().
Funkcja WinUsb_GetCurrentAlternateSetting() BOOL stdcall WinUsb_GetCurrentAlternateSetting( IN WINUSB_INTERFACE_HANDLE InterfaceHandle, OUT PUCHAR AlternateSetting ); A synchronicznie w yw oływ ana funkcja je st w ykorzystywana do określania alterna tywnych ustawień interfejsu identyfikowanego przez InterfaceHandle. Alternatywne ustawienia interfejsu są identyfikowane przez wskaźnik A lternateS etting.
Funkcja WinUsb_GetDescriptor() BOOL IN IN IN IN OUT IN OUT );
stdcall WinUsb_GetDescriptor( WINUSB_INTERFACE_HANDLE InterfaceHandle, UCHAR DescriptorType, UCHAR Index, USHORT LanguageID, PUCHAR Buffer, ULONG BufferLength, PULONG LengthTransferred
Asynchronicznie w yw oływ ana funkcja służy do odczytu żądanego typu deskryptora DescriptorType, który może być jednym z: USB_DEVICE_DESCRIPTOR_TYPE, USB_CONFIGURATION_DESCRIPTOR_TYPE lub USB_STRING_DESCRIPTOR_TYPE. Parametr Index jest in deksem deskryptora, a LanguageID identyfikuje język opisu, jeżeli żądanym deskryptorem jest deskryptor łańcuchowy [17]. Wskaźnik Buffer wskazuje bufor danych przechowują cy zawartość deskryptora, BufferLength określa rozmiar (w bajtach) bufora, zaś wskaź nik LengthTransferred wskazuje liczbę bajtów rzeczywiście umieszczonych w buforze.
28 4
USB. Praktyczne programowanie z Windows API w C++
Funkcja WinUsb_GetOverlappedResult() BOOL IN IN OUT IN );
stdcall WinUsb_GetOverlappedResult( WINUSB_INTERFACE_HANDLE InterfaceHandle, LPOVERLAPPED lpOverlapped, LPDWORD lpNumberOfBytesTransferred, BOOL bWait
Funkcja pod postacią wskaźnika lpNumberOfBytesTransferred zwraca liczbę aktualnie transferowanych bajtów podczas w ykonyw ania synchronicznych operacji zapisu lub odczytu danych do lub z portu. Znaczenie pozostałych parametrów było ju ż omawia ne w tym podrozdziale.
Funkcja WinUsb_GetPipePolicy() BOOL stdcall WinUsb_GetPipePolicy( IN WINUSB_INTERFACE_HANDLE InterfaceHandle, IN UCHAR PipeID, IN ULONG Poli cyType, INOUT PULONG ValueLength, OUT PVOID Value ); Asynchronicznie wywoływana funkcja pobiera parametry transmisji PolicyType w ła ściwe dla określonego punktu końcowego identyfikowanego przez PipeID. W artością domyślną wszystkich elementów je st FALSE — 0, ale można im przypisać następujące wartości: ♦ SHORT_PACKET_TERMINATE (0x01) — jeżeli pole danych pakietu posiada rozmiar mniejszy, niż jest to zapisane w polu wMaxPacketSize deskryptora punktu końcowego, oznacza to, że pakiet tego typu będzie kończył każde żądanie zapisu lub transmisji danych. ♦ AUTO_CLEAR_STALL (0x02) — reset pakietu STALL (patrz rozdział 1., tabela 1.11). ♦ PIPE_TRANSFER_TIMEOUT (0x03) — czas przeterm inowania operacji wyrażony w milisekundach. Sterownik kontrolera hosta anuluje transfer danych, jeżeli nie został on skompletowany w określonym przedziale czasu. ♦ IGNORE_SHORT_PACKETS (0x04) — w łączenie tej opcji powoduje ignorowanie przez sterownik kontrolera hosta pakietu z polem danych mniejszym, niż jest to zapisane w polu wMaxPacketSize deskryptora punktu końcowego. Zakończenie operacji czytania z portu (skompletowanie danych wejściowych) nastąpi dopiero po odebraniu określonej liczby bajtów. ♦ ALLOW_PARTIAL_READS (0x05) — wyłączenie tej opcji (FALSE) powoduje niepowodzenie odczytu danych, jeżeli masowy lub przerwaniowy punkt końcowy zwróci pakiet niemieszczący się w buforze odbiorczym. Włączenie opcji (TRUE) powoduje odrzucenie nadmiarowej porcji danych, w przypadku gdy masowy lub przerwaniowy punkt końcowy wysyła ich więcej, niż może pomieścić bufor odbiornika.
Rozdział 7. ♦ Biblioteki WinUSB oraz LibUSB
285
♦ AUTO_FLUSH (0x06) — opcja używana razem z ALLOW_PARTIAL_READS powoduje usuwanie z bufora odbiornika nadmiarowej porcji informacji. ♦ MAXIMUM_TRANSFER_SIZE (0x08) — maksymalny rozmiar transmitowanego pakietu. ♦ RESET_PIPE_ON_RESUME (0x09) — resetowanie punktu końcowego. Param etr PipeID odpowiada wartości zapisanej w polu bEndpointAddress deskryptora punktu końcowego (patrz rozdział 3., tabela 3.12). W skaźnik ValueLength wskazuje rozmiar bufora danych przechowującego parametry transmisji i wskazywanego przez wskaźnik ogólny Value.
Funkcja WinUsb_GetPowerPolicy() BOOL stdcall WinUsb_GetPowerPolicy( IN WINUSB_INTERFACE_HANDLE InterfaceHandle, IN ULONG Poli cyType, INOUT PULONG ValueLength, OUT PVOID Value ); Asynchronicznie wywoływana funkcja pobiera parametry opisujące sposób zasilania urządzenia. Parametr PolicyType może mieć wartość AUTO_SUSPEND (0x81) lub SUSPEND_ DELAY (0x83). Jeżeli zostanie wybrany pierwszy parametr, funkcja pod postacią wskaź nika Value zwróci wartość odpowiadającą przejściu urządzenia do stanu wstrzymania działania (ang. suspend). W ybór drugiej opcji powoduje zwrócenie przez funkcję pod postacią w skaźnika Value wartości informującej o w yspecyfikow aniu minimalnego przedziału czasu (wyrażonego w milisekundach), w którym sterownik W inUSB powi nien um ożliwić kontynuację transferu danych, zanim urządzenie przejdzie do stanu wstrzymania działania. W skaźnik ValueLength wskazuje zmienną określającą rozmiar bufora danych przechowującego wartość Value.
Funkcja WinUsb_QueryDeviceInformation() BOOL stdcall WinUsb_QueryDeviceInformation( IN WINUSB_INTERFACE_HANDLE InterfaceHandle, IN ULONG InformationType, INOUT PULONG BufferLength, OUT PVOID Buffer ); Funkcja pobiera informacje na tem at urządzenia, którego interfejs jest identyfikowany poprzez InterfaceH andle. Param etrow i InformationType należy przypisać wartość DEVICE_SPEED (0x01). W skaźnik BufferLength wskazuje zm ienną określającą maksy m alną liczbę bajtów umieszczonych w buforze wskazywanym przez Buffer. Funkcja umieszcza w buforze wskazywanym przez Buffer wartość określającą szybkość trans feru danych, którym może się posługiwać urządzenie: wartość 0x03 oznacza możliwość uzyskania transferu HS lub SS, zaś 0x01 oznacza urządzenie FS lub LS.
286
USB. Praktyczne programowanie z Windows API w C++
Funkcja WinUsb_QueryInterfaceSettings() BOOL IN IN OUT );
stdcall WinUsb_QueryInterfaceSettings( WINUSB_INTERFACE_HANDLE InterfaceHandle, UCHAR AlternateSettingNumber, PUSB_INTERFACE_DESCRIPTOR UsbAltInterfaceDescriptor
Funkcja pobiera deskryptor interfejsu dla specyficznych ustawień alternatywnych. Param etr AlternateSettingN um ber przechow uje w artość kolejnych alternatywnych ustawień interfejsu w tablicy interfejsów danej konfiguracji. Wskaźnik U sbAltInter ^faceD escriptor wskazuje strukturę USB_INTERFACE_DESCRIPTOR opisującą deskryptor interfejsu urządzenia (patrz rozdział 3., tabela 3.14). //
-------------------------------------------------------------------
USB_INTERFACE_DESCRIPTOR *interfaceDescriptor = new \ USB_INTERFACE_DESCRIPTOR[sizeof(USB_INTERFACE_DESCRIPTOR)]; WINUSB_PIPE_INFORMATION *pipeInformation = new \ WINUSB_PIPE_INFORMATION[sizeof(WINUSB_PIPE_INFORMATION)]; //... bResult = WinUsb_QueryInterfaceSettings(interfaceHandle, 0, interfaceDescriptor); if (bResult) { for (int numEP = 0; numEP < interfaceDescriptor->bNumEndpoints; numEP++){ bResult = WinUsb_QueryPipe(interfaceHandle, 0, numEP, pipeInformation); if (bResult){ if (pipeInformation->PipeType == UsbdPipeTypeControl){ //transfer kontrolny printf("EP: %d typ potoku: ID potoku: %d.\n", numEP, pipeInformation->PipeType, pipeInformation->PipeId); //... } / / --------------------------------------------------------------------------
Funkcja WinUsb_QueryPipe() BOOL IN IN IN OUT );
stdcall WinUsb_QueryPipe( WINUSB_INTERFACE_HANDLE InterfaceHandle, UCHAR AlternateInterfaceNumber, UCHAR Pi peIndex, PWINUSB_PIPE_INFORMATION PipeInformation
Funkcja pobiera inform acje na tem at punktu końcow ego identyfikow anego przez PipeIndex. Zakres wartości PipeIndex powinien być mniejszy od wartości zapisanych w polu bNumEndpoints struktury opisującej deskryptor interfejsu (patrz rozdział 3., ta bela 3.14). Param etr AlternateInterfaceN um ber określa numer alternatywnego inter fejsu dla danej konfiguracji. W skaźnik PipeInform ation wskazuje strukturę WINUSB_ PIPE_INFORMATION opisaną w tabeli 7.4. / / -------------------------------------------------------------------------WINUSB_PIPE_INFORMATION *pipeInformation = new \ WINUSB_PIPE_INFORMATION[sizeof(WINUSB_PIPE_INFORMATION)]; //... bResult = WinUsb_QueryPipe(interfaceHandle, 0, (UCHAR) 0, pipeInformation); if (USB_ENDPOINT_DIRECTION_IN(pipeInformation->PipeId)) == TRUE {
Rozdział 7. ♦ Biblioteki WinUSB oraz LibUSB
287
Tabela 7.4. Zasoby struktury WINUSBPIPEINFORMATION Typ
Element struktury
Opis
USBD_PIPE_TYPE
PipeType
Patrz rozdział 3., tabela 3.1. Biblioteka WinUSB nie obsługuje potoków izochronicznych
UCHAR
PipeID
Identyfikator punktu końcowego. W module usb100.h są zdefiniowane dwie makrodefinicje pomocne w określaniu kierunku transferu danych związanego z określonymi typami punktów końcowych EP IN oraz EP OUT: USB_ENDPOINT_DIRECTION_IN(PipeId) USB_ENDPOINT_DIRECTION_OUT(PipeId)
USHORT
MaximumPacketSize
Patrz rozdział 3., tabela 3.2
UCHAR
Interval
Patrz rozdział 3., tabela 3.2
//EP IN } if (USB_ENDPOINT_DIRECTION_OUT(pipeInformation->Pi peld)) == TRUE { //EP OUT } / / --------------------------------------------------------------------------
Funkcja WinUsb_ReadPipe() BOOL stdcall WinUsb_ReadPipe( IN WINUSB INTERFACE HANDLE InterfaceHandle IN UCHAR PipeID, OUT PUCHAR Buffer, IN ULONG BufferLength, OUT PULONG LengthTransferred, IN LPOVERLAPPED Overlapped ); Funkcja pobiera (czyta) dane z bufora punktu końcowego identyfikowanego przez PipeID. Parametr PipeID odpowiada zawartości pola bEndpointAddress struktury opisu jącej deskryptor punktu końcowego (patrz rozdział 3., tabela 3.12). W skaźnik Buffer wskazuje bufor danych w ejściow ych. Param etr BufferLength określa m aksym alną liczbę bajtów umieszczanych w buforze wejściowym. W skaźnik LengthTransferred wskazuje liczbę aktualnie przeczytanych bajtów. Liczba faktycznie odebranych bajtów może być mniejsza niż deklarowany rozm iar bufora; może to oznaczać zakończenie odbierania danych (patrz opis znacznika SHORT_PACKET_TERMINATE). Liczbę faktycznie odebranych bajtów funkcja umieszcza w zmiennej wskazywanej przez LengthTrans ferred, stanowiącej przedostatni parametr. W ten sposób działa mechanizm ochrony dla odbieranych danych. W skaźnik Overlapped wskazuje strukturę OVERLAPPED. / / -------------------------------------------------------------------------ULONG bufferSize = 0; UCHAR* buffer = new UCHAR[bufferSize*sizeof(UCHAR)]; ULONG lengthTransferred = 0; WINUSB_PIPE_INFORMATION *pipeInformation = new \ WINUSB_PIPE_INFORMATION[sizeof(WINUSB_PIPE_INFORMATION)]; WinUsb_ReadPipe(interfaceHandl e,
288
USB. Praktyczne programowanie z Windows API w C++
USB_ENDPOINT_DIRECTION_IN(pipeInformation->PipeId), buffer, bufferSize, &lengthTransferred, 0); printf("Odczyt z potoku %d: %s \nbajty przeczytane: %d.\n", USB_ENDPOINT_DIRECTION_IN(pipeInformation->PipeId), buffer, lengthTransferred); //
-------------------------------------------------------------------
Funkcja WinUsb_ResetPipe() BOOL stdcall WinUsb_ResetPipe( IN WINUSB_INTERFACE_HANDLE InterfaceHandle, IN UCHAR PipeID ); Funkcja resetuje zawartość bufora punktu końcowego identyfikowanego przez PipeID.
Funkcja WinUsb_SetCurrentAlternateSetting() BOOL stdcall WinUsb_SetCurrentAlternateSetting( IN WINUSB_INTERFACE_HANDLE InterfaceHandle, IN UCHAR AlternateSetting ); Funkcja umieszcza alternatywne ustawienia interfejsu identyfikowanego przez In te r faceHandle w tablicy interfejsów danej konfiguracji urządzenia wykonawczego.
Funkcja WinUsb_SetPipePolicy() BOOL IN IN IN IN IN );
stdcall WinUsb_SetPipePolicy( WINUSB_INTERFACE_HANDLE InterfaceHandle, UCHAR PipeID, ULONG PolicyType, ULONG ValueLength, PVOID Value
Asynchronicznie wywoływana funkcja konfiguruje parametry transmisyjne związane z wybranym punktem końcowym. Znaczenie poszczególnych param etrów je st iden tyczne ja k dla WinUsb_GetPipePolicy(). / / -------------------------------------------------------------------------WINUSB_PIPE_INFORMATION *pipeInformation = new \ WINUSB_PIPE_INFORMATION[sizeof(WINUSB_PIPE_INFORMATION)]; ULONG timeOut = 1000; //Deklarowany czas przeterminowania wynosi 1000 milisekund bResult = WinUsb_SetPipePolicy(interfaceHandle, pipeInformation->PipeId, PIPE_TRANSFER_TIMEOUT, sizeof(timeOut), &timeOut); / / --------------------------------------------------------------------------
Funkcja WinUsb_SetPowerPolicy() BOOL stdcall WinUsb_SetPowerPolicy( IN WINUSB_INTERFACE_HANDLE InterfaceHandle,
Rozdział 7. ♦ Biblioteki WinUSB oraz LibUSB
289
IN ULONG PolicyType, IN ULONG ValueLength, IN PVOID Value ); Funkcja ustala parametry opisujące sposób zasilania urządzenia. Znaczenie poszcze gólnych parametrów je st identyczne ja k w przypadku WinUsb_GetPowerPolicy().
Funkcja WinUsb_WritePipe() BOOL stdcall WinUsb_WritePipe( IN WINUSB INTERFACE HANDLE InterfaceHandle IN UCHAR PipeID, IN PUCHAR Buffer, IN ULONG BufferLength, OUT PULONG LengthTransferred, IN LPOVERLAPPED Overlapped ); Funkcja wysyła (zapisuje) dane do bufora punktu końcowego identyfikowanego przez PipeID. Parametr ten odpowiada zawartości pola bEndpointAddress struktury opisują cej deskryptor punktu końcowego (patrz rozdział 3., tabela 3.12). W skaźnik Buffer wskazuje bufo r danych w yjściow ych. Param etr BufferLength określa m aksym alną liczbę bajtów umieszczanych w buforze wyjściowym. W skaźnik LengthTransferred wskazuje liczbę aktualnie wysłanych bajtów. Liczba faktycznie wysłanych bajtów może być mniejsza niż deklarowany rozmiar bufora; może to oznaczać zakończenie transmisji danych (patrz opis znacznika SHORT_PACKET_TERMINATE). Liczbę faktycznie przetransm itowanych bajtów funkcja umieszcza w zmiennej wskazywanej przez LengthTrans ferred, stanowiącej przedostatni parametr. W ten sposób działa mechanizm ochrony dla wysyłanych danych. W skaźnik Overlapped wskazuje strukturę OVERLAPPED. / / -------------------------------------------------------------------------UCHAR buffer[] = "xxxxxx"; ULONG bufferLength = strlen(buffer); ULONG lengthTransferred = 0; WinUsb_Wri tePipe(interfaceHandle, USB_ENDPOINT_DIRECTION_OUT(pipeInformation->PipeId), buffer, bufferLength, &lengthTransferred, 0); / / --------------------------------------------------------------------------
Biblioteka LibUSB Zasoby biblioteki LibUSB są dostępne na zasadach opisanych regułam i licencji GPL (powszechnej licencji GNU) oraz LGPL (mniejszej ogólnej powszechnej licencji GNU) i można je pobrać ze strony: http://sourceforge.net/apps/trac/libusb-win32/wiki. W ka talogu \bin\ znajduje się aplikacja inf-wizard.exe, za pom ocą której m ożna utworzyć plik .in f oraz katalog instalacyjny dla wybranego urządzenia peryferyjnego, tak jak pokazano na rysunkach 7.10 - 7.12.
290
USB. Praktyczne programowanie z Windows API w C++
Rysunek 7.10. Wybór urządzenia
”
Iibusb-win32 [nf-Wizard
1=1
i
l~ ^ __J
Device Selection Select your device from the list o f detected devices below. I f your device isn't listed then either connect it or dick 'Next” and enter your device description manually.
Vendor ID
Product ID
Description
OxFFFF
0x8081
Urządzenie USB interfejsu HID {Interface 1)
OxFFFF
0x8081
Urządzenie USB interfejsu HID (Interface 0)
0X22BA OxOBDA
0x0108 0x0158
Urządzenie USB interfejsu HID USB Mass Stroage Device
0x0A 12
0x0001
Generic Bluetooth Radio
0x0-09
0x0499
Urządzenie USB interfejsu HID »r
[
< Back
~|
]
[
Next >
]
[
►
Cancel
]
Rysunek 7.11. Dane konfiguracyjne urządzenia
Rysunek 7.12. Potwierdzenie utworzenia pakietu instalacyjnego urządzenia libusb
"
Iibusb-win32 Inf-Wizard
A window s d riv e r in stallation package has been cre ated fo r the following device: V endor ID:
0x22BA
Product ID:
0x0108
Device description:
Urządzenie USB Interfejsu HID
Manufacturer:
Technology Innovation Holdings, Ltd
This package contains libusb-t
[
Install Now..
]
Done
Prawidłowo zainstalowany sprzęt jest widoczny w menedżerze urządzeń jako urządze nie libusb, tak ja k pokazano na rysunku 7.13.
Rozdział 7. ♦ Biblioteki WinUSB oraz LibUSB
291
Rysunek 7.13. Umiejscowienie urządzenia libusb w menedżerze urządzeń
Poprawnie zainstalowany sterownik libusb0.sys tworzy w trybie jądra operacyjny obiekt urządzenia FDO, tak jak obrazowo pokazano na rysunku 7.14. Biblioteka libusb0.dll implementuje zestaw funkcji eksportowych, umożliwiający programiście bezpośred nie wykonywanie operacji wejścia-wyjścia, bez konieczności jaw nego posługiwania się funkcjami oraz rozkazami opisanymi w rozdziale 6. Prezentowane w dalszej części podrozdziału funkcje opakowują rozkazy używane na poziomie jądra systemu i przek azująje bezpośrednio do sterownika libusb0.sys w celu dalszego przetwarzania. Rysunek 7.14. Model warstwowy sterowników urządzenia libusb
Definicje wszystkich struktur oraz funkcji biblioteki znajdują się w pliku nagłówko wym lusb0jusb.h. Elementy biblioteki LibUSB w systemach Windows są implemento wane w zewnętrznej bibliotece dynamicznej libusb0.dll, która powinna się znajdować w katalogu systemowym. Sterownik libusb0.sys powinien się znajdować w katalogu \drivers\. Począwszy od wersji 1.2.0.0, sterownik ten powinien być cyfrowo podpisa ny, co umożliwia instalowanie go w 64-bitowych systemach operacyjnych. Podczas kom pilacji program ów korzystających z LibUSB należy pamiętać o statycz nym łączeniu ich z biblioteką im portową libusb.lib, która jest dostępna w katalogu in stalacyjnym \lib dla różnych wersji kompilatorów C++. Struktury danych używane przez bibliotekę LibUSB, które opisują urządzenia, interfej sy, punkty końcowe i konfiguracje oraz odpowiadające im deskryptory, są w większo ści identyczne z przedstawionymi w rozdziale 3. W katalogu instalacyjnym \bin\x86\
292
USB. Praktyczne programowanie z Windows API w C++
znajduje się aplikacja testlibusb-win.exe, za pom ocą której można odczytać zawartość poszczególnych deskryptorów urządzenia, tak jak pokazano na rysunku 7.15. Rysunek 7.15. Odczyt zawartości deskryptorów urządzenia libusb
W dalszej części podrozdziału zamieszczono wykaz funkcji jądra biblioteki, funkcji w ykorzystyw anych do zarządzania urządzeniam i USB oraz funkcji kontrolujących transfer danych w systemie.
Funkcje jądra biblioteki Funkcja usb_init() void usb_init(void); Inicjalizuje wszystkie wewnętrzne struktury biblioteki LibUSB i powinna być w ywo ływana jako pierwsza w programie.
Funkcja usb_find_busses() int usb_find_busses(void); Zwraca liczbę portów magistrali USB dostępnych w systemie i powinna być wywo ływ ana każdorazowo po funkcji u s b _ in it().
Rozdział 7. ♦ Biblioteki WinUSB oraz LibUSB
293
Funkcja usb_find_devices() int usb_find_devices(void); Zw raca liczbę urządzeń USB, które są aktualnie przyłączone do portów magistrali, określonych za pom ocą funkcji usb_find_busses().
Funkcja usb_get_busses() struct usb_bus *usb_get_busses(void); Zwraca wskaźnik do struktury. struct usb_bus { struct usb_bus *next, *prev; char dirname[LIBUSB_PATH_MAX]; struct usb_device *devices; unsigned long location; struct usb_device *root_dev; };
Funkcja usb_set_debug() void usb_set_debug(int level); Umożliwia ustalenie poziomu debugowania; parametrowi level można przypisać na stępujące wartości: 0 — LOG_OFF, 1 — LOG_ERROR, 2 — LOG_WARNING, 3 — LOG_INFO lub 4 — LOG DEBUG.
Funkcje do zarządzania urządzeniem libusb Funkcja usb_open() usb_dev_handle *usb_open(struct usb_device *dev); Otwiera urządzenie libusb do transmisji (otwiera połączenie z urządzeniem) i zwraca jego identyfikator. Argumentem funkcji je st wskaźnik do struktury: struct usb_device { struct usb_device *next, *prev; char filename[LIBUSB_PATH_MAX]; struct usb_bus *bus; struct usb_device_descriptor descriptor; struct usb_config_descriptor *config; void *dev; / / Darwin support unsigned char devnum; unsigned char num_children; struct usb_device **children; };
29 4
USB. Praktyczne programowanie z Windows API w C++
Funkcja usb_close() int usb_close(usb_dev_handle *dev); Zam yka do transmisji urządzenie (zamyka połączenie z urządzeniem), którego identy fikator został uprzednio przydzielony za pom ocą funkcji usb_open(). N a listingu 7.3 zaprezentowano przykład posługiwania się funkcjami u sb _ in it(), usb_ find_busses(), usb_find_devices(), usb_get_busses(), usb_open() oraz usb_close() w celu odczytania zawartości deskryptora urządzenia libusb. Listing 7.3. Odczytanie fragmentu deskryptora urządzenia zainstalowanego jako urządzenie libusb #include "lusb0_usb.h" #include using namespace std; #pragma comment(lib, "libusb.lib") #define VID 0x22ba #define PID 0x0108 / / -------------------------------------------------------------------int getDevice(struct usb_device *usbDev) { usb_dev_handle *devHandle = NULL; char buffer[256]; int success; devHandle = usb_open(usbDev); if (devHandle) { if (usbDev->descriptor.iManufacturer) { success = usb_get_string_simple(devHandle, usbDev->descriptor.iManufacturer, buffer, sizeof(buffer)); if (success > 0) { printf("Manufacturer: %s\n",buffer); } } if (usbDev->descriptor.iProduct) { success = usb_get_string_simple(devHandle, usbDev->descriptor.iProduct, buffer, sizeof(buffer)); if (success > 0) { printf("Product %s\n",buffer); } } printf("Descriptor Length = %u\n",usbDev->descriptor.bLength); printf("NumConfigurations = %d\n",usbDev->descriptor.bNumConfigurations); printf("MaxPacketSize0 = %d\n",usbDev->descriptor.bMaxPacketSize0); printf("Descriptor Type = %u\n",usbDev->descriptor.bDescriptorType); printf("SerialNumber = %d\n",usbDev->descriptor.iSerialNumber); } if (devHandle) usb_close(devHandle); return 0; }
Rozdział 7. ♦ Biblioteki WinUSB oraz LibUSB
295
/ / -------------------------------------------------------------------int main() { usb_bus *bus; usb_ini t(); usb_find_busses(); usb_find_devices(); for (bus = usb_get_busses(); bus; bus = bus->next) { struct usb_device *device; for (device = bus->devices; device; device = device->next) { if ((device->descriptor.idProduct == PID) || (device->descriptor.idVendor == VID)){ printf("Devi ce:\nPID %x\nVID %x\n",devi ce->descriptor.idProduct, device->descriptor.idVendor); getDevice(device); } } } system("PAUSE"); return 0; } / / --------------------------------------------------------------------
Funkcja usb_set_configuration() int usb_set_configuration(usb_dev_handle *dev, int configuration); Przypisuje urządzeniu identyfikowanemu przez wskaźnik dev numer konfiguracji okre ślony param etrem co n fig u ratio n . N um er żądanej konfiguracji je st przechowywany w polu bConfigurationValue struktury usb_config_descriptor opisującej deskryptor konfiguracji urządzenia libusb. Prawidłowo wykonana funkcja zwraca 0, a w przypad ku wystąpienia błędu — wartość ujemną.
Funkcja usb_set_altinterface() int usb_set_altinterface(usb_dev_handle *dev, int alternate); Poprzez parametr a lte rn a te określa alternatywne ustawienia interfejsu dla danej kon figuracji urządzenia identyfikowanego przez wskaźnik dev. Alternatywne ustawienia interfejsu są zapisane w polu bA ltern ateS etting struktury u sb _ in terface_descriptor opisującej deskryptor interfejsu urządzenia libusb. Prawidłowo wykonana funkcja zwra ca 0, a w przypadku wystąpienia błędu — wartość ujemną.
Funkcja usb_clear_halt() int usb_clear_halt(usb_dev_handle *dev, unsigned int ep); Resetuje ustaw ienia punktu końcowego o adresie identyfikow anym przez param etr ep. Adres punktu końcowego jest przechowywany w polu bEndpointAddress struktury
296
USB. Praktyczne programowanie z Windows API w C++
opisującej deskryptor punktu końcowego. Prawidłowo wykonana funkcja zwraca 0, a w przypadku wystąpienia błędu — wartość ujemną.
Funkcja usb_reset() int usb_reset(usb_dev_handle *dev); Resetuje urządzenie poprzez wysłanie kom unikatu R E SE T do portu, do którego jest przyłączone urządzenie identyfikowane przez wskaźnik dev. Prawidłowo wykonana funkcja zwraca wartość 0. Po wywołaniu funkcji usb_reset() należy powtórnie prze prowadzić proces enumeracji urządzeń oraz wywołać funkcję usb_open().
Funkcja usb_claim_interface() int usb_claim_interface(usb_dev_handle *dev, int interface); Przypisuje żądany numer interfejsu in te rfa c e do urządzenia. Numer dostępnego inter fejsu jest przechowywany w polu bInterfaceNumber struktury usb_interface_descriptor opisującej deskryptor interfejsu urządzenia libusb. Funkcja powinna być używana przed wywołaniami innych funkcji uzyskujących dostęp do deskryptora interfejsu, np. usb_ s e t_ a ltin te r f a c e ( ) lub usb_bulk_w rite(). Prawidłowo w ykonana funkcja zwraca wartość 0. M ożliwe kody błędów to: EBUSY — interfejs niedostępny dla urządzenia, ENOMEM — niew ystarczająca ilość pam ięci do przeprowadzenia operacji przypisania interfejsu do urządzenia.
Funkcja usb_release_interface() int usb_release_interface(usb_dev_handle *dev, int interface); Zwalnia interfejs uprzednio przydzielony za pomocą usb_claim _interface(). Prawidło wo wykonana funkcja zwraca 0, a w przypadku wystąpienia błędu — wartość ujemną.
Funkcja usb_control_msg() int usb_control_msg(usb_dev_handle *dev, int requesttype, int request, int value, int index, char *bytes, int size, int timeout); W ysyła poprzez domyślny (zerowy) potok kontrolny dane do urządzenia identyfiko wanego w skaźnikiem dev. Param etr requesttype identyfikuje typ rozkazu, parametr request jest kodem rozkazu, wartość parametru value zależy od typu rozkazu, a w ar tość param etru index — od typu adresata rozkazu (patrz rozdział 6., tabele 6.4 i 6.5 oraz tabela 7.3 w tym rozdziale). Standardowe kody rozkazów LibUSB to: USB_REQ_GET_STATUS USB_REQ_CLEAR_FEATURE //zarezerwowane USB_REQ_SET_FEATURE //zarezerwowane USB_REQ_SET_ADDRESS
0x00 0x01 0x02 0x03 0x04 0x05
Rozdział 7. ♦ Biblioteki WinUSB oraz LibUSB
USB_REQ_GET_DESCRIPTOR USB_REQ_SET_DESCRIPTOR USB_REQ_GET_CONFIGURATION USB_REQ_SET_CONFIGURATION USB_REQ_GET_INTERFACE USB_REQ_SET_INTERFACE USB_REQ_SYNCH_FRAME
297
0x06 0x07 0x08 0x09 0x0A 0x0B 0x0C
Standardowe typy żądań LibUSB to: USB_TYPE_STANDARD USB_TYPE_CLASS USB_TYPE_VENDOR USB_TYPE_RESERVED
(0x00 << (0x01 << (0x02 << (0x03 <<
5) 5) 5) 5)
Standardowe typy adresatów LibUSB to: USB_RECIP_DEVICE USB_RECIP_INTERFACE USB_RECIP_ENDPOINT USB_RECIP_OTHER
0x00 0x01 0x02 0x03
Kierunek transferu danych je st określony jako: USB_ENDPOINT_IN USB_ENDPOINT_OUT
0x80//10000000b 0x00 //00000000b
(patrz rozdział 3., tabela 3.12)
W przypadku transferu typu OUT wskaźnik bytes wskazuje bufor z danymi w yjściowy mi (wysyłanymi), zaś w przypadku transferu typu IN — bufor danych wejściowych. Parametr size jest rozmiarem bufora danych. Parametr timeout określa czas przetermi nowania operacji transferu danych wyrażony w milisekundach. Funkcja zwraca liczbę bajtów wysłanych/odebranych lub wartość ujem ną w przypadku niepowodzenia. / / ----------------------------------------------------------------char bytes[7]; //bytes[0] = 0x00; //bytes[1] = /* ...* /; //... int nBytes; nBytes = usb_control_msg(devHandle, USB_TYPE_STANDARD | USB_RECIP_DEVICE | USB_ENDPOINT_IN, USB_REQ_GET_STATUS, 0,0, bytes, sizeof(bytes), 100); printf("\nBytes = %d\n", nBytes); / / ----------------------------------------------------------------Producenci urządzeń m ogą definiować własne typy rozkazów; wywołanie funkcji może się wówczas odbywać w następujący sposób: / / ----------------------------------------------------------------#define USB_VENDOR_COMMAND 0x15 //... char bytes[100]; int nBytes; nBytes = usb_control_msg(devHandle, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN, USB_VENDOR_COMMAND, 0,0, bytes, sizeof(bytes), 1000); / / -----------------------------------------------------------------
298
USB. Praktyczne programowanie z Windows API w C++
Funkcja usb_get_string() int usb_get_string(usb_dev_handle *dev, int index, int langid, char *buf, size_t buflen); U m ieszcza w buforze w skazyw anym przez buf deskryptor łańcuchowy urządzenia w skazywanego przez dev. Param etr index określa indeks deskryptora łańcuchowego (patrz rozdział 6., listing 6.17), a parametr langid jest identyfikatorem języka użytego do opisu deskryptora [17]. Prawidłowo wykonana funkcja zwraca liczbę odczytanych bajtów, a w przypadku niepowodzenia — wartość ujemną. Poniżej zamieszczono przykłady odczytu wybranego indeksu deskryptora łańcucho wego urządzenia za pom ocą funkcji usb_control_msg() oraz u sb _ g et_ strin g (). / / ---------------------------------------------------------------------------------------------------------------
typedef struct _LIB_USB_DESCRIPTPR_REQUEST { //samodzielnie zdefiniowana //struktura struct { int requesttype; int request; int value; int index; } setupPacket; char bytes[255]; //lub Data int size; int timeout; } LIB_USB_DESCRIPTPR_REQUEST; LIB_USB_DESCRIPTPR_REQUEST descRequest; descRequest.setupPacket.requesttype = USB_TYPE_STANDARD | USB_RECIP_DEVICE | USB_ENDPOINT_IN; descRequest.setupPacket.request = USB_REQ_GET_DESCRIPTOR; descRequest.setupPacket.value = (USB_DT_STRING << 8) | 1;//indeks deskryptora = 1 descRequest.setupPacket.index = 0x0409;/ / l a n g I D ; descRequest.size = sizeof(descRequest.bytes); descRequest.timeout = 10; nBytes = usb_control_msg(devHandle, descRequest.setupPacket.requesttype, descRequest.setupPacket.request, descRequest.setupPacket.value, descRequest.setupPacket.index, descRequest.bytes, descRequest.size, descRequest.timeout); printf("\nBytes = %d\n", nBytes); printf("Deskryptor łańcuchowy = %ls\n",&descRequest.bytes[2]); / / ----------------------------------------------------------------int descriptorIndex = 2; //indeks deskryptora = 2 int langID = 0x0409; char bytes[255] = {0}; nBytes = usb_get_string(devHandle, descriptorIndex, langID, bytes, sizeof(bytes)); printf("\nBytes = %d\n", nBytes); printf("Deskryptor łańcuchowy = %ls\n",&bytes[2]); / / -----------------------------------------------------------------
Rozdział 7. ♦ Biblioteki WinUSB oraz LibUSB
299
Funkcja usb get string simple() int usb_get_string_simple(usb_dev_handle *dev, int index, char *buf, size_t buflen); Odczytuje deskryptor łańcuchowy urządzenia, używając językowych ustawień domyśl nych, i konwertuje go na łańcuch ASCII.
Funkcja usb_get_descriptor() int usb_get_descriptor(usb_dev_handle *dev, unsigned char type, unsigned char index, void *buf, int size); Umieszcza w buforze wskazywanym przez buf deskryptor urządzenia określony przez parametry type oraz index. Typy deskryptorów urządzeń libusb są definiowane w następujący sposób: USB DT DEVICE USB DT CONFIG USB DT STRING USB DT INTERFACE USB DT ENDPOINT USB DT HID USB_DT_REPORT USB DT PHYSICAL USB_DT_HUB
0x01 0x02 0x03 0x04 0x05 0x21 0x22 0x23 0x29
Param etr siz e określa rozm iar deskryptora i w bibliotece LibU SB je st definiowany następująco: USB DT DEVICE SIZE usb"DT"CONFIG SIZE usb"DT"interface SIZE usb"DT"endpoint size usb"DT"endpoint audio size usb"DT"hub nonvar size
18 9 9 7 9 7
Zawartość deskryptora jest przesyłana domyślnym potokiem kontrolnym. Prawidłowo wykonana funkcja zwraca liczbę odczytanych bajtów, a w przypadku niepowodzenia — wartość ujemną. Poniżej zamieszczono przykład odczytania fragmentu deskryptora urządzenia. //
-----------------------------------------------------------------
nBytes = usb_get_descriptor(devHandle, USB_DT_DEVICE, 0, &usbDev->descriptor, USB_DT_DEVICE_SIZE); printf("\nBytes = %d\n", nBytes); printf("\nbMaxPacketSize0 = %d\n", usbDev->descriptor.bMaxPacketSize0); printf("\nidVendor = %x\n", usbDev->descriptor.idVendor); printf("\nidProduct = %x\n", usbDev->descriptor.idProduct); / / -----------------------------------------------------------------
Funkcja usb_get_descriptor_by_endpoint() int usb_get_descriptor_by_endpoint(usb_dev_handle *dev, int ep, unsigned char type, unsigned char index, void *buf, int size);
300
USB. Praktyczne programowanie z Windows API w C++
U m ieszcza w buforze w skazyw anym przez buf zaw artość deskryptora urządzenia. Postać deskryptora je st określona parametrami type oraz index. Zawartość deskrypto ra je st przesyłana potokiem kontrolnym identyfikowanym przez param etr ep (patrz opis pola bmAttributes w tabeli 3.12). Prawidłowo wykonana funkcja zwraca liczbę odczytanych bajtów, a w przypadku niepow odzenia — wartość ujemną. Poniżej za mieszczono przykład odczytania fragmentu deskryptora urządzenia poprzez domyślny potok kontrolny. / / ----------------------------------------------------------------nBytes = usb_get_descriptor_by_endpoint(devHandle, USB_ENDPOINT_TYPE_CONTROL, USB_DT_DEVICE, 0, &usbDev->descriptor, USB_DT_DEVICE_SIZE); printf("\nBytes = %d\n", nBytes); printf("\nbNumConfigurations = %u\n", usbDev->descriptor.bNumConfigurations); / / -----------------------------------------------------------------
Funkcje realizujące transfer masowy Funkcja usb_bulk_write() int usb_bulk_write(usb_dev_handle *dev, int ep, char *bytes, int size, int timeout); Realizuje masowy transfer danych w skazyw anych przez wskaźnik bytes do punktu końcowego o adresie identyfikowanym przez parametr ep. Adres punktu końcowego je st przechowywany w polu bEndpointAddress struktury opisującej deskryptor punktu końcowego. Adresy oraz typy punktów końcowych można określić, korzystając z makrodefinicji: USB_ENDPOINT_ADDRESS_MASK USB_ENDPOINT_DIR_MASK USB_ENDPOINT_TYPE_MASK USB_ENDPOINT_TYPE_CONTROL USB_ENDPOINT_TYPE_ISOCHRONOUS USB_ENDPOINT_TYPE_BULK USB_ENDPOINT_TYPE_INTERRUPT
0x0f 0x80 0x03 0 1 2 3
//bEndpointAddress, patrz tabela 3.12 //bmAttributes, patrz tabela
3.12
Param etr size je st rozmiarem transmitowanych danych, zaś timeout identyfikuje czas przeterminowania operacji zapisu danych do bufora punktu końcowego ep. Prawidło wo wykonana funkcja zwraca liczbę wysłanych bajtów, a w przypadku niepowodzenia — wartość ujemną. / / ----------------------------------------------------------------#define EP_OUT 0x02 char bytes[7]; bytes[0] = 0x00; bytes[1] = /* ...* / if(usb_bulk_write(devHandle, EP_OUT, bytes, sizeof(bytes), 1000) != sizeof(bytes)) //błąd / / -----------------------------------------------------------------
Rozdział 7. ♦ Biblioteki WinUSB oraz LibUSB
301
Funkcja usb_bulk_read() int usb_bulk_read(usb_dev_handle *dev, int ep, char *bytes, int size, int timeout); Odczytuje dane transmitowane potokiem masowym, których źródłem jest punkt koń cowy o adresie identyfikow anym przez param etr ep. Odebrane dane są umieszczane w buforze wskazywanym przez wskaźnik bytes. Prawidłowo wykonana funkcja zwraca liczbę odebranych bajtów, a w przypadku niepowodzenia — wartość ujemną.
Funkcje realizujące transfer przerwaniowy Funkcja usb_interrupt_write() int usb_interrupt_write(usb_dev_handle *dev, int ep, char *bytes, int size, int timeout); R ealizuje transfer przerw aniow y danych w skazyw anych przez w skaźnik bytes do punktu końcowego o adresie identyfikowanym przez parametr ep. Parametr siz e jest rozmiarem transmitowanych danych, zaś timeout identyfikuje czas przeterminowania operacji zapisu danych do bufora punktu końcowego ep. Prawidłowo wykonana funkcja zwraca liczbę wysłanych bajtów, a w przypadku niepowodzenia — wartość ujemną.
Funkcja usb_interrupt_read() int usb_interrupt_read(usb_dev_handle *dev, int ep, char *bytes, int size, int timeout); Odczytuje dane transmitowane potokiem przerwaniowym, których źródłem je st punkt końcowy ep urządzenia identyfikowanego przez wskaźnik dev. Odebrane dane są umiesz czane w buforze wskazywanym przez wskaźnik bytes. Prawidłowo wykonana funkcja zwraca liczbę odebranych bajtów, a w przypadku niepowodzenia — wartość ujemną.
Funkcje asynchroniczne Funkcja usb_isochronous_setup_async() int usb_isochronous_setup_async(usb_dev_handle *dev, void **context, unsigned char ep, int pktsize); K ieruje żądanie alokow ania bufora danych o rozmiarze p k tsiz e do izochronicznego punktu końcowego o adresie ep urządzenia wskazywanego przez dev. Rozmiar bufo ra nie może być większy niż wartość zapisana w polu wMaxPacketSize struktury usb_ endpoint_descriptor opisującej deskryptor punktu końcowego urządzenia libusb. R o dzaj żądania jest przechowywany pod postacią wskaźnika context odgrywającego rolę nazwy wyróżniającej dla grupy logicznie powiązanych, następujących po sobie ope racji kierowanych do określonego punktu końcowego. Po zakończeniu wykonywania
302
USB. Praktyczne programowanie z Windows API w C++
operacji kierowanych do w yróżnionego punktu końcowego wskaźnik context pow i nien zostać zwolniony. Prawidłowo w ykonana funkcja zwraca 0, a w przypadku nie powodzenia — wartość ujemną.
Funkcja usb_bulk_setup_async() int usb_bulk_setup_async(usb_dev_handle *dev, void **context, unsigned char ep); K ieruje żądanie transm isji potokiem masowym do punktu końcowego o adresie ep urządzenia wskazywanego przez dev. Rodzaj żądania jest przechowywany pod posta cią wskaźnika context. Prawidłowo w ykonana funkcja zwraca 0, a w przypadku nie powodzenia — wartość ujemną.
Funkcja usb_interrupt_setup_async() int usb_interrupt_setup_async(usb_dev_handle *dev, void **context, unsigned char ep); Kieruje żądanie transmisji potokiem przerwaniowym do punktu końcowego o adresie ep urządzenia wskazywanego przez dev. Rodzaj żądania jest przechowywany pod po stacią w skaźnika context. Prawidłowo wykonana funkcja zwraca 0, a w przypadku niepowodzenia — wartość ujemną.
Funkcja usb_submit_async() int usb_submit_async(void *context, char *bytes, int size); W ramach uprzednio określonego kontekstu wysyła komunikat z bufora wskazywane go przez wskaźnik bytes. Rozmiar bufora określa parametr size. W zależności od ty pu punktu końcowego potoku określonego w ramach kontekstu zdefiniowanego funk cjami usb_xxx_setup_async(), dane będą zapisywane do (EP OUT) lub czytane z (EP IN) wybranego punktu końcowego. Prawidłowo wykonana funkcja zwraca 0, a w przy padku niepowodzenia — wartość ujemną.
Funkcja usb_reap_async() int usb_reap_async(void *context, int timeout); Po upływie czasu przeterm inow ania timeout (wyrażonego w milisekundach) przery w a operacje wykonywane w ram ach określonego kontekstu. Prawidłowo wykonana funkcja zwraca liczbę w ysłanych lub odebranych bajtów.
Funkcja usb_reap_async_nocancel() int usb_reap_async_nocancel(void *context, int timeout); Anuluje rozkaz przerw ania operacji w ykonyw anych w ramach kontekstu po upływie czasu przeterminowania.
Rozdział 7. ♦ Biblioteki WinUSB oraz LibUSB
303
Funkcja usb_cancel_async() int usb_cancel_async(void *context); Bezwarunkowo wstrzymuje wykonywanie operacji transmisji danych w ramach okre ślonego kontekstu.
Funkcja usb_free_async() int usb_free_async(void **context); Zwalnia wskaźnik context, co oznacza zwolnienie pamięci przydzielonej dla kontek stu określonego funkcjami usb_xxx_setup_async(). Na listingu 7.4 zaprezentowano przykład zdefiniowania kontekstu dla operacji cyklicz nego odczytu danych z określonego punktu końcowego oraz posługiwania się wybra nymi funkcjami biblioteki LibUSB, które są wywoływane w sposób asynchroniczny w celu odczytania zawartości bufora wyjściowego urządzenia libusb, komunikującego się z komputerem za pośrednictwem potoku przerwaniowego. N a rysunku 7.16 zaprezen towano model logiczny urządzenia, którego działanie kontroluje program z listingu 7.4. Wynik działania programu jest podobny do pokazanego w rozdziale 6. na rysunku 6.3. Rysunek 7.16. Model logiczny urządzenia, którego działanie kontroluje program z listingu 7.4
Listing 7.4. Cykliczny odczyt danych napływających potokiem przerwaniowym z punktu końcowego urządzenia libusb #include "lusb0_usb.h" #include using namespace std; #pragma comment(lib, "libusb.lib") #define #define #define #define #define #define
VID 0x22ba PID 0x0108 INTERFACE 0 CONFIGURATION 1 BUFFER_SIZE 8 EP1_IN 0x81
usb_dev_handle *getDevice(); usb_dev_handle* libUsbDeviceSetup()
30 4
USB. Praktyczne programowanie z Windows API w C++
{ usb_dev_handle *devHandle = NULL; usb_init(); usb_find_busses(); usb_find_devi ces(); if(!(devHandle = getDevice())) { printf("Nie znaleziono urządzenia.\n"); cin.get(); return NULL; } if(usb_set_configuration(devHandle, CONFIGURATION) < 0) { printf("Nie można przypisać konfiguracji.\n"); cin.get(); return NULL; } if(usb_claim_interface(devHandle, INTERFACE) < 0) { printf("Nie przypisano interfejsu.\n"); cin .g et(); return NULL; } return devHandle; } / / -------------------------------------------------------------------usb_dev_handle *getDevice() { struct usb_bus *bus; struct usb_device *device; for (bus = usb_get_busses(); bus; bus = bus->next) { for (device = bus->devices; device; device = device->next) { if (device->descriptor.idVendor == VID && device->descriptor.idProduct == PID ) { usb_dev_handle *devHandle; printf("Znaleziono VID: %x , PID: %x \n", VID, PID); if (!(devHandle = usb_open(device))) { printf("Nie można otworzyć urządzenia do transm isji.\n"); return NULL; } return devHandle; } } } return NULL; } / / -------------------------------------------------------------------void interruptTransfer(usb_dev_handle *dev) { byte bufer[BUFFER_SIZE] = {0}; void *readContext = NULL; //Ustalenie kontekstu dla asynchronicznego odczytu danych z EP1 //potoku przerwaniowego usb_interrupt_setup_async(dev, &readContext, EP1_IN); usb_submit_async(readContext, bufer, sizeof(bufer)); while (true){ //Cykliczny odczyt z potoku przerwaniowego usb_reap_async(readContext, 1000); usb_submit_async(readContext, bufer, sizeof(bufer)); printf("%d, %d, %d, %d, %d, %d, %d\n", bufer[0],bufer[1],bufer[2],bufer[3],bufer[4],
Rozdział 7. ♦ Biblioteki WinUSB oraz LibUSB
305
bufer[5], bufer[6]); if(bufer[4] == 16) / / Dopóki nie naciśnięto przycisku na konsoli break; } usb_reap_async(readContext, 1000); //Zwolnienie kontekstu usb_free_async(&readContext); //Zwolnienie interfejsu usb_set_altinterface(dev, INTERFACE); usb_release_i nterface(dev, INTERFACE); } / / --------------------------------------------------------------------int main() { usb_dev_handle *devHandle; if ((devHandle = libUsbDeviceSetup()) == NULL) { e x it(-1); } interruptTransfer(devHandle); usb_close(devHandle); system("PAUSE"); return 0; } / / --------------------------------------------------------------------
Podsumowanie Niniejszy rozdział należy traktować jako uzupełnienie poprzedniego. Zawarto w nim opis praktycznych metod wykorzystywania w działających programach zasobów eks portow ych bibliotek WinUSB oraz LibUSB. Omawiane kody zostały przedstawione w formach proceduralnych w ten sposób, aby Czytelnicy mogli je bez trudu wykorzy stać. Przedstawione algorytmy są podatne na wszelkiego rodzaju modyfikacje i uzu pełnienia, w zależności od własnych potrzeb i aktualnych wymagań. Więcej praktycz nych porad na tem at w ykorzystania zasobów biblioteki WinUSB podczas tw orzenia oprogramowania dla przykładowego urządzenia wykonawczego można znaleźć w ob szernym artykule How to A ccess a USB D evice by Using WinUSB Functions dostęp nym na stronie http://msdn.microsoft.com/enus/library/windows/hardware/ff540174(v= vs.85).aspx oraz w dokumencie How to Use WinUSB to Communicate with a USB D e vice, który można pobrać ze strony http://msdn.microsoft.com/en-us/library/windows/ hardware/gg487341.aspx.
306
USB. Praktyczne programowanie z Windows API w C++
Rozdział 8.
Programowanie obiektowe transmisji USB Oprogramowanie tworzone z wykorzystaniem paradygmatu obiektowego koncentruje się na obiektach, a nie — ja k dotychczas — na funkcjach. W tradycyjnym ujęciu obiekt zwykło się definiować jako zestaw danych wraz z metodami (będącymi obiektowym określeniem funkcji). W ielką zaletą stosowania obiektów jest to, że można im przypi sać określoną odpowiedzialność w trakcie działania programu. Obiekty zawsze mają informację o przynależności do określonego typu i zmianach swojego stanu, a kod re prezentowany przez metody umożliwia im wykonywanie konkretnych działań.
Obiektowość Jedną z najodpowiedniejszych strategii tworzenia kodu jest implementowanie konkret nej reguły tylko w jednym miejscu. Ten sposób projektowania określa się jako regułę jednego wystąpienia [3, 4]. Jeżeli istnieje jakaś reguła określająca sposób wykonywa nia danej operacji, to należy j ą zaimplementować tylko jeden raz. Zazwyczaj wymaga to stworzenia odpowiedniej klasy (lub kilku niezależnych klas) hermetyzującej kon kretne operacje oraz precyzyjnego zdefiniow ania sposobu ich wywoływania. W yko rzystując tę regułę, na obiekty m ożna spojrzeć poprzez jedną z trzech perspektyw za proponowanych przez Fowlera [8]: ♦ na poziomie koncepcji obiekt jest zbiorem różnego rodzaju odpowiedzialności, ♦ na poziom ie specyfikacji obiekt jest zbiorem metod (zachowań), które m ogą być wywoływane przez jego metody lub metody innych obiektów, ♦ na poziom ie implementacji obiekt składa się z kodu i danych oraz interakcji między nimi. N a rysunku 8.1 pokazano przykład statycznego diagramu klas dającego ogólny pogląd na logiczną konstrukcję kodu analizowanego z poziom u koncepcji. Projektując klasy
308
USB. Praktyczne programowanie z Windows API w C++
Rysunek 8.1. Statyczny diagram opisujący elementy składowe klasy TUSBDevice (na bazie których w przyszłości powstaną obiekty), należy się kierować zasadą, zgod nie z którą rodzaje odpowiedzialności obiektów za własne działania powinny być ści śle określone. D latego też jako przykłady odpowiedzialności obiektu reprezentującego urządzenia USB, które są dostępne w systemie, m ożna podać: ♦ dokonanie — na podstawie identyfikatorów GUID — identyfikacji i selekcji dołączonych urządzeń klasy HID, ♦ w ybór konkretnego przyrządu, na przykład z wykorzystaniem jego identyfikatora VID, ♦ uzyskanie ścieżki dostępu do interfejsu urządzenia, ♦ otwarcie portu USB do transmisji danych (uzyskanie dostępu do sterownika urządzenia), ♦ zaimplementowanie reguł odczytu danych z bufora wejściowego. U żytkow nik (klient) je st reprezentow any przez funkcję main() (program główny). W omawianym przypadku do zakresów jego odpowiedzialności należy: ♦ stworzenie obiektu na bazie klasy TUSBDevice, ♦ w odpowiedniej kolejności wywołanie funkcji składowych (metod) tego obiektu, ♦ na podstawie samodzielnie odczytanych właściwości urządzenia zaalokowanie pamięci i ustalenie rozmiaru odpowiednich buforów danych, ♦ prezentacja odczytanych raportów wejściowych, ♦ zwolnienie zaalokowanych przez program zasobów.
Rozdział 8. ♦ Programowanie obiektowe transmisji USB
309
N a listingu 8.1 zaprezentowano definicje odpowiadające statycznemu diagramowi klas z rysunku 8.1. Klasa TUSBDevice agreguje struktury HIDD_ATTRIBUTES, SP_DEVICE_INTERFACE_DETAIL_DATA, SP_DEVICE_INTERFACE_DATA oraz GUID. Z kolei klasa TUSBDevice oraz struktury HIDP_CAPS i HIDP_PREPARSED_DATA są deklarowane i używane w funkcji m ain(), dlatego też pozostają w relacjach zależności z programem klienta reprezento wanym przez tę funkcję. N a listingu 8.2 pokazano kod użytych funkcji składowych klasy TUSBDevice i głównej funkcji programu. W omawianym przykładzie port USB zostanie otwarty do transmi sji w trybie synchronicznym za pom ocą funkcji synchOpenUSBDevice(). Przykłady z listingów 8.1 i 8.2 dają ogólny wgląd w konstrukcję kodu analizowanego odpowiednio z poziomów specyfikacji i implementacji. N a rysunku 8.2 zaprezentowa no program w trakcie działania. Listing 8.1. Kod pliku nagłówkowego usb_R8_1.h, zawierającego definicje odpowiadające rysunkowi 8.1 # i f n d e f usb _R8_1H # d e f i n e usb _R8_1H # d e fin e search M axD evice
//
10
--------------------------------------------------------------------------
t e m p l a t e < c l a s s T> in lin e
v o i d r e l e a s e M e m o r y ( T &x)
{ assert(x de le te
[]
!= N U L L ) ; x;
x = NULL; }
//
--------------------------------------------------------------------------
t y p e d e f s t r u c t _HIDD_ ATT RIB UTE S { ULONG S i z e ; USHORT V e n d o r I D ; USHORT P r o d u c t I D ; USHORT V e r s i o n N u m b e r ; } HIDD _AT TR IBU TES , *PHIDD_ ATT RIB UT ES;
//
--------------------------------------------------------------------------
v o id
(
s td c a ll
*H idD _G etH idG uid)(O U T
bool
(
s td c a ll
* H id D _ G e tA ttrib u te s )(IN
LPGUID H i d G u i d ) ; HANDLE H i d D e v i c e O b j e c t ,
OUT PHIDD_ATTRIBUTES A t t r i b u t e s ) ;
//
--------------------------------------------------------------------------
cla ss
TUSBD evice {
p riv a te : s trin g
pathU SB D evice;
DWORD m em b e rI n d e x; HIDD_ATTRIBUTES h i d d A t t r i b u t e s ; PS P_ DE V IC E_ IN TE RF AC E_ DE TA IL _ DA TA d e v i c e I n t e r f a c e D e t a i l D a t a ; DWORD n u m b e r O f B y t e s R e a d ; DWORD r e s u l t ; GUID c l a s s G u i d ; DWORD d e v i c e I n t e r f a c e D e t a i l D a t a S i z e ; HDEVINFO d e v i c e I n f o S e t ; S P_ DE V IC E_I NT ER FA CE _D A TA d e v i c e I n t e r f a c e D a t a ; USHORT u s V i d ; co nst ch a r * chVid;
310
USB. Praktyczne programowanie z Windows API w C++
p u b lic : T U S B D e v ice (); ~ T U S B D e vic e (); v o id setU shortVid(U SH O RT v id ) ; v o id d is p la y E r r o r ( c o n s t bool
c h a r * m s g );
synchReadU SBReport(H AND LE h i d D e v O b j e c t , v o i d * i n p u t R e p o r t B u f f e r , ULONG i n p u t R e p o r t B u f f e r L e n g t h ) ;
HANDLE s y n c h O p e n U S B D e v i c e ( ) ; s trin g
ge tU S B D e v ice P a th (in t);
};
/ / -------------------------------------------------------------------------t y p e d e f USHORT USAGE, *PUSAGE; t y p e d e f s t r u c t _HIDP_PREPARSED_DATA *P HIDP_ PREPARSED_DATA; HMODULE h H i d L i b ;
//
/ / I d e n t y f i k a t o r b i b l i o t e k i HID j a k o zmi enna g l o b a l n a
--------------------------------------------------------------------------
t y p e d e f s t r u c t _ HI D P_C A PS { USAGE U sa g e ; USAGE U s a g e P a g e ; USHORT I n p u t R e p o r t B y t e L e n g t h ; USHORT O u t p u t R e p o r t B y t e L e n g t h ; USHORT F e a t u r e R e p o r t B y t e L e n g t h ; USHORT R e s e r v e d [ 1 7 ] ; USHORT N u m b e r L i n k C o l l e c t i o n N o d e s ; USHORT N u m b e r I n p u t B u t t o n C a p s ; USHORT N u m b e r I n p u t V a l u e C a p s ; USHORT N u m b e r I n p u t D a t a I n d i c e s ; USHORT N u m b e r O u t p u t B u t t o n C a p s ; USHORT N u m b e r O u t p u t V a l u e C a p s ; USHORT N u m b e r O u t p u t D a t a I n d i c e s ; USHORT N u m b e r F e a t u r e B u t t o n C a p s ; USHORT N u m b e r F e a t u r e V a l u e C a p s ; USHORT N u m b e r F e a t u r e D a t a I n d i c e s ; } HID P_ CA PS ,
//
* PH ID P_ CA PS;
--------------------------------------------------------------------------
# e n d if
//
--------------------------------------------------------------------------
Listing 8.2. Kod modułu usb_R8_1.cpp, implementującegofunkcje zadeklarowane w klasie TUSBDevice # i n c l u d e #pragma o p t i o n
p u s h -a 1
# in c lu d e < s e tu p a p i> #pragma o p t i o n
pop
# in c lu d e # in c lu d e # in c lu d e < c str in g > # in clu d e
"usb_R8_1.h"
u s i n g n a m es p ac e s t d ;
/ / -------------------------------------------------------------------T U S B D e v ice ::T U S B D e v ice () { m em be rI nd e x = 0; d e v i c e I n t e r f a c e D e t a i l D a t a = NULL; h H i d L i b = NULL; h H id L ib = L o a d L ib ra ry ("C :\\W in d o w s \\S yS W O W 6 4 \\H ID .D L L "); if
(!h H id L ib )
Rozdział 8. ♦ Programowanie obiektowe transmisji USB
d is p la y E rro r("B łą d
d o łą c ze n ia b i b lio t e k i
311
H ID .D LL.");
(FARPROC&)
H id D _ G e tH id G u id = G e tP r o c A d d re s s ( h H id L ib ,
(FARPROC&)
H id D _ G e tA ttrib u te s = G e tP ro c A d d re s s (h H id L ib ,
if
(!H id D _G e tH id G u id
||
"HidD_G etH idG ui d " ) ; "H id D _ G e tA ttrib u te s");
!H id D _ G e tA ttrib u te s ){
F re e L ib ra ry ( h H id L ib ) ; d is p la y E rro r("N ie
z n a l e z i o n o żadnych f u n k c j i
e k sp orto w ych.\n");
} }
//
--------------------------------------------------------------------------
TU SBD evice ::~ T U SB D evice() { if(h H id L ib
!= NULL)
F re e L ib ra ry ( h H id L ib ) ; }
//
--------------------------------------------------------------------------
v o id T U S B D e v ic e ::d is p la y E rro r( c o n s t
c h a r * msg){
c o u t << msg << e n d l ; system ("PAUSE"); e x it(0 ); };
//
--------------------------------------------------------------------------
v o id T U S B D e v ic e ::s e tU s h o rtV id (U S H O R T v id ) { u sV id = v id ; }
//
--------------------------------------------------------------------------
s trin g
TU S BD ev i c e : : g e t U S B D e v i c e P a t h ( U I N T )
{ H id D _ G e tH id G u id (& c la s s G u id ); d e v ic e I n fo S e t = S e tu p D iG e tC la s s D e v s (& c la s s G u id , DIGCF_PRESENT if
NULL,
NULL,
| D I G C F _ I N T E R F A C E D E V I C E );
( d e v i c e I n f o S e t == INVALI D_ HAN DLE _VA LU E){ F re e L ib ra ry ( h H id L ib ) ; d is p la y E rro r("N ie
z id e n ty fik o w a n o podłączonych
urząd zeń.\n");
} d e v ic e In te rfa c e D a ta .c b S iz e
= sizeo f(SP_D EV IC E _IN TE R F A C E_D A TA );
SetupD i E n u m D e v ic e I n te r f a c e s ( d e v ic e I n fo S e t,
NU LL,
memberIndex, S e tu p D iG e tD e v ic e In te rfa c e D e ta il(d e v ic e In fo S e t, NULL ,
0,
& c la s s G u id ,
& d e v ice In te rfa c e D a ta ); &devi c e I n t e r f a c e D a t a ,
& d e v ic e In te rfa c e D e ta ilD a ta S iz e ,
NULL);
d e v i c e I n t e r f a c e D e t a i l D a t a = (PS P_ DE V IC E_ IN T ER F A CE _ DE T A IL _D A TA ) new D W O R D [ d e v i c e I n t e r f a c e D e t a i l D a t a S i z e ] ; d e v ice In te rfa ce D e ta ilD a ta -> cb S ize = size o f(S P _ D E V IC E _ IN T E R F A C E _ D E T A IL_ D A T A ); if
(S e tu p D iG e tD e v ic e In te rfa c e D e ta il(d e v ic e In fo S e t, d e v ic e In te rfa c e D e ta ilD a ta , NULL,
NU LL ) )
& d e v ice In te rface D a ta ,
devi c e In te r fa c e D e ta ilD a t a S i ze,
{
path U S B D evic e = d e v i c e I n t e r f a c e D e t a il D a t a - > D e v i c e P a t h ;
/ / c o u t << pathUSBDevi ce << e n d l ; } re le a s e M e m o ry (d e v ic e In te rfa c e D e ta ilD a ta ); S e tu p D iD e s tro y D e v ic e In fo L is t(d e v ic e In fo S e t); return
}
p ath U S B D evic e;
312
USB. Praktyczne programowanie z Windows API w C++
//
--------------------------------------------------------------------------
HANDLE TU S BD ev i c e : : s y n c h O p e n U S B D e v i c e ( ) { s trin g
devUSBpath;
HANDLE
h i d D e v O b j e c t = IN VALID_HANDLE_VALUE;
w h ile ((d e v U S B p a th
= getUSBDevicePath(m em berIndex+ +))
h id D ev O b jec t = C r e a t e F ile ( d e v U S B p a t h . c _ s t r ( ) , GENERIC_WRITE,
FILE_SHA RE _RE AD
NU LL, OP EN _ EX I S TI N G , H id D _ G e tA ttrib u te s (h id D e v O b je c t,
!= " "
){
GENERIC_READ | \ | FILE_ SH ARE _WR IT E,
F IL E_ FL A G_ O VE RL AP PE D ,
NULL ) ;
& h id d A ttrib u te s );
p r in t f ( " V I D / P I D / w e r s j a = % d /% d / % d \n ",h id d A ttrib u te s .V e n d o rID , h id d A ttrib u te s .P ro d u c tID , h id d A ttrib u te s .V e rs io n N u m b e r); if(h id d A ttrib u te s .V e n d o rI D return
== u s V i d ) {
h id D e v O b je c t;
break; } e lse if(m em b erIn dex > searchM axD evice) d is p la y E rr o r(" N ie } return }
//
zn a le zio n o
u r z ą d z e n i a o podanym V I D . " ) ;
IN VAL ID_ HANDLE_ VALUE;
--------------------------------------------------------------------------
bool
TU S B D evice ::syn ch R e adU SB R eport(H A N D LE h id D e v O b je c t , v o id * in p u tR e p o r tB u ffe r, ULONG i n p u t R e p o r t B u f f e r L e n g t h )
{ r e s u lt
= 0;
n u m b e r O f B y t e s R e a d = 0; OVERLAPPED » o v e r l a p p e d = NULL; i f ( o v e r l a p p e d == NULL) { o v e r l a p p e d = new OVERLAPPED; overlappe d-> hEvent = CreateEvent(N U LL,
TRUE,
TRUE,
"");
o v e r l a p p e d - > O f f s e t = 0; o v e r l a p p e d - > O f f s e t H i g h = 0; } if(!R e a d F ile (h id D e v O b je c t,
in p u tR e p o rtB u ffe r,
&num berO fBytesRead, if(G e tL a s tE rro r() r e s u lt
ove rla p p e d ))
== ERROR_IO_PENDING)
in p u tR e p o rtB u ffe rLe n g th , {
{
= W a itF o rS in g le O b je c t(o ve rla p p e d -> h E ve n t,
if( r e s u lt
== WAIT_TIMEOUT)
INFINITE);
{
C a n c e lIo (h id D e v O b je c t); return
fa lse ;
} e lse if( r e s u lt
== W A I T _ F A I L E D ) {
c o u t << " B ł ą d o d c z y t u d a n y c h . " ; return
fa lse ;
} G e tO v e rla p p e d R e s u lt(h id D e v O b je c t,
ove rla p p e d ,
&num berO fBytesRead, } e lse d is p la y E rro r("B łą d
}
odczytu d a n y ch .");
FALSE);
Rozdział 8. ♦ Programowanie obiektowe transmisji USB
313
R e se tE v e n t(o v e rla p p e d -> h E v e n t); d e le te overlappe d; return }
//
true;
--------------------------------------------------------------------------
i n t m ain () { HIDP_CAPS c a p a b i l i t i e s ; PHIDP_PREPARSED_DATA p r e p a r s e d D a t a ; HMODULE h L i b = NULL; HANDLE
h id D e v ic e O b je c t;
BYTE * i n p u t R e p o r t B u f f e r ; lo ng
(
s td c a ll*
H id P _G etC a p s)(IN
PHIDP_PREPARSED_DATA P r e p a r s e d D a t a ,
OUT PHIDP_CAPS C a p a b i l i t i e s ) ; bool
(
s td c a ll*
H i d D _ G e t P r e p a r s e d D a t a ) ( I N HANDLE
H id D e v ic e O b je c t,
OUT PHIDP_PREPARSED_DATA * P r e p a r s e d D a t a ) ; bool
(
s td c a ll*
H id D _ FreeP rep arsed D ata)(IN
PHIDP_PREPARSED_DATA P r e p a r s e d D a t a ) ;
T U S B D e v i c e * u s b D e v i c e = new T U S B D e v i c e ( ) ; (FARPROC&)
H id P _ G e tC a p s = G e tP ro cA d d re s s (h H id L ib ,
(FARPROC&)
H id D _ G e tP re p a rs e d D a ta = G e tP ro cA d d re s s(h H id L ib ,
"H id P _ G e tC a p s");
(FARPROC&)
H id D _ F re e P re p a rs e d D a ta = G e tP ro cA d d re s s(h H id L ib ,
"H id D _ G e tP re p a rse d D a ta "); "H id D _ Fre e P re p a rse d D a ta "); if
(!H id P _G e tC a p s
||
!H id D _ G e tP re p a rse dD a ta
||
!H id D _ F re e P re p a rse d D a ta ){
F re e L ib ra ry (h L ib ); u s b D e v ic e - > d is p la y E r r o r ( " N ie z n a le z io n o żadnych
fu n k c ji"
\
" e ksp orto w ych .\n "); }
//Synchroni czne operacj e odczytu usbDevi c e -> s e t U s h o r t V id ( 8 8 9 0 ) ; h id D e v ic e O b je c t = u sbD evice-> synchO penUSB D evice(); if(H id D _ G e tP re p a rs e d D a ta (h i d D e v ice O b je ct, H id P_G etC aps(prep arsedD ata,
& preparsedD ata)){
& c a p a b ili t i e s ) ;
i n p u t R e p o r t B u f f e r = new B Y T E [ c a p a b i l i t i e s . I n p u t R e p o r t B y t e L e n g t h ] ; w h ile ( tru e )
{
u sb D e v ic e -> s yn c h R e a d U S B R e p o rt(h id D e v i c e O b je c t ,
in p u tR e p o rtB u ffe r ,
c a p a b ilitie s .In p u tR e p o rtB y te L e n g th ); p r i n t f ( " % d %d %d %d %d %d % d \n ",
in p u tR e p o rtB u ffe r [ 0 ] ,
in p u tR e p o rtB u ffe r[1 ],
in p u tR e p o rtB u ffe r [ 2 ] ,
in p u tR e p o rtB u ffe r[3 ],
in p u tR e p o rtB u ffe r [ 4 ] ,
in p u tR e p o rtB u ffe r[5 ],
in p u tR e p o rtB u ffe r [ 6 ] ) ;
if ( in p u tR e p o r tB u f fe r[ 6 ] = = 6 4
||
\
h i d D e v i c e O b j e c t == INVALI D_ HAN DLE _VA LU E){ H id D _ F re e P re p a rs e d D a ta (p re p a rse d D a ta ); re le a se M e m o ry(in p u tR e p o rtB u ffe r); break; } } / /k o ni ec while } C lo s e H a n d le (h id D e v ic e O b je c t); F re e L ib ra ry (h L ib ); d e le te
u sb D evice;
31 4
USB. Praktyczne programowanie z Windows API w C++
system ("PAUSE"); return }
//
0;
--------------------
Rysunek 8.2. Aplikacja proj_USB_R8_1 w trakcie działania
USB.Praktyczne programowanie\Rozdział 8 V ID / P ID / w e rsja = 8890/264/7 S7 0 129 127 128 128 0 0 0 128 127 128 128 0 0 0 128 127 128 128 16 0 0 128 127 128 128 80 0 0 128 127 128 128 64 0 0 128 127 128 128 0 0 0 128 127 128 128 64 0 0 128 127 128 128 0 0 0 128 127 128 128 0 32 0 128 127 128 128 0 40 0 128 127 128 128 0 3 0 128 127 128 128 0 0 0 128 127 128 128 0 2 0 128 127 128 128 0 0 0 129 127 128 128 0 0 0 128 127 128 128 0 0 0 128 127 128 128 0 64 Aby ko ntynu ow ać, n a c i ś n i j dow olny k la w is z .
.
!_l\proj_USB_R8_l.exe
I ° I @li
.
Wzorce projektowe Projektując systemy obiektowo, należy je podzielić na odpowiednie klasy i zdefinio wać interfejsy oraz główne związki między klasami. Proces projektowania systemu inform atycznego można znacznie przyspieszyć, korzystając z gotowych rozwiązań w drożonych przy okazji rozw iązyw ania innych problemów. W zorce projektowe [9] stanow ią zbiór ogólnych rozw iązań często spotykanych problem ów projektow ych i programistycznych. Sprawiają, że projekty stają się bardziej elastyczne; można je też łatwiej ponownie wykorzystać. Projektując w ten sposób, po pewnym czasie dojdzie my do wniosku, że dysponujemy biblioteką gotowych rozwiązań najczęściej pojawia jących się problemów. Co więcej, rozwiązania te były ju ż modyfikowane kilkakrotnie podczas dostosow yw ania ich do w cześniejszych projektów, mamy więc pewność, że są one wystarczająco dobrze sprawdzone.
Singleton Programowanie zorientowane obiektowo pozwala na tworzenie teoretycznie nieskoń czenie w ielu egzemplarzy tego samego obiektu. Może się jednak zdarzyć sytuacja, w której zażądamy, aby każdy nowy egzemplarz obiektu odwoływał się do tych sa m ych danych. Bardzo często dzieje się tak przy tworzeniu połączeń z portami komu nikacyjnymi. Tworzenie w ielu połączeń nie ułatw ia programowej obsługi transmisji danych — tak czy inaczej każde nowe połączenie do wybranego portu będzie musiało czekać w kolejce, aż poprzednie połączenie zamknie port do transmisji. Singleton po zwala na odwoływanie się do tych samych właściwości (w naszym przypadku iden tyfikatora portu) dla każdego nowego obiektu danej klasy, który zostanie stworzony. Zapewnia utworzenie i dostarczenie wyłącznie jednego egzemplarza obiektu danego typu. O biekt ten pow inien być globalnie dostępny i nie pow inien być kontrolowany
Rozdział 8. ♦ Programowanie obiektowe transmisji USB
315
przez inne obiekty. Klient tworzy egzemplarz singletonu wyłącznie za pom ocą meto dy g etIn sta n c e (). N a rysunku 8.3 pokazano uproszczoną adaptację wzorca singletonu na potrzeby klasy TUSBDevice. Rysunek 8.3. Klasa TUSBDevice jako singleton
T U S B D e v ice -pathUSBDevice: string -memberlndex: DWORD -devic eInt erfac eDet ai IDat a: PSP_DE VI CE_I NTE RFACE_DETAI L_DATA -numberOfBytesRead: DWORD -result: DWORD -classGuid: GUID -hHidLib: HMODULE -devic eInte rfac eDetai IDataSize : DWORD -devicelnfoSet: HDEVINFO -deviceInterfaceData: SP_DEVICE_INTERFACE_DATA -usVid: USHORT -chVid: char <
W klasie TUSBDevice jest zadeklarowany statyczny atrybut instance o dostępie prywat nym, który będzie stanowić odwołanie do egzemplarza klasy. Początkowo odwołanie to powinno być zainicjowane pustym wskaźnikiem (NULL). Następnie należy zadeklaro wać statyczną metodę getInstance() o dostępie publicznym, która tworzy egzemplarz klasy, w przypadku gdy odwołanie instance = NULL, a w dalszej kolejności odpowied nio inicjuje in sta n c e i zw raca jego wartość. Konstruktor klasy je st zadeklarowany w sekcji prywatnej, aby zapobiec możliwości wywołania go bezpośrednio przez klienta (z poziomu programu głównego). N a listingach 8.3 i 8.4 pokazano odpowiednio definicję klasy TUSBDevice oraz przykła dową, uproszczoną im plementację zdefiniow anych w niej funkcji składowych. Iden tyfikacja urządzenia opiera się na odczytaniu jego identyfikatora VID (w formie łań cuchowej). Ponadto elementy składowe klasy TUSBDevice realizują transmisję danych w formie asynchronicznej. N a rysunku 8.4 zaprezentowano wynik działania programu. Listing 8.3. Kod pliku nagłówkowego usb_R8_2.h zawierającego definicje odpowiadające rysunkowi 8.3 # i f n d e f usb _R8_2H # d e f i n e usb _R8_2H # in c lu d e < c str in g > # in c lu d e u s i n g n a m es p a ce s t d ; # d e fin e b u ff e rL e n g th
32
# d e fin e search M axD evice
//
10
--------------------------------------------------------------------------
t e m p l a t e < c l a s s T> in lin e
v o i d r e l e a s e M e m o r y ( T &x)
{ assert(x
!= N U L L ) ;
316
USB. Praktyczne programowanie z Windows API w C++
d e le te
[]
x;
x = NULL; }
//
--------------------------------------------------------------------------
v o id
//
(
s td c a ll
*H idD _G etH idG uid)(O U T
LPGUID H i d G u i d ) ;
--------------------------------------------------------------------------
cla ss
TUSBD evice {
p riv a te : s ta tic
TUSBD evice * i n s t a n c e ;
T U S B D e v ice (); s trin g
pathU SB D evice;
DWORD m em b e rI n d e x; PS P_ DE V IC E_ IN TE RF AC E_ DE TA IL _ DA TA d e v i c e I n t e r f a c e D e t a i l D a t a ; DWORD n u m b e r O f B y t e s R e a d ; DWORD r e s u l t ; GUID c l a s s G u i d ; HMODULE h H i d L i b ; DWORD d e v i c e I n t e r f a c e D e t a i l D a t a S i z e ; HDEVINFO d e v i c e I n f o S e t ; S P_ DE V IC E_I NT ER FA CE _D A TA d e v i c e I n t e r f a c e D a t a ; USHORT u s V i d ; co nst ch a r * chVid; p u b lic : s ta tic
TUSBD evice * g e t I n s t a n c e ( ) ;
~ T U S B D e vic e (); v o id s e tC h a rV id (c o n s t
char * vid );
v o id d is p la y E r r o r ( c o n s t bool
c h a r * m sg);
asynchReadU SBReport(H AND LE h id D e v O b j e c t ,
v o id * in p u tR e p o r tB u ffe r,
ULONG i n p u t R e p o r t B u f f e r L e n g t h ) ; HANDLE a s y n c h O p e n U S B D e v i c e ( ) ; s trin g
getU S B D evicePa th (U IN T );
};
//
--------------------------------------------------------------------------
# e n d if
//
--------------------------------------------------------------------------
Listing 8.4. Kod modułu usb_R8_2.cpp z implementacjąfunkcji składowych klasy # i n c l u d e #pragma o p t i o n
p u s h -a 1
# in c lu d e < s e tu p a p i> #pragma o p t i o n
pop
# in c lu d e # in clu d e
"usb_R8_2.h"
u s i n g n a m es p ac e s t d ;
//
-------------------------------------------
T U S B D e v ic e * T U S B D e v i c e : : g e t I n s t a n c e ( ) { if
( T U S B D e v i c e : : i n s t a n c e == NULL)
{
i n s t a n c e = new T U S B D e v i c e ( ) ; } return }
//
in s ta n c e ;
-------------------------------------------
T U S B D e v ice ::T U S B D e v ice () { m em be rI nd e x = 0;
Rozdział 8. ♦ Programowanie obiektowe transmisji USB
317
h H i d L i b = NULL; d e v i c e I n t e r f a c e D e t a i l D a t a = NULL; h H id L ib = L o a d L ib ra ry ("C :\\W in d o w s \\S y s te m 3 2 \\H ID .D L L "); if
(!h H id L ib ) d is p la y E rro r("B łą d
(FARPROC&) if
d o łą c ze n ia b i b lio t e k i
H ID .D LL.");
H id D _ G e tH id G u id = G e tP r o c A d d re s s ( h H id L ib ,
(!H id D _G e tH id G u id / * | |
"H id D _ G e tH id G u id ");
!H id D _ G e tA ttrib u te s * / ) {
F re e L ib ra ry ( h H id L ib ) ; d is p la y E rro r("N ie
z n a l e z i o n o żadnych f u n k c j i
e k sp orto w ych.\n");
} }
/ / --------------------------------------------------------------------------TU SBD evice ::~ T U SB D evice() { if(h H id L ib
!= NULL)
F re e L ib ra ry ( h H id L ib ) ; d e le te
T U S B D e v ic e ::in sta n c e ;
}
/ / ----------------------------------------------------------------------------------------------------------------v o id T U S B D e v ic e ::d is p la y E rro r( c o n s t
c h a r * msg){
c o u t << msg << e n d l ; system ("PAUSE"); e x it(0 ); };
//
--------------------------------------------------------------------------
v o id T U S B D e v ic e ::s e tC h a rV id (c o n s t char* v id ) { ch V id = v id ; }
/ / ----------------------------------------------------------------------------------------------------------------s trin g
TU S BD ev i c e : : g e t U S B D e v i c e P a t h ( U I N T )
{ H id D _ G e tH id G u id (& c la s s G u id ); d e v ic e I n fo S e t = S e tu p D iG e tC la s s D e v s (& c la s s G u id , DIGCF_PRESENT if
NULL,
NULL,
| D IG C F _INTERFACEDEVICE);
( d e v i c e I n f o S e t == INVALI D_ HAN DLE _VA LU E){ F re e L ib ra ry ( h H id L ib ) ; d is p la y E rro r("N ie
z id e n ty fik o w a n o podłączonych
urząd zeń.\n");
} d e v ic e In te rfa c e D a ta .c b S iz e
= size o f(SP_D EVICE _IN TE RFACE_D ATA);
SetupD i E n u m D e v ic e I n te r f a c e s ( d e v ic e I n fo S e t, memberIndex,
NU LL,
S e tu p D iG e tD e v ic e In te rfa c e D e ta il(d e v ic e In fo S e t, NULL ,
0,
& c la s s G u id ,
& d e v ice In te rfa c e D a ta ); &devi c e I n t e r f a c e D a t a ,
& d e v ic e In te rfa c e D e ta ilD a ta S iz e ,
NULL);
d e v i c e I n t e r f a c e D e t a i l D a t a = (PS P_ DE V IC E_ IN T ER F A CE _ DE T A IL _D A TA ) new D W O R D [ d e v i c e I n t e r f a c e D e t a i l D a t a S i z e ] ; d e v ice In te rfa ce D e ta ilD a ta -> cb S ize = size o f(S P _ D E V IC E _ IN T E R F A C E _ D E T A IL_ D A T A ); if
(S e tu p D iG e tD e v ic e In te rfa c e D e ta il(d e v ic e In fo S e t, d e v ic e In te rfa c e D e ta ilD a ta , NULL,
NU LL ) )
& d e v ice In te rface D a ta ,
d e v ic e In te rfa c e D e ta ilD a ta S iz e ,
{
path U S B D evic e = d e v i c e I n t e r f a c e D e t a il D a t a - > D e v i c e P a t h ;
/ / c o u t << pathUSBDevi ce << e n d l ;
}
318
USB. Praktyczne programowanie z Windows API w C++
re le a s e M e m o ry (d e v ic e In te rfa c e D e ta ilD a ta ); S e tu p D iD e s tro y D e v ic e In fo L is t(d e v ic e In fo S e t); return
p ath U S B D evic e;
}
/ / ----------------------------------------------------------------------------------------------------------------HANDLE TU S BD ev i c e : : a s y n c h O p e n U S B D e v i c e ( ) { s trin g
devUSBpath;
HANDLE
h i d D e v O b j e c t = IN VALID_HANDLE_VALUE;
w h ile ((d e v U S B p a th if
= getUSBDevicePath(m em berIndex+ +))
(NULL != s t r s t r ( d e v U S B p a t h . c _ s t r ( ) , c o u t << e n d l
){
<< d e v U S B p a t h < < e n d l;
h id D ev O b jec t = C r e a t e F ile ( d e v U S B p a t h . c _ s t r ( ) , GENERIC_WRITE, if(h id D e v O b je ct
GENERIC_READ | \
FILE_SHA RE _RE AD
NU LL, OP EN _ EX I S TI N G , return
!= " "
c h V id )){
0,
| FIL E_ SH ARE _W RIT E,
NULL ) ;
!= INVALID_HANDLE_VALUE)
h id D e v O b je c t;
break; } e lse if
(m em be rI n de x > s e a r c h M a x D e v i c e ) d is p la y E rr o r(" N ie
} return }
//
zn a le zio n o
u r z ą d z e n i a o podanym V I D . \ n " ) ;
IN VALID_HANDLE_VALUE;
--------------------------------------------------------------------------
bool
TU S B D evice ::asyn ch R e ad U SB R ep ort(H A N D LE h id D e v O b je c t ,
v o id * in p u tR e p o r tB u ffe r,
ULONG i n p u t R e p o r t B u f f e r L e n g t h ) { n u m b e r O f B y t e s R e a d = 0; return
R e a d F ile (h id D e v O b je c t,
in p u tR e p o rtB u ffe r ,
in p u t R e p o r t B u ffe r L e n g t h , }
//
& num berO fBytesRead,
NULL);
--------------------------------------------------------------------------
T U S B D e v i c e * T U S B D e v i c e : : i n s t a n c e = NULL; / / ----------------------------------------------------------------------------------------------------------------i n t m ain () { HANDLE
h id D e v ic e O b je c t;
BYTE i n p u t R e p o r t B u f f e r [ b u f f e r L e n g t h ] ; TUSBD evice * u s b D e v ic e = T U S B D e v ic e : : g e t I n s t a n c e ( ) ;
/ / Asynchroniczne operacj e odczytu usbDevi c e - > s e t C h a r V i d ( " 2 2 b a " ) ; h id D e v ic e O b je c t = u sbD evice-> asynchO penUSB D evice(); w h ile (tru e )
{
u sb D e v ice -> a s yn c h R e a d U S B R e p o rt(h id D e v ice O b je c t,
in p u tR e p o rtB u ffe r,
s iz e o f(in p u tR e p o rtB u ffe r)); p r i n t f ( " % d %d %d %d %d %d % d \n ",
in p u tR e p o rtB u ffe r [0 ],
in p u tR e p o rtB u ffe r[1 ], in p u tR e p o rtB u ffe r[2 ], in p u tR e p o rtB u ffe r[3 ], in p u tR e p o rtB u ffe r[4 ], in p u tR e p o r tB u ffe r [ 5 ] , in p u tR e p o rtB u ffe r[6 ]); if(in p u tR e p o r tB u ffe r[6 ]= = 6 4
||
\
h i d D e v i c e O b j e c t == INVALI D_ HA NDL E_ VAL UE) { break; }
} / / k o n i e c whi le C lo s e H a n d le (h id D e v ic e O b je c t);
Rozdział 8. ♦ Programowanie obiektowe transmisji USB
319
system ("PAUSE"); return
0;
}
/ / ------------------------------
Rysunek 8.4. Aplikacja proj_USB_R8_2 w trakcie działania
USB.Praktyczne programowanie\Rozdział 8\R8_2\proj_USB_R8_2.exe
1
° |H l - S - l
V \ ? \ h i d#vi d_22ba&pi _0108#8A5 9 f 5 d85 &M0000#{4 d le 5 5 b2 - f 1 6 f - l l c f - 8 8 c b - 001111000030} 0 129 127 128 128 0 0 0 128 127 128 128 0 0 0 129 127 128 128 0 0 0 128 127 128 128 0 0 0 128 127 128 128 16 0 0 128 127 128 128 0 0 0 128 127 128 128 6 * 0 0 128 127 128 128 0 0 0 128 127 128 128 0 8 0 128 127 128 128 0 40 0 128 127 128 128 0 32 0 128 127 128 128 0 0 0 129 127 128 128 0 0 0 128 127 128 128 0 0 0 129 127 128 128 0 0 0 128 127 128 128 0 0 0 128 127 128 128 1 0 0 128 127 128 128 2 0 0 128 127 128 128 0 0 0 128 127 128 128 8 0 0 128 127 128 128 7 0 0 128 127 128 128 6 0 0 128 127 128 128 5 0 0 128 127 128 128 0 0 0 128 127 128 128 0 64 4by ko ntynu ow ać, n a c i ś n i j dow oln y k la w is z . . .
H
Testując powyższy program, łatwo zauważymy, że singleton je st obiektem bezstanowym, tzn. sposób działania metody statycznej getInstance() nie zależy od stanu, w ja kim znajduje się program: klient otrzymuje egzemplarz klasy na żądanie niezależnie od tego, czy został on utworzony wcześniej, czy nie. Singleton pozwala również stoso wać dziedziczenie w celu zmiany tworzonej przez siebie klasy i zwracać egzemplarze podklas. Dołączenie podklasy do wzorca nie wymaga modyfikacji po stronie klienta.
Interfejsy W dotychczas omawianych przykładach klient bezpośrednio tworzył obiekt interesującej go klasy. Elementy składowe klasy TUSBDevice zawierały przykładowe implementacje dziedziny problemu. Prezentacja danych w ejściow ych była dokonywana w głównej funkcji programu. Jednak takie podejście nie zawsze byw a optymalne. Często funk cjonalność kodu wymaga, aby warstwy implementacji i prezentacji były oddzielone w sposób uniemożliwiający bezpośrednie stworzenie obiektu na bazie klasy implemen tującej zachowanie się testowanego urządzenia. Aby zrealizować tego rodzaju wym a gania, warstwy implementacji i prezentacji odseparowuje się za pomocą warstwy abs trakcji w postaci odpowiednio zaprojektowanych i skonstruowanych interfejsów. Interfejs jest typem składającym się wyłącznie z funkcji czysto wirtualnych. W odróż nieniu od innych języków programowania, interfejsy w C++ są emulowane za pomocą klas abstrakcyjnych pozbawionych pól (atrybutów) [7]. W C++ podczas pisania inter fejsu można użyć słowa in te rfa c e, należy jednak pamiętać, że jest to jedynie makrodefinicja (wprowadzona w celu zachowania zgodności z językiem IDL), której użycie wymaga włączenia pliku nagłówkowego objbase.h.
32c
USB. Praktyczne programowanie z Windows API w C++
N a rysunku 8.5 pokazano uogólniony m odel konstruowania interfejsów. Listing 8.5 zawiera implementację diagramu 8.5.
Rysunek 8.5. Model realizacji interfejsów przez klasę TUSBDevice Podstawowy związek między klasą a interfejsem nazywamy realizacją (chociaż klasy i interfejsy m ogą pozostawać również w innych relacjach). Klasa realizuje każdą ope rację interfejsu poprzez implementację operacji o tej samej nazwie oraz tych samych argumentach i tym samym sposobie wywoływania. C++ automatycznie dopasowuje operacje klasy do operacji danego interfejsu. Uzyskanie dostępu do elementów skła dowych obiektu stworzonego na bazie danej klasy drogą inną niż poprzez funkcje in terfejsu nie je s t możliwe. N a rysunku 8.5 pokazano diagram odpowiadający listingowi 8.5, w którym klasa TUSBDevice realizuje dwa przykładowe interfejsy: ISynchronous i IAsynchronous. Są one odpowiedzialne za transmisję danych przez obiekt klasy TUSBDevice odpowiednio w wariantach synchronicznym lub asynchronicznym. N a listingu 8.6 i rysunku 8.6 zaprezentowano kod głównego m odułu i wynik działania om awia nej aplikacji. Listing 8.5. Kod pliku nagłówkowego usb_R8_3.h zawierającego definicje odpowiadające rysunkowi 8.5 # i f n d e f usb _R8_3H # d e f i n e usb _R8_3H # in c lu d e # in c lu d e < c str in g > # in c lu d e u s i n g n a m es p a ce s t d ; # d e fin e b u ff e rL e n g th
32
Rozdział 8. ♦ Programowanie obiektowe transmisji USB
# d e fin e search M axD evice
321
10
/ / ---------------------------------------------------------------------------------t e m p l a t e < c l a s s T> in lin e
v o i d r e l e a s e M e m o r y ( T &x)
{ assert(x de le te
!= N U L L ) ;
[]
x;
x = NULL; }
//
--------------------------------------------------------------------------
t y p e d e f s t r u c t _HIDD_ ATT RIB UTE S { ULONG S i z e ; USHORT V e n d o r I D ; USHORT P r o d u c t I D ; USHORT V e r s i o n N u m b e r ; } HIDD _AT TR IBU TES , *PHIDD_ ATT RIB UT ES; / / ----------------------------------------------------------------------------------------------------------------v o id
(
s td c a ll
*H idD _G etH idG uid)(O U T
bool
(
s td c a ll
* H id D _ G e tA ttrib u te s )(IN
LPGUID H i d G u i d ) ; HANDLE H i d D e v i c e O b j e c t ,
OUT PHIDD_ATTRIBUTES A t t r i b u t e s ) ; / / ----------------------------------------------------------------------------------------------------------------in te rfa ce
ISynchronous
{
p u b lic : v irt u a l
bool
s t d c a l l synchReadUSBReport(HANDLE h id D e v O b je c t ,
v o id * in p u tR e p o r tB u ffe r, v irt u a l v irt u a l v irt u a l
HANDLE
s td c a ll
v o id
ULONG i n p u t R e p o r t B u f f e r L e n g t h )
synchOpenUSBDevice()
s t d c a l l setU shortVid(U SH O RT v id )
~ ISynchronous()
{ };
= 0;
= 0; = 0;
//w irtu a ln y destruktor
};
/ / ----------------------------------------------------------------------------------------------------------------in te rfa ce
IAsynchronous
{
p u b lic : v irt u a l
bool
s t d c a l l asynchReadU SBReport(H AN D LE h id D e v O b j e c t ,
v o id * in p u tR e p o r tB u ffe r, v irt u a l v irt u a l v irt u a l
HANDLE
s td c a ll
v o id
ULONG i n p u t R e p o r t B u f f e r L e n g t h ) =
asynchOp enU SBD evice()
s t d c a ll s e tC h a rV id (c o n s t
~ IAsynchronous()
{ };
= 0;
char* v id )
= 0;
//w irtu a ln y destruktor
};
/ / ----------------------------------------------------------------------------------------------------------------cla ss
TUSBD evice:
p u b lic
ISynchronous,
p u b lic
IAsynchro nous
{
p riv a te : s trin g
pathU SB D evice;
DWORD m em b e rI n d e x; HIDD_ATTRIBUTES h i d d A t t r i b u t e s ; PS P_ DE V IC E_ IN TE RF AC E_ DE TA IL _ DA TA d e v i c e I n t e r f a c e D e t a i l D a t a ; DWORD n u m b e r O f B y t e s R e a d ; DWORD r e s u l t ; GUID c l a s s G u i d ; HMODULE h H i d L i b ; DWORD d e v i c e I n t e r f a c e D e t a i l D a t a S i z e ; HDEVINFO d e v i c e I n f o S e t ; S P_ DE V IC E_I NT ER FA CE _D A TA d e v i c e I n t e r f a c e D a t a ; USHORT u s V i d ; co nst ch a r * chVid; p u b lic : T U S B D e v ice (); ~ T U S B D e vic e (); v o id
s td c a ll
setU shortVid(U SH O RT v id ) ;
0;
322
USB. Praktyczne programowanie z Windows API w C++
v o id
s t d c a ll se tC h a rV id (c o n st
v o id
s td c a ll d is p la y E rr o r(c o n s t
char * vid );
bool
s t d c a l l synchReadUSBReport(HANDLE h id D e v O b je c t ,
bool
s t d c a l l asynchReadU SBReport(H AN D LE h id D e v O b j e c t ,
c h a r * m sg ) ;
v o id * in p u tR e p o r tB u ffe r, v o id * in p u tR e p o r tB u ffe r,
ULONG i n p u t R e p o r t B u f f e r L e n g t h ) ; ULONG i n p u t R e p o r t B u f f e r L e n g t h ) ;
HANDLE
s td c a ll
synchOpenU SBDevice();
HANDLE
s td c a ll
asynchOpenU SBDevice();
s trin g
s td c a ll
getU SB D evicePath(U IN T);
};
/ / ----------------------------------------------------------------------------------------------------------------# e n d if / / -----------------------------------------------------------------------------------------------------------------
Listing 8.6. Kod modułu usb_R8_3.cpp # i n c l u d e #pragma o p t i o n
p u s h -a 1
# in c lu d e < s e tu p a p i> #pragma o p t i o n
pop
# in c lu d e # in clu d e
"usb_R8_3.h"
u s i n g n a m es p ac e s t d ;
/ / ---------------------------------------------------------------------------------T U S B D evice ::T U S B D evi ce () { m e m b e r l n d e x = 0; h H i d L i b = NULL; d e v i c e l n t e r f a c e D e t a i l D a t a = NULL; h H id L ib = L o a d L ib ra ry ("C :\\W in d o w s \\S y s te m 3 2 \\H ID .D L L "); if
(I h H id L ib ) d is p la y E rro r("B łą d
d o łą c ze n ia b i b lio t e k i
H ID .D LL.");
(FARPROC&)
H id D _ G e tH id G u id = G e tP r o c A d d re s s ( h H id L ib ,
(FARPROC&)
H id D _ G e tA ttrib u te s = G e tP ro c A d d re s s (h H id L ib ,
if
(!H id D _G e tH id G u id
||
"H id D _ G e tH id G u id "); "H id D _ G e tA ttrib u te s");
!H id D _ G e tA ttrib u te s ){
F re e L ib ra ry ( h H id L ib ) ; d is p la y E rro r("N ie
z n a l e z i o n o żadnych f u n k c j i
e k sp orto w ych.\n");
} }
/ / ----------------------------------------------------------------------------------------------------------------TU SBD evice ::~ T U SB D evice() { if(h H id L ib
!= NULL)
F re e L ib ra ry ( h H id L ib ) ; }
/ / ----------------------------------------------------------------------------------------------------------------v o id
s td c a ll
T U S B D e v ice ::d is p la y E rro r(c o n st
c h a r * msg ){
c o u t << msg << e n d l ; system ("PAUSE"); e x it(0 ); };
/ / ----------------------------------------------------------------------------------------------------------------v o id
s td c a ll
{ u sV id = v id ;
T U S B D e v ic e ::s e tU s h o rtV id (U S H O R T v id )
Rozdział 8. ♦ Programowanie obiektowe transmisji USB
323
}
/ / ----------------------------------------------------------------------------------------------------------------v o id
s td c a ll
T U S B D e v ic e ::s e tC h a rV id (c o n s t char* v id )
{ ch V id = v id ; }
/ / ----------------------------------------------------------------------------------------------------------------s trin g
s td c a ll
T U S B D e v ic e ::g e tU S B D e v ic e P a th (U IN T )
{ H id D _ G e tH id G u id (& c l a s s G u i d ) ; d e v ic e I n fo S e t = S e tu p D iG e tC la s s D e v s (& c la s s G u id , DIGCF_PRESENT if
NULL,
NULL,
| D I G C F _ I N T E R F A C E D E V I C E );
( d e v i c e I n f o S e t == INVALI D_ HAN DLE _VA LU E){ F re e L ib ra ry ( h H id L ib ) ; d is p la y E rro r("N ie
z id e n ty fik o w a n o podłączonych
urząd zeń.\n");
} d e v ic e In te rfa c e D a ta .c b S iz e
= size o f(SP_D EVICE _IN TE RFACE_D ATA);
SetupD i E n u m D e v ic e I n te r f a c e s ( d e v ic e I n fo S e t, memberIndex,
NU LL,
S e tu p D iG e tD e v ic e In te rfa c e D e ta il(d e v ic e In fo S e t, NULL ,
0,
& c la s s G u id ,
& d e v ice In te rfa c e D a ta ); &devi c e I n t e r f a c e D a t a ,
& d e v ic e In te rfa c e D e ta ilD a ta S iz e ,
NULL);
d e v i c e I n t e r f a c e D e t a i l D a t a = (PS P_ DE V IC E_ IN T ER F A CE _ DE T A IL _D A TA ) new D W O R D [ d e v i c e I n t e r f a c e D e t a i l D a t a S i z e ] ; d e v ice In te rfa ce D e ta ilD a ta -> cb S ize = size o f(S P _ D E V IC E _ IN T E R F A C E _ D E T A IL_ D A T A ); if
(S e tu p D iG e tD e v ic e In te rfa c e D e ta il(d e v ic e In fo S e t, d e v ic e In te rfa c e D e ta ilD a ta , NULL,
NU LL ) )
& d e v ice In te rface D a ta ,
d e v ic e In te rfa c e D e ta ilD a ta S iz e ,
{
path U S B D evic e = d e v i c e I n t e r f a c e D e t a il D a t a - > D e v i c e P a t h ;
/ / c o u t << pathUSBDevi ce << e n d l ; } re le a s e M e m o ry (d e v ic e In te rfa c e D e ta ilD a ta ); S e tu p D iD e s tro y D e v ic e In fo L is t(d e v ic e In fo S e t); return
p ath U S B D evic e;
}
/ / ----------------------------------------------------------------------------------------------------------------HANDLE
s td ca ll
T U S BD evice ::asyn ch O p en U S B D evic e()
{ s trin g HANDLE
devUSBpath; h i d D e v O b j e c t = IN VALID_HANDLE_VALUE;
w h ile ((d e v U S B p a th if
= getUSBDevicePath(m em berIndex+ +))
(NULL != s t r s t r ( d e v U S B p a t h . c _ s t r ( ) , c o u t << e n d l
){
<< d e v U S B p a t h < < e n d l;
h id D ev O b jec t = C r e a t e F ile ( d e v U S B p a t h . c _ s t r ( ) , GENERIC_WRITE,
FILE_SHA RE _RE AD
NULL, O PE N _ EX I S TI N G , if(h id D e v O b je ct return
!= " "
c h V id )){
0,
GENERIC_READ
NULL ) ;
!= INVALID_HANDLE_VALUE)
h id D e v O b je c t;
break; } e lse if
(m em be rI n de x > s e a r c h M a x D e v i c e ) d is p la y E rr o r(" N ie
} return
}
zn a le zio n o
IN VALID_HANDLE_VALUE;
| \
| FILE_ SH ARE _WR ITE,
u r z ą d z e n i a o podanym V I D . \ n " ) ;
32 4
USB. Praktyczne programowanie z Windows API w C++
/ / ----------------------------------------------------------------------------------------------------------------HANDLE
s td c a ll
TU S BD e vice ::s yn ch O p e n U S B D e vic e ()
{ s trin g
devUSBpath;
HANDLE
h i d D e v O b j e c t = IN VALID_HANDLE_VALUE;
w h ile ((d e v U S B p a th
= getUSBDevicePath(m em berIndex+ +))
h id D ev O b jec t = C r e a t e F ile ( d e v U S B p a t h . c _ s t r ( ) , GENERIC_WRITE, NULL,
FILE_SHA RE _RE AD
OP EN _ EX IS TI N G,
H id D _ G e tA ttrib u te s (h id D e v O b je c t,
!= " "
){
GENERIC_READ j \
j FILE_ SH ARE _WR ITE,
F IL E_ FL A G_ O VE RL AP PE D ,
NULL ) ;
& h id d A ttrib u te s );
p r in t f ( " V I D / P I D / w e r s j a = % d /% d / % d \n ",h id d A ttrib u te s .V e n d o rID , h id d A ttrib u te s .P ro d u c tID , h id d A ttrib u te s .V e rs io n N u m b e r); if(h id d A ttrib u te s .V e n d o rI D return
== u s V i d ) {
h id D e v O b je c t;
break; } e lse if(m em b erIn dex > searchM axD evice) d is p la y E rr o r(" N ie } return
zn a le zio n o
u r z ą d z e n i a o podanym V I D . " ) ;
IN VAL ID_ HANDLE_ VALUE;
}
/ / ----------------------------------------------------------------------------------------------------------------bool
s td c a ll
T U S B D evice ::asynchR e adU SB R eport(H A N D LE h id D e v O b je c t ,
v o id * in p u tR e p o r tB u ffe r,
ULONG i n p u t R e p o r t B u f f e r L e n g t h )
{ n u m b e r O f B y t e s R e a d = 0; return
R e a d F ile (h id D e v O b je c t,
in p u tR e p o rtB u ffe r ,
in p u tR e p o rtB u ffe rL e n g th ,
& num berO fBytesRead,
NULL);
}
/ / ----------------------------------------------------------------------------------------------------------------bool
s td ca ll
TU S B D evice ::syn ch R e adU SB R eport(H A N D LE h id D e v O b je c t , v o id * in p u tR e p o r tB u ffe r,
ULONG i n p u t R e p o r t B u f f e r L e n g t h )
{ r e s u lt
= 0;
n u m b e r O f B y t e s R e a d = 0; OVERLAPPED * o v e r l a p p e d = NULL; i f ( o v e r l a p p e d == NULL) { o v e r l a p p e d = new OVERLAPPED; overlappe d-> hEvent = CreateEvent(N U LL,
TRUE,
TRUE,
"");
o v e r l a p p e d - > O f f s e t = 0; o v e r l a p p e d - > O f f s e t H i g h = 0; } if(!R e a d F ile (h id D e v O b je c t,
in p u tR e p o rtB u ffe r,
&num berO fBytesRead, if(G e tL a s tE rro r() r e s u lt
ove rla p p e d ))
== ERROR_IO_PENDING)
in p u tR e p o rtB u ffe rLe n g th , {
{
= W a itF o rS in g le O b je c t(o ve rla p p e d -> h E ve n t,
if( r e s u lt
== WAIT_TIMEOUT)
{
C a n c e lIo (h id D e v O b je c t); return
fa lse ;
} e lse if( r e s u lt
== W A I T _ F A I L E D ) {
c o u t << " B ł ą d o d c z y t u d a n y c h . " ;
INFINITE /* 1 0 0 0 * /);
Rozdział 8. ♦ Programowanie obiektowe transmisji USB
return
325
fa lse ;
} G e tO v e rla p p e d R e su lt( h id D e v O b je c t,
ove rla p p e d ,
&num berO fBytesRead,
FALSE);
} e lse d is p la y E rro r("B ią d
odczytu d a n y ch .");
} R e se tE v e n t(o v e rla p p e d -> h E v e n t); d e le te overlappe d; return
true;
}
/ / ---------------------------------------------------------------------------------i n t m ain () { HANDLE
h id D e v ic e O b je c t;
BYTE i n p u t R e p o r t B u f f e r [ b u f f e r L e n g t h ] ;
//Synchroni czne operacj e odczytu I S y n c h r o n o u s * i S y n c h U s b D e v i c e = new T U S B D e v i c e ( ) ; iS y n ch U sb D e v ice -> s e tU sh o rtV id (8 8 9 0 ); h id D e v ic e O b je c t = iS yn ch U sb D evice-> syn ch O p en U SB D e vice(); if
(h id D e v ic e O b je c t w h ile ( tru e )
!= INVALID_HANDLE_VALUE)
{
{
iS yn ch U sb D e vi c e -> s y n ch R e a d U S B R e p o rt(h id D e v i c e O b je c t ,
in p u tR e p o rtB u ffe r ,
s iz e o f(in p u tR e p o rtB u ffe r)); p r i n t f ( " % d %d %d %d %d %d % d \n ",
in p u tR e p o rtB u ffe r [ 0 ] ,
in p u tR e p o rtB u ffe r [1 ],
in p u tR e p o rtB u ffe r [2 ],
in p u tR e p o rtB u ffe r [3 ],
in p u tR e p o rtB u ffe r [4 ],
in p u tR e p o rtB u ffe r [5 ],
in p u tR e p o r tB u ffe r [ 6 ] ) ;
if ( in p u tR e p o r tB u f fe r[ 6 ] = = 6 4
||
\
h i d D e v i c e O b j e c t == IN V A L I D _ H A N D L E _ V A L U E ) { break; }
}/ /k o n ie c while } /*
/ / A s y n c h r o n i c z n e o p e r a c j e od c z y t u I A s y n c h r o n o u s * i A s y n c h U s b D e v i c e = new T U S B D e v i c e ( ) ; iA s y n ch U sb D e v ic e -> se tC h a rV id ("2 2 b a "); h id D e v i c e O b je c t = iA s y n ch U sb D e v ic e -> a sy n ch O p e n U S B D e v ic e (); if
(h id D e v ic e O b je c t w h ile (tru e )
!= INVALID_HANDLE_VALUE)
{
{
iA sy n ch U sbD ev ice-> a sy n ch R ea dU S B R eport(h idD ev i c e O b je c t,
in p u tR e p o rtB u ffe r ,
s iz e o f(in p u tR e p o rtB u ffe r)); p r i n t f ( " % d %d %d %d %d %d %d % d \n ",
in p u tR e p o rtB u ffe r [0 ],
in p u tR e p o rtB u ffe r[1 ], in p u tR e p o rtB u ffe r[2 ], in p u tR e p o rtB u ffe r[3 ], in p u tR e p o rtB u ffe r[4 ], in p u tR e p o rtB u ffe r[5 ], in p u tR e p o rtB u ffe r[6 ], i n p u t R e p o r t B u f f e r [ 7 ] ); if(in p u tR e p o r tB u ffe r[6 ]= = 6 4
||
\
h i d D e v i c e O b j e c t == INVALI D_ HA NDL E_ VAL UE) { break; } }/ / k o n i e c whi le
} */
326
USB. Praktyczne programowanie z Windows API w C++
C lo s e H a n d le (h id D e v ic e O b je c t);
//delete d e le te
i AsynchUsbDevi ce;
iS y n c h U sb D e v ic e ;
system ("PAUSE"); return
0;
}
/ / -------------------------------------------------------
Rysunek 8.6. Aplikacja proj_USB_R8_3 w trakcie cyklicznego odczytu raportu wejściowego z urządzenia wykonawczego
USB.Praktyczne pragramowanie\Pozdział 8\R8_3\proj_USB_RS_3.exe [ 1=1 I ^ IVID/PID/wers](i = 8890/264/787 0 129 127 128 128 0 0 c 128 127 128 128 0 0 0 129 127 128 128 0 0 c 128 127 128 128 0 0 0 129 127 128 128 0 0 0 128 127 128 128 0 0 c 129 127 128 128 0 0 0 128 127 128 128 0 0 u 128 127 128 128 0 2 0 128 127 128 128 0 3 u 128 127 128 128 0 11 0 128 127 128 128 0 9 n 128 127 128 128 0 13 0 128 127 128 128 0 12 c 128 127 128 128 0 44 0 128 127 128 128 0 36 c 128 127 128 128 0 4 0 128 127 128 128 0 0 0 128 127 128 128 0 16 G 128 127 128 128 0 0 0 129 127 128 128 0 0 G 128 127 128 128 0 0 0 129 127 128 128 0 0 U 128 127 128 128 0 0 0 128 127 128 128 3 0 n 128 127 128 128 2 0 0 128 127 128 128 0 0 n 128 127 128 128 2 0 0 128 127 128 128 3 0 c 128 127 128 128 0 0 0 129 127 128 128 0 0 c 128 127 128 128 0 0 G 129 127 128 128 0 0 C 128 127 128 128 0 0 G 128 127 128 128 0 64 A by kontynuow ać, rt a c i ś n i j dow olny k la w is z . . .
1
|
Nowe interfejsy m ogą być deklarowane poprzez dziedziczenie po istniejących inter fejsach (rysunek 8.7). Podobnie ja k wszystkie klasy dziedziczą po TObject, wszystkie interfejsy dziedziczą po IUnknown.
Uwaga
IUnknown jest odpowiednikiem interfejsu Iln te rfa c e z modułu system.hpp. Podczas tworzenia aplikacji niezależnych od platformy systemowej należy używać IInterface. IUnknown je st zarezerwowany dla platformy W in32/64.
Zliczanie odwołań do interfejsu Interfejs IUnknown deklaruje trzy m etody: AddRef(), Release() i Q u ery In terfac e(). Pierwsze dwie zarządzają zliczaniem odw ołań w czasie życia obiektu implementują cego interfejs. AddRef() zwiększa licznik odwołań do interfejsu, natomiast Release() zmniejsza go. Q ueryInterface() kontaktuje się z innymi interfejsami, które może im plementować obiekt. Do użycia Q ueryInterface() niezbędny jest identyfikator inter fejsu. W celu wydobycia identyfikatora zawsze można podać nazwę interfejsu. C++ automatycznie konwertuje nazwę interfejsu na jego identyfikator. W celu utworzenia początkowo niezainicjowanego obiektu używamy funkcji C reateInstance().
Rozdział 8. ♦ Programowanie obiektowe transmisji USB
327
« in t e r f a c e » IU n kno w n
+QueryInterfaced +AddRefQ +ReleaseQ
« C p p S tru c t» HIDD ATTRIBUTES
« C p p T y p e d e f»
+synchReadUSBReporfQ +synchOpenUSBDeviceQ +setUshorfVidQ
+asynchReadUSBReporfQ +asynchOpenUSBDeviceQ +setGharVidO
7 T
TU S B D evice
~7T
PHIDD A T T R I B U T E S
« in t e r f a c e » ■A synchronous
7 T
+Size: ULONG +VendorlD: USHORT +ProductlD: USHORT +VersionNumber: USHORT
«C ppS ynonym »
« in t e r f a c e » I S ynch ro no u s
«C ppS ynonym »
« C p p T y p e d e f» HIDD ATTRIBUTES -hiddAttributes
-Addend: LONG -pathUSBDevice: string -memberlndex: DWORD -devicelnterfaceDetailData: PSP_DEVICE_lNTERFACE_DETAIL_DATA -numberOtBytesRead: DWORD -result: DW ORD -classGuid: GUID -hHidLib: HMODULE -devic el nte rfac eDetail DataSize: DWORD -devicelnfoSet: HDEVINFO -devic el nte rfac eData: SP_DEVI CE_INTERFACE_DATA -usVid: USHORT -chVid: char « c re ate » +TU SBDevic e() « d e s tro y » + T U S B D e v ic e () +setUshortVid[) +setCharVid() +displayError() + synch Read USBRe port() +asynchReadUSBReporfO +synchOpenUSBDeviceO +asynchOpenUSBDevice() +getUSBDevicePathO +Querylnterface() +AddRef() +ReleaseO
Rysunek 8.7. Realizacja identyfikowanych interfejsów
Identyfikator interfejsu Pełniejszą realizację interfejsu można uzyskać poprzez wydobycie jego identyfikatora. Typ strukturowy GUID, zdefiniowany w pliku nagłówkowym mapiguid.h, przechowuje globalnie unikatowy identyfikator GUID. t y p e d e f s t r u c t _GUID { u n sig n e d lo n g
Data1;
unsigned s h o r t Data2; unsigned s h o r t Data3; unsigned ch a r
Data4[8];
} GUID;
System operacyjny W indows, generując nowy identyfikator GUID, gwarantuje, że b ę dzie on unikatowy w skali wszystkich identyfikatorów tego typu wygenerowanych na całym świecie. C++ używa wartości GUID do identyfikowania interfejsów. Środowisko
328
USB. Praktyczne programowanie z Windows API w C++
IDE w systemie Windows generuje automatycznie nowy identyfikator GUID po naci śnięciu kom binacji klawiszy Ctrl+Shift+G; podobnie jest w przypadku Linuksa, na przykład: [ '{ E 7 D 1 F D E 1 - 8 A 6 1 - 1 1 D 9 - 8 C 6 8 - 0 0 E 0 7 D 8 4 3 8 5 2 } ' ]
Poszczególne elementy identyfikatora należy wpisać w postaci szesnastkowej do po szczególnych pól struktury GUID, tak ja k pokazano na listingu 8.7. Listing 8.7. Kod pliku nagłówkowego usb_R8_4.h zawierającego definicje odpowiadające rysunkowi 8.7 # i f n d e f usb _R8_4H # d e f i n e usb _R8_4H # in c lu d e # in c lu d e < c str in g > # in c lu d e u s i n g n a m es p a ce s t d ; # d e fin e b u ff e rL e n g th
32
# d e f i n e s e a r c h M a x D e v i c e 10 / / ----------------------------------------------------------------------------------------------------------------t e m p l a t e < c l a s s T> in lin e
v o i d r e l e a s e M e m o r y ( T &x)
{ assert(x de le te
!= N U L L ) ;
[]
x;
x = NULL; }
/ / ----------------------------------------------------------------------------------------------------------------t y p e d e f s t r u c t _HIDD_ ATT RIB UTE S { ULONG S i z e ; USHORT V e n d o r I D ; USHORT P r o d u c t I D ; USHORT V e r s i o n N u m b e r ; } HIDD _AT TR IBU TES , *PHIDD_ ATT RIB UT ES; / / ----------------------------------------------------------------------------------------------------------------v o id
(
s td c a ll
*H idD _G etH idG uid)(O U T
bool
(
s td c a ll
* H id D _ G e tA ttrib u te s )(IN
LPGUID H i d G u i d ) ; HANDLE H i d D e v i c e O b j e c t ,
OUT PHIDD_ATTRIBUTES A t t r i b u t e s ) ; / / ----------------------------------------------------------------------------------------------------------------in te rfa ce
ISynchronous:
p u b lic
IUnknown
{
p u b lic : v irt u a l
bool
s td c a ll
synchReadUSBReport(HANDLE h id D e v O b je c t ,
v o id * in p u tR e p o r tB u ffe r, v irt u a l
HANDLE
s td c a ll
v irt u a l
v o id
v irt u a l
~ ISynchronous()
s td c a ll
ULONG i n p u t R e p o r t B u f f e r L e n g t h )
synchOpenUSBDevice()
setU shortVid(U SH O RT v id )
= 0;
= 0; = 0;
{};
};
/ / ----------------------------------------------------------------------------------------------------------------in te rfa ce
IAsynchronous:
p u b lic
IUnknown {
p u b lic : v irt u a l
bool
s td c a ll
asynchReadU SBReport(H AND LE h id D e v O b j e c t ,
v o id * in p u tR e p o r tB u ffe r, v irt u a l
HANDLE
s td c a ll
v irt u a l
v o id
v irt u a l
~ IAsynchronous()
s td c a ll
ULONG i n p u t R e p o r t B u f f e r L e n g t h ) =
asynchOp enU SBD evice()
se tC h a rV id (c o n s t {};
= 0;
char* v id )
= 0;
0;
Rozdział 8. ♦ Programowanie obiektowe transmisji USB
329
};
/ / ----------------------------------------------------------------------------------------------------------------//deklaracje
i d e n t y f i k a t o r ó w GUID
/ / [ ' { E7D1FDE1-8A61-11D9-8C68-00E07D843852} ' ] s ta tic {
GUID i i d I S y n c h r o n o u s =
0 xE 7 D1 FD E1 , 0 x 8 A 6 1 , 0xE0,
0x11D9,
0 x7 D ,
{ 0x8C, 0x68,
0x84,
0x00,
0 x 3 8 , 0x 52 } };
/ / [ ' { E7D1FDE2- 8A61- 11D9- 8C68- 00E07D843852} ' ] s ta tic {
GUID i i d I A s y n c h r o n o u s
0 xE 7 D1 FD E2 , 0 x 8 A 6 1 , 0xE0,
=
0x11D9,
0 x7 D ,
{ 0x8C, 0x68,
0x84,
0x00,
0 x 3 8 , 0x 52 } };
/ / ----------------------------------------------------------------------------------------------------------------cla ss
TUSBD evice:
p u b lic
ISynchronous,
p u b lic
IAsynchro nous
{
p riv a te : LONG A d d e n d ; s trin g
/ / l i c z n i k odwoł ań do i n t e r f e j s u
pathU SB D evice;
DWORD m em b e rI n d e x; HIDD_ATTRIBUTES h i d d A t t r i b u t e s ; PS P_ DE V IC E_ IN TE RF AC E_ DE TA IL _ DA TA d e v i c e I n t e r f a c e D e t a i l D a t a ; DWORD n u m b e r O f B y t e s R e a d ; DWORD r e s u l t ; GUID c l a s s G u i d ; HMODULE h H i d L i b ; DWORD d e v i c e I n t e r f a c e D e t a i l D a t a S i z e ; HDEVINFO d e v i c e I n f o S e t ; S P_ DE V IC E_I NT ER FA CE _D A TA d e v i c e I n t e r f a c e D a t a ; USHORT u s V i d ; co nst ch a r * chVid; p u b lic : T U S B D e v ice (); ~ T U S B D e vic e (); v o id
s t d c a l l setU shortVid(U SH O RT v id ) ;
v o id
s t d c a ll se tC h a rV id (c o n st
v o id
s td c a ll d is p la y E rr o r(c o n s t
bool
s t d c a l l synchReadUSBReport(HANDLE h id D e v O b je c t ,
bool
s td c a ll
char * vid ); c h a r * m sg );
v o id * in p u tR e p o r tB u ffe r, v o id * in p u tR e p o r tB u ffe r, HANDLE
ULONG i n p u t R e p o r t B u f f e r L e n g t h ) ;
asynchReadU SBReport(H AND LE h id D e v O b j e c t , ULONG i n p u t R e p o r t B u f f e r L e n g t h ) ;
s t d c a l l synchOpenU SBDevice();
HANDLE
s t d c a l l asynchOpenU SBDevice();
s trin g
s t d c a l l getU SB D evicePath(U IN T);
v irt u a l
HRESULT
std ca ll
Q u eryIn terface(co n st v irt u a l
ULONG
s td c a ll
AddRef();
v irt u a l
ULONG
s td c a ll
R e le a s e ();
IID& i i d ,
v o id **O bj);
};
/ / ----------------------------------------------------------------------------------------------------------------# e n d if
/ / -----------------------------------------------------------------------------------------------------------------
W ywołania funkcji AddRef() i Rel ease() pozwalają zarządzać czasem życia interfej sów. W celu użycia mechanizmu automatycznego zliczania odwołań należy zadekla rować w skaźnik do typu bazow ego interfejsu. Po jego zainicjow aniu wywołujem y metodę AddRef() z funkcją InterlockedIncrem ent(), zw iększającą licznik odwołań Addend do interfejsu. Kiedy obiekt kończy swoje operacje, wywoływana je st funkcja
330
USB. Praktyczne programowanie z Windows API w C++
Release() z funkcją InterlockedDecrement(), zmniejszającą licznik odwołań do inter fejsu. Funkcję Q ueryInterface() można zaimplementować w dowolny sposób. W po w yższym przykładzie zastosowano rozwiązanie preferow ane przez klasę T ln te rfa cedObject. N a listingu 8.8 zaprezentowano kod modułu z implementacją zarządzania czasem życia interfejsów.
Uwaga
Funkcji InterlockedIncrem ent() i InterlockedDecrement(), zdefiniowanych w mo dule sysutils.hpp, należy używać tylko na platformie linuksowej. Aplikacje Windows korzystają z analogicznych funkcji API zdefiniowanych w module windows.hpp.
Listing 8.8. Kod modułu usb_R8_4.cpp z implementacją zarządzania czasem życia interfejsów # i n c l u d e #pragma o p t i o n
p u s h -a 1
# i n c lu d e < s e tu p a p i> #pragma o p t i o n
pop
# in c lu d e # in clu d e
"usb_R8_4.h"
u s i n g n a m es p a ce s t d ; / / ----------------------------------------------------------------------------------------------------------------T U S B D e v ice ::T U S B D e v ice () { m em be rI nd e x = 0; d e v i c e I n t e r f a c e D e t a i l D a t a = NULL; h H i d L i b = NULL; h H id L ib = L o a d L ib ra ry ("C :\\W in d o w s \\S y s te m 3 2 \\H ID .D L L "); if
(!h H id L ib ) d is p la y E rro r("B łą d
d o łą c ze n ia b i b lio t e k i
H ID .D LL.");
(FARPROC&) H i d D _ G e t H i d G u i d = G e t P r o c A d d r e s s ( h H i d L i b , (FARPROC&) if
"H id D _ G etH id G u id ” ) ;
H id D _ G e tA ttrib u te s = G e tP ro c A d d re s s (h H id L ib ,
(!H id D _G e tH id G u id
||
"H id D _ G e tA ttrib u te s");
!H id D _ G e tA ttrib u te s ){
F re e L ib ra ry ( h H id L ib ) ; d is p la y E rro r("N ie
z n a l e z i o n o żadnych f u n k c j i
e k sp orto w ych.\n");
} }
/ / ----------------------------------------------------------------------------------------------------------------TU SBD evice ::~ T U SB D evice() { if(h H id L ib
!= NULL)
F re e L ib ra ry ( h H id L ib ) ; }
/ / ----------------------------------------------------------------------------------------------------------------v o id
s td c a ll
T U S B D e v ice ::d is p la y E rro r(c o n st
c h a r * msg ){
c o u t << msg << e n d l ; system ("PAUSE"); e x it(0 ); };
/ / ----------------------------------------------------------------------------------------------------------------v o id
s td c a ll
T U S B D e v ic e ::s e tU s h o rtV id (U S H O R T v id )
{ u sV id = v id ; }
/ / --------------------------------------------------------------------------------------------
Rozdział 8. ♦ Programowanie obiektowe transmisji USB
v o id
s td c a ll
331
T U S B D e v ic e ::s e tC h a rV id (c o n s t char* v id )
{ ch V id = v id ; }
/ / ---------------------------------------------------------------------------------s trin g
s td c a ll
T U S B D e v ic e ::g e tU S B D e v ic e P a th (U IN T )
{ H id D _ G e tH id G u id (& c l a s s G u i d ) ; d e v ic e ln f o S e t = S e tu p D iG e tC la s s D e v s (& c la s s G u id , DIGCF_PRESENT if
NULL,
NULL,
| D I G C F _ I N T E R F A C E D E V I C E );
( d e v i c e I n f o S e t == INVALI D_ HAN DLE _VA LU E){ F re e L ib ra ry ( h H id L ib ) ; d is p la y E rro r("N ie
z id e n ty fik o w a n o podłączonych
urząd zeń.\n");
} d e v ic e In te rfa c e D a ta .c b S iz e
= size o f(SP_D EVICE _IN TE RFACE_D ATA);
SetupD i E n u m D e v ic e I n te r f a c e s ( d e v ic e I n fo S e t, memberIndex,
NU LL,
S e tu p D iG e tD e v ic e In te rfa c e D e ta il(d e v ic e In fo S e t, NULL ,
0,
& c la s s G u id ,
& d e v ice In te rfa c e D a ta ); &devi c e I n t e r f a c e D a t a ,
& d e v ic e In te rfa c e D e ta ilD a ta S iz e ,
NULL);
d e v i c e I n t e r f a c e D e t a i l D a t a = (PS P_ DE V IC E_ IN T ER F A CE _ DE T A IL _D A TA ) new D W O R D [ d e v i c e I n t e r f a c e D e t a i l D a t a S i z e ] ; d e v ice In te rfa ce D e ta ilD a ta -> cb S ize = size o f(S P _ D E V IC E _ IN T E R F A C E _ D E T A IL_ D A T A ); if
(S e tu p D iG e tD e v ic e In te rfa c e D e ta il(d e v ic e In fo S e t, d e v ic e In te rfa c e D e ta ilD a ta , NULL,
NU LL ) )
& d e v ice In te rface D a ta ,
d e v ic e In te rfa c e D e ta ilD a ta S iz e ,
{
path U S B D evic e = d e v i c e I n t e r f a c e D e t a il D a t a - > D e v i c e P a t h ;
/ / c o u t << pathUSBDevi ce << e n d l ; } re le a s e M e m o ry (d e v ic e In te rfa c e D e ta ilD a ta ); S e tu p D iD e s tro y D e v ic e In fo L is t(d e v ic e In fo S e t); return
p ath U S B D evic e;
}
/ / ----------------------------------------------------------------------------------------------------------------HANDLE
s td ca ll
T U S BD evice ::asyn ch O p en U S B D evic e()
{ s trin g HANDLE
devUSBpath; h i d D e v O b j e c t = IN VALID_HANDLE_VALUE;
w h ile ((d e v U S B p a th if
= getUSBDevicePath(m em berIndex+ +))
(NULL != s t r s t r ( d e v U S B p a t h . c _ s t r ( ) , c o u t << e n d l
){
<< d e v U S B p a t h < < e n d l;
h id D ev O b jec t = C r e a t e F ile ( d e v U S B p a t h . c _ s t r ( ) , GENERIC_WRITE,
GENERIC_READ
FILE_SHA RE _RE AD
NU LL, OP EN _ EX I S TI N G , if(h id D e v O b je ct return
!= " "
c h V id )){
0,
NULL ) ;
!= INVALID_HANDLE_VALUE)
h id D e v O b je c t;
break; } e lse if
(m em be rI n de x > s e a r c h M a x D e v i c e ) d is p la y E rr o r(" N ie
} return
zn a le zio n o
u r z ą d z e n i a o podanym V I D . \ n " ) ;
NULL;
}
/ / ----------------------------------------------------------------------------------------------------------------HANDLE {
s td ca ll
TU S BD e vice ::s yn ch O p e n U S B D e vic e ()
| \
| FILE_ SH ARE _WR IT E,
332
USB. Praktyczne programowanie z Windows API w C++
s trin g
devUSBpath;
HANDLE
h i d D e v O b j e c t = IN VALID_HANDLE_VALUE;
w h ile ((d e v U S B p a th
= getUSBDevicePath(m em berIndex+ +))
h id D ev O b jec t = C r e a t e F ile ( d e v U S B p a t h . c _ s t r ( ) , GENERIC_WRITE,
FILE_SHA RE _RE AD
NULL , O PE N _ EX I S TI N G , H id D _ G e tA ttrib u te s (h id D e v O b je c t,
!= " "
){
GENERIC_READ | \ | FIL E_ SH ARE _W RIT E,
F IL E_ FL AG _ O VE RL AP PE D ,
NULL);
& h id d A ttrib u te s );
p r in t f ( " V I D / P I D / w e r s j a = % d /% d / % d \n ",h id d A ttrib u te s .V e n d o rID , h id d A ttrib u te s .P ro d u c tID , h id d A ttrib u te s .V e rs io n N u m b e r); if(h id d A ttrib u te s .V e n d o rI D return
== u s V i d ) {
h id D e v O b je c t;
break; } e lse if(m em b erIn dex > searchM axD evice) d is p la y E rr o r(" N ie } return
zn a le zio n o
u r z ą d z e n i a o podanym V I D . " ) ;
NULL;
}
/ / ----------------------------------------------------------------------------------------------------------------bool
s td c a ll
T U S B D evice ::asynchR e adU SB R eport(H A N D LE h id D e v O b je c t ,
v o id * in p u tR e p o r tB u ffe r,
ULONG i n p u t R e p o r t B u f f e r L e n g t h )
{ n u m b e r O f B y t e s R e a d = 0; return
R e a d F ile (h id D e v O b je c t,
in p u tR e p o rtB u ffe r ,
in p u tR e p o rtB u ffe rL e n g th ,
& num berO fBytesRead,
NULL);
}
/ / ----------------------------------------------------------------------------------------------------------------bool
s td c a ll
TU S B D evice ::syn ch R e adU SB R eport(H A N D LE h id D e v O b je c t , v o id * in p u tR e p o r tB u ffe r,
ULONG i n p u t R e p o r t B u f f e r L e n g t h )
{ r e s u lt
= 0;
n u m b e r O f B y t e s R e a d = 0; OVERLAPPED » o v e r l a p p e d = NULL; i f ( o v e r l a p p e d == NULL) { o v e r l a p p e d = new OVERLAPPED; overlappe d-> hEvent = CreateEvent(N U LL,
TRUE,
TRUE,
"");
o v e r l a p p e d - > O f f s e t = 0; o v e r l a p p e d - > O f f s e t H i g h = 0; } if(!R e a d F ile (h id D e v O b je c t,
in p u tR e p o rtB u ffe r,
&num berO fBytesRead, if(G e tL a s tE rro r() r e s u lt
ove rla p p e d ))
== ERROR_IO_PENDING)
in p u tR e p o rtB u ffe rLe n g th , {
{
= W a itF o rS in g le O b je c t(o ve rla p p e d -> h E ve n t,
if( r e s u lt
== WAIT_TIMEOUT)
INFINITE);
{
C a n c e lIo (h id D e v O b je c t); return
fa lse ;
} e lse if( r e s u lt
== W A I T _ F A I L E D ) {
c o u t << " B ł ą d o d c z y t u d a n y c h . " ; return
fa lse ;
} G e tO v e rla p p e d R e s u lt(h id D e v O b je c t,
ove rla p p e d ,
&num berO fBytesRead,
FALSE);
Rozdział 8. ♦ Programowanie obiektowe transmisji USB
333
} e lse d is p la y E rro r("B ią d
odczytu d a n y ch .");
} R e se tE v e n t(o v e rla p p e d -> h E v e n t); d e le te overlappe d; return
true;
}
/ / ----------------------------------------------------------------------------------------------------------------IUnknown * C r e a t e I n s t a n c e ( ) { IUnknown *p = ( I S y n c h r o n o u s * ) n e w T U S B D e v i c e ( ) ; p->AddRef(); r e t u r n p; }
/ / ----------------------------------------------------------------------------------------------------------------HRESULT _ s t d c a l l
T U S B D e v ic e ::Q u e ry I n te rfa c e (c o n s t
IID& i i d ,
v o i d **Obj) { if
( iid
== i i d I S y n c h r o n o u s )
*Obj = ( I S y n c h r o n o u s * ) t h i s ;
/ / w y w o ł u j e el ement y / / i n t e r f e j s u I Synchr onous
e lse if
( iid
== i i d I A s y n c h r o n o u s )
*Obj = ( I A s y n c h r o n o u s * ) t h i s ; e lse
/ / w y w o ł u j e el ement y / / i n t e r f e j s u I Asynchr onous
{
O bj = NULL; re turn
E_NOINTERFACE;
} ((IUnknow n*)(*O b j))-> A d d R e f(); r e t u r n S_OK; }
/ / ----------------------------------------------------------------------------------------------------------------ULONG _ s t d c a l l
T U S B D e v ice ::A d d R e f()
{ return
InterlockedIncrem ent(& A ddend );
}
/ / ----------------------------------------------------------------------------------------------------------------ULONG _ s t d c a l l
T U S B D e v ice ::R e le a se ()
{ if
(InterlockedD ecrem ent(&Addend) d e le te
th is ;
return
0;
== 0)
{
} r e t u r n A dd en d; }
/ / ----------------------------------------------------------------------------------------------------------------i n t m ain () { HANDLE
h id D e v ic e O b je c t;
BYTE i n p u t R e p o r t B u f f e r [ b u f f e r L e n g t h ] ; HRESULT h r ;
/ / I n i c j a l i z a c j a komponentu IUnknown *pUnknown = C r e a t e I n s t a n c e ( ) ;
//Synchroni czne operacj e odczytu
33 4
USB. Praktyczne programowanie z Windows API w C++
I S y n c h r o n o u s * p I S y n c h r o n o u s = NULL; hr = p U n k n o w n -> Q u e ry In te rfa ce (iid IS y n ch ro n o u s, if
( S U C C E E D E D ( h r) )
(v o id * * )& p IS y n ch ro n o u s);
{
p IS y n ch ro n o u s -> se tU s h o rtV id (8 8 9 0 ); h id D e v ic e O b je c t = pISynchronous-> synchO penU SB D evice(); w h ile ( tru e )
{
p IS y n c h ro n o u s -> sy n ch R e a d U S B R e p o rt(h id D e v ic e O b je ct, in p u tR e p o rtB u ffe r,
s iz e o f(in p u tR e p o rtB u ffe r));
p r i n t f ( " % d %d %d %d %d %d % d \n ",
in p u tR e p o rtB u ffe r [0 ],
in p u tR e p o rtB u ffe r[1 ],
in p u tR e p o rtB u ffe r [ 2 ] ,
in p u tR e p o rtB u ffe r[3 ],
in p u tR e p o rtB u ffe r [ 4 ] ,
in p u tR e p o rtB u ffe r[5 ], if(in p u tR e p o r tB u ffe r[6 ]= = 6 4
in p u tR e p o rtB u ffe r[6 ]); ||
\
h i d D e v i c e O b j e c t == INVALI D_ HAN DLE _VA LU E){ break; } } / /k o ni ec while p IS y n ch ro n o u s-> R e le a se (); }; /*
/ / A s y n c h r o n i c z n e o p e r a c j e od c z y t u I A s y n c h r o n o u s * p I A y n c h r o n o u s = NULL; hr = pU n k n o w n -> Q ue ry In te rfa ce (iid IA s y n ch ro n o u s , if
( S U C C E E D E D ( h r) )
(v o id ** )& p IAynchronous);
{
p IA y n c h ro n o u s -> s e tC h a rV id ("2 2 b a "); h id D e v ic e O b je c t = pIA ynchronous-> asynchO penUSB D evice(); w h ile ( tru e )
{
p IA y n ch ro n o u s-> a sy n ch R e a d U S B R e p o rt(h id D e v i c e O b je c t , in p u tR ep o rtB u ffer,
s i z e o f ( i n p u t R e p o r t B u f f e r ) );
p r i n t f ( " % d %d %d %d %d %d % d \n ",
in p u tR e p o rtB u ffe r [0 ],
in p u tR e p o rtB u ffe r[1 ],
in p u tR e p o rtB u ffe r [ 2 ] ,
in p u tR e p o rtB u ffe r[3 ],
in p u tR e p o rtB u ffe r [ 4 ] ,
in p u tR e p o rtB u ffe r[5 ], if(in p u tR e p o r tB u ffe r[6 ]= = 6 4
in p u tR e p o rtB u ffe r[6 ]); ||
\
h i d D e v i c e O b j e c t == INVALI D_ HA NDL E_ VAL UE) { break; } } / /k o ni ec while p IA y n ch ro n o u s-> R e le a se (); }; */ C lo s e H a n d le (h id D e v ic e O b je c t); pUnknow n-> Release (); system ("PAUSE"); return
0;
} / / -----------------------------------------------------------------------------------------------------------------
Podczas testowania powyższego programu z łatw ością zauważymy, że system opera cyjny lokalizuje interfejsy, biorąc pod uwagę ich identyfikatory, a nie nazwę. Aby zatem interfejsy były rozpoznawane przez system, wystarczy, że będą mieć odmienne identyfikatory. Praktyczne wykorzystywanie tak skonstruowanych interfejsów jest czynnością nie zbyt skomplikowaną. Program klienta posiada funkcję main(), w której deklarowane są wskaźniki do istniejących interfejsów. M etoda C reateInstance() służy do tworze-
Rozdział 8. ♦ Programowanie obiektowe transmisji USB
335
nia nowego komponentu USBDevice i pobierania z niego wskaźnika do interfejsu bazo wego IUnknown, tak ja k pokazano na diagramie komponentów z rysunku 8.8. Rysunek 8.8. Diagram komponentów odpowiadający przykładowi z listingu 8.8
Kiedy programista potrzebuje dodać nową metodę do istniejącego interfejsu, np. IAsyn ^chronous, tworzy nową wersję interfejsu IAsynchronous_1, tak jak pokazano na rysunku 8.9. Nowo utworzony interfejs powinien być zaimplementowany z nowym identyfika torem, powiedzmy iidIAsynchronous_1. Po tej modyfikacji programista ma do dyspo zycji now ą wersję komponentu modelującego urządzenie USBDevice. Uprzednio skon struowany program kliencki, który nie wie o nowej metodzie asynchWriteUSBReport() interfejsu, może dalej używać starszej wersji komponentu. Nie ma potrzeby rekompi lacji starego klienta. Nowy program kliencki wie o nowej metodzie asynchWriteUSB ^R ep o rt(), więc może swobodnie używać nowej wersji komponentu, tak jak pokazano na rysunku 8.10. « in t e r f a c e » ■ S yn chronous
« in t e r f a c e » ■A synchronous
« in te rfa c e > > IA syn ch ro no us_ 1
+synchReadUSBReporf() +synchOpenUSBDeviceQ +setUshortVidQ
+asynchReadUSBReporfQ +asynchOpenUSBDeviceQ +setCharVidQ
+asynchReadUSBReport() + asynch WnteUSBReportQ +asynchOpenUSBDeviceQ +setCharVidO
t>
A T U S B D e vice
-Addend: LONG -path USBDevice: string -m em berlndex: DW ORD -d evicel nte rfac eDetai IData: PSP_DEVIC E_l NTERFACE_DETAI L_DATA -num berO fBytesRead: DW ORD -result: DW ORD -classG uid: GUID -hHidLib: HM ODULE -d evicel nte rfac eDetai IDataSize: DW ORD -d evicel nfoSet: HDEVINFO -d evicel nte rfac eData: SP_DEVI CE_I NTERFACE_DATA -usVid: USHORT -chVid: char « c re a t e » +TU SBDevic e () « d e stra y > > +TU SBDevic e () +setUshortVid() +setCharVid() +displayError() + s y ne h Re ad U SBRe port [) +asynchReadUSBReport() +synchOpenUSBDevice() +asynchO penUSBDevice() +getU SBDevice Path () +Querylnterface() +AddRefQ +Release()
Rysunek 8.9. Dodawanie nowej metody do interfejsu
336
USB. Praktyczne programowanie z Windows API w C++
Rysunek 8.10. Dodawanie nowych funkcji do istniejących interfejsów Podczas pracy z identyfikowanymi interfejsam i program ista pow inien pamiętać, że zawsze należy stworzyć now ą wersję interfejsu, gdy modyfikacji podlega: ♦ liczba funkcji w interfejsie, ♦ kolejność funkcji w interfejsie, ♦ liczba parametrów w funkcji interfejsu, ♦ kolejność parametrów w funkcji interfejsu, ♦ typ parametrów w funkcji interfejsu, ♦ typ wartości zwracanej przez funkcję interfejsu. W systemach o strukturze komponentowej komponenty reprezentują wiedzę o dzie dzinie problemu, zaś interfejsy — wiedzę o istnieniu komponentów.
Komponenty wizualne Komponent jest wym ienną częścią systemu implementującą co najmniej jed n ą klasę. Jako przykład wykorzystania gotowych komponentów w aplikacji obsługującej inter fejs USB zostanie zaprezentowany działający komponent, którego zadaniem będzie diagnozowanie dołączania urządzeń do magistrali USB lub ich odłączania. Podstawo w a wersja klasy kom ponentu USBDetect składa się z jednego pola (zmiennej), pięciu funkcji składowych (metod) i dwóch własności. K luczow ym elem entem definicji klasy tworzącej kom ponent je st operacja device ^AttachDetachO, której zadaniem jest diagnozowanie zdarzeń polegających na sygnali zowaniu dołączania lub odłączania urządzeń zewnętrznych. Określenie definicji takiej operacji pociąga za sobą konieczność posłużenia się w skaźnikiem do funkcji obsługi zdarzeń: ty p e d e f v o id
fa s tc a ll
(
c lo s u r e * T N o tify E v e n t)
(S ystem ::T O b ject *Sender);
Rozdział 8. ♦ Programowanie obiektowe transmisji USB
337
Należy również delegować własności USBAttach i USBDetach do odpowiednich funkcji obsługi zdarzeń, tak ja k pokazano na listingu 8.9 i odpowiadającym mu rysunku 8.11. Rysunek 8.11. Statyczny diagram klas odpowiadający przykładowi z listingu 8.9
T C om ponent
1 T U S B D e te ct -FW indowHandle: HWND -FU SBAttach: TNotifyEvent -FUSBDetach: TNotifyEvent +U SB A ttach: TNotifyEvent +USBDetach: TNotifyEvent
« d e le g a t e » T N o tifyE ve n t
-W lndowProc[) -USBDeviceNotify() #deviceAttachDetach() <>+TUSBDetect[) « d estroy» +TU SBDet ect()
Listing 8.9. Definicja klasy TUSBDetect komponentu USBDetect zawarta w pliku USBDetect.h / / --------------------------------------# i f n d e f USBDetectH # d e f in e USBDetectH / / --------------------------------------# in clu d e < S y sU tils .h p p > # in c lu d e < C o ntrols.h p p> # in c lu d e < Classes.hpp> # i n c l u d e # in c lu d e # i n c l u d e s ta tic
GUID GUID_DEVINTER FACE _U SB_ DEV ICE =
{0x A5 DC B F1 0 ,
0 x6530, 0x11D2,
{0x90, 0x4F,
0x1F, 0xB9,
0x00, 0x51,
0 xC 0 , 0xED}};
/ / -----------------------------------------------------------------------------cla ss
PACKAGE T U S B D e t e c t
: p u b l i c TCompo nen t
{
p riv a te : HWND F W in d o w H a n d le ; TN o tify E v e n t
FUSBAttach;
TN o tify E v e n t
F U S B D e t a ch ;
v o id
f a s t c a l l W i n d o w P r o c ( T M e s s a g e &msg);
bool
f a s t c a l l U S B D e v ic e N o tify();
protected: v o id
f a s t c a l l d e v i c e A t t a c h D e t a c h ( T M e s s a g e &msg);
p u b lic : fa s tc a ll
T U S B D e t e c t ( T C o m p o n e n t * O w n e r) ;
fa s tc a ll
v irt u a l
~TUSBDetect();
_ _ p u b lis h e d : p r o p e r t y T N o t if y E v e n t U SBAttach = {read= FU SBAttach, w rite= FU SB A ttach}; p r o p e r t y T N o t i f y E v e n t U S B D e ta ch = { r e a d = F U S B D e t a c h , w rite= FU SBD etach}; };
/ / ----------------------------------------------------------------------------------------------------------# e n d if
/ / ---------------------------------------------------------------------------------------
338
USB. Praktyczne programowanie z Windows API w C++
N a listingu 8.10 zamieszczono kompletny kod modułu implementującego funkcje skła dowe komponentu USBDetect. Proces wykrywania dołączenia lub odłączenia urządzenia odbywa się w ciele funkcji deviceAttachDetach(), wywoływanej na rzecz WindowProc(), w momencie gdy system wykryje zmianę stanu urządzenia USB. Listing 8.10. Kod modułu USBDetect.cpp implementującego elementy składowe klasy komponentu / / ----------------------------------------------------------------------------------------------------------------# i n c l u d e < v c l .h> #pragma h d r s t o p # in clu d e
"U S BD etect.h"
#pragma p a c k a g e ( s m a r t _ i n i t ) / / ----------------------------------------------------------------------------------------------------------------s ta tic
in lin e
v o i d V a l i d C t r C h e c k ( T U S B D e t e c t *)
{ new T U S B D e t e c t ( N U L L ) ; }
//
-------------------------------------------------------------------------fa s tc a ll
T U S B D e t e c t : : T U S B D e t e c t ( T C o m p o n e n t * Owner)
: T Co m po ne n t(O w n er ) { F W in d o w H a n d le = A l l o c a t e H W n d ( W i n d o w P r o c ) ; U S B D e v ice N o ti f y ( ) ; }
//
--------------------------------------------------------------------------
n a m es p ac e U s b d e t e c t { v o id
fa s tc a ll
PACKAGE R e g i s t e r ( )
TCom po ne ntClass c l a s s e s [ 1 ]
{
= {
R e g is te rC o m p o n e n ts ("S a m p le s ",
c la s s id (T U S B D e te c t)} ; cla ss e s ,
0);
} }
//
-------------------------------------------------------------------------fa s tc a ll
TU SBD etect::~ TU SBD etect()
{ D e a llo ca te H W n d (F W in d o w H a n d le ); }
//
--------------------------------------------------------------------------
v o id
fa s tc a ll
T U S B D e t e c t : : W i n d o w P r o c ( T M e s s a g e &msg)
{ if
(m sg .M s g = WM_DEVICECHANGE) try
{
{
d e v iceA ttach D etach (m sg); } ca tc h (...)
{
A p p lic a t i o n -> H a n d le E x c e p tio n (th is ); } } e lse m s g . R e s u l t = D e f W in d o w P r o c ( F W i n d o w H a n d l e , m s g .M s g , msg.W Par am , m s g . L P a r a m ) ; };
//
--------------------------------------------------------------------------
v o id
fa s tc a ll
T U S B D e t e c t : : d e v i c e A t t a c h D e t a c h ( T M e s s a g e &msg)
{ UINT d e v T y p e ; PDEV_BROADCAST_HDR h d r ;
Rozdział 8. ♦ Programowanie obiektowe transmisji USB
if
(( msg .W Par am==DBT_DEVICEARRIVAL)
||
339
l / d o ł ą c z o n e l ub { //odłączone
(msg.WParam==DBT_DEVICEREMOVECOMPLETE)) h d r = (PD EV _B ROADCAST_HDR)msg.LParam; devType = h d r-> d b c h _ d e v ic e ty p e ; if
(devType==D BT_ DE VTY P_ DE VICE INTE RFA CE) if
(msg.WParam==DBT_DEVICEARRIVAL) if
{ / / u r z ą d z e n i e USB
{ // u r z ąd z e n i a pr zyłączone
(FUSBAttach) F U S B A tta ch (th is);
} e lse if
{ ( F U S B D e ta c h )
F U S B D e ta ch (th is );
/ / ----------------------------------------------------------------------------------------------------------------bool
fa s tc a ll
T U S BD etect::U S B D eviceN o tify()
{ v o id * r e s u lt ; DEV_BROADCAST_DEVICEINTERFACE N o t i f i c a t i o n F i l t e r ; Z e ro M e m o ry ( & N o tific a tio n F ilte r, N o tific a tio n F ilte r.d b c c _ s iz e
s iz e o f( N o tific a tio n F ilte r) ) ;
= sizeof(DEV_BROADCAST_DEVICEINTERFACE);
N o t i f i c a t i o n F i l t e r . d b c c _ d e v i c e t y p e = DBT_DEVTYP_ DE VICE INT ER FA CE; N o tific a tio n F ilte r.d b c c _ c la s s g u id r e s u lt
= GUID_DEV INT ER FAC E_ USB _D EV ICE;
= R e g is te rD e v ic e N o tific a tio n (F W in d o w H a n d le ,
& N o tific a tio n F ilte r,
DEVICE_NOTIFY_WINDOW_HANDLE); if( !r e s u lt) return return
fa lse ;
tru e;
}
/ / -----------------------------------------------------------------------------------------------------------------
N a listingu 8.11 i rysunku 8.12 pokazano aplikację im plem entującą funkcje obsługi zdarzeń komponentu USBDetect oraz wynik działania aplikacji w momencie wykrycia nowo przyłączanego urządzenia USB. Listing 8.11. Kod głównego modułu projektu usb_R8_5.cpp //
--------------------------------------------------------------------------
# i n c l u d e < v c l .h> #pragma h d r s t o p # in c lu d e "usb_R8_5.h" / / ----------------------------------------------------------------------------------------------------------------#pragma p a c k a g e ( s m a r t _ i n i t ) #pragma l i n k
"USBD etect"
#pragma r e s o u r c e
"*.dfm "
T F o r m l *Fo rm 1; / / ----------------------------------------------------------------------------------------------------------------fa s tc a ll
T F o r m 1 : : T F o r m 1 ( T C o m p o n e n t * Owner)
: TF or m (O wn er ) { }
//
--------------------------------------------------------------------------
v o id
fa s tc a ll
T F o rm 1 ::U S B D e te c t1 U S B A tta ch (T O b je c t *Sender)
340
USB. Praktyczne programowanie z Windows API w C++
{ S h o w M e s s a g e ( " U r z ą d z e n i e USB z o s t a ł o p r z y ł ą c z o n e . " ) ; }
//
----------------------------------------------------------------------
v o id
fa s tc a ll
T F o rm 1::U S B D e tect1U S B D e ta ch (T O b ject *Sender)
{ S h o w M e s s a g e ( " U r z ą d z e n i e USB z o s t a ł o
o dłą czo n e .");
}
/ / ------------------------------------------------------------------------------------------------------------
Rysunek 8.12. Aplikacja projektu proj_USB_R8_5 w trakcie działania
Podsumowanie Rozdział zawiera krótkie wprowadzenie do metod obiektowego projektowania i imple mentacji oprogramowania sterującego łączem USB. Dla osób pragnących poeksperymentować z bardziej zaawansowanymi technikami programowania szeregowej trans misji danych przygotowano serię ćwiczeń do samodzielnego wykonania.
Ćwiczenia Ćwiczenie 8.1 Uzupełnij przedstawione w tym rozdziale klasy, tak aby zawierały metody bazujące na wybranych funkcjach rodzin HidD_Xxx() oraz HidP_Xxx(), nieuwzględnionych w tym rozdziale, a omawianych w rozdziałach 4. i 6.
Rozdział 8. ♦ Programowanie obiektowe transmisji USB
341
Ćwiczenie 8.2 W zorzec projektowy fabryki abstrakcyjnej (ang. abstract factory) pozwala na enkapsulację grupy metod fabrykujących dotyczących tego samego zagadnienia. Z reguły fabryka abstrakcyjna jest przedstawiana w postaci interfejsu. Następnie (w oprogramo w aniu klienta) tworzone są konkretne implementacje fabryki realizujące ten interfejs. Konkretne obiekty są tworzone poprzez wywołanie metod poszczególnych interfej sów. W ten sposób od implem entacji fabryki zależny jest tylko i wyłącznie fragment kodu tworzący określoną fabrykę. Fabryka pozwala na tworzenie zestawów obiektów dopasowanych do konkretnych zastosowań (np. różnych protokołów transmisji danych, w yboru różnych sterowników itp.). K ażda fabryka pozwala na tworzenie kolekcji obiektów zajmujących się pewnym za gadnieniem, takim jak na przykład obsługa interfejsu użytkownika dla programów w y konujących transmisję danych. Przykładowo można wyobrazić sobie interfejs IDeviceFactory realizowany przez klasy TRS232CDeviceFactory i TUSBDeviceFactory (rysunek 8.13). Zaimplementowane w nich metody fabrykują grupę urządzeń A (np. przyrządy pomiarowe) oraz grupę urządzeń B (np. popularnych urządzeń peryferyjnych, takich ja k różnego rodzaju kontrolery gier), które mogą się posługiwać protokołami transmi sji danych RS 232C i USB. « in te r f a c e »
« in te r f a c e »
IDeviceFactory
IDeviceA
+fDeviceA(): IDeviceA +fDeviceBO IDeviceB « d e s tro y » + ID e v ic e F a c to ry ()
+disp la yD e viceQ : void
« d e stroy > >+1Devic eA()
7T TUSBDeviceA
TRSDeviceA
-displayDevice(): void
-displayDeviceQ: void
TU SBDeviceFactory
TRS ?32CDeviceFactory
-Device A(): IDeviceA -Device B[): IDeviceB
-D ev ceA(): IDeviceA -D ev ceBQ: IDeviceB
« in te r f a c e »
IDeviceB +displayDevice(): void « d e s tro y » + ID e v ic e B Q
7T TUSBDeviceB
TRSDeviceB
-displayDeviceQ void
-displayDeviceQ void
Rysunek 8.13. Ogólna struktura wzorca fabryki abstrakcyjnej w zastosowaniu do dwóch grup urządzeń posługujących się odmiennymi protokołami transmisji danych N a listingu 8.12 zamieszczono szkielet przykładowej im plementacji fabryki z rysun ku 8.13.
342
USB. Praktyczne programowanie z Windows API w C++
Listing 8.12. Kod modułu usb_R8_C1.cpp # in c lu d e u s i n g n a m es p ac e s t d ; cla ss
ID eviceA
{
p u b lic : v irt u a l
v o id d is p la y D e v ic e ( )
v irt u a l
~ ID e viceA ()
= 0 ;
{};
};
/ / ----------------------------------------------------------------------------cla ss
ID eviceB
{
p u b lic :
};
//
v irt u a l
v o id d is p la y D e v ic e ( )
v irt u a l
~ ID e vice B ()
= 0 ;
{};
--------------------------------------------------
cla ss
TR SD eviceA
: p u b lic
ID ev iceA
{
p riv a te : v o id d is p la y D e v ic e ( )
{
c o u t << "RS 232C D e v i c e A" << e n d l
;
} };
//
--------------------------------------------------
cla ss
T R SD eviceB
: p u b lic
ID eviceB
{
p riv a te : v o id d is p la y D e v ic e ( )
{
c o u t << "RS 232C D e v i c e B" << e n d l ;
//
---------------------------------------------
cla ss
T U S B D e v ic e B
: p u b lic
ID eviceB
{
p riv a te : v o id d is p la y D e v ic e ( )
{
c o u t << "USB D e v i c e
B" << e n d l
;
} };
/ / ---------------------------------------------------------------------cla ss
T U S B D e v ic e A
: p u b lic
ID eviceA
{
p riv a te : v o id d is p la y D e v ic e ( )
{
c o u t << "USB D e v i c e A" << e n d l
;
// cla ss
ID e v ice F a cto ry
{
p u b lic : v irt u a l
ID e vice A * fD e v ic e A ( )
= 0;
v irt u a l
IDeviceB * fD e v ic e B ()
= 0;
v irt u a l
~ ID e vice F a cto ry ()
{};
};
/ / -------------------------------------------------------------------------------------------cla ss
TR S2 32 CD e viceFacto ry
: p u b lic
p riv a te : ID eviceA* fD e v ic e A () return
}
{
new T R S D e v i c e A
;
ID e v ice F a cto ry
{
Rozdział 8. ♦ Programowanie obiektowe transmisji USB
ID eviceB * return
fD e v ic e B ()
343
{
new T R S D e v i c e B
;
} };
/ / --------------------------------------------------------------------------------------cla ss
T U S B D e v ice Fa cto ry
: p u b lic
ID e v ice F a cto ry
{
p riv a te : ID eviceA* fD e v ic e A () return
{
new T U S B D e v ic e A ;
} ID eviceB * fD e v ic e B () return
{
new T U S B D e v ic e B
;
} };
/ / --------------------------------------------------------------------------------------I D e v ic e F a c to r y *df; ID eviceB * id
;
/ / --------------------------------------------------------------------------------------i n t m ain () { d f = new T U S B D e v i c e F a c t o r y ;
/ / d f = new TRS232CDevi ceFactor y; id = d f-> fD e v ic e B (); id -> d is p la y D e v ic e (); d e le te
df;
system ("PAUSE"); return
0;
}
/ / ---------------------------------------------------------------------------------------
Zmodyfikuj powyższy kod w taki sposób, aby mógł realizować komunikację z zewnętrz nymi urządzeniami posługującymi się szeregowymi protokołami transmisji danych opar tymi na standardach RS 232C [6] lub USB.
Ćwiczenie 8.3 W zorzec projektowy obserwatora (ang. observer) służy do tworzenia relacji typu jeden-do-w ielu łączącej grupę obiektów. W zorzec składa się z dwóch ról: przedmiotu TSubject oraz interfejsu obserwatora IObserver. Dzięki tego typu relacji zmiana stanu obiektu po stronie przedmiotu umożliwia automatyczne powiadomienie o niej wszyst kich innych zainteresowanych obiektów (tzw. obserwatorów). W klasie TSubject są zdefiniowane metody pozwalające na dołączanie i odłączanie obserwatorów; każdy zainteresowany obiekt może się zarejestrować jako obserwator. M etoda n o tify () służy do pow iadam iania w szystkich zarejestrowanych obserwato rów poprzez odpowiednie wywołanie w pętli metody update() zdefiniowanej w inter fejsie IObserver. M etoda ta jest wykorzystywana do powiadam iania o zmianie stanu obserwowanego obiektu. N a rysunku 8.14 pokazano statyczny diagram klas wzorca obserwatora. Konkretnym obserwatorem jest klasa TResultReadReport realizująca in terfejs IObserver, a rolę konkretnego przedm iotu odgrywa klasa TUSBDevice dziedzi cząca po TSubject. N a listingu 8.13 zamieszczono szkielet przykładowej implementa cji wzorca obserwatora pokazanego na rysunku 8.14.
34 4
USB. Praktyczne programowanie z Windows API w C++
Rysunek 8.14. Ogólna struktura wzorca obserwatora zastosowana do obsługi urządzenia USB
Listing 8.13. Kod modułu usb_R8_C2.cpp # in c lu d e # in clu d e < ite ra to r> # in clu d e < lis t > u s i n g n a m es p ac e s t d ; c la ss
T S u bject;
c la ss
IO b s e rv e r { //Obserwat or
p u b lic : v irt u a l
v o id
v irt u a l
~ IO bserver()
u p d a t e ( T S u b j e c t * ) = 0; {};
};
/ / ----------------------------------------------------------------------------------------------------------------cla ss
T S u bject
{ //Przedmiot
p u b lic : TS u bject(); v irt u a l v irt u a l v irt u a l v irt u a l
//dołącza ( r e je s tru je ) obiekty / / k l a s y obs e r wa t o r a v o i d d e t a c h ( I O b s e r v e r * ) ; / / u s u wa o b i e k t y / / k l a s y obs e r wa t o r a v o id n o t i f y ( ) ; / / p o w i a d a mi a o w y s t ą p i e n i u z d a r z e n i a v o i d s e t C h a n g e ( ) ; / / r e j e s t r u j e zmi anę s t a n u po j edynczego //obiektu v o id a tta c h (IO b s e rv e r * );
p riv a te : l i s t < I O b s e r v e r * > o b s e r v e r s ; / / d wu k i e r u n k o w a l i s t a
/ / k l a s y obserwatora; // dwuki er unkowym bool
obiektów l i s t j e s t kont enerem
/ / r e p r e z e n t u j e s t a n p o j e dy nc z e go o b i e k t u
change;
};
/ / ----------------------------------------------------------------------------------------------------------------v o id T S u b je c t::se tC h a n g e () change = t r u e
{
;
}
/ / ----------------------------------------------------------------------------------------------------------------T S u b je c t::T S u b je c t():
ch a n g e (fa ls e )
{} / / p o c z ą t k o w o p r z e d m i o t
/ / j e s t w s t a n i e ni eakt ywnym / / -----------------------------------------------------------------------------------------------------------------
Rozdział 8. ♦ Programowanie obiektowe transmisji USB
v o id T S u b je c t:: a t ta c h
( I O b s e r v e r *o)
{
/ / d o d a j e o b s e r w a t o r na końcu kon t e n e r a
o bservers.push_back(o); }
/ / ----------------------------------------------------------------------------------------------------------------v o id T S u b je c t::d e ta c h
( I O b s e r v e r * o)
{
observers.rem ove(o); }
/ / ----------------------------------------------------------------------------------------------------------------v o id T S u b je c t : : n o t if y
()
{
/ / p o w i a d a mi a o b s e r w a t o r o z mi a n i e s t a n u if
(change)
{
lis t< I O b s e rv e r* > :: ite r a to r i
= o b s e rv e rs.b e g in ();
fo r
i++ )
( ; i!= o b s e r v e r s . e n d ( ) ;
{
( * i) -> u p d a te ( th is ); } change = f a l s e ; } }
/ / ----------------------------------------------------------------------------------------------------------------cla ss
TUSBD evice:
p u b lic T Subject
{ / / k o n k r e t n y m pr zedmi ot em j e s t / / u r z ą d z e n i e USB
p riv a te : in t
report;
p u b lic : T U S B D e v ice (); v o id re a d R e p o rt(); in t
getReport();
// o dc z yt uj e raport / / z w r a c a wy n i k od c z y t u
};
/ / ----------------------------------------------------------------------------------------------------------------T U S B D e v i c e : : T U S B D e v i c e ( ) : r e p o r t ( 0 ) {} / / ----------------------------------------------------------------------------------------------------------------v o id T U S B D e v ice ::re a d R e p o rt()
{
report+ +; setChange(); }
/ / ----------------------------------------------------------------------------------------------------------------i n t T U S B D e v ice ::g e tR e p o rt() return
rep ort;
{
/ / u r z ą d z e n i e zwr aca r a p o r t
}
/ / ----------------------------------------------------------------------------------------------------------------cla ss
TR esu ltR ead R ep ort:
p u b lic
IO bserver
{ / / w y n i k odczytu j a k o / / k o n k r e t n y obserwator
p riv a te : v o id
upd ate(T Subject* );
};
/ / ----------------------------------------------------------------------------------------------------------------v o i d T R e s u l t R e a d R e p o r t : : u p d a t e ( T S u b j e c t *o)
{
/ / p o l i m o r f i c z n e r z u t o w a n i e czasu wykonani a T U S B D e v i c e *m = d y n a m i c _ c a s t < T U S B D e v i c e * > ( o ) ; if
(m)
{
c o u t << "R = " << m - > g e t R e p o r t ( ) ; } }
/ / ----------------------------------------------------------------------------------------------------------------TUSBD evice * u s b D e v ic e ; IO bserver * re su ltR e a d R e p o rt;
/ / -----------------------------------------------------------------------------------------------------------------
345
346
USB. Praktyczne programowanie z Windows API w C++
i n t m ain () {
/ / k o n k r e t n y p r z e d mi o t u s b D e v i c e = new T U S B D e v i c e ( ) ;
/ / k o n k r e t n y o b s e r wa t o r r e s u l t R e a d R e p o r t = new T R e s u l t R e a d R e p o r t ( ) ;
//dołączenie
(zarejestrowanie)
k o n k r et n ego ob s er wat o r a
u sb D e v ic e -> a tta c h (re su ltR e a d R e p o rt); fo r ( in t
i
= 0;
i < 10;
i++)
{
u sb D evice-> read R ep o rt();
/ /powiadamia o odczycie u sb D evice-> noti f y ( ) ; c o u t << e n d l ; }
//usunięcie
( w y r e j e s t r o w a n i e ) k o n k r e t n e g o obs e r wa t o r a
u sb D e v ic e -> d e ta c h (re s u ltR e a d R e p o rt); d e le te
u sb D e vice ;
d e le te
re s u ltR e a d R e p o rt;
system ("PAUSE"); return
0;
}
/ / ----------------------------------------------------------------------------------------------------
Zmodyfikuj powyższy kod w taki sposób, aby realizował transmisję danych z zewnętrz nym urządzeniem USB.
Ćwiczenie 8.4 Załóżmy, że w systemie występują klasy odpowiedzialne za transmisję danych w try bach asynchronicznym (TAsynchReadWrite) i synchronicznym (TSynchReadWrite), które realizują wspólny interfejs IUSBDevice, oraz klasa m odelująca konkretne urządzenie wykonawcze TUSBDevice, tak jak pokazano na diagramie z rysunku 8.15. Rysunek 8.15. Delegowanie interfejsu
TA synchReadW rite +readUSBData(): void +writeUSBData(): void «create»+T A synchR eadW rite() « d e stroy > > +TAsy nch Re adWrite ()
.............-[> p.
« in te rfa c e » lUSBDevice ± re ad JS B D ata (): void +iv/T/eL/SBDafai9. void
« d es tro y » + l USBDevic e()
7 T T S y n c h R e a d W rite
+readUSBData(): void +writeUSBData(): void <
TUSBDevice -ptrlUSBDevice: lUSBDevice « e r e ate » +TU SBDevic e() « d e stroy > >+TU SBDevic e() +readUSBData(): void +writeUSBData(): void +de Iegati onToTSync hRe adWrite (): void +de Iegati onToTAsync hRe adWrite (): void
W klasie TUSBDevice zdefiniowano operacje delegationToTAsynchReadWrite() oraz delegationToTSynchReadWrite(). Ich wywołanie określa obiekty klas, do których obiekt
Rozdział 8. ♦ Programowanie obiektowe transmisji USB
347
klasy TUSBDevice będzie delegował dalsze wywołania. W efekcie TAsynchReadWrite oraz TSynchReadWrite będą spełniać rolę superklas klasy TUSBDevice. Zrealizowanie tego m echanizm u poprzez wspólny interfejs daje pewność, że wywoływane metody zostały prawidłowo zaimplementowane w każdej z klas. Łańcuch kolejnych delegacji poprzez wspólny interfejs może mieć dow olną długość. Wystarczy, aby obiekt klasy, który ma być delegowany, miał interfejs w swojej ścieżce realizacji, tak jak pokazano na rysunku 8.15 oraz listingu 8.14. Delegowanie operacji interfejsu jest możliwe dzięki zagregowaniu interfejsu z klasą, w której są zaimplementowane operacje delegujące. Dzięki temu operacje delegujące z klasy TUSBDevice mają dostęp do wskaźników wska zujących inne obiekty w systemie, które realizują wspólny interfejs. Listing 8.14. Kod modułu usb_R8_C3.cpp # in c lu d e u s i n g n a m es p ac e s t d ; c la ss
IUSBD evice
{ //Interfejs
p u b lic : v irt u a l
v o id readUSBData()
v irt u a l
v o id w riteU S BD ata()
= 0;
v irt u a l
~ IU S B D e v ice ()
= 0;
{
co ut < < "W irtu a ln y d e s t r u k t o r in t e r f e j s u \ n " ; } };
//
--------------------------------------------------------------------
cla ss
TA syn ch Read W rite
: p u b lic
IUSBD evice
{
p u b lic : v o id readUSBData()
{
c o u t << " T A s y n c h R e a d W r i t e : : r e a d U S B D a t a ( ) \ n " ; } v o id w riteU S B D ata()
{
c o u t << " T A s y n c h R e a d W r i t e : : w r i t e U S B D a t a ( ) \ n " ; } TA syn c h R e a d W rite ()
{
c o u t << " K o n s t r u k t o r T A s y n c h R e a d W r i t e \ n " ; } ~ TA synchRead W rite()
{
c o u t << " D e s t r u k t o r T A s y n c h R e a d W r i t e \ n \ n " ; } }; / / -------------------------------------------------------------------------------------------------------cla ss
TSynchReadW rite
: p u b lic
IUSBDevice
{
p u b lic : v o id readUSBData()
{
c o u t << " T S y n c h R e a d W r i t e : : r e a d U S B D a t a ( ) \ n " ; } v o id w riteU S B D ata()
{
c o u t << " T S y n c h R e a d W r i t e : : w r i t e U S B D a t a ( ) \ n " ; } TSynchRe adW rite()
{
c o u t << " K o n s t r u k t o r T S y n c h R e a d W r i t e \ n " ; } ~ T S yn ch R ead W rite()
{
c o u t << " D e s t r u k t o r T S y n c h R e a d W r i t e \ n \ n " ;
}
348
USB. Praktyczne programowanie z Windows API w C++
};
//
--------------------------------------------------------------------
cla ss
TUSBD evice
: p u b lic
IUSBD evice
{
p u b lic :
/ / K o n s t r u k t o r TUSBDevi ce() wywoł uj e k o n s t r u k t o r TAsynchReadWri t e() TU S B D evice ()
: p trIU S B D e v ic e (
new T A s y n c h R e a d W r i t e ( )
)
{
c o u t << " K o n s t r u k t o r T U S B D e v i c e \ n " ; }
/ / D e s t r u k t o r k l a s y TUSBDevice ~ T U S B D e v ic e ( )
{ d e le te p trlU S B D e v ic e ;
v o id readUSBData() v o id w riteU S B D ata()
}
{ p trIU S B D e v ic e -> re a d U S B D a ta ();
}
{ p trIU S B D e v ic e -> w rite U S B D a ta ();
v o id d e le g a tio n T o T S y n ch R e a d W rite ()
}
{
d e le t e ptrIU S B D e v ic e ; p t r I U S B D e v i c e = new T S y n c h R e a d W r i t e ( ) ; } v o id d e le g a tio n T o T A s y n ch R e a d W rite ()
{
d e le t e ptrIU S B D e v ic e ; p t r I U S B D e v i c e = new T A s y n c h R e a d W r i t e ( ) ; } p riv a te :
/ / A g r e g a c j a i n t e r f e j s u IUSBDevi ce z k l a s ą TUSBDevice IUSBDevice* p tr IU S B D e v ic e ; };
//
--------------------------------------------------------------------
i n t m a in ()
{
/ / T w o r z o n y j e s t o b i e k t anoni mowy k l a s y TAsynchReadWri te, / / a n a s t ę p n i e kończona j e s t k o n s t r u k c j a nazwanego o b i e k t u / / u s b D e v i c e k l a s y TUSBDevice TUSBD evice u s b D e v ic e ;
//Wywoł ywane są met ody k l a s y TAsynchReadWri te usb D ev ice.read U S B D ata(); usbDevi c e .w r it e U S B D a t a ( ) ;
/ / Wywoł ywany j e s t d e s t r u k t o r k l a s y TAsynchReadWri te / / i t wor z ony j e s t o b i e k t anoni mowy k l a s y TSynchReadWri te usbDevi c e .d e le g a t io n T o T S y n c h R e a d W r it e ( ) ;
//Wywoł ywane są met ody k l a s y TSynchReadWri te u sb D ev ice.read U S B D ata(); usbDevi c e .w r i t e U S B D a t a ( ) ;
/ / Ł a ń c u c h d e l e g a c j i może być p o wt a r z a n y w i e l o k r o t n i e usbDevi c e .d e le g a t io n T o T A s y n c h R e a d W r it e ( ) ; u sb D ev ice.read U S B D ata(); u sb D e v ic e .w rite U S B D a ta (); c in .g e t(); return
0;
}
/ / --------------------------------------------------------------------------------------------------------
N a rysunku 8.16 pokazano diagram sekw encji odpow iadający im plem entacji kodu z listingu 8.14 i odzwierciedlający działanie programu po uruchomieniu. Diagram ten jednoznacznie wyjaśnia sposób korzystania z mechanizmów wielokrotnych delegacji obiektów realizujących wspólny interfejs. Z przebiegu diagramu sekwencji pokazane go na rysunku 8.16 z łatw ością odczytamy, że w trakcie delegowania je st tw orzona hierarchia obiektów, a nie klas, i hierarchia ta może zostać zmodyfikowana w dowol nym momencie działania programu.
Rozdział 8. ♦ Programowanie obiektowe transmisji USB
349
Rysunek 8.16. Diagram sekwencji odpowiadający implementacji kodu z listingu 8.14 Uzupełnij kod przedstawiony na listingu 8.14 w taki sposób, aby realizował transmisję danych z zewnętrznym urządzeniem USB.
350
USB. Praktyczne programowanie z Windows API w C++
Rozdział 9.
Wewnętrzne struktury danych Elementy opisu dostępnych w systemie urządzeń USB są często zamieszczane w je d nej przestrzeni nazw w polach odpowiednio konstruowanych struktur danych. Poniżej zamieszczono przykładową strukturę DEVICE_DATA: / / ---------------------------------------------------------------------------------t y p e d e f s t r u c t _DEVI CE_ DA TA { TCHAR
*Hardw areId;
TCHAR
*Path;
DWORD
D e vice In sta n ce ;
HANDLE
h id D e v ic e O b je c t;
II--BYTE
* in p u tR e p o rtB u ffe r;
USHORT
in p u tR e p o rtB y te L e n g th ;
II--BYTE
*outputR eportBuffer;
USHORT
outputR ep ortB yteLength ;
II--BYTE
* fea tu re R e p o rtB u ffe r;
USHORT I* *I
featureR ep ortB yteLength;
PHIDP_PREPARSED_DATA p r e p a r s e d D a t a ; HIDP_CAPS c a p a b i l i t i e s ; } DE VIC E_DATA,
*PDEVICE_DATA;
/ / -----------------------------------------------------------------------------------------------------------------
Pola tej struktury m ogą przechowywać podstawowe dane zawierające identyfikator sprzętu (HardwareId), ścieżkę dostępu do interfejsu urządzenia (Path) oraz DeviceIn stance, który będzie lokalizował element DevInst struktury USB_DEFINFO_DATA. Struk tury tego typu powinny również zawierać pola przechowujące wskaźniki do buforów danych wejściowych, wyjściowych i konfiguracyjnych wraz z ich rozmiarami. Łatwo zauważyć, że zawartość DEVICE_DATA pozwala dokładniej zidentyfikować i opisać pod stawowe i ogólne parametry urządzenia dzięki zagregowaniu struktur PHIDP_PREPARSED_ DATA oraz HIDP CAPS.
352
USB. Praktyczne programowanie z Windows API w C++
W tym rozdziale prześledzimy możliwości ewolucji programu wykorzystującego w e wnętrzną strukturę danych w celu enkapsulacji informacji o urządzeniu HID USB.
Program proceduralny K od program u proceduralnego je st podzielony na fragmenty (funkcje), wykonujące ściśle określone działania. W miarę możliwości funkcje nie powinny nadużywać zmien nych globalnych, lecz pobierać i przekazywać dane (lub wskaźniki do nich) jako para metry wywołania. Na rysunku 9.1 przedstawiono statyczny diagram zawierający struk turę DEVICE_DATA przechow ującą w swoich polach podstawowe informacje na temat sprzętu. Już pobieżna analiza tego rysunku sugeruje, że użytkownik będzie identyfiko wał sprzęt poprzez numer indeksu interfejsu urządzenia. Jeżeli ktoś zechciałby zasto sować sposób identyfikacji urządzenia oparty na jego aktualnych wartościach VID, PID lub nazwie producenta, powinien uzupełnić ten diagram o agregującą z DEVICE_DATA strukturę HIDD_ATTRIBUTES (patrz treść ćwiczenia 9.1).
Rysunek 9.1. Statyczny diagram programu proceduralnego N a listingu 9.1 zaprezentowano kod odpowiadający diagramowi z rysunku 9.1. Warto w tym miejscu zauważyć, że na diagramie 9.1 nie uwzględniono zależności klienta od struktur SP_DEVICE_INTERFACE_DETAIL_DATA, SP_DEVICE_INTERFACE_DATA, SP_DEVINFO_ DATA oraz GUID z tej przyczyny, że wym ienione struktury są wewnętrzne względem jednej z funkcji programu. Listing 9.1. Kod modułu usb_R9_1.cpp # i n c l u d e #pragma o p t i o n
p u s h -a 1
# in c lu d e < s e tu p a p i> #pragma o p t i o n
pop
# in c lu d e
Rozdział 9. ♦ Wewnętrzne struktury danych
# in c lu d e u s i n g n a m es p ac e s t d ; v o id d is p la y E r r o r ( c o n s t
c h a r * msg){
c o u t << msg << e n d l ; system ("PAUSE"); e x it(0 ); };
I I -----------------------------------------------------------------------------------------------------t e m p l a t e < c l a s s T> in lin e
v o i d r e l e a s e M e m o r y ( T &x)
{ assert(x de le te
!= N U L L ) ;
[]
x;
x = NULL; }
I I -----------------------------------------------------------------------------------------------------t y p e d e f USHORT USAGE, *PUSAGE; t y p e d e f s t r u c t _HIDP_PREPARSED_DATA *PHIDP_PREPARSED_DATA;
I I -----------------------------------------------------------------------------------------------------t y p e d e f s t r u c t _ HI D P_C A PS { USAGE U sa g e ; USAGE U s a g e P a g e ; USHORT I n p u t R e p o r t B y t e L e n g t h ; USHORT O u t p u t R e p o r t B y t e L e n g t h ; USHORT F e a t u r e R e p o r t B y t e L e n g t h ; USHORT R e s e r v e d [ 1 7 ] ; USHORT N u m b e r L i n k C o l l e c t i o n N o d e s ; USHORT N u m b e r I n p u t B u t t o n C a p s ; USHORT N u m b e r I n p u t V a l u e C a p s ; USHORT N u m b e r I n p u t D a t a I n d i c e s ; USHORT N u m b e r O u t p u t B u t t o n C a p s ; USHORT N u m b e r O u t p u t V a l u e C a p s ; USHORT N u m b e r O u t p u t D a t a I n d i c e s ; USHORT N u m b e r F e a t u r e B u t t o n C a p s ; USHORT N u m b e r F e a t u r e V a l u e C a p s ; USHORT N u m b e r F e a t u r e D a t a I n d i c e s ; } HID P_ CA PS ,
* PH ID P_ CA PS;
I I -----------------------------------------------------------------------------------------------------t y p e d e f s t r u c t _DEVI CE_ DA TA { TCHAR
*Hardw areId;
TCHAR
*Path;
DWORD
D e vice In sta n ce ;
HANDLE
h id D e v ic e O b je c t;
BYTE
* in p u tR e p o rtB u ffe r;
USHORT
in p u tR e p o rtB y te L e n g th ;
BYTE
*outputR eportBuffer;
USHORT
outputR ep ortB yteLength;
BYTE
* fea tu re R e p o rtB u ffe r;
USHORT
I*
featureR ep ortB yteLength;
*I
PHIDP_PREPARSED_DATA p r e p a r s e d D a t a ; HIDP_CAPS c a p a b i l i t i e s ; } DE VIC E_DATA,
*PDEVICE_DATA;
I I -----------------------------------------------------------------------------------------------------PDEVICE_DATA d e v i c e L i s t ;
I I ------------------------------------------------------------------------------------------------------
353
35 4
USB. Praktyczne programowanie z Windows API w C++
BOOL g e t H i d D e v i c e C a p a b i l i t i e s ( U I N T m em be rI n de x) { HMODULE h H i d L i b ; bool
statu s;
lo n g
(
s td c a ll*
H id P _G e tC a p s)(IN
PHIDP_PREPARSED_DATA P r e p a r s e d D a t a ,
OUT PHIDP_CAPS C a p a b i l i t i e s ) ; bool
(
s td c a ll*
H i d D _ G e t P r e p a r s e d D a t a ) ( I N HANDLE
H id D e v ice O b je c t,
OUT PHIDP_PREPARSED_DATA * P r e p a r s e d D a t a ) ; bool
(
s td c a ll*
H id D _ F r e e P re p a rs e d D a ta ) (IN PHIDP_PREPARSED_DATA P r e p a r s e d D a t a ) ;
h H id L ib = L o a d L ib ra ry ("C :\\W in d o w s \\S yS W O W 6 4 \\H ID .D L L "); if
(!h H id L ib ) d is p la y E rro r("B łą d
d o łą c ze n ia b i b lio t e k i
H ID .D LL.");
(FARPROC&)
H id P _ G e tC a p s = G e tP ro c A d d re s s (h H id L ib ,
(FARPROC&)
H id D _ G e tP re p a rs e d D a ta = G e tP ro cA d d re s s(h H id L ib ,
(FARPROC&)
H id D _ F re e P re p a rs e d D a ta = G e tP ro cA d d re s s(h H id L ib ,
"Hi dP_ G etC a p s"); "H id D _ G e tP re p a rse d D a ta "); "H id D _ Fre e P re p a rse d D a ta "); if
(!H id P _G e tC a p s
||
!H id D _ G e tP re p a rse dD a ta
||
!H id D _ F re e P re p a rse d D a ta ){
F re e L ib ra ry ( h H id L ib ) ; d is p la y E rro r("N ie
z n a l e z i o n o żadnych f u n k c j i
e k sp orto w ych.\n");
} sta tu s= H id D _ G e tP re p a rse d D a ta (d e v ice L ist[m e m b e rIn d e x ].h id D e v ic e O b je c t, & d e v ice L is t-> p re p a rs e d D a ta ); if( s ta tu s ) { H id P _ G e tC a p s (d e v ic e L is t-> p re p a rs e d D a ta ,
& d e v ic e L is t-> c a p a b ilitie s );
d e v ic e L is t[m e m b e rI n d e x ].in p u tR e p o rtB y te L e n g th = d e v ic e L is t-> c a p a b ilitie s .In p u tR e p o rtB y te L e n g th ; d e v ic e L is t[m e m b e rIn d e x ].o u tp u tR e p o rtB y te L e n g th = d e v ic e L is t-> c a p a b ilitie s .O u tp u tR e p o r tB y te L e n g th ; d e v ice L ist[m e m b e rIn d e x].fe a tu re R e p o rtB y te L e n g th
=
d e v ic e L is t-> c a p a b ilitie s .F e a tu re R e p o rtB y te L e n g th ; H id D _ F r e e P re p a rs e d D a ta (d e v ic e L is t- > p re p a r s e d D a ta ); } F re e L ib ra ry ( h H id L ib ) ; return
statu s;
}
/ / -------------------------------------------------------------------------------------------------------UINT s e t G e t H i d D e v i c e D a t a ( ) { DWORD p r o p e r t y B u f f e r S i z e = 0; c h a r * p r o p e r t y B u f f e r = NULL; HMODULE h H i d L i b ; SP_DEVINFO_DATA d e v i c e I n f o D a t a ; HDEVINFO d e v i c e I n f o S e t ; SP _INT E RF AC E_ DE V IC E_ DA TA d e v i c e I n t e r f a c e D a t a ; DWORD
m em b e rI n d e x = 0;
GUID c l a s s G u i d ; P S P_ DE V IC E_ IN TE RF AC E_ DE TA IL _ DA TA d e v i c e I n t e r f a c e D e t a i l D a t a = NULL; DWORD r e q u i r e d S i z e = 0; DWORD d e v i c e I n t e r f a c e D e t a i l D a t a S i z e = 0;
Rozdział 9. ♦ Wewnętrzne struktury danych
/ / maksymal na l i c z b a i n t e r f e j s ó w ur ządzeń
DWORD s e a r c h M a x D e v i c e = 100; bool
do n e = f a l s e ;
v o id
(
s td c a ll
355
* H id D _ G e tH id G u id )(O U T
LPGUID H i d G u i d ) ;
h H id L ib = L o a d L ib ra ry ("C :\\W in d o w s \\S yS W O W 6 4 \\H ID .D L L "); if
(Ih H id L ib ) d is p la y E rro r("B łą d
(FARPROC&)
d o łą c ze n ia b ib lio t e k i
H ID .D LL.");
H id D _ G e tH id G u id = G e t P r o c A d d r e s s ( h H id L ib , "H id D _ G e tH id G u id ");
if
( ! H id D _ G e t H i d G u i d ) { F re e L ib ra ry ( h H id L ib ) ; d is p la y E rr o r(" N ie
zn a le zio n o
id e n ty fik a to r a
G U ID .");
} H id D _ G e tH id G u id
(& cla ssG u i d);
d e v ic e I n fo S e t = S e tu p D iG e tC la s s D e v s (& c la s s G u id ,
NU LL,
(DIGCF_PRESENT
NULL,
| DIGCF_DEVICEINTERFACE));
d e v i c e I n t e r f a c e D a t a . c b S i z e = s izeo f(SP_IN TE R F A C E_D EV IC E_D A TA ); w h ile (!d o n e )
{
d e v ic e L is t
=
new \ D E V IC E _ D A T A [((m em berIn d ex+ 1)*sizeof(D E V IC E _D A T A )) ] ;
fo r(;
m em b e rI n d e x < s e a r c h M a x D e v i c e ; memberIndex++)
{
if(S e tu p D iE n u m D e v i c e I n t e r f a c e s ( d e v i c e I n f o S e t , 0 , & c la s s G u i d , m em berIndex,&devi c e I n t e r f a c e D a t a ) )
{
S e tu p D iG e t D e v ic e I n te rfa c e D e t a il(d e v ic e In fo S e t,& d e v ic e In te rfa c e D a ta , N U L L ,0 ,& d e v ic e In te rfa c e D e ta ilD a ta S ize , N U LL ) ; re q u ire d S iz e = d e v ic e I n te rfa c e D e ta ilD a ta S iz e ; d e v i c e I n t e r f a c e D e t a i l D a t a = ( P S P _ D E V I C E _ IN T E R F A C E _ D E T A IL _ D A T A ) \ new D W O R D [ d e v i c e I n t e r f a c e D e t a i l D a t a S i z e ] ; if( d e v ic e I n t e rfa c e D e t a ilD a t a )
{
devi c e In te rfa c e D e ta ilD a ta -> c b S iz e = \ sizeo f(SP_IN TE RF ACE_D EV ICE_D ETAIL_D AT A); } e lse
{
S etu p D iD e stro y D e v i c e I n f o L i s t ( d e v i c e I n fo S e t) ; releaseM em ory(d evi c e I n t e r f a c e D e t a ilD a t a ) ; return
0;
} d e v i c e I n f o D a t a . c b S i z e = size o f(S P _ D E V IN F O _ D A T A ); if
( !S e tu p D iG e tD e v ic e In te rfa c e D e ta il(d e v ic e In fo S e t, & d e v ice In te rface D a ta ,
d e v ic e In te rfa c e D e ta ilD a ta ,
d e v ic e In te rfa c e D e ta ilD a ta S iz e , & re q u ire d S iz e ,
& d eviceIn fo D ata))
{
S etu p D iD e stro y D e v i c e I n f o L i s t ( d e v i c e I n fo S e t) ; releaseM em ory(d evi c e I n t e r f a c e D e t a ilD a t a ) ; return
0;
}
size_t nLen = strlen(deviceInterfaceDetailData->DevicePath)+1;
356
USB. Praktyczne programowanie z Windows API w C++
d e v i c e L i s t [ m e m b e r I n d e x ] . P a t h = new T C H A R [ ( n L e n * s i z e o f ( T C H A R ) ) ] ; s trn c p y (d e v ic e L ist[m e m b e rIn d e x ] .P a th , d e v ice In te rfa ce D e ta ilD a ta -> D e v ic e P a th , c o u t < < "\n D e v ic e L ist["< < m e m b e rIn d e x < < "].P a th :
nLen);
\ n " <<
d e v i c e L i s t [ m e m b e r I n d e x ] . P a t h << e n d l ; d e v ic e L is t[m e m b e rIn d e x ].D e v ic e In s ta n c e = d e v ic e In fo D a ta .D e v In s t; S e tu p D iG e tD e v ic e R e g is try P ro p e rty (d e v i c e In fo S e t,
& d eviceIn fo D ata,
SPDRP_HARDWAREID, NU LL,
NU LL,
0,
& p ro p e rty B u ffe r S ize );
/ /alokowanie pamięci dla bufora danych p r o p e r t y B u f f e r = new c h a r [ ( p r o p e r t y B u f f e r S i z e * s i z e o f ( T C H A R ) ) ] ; S e tu p D iG e tD e v ic e R e g is try P ro p e rty (d e v i c e In fo S e t,
& d eviceIn fo D ata,
SPDRP_HARDWAREID,NULL, p ropertyB u ffe r,
p ro p e rty B u ffe rS iz e ,
N ULL); d e v ice L is t[m e m b e rIn d e x ].H a rd w a re Id
= p ro pertyB u ffe r;
c o u t < < "\n D e vic e Lis t["< < m e m b e rIn d e x< < "].H a rd w a re Id :
\ n " <<
d e v i c e L i s t [ m e m b e r I n d e x ] . H a r d w a r e I d << e n d l ; } e ls e { i f(ERROR_NO_MORE_ITEMS == G e t L a s t E r r o r ( ) ) { do ne = TRUE; break; } } releaseM em ory(d evi c e I n t e r f a c e D e t a i lD a t a ) ; re le a se M e m o ry(p ro p e rty B u ffe r);
//releaseMemory(deviceList[memberIndex].Path); }
//releaseMemory(deviceList); } S etu p D iD e stro y D e v i c e I n fo L i s t ( d e v i c e I n f o S e t ) ; F re e L ib ra ry ( h H id L ib ) ; r e t u r n m em b e rI n d e x; }
/ / ---------------------------------------------------------------------------HANDLE o p e n H i d U S B D e v i c e ( U I N T m em be rI n de x) { d e v i c e L i s t [ m e m b e r I n d e x ] . h i d D e v i c e O b j e c t == IN VAL ID_ HANDLE_ VALUE; d e v ic e L is t[m e m b e rIn d e x ].h id D e vic e O b je c t = C r e a t e F ile ( d e v i ce List[m e m b e rInd e x].P a th , GENERIC_READ | GENERIC_WRITE, FILE_SHA RE _RE AD
| FILE_ SH ARE _WR ITE,
N ULL,OPEN_EXISTING,FILE_FLAG_OVERLAPPED,NULL); if(d e v ic e L ist[m e m b e rIn d e x ].h id D e v ic e O b je c t return e lse return
}
!= INVALID_HANDLE_VALUE)
d e v ic e L is t[m e m b e rIn d e x ].h id D e vic e O b je c t; IN VALID_HANDLE_VALUE;
Rozdział 9. ♦ Wewnętrzne struktury danych
357
/ / -------------------------------------------------------------------------------------------------------BOOL c l o s e H i d U S B D e v i c e ( H A N D L E d e v H a n d l e ) { i f ( ( d e v H a n d l e == 0)
||
( d e v H a n d l e == INVA L ID _ HA ND L E_ VA L U E) ){ re turn
fa lse ;
} e lse re turn }
//
C lo se H a n d le (d e v H a n d le );
--------------------------------------------------------------------
BOOL r e a d U S B R e p o r t ( U I N T m em be rI n de x) { DWORD r e s u l t
= 0;
DWORD n u m b e r O f B y t e s R e a d = 0; OVERLAPPED * o v e r l a p p e d = NULL; i f ( o v e r l a p p e d == NULL) { o v e r l a p p e d = new OVERLAPPED; overlappe d-> hEvent = C reateEvent(N U LL,
TRUE, TRUE,
"");
o v e r l a p p e d - > O f f s e t = 0; ov e rla p p e d -> O ffs e tH ig h
= 0;
} if( !R e a d F ile ( d e v ic e L is t[ m e m b e rI n d e x ] .h id D e v ic e O b je c t, d e v ic e L is t[m e m b e rIn d e x ].in p u tR e p o rtB u ffe r, d e v ic e L ist[m e m b e rIn d e x].in p u tR e p o rtB y te L e n g th , &num berO fBytesRead, if(G e tL a s tE rro r() re s u lt
ove rla p p e d ))
== ERROR_IO_PENDING)
{
{
= W a itF o rS in g le O b je c t(o ve rla p p e d -> h E ve n t,
i f ( re s u lt
== WAIT_TIMEOUT)
100);
{
C a n c e lI o (d e v i c e L ist[m e m b e rIn d e x ].h id D e v i c e O b j e c t ) ; return
fa lse ;
} e lse if( r e s u lt
== W A I T _ F A I L E D ) {
d is p la y E rro r("B ią d return
odczytu d a n y ch .");
fa lse ;
} G e tO v e rla p p e d R e su lt(d e v ic e L is t[m e m b e rIn d e x ].h id D e v ic e O b je c t, ove rla p p e d ,
& num berO fBytesRead,
FALSE);
} e lse d is p la y E rro r("B ią d
odczytu d a n y ch .");
} R e se tE v e n t(o v e rla p p e d -> h E v e n t); i f ( n u m b e r O f B y t e s R e a d == d e v i c e L i s t [ m e m b e r I n d e x ] . i n p u t R e p o r t B y t e L e n g t h ) { fo r ( U S H O R T i= 0 ; p rin tf("% d
i< d e v i c e L i s t [ m e m b e r I n d e x ] . i n p u t R e p o r t B y t e L e n g t h ;
",
d e v i c e L is t [ m e m b e r I n d e x ] . i n p u t R e p o r t B u f f e r [ i] );
p r in tf( " \ n " ) ; } e ls e
{
p r in tf(" B ię d n a
lic z b a
odebranych b a jtó w .\n ",n u m b e rO fB y te s R e a d );
} d e le te overlappe d; return
true;
}
/ / -------------------------------------------------------------------in t m a in ( ) {
i++)
358
USB. Praktyczne programowanie z Windows API w C++
UINT i n t e r f a c e I n d e x ; //numer
interfejsu urządzenia HID
//enumeracja aktualnie podłączonych urządzeń HID setG etH idD e vice D a ta (); co ut < < "\n\n\Podaj
nr is tn ie ją c e g o
in te rfe js u
u r z ą d z e n i a = ";
c i n >> i n t e r f a c e I n d e x ; cout < < "\ n D e v ic e L is t["< < in te rfa c e I n d e x < < "].P a th : d e v ic e L is t[in te rfa c e In d e x ].P a th
\ n " << << e n d l ;
//otwarcie portu USB o p e n H id U S B D e v ic e (in te rfa c e In d e x );
//odczyt właściwości urządzenia if
(g e tH id D e v ic e C a p a b ilitie s (in te rfa c e In d e x ))
{
//alokacja pamięci dla bufora wejściowego d e v ic e L is t[in te rfa c e In d e x ].in p u tR e p o rtB u ffe r = \ new B Y T E [ d e v i c e L i s t [ i n t e r f a c e I n d e x ] . i n p u t R e p o r t B y t e L e n g t h ] ; w h ile ( tru e )
{
//cykliczny odczyt raportów z wybranego urządzenia
re a d U S B R e p o rt(in terfaceIn d ex ); i f ( d e v i c e L i s t [ i n t e r f a c e I n d e x ] . i n p u t R e p o r t B u f f e r [ 6 ] ==64)
{
clo s e H id U S B D e v ic e (d e v ic e L is t[in te rfa c e In d e x ].h id D e v ic e O b je c t); break; } }
//zwolnienie pamięci dla bufora wejściowego re le a s e M e m o ry ( d e v ic e L is t[ in te rfa c e In d e x ] .in p u tR e p o rtB u ffe r) ; }
//zwolnienie pamięci dla elementów struktury DEVICE_DATA //releaseMemory(deviceList[interfaceIndex].Path); re le a s e M e m o ry (d e v ic e L ist); c o u t << e n d l ; system ("PAUSE"); return
0;
}
/ / --------------------------------------------------------------------
Klient w funkcji main() w programie głównym najpierw enumeruje aktualnie dołączo ne do komputera urządzenia klasy HID. Każdem u ze znalezionych interfejsów urzą dzenia zostaje przypisany unikatowy indeks (numer interfejsu). Następnie na podsta wie tego numeru zostaje wyw ołana funkcja otwierająca (odblokowująca) urządzenia do transmisji danych. W przypadku gdy klient prawidłowo uzyska dostęp do sterowni ka urządzenia, przystępuje do pobrania podstawowych parametrów eksploatacyjnych urządzenia, wywołując funkcję getH idD eviceC apabilities(). Po odczytaniu interesu jących go informacji klient wywołuje funkcję czytającą raporty wejściowe z urządze nia. N a koniec wszystkie zaalokowane przez program zasoby w miarę możliwości po winny zostać prawidłowo zwolnione. W trakcie analizy i testowania powyższego kodu zauważymy, że zewnętrzne urządze nie USB je st rzeczywiście rozpoznawane po numerze jego interfejsu, tak jak pokaza no na rysunku 9.2.
Rozdział 9. ♦ Wewnętrzne struktury danych
359
USB,Praktyczne programowanie\Rozdział 9\R9_l\proj_USB_R9_l.exe D e vi c e L i s t [ O ]. P a t h : \\?\h i d#vi d_09d a&pi d_000a#7&3 394 5 b6840&OOOC>#{4 d le 5 5 b2 - f 1 6 f - l l c f - 8 8 cb - 001111D0003 0} D e vi c e L i s t [ O ]. H a rd w a re ld : HID\VID_09DA&PID_OOOA&REV_OOD2 D e vi c e L i s t [ 1 ] . P a t h : \ \ ? \ h id#vi d_22ba£pi d_0108#8&5 9 f 5 d85&0&0000#{4dle5 5 b 2 - fl6 f- llc f- 8 8 c b - 0 0 1 1 1 1 0 0 0 0 3 0 } Devi c e L i s t [ 1 ] . H a rd w a re ld : HID\VID_22 E3A&PID_0108&REV_0313 D e vi c e L i s t [ 2 ] . P a t h : \ \ ? \ h i d # v i d _ f f f f & p i d_8081fimi_00#8&245 0d30e&0fit0000#{4dle5 5 b 2 - fl6 f- llc f- 8 8 c b - 0 0 1 1 1 1 0 0 0 0 3 0 } D e vi c e L i s t [ 2 ] . H a rd w a re ld : HID\VID_FFFF&PID_8081&REV_0106£W I_00 D e vi c e L i s t [ 3 ] . P a t h : \\?\h i d#vi d _ f f f f & p i d_8081&mi _01#8&112 0b6d0&0&0000#{4dle5 5 b 2 - f 1 6 f - l l c f - 88cb-001111000030} D e vi c e L i s t [ 3 ] . H a rd w a re ld : HI D\VID_FFFF&PI D_8081&REV_010&SM I_01
Po d a j n r i s t n i e j ą c e g o i n t e r f e j s u
u r z ą d z e n ia = 1
D e vi c e L i s t [ 1 ] . P a t h : \ \ ? \ h i d#vi d_22bafipi d_0108#8&5 9 f 5 d85S!0&0000#{4dle5 5 b2 - f 1 6 f - l l c f - 88cb-001 11100003 0} 0 129 127 128 128 0 0 0 128 127 128 128 0 0 0 128 127 128 128 0 64 A by ko ntynu ow ać, n a c i ś n i j
dow oln y k la w is z .
.
. _
Rysunek 9.2. Aplikacja proj_USB_R9_1 w trakcie działania
Program obiektowy Program owanie obiektowe je st m etodą tw orzenia program ów za pom ocą obiektów, czyli elementów łączących stan (dane posiadające wartości) i zachowanie (operacje określane też jako funkcje składowe klasy). Obiektowy program kom puterowy je st wyrażony jako zbiór takich obiektów komunikujących się między sobą w celu wyko nywania określonych zadań. W odróżnieniu od programu zorientowanego obiektowo, program obiektowy może się posługiwać tylko jednym obiektem stworzonym na ba zie tylko jednej klasy. Warto zauważyć, że na bazie każdego programu proceduralnego można stworzyć program obiektowy. N a rysunku 9.3 zaprezentowano statyczny dia gram będący obiektową w ersją programu proceduralnego z listingu 9.1, który uzupeł niono o klasę TUSBDevice, której jed en z atrybutów wskazuje strukturę DEVICE_DATA. Struktura ta jest inicjowana w konstruktorze klasy. Pozostałe funkcje składowe klasy TUSBDevice charakteryzują się identyczną budow ą ja k w przypadku programu proce duralnego. Główna różnica między listingami 9.1 i 9.2 polega na tym, że obecnie ca łość warstwy implementacji algorytmu zamieszczono w klasie TUSBDevice. N a listingu 9.2 zaprezentowano przykład implementacji diagramu z rysunku 9.3, a na rysunku 9.4 — wynik działania programu. Listing 9.2. Kod modułu usb_R9_2.cpp # i n c l u d e #pragma o p t i o n
p u s h -a 1
# in c lu d e < s e tu p a p i> #pragma o p t i o n
pop
# in c lu d e
360
USB. Praktyczne programowanie z Windows API w C++
Rysunek 9.3. Struktura logiczna programu obiektowego posługującego się strukturą DEVICE DATA # in c lu d e # d e fin e sea rch M a xD e vices
10 // maksymal na l i c z b a
i n t e r f e j s ó w ur ządzeń
u s i n g n a m es p a ce s t d ;
//
--------------------------------------------------------------------
t e m p l a t e < c l a s s T> in lin e
v o i d r e l e a s e M e m o r y ( T &x)
{ assert(x de le te
[]
!= N U L L ) ; x;
x = NULL; }
//
--------------------------------------------------------------------
t y p e d e f USHORT USAGE, *PUSAGE; t y p e d e f s t r u c t _HIDP_PREPARSED_DATA *PHIDP_PREPARSED_DATA;
//
--------------------------------------------------------------------
t y p e d e f s t r u c t _ HI D P_C A PS { USAGE U sa g e ; USAGE U s a g e P a g e ; USHORT I n p u t R e p o r t B y t e L e n g t h ; USHORT O u t p u t R e p o r t B y t e L e n g t h ; USHORT F e a t u r e R e p o r t B y t e L e n g t h ; USHORT R e s e r v e d [ 1 7 ] ; USHORT N u m b e r L i n k C o l l e c t i o n N o d e s ; USHORT N u m b e r I n p u t B u t t o n C a p s ; USHORT N u m b e r I n p u t V a l u e C a p s ; USHORT N u m b e r I n p u t D a t a I n d i c e s ; USHORT N u m b e r O u t p u t B u t t o n C a p s ; USHORT N u m b e r O u t p u t V a l u e C a p s ; USHORT N u m b e r O u t p u t D a t a I n d i c e s ; USHORT N u m b e r F e a t u r e B u t t o n C a p s ;
Rozdział 9. ♦ Wewnętrzne struktury danych
361
USHORT N u m b e r F e a t u r e V a l u e C a p s ; USHORT N u m b e r F e a t u r e D a t a I n d i c e s ; } HID P_ CA PS ,
//
* PH ID P_ CA PS;
--------------------------------------------------------------------
t y p e d e f s t r u c t _DEVI CE_ DA TA { TCHAR
*Hardw areId;
TCHAR
*Path;
DWORD
D e vice In sta n ce ;
HANDLE
h id D e v ic e O b je c t;
BYTE
* in p u tR e p o rtB u ffe r;
USHORT
/*
in p u tR e p o rtB y te L e n g th ;
*/
USHORT
outputR ep ortB yteLength;
USHORT
featureR ep ortB yteLength;
/*
*/
PHIDP_PREPARSED_DATA p r e p a r s e d D a t a ; HIDP_CAPS c a p a b i l i t i e s ; } DE VIC E_DATA,
//
*PDEVICE_DATA;
--------------------------------------------------------------------
cla ss
TUSBD evice {
p riv a te : UINT fM e m b e r I n d e x ; v o id d is p la y E r r o r ( c o n s t
c h a r * m sg ) ;
p u b lic : PDEVICE_DATA d e v i c e L i s t ; BOOL g e t H i d D e v i c e C a p a b i l i t i e s ( U I N T m e m b e r I n d e x ) ; UINT s e t G e t H i d D e v i c e D a t a ( ) ; HANDLE o p e n H i d U S B D e v i c e ( U I N T m e m b e r I n d e x ) ; BOOL r e a d U S B R e p o r t ( U I N T m e m b e r I n d e x ) ; BOOL c l o s e H i d U S B D e v i c e ( H A N D L E d e v H a n d l e ) ; TU SBD evice(UINT m em berIndex); -TUSBDevi c e ( ) ;
//
--------------------------------------------------------------------
T U S B D e v i c e : : T U S B D e v i c e ( U I N T m em be rI nd e x) fM e m b e r I n d e x = m em b e rI n d e x; d e v i c e L i s t = new \ D E VIC E _ D A T A [((fM em b erIn d ex+ 1 )* sizeo f(D E VIC E _D A T A))];
//
--------------------------------------------------------------------
T U S B D e v ice ::-T U S B D e v ice () re le a s e M e m o ry (d e v ic e L ist); system ("PAUSE");
//
--------------------------------------------------------------------
v o id T U S B D e v ic e ::d is p la y E rro r( c o n s t
c h a r * msg)
c o u t << msg << e n d l ; system ("PAUSE"); e x it(0 );
//
--------------------------------------------------------------------
BOOL TU SBD ev i c e : : g e t H i d D e v i c e C a p a b i l i t i e s ( U I N T m em be rI n de x) HMODULE h H i d L i b ; bool
statu s;
3e2
USB. Praktyczne programowanie z Windows API w C++
lo n g
(
s td c a ll*
H id P _G e tC a p s)(IN
PHIDP_PREPARSED_DATA P r e p a r s e d D a t a ,
OUT PHIDP_CAPS C a p a b i l i t i e s ) ; bool
(
s td c a ll*
H i d D _ G e t P r e p a r s e d D a t a ) ( I N HANDLE
H id D e v ice O b je c t,
OUT PHIDP_PREPARSED_DATA * P r e p a r s e d D a t a ) ; bool
(
s td c a ll*
H id D _ F r e e P re p a rs e d D a ta ) (IN PHIDP_PREPARSED_DATA P r e p a r s e d D a t a ) ;
h H id L ib = L o a d L ib ra ry ("C :\\W in d o w s \\S yS W O W 6 4 \\H ID .D L L "); if
(!h H id L ib ) d is p la y E rro r("B łą d
d o łą c ze n ia b i b lio t e k i
H ID .D LL.");
(FARPROC&)
H id P _ G e tC a p s = G e tP ro cA d d re s s (h H id L ib ,
(FARPROC&)
H id D _ G e tP re p a rs e d D a ta = G e tP ro cA d d re s s(h H id L ib ,
(FARPROC&)
H id D _ F re e P re p a rs e d D a ta = G e tP ro cA d d re s s(h H id L ib ,
"H idP_ G etC aps"); "H id D _ G e tP re p a rse d D a ta "); "H id D _ Fre e P re p a rse d D a ta "); if
(!H id P _G e tC a p s
||
!H id D _ G e tP re p a rse dD a ta
||
!H id D _ Fre eP rep a rse d D a ta ){
F re e L ib ra ry ( h H id L ib ) ; d is p la y E rro r("N ie
z n a l e z i o n o żadnych f u n k c j i
e k sp orto w ych.\n");
} sta tu s= H id D _ G e tP re p a rse d D a ta (d e v ice L ist[m e m b e rIn d e x ].h id D e v ic e O b je c t, & d e v ice L is t-> p re p a rs e d D a ta ); if( s ta tu s ) { H id P _ G e tC a p s (d e v ic e L is t-> p re p a rs e d D a ta ,
& d e v ic e L is t-> c a p a b ilitie s );
d e v ic e L is t[m e m b e rI n d e x ].in p u tR e p o rtB y te L e n g th = d e v ic e L is t-> c a p a b ilitie s .In p u tR e p o rtB y te L e n g th ; d e v ic e L is t[m e m b e rIn d e x ].o u tp u tR e p o rtB y te L e n g th = d e v ic e L is t-> c a p a b ilitie s .O u tp u tR e p o r tB y te L e n g th ; d e v ice L ist[m e m b e rIn d e x].fe a tu re R e p o rtB y te L e n g th
=
d e v ic e L is t-> c a p a b ilitie s .F e a tu re R e p o rtB y te L e n g th ; H id D _ F r e e P re p a rs e d D a ta (d e v ic e L is t- > p re p a r s e d D a ta ); } F re e L ib ra ry ( h H id L ib ) ; return }
//
statu s;
--------------------------------------------------------------------
UINT TU SBD ev i c e : : s e t G e t H i d D e v i c e D a t a ( ) { DWORD p r o p e r t y B u f f e r S i z e = 0; c h a r * p r o p e r t y B u f f e r = NULL; HMODULE h H i d L i b ; SP_DEVINFO_DATA d e v i c e I n f o D a t a ; HDEVINFO d e v i c e I n f o S e t ; SP _INT E RF AC E_ DE V IC E_ DA TA d e v i c e I n t e r f a c e D a t a ; fM e m b e r I n d e x = 0; GUID c l a s s G u i d ; P S P_ DE V IC E_ IN TE RF AC E_ DE TA IL _ DA TA d e v i c e I n t e r f a c e D e t a i l D a t a = NULL; DWORD r e q u i r e d S i z e = 0; DWORD d e v i c e I n t e r f a c e D e t a i l D a t a S i z e = 0; DWORD m a x D e v i c e = s e a r c h M a x D e v i c e s ; bool
do ne = f a l s e ;
v o id
(
s td c a ll
* H id D _ G e tH id G u id )(O U T
LPGUID H i d G u i d ) ;
h H id L ib = L o a d L ib ra ry ("C :\\W in d o w s \\S yS W O W 6 4 \\H ID .D L L ");
Rozdział 9. ♦ Wewnętrzne struktury danych
if
363
(!h H id L ib ) d is p la y E rro r("B łą d
(FARPROC&)
d o łą c ze n ia b i b lio t e k i
H ID .D LL.");
H id D _ G e tH id G u id = G e t P r o c A d d r e s s ( h H id L ib , "H id D _ G e tH id G u id ");
if
( ! H id D _ G e t H i d G u i d ) { F re e L ib ra ry ( h H id L ib ) ; d is p la y E rr o r(" N ie
zn a le zio n o
id e n ty fik a to r a
G U ID .");
} H id D _ G e tH id G u id d e v ice ln fo S e t
(& cla ssG u i d);
= S e tu p D iG e tC la s s D e v s (& c la s s G u id ,
N ULL ,
(DIGCF_PRESENT
NULL,
| DIGCF_DEVICEINTERFACE));
d e v i c e l n t e r f a c e D a t a . c b S i z e = s izeo f(SP_IN TE R F A C E_D EV IC E_D A TA ); w h ile (!d o n e ) fo r(;
{
fM e m b e r I n d e x < m a x D e v i c e ;
fM em ber In de x+ + )
{
if(Se tupD iEnum D evi c e I n t e r f a c e s ( d e v ic e I n f o S e t , 0 , & c la s s G u id , fM e m b e rIn d e x ,& d e vice In te rfa ce D a ta ))
{
S e tu p D iG e tD e v ic e In te rfa c e D e ta il(d e v ic e In fo S e t,& d e v ic e In te rfa c e D a ta , N U L L ,0 ,& d e v ic e In te r fa c e D e ta ilD a ta S i ze, N U LL ) ; re q u ire d S iz e = d e v ic e I n te rfa c e D e ta ilD a ta S iz e ; d e v i c e I n t e r f a c e D e t a i l D a t a = ( P S P _ D E V I C E _ IN T E R F A C E _ D E T A IL _ D A T A ) \ new DWORD[devi c e I n t e r f a c e D e t a i l D a t a S i z e ] ; if ( d e v i ce In te rfa c e D e ta ilD a ta )
{
devi c e In te rfa c e D e ta ilD a ta -> c b S iz e = \ sizeo f(SP_IN TE RF ACE_D EV ICE_D ETAIL_D AT A); } e lse
{ S etu p D iD e stro y D e v i c e I n f o L i s t ( d e v i c e I n fo S e t) ; releaseM em ory(d evi c e I n t e r f a c e D e t a ilD a t a ) ; return
0;
} d e v i c e I n f o D a t a . c b S i z e = s iz e o f ( S P _ D E V I N F O _ D A T A ) ; if(!S e tu p D iG e tD e v ic e In te rfa c e D e ta il(d e v i ce Info Se t, & d e v ice In te rface D a ta ,
d e v ic e In te rfa c e D e ta ilD a ta ,
d e v ic e In te rfa c e D e ta ilD a ta S iz e , & re q u ire d S iz e ,
& d eviceIn fo D ata))
{
S etu p D iD e stro y D e v i c e I n f o L i s t ( d e v i c e I n fo S e t) ; releaseM em ory(d evi c e I n t e r f a c e D e t a ilD a t a ) ; re turn
0;
} s i z e _ t n Le n = s t r l e n ( d e v i c e I n t e r f a c e D e t a i l D a t a - > D e v i c e P a t h ) d e v ic e L is t[fM e m b e rIn d e x ].P a th
+ 1;
= new T C H A R [ ( n L e n * s i z e o f ( T C H A R ) ) ] ;
s trn c p y (d e v ic e L is t[fM e m b e r In d e x ].P a th , de v ice In te rfa ce D e ta ilD a ta -> D e v ice P a th ,
nLen);
d e v ic e L is t[fM e m b e rIn d e x ].D e v ic e In s ta n c e = d e v ic e In fo D a ta .D e v In s t; S e tu p D iG e tD e v ic e R e g is try P ro p e rty (d e v i c e In fo S e t,
& d eviceIn foD ata,
SPDRP_HARDWAREID, NU LL, & p ro p e rty B u ffe r S ize );
NU LL,
0,
36 4
USB. Praktyczne programowanie z Windows API w C++
/ /alokowanie pamięci dla bufora danych p r o p e r t y B u f f e r = new c h a r [ ( p r o p e r t y B u f f e r S i z e * s i z e o f ( T C H A R ) ) ] ; S e tu p D iG e tD e v ic e R e g is try P ro p e rty (d e v ice In fo S e t,
& d eviceIn fo D ata,
SPDRP_HARDWAREID,NULL, propertyB u ffe r,
p r o p e r t y B u ffe r S i ze,
NULL); d e v ic e L is t[fM e m b e rIn d e x ].H a rd w a re Id
= pro pertyB u ffe r;
c o u t < < "\n D e v ic e L ist["< < fM e m b e rIn d e x < < "].H a rd w a re Id :
\ n " <<
d e v i c e L i s t [ f M e m b e r I n d e x ] . H a r d w a r e I d << e n d l ; re le a s e M e m o r y (d e v ic e In te r fa c e D e ta ilD a ta ); re le a se M e m o ry(p ro p e rty B u ffe r); } e lse
{ i f(ERROR_NO_MORE_ITEMS == G e t L a s t E r r o r ( ) ) { do n e = TRUE; break; }
} } } S etu p D iD e stro y D e v i c e I n fo L i s t ( d e v i c e I n fo S e t) ; F re e L ib ra ry ( h H id L ib ) ; re turn
fM e m b e r I n d e x ;
}
/ / -------------------------------------------------------------------HANDLE T U S B D e v i c e : : o p e n H i d U S B D e v i c e ( U I N T m em be rI n de x) { d e v i c e L i s t [ m e m b e r I n d e x ] . h i d D e v i c e O b j e c t == IN VAL ID_ HANDLE_ VALUE; d e v ic e L is t[m e m b e rIn d e x ].h id D e vic e O b je c t = C r e a t e F ile ( d e v i ce List[m e m b e rInd e x].P a th , GENERIC_READ | GENERIC_WRITE, FILE_SHA RE _RE AD
| FILE_ SH ARE _WR ITE,
N ULL,OPEN_EXISTING,FILE_FLAG_OVERLAPPED,NULL); if(d e v ic e L ist[m e m b e rIn d e x ].h id D e v ic e O b je c t return
!= INVALID_HANDLE_VALUE)
d e v ic e L is t[m e m b e rIn d e x ].h id D e vic e O b je c t;
e lse return
IN VALID_HANDLE_VALUE;
}
/ / -------------------------------------------------------------------BOOL T U S B D e v i c e : : c l o s e H i d U S B D e v i c e ( H A N D L E d e v H a n d l e ) { i f ( ( d e v H a n d l e == 0)
||
( d e v H a n d l e == INVA L ID _ HA ND L E_ VA L U E) ){ return
fa lse ;
} e lse re turn
C lo se H a n d le (d e v H a n d le );
}
/ / -------------------------------------------------------------------BOOL T U S B D e v i c e : : r e a d U S B R e p o r t ( U I N T m em be rI n de x) { DWORD r e s u l t = 0; DWORD n u m b e r O f B y t e s R e a d = 0;
Rozdział 9. ♦ Wewnętrzne struktury danych
365
OVERLAPPED » o v e r l a p p e d = NULL; i f ( o v e r l a p p e d == N U L L ) { o v e r l a p p e d = new OVERLAPPED; overlappe d-> hEvent = C reateEvent(N U LL,
TRUE, TRUE,
"");
o v e r l a p p e d - > O f f s e t = 0; ov e rla p p e d -> O ffs e tH ig h
= 0;
} m e m s e t(d e v ic e L is t[m e m b e rI n d e x ].in p u tR e p o rtB u ffe r,
0x00,
d e v ic e L is t[m e m b e rIn d e x ].in p u tR e p o rtB y te L e n g th ); if( !R e a d F ile ( d e v ic e L is t[ m e m b e rI n d e x ] .h id D e v ic e O b je c t, d e v ic e L is t[m e m b e rIn d e x ].in p u tR e p o rtB u ffe r, d e v ic e L ist[m e m b e rIn d e x].in p u tR e p o rtB y te L e n g th , &num berO fBytesRead, if(G e tL a s tE rro r() re s u lt
ove rla p p e d ))
== ERROR_IO_PENDING)
{
{
= W a itF o rS in g le O b je c t(o ve rla p p e d -> h E ve n t,
if( re s u lt
== WAIT_TIMEOUT)
100);
{
C a n c e lI o (d e v i c e L ist[m e m b e rIn d e x ].h id D e v i c e O b j e c t ) ; return
fa lse ;
} e lse if( r e s u lt
== W A I T _ F A I L E D ) {
d is p la y E rr o r(" B T ą d return
odczytu d a n y ch .");
fa lse ;
} G e tO v e rla p p e d R e su lt(d e v ic e L is t[m e m b e rIn d e x ].h id D e v ic e O b je c t, o v e rla p p e d ,
& num berO fBytesRead,
FALSE);
} e lse d is p la y E rr o r( " B T ą d odczytu d a n y ch ."); } R e se tE v e n t(o v e rla p p e d -> h E v e n t); i f ( n u m b e r O f B y t e s R e a d == ( U I N T ) d e v i c e L i s t [ m e m b e r I n d e x ] . i n p u t R e p o r t B y t e L e n g t h ) { fo r ( U S H O R T i= 0 ; p rin tf("% d
",
i< d e v i c e L i s t [ m e m b e r I n d e x ] . i n p u t R e p o r t B y t e L e n g t h ;
i++)
d e v i c e L is t [ m e m b e r I n d e x ] . i n p u t R e p o r t B u f f e r [ i] );
p r in tf( " \ n " ) ; } e ls e
{
p r in tf("B T ę d n a lic z b a
odebranych b a jtó w .\n ",n u m b e rO fB y te s R e a d );
} d e le te overlappe d; return
true;
}
/ / -------------------------------------------------------------------in t m a in ( ) {
1;//numer istniejącego interfejsu urządzenia HID ¡¡utworzenie obiektu klasy TUSBDevice UINT i n t e r f a c e I n d e x =
T U S B D e v i c e » u s b D e v i c e = new T U S B D e v i c e ( i n t e r f a c e I n d e x ) ;
//enumeracja aktualnie podłączonych urządzeń klasy HID usbDevi c e - > s e t G e t H id D e v ic e D a t a ( ) ; cout < < "\ n D e v ic e L is t["< < in te rfa c e I n d e x < < "].P a th :
\ n " <<
u s b D e v i c e - > d e v i c e L i s t [ i n t e r f a c e I n d e x ] . P a t h << e n d l ;
//otwarcie portu USB usb D evi ce -> openHidUSB Devi c e ( i n t e r f a c e I n d e x ) ;
//odczyt właściwości urządzenia if
(u sb D e v ic e -> g e tH id D e v i c e C a p a b ili t i e s ( i n t e r f a c e I n d e x ) )
{
u s b D e v i c e - > d e v i c e L i s t [ i n t e r f a c e I n d e x ] . i n p u t R e p o r t B u f f e r = new \ B Y T E [u s b D e v ic e -> d e v ic e L is t[in te rfa c e In d e x ].in p u tR e p o r tB y te L e n g th ];
366
USB. Praktyczne programowanie z Windows API w C++
w h ile ( tru e )
{
//cykliczny odczyt raportu wejściowego
usbDevi c e - > r e a d U S B R e p o r t ( in t e r f a c e I n d e x ) ; i f ( u s b D e v i c e - > d e v i c e L i s t [ i n t e r f a c e I n d e x ] . i n p u t R e p o r t B u f f e r [ 6 ] ==64) break; } u s b D e v ic e -> c lo s e H id U S B D e v ic e (u s b D e v ic e - > d e v ic e L is t[in te r fa c e In d e x ].\ h id D e v ic e O b je c t); } re le a se M e m o ry(u sb D e vice -> d e vice L ist[inte rfa ce Ind e x].P a th ); re le a s e M e m o ry (u s b D e v ic e -> d e v ic e L ist[in te rfa c e In d e x ].in p u tR e p o rtB u ffe r);
//wywołanie destruktora klasy TUSBDevice d e le te
u sb D evice;
return
0;
}
/ / --------------------------------------------------------------------
Rysunek 9.4. Aplikacja proj_USB_R9_2 w trakcie pobierania raportów wejściowych
a m o w a n ie \R o z d z ia ł 9 \R 9 J D e vi c e L i s t [ 0 ] . H a r d w a r e ld : H ID \V ID_09DA&PID_000A&REV_0002 D e vi c e L i s t [ 1 ] . H a r d w a r e ld : H ID\VID_22BA&PID_0108&REV_0313 D e v ic e L i s t [ 2 ] . H a r d w a r e ld : H ID\VID_FFFF& PID_8081&REV_01068flI_00 D e vi c e L i s t [ 3 ] . H a r d w a r e ld : HID\VID_FFFF&PID_8081&REV_01068MI_01 D e vi c e L i s t [ 1 ] . P a t h : \ \ ? \ h id # v i d_22ba& pi d_0108#8A5 9 f 5 d85&OAOOOO#{4dle55 b 2 - f 1 6 f - l l c f - 8 8 c b - 0 0 1 1 1 1 000030} 0 129 127 128 128 0 0 0 128 127 128 128 0 0 0 129 127 128 128 0 0 0 128 127 128 128 0 0 0 128 127 128 128 0 64 A b y k o n ty n u o w a ć , n a c i ś n i j d o w o ln y k l a w is z . . . _
Aplikacja środowiska graficznego Głównym elem entem aplikacji środowiska graficznego jest klasa formularza będąca w łaścicielem w szystkich kom ponentów w izualnych tworzących graficzną warstwę programu. Przejście od aplikacji konsolowej, posługującej się klasą TUSBDevice opisu j ącą zachowanie urządzenia, do aplikacji środowiska graficznego jest czynnością nie zwykle prostą. W celu wykonania takiego przejścia wystarczy odpowiednio powiązać (lub zagregować) klasę reprezentującą warstwę implementacji z głów ną klasą repre zentującą warstwę prezentacji, tak ja k przedstaw ia to rysunek 9.5. Łatwo zauważyć, że w arstw a implem entacji pozostała praktycznie niezmieniona, a z klasą formularza TForm1 zagregowano pięć klas komponentów wizualnych. Obiekt stworzony na bazie klasy TButton będzie jedynym elementem sterującym pracą programu. Obiekty stwo rzone na bazie klas TListView i TListItem posłużą do wyświetlania i przechowywania indeksowanych ścieżek dostępu do interfejsów urządzeń klasy HID sekwencyjnie w y krywanych w procesie enumeracji. Tworzenie obiektu klasy TUSBDevice i proces enum eracji dołączonych urządzeń są wykonywane w konstruktorze TForm1() klasy for m ularza (rysunek 9.6). W izualizacją danych odczytywanych z jednego z pól raportu
Rozdział 9. ♦ Wewnętrzne struktury danych
« C p p T y p e d e f» PHI DP CAPS
3S7
« C p p T y p e d e f» DEVICE DATA
« C p p T y p e d e f» HI DP CAPS «C ppS ynonym »
«C ppS ynonym » «C ppS ynonym »
« C p p S tru c t» Hl DP CAPS łlnputReportByteLength: USHORT +OutputReportByteLength: USHORT +FeatureReportByte Length: USHORT +Reserved: USHORT +NumberLinkCollectionNodes: USHORT +NumberlnputButtonCaps: USHORT +NumberlnputValueCaps: USHORT +NumberlnputDatalndices: USHORT łNumberOutputButtonCaps: USHORT +NumberOutputValueCaps: USHORT +NumberOutputDatalndices: USHORT +NumberFeatureButtonCaps: USHORT +NumberFeatureValueCaps: USHORT łNumberFeatureDatalndices: USHORT
« C p p T y p e d e f» PHIDP PREPARSED DATA
+pre parsed Data
cC p pS yn on ym »
+capabilities « C p p S tru c t» DEVICE DATA łUsagePage
« C p p T y p e d e f» USAGE
+Usage « C p p T y p e d e f» PUSAGE
+Hardwareld: TCHAR +Path: TCHAR +Devicelnstance: DWORD +hidDeviceObject: HANDLE +inputReportBufFer: BYTE +inputReportByteLength: USHORT +outputReportByteLength: USHORT +featureReportByteLength: USHORT
« C p p S tru c t» HI DP P RE PAR SED DATA
« C p p S y nonym » « C p p T y p e d e f» PDEVICE DATA
TForm ! « p u b lish e d » -B u tto n 1 : TButton « pu blish ed »-T ra ckB a r1 : TTrackBar « p u b lish e d » -M e m o 1 : TMemo « pu blish ed »-ListV ie w 1 : TListView « p u b lis h e d » -L is tlte m : TListltem « p u b lis h e d » -B u tto n 1ClickQ « pu blish ed »-F orm C lo se [) « e r e ate »+TFo rm 1 ()
-fMemberlndex: UINT -displayError() +getHidDeviceCapabilities() +setGetHidDeviceData[) +openHidUSBDevice() +readUSBReport() +closeHidUSBDevice() « c r e a t e » +TUSBDevice[) «destroy>>+TUSBDevice[)
Rysunek 9.5. Statyczny diagram klas dla programu zorientowanego obiektowo, posługującego się strukturą DEVICEDATA i klasą formularza
Rysunek 9.6. Lista interfejsów aktualnie dostępnych w systemie urządzeń klasy HID wejściowego zajmuje się obiekt klasy TTrackBar (rysunek 9.7). W prezentowanym ko dzie koniec odczytu danych jest sygnalizowany poprzez umieszczenie w piątym bajcie (szóstym polu) raportu wejściowego dziesiętnej wartości 64, oznaczającej naciśnięcie na panelu sterowniczym testowanego urządzenia przycisku o numerze (indeksie) 11. N a listingach 9.3 i 9.4 pokazano odpowiednio definicję struktury klas z rysunku 9.5 oraz im plementację funkcji składowych tych klas. W ynik działania programu przed stawiono na rysunkach 9.6 i 9.7.
368
USB. Praktyczne programowanie z Windows API w C++
Rysunek 9.7. Aplikacja proj_USB_R9_3 w trakcie odczytu raportów wejściowych Listing 9.3. Kod modułu usb_R9_3.h //
--------------------------------
# i f n d e f usb _R9_3H # d e f i n e usb _R9_3H # in c lu d e < Classes.hpp> # in c lu d e < C o ntrols.h p p> # in clu d e < S td C trls .h p p > # i n c l u d e # i n c lu d e < Co m Ctrls.hpp > #pragma o p t i o n
p u s h -a 1
# in c lu d e < s e tu p a p i.h > #pragma o p t i o n
pop
# in clu d e < asse rt.h>
//
--------------------------------
# d e fin e sea rch M a xD e vices
//
10
--------------------------------
t e m p l a t e < c l a s s T> in lin e
v o i d r e l e a s e M e m o r y ( T &x)
{ assert(x de le te
[]
!= N U L L ) ; x;
x = NULL; }
//
--------------------------------
t y p e d e f USHORT USAGE, *PUSAGE; t y p e d e f s t r u c t _HIDP_PREPARSED_DATA *PHIDP_PREPARSED_DATA;
/ / ------------------------------------------------------------------t y p e d e f s t r u c t _ HI D P_C A PS { USAGE U sa g e ; USAGE U s a g e P a g e ; USHORT I n p u t R e p o r t B y t e L e n g t h ; USHORT O u t p u t R e p o r t B y t e L e n g t h ; USHORT F e a t u r e R e p o r t B y t e L e n g t h ; USHORT R e s e r v e d [ 1 7 ] ; USHORT N u m b e r L i n k C o l l e c t i o n N o d e s ; USHORT N u m b e r I n p u t B u t t o n C a p s ;
Rozdział 9. ♦ Wewnętrzne struktury danych
369
USHORT N u m b e r I n p u t V a l u e C a p s ; USHORT N u m b e r I n p u t D a t a I n d i c e s ; USHORT N u m b e r O u t p u t B u t t o n C a p s ; USHORT N u m b e r O u t p u t V a l u e C a p s ; USHORT N u m b e r O u t p u t D a t a I n d i c e s ; USHORT N u m b e r F e a t u r e B u t t o n C a p s ; USHORT N u m b e r F e a t u r e V a l u e C a p s ; USHORT N u m b e r F e a t u r e D a t a I n d i c e s ; } HID P_ CA PS ,
//
* PH ID P_ CA PS;
--------------------------------------------------------------------
t y p e d e f s t r u c t _DEVI CE_ DA TA { TCHAR
*Hardw areId;
TCHAR
*Path;
DWORD
D e vice In sta n ce ;
HANDLE
h id D e v ic e O b je c t;
BYTE
* in p u tR e p o rtB u ffe r;
USHORT
/*
in p u tR e p o rtB y te L e n g th ;
*/
USHORT
outputR ep ortB yteLength;
USHORT
featureR ep ortB yteLength;
/*
*/
PHIDP_PREPARSED_DATA p r e p a r s e d D a t a ; HIDP_CAPS c a p a b i l i t i e s ; } DE VIC E_DATA,
//
*PDEVICE_DATA;
--------------------------------------------------------------------
cla ss
TUSBD evice {
p r iv a te : UINT fM e m b e r I n d e x ; v o id d is p la y E r r o r ( c o n s t
c h a r * m sg ) ;
p u b lic : PDEVICE_DATA d e v i c e L i s t ; BOOL g e t H i d D e v i c e C a p a b i l i t i e s ( U I N T m e m b e r I n d e x ) ; UINT s e t G e t H i d D e v i c e D a t a ( ) ; HANDLE o p e n H i d U S B D e v i c e ( U I N T m e m b e r I n d e x ) ; BOOL r e a d U S B R e p o r t ( U I N T m e m b e r I n d e x ) ; BOOL c l o s e H i d U S B D e v i c e ( H A N D L E d e v H a n d l e ) ; TU SBD evice(UINT m em berIndex); ~ T U S B D e vic e (); };
//
--------------------------------------------------------------------
cla ss
TForm1
: p u b l i c TForm
{ p u b lis h e d :
//K om pone nty IDE (z in te g ro w a n e g o ś r o d o w i s k a p r o g r a m i s t y )
TButton * Butto n1 ; TTrackBar *TrackBar1; TMemo *Memo1; T L is tV ie w
* L is tV ie w 1 ;
v o id
fa s tc a ll
B u tto n 1 C lic k (T O b je c t *Sender);
v o id
fa s tc a ll
F orm C lose(T O bject *Sender, T C lo se A c tio n
p r iv a te :
& A ctio n );
/ / D e k l a r a c j e u ż y tk o w n ik a
TUSBD evice * u s b D e v ic e ; p u b lic :
/ / D e k l a r a c j e u ż y tk o w n ik a
fa s tc a ll T L is tIte m
T F o rm 1 ( T C o m p o n e n t* O w n e r) ; * ListItem ;
};
/ / -------------------------------------------------------------------extern
PACKAGE TForm1 *F orm 1;
370
USB. Praktyczne programowanie z Windows API w C++
//
----
# e n d if
//
----
Listing 9.4. Kod modułu usb_R9_3.cpp //
--------------------------------------------------------------
# i n c l u d e < v c l .h> #pragma h d r s t o p # in clu d e
"usb_R9_3.h"
#pragma p a c k a g e ( s m a r t _ i n i t ) #pragma r e s o u r c e
"*.dfm "
TForm1 *Fo rm 1;
//
--------------------------------------------------------------
T U S B D e v i c e : : T U S B D e v i c e ( U I N T m em be rI nd e x) { f M e m b e r I n d e x = m em b e rI n d e x; d e v i c e L i s t = new \ D E VIC E _ D A T A [((fM em b erIn d ex+ 1 )* sizeo f(D E VIC E _D A T A))]; }
//
--------------------------------------------------------------
TU SBD evice ::~ T U SB D evice() { re le a se M e m o ry(d e vice L ist); Show Message("Pam ięć z w o ln io n a p r a w id ło w o ." ) ; }
//
--------------------------------------------------------------
v o id T U S B D e v ic e ::d is p la y E rro r( c o n s t
c h a r * msg)
{ ShowMessage(msg); e x it( 0 ) ; };
//
--------------------------------------------------------------
BOOL TU SBD ev i c e : : g e t H i d D e v i c e C a p a b i l i t i e s ( U I N T m em be rI n de x) { HMODULE h H i d L i b ; bool
status;
lo n g
(
s td c a ll*
H id P _G e tC a p s)(IN
PHIDP_PREPARSED_DATA P r e p a r s e d D a t a ,
OUT PHIDP_CAPS C a p a b i l i t i e s ) ; bool
(
s td c a ll*
H i d D _ G e t P r e p a r s e d D a t a ) ( I N HANDLE
H id D e v ice O b je c t,
OUT PHIDP_PREPARSED_DATA * P r e p a r s e d D a t a ) ; bool
(
s td c a ll*
H id D _ F r e e P re p a rs e d D a ta ) (IN PHIDP_PREPARSED_DATA P r e p a r s e d D a t a ) ;
h H id L ib = L o a d L ib ra ry ("C :\\W in d o w s \\S yS W O W 6 4 \\H ID .D L L "); if
(!h H id L ib ) d is p la y E rro r("B łą d
d o łą c ze n ia b i b lio t e k i
H ID .D LL.");
(FARPROC&)
H id P _ G e tC a p s = G e tP ro c A d d re s s (h H id L ib ,
(FARPROC&)
H id D _ G e tP re p a rs e d D a ta = G e tP ro cA d d re s s(h H id L ib ,
(FARPROC&)
H id D _ F re e P re p a rs e d D a ta = G e tP ro cA d d re s s(h H id L ib ,
"H idP_ G etC aps"); "H id D _ G e tP re p a rse d D a ta "); "H id D _ Fre e P re p a rse d D a ta ");
Rozdział 9. ♦ Wewnętrzne struktury danych
if
(!H id P _G e tC a p s
||
371
!H id D _ G e tP re p a rse dD a ta
||
!H id D _ F re e P re p a rse d D a ta ){
F re e L ib ra ry ( h H id L ib ) ; d is p la y E rro r("N ie
z n a l e z i o n o żadnych f u n k c j i
e ksp o rto w ych .");
} s ta tu s= H id D _ G e tP re p a rse d D a ta (d e v ice L ist[m e m b e rIn d e x ].h id D e v ic e O b je c t, & d e v ice L is t-> p re p a rs e d D a ta ); if( s ta tu s ) { H id P _ G e tC a p s (d e v ic e L is t-> p re p a rs e d D a ta ,
& d e v ic e L is t-> c a p a b ilitie s );
d e v ic e L is t[m e m b e rI n d e x ].in p u tR e p o rtB y te L e n g th = d e v ic e L is t-> c a p a b ilitie s .In p u tR e p o rtB y te L e n g th ; d e v ic e L is t[m e m b e rIn d e x ].o u tp u tR e p o rtB y te L e n g th = d e v ic e L is t-> c a p a b ilitie s .O u tp u tR e p o r tB y te L e n g th ; d e v ice L ist[m e m b e rIn d e x].fe a tu re R e p o rtB y te L e n g th
=
d e v ic e L is t-> c a p a b ilitie s .F e a tu re R e p o rtB y te L e n g th ; A n s i S t r i n g S1 = S 1 . F o r m a t ( " I n p u t R e p o r t B y t e L e n g t h = % d \ OutputR eportByteLength= % d\ FeatureR eportByteLength= % d", O P E N A R R A Y (T V a rR e c,(d e v ic e L ist[m e m b e rIn d e x ].in p u tR e p o rtB y te L e n g th , d e v ic e L is t[m e m b e rIn d e x ].o u tp u tR e p o rtB y te L e n g th , devi ce List[m e m b e rIn d e x].fe a tu re R e p o rtB yte Le n g th ))); Form 1-> M em o1-> Lines-> Add(S1); H id D _ F r e e P re p a rs e d D a t a ( d e v ic e L is t- > p re p a r s e d D a ta ) ; } F re e L ib ra ry ( h H id L ib ) ; return }
//
statu s;
--------------------------------------------------------------------
UINT TU SBD ev i c e : : s e t G e t H i d D e v i c e D a t a ( ) { DWORD p r o p e r t y B u f f e r S i z e = 0; c h a r * p r o p e r t y B u f f e r = NULL; HMODULE h H i d L i b ; SP_DEVINFO_DATA d e v i c e I n f o D a t a ; HDEVINFO d e v i c e I n f o S e t ; SP _INT E RF AC E_ DE V IC E_ DA TA d e v i c e I n t e r f a c e D a t a ; fM e m b e r I n d e x = 0; GUID c l a s s G u i d ; P S P_ DE V IC E_ IN TE RF AC E_ DE TA IL _ DA TA d e v i c e I n t e r f a c e D e t a i l D a t a = NULL; DWORD r e q u i r e d S i z e = 0; DWORD d e v i c e I n t e r f a c e D e t a i l D a t a S i z e = 0; DWORD m a x D e v i c e = s e a r c h M a x D e v i c e s ; bool
do ne = f a l s e ;
v o id
(
s td c a ll
//m aksym aln a l i c z b a i n t e r f e j s ó w urządzeń
* H id D _ G e tH id G u id )(O U T
LPGUID H i d G u i d ) ;
h H id L ib = L o a d L ib ra ry ("C :\\W in d o w s \\S yS W O W 6 4 \\H ID .D L L "); if
(!h H id L ib ) d is p la y E rro r("B łą d
(FARPROC&)
d o łą c ze n ia b i b lio t e k i
H ID .D LL.");
H id D _ G e tH id G u id = G e t P r o c A d d r e s s ( h H id L ib , "H id D _ G e tH id G u id ");
if
(!H id D _ G e tH id G u id ){ F re e L ib ra ry ( h H id L ib ) ; d is p la y E rr o r(" N ie
}
zn a le zio n o
id e n ty fik a to r a
G U ID .");
372
USB. Praktyczne programowanie z Windows API w C++
H id D _ G e tH id G u id
(& cla ssG u i d);
d e v ic e I n fo S e t = S e tu p D iG e tC la s s D e v s (& c la s s G u id ,
N ULL ,
(DIGCF_PRESENT
NULL,
| DIGCF_DEVICEINTERFACE));
d e v i c e I n t e r f a c e D a t a . c b S i z e = s izeo f(SP_IN TE R F A C E_D EV IC E_D A TA ); w h ile (!d o n e ) fo r(;
{
fM e m b e r I n d e x < m a x D e v i c e ;
fM em ber In de x+ + )
{
if(S e tu p D iE n u m D e v i c e I n t e r f a c e s ( d e v i c e I n f o S e t , 0 , & c la s s G u i d , fM em b erIn d e x,& d e viceIn terfaceD ata))
{
S e tu p D iG e tD e v ic e In te rfa c e D e ta il(d e v ic e In fo S e t,& d e v ic e In te rfa c e D a ta , N U L L ,0 ,& d e v ic e In te rfa c e D e ta ilD a ta S ize , N U LL ) ; re q u ire d S iz e = d e v ic e I n te rfa c e D e ta ilD a ta S iz e ; d e v i c e I n t e r f a c e D e t a i l D a t a = ( P S P _ D E V I C E _ IN T E R F A C E _ D E T A IL _ D A T A ) \ new D W O R D [ d e v i c e I n t e r f a c e D e t a i l D a t a S i z e ] ; if ( d e v i ce In te rfa c e D e ta ilD a ta )
{
devi c e In te rfa c e D e ta ilD a ta -> c b S iz e = \ sizeo f(SP_IN TE RF ACE_D EV ICE_D ETAIL_D AT A); } e lse
{ S etu p D iD e stro y D e v i c e I n f o L i s t ( d e v i c e I n fo S e t) ; releaseM em ory(d evi c e I n t e r f a c e D e t a ilD a t a ) ; return
0;
} d e v i c e I n f o D a t a . c b S i z e = s iz e o f ( S P _ D E V I N F O _ D A T A ) ; if
( !S e tu p D iG e tD e v ic e In te rfa c e D e ta il(d e v ic e In fo S e t, & d e v ice In te rface D a ta ,
d e v ic e In te rfa c e D e ta ilD a ta ,
d e v ic e In te rfa c e D e ta ilD a ta S iz e , & re q u ire d S iz e ,
& d eviceIn fo D ata))
{
S etu p D iD e stro y D e v i c e I n f o L i s t ( d e v i c e I n fo S e t) ; releaseM em ory(d evi c e I n t e r f a c e D e t a ilD a t a ) ; return
0;
} s i z e _ t n Le n = s t r l e n ( d e v i c e I n t e r f a c e D e t a i l D a t a - > D e v i c e P a t h ) d e v ic e L is t[fM e m b e rIn d e x ].P a th
+ 1;
= new T C H A R [ ( n L e n * s i z e o f ( T C H A R ) ) ] ;
s trn c p y (d e v ic e L is t[fM e m b e r In d e x ].P a th , d e v ice In te rfa ce D e ta ilD a ta -> D e v ice P a th ,
nLen);
F o rm 1 -> L is tIte m = F o rm 1 -> L is tV ie w 1 -> Ite m s -> A d d (); F orm 1 -> L istIte m -> C a p tio n = d e v ic e L is t[ fM e m b e r I n d e x ] .P a t h ; F orm 1-> L is tIte m -> S u b Ite m s-> A d d (d e v ic e L is t[fM e m b e rIn d e x ].P a th ); d e v ic e L is t[fM e m b e rIn d e x ].D e v ic e In s ta n c e = d e v ic e In fo D a ta .D e v In s t; S e tu p D iG e tD e v ic e R e g is try P ro p e rty (d e v i c e In fo S e t,
& d eviceIn fo D ata,
SPDRP_HARDWAREID, NU LL,
NU LL,
0,
& p ro p e rty B u ffe r S iz e );
/ / a l o k o w a n i e p a m i ę c i d l a b u f o r a danych p r o p e r t y B u f f e r = new c h a r [ ( p r o p e r t y B u f f e r S i z e * s i z e o f ( T C H A R ) ) ] ; S e tu p D iG e tD e v ic e R e g is try P ro p e rty (d e v i c e In fo S e t,
& d eviceIn fo D ata,
SPDRP_HARDWAREID,NULL,
Rozdział 9. ♦ Wewnętrzne struktury danych
373
propertyB u ffe r,
p r o p e r t y B u ffe r S i ze,
N ULL); d e v ic e L is t[fM e m b e rIn d e x ].H a rd w a re Id } e lse
= pro pertyB u ffe r;
{ i f(ERROR_NO_MORE_ITEMS == G e t L a s t E r r o r ( ) ) { don e = TRUE; break; }
} releaseM em ory(d evi c e I n t e r f a c e D e t a ilD a t a ) ; rele a se M e m o ry(p ro p e rty B u ffe r); } } S etu p D iD e stro y D e v i c e I n fo L i s t ( d e v i c e I n fo S e t) ; F re e L ib ra ry ( h H id L ib ) ; re turn }
//
fM e m b e r I n d e x ;
--------------------------------------------------------------
HANDLE T U S B D e v i c e : : o p e n H i d U S B D e v i c e ( U I N T m em be rI n de x) { d e v i c e L i s t [ m e m b e r I n d e x ] . h i d D e v i c e O b j e c t == IN VAL ID_ HANDLE_ VALUE; d e v ic e L is t[m e m b e rIn d e x ].h id D e vic e O b je c t = C r e a t e F ile ( d e v i ce List[m e m b e rInd e x].P a th , GENERIC_READ | GENERIC_WRITE, FILE_SHA RE _RE AD
| FILE_ SH ARE _WR ITE,
N U L L , O P E N _ E X IS T I N G , F I L E _ F L A G _ O V E R L A P P E D ,N U L L ) ; if(d e v ic e L ist[m e m b e rIn d e x ].h id D e v ic e O b je c t return
!= INVALID_HANDLE_VALUE)
d e v ic e L is t[m e m b e rIn d e x ].h id D e vic e O b je c t;
e lse return }
//
IN VALID_HANDLE_VALUE;
--------------------------------------------------------------
BOOL T U S B D e v i c e : : c l o s e H i d U S B D e v i c e ( H A N D L E d e v H a n d l e ) { i f ( ( d e v H a n d l e == 0)
||
( d e v H a n d l e == INVA L ID _ HA ND L E_ VA L U E) ){ re turn
fa lse ;
} e lse re turn }
//
C lo se H a n d le (d e v H a n d le );
--------------------------------------------------------------
BOOL T U S B D e v i c e : : r e a d U S B R e p o r t ( U I N T m em be rI n de x) { DWORD r e s u l t
= 0;
DWORD n u m b e r O f B y t e s R e a d = 0; OVERLAPPED * o v e r l a p p e d = NULL; i f ( o v e r l a p p e d == N U L L ) { o v e r l a p p e d = new OVERLAPPED; overlappe d-> hEvent = C reateEvent(N U LL, o v e r l a p p e d - > O f f s e t = 0; ov e rla p p e d -> O ffs e tH ig h
}
= 0;
TRUE, TRUE,
"");
37 4
USB. Praktyczne programowanie z Windows API w C++
if( !R e a d F ile ( d e v ic e L is t[ m e m b e rI n d e x ] .h id D e v ic e O b je c t, d e v ic e L is t[m e m b e rIn d e x ].in p u tR e p o rtB u ffe r, d e v ic e L ist[m e m b e rIn d e x].in p u tR e p o rtB y te L e n g th , & num berO fBytesRead, if(G e tL a s tE rro r() re s u lt
ove rla p p e d ))
== ERROR_IO_PENDING)
{
{
= W a itF o rS in g le O b je c t(o ve rla p p e d -> h E ve n t,
i f ( re s u lt
== WAIT_TIMEOUT)
100);
{
C a n c e lI o (d e v i c e L ist[m e m b e rIn d e x ].h id D e v i c e O b j e c t ) ; return
fa lse ;
} e lse if( r e s u lt
== W A I T _ F A I L E D ) {
d is p la y E rro r("B ią d return
odczytu d a n y ch .");
fa lse ;
} G e tO v e rla p p e d R e su lt(d e v ic e L is t[m e m b e rIn d e x ].h id D e v ic e O b je c t, ove rla ppe d,
& num berO fBytesRead,
FALSE);
} e lse d is p la y E rro r("B ią d
odczytu d a n y ch .");
} R e se tE v e n t(o v e rla p p e d -> h E v e n t); i f ( n u m b e r O f B y t e s R e a d == ( U I N T ) d e v i c e L i s t [ m e m b e r I n d e x ] . i n p u t R e p o r t B y t e L e n g t h ) { F o r m 1 - > T r a c k B a r 1 - > P o s i t i on = d e v i c e L i s t [ m e m b e r I n d e x ] . i n p u t R e p o r t B u f f e r [ 1 ] ; } e ls e
{
d is p la y E rro r("B ię d n a lic z b a
odebranych b a jt ó w . " ) ;
} d e le te o verlap p e d ; return }
//
true;
-------------------------------------------------------------fa s tc a ll
T F o r m 1 : : T F o r m 1 ( T C o m p o n e n t * Owner)
: TF or m (O wn er ) { L is tV ie w 1 -> V ie w S ty le = v sS m a llIc o n ; u s b D e v i c e = NULL; u s b D e v i c e = new T U S B D e v i c e ( s e a r c h M a x D e v i c e s ) ; usb D ev ice-> setG etH id D e vi ce D a ta (); }
//
--------------------------------------------------------------
v o id
fa s tc a ll
T F o r m 1 : : B u t t o n 1 C li c k ( T O b j e c t *Sender)
{ if( L is tV ie w 1 -> S e le c te d )
{ / / j e ż e l i zaznaczono i n t e r f e j s
if(u s b D e v ic e -> o p e n H id U S B D e v ic e (L is tV ie w 1 -> S e le c te d -> In d e x )!=
\
INVALID_HANDLE_VALUE) S h o w M e s s a g e ( " U r z ą d z e n i e o d b l o k o w a n e do t r a n s m i s j i . " ) ; e lse S h o w M e s s a g e ( " U r z ą d z e n i a n i e można o d b l o k o w a ć do t r a n s m i s j i . " ) ;
/ / o d c z y t w łaściw ości urządzenia if( u s b D e v ic e -> g e tH id D e v ic e C a p a b ilitie s(L istV ie w 1 -> S e le c te d -> In d e x )){
/ / a l o k o w a n i e p a m i ę c i d l a b u f o r a w ejśc iow eg o u sb D e v ic e -> d e v ice L is t[L is tV ie w 1 -> S e le cte d -> In d e x ].in p u tR e p o rtB u ffe r = new B Y T E [ u s b D e v i c e - > d e v i c e L i s t [ L i s t V i e w 1 - > S e l e c t e d - > I n d e x ] . \ in p u tR ep o rtB yteLen gth ]; w h ile ( tru e )
{ / / c y k l i c z n y o d c z y t r a p o r t u w ejśc iow eg o
Rozdział 9. ♦ Wewnętrzne struktury danych
375
usbDevi c e -> r e a d U S B R e p o rt( L is tV ie w 1 -> S e l e c te d - > I n d e x ) ; if(u s b D e v ic e -> d e v i c e L is t[ L is tV ie w 1 -> S e le c te d - > I n d e x ] .\ in p u tR e p o rtB u ffe r[6 ]= = 6 4 )
{
l / z w o l n i e n i e p a m i ę c i d l a b u f o r a w ejśc iow eg o re le a s e M e m o ry(u s b D e vic e -> d e vic e L ist[L is tV ie w 1 -> \ S e le cte d -> In d e x ].in p u tR e p o rtB u ffe r); F orm 1-> C ap tion = " K o n ie c o d c z y t u d a n y c h . " ; break; } } u sb D e v ic e -> c lo se H id U S B D e v ic e (u sb D e v ic e -> d e v ic e L is t[L is tV ie w 1 -> \ S e le c te d -> In d e x ].h id D e v ic e O b je c t); } } e lse Show M e ssa ge ("N ie wybrano i n t e r f e j s u }
ll
u rzą d ze n ia .");
--------------------------------------------------------------
v o id
fa s tc a ll
T F o rm 1 ::F o rm C lo se (T O b je ct *Sender, T C lo se A c tio n
& Action)
{ if( L is tV ie w 1 -> S e le c te d ) { re le a s e M e m o ry(u s b D e vic e -> d e vic e L ist[L is tV ie w 1 -> \ S e le c te d -> In d e x ].P a th ); } d e le t e usb D evice; A ctio n }
ll
= caFree;
--------------------------------------------------------------
Podsumowanie W rozdziale zaprezentowano różne techniki projektowania i implementacji oprogra mowania wykorzystującego w ewnętrzną strukturę danych, opisującą podstawowe pa rametry urządzenia USB. Prześledziliśm y je d n ą z możliwych dróg ewolucji takiego oprogramowania, począwszy od podejścia proceduralnego, poprzez obiektowe, a skoń czywszy na aplikacji z graficznym interfejsem użytkownika. Dla osób pragnących poeksperymentować z bardziej zaawansowanym i wewnętrznymi strukturami danych przygotowano ćwiczenia do samodzielnego wykonania.
Ćwiczenia Ćwiczenie 9.1 W rozdziale zostały omówione przykłady wykorzystania w programie obsługującym zewnętrzne urządzenie USB wewnętrznej struktury danych zawierającej opis podsta wowych właściwości urządzenia klasy HID. Kolejnym krokiem w rozwoju tego rodzaju oprogramowania może być uwzględnienie opisu właściwości elementów sterujących
376
USB. Praktyczne programowanie z Windows API w C++
i kontrolnych w raportach, za pom ocą których urządzenie komunikuje się z macierzy stym komputerem. N a rysunku 9.8 pokazano przykładowy diagram zawierający struk turę DEVICE_DATA opisującą w łaściw ości urządzenia oraz agregujące z nią struktury opisujące zawartość raportów, którym i posługuje się urządzenie. N a listingu 9.5 za mieszczono szkielet implementacji zestawu struktur z rysunku 9.8.
Rysunek 9.8. Struktury opisujące urządzenie klasy HID i raporty, którymi się posługuje Listing 9.5. Przykładowa implementacja diagramu z rysunku 9.8 //
---------------------------------------------------------
t y p e d e f s t r u c t _HID_DATA { USAGE U s a g e P a g e ; ULONG R e p o r t I D ; u n io n
{
stru ct
{
ULONG
UsageMin;
ULONG
Usa g eM a x ;
ULONG
MaxUsageLength;
PUSAGE U s a g e s ;
/ / l i s t a p r z y c i s k ó w w s t a n i e ON
//... } B uttonD ata; stru ct
{
USAGE
Usage;
ULONG
V a lu e ;
//... } V a lu e D a ta ; }; } HID_DATA,
//
*PHID_DATA;
---------------------------------------------------------
Rozdział 9. ♦ Wewnętrzne struktury danych
typedef s tru c t
377
_DEVICE_DATA {
TCHAR
*H ardwareId;
TCHAR
*Path;
DWORD
Devi c e I n s t a n c e ;
HANDLE
h id D e v ic e O b je c t;
//--BYTE
* in p u tR e p o r tB u ffe r;
USHORT
in p u tR e p o rtB yte Le n g th ;
PHID_DATA
in p u tD a ta ;
PHIDP_BUTTON_CAPS
in p u tB u tto n C a p s;
PHIDP_VALUE_CAPS
in p u tV a lu e C a p s ;
//--BYTE
* ou tpu tR eportB u ffer;
USHORT
outp utReportB yteLength;
PHID_DATA
outputD ata;
PHIDP_BUTTON_CAPS
outputButtonCaps;
PHIDP_VALUE_CAPS
o u t p u t V a l ueCaps;
//--BYTE
* fe a tu re R e p o rtB uffe r;
USHORT
featureR ep ortB yteLength;
PHID_DATA
fea tu reD a ta ;
PHIDP_BUTTON_CAPS
fe a tu re B u tto n C a p s;
PHIDP_VALUE_CAPS
fe atu reV alu eC ap s;
PHIDP_PREPARSED_DATA p r e p a r s e d D a t a ; HIDP_CAPS
c a p a b ilitie s ;
HIDD_ATTRIBUTES } DE VIC E_DATA,
a ttr ib u te s ;
*PDEVICE_DATA;
/ / ----------------------------------------------------------------
Zmodyfikuj kody przedstawione w tym rozdziale, wykorzystując implementację po wyższych struktur.
Ćwiczenie 9.2 W zasobach W DK w katalogu \src\hid\hclient można odszukać przykłady implemen tacji wewnętrznych struktur danych. Zapoznaj się z nimi.
Ćwiczenie 9.3 Zaprojektuj i wykonaj aplikację środowiska graficznego w yśw ietlającą listę w szyst kich urządzeń podłączonych aktualnie do magistrali USB. Odczytaj pełne informacje o tych urządzeniach.
378
USB. Praktyczne programowanie z Windows API w C++
Rozdział 10.
Programy wielowątkowe C++ posiada cechy umożliwiające programowanie współbieżne (wielowątkowe). Oprócz cech samego języka C++ programista ma do dyspozycji API Windows, w tym sema fory, wątki, procesy, potoki, współdzieloną pamięć itp. Niniejszy rozdział opisuje nie które zasoby C++ i API W indows oraz wyjaśnia, w jaki sposób efektywnie je wyko rzystać do programowania współbieżnego.
Wątki i procesy W ą te k (ang. thread) je st definiowany jako odrębny przebieg aplikacji. Aplikacja pi sana dla systemu W indows może zawierać wiele wątków, każdy z własnym stosem, własnym identyfikatorem i kopią rejestrów procesora. W kom puterach wieloproceso rowych poszczególne procesory są w stanie wykonywać dane wątki w sposób nieza leżny. W kom puterach jednoprocesorow ych otrzymujemy wrażenie jednoczesnego (współbieżnego) wykonywania wielu wątków, chociaż w rzeczywistości w danym prze dziale czasu procesor może wykonać tylko jeden wątek. Proces je st definiowany jako wykonujący się program, mający postać kolekcji wielu wątków, które pracują we wspólnej przestrzeni adresowej procesora. Każdy proces musi zawierać przynajmniej jeden wątek główny (ang. main thread). W ątki należące do tego samego procesu m ogą współdzielić różne zasoby aplikacji (lub systemu), ta kie ja k otwarte pliki lub uruchom ione aplikacje, oraz odwoływać się do prawidłowo wybranego adresu pamięci w przestrzeni adresowej procesora. Uogólniając, w spółbieżność odrębnych procesów może być realizowana na jednym z trzech poziomów: ♦ sprzętowym (komputer posiadający architekturę wieloprocesorową lub wielordzeniową), ♦ systemowym, ♦ aplikacji (podział czasu procesora między różne elementy tej samej aplikacji).
380
USB. Praktyczne programowanie z Windows API w C++
W dalszej części rozdziału zostaną omówione niektóre aspekty współbieżności reali zowanej na poziomie systemu operacyjnego oraz aplikacji. System operacyjny zarządza wątkami na podstawie ich priorytetu. W ątki o wyższym priorytecie m ają pierwszeństwo w wykonaniu przed wątkami o niższym priorytecie. N a poziomie tego samego priorytetu w ątki są zarządzane w taki sposób, aby każdy z nich mógł być wykonany. System może wstrzymać wykonanie wątku (sytuację taką nazywa się wywłaszczeniem), aby przekazać czas procesora na rzecz innego wątku. W systemie operacyjnym MS W indows są definiowane trzy podstawowe kategorie określające stan wątku: ♦ wątek wykonujący się, ♦ wątek gotowy, ♦ wątek blokowany. Każdy w ątek może być wykonany, pod warunkiem że w danej chwili ma dostęp do rejestrów procesora. System operacyjny współdzieli tyle w ykonujących się wątków, ile ma procesorów — po jednym wątku na procesor. W ątek pozostaje w stanie wyko nania do momentu, kiedy zostanie wstrzymany w oczekiwaniu na jakąś konkretną ope rację. Jest wtedy wywłaszczany przez system operacyjny, aby mógł zostać wykonany inny wątek, lub sam zawiesza działanie. W ątek jest gotowy do wykonania, jeżeli nie działa i nie je st blokowany. Gotowy w ątek może wywłaszczyć wykonywany wątek o tym samym priorytecie, ale nie wątek o wyższym priorytecie. W ątek je st blokowany, jeżeli oczekuje na wykonanie konkretnej operacji. Zawsze można jaw nie zablokować w ątek przez jego zaw ieszenie (ang. suspend). Zawieszony w ątek będzie oczekiwał w nieskończoność (ang. infmite) lub do momentu jego wznowienia (ang. resume). Jądro systemu Windows działa w trybie z wywłaszczaniem. N a rysunku 10.1 pokaza no uproszczony diagram klas dla systemu z jądrem działającym w trybie z wywłasz czaniem [7]. Rysunek 10.1. Uproszczony schemat dla systemu operacyjnego działającego w trybie z wywłaszczaniem
Rozdział 10. ♦ Programy wielowątkowe
381
Projektując aplikację zawierającą elementy wielowątkowości, należy zadbać o to, aby wykonywane wątki jak najmniej czasu spędzały w stanie zawieszenia (zablokowania) i ja k najwięcej w stanie wykonywania.
Funkcja CreateThread() Podstawową funkcją Windows API, tworzącą nowy watek, jest CreateThread(). Funk cja wątku przyjmuje parametr typu LPVOID i zwraca wartość typu całkowitego, będącą kodem zakończenia wątku. M oduł system.hpp definiuje omawianą funkcję w następu j ący sposób: HANDLE C r e a t e T h r e a d ( IN LPSE CU RI TY _A TT RI B U TE S l p T h r e a d A t t r i b u t e s , IN DWORD d w S t a c k S i z e , IN LPTHREAD_START_ROUTINE l p S t a r t A d d r e s s , IN LPVOID l p P a r a m e t e r , IN DWORD d w C r e a t i o n F l a g s , OUT LPDWORD l p T h r e a d I d ) ;
Funkcja CreateThread() rozpoczyna w ątek w program ie w ielow ątkow ym poprzez wywołanie funkcji ThreadFunc() w kontekście nowego wątku. W ątek zostaje zakoń czony w chwili powrotu z funkcji. CreateThread() zwraca identyfikator (tzw. uchwyt) nowego wątku lub pusty wskaźnik, jeżeli Windows nie je st w stanie utworzyć nowe go wątku. C reateThread() definiuje ponadto parametry ThreadFunc i odwołaniowy ThreadID (tzw. pseudoidentyfikator wątku) w stylu języka C++. Jeżeli parametr Cre ationFlags zaw iera STACK_SIZE_IS_A_RESERVATION, to StackSize jest rozmiarem sto su zarezerwowanym dla nowego wątku. Param etr C reationFlags podaje odpowiedni znacznik kontroli sposobu utworzenia nowego wątku. Jeżeli wyspecyfikujemy parametr CREATE_SUSPENDED, działanie wątku będzie zawieszone (wstrzymane) do czasu wywo łania funkcji API Windows ResumeThread(). Jeżeli zostanie mu przypisana wartość 0, nowy wątek zostanie natychmiast uruchomiony. Podobnie ja k w przypadku innych zasobów systemu Windows, po zakończeniu wątku należy obowiązkowo wywołać funkcję CloseHandle(), aby mieć pewność, że Windows zwolni wszystkie zasoby związane z wątkiem. C++ powoduje mały przeciek zasobów, jeżeli wątek zostanie uruchomiony w trybie zawieszenia, a następnie zamknięty bez ponowienia pracy. Aby uniknąć tego przecieku, należy zawsze wznawiać pracę wątku przed jego zakończeniem. W ielką zaletą posługiwania się funkcją CreateThread() jest to, że możemy w niej od wołać się do standardowej funkcji C++, która dzięki temu będzie mogła być potrakto w ana jako osobny wątek. Rolę takiej funkcji z powodzeniem może odgrywać wskaź nik do funkcji: DWORD WINAPI T h r e a d F u n c ( I N
LPVOID p a r a m e t e r )
ThreadFunc definiuje pew ien wskaźnik do funkcji typu DWORD z argumentem w postaci wskaźnika ogólnego. Nazwa funkcji w momencie użycia jest traktowana jako adres startowy nowego w ątku (obiektu Win32 lub Win64).
382
USB. Praktyczne programowanie z Windows API w C++
Ilustrację w ykorzystania w praktyce funkcji CreateThread() stanowi stworzenie nie skomplikowanej aplikacji, której zadaniem będzie wyświetlenie w osobnym wątku w oknie tekstowym zawartości kolejnych raportów wejściowych. W tym celu zostanie zaprojektowana abstrakcyjna klasa Thread, zawierająca czysto w irtualną funkcję runThread(), statyczną funkcję ThreadFunc() i funkcję beginThread(), której wartością powrotną jest wynik wykonania funkcji CreateThread(). Sposób wykonania tego za dania został przedstawiony na rysunku 10.2 i listingu 10.1. Kod wątku zostanie zapi sany w implementacji funkcji runThread() w klasie dziedziczącej TThreadUSBPort, tak ja k pokazano na listingu 10.2.
Rysunek 10.2. Logiczny diagram klas programu wielowątkowego Listing 10.1. Kod modułu usb_R10_1.h zawierający implementację funkcji składowych klas z rysunku 10.2 # i f n d e f usb_R 10_ 1H # d e f i n e usb_R 10_ 1H # i n c l u d e #pragma o p t i o n
p u s h -a 1
# in c lu d e < s e tu p a p i> #pragma o p t i o n
pop
# in c lu d e # in c lu d e # d e fin e sea rch M a xD e vices
//
10 //m aksym aln a l i c z b a
i n t e r f e j s ó w urządzeń
-------------------------------------------------------------------
t e m p l a t e < c l a s s T> in lin e
v o i d r e l e a s e M e m o r y ( T &x)
{ assert(x de le te
[]
x = NULL;
!= N U L L ) ; x;
Rozdział 1c. ♦ Programy wielowątkowe
j
//
3a3
-----------------------------------------------------------------
t y p e d e f USHORT USAGE, *PUSAGE; t y p e d e f s t r u c t _HIDP_PREPARSED_DATA *PHIDP_PREPARSED_DATA;
//
-----------------------------------------------------------------
t y p e d e f s t r u c t _ HI D P_C A PS { USAGE U sa g e ; USAGE U s a g e P a g e ; USHORT I n p u t R e p o r t B y t e L e n g t h ; USHORT O u t p u t R e p o r t B y t e L e n g t h ; USHORT F e a t u r e R e p o r t B y t e L e n g t h ; USHORT R e s e r v e d [ 1 7 ] ; USHORT N u m b e r L i n k C o l l e c t i o n N o d e s ; USHORT N u m b e r I n p u t B u t t o n C a p s ; USHORT N u m b e r I n p u t V a l u e C a p s ; USHORT N u m b e r I n p u t D a t a I n d i c e s ; USHORT N u m b e r O u t p u t B u t t o n C a p s ; USHORT N u m b e r O u t p u t V a l u e C a p s ; USHORT N u m b e r O u t p u t D a t a I n d i c e s ; USHORT N u m b e r F e a t u r e B u t t o n C a p s ; USHORT N u m b e r F e a t u r e V a l u e C a p s ; USHORT N u m b e r F e a t u r e D a t a I n d i c e s ; j HID P_ CA PS , * PH ID P_ CA PS; / / ------------------------------------------------------------------------------------------------------------t y p e d e f s t r u c t _DEVI CE_ DA TA { TCHAR
*Hardw areId;
TCHAR
*Path;
DWORD
D e vice In sta n ce ;
HANDLE
h id D e v ic e O b je c t;
BYTE
* in p u tR e p o rtB u ffe r;
USHORT
in p u tR e p o rtB y te L e n g th ;
PHIDP_PREPARSED_DATA p r e p a r s e d D a t a ; HIDP_CAPS c a p a b i l i t i e s ; j DE VIC E_DATA,
//
*PDEVICE_DATA;
-----------------------------------------------------------------
cla ss
TUSBD evice {
p riv a te : UINT fM e m b e r I n d e x ; v o id d is p la y E r r o r ( c o n s t
c h a r * m sg ) ;
p u b lic : PDEVICE_DATA d e v i c e L i s t ; BOOL g e t H i d D e v i c e C a p a b i l i t i e s ( U I N T m e m b e r I n d e x ) ; UINT s e t G e t H i d D e v i c e D a t a ( ) ; HANDLE o p e n H i d U S B D e v i c e ( U I N T m e m b e r I n d e x ) ; BOOL r e a d U S B R e p o r t ( U I N T m e m b e r I n d e x ) ; BOOL c l o s e H i d U S B D e v i c e ( H A N D L E d e v H a n d l e ) ; TU SBD evice(UINT m em berIndex); -TUSBDevi c e ( ) ; j;
//
-----------------------------------------------------------------
cla ss
Thread
{
p riv a te : s ta tic
DWORD WINAPI T h r e a d F u n c ( v o i d * p a r a m e t e r )
(re in te rp re t_ ca st< T h re a d return
O;
j p u b lic : t y p e d e f DWORD t h r e a d I D ;
{
* > (param eter))-> runThread();
38 4
USB. Praktyczne programowanie z Windows API w C++
HANDLE b e g i n T h r e a d ( ) { thread ID id ; return
CreateThread(N ULL,
0,
ThreadFunc,
CREATE_SUSPENDED, } v irt u a l
v o id runThread ()
v o id resum eThread() v o id c lo s e H a n d le () };
//
th is ,
& id );
= 0;
{Resu m eTh rea d(b egin Th rea d());} { C lo s e H a n d le (b e g in T h re a d ());}
-----------------------------------------------------------
cla ss
TThreadUSBPort:
p u b l i c Thread
{ p u b lic : UINT i n t e r f a c e I n d e x ; v o id ru n T h re a d (); };
//
-----------------------------------------------------------
# e n d if
Listing 10.2. Kod modułu usb_R10_1.cpp # in c lu d e # in clu d e
"usb_R 10_1.h"
u s i n g n a m es p ac e s t d ;
/ / -------------------------------------------------------------------------T U S B D e v ice ::T U S B D e v ice (U IN T memberlndex) { fM em berlndex = memberlndex; d e v i c e L i s t = new \ D E VIC E _ D A T A [((fM em b erIn d ex+ 1 )* sizeo f(D E VIC E _D A T A))]; }
//
-------------------------------------------------------------------
TU SBD evice ::~ T U SB D evice() { re le a s e M e m o ry (d e v ic e L ist); }
/ / ----------------------------------------------------------------------------------------------------------------v o id T U S B D e v ic e ::d is p la y E rro r( c o n s t
c h a r * msg)
{ c o u t << msg << e n d l ; system ("PAUSE"); e x it(0 ); };
//
-------------------------------------------------------------------
BOOL TU SBD ev i c e : : g e t H i d D e v i c e C a p a b i l i t i e s ( U I N T m em be rI n de x) { HMODULE h H i d L i b ; bool
statu s;
lo n g
(
s td c a ll*
H id P _G e tC a p s)(IN
PHIDP_PREPARSED_DATA P r e p a r s e d D a t a ,
OUT PHIDP_CAPS C a p a b i l i t i e s ) ; bool
(
s td c a ll*
H i d D _ G e t P r e p a r s e d D a t a ) ( I N HANDLE
H id D e v ice O b je c t,
OUT PHIDP_PREPARSED_DATA * P r e p a r s e d D a t a ) ; bool
(
s td c a ll*
H id D _ F r e e P re p a rs e d D a ta ) (IN PHIDP_PREPARSED_DATA P r e p a r s e d D a t a ) ;
Rozdział 1c. ♦ Programy wielowątkowe
3a5
h H id L ib = L o a d L ib ra ry ("C :\\W in d o w s \\S y s te m 3 2 \\H ID .D L L "); if
(!h H id L ib ) d is p la y E rro r("B łą d
d o łą c ze n ia b i b lio t e k i
H ID .D LL.");
(FARPROC&)
H id P _ G e tC a p s = G e tP ro c A d d re s s (h H id L ib ,"H id P _ G e tC a p s ");
(FARPROC&)
H id D _ G e tP re p a rs e d D a ta = G e tP ro cA d d re s s(h H id L ib ,
(FARPROC&)
H id D _ F re e P re p a rs e d D a ta = G e tP ro cA d d re s s(h H id L ib ,
"H id D _ G e tP re p a rse d D a ta "); "H id D _ Fre e P re p a rse d D a ta "); if
(!H id P _G e tC a p s
jj
!H id D _ G e tP re p a rse dD a ta
jj
!H id D _ F re e P re p a rse d D a ta ){
F re e L ib ra ry ( h H id L ib ) ; d is p la y E rro r("N ie
z n a l e z i o n o żadnych f u n k c j i
e k sp orto w ych.\n");
j sta tu s= H id D _ G e tP re p a rse d D a ta (d e v ice L ist[m e m b e rIn d e x ].h id D e v ic e O b je c t, & d e v ice L is t-> p re p a rs e d D a ta ); if( s ta tu s ) { H id P _ G e tC a p s (d e v ic e L is t-> p re p a rs e d D a ta ,
& d e v ic e L is t-> c a p a b ilitie s );
d e v ic e L is t[m e m b e rI n d e x ].in p u tR e p o rtB y te L e n g th = devi c e L is t- > c a p a b ilitie s .I n p u tR e p o r tB y te L e n g th ; H id D _ F r e e P re p a rs e d D a t a ( d e v ic e L is t- > p re p a r s e d D a ta ) ; j F re e L ib ra ry ( h H id L ib ) ; return
statu s;
j
/ / ----------------------------------------------------------------------------------------------------------------UINT TU SBD ev i c e : : s e t G e t H i d D e v i c e D a t a ( ) { DWORD p r o p e r t y B u f f e r S i z e = O; c h a r * p r o p e r t y B u f f e r = NULL; HMODULE h H i d L i b ; SP_DEVINFO_DATA d e v i c e I n f o D a t a ; HDEVINFO d e v i c e I n f o S e t ; SP _INT E RF AC E_ DE V IC E_ DA TA d e v i c e I n t e r f a c e D a t a ; fM e m b e r I n d e x = O; GUID c l a s s G u i d ; P S P_ DE V IC E_ IN TE RF AC E_ DE TA IL _ DA TA d e v i c e I n t e r f a c e D e t a i l D a t a = NULL; DWORD r e q u i r e d S i z e = O; DWORD d e v i c e I n t e r f a c e D e t a i l D a t a S i z e = O; DWORD m a x D e v i c e = s e a r c h M a x D e v i c e s ; bool
do ne = f a l s e ;
v o id
(
s td c a ll
* H id D _ G e tH id G u id )(O U T
LPGUID H i d G u i d ) ;
h H id L ib = L o a d L ib ra ry ("C :\\W in d o w s \\S y s te m 3 2 \\H ID .D L L "); if
(!h H id L ib ) d is p la y E rro r("B łą d
(FARPROC&) if
d o łą c ze n ia b i b lio t e k i
H ID .D LL.");
H id D _ G e tH id G u id = G e t P r o c A d d r e s s ( h H id L ib , " H id D _ G e t H id G u id " ) ;
(!H id D _ G e tH id G u id ){ F re e L ib ra ry ( h H id L ib ) ; d is p la y E rr o r(" N ie
j H id D _ G e tH id G u id
zn a le zio n o
id e n ty fik a to r a
G U ID .");
(& cla ssG u i d);
d e v ic e I n fo S e t = S e tu p D iG e tC la s s D e v s (& c la s s G u id , (DIGCF_PRESENT
N ULL ,
NULL,
j DIGCF_DEVICEINTERFACE));
d e v i c e I n t e r f a c e D a t a . c b S i z e = s izeo f(SP_IN TE R F A C E_D EV IC E_D A TA );
386
USB. Praktyczne programowanie z Windows API w C++
w h ile (!d o n e ) fo r(;
{
fM e m b e r I n d e x < m a x D e v i c e ;
fM em ber In de x+ + )
{
if(S e tu p D iE n u m D e v i c e I n t e r f a c e s ( d e v i c e I n f o S e t , 0 , & c la s s G u i d , fM e m b e rIn d e x ,& d e vice In te rfa ce D a ta ))
{
S e tu p D iG e t D e v ic e I n te rfa c e D e t a il(d e v ic e In fo S e t,& d e v ic e In te rfa c e D a ta , N U LL ,0 ,& d e vice In te rfa ce D e ta i lD a ta S iz e , N U LL ) ; re q u ire d S iz e = d e v ic e I n te rfa c e D e ta ilD a ta S iz e ; d e v i c e I n t e r f a c e D e t a i l D a t a = ( P S P _ D E V I C E _ IN T E R F A C E _ D E T A IL _ D A T A ) \ new D W O R D [ d e v i c e I n t e r f a c e D e t a i l D a t a S i z e ] ; if ( d e v i ce In te rfa c e D e ta ilD a ta )
{
devi c e In te rfa c e D e ta ilD a ta -> c b S iz e = \ sizeo f(SP_IN TE RF ACE_D EV ICE_D ETAIL_D AT A); } e lse
{ S etu p D iD e stro y D e v i c e I n f o L i s t ( d e v i c e I n fo S e t) ; releaseM em ory(d evi c e I n t e r f a c e D e t a ilD a t a ) ; return
0;
} d e v i c e I n f o D a t a . c b S i z e = s iz e o f ( S P _ D E V I N F O _ D A T A ) ; if(!S e tu p D iG e tD e v ic e In te rfa c e D e ta il(d e v i ce Info Se t, & d e v ice In te rface D a ta ,
d e v ic e In te rfa c e D e ta ilD a ta ,
d e v ic e In te rfa c e D e ta ilD a ta S iz e , & re q u ire d S iz e ,
& d eviceIn fo D ata))
{
S etu p D iD e stro y D e v i c e I n f o L i s t ( d e v i c e I n fo S e t) ; releaseM em ory(d evi c e I n t e r f a c e D e t a ilD a t a ) ; re turn
0;
} s i z e _ t n Le n = s t r l e n ( d e v i c e I n t e r f a c e D e t a i l D a t a - > D e v i c e P a t h ) d e v ic e L is t[fM e m b e rIn d e x ].P a th
+ 1;
= new T C H A R [ ( n L e n * s i z e o f ( T C H A R ) ) ] ;
s trn c p y (d e v ic e L is t[fM e m b e r In d e x ].P a th , de v ice In te rfa ce D e ta ilD a ta -> D e v ice P a th ,
nLen);
d e v ic e L is t[fM e m b e rIn d e x ].D e v ic e In s ta n c e = d e v ic e In fo D a ta .D e v In s t; S e tu p D iG e tD e v ic e R e g is try P ro p e rty (d e v i c e In fo S e t,
& d eviceIn foD ata,
SPDRP_HARDWAREID, NU LL,
NU LL,
0,
& p ro p e rty B u ffe r S ize );
/ / a l o k o w a n i e p a m i ę c i d l a b u f o r a danych p r o p e r t y B u f f e r = new c h a r [ ( p r o p e r t y B u f f e r S i z e * s i z e o f ( T C H A R ) ) ] ; S e tu p D iG e tD e v ic e R e g is try P ro p e rty (d e v i c e In fo S e t,
& d eviceIn foD ata,
SPDRP_HARDWAREID,NULL, p ropertyB u ffe r,
p ro p e rty B u ffe rS iz e ,
N ULL); d e v ic e L is t[fM e m b e rIn d e x ].H a rd w a re Id
= pro pertyB u ffe r;
releaseM em ory(d evi c e I n t e r f a c e D e t a i lD a t a ) ; re le a se M e m o ry(p ro p e rty B u ffe r); } e lse
{ i f(ERROR_NO_MORE_ITEMS == G e t L a s t E r r o r ( ) ) { d on e = TRUE; break;
} } }
Rozdział 10. ♦ Programy wielowątkowe
387
} S etu p D iD e stro y D e v i c e I n fo L i s t ( d e v i c e ln f o S e t ) ; F re e L ib ra ry ( h H id L ib ) ; return
fM e m b e r I n d e x ;
}
/ / ----------------------------------------------------------------------------------------------------------------HANDLE T U S B D e v i c e : : o p e n H i d U S B D e v i c e ( U I N T m e m b e rl n d e x ) { d e v i c e L i s t [ m e m b e r I n d e x ] . h i d D e v i c e O b j e c t == IN VAL ID_ HANDLE_ VALUE; d e v ic e L is t[m e m b e rIn d e x ].h id D e vic e O b je c t = C r e a t e F ile ( d e v i ce List[m e m b e rInd e x].P a th , GENERIC_READ | GENERIC_WRITE, FILE_SHA RE _RE AD
| FILE_ SH ARE _WR ITE,
N ULL,OPEN_EXISTING,FILE_FLAG_OVERLAPPED,NULL); if(d e v ic e L ist[m e m b e rIn d e x ].h id D e v ic e O b je c t return
!= INVALID_HANDLE_VALUE)
d e v ic e L is t[m e m b e rIn d e x ].h id D e vic e O b je c t;
e lse return
IN VALID_HANDLE_VALUE;
}
/ / ----------------------------------------------------------------------------------------------------------------BOOL T U S B D e v i c e : : c l o s e H i d U S B D e v i c e ( H A N D L E d e v H a n d l e ) { i f ( ( d e v H a n d l e == 0)
||
( d e v H a n d l e == INVA L ID _ HA ND L E_ VA L U E) ){ re turn
fa lse ;
} e lse re turn
C lo se H a n d le (d e v H a n d le );
}
/ / ----------------------------------------------------------------------------------------------------------------BOOL T U S B D e v i c e : : r e a d U S B R e p o r t ( U I N T m em be rI n de x) { DWORD r e s u l t
= 0;
DWORD n u m b e r O f B y t e s R e a d = 0; OVERLAPPED » o v e r l a p p e d = NULL; i f ( o v e r l a p p e d == NULL) { o v e r l a p p e d = new OVERLAPPED; overlappe d-> hEvent = C reateEvent(N U LL,
TRUE, TRUE,
"");
o v e r l a p p e d - > O f f s e t = 0; ov e rla p p e d -> O ffs e tH ig h
= 0;
} m e m s e t(d e v ic e L is t[m e m b e rI n d e x ].in p u tR e p o rtB u ffe r,
0x00,
d e v ic e L is t[m e m b e rIn d e x ].in p u tR e p o rtB y te L e n g th ); if( !R e a d F ile ( d e v ic e L is t[ m e m b e rI n d e x ] .h id D e v ic e O b je c t, d e v ic e L is t[m e m b e rIn d e x ].in p u tR e p o rtB u ffe r, d e v ic e L ist[m e m b e rIn d e x].in p u tR e p o rtB y te L e n g th , &num berO fBytesRead, if(G e tL a s tE rro r() re s u lt
ove rla p p e d ))
== ERROR_IO_PENDING)
{
{
= W a itF o rS in g le O b je c t(o ve rla p p e d -> h E ve n t,
if( re s u lt
== WAIT_TIMEOUT)
IN FIN ITE);
{
C a n c e lI o (d e v i c e L ist[m e m b e rIn d e x ].h id D e v i c e O b j e c t ) ; return
fa lse ;
} e lse if( r e s u lt
== W A I T _ F A I L E D ) {
d is p la y E rro r("B łą d return
}
fa lse ;
odczytu d a n y ch .");
388
USB. Praktyczne programowanie z Windows API w C++
G e tO v e rla p p e d R e su lt(d e v ic e L is t[m e m b e rIn d e x ].h id D e v ic e O b je c t, o v e rla p p e d ,
& num berO fBytesRead,
FALSE);
} e lse d is p la y E rro r("B ią d
odczytu d a n y ch .");
} R e se tE v e n t(o v e rla p p e d -> h E v e n t); i f ( n u m b e r O f B y t e s R e a d == ( U I N T ) d e v i c e L i s t [ m e m b e r I n d e x ] . i n p u t R e p o r t B y t e L e n g t h ) { fo r ( U S H O R T i= 0 ; p rin tf("% d
i< d e v i c e L i s t [ m e m b e r I n d e x ] . i n p u t R e p o r t B y t e L e n g t h ;
",
i++)
d e v i c e L is t [ m e m b e r I n d e x ] . i n p u t R e p o r t B u f f e r [ i] );
p r in tf( " \ n " ) ; } e ls e
{
p r in tf(" B ię d n a
lic z b a
odebranych b a jtó w .\n ",n u m b e rO fB y te s R e a d );
} d e le te overlappe d; return
true;
}
/ / ---------------------------------------------------------------------------------v o id TThreadUSBPort::runThread()
{
/ / S t w o r z e n i e anonimowego o b i e k t u k l a s y TUSBDevice z a ktu a ln y m indeksem //n u m e r u i n t e r f e j s u T U S B D e v i c e * u s b D e v i c e = new T U S B D e v i c e ( i n t e r f a c e I n d e x ) ; usbDevi c e - > s e t G e t H id D e v ic e D a t a ( ) ; cout < < "\n D e v ic e L is t["< < in te rfa c e I n d e x < < "].P a th :
\ n " <<
u s b D e v i c e - > d e v i c e L i s t [ i n t e r f a c e I n d e x ] . P a t h << e n d l ; usb D evi ce -> openHidUSB Devi c e ( i n t e r f a c e I n d e x ) ; if
(u sb D e v ic e -> g e tH id D e v i c e C a p a b ili t i e s ( i n t e r f a c e I n d e x ) )
{
usbDevi c e - > d e v i c e L i s t [ i n t e r f a c e I n d e x ] . i n p u t R e p o r t B u f f e r = (ch a r*)H eapA lloc(G etPro cessH eap (),H EA P_ZER O _M EM O R Y , usb D evice-> devi c e L is t [ in t e r f a c e I n d e x ] . in p u t R e p o r t B y t e L e n g t h + 1 ) ; w h ile (u sb D e v i c e - > d e v ic e L is t [ in t e r f a c e I n d e x ] .in p u t R e p o r t B u f f e r [ 6 ] ! = 6 4 ) { usbDevi c e - > r e a d U S B R e p o r t ( in t e r f a c e I n d e x ) ; } u s b D e v ic e -> c lo s e H id U S B D e v ic e (u s b D e v ic e - > d e v ic e L is t[in te r fa c e In d e x ].\ h id D e v ic e O b je c t); } re le a se M e m o ry(u sb D e vice -> d e vice L ist[inte rfa ce Ind e x].P a th ); re le a s e M e m o ry (u s b D e v ic e -> d e v ic e L ist[in te rfa c e In d e x ].in p u tR e p o rtB u ffe r);
//H ea pF re e (G e tP ro c e s s H e a p (),0 ,u s b D e v ic e -> d e v ic e L is t[in te rfa c e In d e x ].\ // in p u tR e p o rtB u ffe r); d e le te
u sb D evice;
}
/ / ----------------------------------------------------------------------------------------------------------------T T h r e a d U S B P o r t * u s b P o r t T h r e a d = NULL;
/ / ----------------------------------------------------------------------------------------------------------------i n t m ain () { u s b P o r t T h r e a d = new T T h r e a d U S B P o r t ( ) ;
/ / N a l e ż y w pisać żąd any inde ks i n t e r f e j s u d l a w y b ra n e j k o n f i g u r a c j i / / t e s t o w a n e g o u r z ą d z e n i a USB ( p a t r z r o z d z i a ł 6 . , ry s u n e k 6 .1 ) u s b P o r t T h r e a d - > i n t e r f a c e I n d e x = 1; u sb P o rtT h re a d -> b e g in T h re a d ();
Rozdział 10. ♦ Programy wielowątkowe
389
usbPortT hread-> resum eThread(); c o u t <<"Aby z a k o ń c z y ć o d c z y t , " oraz n a c iś n ij
n a c iś n ij
p rz y c is k
[11]
na k o n s o l i "
E n te r...\n ";
c in .g e t(); u sb P o rtT h re a d -> c lo s e H a n d le (); d e le te
usb PortT hread;
return
0;
}
/ / ---------------------------------------------------------------------------------Rysunek 10.3. Aplikacja proj_USB_R10_1 w trakcie cyklicznego odczytu raportu wejściowego
USB.Praktyczne programowanie\Rozdział 10\R10_l\proj_USB_R10_l.exe łb y zakon czyc o dczyt
n a c iś n ij
p r z y c i s k 11 na k o n s o li o r a z n a c i ś n i j
E n t e r ...
D evi c e L i s t [ 1 ] . P a t h : V \ ? \ h i d#vi d_22ba&pid__0108#8A5 9 f 5 d 85 &MOOOO#{4 d le S 5 b 2 - f l 6 f - l l c f - 8 8 c b - 001111000030} 0 156 127 128 128 80 0 0 155 127 128 128 80 0 0 154 127 128 128 80 0 0 153 127 128 128 80 0 0 152 127 128 128 80 0 0 151 127 128 128 80 0 D 148 127 128 128 80 0 0 147 127 128 128 80 0 0 144 127 128 128 80 0 0 142 127 128 128 80 0 0 138 127 128 128 80 0 0 136 127 128 128 80 0 0 132 127 128 128 80 0 0 130 127 128 128 80 0 0 128 127 128 128 80 0 0 126 127 128 128 80 0
Klasa TThread Kolejnym sposobem stworzenia aplikacji wielowątkowej w C++ jest skorzystanie z kla sy wątku dziedziczącej po abstrakcyjnej klasie TThread. cla ss
TThread
: p u b lic S y stem ::TO bject
Klasa TThread, jako że nie stanowi części języka C++, jest zadeklarowana w module classes.hpp. Instrukcje lub funkcje wykonywane przez wątek należy umieścić w prze słoniętej funkcji Execute(). Zakończenie funkcji Execute() oznacza zakończenie wątku. Dowolny wątek może stworzyć kolejny wątek poprzez utworzenie następnego obiek tu potomnej klasy wątku. Każdy egzem plarz klasy je st wykonywany jako oddzielny wątek z własnym stosem. N a listingach 10.3 i 10.4 zaprezentowano odpowiednio kod m odułu zawierającego implementację klas i struktur z rysunku 10.4 oraz kod modułu z implementacją funk cji składowych omawianych klas. Listing 10.3. Moduł usb_R10_2.h jako implementacja logicznej struktury klas i struktur z rysunku 10.3 # i f n d e f usb_R 10_ 2H # d e f i n e usb_R 10_ 2H # in c lu d e # in c lu d e < c la sse s.h p p > # d e fin e sea rch M a xD e vices
10 //m aksym aln a l i c z b a
i n t e r f e j s ó w urządzeń
u s i n g n a m es p ac e s t d ; / / -----------------------------------------------------------------------------------------------------------------
390
USB. Praktyczne programowanie z Windows API w C++
« C p p S tru c t» HIDP CAPS
«C ppS ynonym »
łlnputReportByteLength: USHORT +OutputReportByteLength: USHORT + UsagePage +FeatureReportByteLength: USHORT ^ « C p p T y p e d e f» +Reserved: USHORT USAGE +Usage +Nu mberLmkCol lection Nodes: USHORT +NumberlnputButtonCaps: USHORT +NumberlnputValueCaps: USHORT łNum berlnputDatalndices: USHORT +NumberOutputButtonCaps: USHORT +NumberOutputValueCaps: USHORT « C p p T y p e d e f» +NumberOutputDatalndices: USHORT PHIDP CAPS « C p p S y no n y m » łNum berFeatureButtonCaps: USHORT +NumberF 0 atureValueCaps: USHORT łNum berFeatureDatalndices: USHORT
T T h re ad U S B P o rt
TU S B D evice
-fRunThread: UINT -fMemberlndex: UINT
« C p p T y p e d e f» HIDP CAPS
« C p p T y p e d e f» DEVICE DATA
-»-capabilities
«C ppS ynonym »
« C p p S tru c t» DEVICE DATA +Hardwareld: TCHAR +Path: TCHAR +Device Instance: DWORD +hidDeviceObject: HANDLE +inputReportBuffer: BYTE ■HnputReportByteLength: USHORT
TT
« C p p T y p e d e f» PHIDP PREPARSED DATA
-fMemberlndex: UINT -displayError() +getHidDeviceCapabilities() +setGetH id Device DataQ +openHidUSBDevice() +readUSBReport() +closeHidUSBDeviceO « c re ate » + T U S B D e vic e() « d e stroy> > +TU SBDevic e [)
#Execute() « e r e ate » + T T h re ad U SBPortQ
« C p p T y p e d e f» P USAGE
+pre parsed Data
«C ppS ynonym »
+deviceList « C p p T y p e d e f» PDEVICE DATA
^
«C ppS ynonym » ;
Rysunek 10.4. Logiczna struktura wielowątkowego programu korzystającego z abstrakcyjnej klasy TThread t e m p l a t e < c l a s s T> in lin e
v o i d r e l e a s e M e m o r y ( T &x)
{ assert(x de le te
[]
!= N U L L ) ; x;
x = NULL; }
/ / ----------------------------------------------------------------------------------------------------------------t e m p l a t e < c l a s s T> in lin e
v o i d r e l e a s e T h r e a d ( T &x)
{ assert(x
!= N U L L ) ;
x-> T e rm in a te (); d e l e t e x; x = NULL; }
/ / ---------------------------------------------------------------------------------t y p e d e f USHORT USAGE, *PUSAGE; t y p e d e f s t r u c t _HIDP_PREPARSED_DATA *PHIDP_PREPARSED_DATA;
/ / ----------------------------------------------------------------------------------------------------------------t y p e d e f s t r u c t _ HI D P_C A PS { USAGE U sa g e ; USAGE U s a g e P a g e ; USHORT I n p u t R e p o r t B y t e L e n g t h ; USHORT O u t p u t R e p o r t B y t e L e n g t h ; USHORT F e a t u r e R e p o r t B y t e L e n g t h ; USHORT R e s e r v e d [ 1 7 ] ; USHORT N u m b e r L i n k C o l l e c t i o n N o d e s ; USHORT N u m b e r I n p u t B u t t o n C a p s ; USHORT N u m b e r I n p u t V a l u e C a p s ; USHORT N u m b e r I n p u t D a t a I n d i c e s ; USHORT N u m b e r O u t p u t B u t t o n C a p s ;
___
« C p p S tru c t» HIDP PREPARSED DATA
Rozdział 10. ♦ Programy wielowątkowe
391
USHORT N u m b e r O u t p u t V a l u e C a p s ; USHORT N u m b e r O u t p u t D a t a I n d i c e s ; USHORT N u m b e r F e a t u r e B u t t o n C a p s ; USHORT N u m b e r F e a t u r e V a l u e C a p s ; USHORT N u m b e r F e a t u r e D a t a I n d i c e s ; } HID P_ CA PS ,
* PH ID P_ CA PS;
/ / -------------------------------------------------------------------------t y p e d e f s t r u c t _DEVI CE_ DA TA { TCHAR
*Hardw areId;
TCHAR
*Path;
DWORD
D e vice In sta n ce ;
HANDLE
h id D e v ic e O b je c t;
BYTE
* in p u tR e p o rtB u ffe r;
USHORT
in p u tR e p o rtB y te L e n g th ;
PHIDP_PREPARSED_DATA p r e p a r s e d D a t a ; HIDP_CAPS c a p a b i l i t i e s ; } DE VIC E_DATA, *PDEVICE_DATA; / / -----------------------------------------------------------------------------------------------------cla ss
TUSBD evice {
p riv a te : UINT fM e m b e r I n d e x ; v o id d is p la y E r r o r ( c o n s t
c h a r * m sg ) ;
p u b lic : PDEVICE_DATA d e v i c e L i s t ; BOOL g e t H i d D e v i c e C a p a b i l i t i e s ( U I N T m e m b e r I n d e x ) ; UINT s e t G e t H i d D e v i c e D a t a ( ) ; HANDLE o p e n H i d U S B D e v i c e ( U I N T m e m b e r I n d e x ) ; BOOL r e a d U S B R e p o r t ( U I N T m e m b e r I n d e x ) ; BOOL c l o s e H i d U S B D e v i c e ( H A N D L E d e v H a n d l e ) ; TU SBD evice(UINT m em berIndes); -TUSBDevi c e ( ) ; };
/ / -----------------------------------------------------------------------------------------------------cla ss
TThreadUSBPort:
p u b l i c TThread
{ p riv a te : TUSBD evice * u s b D e v ic e ; UINT * f R u n T h r e a d ; UINT f M e m b e r I n d e x ; protected: v o id
fa s tc a ll
Execute();
p u b lic : TThreadUSBPort(BO O L C re a t e S u sp e n d e d ,
UINT * r u n T h r e a d ,
UINT m e m b e r I n d e x ) ; };
/ / -----------------------------------------------------------------------------------------------------# e n d if
Listing 10.4. Kod modułu usb_R10_2.cpp # i n c l u d e #pragma o p t i o n
p u s h -a 1
# in c lu d e < s e tu p a p i> #pragma o p t i o n
pop
# in c lu d e # in clu d e
" u s b R10 2 . h "
using namespace std;
392
USB. Praktyczne programowanie z Windows API w C++
/ / ----------------------------------------------------------------------------------------------------------------T U S B D e v i c e : : T U S B D e v i c e ( U I N T m em be rI nd e x) { f M e m b e r I n d e x = m em b e rI n d e x; d e v i c e L i s t = new \ D E VIC E_ D A T A [((m em berInd ex+ 1)*sizeof(D EV IC E _D A T A ))] ; j
/ / ----------------------------------------------------------------------------------------------------------------TU SBD evice ::~ T U SB D evice() { re le a s e M e m o ry (d e v ic e L ist); j
/ / ----------------------------------------------------------------------------------------------------------------v o id T U S B D e v ic e ::d is p la y E rro r( c o n s t
c h a r * msg)
{ c o u t << msg << e n d l ; system ("PAUSE"); e x it(O ); j;
/ / ----------------------------------------------------------------------------------------------------------------BOOL TU SBD ev i c e : : g e t H i d D e v i c e C a p a b i l i t i e s ( U I N T m em be rI n de x) { HMODULE h H i d L i b ; bool
statu s;
lo n g
(
s td c a ll*
H id P _G e tC a p s)(IN
PHIDP_PREPARSED_DATA P r e p a r s e d D a t a ,
OUT PHIDP_CAPS C a p a b i l i t i e s ) ; bool
(
s td c a ll*
H i d D _ G e t P r e p a r s e d D a t a ) ( I N HANDLE
H id D e v ice O b je c t,
OUT PHIDP_PREPARSED_DATA * P r e p a r s e d D a t a ) ; bool
(
s td c a ll*
H id D _ F r e e P re p a rs e d D a ta ) (IN PHIDP_PREPARSED_DATA P r e p a r s e d D a t a ) ;
h H id L ib = L o a d L ib ra ry ("C :\\W in d o w s \\S y s te m 3 2 \\H ID .D L L "); if
(!h H id L ib )
d is p la y E rro r("B łą d
d o łą c ze n ia b i b lio t e k i
H ID .D LL.");
(FARPROC&)
H id P _ G e tC a p s = G e tP ro cA d d re s s (h H id L ib ,
(FARPROC&)
H id D _ G e tP re p a rs e d D a ta = G e tP ro cA d d re s s(h H id L ib ,
"H id P _ G e tC a p s");
(FARPROC&)
H id D _ F re e P re p a rs e d D a ta = G e tP ro cA d d re s s(h H id L ib ,
"H id D _ G e tP re p a rse d D a ta "); "H id D _ Fre e P re p a rse d D a ta "); if
(!H id P _G e tC a p s
jj
!H id D _ G e tP re p a rse dD a ta
jj
!H id D _ F re e P re p a rse d D a ta ){
F re e L ib ra ry ( h H id L ib ) ; d is p la y E rro r("N ie
z n a l e z i o n o żadnych f u n k c j i
e k sp orto w ych.\n");
j s ta tu s = H id D _ G e tP re p a rs e d D a ta (d e v ic e L is t[m e m b e rIn d e x ].h id D e v ic e O b je c t, & d e v ice L is t-> p re p a rs e d D a ta ); if( s ta tu s ) { H id P _ G e tC a p s (d e v ic e L is t-> p re p a rs e d D a ta ,
& d e v ic e L is t-> c a p a b ilitie s );
d e v ic e L is t[m e m b e rI n d e x ].in p u tR e p o rtB y te L e n g th = d e v ic e L is t-> c a p a b ilitie s .In p u tR e p o rtB y te L e n g th ; H id D _ F r e e P re p a rs e d D a t a ( d e v ic e L is t- > p re p a r s e d D a ta ) ; j F re e L ib ra ry ( h H id L ib ) ; return j
statu s;
Rozdział 10. ♦ Programy wielowątkowe
393
/ / ----------------------------------------------------------------------------------------------------------------UINT TU SBD ev i c e : : s e t G e t H i d D e v i c e D a t a ( ) { DWORD p r o p e r t y B u f f e r S i z e = 0; c h a r * p r o p e r t y B u f f e r = NULL; HMODULE h H i d L i b ; SP_DEVINFO_DATA d e v i c e I n f o D a t a ; HDEVINFO d e v i c e I n f o S e t ; SP _INT E RF AC E_ DE V IC E_ DA TA d e v i c e I n t e r f a c e D a t a ; fM e m b e r I n d e x = 0; GUID c l a s s G u i d ; P S P_ DE V IC E_ IN TE RF AC E_ DE TA IL _ DA TA d e v i c e I n t e r f a c e D e t a i l D a t a = NULL; DWORD r e q u i r e d S i z e = 0; DWORD d e v i c e I n t e r f a c e D e t a i l D a t a S i z e = 0; DWORD m a x D e v i c e = s e a r c h M a x D e v i c e s ; bool
do ne = f a l s e ;
v o id
(
s td c a ll
* H id D _ G e tH id G u id )(O U T
LPGUID H i d G u i d ) ;
h H id L ib = L o a d L ib ra ry ("C :\\W in d o w s \\S y s te m 3 2 \\H ID .D L L "); if
(!h H id L ib ) d is p la y E rro r("B łą d
(FARPROC&)
d o łą c ze n ia b i b lio t e k i
H ID .D LL.");
H id D _ G e tH id G u id = G e t P r o c A d d r e s s ( h H id L ib , " H idD _ G e tH idG u id ") ;
if
(!H id D _ G e tH id G u id ){ F re e L ib ra ry ( h H id L ib ) ; d is p la y E rr o r(" N ie
j H id D _ G e tH id G u id
zn a le zio n o
id e n ty fik a to r a
G U ID .");
(& cla ssG u i d);
d e v ic e I n fo S e t = S e tu p D iG e tC la s s D e v s (& c la s s G u id ,
N ULL ,
( d IGCF_PRESENT
NULL,
j DIGCF_DEVICEINTERFACE));
d e v i c e I n t e r f a c e D a t a . c b S i z e = s izeo f(SP_IN TE R F A C E_D EV IC E_D A TA ); w h ile (!d o n e ) fo r(;
{
fM e m b e r I n d e x < m a x D e v i c e ;
fM em ber In de x+ + )
{
if(Se tupD iEnum D evi c e I n t e r f a c e s ( d e v ic e I n f o S e t , O ,& c la s s G u id , fM em berInde x,& de vi c e I n t e r f a c e D a t a ) )
{
S e tu p D iG e tD e v ic e In te rfa c e D e ta il(d e v ic e In fo S e t,& d e v ic e In te rfa c e D a ta , N U L L ,O ,& d e v ic e In te rfa c e D e ta ilD a ta S iz e , N U LL ); re q u ire d S iz e = d e v ic e I n te rfa c e D e ta ilD a ta S iz e ; d e v i c e I n t e r f a c e D e t a i l D a t a = ( P S P _ D E V I C E _ IN T E R F A C E _ D E T A IL _ D A T A ) \ new D W O R D [ d e v i c e I n t e r f a c e D e t a i l D a t a S i z e ] ; if ( d e v i ce In te rfa c e D e ta ilD a ta )
{
devi c e In te rfa c e D e ta ilD a ta -> c b S iz e = \ sizeo f(SP_IN TE RF ACE_D EV ICE_D ETAIL_D AT A); j e lse
{ S etu p D iD e stro y D e v i c e I n f o L i s t ( d e v i c e I n fo S e t) ; releaseM em ory(d evi c e I n t e r f a c e D e t a ilD a t a ) ; return
O;
j d e v i c e I n f o D a t a . c b S i z e = s iz e o f ( S P _ D E V I N F O _ D A T A ) ; if(!S e tu p D iG e tD e v ic e In te rfa c e D e ta il(d e v i ce Info Se t, & d e v ice In te rface D a ta ,
d e v ic e In te rfa c e D e ta ilD a ta ,
d e v ic e In te rfa c e D e ta ilD a ta S iz e , & re q u ire d S iz e ,
& d eviceIn fo D ata))
{
39 4
USB. Praktyczne programowanie z Windows API w C++
S etu p D iD e stro y D e v i c e I n f o L i s t ( d e v i c e I n fo S e t) ; releaseM em ory(d evi c e I n t e r f a c e D e t a ilD a t a ) ; return
0;
} s i z e _ t n Le n = s t r l e n ( d e v i c e I n t e r f a c e D e t a i l D a t a - > D e v i c e P a t h ) d e v ic e L is t[fM e m b e rIn d e x ].P a th
+ 1;
= new T C H A R [ ( n L e n * s i z e o f ( T C H A R ) ) ] ;
s trn c p y (d e v ic e L is t[fM e m b e r In d e x ].P a th , d e v ice In te rfa ce D e ta ilD a ta -> D e v ice P a th ,
nLen);
d e v ic e L is t[fM e m b e rIn d e x ].D e v ic e In s ta n c e = d e v ic e In fo D a ta .D e v In s t; S e tu p D iG e tD e v ic e R e g is try P ro p e rty (d e v i c e In fo S e t,
& d eviceIn fo D ata,
SPDRP_HARDWAREID, NU LL,
NU LL,
0,
& p ro p e rty B u ffe r S iz e );
/ / a l o k o w a n i e p a m i ę c i d l a b u f o r a danych p r o p e r t y B u f f e r = new c h a r [ ( p r o p e r t y B u f f e r S i z e * s i z e o f ( T C H A R ) ) ] ; S e tu p D iG e tD e v ic e R e g is try P ro p e rty (d e v i c e In fo S e t,
& d eviceIn fo D ata,
SPDRP_HARDWAREID,NULL, propertyB u ffe r,
p ro p e rty B u ffe rS iz e ,
NULL); d e v ic e L is t[fM e m b e rIn d e x ].H a rd w a re Id
= pro pertyB u ffe r;
c o u t < < "\n D e v ic e L ist["< < fM e m b e rIn d e x < < "].H a rd w a re Id :
\ n " <<
d e v i c e L i s t [ f M e m b e r I n d e x ] . H a r d w a r e I d << e n d l ; releaseM em ory(d evi c e I n t e r f a c e D e t a i lD a t a ) ; re le a se M e m o ry(p ro p e rty B u ffe r); } e lse { i f(ERROR_NO_MORE_ITEMS == G e t L a s t E r r o r ( ) ) { d on e = TRUE; break; } } } } S etu p D iD e stro y D e v i c e I n fo L i s t ( d e v i c e I n fo S e t) ; F re e L ib ra ry ( h H id L ib ) ; return }
//
fM e m b e r I n d e x ;
-------------------------------------------------------------------
HANDLE T U S B D e v i c e : : o p e n H i d U S B D e v i c e ( U I N T m em be rI n de x) { d e v i c e L i s t [ m e m b e r I n d e x ] . h i d D e v i c e O b j e c t == IN VAL ID_ HANDLE_ VALUE; d e v ic e L is t[m e m b e rIn d e x ].h id D e vic e O b je c t = C r e a t e F ile ( d e v i ce List[m e m b e rInd e x].P a th , GENERIC_READ | GENERIC_WRITE, FILE_SHA RE _RE AD
| FILE_ SH ARE _WR ITE,
N ULL,OPEN_EXISTING,FILE_FLAG_OVERLAPPED,NULL); if(d e v ic e L ist[m e m b e rIn d e x ].h id D e v ic e O b je c t return e lse return
}
!= INVALID_HANDLE_VALUE)
d e v ic e L is t[m e m b e rIn d e x ].h id D e vic e O b je c t; IN VALID_HANDLE_VALUE;
Rozdział 10. ♦ Programy wielowątkowe
395
/ / ----------------------------------------------------------------------------------------------------------------BOOL T U S B D e v i c e : : c l o s e H i d U S B D e v i c e ( H A N D L E d e v H a n d l e ) { i f ( ( d e v H a n d l e == 0)
jj
( d e v H a n d l e == INVA L ID _ HA ND L E_ VA L U E) ){ re turn
fa lse ;
j e lse re turn
C lo se H a n d le (d e v H a n d le );
j
/ / ----------------------------------------------------------------------------------------------------------------BOOL T U S B D e v i c e : : r e a d U S B R e p o r t ( U I N T m em be rI n de x) { DWORD r e s u l t
= 0;
DWORD n u m b e r O f B y t e s R e a d = 0; OVERLAPPED * o v e r l a p p e d = NULL; i f ( o v e r l a p p e d == NULL) { o v e r l a p p e d = new OVERLAPPED; overlappe d-> hEvent = C reateEvent(N U LL,
TRUE, TRUE,
"");
o v e r l a p p e d - > O f f s e t = 0; ov e rla p p e d -> O ffs e tH ig h
= 0;
j m e m s e t(d e v ic e L is t[m e m b e rI n d e x ].in p u tR e p o rtB u ffe r,
0x00,
d e v ic e L is t[m e m b e rIn d e x ].in p u tR e p o rtB y te L e n g th ); if( !R e a d F ile ( d e v ic e L is t[ m e m b e rI n d e x ] .h id D e v ic e O b je c t, d e v ic e L is t[m e m b e rIn d e x ].in p u tR e p o rtB u ffe r, d e v ic e L ist[m e m b e rIn d e x].in p u tR e p o rtB y te L e n g th , &num berO fBytesRead, if(G e tL a s tE rro r() re s u lt
ove rla p p e d ))
== ERROR_IO_PENDING)
{
{
= W a itF o rS in g le O b je c t(o ve rla p p e d -> h E ve n t,
i f ( re s u lt
== WAIT_TIMEOUT)
100);
{
C a n c e lI o (d e v i c e L ist[m e m b e rIn d e x ].h id D e v i c e O b j e c t ) ; return
fa lse ;
j e lse if( r e s u lt
== W A I T _ F A I L E D ) {
d is p la y E rro r("B łą d return
odczytu d a n y ch .");
fa lse ;
j G e tO v e rla p p e d R e su lt(d e v ic e L is t[m e m b e rIn d e x ].h id D e v ic e O b je c t, o v e rla p p e d ,
& num berO fBytesRead,
FALSE);
j e lse d is p la y E rro r("B łą d
odczytu d a n y ch .");
j R e se tE v e n t(o v e rla p p e d -> h E v e n t); i f ( n u m b e r O f B y t e s R e a d == ( U I N T ) d e v i c e L i s t [ m e m b e r I n d e x ] . i n p u t R e p o r t B y t e L e n g t h ) { fo r ( U S H O R T i= 0 ; p rin tf("% d
i< d e v i c e L i s t [ m e m b e r I n d e x ] . i n p u t R e p o r t B y t e L e n g t h ;
",
d e v i c e L is t [ m e m b e r I n d e x ] . i n p u t R e p o r t B u f f e r [ i] );
p r in tf( " \ n " ) ; j e ls e
{
p rin tf("B łę d n a
lic z b a
odebranych b a jt5 w .\n ",n u m b e rO fB yte sR e a d );
j d e le te overlappe d; return
true;
j
/ / -----------------------------------------------------------------------------
i++)
396
USB. Praktyczne programowanie z Windows API w C++
TThreadUSBPort::TThreadU SBPort(BO O L C reateS u spend ed, UINT m e m b e r I n d e x ) :
UINT * r u n T h r e a d ,
TThread (CreateSuspended)
{ f M e m b e r I n d e x = m em b e rI n d e x; fRunThread = ru n T h re a d ; *fRunThread = tr u e ; u s b D e v i c e = new T U S B D e v i c e ( f M e m b e r I n d e x ) ; }
//
--------------------------------------------------------------------------
v o id
fa s tc a ll
T Thread USBPort::Execute()
{ try
{
/ / e n u m e r a c j a a k t u a l n i e po d łą c z o n y c h ur ząd zeń k l a s y HID u sb D evice-> setG etH id D e viceD ata(); co ut < < "\n D ev iceL ist["< < fM em berIn d ex< < "].Pa th :
\ n " <<
u s b D e v i c e - > d e v i c e L i s t [ f M e m b e r I n d e x ] . P a t h << e n d l ;
/ / o t w a r c i e p o r t u USB if(!u s b D e v ic e -> o p e n H id U S B D e v ic e ( fM e m b e r In d e x ) ) return; e lse
{
u s b D e v ic e -> g e tH id D e v ic e C a p a b ilitie s (fM e m b e rI n d e x ); u s b D e v i c e - > d e v i c e L i s t [ f M e m b e r I n d e x ] . i n p u t R e p o r t B u f f e r = new \ B Y T E [u sb D e vice -> d e vice L ist[fM e m b e rIn d e x ].in p u tR e p o rtB y te L e n g th ]; w h i l e ( ! T e r m i n a t e d && * f R u n T h r e a d
!= f a l s e
&&
u s b D e v i c e - > d e v i c e L i s t [ f M e m b e r I n d e x ] . i n p u t R e p o r t B u f f e r [ 6 ] !=6 4){
/ / o d c z y t r a p o r t ó w w ejściow y ch u sb D evice-> read U S B R e port(fM em be rInd ex); } } } __f in a lly
{
u sb D e v ic e -> c lo se H id U S B D e v ic e (u sb D e v ic e -> d e v ic e L is t[fM e m b e rIn d e x ].\ h id D e v ic e O b je c t); re le a s e M e m o ry(u s b D e vice -> d e vice List[fM e m b e rIn d e x]. P a t h ) ; re le a se M e m o ry(u sb D e vice -> d e vice L ist[fM e m b e rIn d e x].in p u tR e p o rtB u ffe r);
/ / w y w o ł a n i e d e s t r u k t o r a k l a s y TUSBDevice d e le te
u sb D evice;
} }
//
--------------------------------------------------------------------------
//
--------------------------------------------------------------------------
T T h r e a d U S B P o r t * t h r e a d U S B P o r t = NULL;
in t m a in ( ) { UINT i n t e r f a c e I n d e x = 1 ; / / n u m e r inde ksu i s t n i e j ą c e g o
i n t e r f e j s u u r z ą d z e n i a HID
UINT r u n U S B T h r e a d ; if
(runUSBThread
!= t r u e ) {
t h r e a d U S B P o r t = new T T h r e a d U S B P o r t ( t r u e ,
& runUSBThread,
in te rfa ce In d e x );
/ / t h r e a d U S B P o r t 1 - > P r i o r i t y = t p T i m e C r i t i c a l ; / / u s t a w p r i o r y t e t wątku t h r e a d U S B P o r t - > R e s u m e ( ) ; / / u r u c h o m i e n i e wątku 1. } c in .g e t(); re leaseT h read(th readU S B P o rt); return
0;
}
/ / -----------------------------------------------------------------------------
Rozdział 10. ♦ Programy wielowątkowe
397
Podsumowanie Niniejszy rozdział był poświęcony przedstaw ieniu podstawowych aspektów związa nych z wykorzystaniem w aplikacji sterującej portem USB elementów wielowątkowości. W pierwszej kolejności zaprezentowano i przeanalizowano efekty działania funk cji API Windows C reateThread(). W dalszej części rozdziału przedstawiono jeden ze sposobów posługiwania się w programie klasą TThread. Więcej bardziej wyczerpujących informacji na temat nowoczesnych metod programo w ania wielowątkowego można znaleźć w publikacjach [6, 7].
Ćwiczenia Ćwiczenie 10.1 Semafor (ang. semaphore) działa ja k bram ka kontrolująca liczbę wątków wykonują cych dany fragment kodu. Nowy semafor jest tworzony w funkcji: HANDLE C r e a t e S e m a p h o r e ( I N
L PS EC URI TY_ ATT RIB UT ES l p S e m a p h o r e A t t r i b u t e s ,
IN LONG l I n i t i a l C o u n t , IN LONG lM a x i m u m C o u n t , IN LPCTSTR lp N a m e ) ;
W ątek tw orzący sem afor specyfikuje w artości w stępną i m aksym alną licznika w y wołań. Inne wątki uzyskują dostęp do sem afora za pom ocą funkcji OpenSemaphore(). Po zakończeniu pracy w sekcji krytycznej w ątek zwalnia semafor za pom ocą funkcji ReleaseSemaphore(), tak ja k pokazano na listingu 10.5. Listing 10.5. Przykład wykorzystania funkcji CreateSemaphore() # in c lu d e
//... u s i n g n a m es p ac e s t d ;
/ / D e k l a r a c j e zmiennych g l o b a l n y c h u n sign ed lo n g th re a d F u n c S e n d (v o id * p a ra m e te r); u n sign ed lo n g th re a d F u n c R e c e iv e ( v o id * p a ra m e te r);
/ / ----------------------------------------------------------------------------------------------i n t m ain () { b u ffe rIn = (c h a r* )H e a p A llo c (G e tP ro c e s sH e a p (), HEAP_Z ERO_M EMORY, s trle n (b u ffe rO u t)+ 1 );
// E n u m e r a c j a ur ządzeń h Sem aphore=CreateSemaphore(NULL,
0,
1,
"FILE_EXISTS");
/ / tworzymy dwa w ą t k i / / 1. do w y s y ł a n i a danych hThread[0]
= CreateThread(N ULL,
0,
( L P T H R E A D _ S T A R T _ R O U T IN E ) th re a d F u n c S e n d , & h S em ap h or e,
0,
& threadID 1);
398
USB. Praktyczne programowanie z Windows API w C++
/ / 2. do o d b i o r u danych hThread[1]
= CreateThread(N ULL,
0,
(LPTHREAD_STAR T_ROU TIN E)threadFuncR eceive, & h S em ap h or e,
0,
& threadID 2);
R e leaseSem aphore(hSem aphore, W a itF o rM u ltip le O b je c ts (2 ,
1, N U L L ) ;
hThread,
TRUE,
10);
C lo se H a n d le (h S e m a p h o re ); C lo se H a n d le (h T h re a d [0 ]); C lo se H a n d le (h T h re a d [1 ]); H e a p F re e(G etP ro cessH eap (),0 ,b u ffe rIn); cin .g e t(); return }
//
0;
--------------------------------------------------------
un sign ed lo n g th re a d F u n c S e n d (v o id * param eter) { v o i d * hS em ap h or e = O pe n Se m aph or e(S EM AP H O RE _A LL_ AC CE SS ,
1,
"FILE_EXISTS"); W aitFo rS in g le O b je ct(h S e m a p h o re ,
INFINITE);
// W y s y ł a n i e danych do u r z ą d z e n i a wykonawczego R e le a s e S e m a p h o r e ( h S e m a p h o r e return }
//
,1
,N U LL);
TRUE;
--------------------------------------------------------
un sign ed lo n g t h re a d F u n c R e c e iv e ( v o id * param eter) { v o i d * hS em ap h or e = O pe n Se m aph or e(S EM AP H O RE _A LL_ AC CE SS ,
1,
"FILE_EXISTS"); W aitFo rS in g le O b je ct(h S e m a p h o re ,
INFINITE);
/ / O d b i ó r danych pochodzących z u r z ą d z e n i a wykonawczego Re leaseSem aphore(hSem aphore, return }
//
1,N U LL);
TRUE;
--------------------------------------------------------
Uzupełnij oraz zmodyfikuj powyższy przykład na potrzeby kodu nadzorującego trans misję USB.
Ćwiczenie 10.2 W zajemne wykluczenie (ang. mutual exclusion) jest sekcją krytyczną, która może być w spółdzielona przez wiele procesów i może działać pomiędzy w ielom a procesami. Programy próbują tworzyć wzajemne wykluczenie pod określoną nazw ą lpName, w y korzystując w tym celu funkcję API Windows: HANDLE WINAPI C r e a t e M u t e x (
);
IN
L PS EC URI TY_ ATT RIB UT ES l p M u t e x A t t r i b u t e s ,
IN
BOOL b I n i t i a l O w n e r ,
IN
LPCTSTR lpName
Rozdział 10. ♦ Programy wielowątkowe
399
Pierwszy proces, któremu uda się utworzyć obiekt wzajemnego wykluczenia, staje się serwerem. Jeżeli wzajemne wykluczenie ju ż istnieje, proces staje się klientem wzglę dem serwera. N a listingu 10.6 pokazano szkielet przykładu, w którym tworzone jest wzajemne wykluczanie współdzielone przez wszystkie procesy. Funkcja zwraca war tość prawdziwą, jeżeli proces jest serwerem, lub wartość fałszywą, jeżeli jest klientem. Ponieważ serwer zawsze staje się właścicielem wykluczenia, zawsze należy go zwol nić, zanim zostanie ono przechwycone przez klienta. Zwolnienie wzajemnego wyklu czenia o podanym identyfikatorze je st wykonywane poprzez funkcję API: BOOL R e l e a s e M u t e x ( I N
HANDLE h M u t e x ) ;
Listing 10.6. Przykład wykorzystania funkcji CreateMutex() # in c lu d e
//... u s i n g n a m es p ac e s t d ;
/ / D e k l a r a c j e zmiennych g l o b a l n y c h un sign ed lo n g th re a d F u n c S e n d (v o id * p a ra m e te r); un sign ed lo n g t h re a d F u n c R e c e iv e ( v o id * p a ra m e te r);
//
--------------------------------------------------------------
i n t m ain () { bu fferIn = (ch ar* )H eapA l lo c(G e tP ro ce s sH e a p (), HEAP_Z ERO_M EMORY, s trle n (b u ffe rO u t)+ 1 );
// E n u m e r a c j a ur ządzeń hMutex=CreateM utex(NULL,
TRUE,
"FILE_EXISTS");
/ / t w o r z y m y dwa w ą t k i / / 1 . do w y s y ł a n i a danych hThread[0]
= CreateThread(N ULL,
0,
( L P T H R E A D _ S T A R T _ R O U T IN E ) th re a d F u n c S e n d , & h M u te x ,
//2.
0,
& thread ID 1);
do c z y t a n i a danych
hThread[1]
= CreateThread(N ULL,
0,
(LPTHREAD_STAR T_ROU TIN E)threadFuncR eceive, & h M u te x ,
0,
& threadID 2);
R e le a s e M u te x(h M u te x); W a itF o rM u ltip le O b je c ts (2 ,
hThread,
TRUE,
10);
C lo se H a n d le (h M u te x); C lo se H a n d le (h T h re a d [0 ]); C lo se H a n d le (h T h re a d [1 ]); H e a p F re e(G etP ro cessH eap (),0 ,b u ffe rIn); cin .g e t(); return }
//
0;
------------------------------------------------------------
un sign ed lo n g th re a d F u n c S e n d (v o id * param eter) { v o i d * h Mu te x = & p a r a m e t e r ; W a itF o rS in g le O b je c t(h M u te x ,
IN FIN ITE);
// W y s y ł a n i e danych do u r z ą d z e n i a wykonawczego R e le a s e M u te x (h M u te x ); return }
TRUE;
/ / --------------------------------------------------------------
400
USB. Praktyczne programowanie z Windows API w C++
un sign ed lo n g t h re a d F u n c R e c e iv e ( v o id * param eter) { v o i d * h Mu te x = & p a r a m e t e r ; W a itF o rS in g le O b je c t(h M u te x ,
IN FIN ITE);
/ / O d b i ó r danych pochodzących z u r z ą d z e n i a wykonawczego R e le a s e M u te x (h M u te x ); return
TRUE;
}
/ / -----------------------------------------------------------------------------------------------
Uzupełnij oraz zmodyfikuj powyższy przykład na potrzeby kodu nadzorującego trans misję USB.
Ćwiczenie 10.3 Jednym z zasadniczych problemów programowania wielowątkowego jest konieczność zachowania integralności danych. Kiedy zachodzi potrzeba ochrony dostępu do b i bliotek komponentów wizualnych, można użyć metody Synchronize() klasy TThread. Przyjmuje ona bezparam etrową funkcję realizującą pracę wątku i wywołuje ją w bez pieczny sposób. Każdorazowe wywołanie Synchronize() zawiesza bieżący wątek i wy m usza na w ątku głównym wywołanie żądanej funkcji. Po jej zakończeniu kontrola je st przekazywana z powrotem do bieżącego wątku. W szystkie odwołania do funkcji Synchronize() są obsługiwane przez wątek główny, co skutkuje dostateczną ochroną przed zjawiskiem wyścigu. Jeżeli wiele w ątków jednocześnie posługuje się funkcją Synchronize(), w danej chwili tylko jeden z nich otrzymuje dostęp do wątku główne go, pozostałe zaś m uszą czekać. Proces ten nazywany jest często serializacją, ponie waż równoległe wywołania metod są zamieniane na szeregowe wywołania metod. Zmodyfikuj kod z listingów 10.3 i 10.4 w taki sposób, aby wizualizacje znaków śle dzenia lokalizatora były chronione za pom ocą funkcji Synchronize(). Przykłady w y korzystania funkcji Synchronize() chroniącej dostęp do bibliotek komponentów w i zualnych można znaleźć w pracach [6, 7].
Rozdział 11.
Adaptery USB Szeroka gama dostępnych na rynku portów komunikacyjnych była powodem konflik tów i problemów z podłączaniem urządzeń peryferyjnych do komputera. W spółcze sne płyty główne są zaopatrzone przede wszystkim w kontrolery portów szeregowych IEEE 1394 oraz USB, umożliwiających podłączanie urządzeń zewnętrznych o różno rodnym przeznaczeniu. W śród nich szczególną grupę stanow ią urządzenia laborato ryjne i przemysłowe. W niedalekiej przeszłości (ale także obecnie) producenci często zaopatrywali (lub dalej zaopatrują) takie urządzenia w interfejsy rodziny RS 232C lub IEEE-488. Coraz popularniejszy staje się również standard bezprzewodowej transmi sji danych krótkiego zasięgu oparty na technologii Bluetooth.
Adaptery USB/RS 232C Jednym ze standardów transm isji szeregowej, który w śród urządzeń laboratoryjnych i przem ysłow ych dalej konkuruje z USB, je s t RS 232C. Sytuacja taka ma m iejsce głównie ze względu na jego użyteczność w zewnętrznych urządzeniach, która w dużej mierze je st związana z prostotą implementacji i obsługi [6]. Dostępne na rynku adaptery USB/RS 232C pozw alają na podłączenie urządzeń za opatrzonych w interfejs szeregowy RS 232C do portów USB w komputerze. Adaptery posiadają połączone odcinkiem kabla złącze USB typu A oraz DSUB9 lub DSUB25 (port RS 232C), w którego obudowie znajduje się układ adaptera zasilanego z portu USB komputera (rysunek 11.1). Rysunek 11.1. Wygląd typowego adaptera USB/RS 232C
402
USB. Praktyczne programowanie z Windows API w C++
W zależności od producenta adaptery tego typu instalują się jako urządzenia PnP lub za pośrednictw em w łasnych sterowników. Dostarczane wraz z adapterem sterowniki są instalowane w systemie Windows, dzięki czemu uzyskujemy dostęp do dodatkowe go 8-bitowego portu o dowolnie zadeklarowanej wartości — od COM1 do COM256, którego można używać jak standardowego portu. Należy zwrócić uwagę, że jest to jed nak port wirtualny, dlatego programy, które bezpośrednio obsługują porty komunikacyj ne (np. MS DOS), nie będą działać poprawnie. W przeciwieństwie do standardowych portów COM, port konw ertera jest bardzo szybki. Sterowniki zapewniają transmisję danych do 921 kb/s z możliwością rozszerzenia w przypadku transmisji asynchronicz nej nawet do 2 Mb/s.
Właściwości portu adaptera Po prawidłowym zainstalowaniu urządzenie adaptera jest widoczne w menedżerze urzą dzeń. Po przejściu do opcji Porty (COM i LPT) możemy z łatwością odszukać wybra ny wirtualny port szeregowy USB (rysunek 11.2). -Pa Menedżer urządzeń Plik
Akcja
Widok
Pomoc
4- ■»IurIHII B m I& I W ul it> - s , Andrzej-PC > ^ Baterie t>"Ęjj| Karły graficzne ■ i»y Karty sieciowe ••ifjji Karta bezprzewodowej sieci LAN802.11n Realtek PCle GBE Family Controller Urządzenie Bluetooth (Protokół TDIRFCOMM) Urządzenie Bluetooth (sieć osobista}
l>~GmKlawiatury > -¡j*l Komputer t>-B^|
Kontrolery dźwięku, wideo i gier
>d
Kontrolery IDE ATA/ATAPI
>■ ■ ^ Kontrolery uniwersalnej magistrali szeregowej p>-^ | Modemy Monitory '0 i inne urządzenia wskazujące Q Odbiorniki radiowe Bluetooth Porty (COM i LPT) Prolific USB-to-Serial Comm Port (COM4) D
Procesory
Aktualizuj oprogramowanie sterownika...
i
Stacje dysków
Wyłącz
^
Stacje dysków CD-ROM/DVD
-T, Urządzenia do obrazowania ÓpJ Urządzenia interfejsu HID
Od instaluj Skanuj w poszukiwaniu zmian sprzętu
flÇI Urządzenia systemowe Właściwości Otwiera arkusz właściwości dla bieżącego zaznaczenia.
Rysunek 11.2. Port adaptera USB/RS 232C Jeśli z kolei wybierzemy Ustawienia portu (rysunek 11.3), możemy się przekonać, że do złudzenia przypominają one swoje odpowiedniki ze standardowych portów szere gowych. Mamy zatem możliwość odpowiedniego wyboru prędkości transmisji, długo ści ramki danych, parzystości, bitów stopu i typu kontroli przepływu danych. Należy w tym miejscu zauważyć, że korzystając z portu USB, nie można ustalić wszystkich prędkości transmisji (b/s) deklarowanych w systemie Windows: 110, 300, 1200, 2400,
Rozdział 11. ♦ Adaptery USB
403
4800, 9600, 19200, 38400, 57600, 115200, 230400, 460800 oraz 921200. Ponieważ niektóre systemy Windows nie pozw alają na deklarowanie pewnych prędkości trans misji, producent zadbał o przygotowanie sterowników z tzw. przemapowaną prędko ścią transmisji. Umożliwiają one używanie wysokich szybkości transmisji danych przy zadeklarow anych w system ie niskich szybkościach. Szczegóły dotyczące sposobu przemapowania prędkości transmisji są zawsze bardzo dokładnie opisane w instrukcji obsługi urządzenia. Rysunek 11.3. Ustawienia portu adaptera
Właściwości: Prolific USB-to-Serial Comm Port (COM4) Ogólne | Ustawienia portu [sterownik | Szczegóły |
Liczba bitów na sekundę: Bity danych: Parzystość: Bity stopu: Sterowanie przepływem:
I-SSQQ Is [Brak |l [Brak
[Zaawansowane... ] [ Przywróć domyślne ]
OK
|
Anuluj
|
Po wybraniu zaawansowanych ustawień portu adaptera (opcja Zaawansowane...) zo staje uaktywniona zakładka pokazana na rysunku 11.4. Rysunek 11.4. Ustawienia portu adaptera
Anülüj"^™|
40 4
USB. Praktyczne programowanie z Windows API w C++
Oprócz możliwości ustalania rozm iarów buforów transmisyjnych (tak ja k w standar dowym łączu szeregowym) istnieje dodatkowa opcja umożliwiająca przypisanie por towi USB wartości łącza COM od 1 do 256. Standardowo w momencie instalacji ste rowników portowi USB je st przypisywany pierwszy wolny port COM, z którego od tej chwili możemy swobodnie korzystać. Jeżeli w przyszłości zechcemy, aby aplikacja nie posługiwała się portem domyślnym, a — powiedzmy — portem COM156, należy w tym miejscu wybrać właśnie taką wartość portu. Często mówimy, że port należy za rejestrować. Samodzielne wybranie wartości portu bezpośrednio w aplikacji w przy padku USB nie przyniesie oczekiwanych efektów. Możliwość ustalenia wyższych war tości dla portów USB stanowi spore udogodnienie dla użytkownika, gdyż w znacznym stopniu ogranicza prawdopodobieństwo w ystąpienia konfliktu łącza USB z innymi portami szeregowymi.
Adaptery USB/IEEE-488 IEEE-488 (znany jako GPIB — ang. General Purpose Interface Bus) jest standardem interfejsu równoległego o krótkim zasięgu, wykorzystywanym w automatycznych sys tem ach pomiarowych. Urządzenia, w których je st zaimplementowany ten interfejs, dzieli się na nadajniki, odbiorniki i kontrolery, porozum iewające się między sobą za pośrednictwem dwóch rodzajów komunikatów: interfejsu i urządzeń. Funkcję nadajnika pełni urządzenie prze syłające komunikaty do jednego lub większej liczby odbiorników. Przed nawiązaniem połączenia kontroler adresuje nadajnik i odbiornik, a w trakcie transmisji monitoruje sieć połączeń. W momencie gdy zauważy, że nadajnik zgłasza się do przesłania infor macji, łączy go z odpowiednim odbiornikiem, umożliwiając transmisję. Jeżeli w sys temie występuje tylko jeden nadajnik, a pozostałe urządzenia są odbiornikami, stoso wanie kontrolera nie je st konieczne. K om puter wyposażony w kartę interfejsu GPIB może być kontrolerem, nadajnikiem lub odbiornikiem. Do magistrali GPIB można przyłączyć do 15 urządzeń, wśród których może występo wać kilka kontrolerów nadzorujących pracę systemu, ale w danym momencie tylko je den z nich powinien być aktywny. Urządzenia współpracujące z magistralą korzystają z dostępu do niej na identycznych zasadach, bez konieczności pośrednictwa centralnej jednostki sterującej. W szystkie rozkazy i większość danych są przesyłane w formie 7-bitowych kodów ASCII. N a rysunku 11.5 pokazano wygląd typowego konwertera USB/GPIB. Konwertery tego typu posiadają połączone odcinkiem kabla złącze USB typu A oraz wtyczkę IEEE-488, w której obudowie znajduje się układ konwertera zasilanego z portu USB komputera. W zależności od producenta konwertery tego typu są instalowane jako urządzenia PnP lub za pośrednictwem w łasnych sterowników. Dostarczane wraz z konwerterem ste rowniki są instalowane w system ach W indows, dzięki czem u uzyskujemy dostęp do dodatkowego 8-bitowego wirtualnego portu COM, którego można używać tak jak stan dardowego portu (identycznie ja k w przypadku konwertera USB/RS 232C).
Rozdział 11. ♦ Adaptery USB
405
Rysunek 11.5. Wygląd typowego adaptera USB/GPIB
Adaptery USB/Bluetooth Dynamiczny rozwój systemów informatycznych oraz coraz większa liczba pojawiają cych się na rynku urządzeń zdolnych do wzajemnej wymiany informacji w czasie rze czywistym skłoniły producentów sprzętu i oprogramowania do opracowania standardu bezprzewodowego przesyłu danych Bluetooth, który zapewnia prostotę wykrywania urządzeń przez systemy, a zarazem zadawalającą uniwersalność i funkcjonalność ob sługi. N a rysunku 11.6 pokazano typowy adapter USB/Bluetooth. Moduł radiowy znaj duje się w obudowie adaptera i jest zasilany bezpośrednio z portu USB. Rysunek 11.6. Wygląd typowego adaptera USB/Bluetooth
Dokumentacja standardu Bluetooth opracow ana przez organizację SIG (ang. Spécial Interest Group) nie zaw iera specyfikacji konkretnego interfejsu programistycznego API w łaściwego danej platform ie systemowej. SIG zakłada, że w trakcie wdrażania technologii Bluetooth na danej platformie potrzeba opracowania właściwego API po w inna spoczywać na programistach. Z powyższych względów postanowiono nie opra cowywać oddzielnych API dla poszczególnych systemów operacyjnych. Zamiast tego za pomocą profili zdefiniowano wszystkie funkcje niezbędne programistom tworzącym oprogramowanie współpracujące z konkretnymi aplikacjami Bluetooth na danej plat formie systemowej. Tym samym, mimo że specyfikacja standardu nie zawiera właści wego API, dostarcza jednak program istom aplikacji w szystkich niezbędnych w ska zówek, które pozw alają w prosty sposób przekształcić je w API konkretnej platformy. Zdefiniowane przez SIG profile określają funkcje urządzenia. Obejmują one różne war stwy i protokoły służące zapewnieniu kompatybilności między aplikacjami oraz urzą dzeniam i B luetooth pochodzącym i od różnych producentów. U rządzenia Bluetooth m ogą współpracować ze sobą jedynie w obrębie wspólnych profili. Profile Bluetooth
406
USB. Praktyczne programowanie z Windows API w C++
są uporządkowane w grupach i m ogą być zależne od innych, jeżeli wykorzystują de klaracje profili nadrzędnych. W śród profili, które powinny szczególnie zainteresować Czytelników niniejszej książki, należy wymienić profil urządzeń interfejsu HID. Profil ten został zaadaptowany ze specyfikacji klasy HID urządzeń USB. Informacje o akcjach użytkownika na elementach sterujących urządzenia Bluetooth (takich ja k naciśnięcie klaw isza) są przesyłane do kom putera w czasie rzeczywistym. Profil ten umożliwia na przykład zdalne sterowanie za pom ocą klawiatury telefonu komórkowego aplika cjami uruchomionymi na komputerze. W ażną cechą standardu B luetooth je st to, że oprócz aplikacji dedykowanych na po trzeby technologii istnieje możliwość korzystania ze starszego oprogram owania kon struowanego pod kątem korzystania z m echanizmów transm isji danych różnych niż B luetooth. Jest to możliwe dzięki zdefiniow aniu przez SIG takich protokołów ja k RFCOM M (ang. Radio Frequency Communication). W praktyce oznacza to, że pro gramy pierwotnie zaprojektowane do pracy z w ykorzystaniem standardowego portu szeregowego będą mogły po niewielkich modyfikacjach korzystać z Bluetooth dzięki oferow anym przez protokół RFCOM M mechanizmom emulacji portu szeregowego (podobnie ja k w przypadku klasycznego adaptera USB/RS 232C). RFCOMM jest jednym z protokołów transportowych zdefiniowanych przez SIG. Bar dzo często protokół ten je st określany mianem emulatora standardowego portu szere gowego RS 232C. Połączeniowy, strumieniowy protokół komunikacyjny RFCOMM zapewnia użytkownikowi prosty i niezawodny dostęp do strumienia danych przezna czonych zarówno do wysłania, ja k i odbioru. Jest on stosowany bezpośrednio przez wiele profili zw iązanych z telefonią jako nośnik kom end AT, a także jako warstwa transportowa dla usług wymiany danych w postaci obiektów OBEX (ang. Object Ex change). Wiele współczesnych aplikacji Bluetooth używa RFCOM M ze względu na jego szerokie wsparcie techniczne w postaci publicznego API dostępnego w większo ści systemów operacyjnych. Warto też zdawać sobie sprawę z faktu, że aplikacje uży wające standardowego portu szeregowego m ogą być szybko zaadaptowane na potrze by RFCOMM. M aksymalna liczba jednocześnie dostępnych połączeń wynosi 60. Komendy AT to zestaw poleceń, które po raz pierwszy zastosowała w swoich urządze niach (w celu ujednolicenia obsługi sprzętu, z którym miał współpracować komputer) znana z produkcji modemów firma Hayes. Pierwotnie polecenia te miały służyć jed y nie do sterowania pracą modemów analogowych. Jednak wraz z upowszechnieniem się technologii GSM bardzo szybko zostały zaadaptowane do obsługi modemów w bu dowanych w telefony komórkowe. Obecnie każde urządzenie bazujące na technologii GSM posiada w budow any interpreter kom end AT i w ykonuje je zgodnie z norm ą przyjętą przez producentów. O znacza to, że im plementacje komend AT dla konkret nych urządzeń m ogą się nieznacznie różnić pomiędzy sobą, co nie zmienia faktu, że zarówno składnia rozkazów, jak i wynik ich realizacji są znormalizowane. N a rysun ku 11.7 pokazano ogólną klasyfikację kom end AT. Rysunek 11.7. Klasyfikacja poleceń AT
Rozdział 11. ♦ Adaptery USB
407
Tak jak pokazano na rysunku 11.7, komendy AT dzielą się na cztery podstawowe grupy: ♦ Polecenia typu Test (testowe) — służą do sprawdzania, czy dana komenda je st obsługiwana przez urządzenie, czy też nie. Składnia: AT=? ♦ Polecenia typu R ead (zapytania) — służą do uzyskiwania informacji na temat aktualnych ustawień urządzenia zewnętrznego. Składnia: AT
? ♦ Polecenia typu Set (zestawy poleceń) — służą do modyfikowania wybranych parametrów ustawień urządzenia zewnętrznego. Składnia: AT
=w artość1 , w artość2 ,
...
, wartośćN
♦ Polecenia typu Execution (wykonywalne) — służą do przesyłania rozkazów wykonania konkretnej operacji przez urządzenie zewnętrzne. Składnia: AT
=param etr1 , param etr2 ,
...
, parametrN
Zgodnie ze standardem każde polecenie rozpoczyna się od prefiksu AT i kończy zna kiem powrotu karetki CR (13 lub \r). Kom enda nie będzie realizowana, dopóki urzą dzenie GSM nie odbierze znaku CR. Przyjęcie polecenia do realizacji przez urządze nie jest potwierdzane znakiem nowej linii LF (10 lub \n). Więcej informacji na temat komend AT można znaleźć w publikacji J. Bogusza, Programowanie i obsługa modu łów GSM, „Elektronika Praktyczna” 2002, nr 8; http://ep.com.pl/files/8073.pdf. N a listingu 11.1 zaprezentowano przykładowy program kontrolujący w sposób asyn chroniczny operacje wysyłania za pośrednictwem wirtualnego portu szeregowego po leceń ATI (typ urządzenia) oraz AT+CCLK? (zapytanie o aktualną datę i czas), a następnie pobierania informacji zwrotnych z urządzenia GSM z funkcją Bluetooth. Transmisja danych je st programowana za pom ocą standardowych funkcji API SDK.
Uwaga
Należy zwrócić uwagę, że do poprawnego działania przedstawionych algorytmów programowej realizacji bezprzewodowej transmisji danych w standardzie Bluetooth wymagane jest, aby urządzenie wykonawcze było wcześniej poprawnie zestawione (sparowane) i uwierzytelnione przez główny moduł radiowy zaimplementowany na przykład w komputerze lub w postaci adaptera USB/Bluetooth.
Listing 11.1. Przykład asynchronicznej transmisji danych poprzez wirtualny port szeregowy pomiędzy głównym modułem radiowym urządzenia nadrzędnego (komputer, adapter USB/Bluetooth) a pozostającym w zasięgu urządzeniem wykonawczym #include #include #define cbInQueue 1024 #define cbOutQueue 1024 using namespace std; void* hCommDev;
408
USB. Praktyczne programowanie z Windows API w C++
DCB dcb; COMMTIMEOUTS commTimeouts; // prototypy funkcji---------void closeSerialPort(); int readSerialPort(void *buffer, unsigned long numberOfBytesToRead); int writeSerialPort(void *buffer, unsigned long numberOfBytesToWrite); bool openSerialPort(const char* portName); bool setCommTimeouts(unsigned long ReadIntervalTimeout, unsigned long ReadTotalTimeoutMultiplier, unsigned long ReadTotalTimeoutConstant, unsigned long WriteTotalTimeoutMultiplier, unsigned long WriteTotalTimeoutConstant); bool setTransmissionParameters(unsigned long BaudRate,int ByteSize, unsigned long fParity,int Parity, int StopBits);
/ / -------------------------------------------------------------------int main() { openSerialPort("COM5"); //wirtualny port szeregowy COM5 setTransmissionParameters(CBR_9600, 8, true, ODDPARITY, ONESTOPBIT); setCommTimeouts(0xFFFFFFFF, 10, 0, 10, 0); char bufferIn[24]; char bufferOut[64] = {0}; char *text; //te x t = "ATI\r"; //przykładowe komendy AT text = "AT+CCLK?\r"; strcpy(bufferIn, text); writeSerialPort(bufferIn, strlen(bufferIn)); cout << "Otrzymano bajtów: " << readSerialPort(bufferOut, sizeof(bufferOut)) << endl; cout << bufferOut; closeSerialPort(); system("PAUSE"); return 0; } // ciała funkcji------------------------------------bool openSerialPort(const char* portName) { hCommDev = CreateFile(portName,GENERIC_READ | GENERIC_WRITE, 0, NULL,OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL); if(hCommDev==INVALID_HANDLE_VALUE){ cout <<"Błąd otwarcia portu " << portName << \ " lub port je st aktywny.\n"; return false; } else SetupComm(hCommDev, cbOutQueue, cbOutQueue); return true; } / / -------------------------------------------------------------------bool setTransmissionParameters(unsigned long BaudRate, int ByteSize, unsigned long fParity, int Parity, int StopBits) {
Rozdział 11. ♦ Adaptery USB
dcb.DCBlength = sizeof(dcb); GetCommState(hCommDev, &dcb); dcb.BaudRate =BaudRate; dcb.ByteSize = ByteSize; dcb.Parity =Parity ; dcb.StopBits =StopBits; dcb.fBinary=true; dcb.fParity=fPari ty; 11... if(SetCommState(hCommDev, &dcb) == 0){ cout << "Błąd wykonania funkcji SetCommState()An"; CloseHandle(hCommDev); return false; } return true; } 11-------------------------------------------------------------------bool setCommTimeouts(unsigned long ReadIntervalTimeout, unsigned long ReadTotalTimeoutMultiplier, unsigned long ReadTotalTimeoutConstant, unsigned long WriteTotalTimeoutMultiplier, unsigned long WriteTotalTimeoutConstant) { if(GetCommTimeouts(hCommDev, &commTimeouts) == 0) return false; commTimeouts.ReadIntervalTimeout = ReadIntervalTimeout; commTimeouts.ReadTotalTimeoutConstant = ReadTotalTimeoutConstant; commTimeouts.ReadTotalT imeoutMul tip lie r = ReadTotalTimeoutMultiplier; commTimeouts.WriteTotalTimeoutConstant = WriteTotalT imeoutConstant; commTimeouts.WriteTotalTimeoutMultiplier = WriteTotalTimeoutMultiplier; if (SetCommTimeouts(hCommDev, &commTimeouts) == 0) { cout << "Błąd wykonania funkcji SetCommTimeouts().\n"; CloseHandle(hCommDev); return false; } return true; } 11-------------------------------------------------------------------int writeSerialPort(void *buffer, unsigned long numberOfBytesToWrite) { BOOL result; unsigned long numberOfBytesWritten = 0; unsigned long errors; unsigned long lastError; unsigned long bytesSent = 0; COMSTAT comStat; OVERLAPPED overlapped; result = WriteFile(hCommDev, buffer, numberOfBytesToWrite, &numberOfBytesWritten, &overlapped); if (!result) { if(GetLastError() == ERROR_IO_PENDING) { while(!GetOverlappedResult(hCommDev, &overlapped, &numberOfBytesWritten, FALSE )) { lastError = GetLastError(); if(lastE rror == ERROR IO INCOMPLETE){
409
410
USB. Praktyczne programowanie z Windows API w C++
numberOfBytesWritten += bytesSent; continue; } else { ClearCommError(hCommDev, &errors, &comStat); break; } } numberOfBytesWritten += bytesSent; } else { ClearCommError(hCommDev, &errors, &comStat); } } else numberOfBytesWritten += bytesSent; FlushFileBuffers(hCommDev); return numberOfBytesWritten; } / / -----------------------------------------------------int readSerialPort(void *buffer, unsigned long numberOfBytesToRead) { BOOL result; COMSTAT comStat ; unsigned long errors; unsigned long bytesRead = 0; unsigned long numberOfBytesRead = 0; unsigned long lastError; OVERLAPPED overlapped; ClearCommError(hCommDev, &errors, &comStat); bytesRead = numberOfBytesToRead; if(bytesRead > 0) { result = ReadFile(hCommDev, buffer, bytesRead, &bytesRead, &overlapped); if(!resu lt) { if(GetLastError() == ERROR_IO_PENDING) { while(!GetOverlappedResult(hCommDev, &overlapped, &bytesRead, TRUE)) { lastError = GetLastError(); if(lastE rror == ERROR_IO_INCOMPLETE) { numberOfBytesRead += bytesRead; continue; } else { ClearCommError(hCommDev, &errors, &comStat); } break; } numberOfBytesRead += bytesRead; } } else numberOfBytesRead += bytesRead; }
else ClearCommError(hCommDev, &errors, &comStat);
Rozdział 11. ♦ Adaptery USB
411
return numberOfBytesRead; } //
--------------------------------------------------------------
void closeSerialPort() { if (CloseHandle(hCommDev)) cout << "\n\nPort został zamknięty do transm isji.\n\n"; return; } / / -------------------------------------------------------------------N a rysunkach 11.8 i 11.9 pokazano omawiany program w trakcie działania. Rysunek 11.8. Odpowiedź urządzenia zewnętrznego na odebraną komendę ATI
Rysunek 11.9. Odpowiedź urządzenia zewnętrznego na odebraną komendę AT+CCLK?
? programowanie\Rozdział 11\R11_1’' O trzym ano b a ]to w : 25 Samsung 5GH-X680
P o rt zo s ta ł
za m k n ię ty do t r a n s m i s j i .
A by ko ntynu ow ać,
n a c iś n i j
O trzym ano b a jtó w :
dow oln y k la w is z
33
+CCLK: 1 2 / 1 0 / 0 3 ,1 8 :5 6 :5 5 OK
P o rt z o s ta ł
z a m k n ię ty do t r a n s m i s j i .
A by ko ntynu ow ać, n a c i ś n i j
dow olny k la w is z .
■-
N a listingu 11.2 zaprezentowano przykładowy program wysyłający do telefonu GSM zestawionego z kom puterem rozkaz ATD dzw onienia pod wybrany numer. Transm isja danych je st program ow ana za pom ocą funkcji z biblioteki WinSock. N a rysunku 11.10 zobrazowano zasadę działania przedstawionego algorytmu. Listing 11.2. Wykorzystanie biblioteki WinSock #include #include #pragma option push -a1 #include #include "D:\\WINDDK\\7600.16385.1\\inc\\api\\Ws2bth.h" #include "D:\\WINDDK\\7600.16385.1\\inc\\api\\BluetoothAPIs.h" #pragma option pop using namespace std; / / -------------------------------------------------------------------void showError() { LPVOID lpMsgBuf; FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
412
USB. Praktyczne programowanie z Windows API w C++
NULL, GetLastError(), 0, (LPTSTR) &lpMsgBuf, 0, NULL ); fp rintf(stderr, "\n%s\n", lpMsgBuf); free(lpMsgBuf); cin.get(); } / / -------------------------------------------------------------------int main() { WORD wVersionRequested; WSADATA wsaData; int result; //in icja liza cja biblioteki WinSock w wersji 2.2 wVersionRequested = MAKEWORD(2,2); if(WSAStartup(wVersionRequested, &wsaData) != 0) { showError(); } SOCKET s; //ustalenie adresu gniazda SOCKADDR_BTH socAddrBTH; int socAddrBTHlength = sizeof(socAddrBTH); memset(&socAddrBTH, 0, sizeof(socAddrBTH)); socAddrBTH.addressFamily = AF_BTH; //adres właściwego urządzenia Bluetooth należy odczytać z Panelu //sterowania\Właściwości urządzenia socAddrBTH.btAddr = (BTH_ADDR)0x001a2a3a4a5a; socAddrBTH.port = BT_PORT_ANY; //ustalenie identyfikatora usługi socAddrBTH.serviceClassId = SerialPortServiceClass_UUID; s = socket(AF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM); if(s == SOCKET_ERROR) { printf("Błędne wykonanie socket. %ld\n", WSAGetLastError()); return 1; } / / połączenie z gniazdem if(SOCKET_ERROR==connect(s, (SOCKADDR*) &socAddrBTH, socAddrBTHlength)) { printf("Błędne wykonanie connect. %ld\n", WSAGetLastError()); return 1; } char komenda[] = "ATD 123456789;\r"; //komenda ATD [;] - rozkaz dzwonienia pod wybrany numer //[;] oznacza połączenie głosowe, //wysłanie rozkazu i zamknięcie gniazda result = send(s, komenda, strlen(komenda), 0); if (result == SOCKET_ERROR) { printf("Błąd wysłania danych: %ld\n", WSAGetLastError()); closesocket(s); WSACleanup(); return 1; } printf("Wysłano bajtów: %d\n", result); Sleep(10000); //próba łączenia przez ok. 10 sekund result = shutdown(s, SD_SEND); if (result == SOCKET_ERROR) {
Rozdział 11. ♦ Adaptery USB
413
printf("Błąd wykonania shutdown: %ld\n", WSAGetLastError()); closesocket(s); WSACleanup(); return 1; } WSACleanup(); system("PAUSE"); return 0; } 11--------------------------------------------------------------------
Rysunek 11.10. Wysłanie rozkazu nawiązania połączenia głosowego z numerem 123 456 789 przez urządzenie o adresie 00:1a:2a:3a:4a:5a
Podsumowanie Kończący książkę rozdział został poświęcony omówieniu obecnie najczęściej wykorzy stywanych adapterów interfejsu USB. Popularność adapterów USB wynika z potrzeby korzystania z takich standardów transmisji danych jak RS 232C, GPIB czy Bluetooth, zaimplementowanych w bardzo wielu wykorzystywanych obecnie urządzeniach labo ratoryjnych, przemysłowych oraz diagnostycznych i medycznych (standard Bluetooth Low Energy). D la tego typu adapterów dostępne są sterowniki zarówno w wersjach PnP, ja k i standardowych, dzięki czemu spełniają one wym agania w szystkich typo wych aplikacji.
414
USB. Praktyczne programowanie z Windows API w C++
Literatura [1] Axelson J., USB Complete, Everything You N eed to Develop Custom USB Peripherals , Lakeview Research, Madison 2005. [2] Axelson J., USB Complete: The D eveloper’s Guide, Fourth Edition, Lakeview Research, Madison 2009. [3] Beck K., Exstreme Programming Explained: Embrace Change, Addison-Wesley, Boston 2000. [4] Buschmann F., Meunier R., Rohnert H., Sommerlad P., Stal M., Pattern-Oriented Software Architecture. A System o f Patterns, John Wiley and Sons, Chichester 1996. [5] Cant C., Writing Windows WDM Device Drivers, CMP, London 1999. [6] Daniluk A., R S 232C — praktyczne programowanie. Od Pascala i C++ do Delphi i Buildera, wydanie III, Helion, Gliwice 2007. [7] Daniluk A., C++Builder Borland Developer Studio 2006. Kompendium program isty , Helion, Gliwice 2006. [8] Fowler M., Analysis Patterns. Reusable Object M odels, Addison-Wesley, Boston 1997. [9] Gamma E., Helm R., Johnson R., Vlissides J.M., Wzorce projektowe. Elementy oprogramowania obiektowego wielokrotnego użytku, Helion, Gliwice 2010. [10] HID Information, http://www.usb.org/developers/hidpage/. [11] Intel Specification, Extensible H ost Controller Interface fo r Universal Serial Bus (xHCI), 2010.
[12] Koeman K., Universal Serial Bus. Handling The IRP M N START DEVICE PnP IRP, http://www.usb.org/developers/whitepapers/irp_mn.pdf. [13] Mielczarek W., USB. Uniwersalny interfejs szeregowy, Helion, Gliwice 2005. [14] Rumbach J., Jacobson I., Booch G., The Unified M odeling Language Reference M anual , 2nd Edition, Addison-Wesley, Boston 2005. [15] Universal Serial Bus 2.0 Specification, http://www.usb.org/developers/docs/.
416
USB. Praktyczne programowanie z Windows API w C++
[16] Universal Serial Bus 3.0 Specification, http://www.usb.org/developers/docs/. [17] USB IF, Language Identifiers (LANGIDs), 3/29/00 Version 1.0, 2000. [18] Wireless Universal Serial Bus Specification 1.1, http://www.usb.org/ developers/wusb/docs .
Skorowidz A adaptery USB, 401 USB/Bluetooth, 405 USB/GPIB, 405 USB/IEEE-488, 404 USB/RS 232C, 401 agregujące klasyfikatory, 81 API, Application Programming Interface, 71 aplikacja środowiska graficznego, 218, 366 asynchroniczna transmisja danych, 407
B bezprzewodowa transmisja danych, 407 biblioteka HID.dll, 144 KMDF, 272 LibUSB, 289, 291 kody rozkazów, 296 typy adresatów, 297 typy żądań, 297 libusb0.dll, 291 Setupapi, 182 STL, 8 Usbdex.lib, 107 WinSock, 411 WinUSB, 271, 278 winusb.dll, 271 bity identyfikatora pakietu, 29 Bluetooth, 405 błąd naruszenia pamięci, 156 bufor z danymi wejściowymi, 110 wyjściowymi, 109
bus driver, 105 bus interval, 24
C C++ Compiler 5.5, 8 certyfikat, 274 CRC, Cyclic Redundancy Check, 24 czas oczekiwania na zdarzenie, 228 przeterminowania, 228 czynności, 158
D delegowanie interfejsu, 346 deskryptory interfejsów urządzeń, 95 koncentratorów, 84 konfiguracji, 100 punktu końcowego, 89 raportu, 111, 113 tekstowe, 104 urządzeń, 80 detekcja interfejsów urządzeń, 157 ścieżek, 181 urządzeń, 149 diagram czynności, 157, 195 klas, 337, 367, 382 komponentów, 335 programu proceduralnego, 352 sekwencji, 349 DisplayPort, 12 długość bufora danych, 203
418
USB. Praktyczne programowanie z Windows API w C++
dostęp do odczytu, 192 pliku sterownika, 194 strumienia danych, 406 uruchamiania pliku, 192 urządzenia USB, 172 zapisu, 192 drukowanie łańcucha znaków, 268
E edytor rejestru, 64 elementy kontrolne, 114 sterujące, 114 enumeracja urządzeń, 149 EP, endpoints, 24
F FDO, Functional Device Objects, 84 Filter DO, Filter Device Objects, 84 FireWire, 11 format danych, 115 rozkazu, 237 FS, Full Speed, 18 funkcja AddRef(), 329 CloseHandle(), 194 CM_Get_DevNode_Registry_Property(), 176 CreateEvent(), 229 CreateFile(), 160, 192, 228 CreateInstance(), 326 CreateMutex(), 399 CreateSemaphore(), 397 CreateThread(), 381 DeviceIoControl(), 236 FileIOCompletionRoutine(), 231 FreeLibrary(), 145 getCollectionDescriptor(), 242 GetCommTimeouts(), 235 getDSPortConnectionIndex(), 255 getDSPortData(), 258 getHubInformation(), 260 GetLastError(), 231 getRegistryPropertyDWORD(), 167 getStringDescriptor(), 258 HidD_FlushQueue(), 123 HidD_FreePreparsedData(), 124 HidD_GetAttributes(), 117 HidD_GetFeature(), 118 HidD_GetHidGuid(), 119
HidD_GetIndexedString(), 122 HidD_GetInputReport(), 116, 119, 207 HidD_GetManufacturerString(), 121 HidD_GetMsGenreDescriptor(), 124 HidD_GetNumInputBuffers(), 121 HidD_GetPhysicalDescriptor(), 124 HidD_GetPreparsedData(), 123 HidD_GetProductString(), 122 HidD_GetSerialNumberString(), 122 HidD_SetFeature(), 119, 226 HidD_SetNumInputBuffers(), 120 HidD_SetOutputReport(), 116, 120, 226 HidP_GetButtonCaps(), 125, 209 HidP_GetButtons(), 125 HidP_GetButtonsEx(), 125 HidP_GetCaps(), 126 HidP_GetData(), 129 HidP_GetExtendedAttributes(), 130 HidP_GetLinkCollectionNodes(), 130 HidP_GetScaledUsageValue(), 131 HidP_GetSpecificButtonCaps(), 132 HidP_GetSpecificValueCaps(), 133 HidP_GetUsages(), 135 HidP_GetUsagesEx(), 135, 203 HidP_GetUsageValue(), 134 HidP_GetUsageValueArray(), 134 HidP_GetValueCaps(), 137, 213 HidP_InitializeReportForID(), 137 HidP_MaxDataListLength(), 138 HidP_MaxUsageListLength(), 138 HidP_SetButtons(), 139 HidP_SetData(), 139 HidP_SetScaledUsageValue(), 139 HidP_SetUsages(), 140 HidP_SetUsageValue(), 140 HidP_SetUsageValueArray(), 140 HidP_UnsetUsages(), 142 HidP_UsageAndPageListDifference(), 143 HidP_UsageListDifference(), 142 InterlockedIncrement(), 329 LoadLibrary(), 144 openDevice(), 259 ReadFile(), 117, 199, 203 ReadFileEx(), 231 Release(), 329 searchInterfaceHidDevices(), 161 SetCommTimeouts(), 235 SetupDiDestroyDeviceInfoList(), 157 SetupDiEnumDeviceInterfaces(), 152 SetupDiGetClassDevs(), 152 SetupDiGetDeviceInterfaceDetail(), 155 SetupDiGetDeviceRegistryProperty(), 163 SleepEx(), 232 strstr(), 199
Skorowidz
Synchronize)), 400 ThreadFunc(), 382 usb_bulk_read(), 301 usb_bulk_setup_async(), 302 usb_bulk_write(), 300 usb_cancel_async(), 303 usb_claim_interface(), 296 usb_clear_halt(), 295 usb_close(), 294 usb_control_msg(), 296 usb_find_busses(), 292 usb_find_devices(), 293 usb_free_async(), 303 usb_get_busses(), 293 usb_get_descriptor(), 299 usb_get_descriptor_by_endpoint(), 299 usb_get_string(), 298 usb_get_string_simple(), 299 usb_init(), 292 usb_interrupt_read(), 301 usb_interrupt_setup_async(), 302 usb_interrupt_write(), 301 usb_isochronous_setup_async(), 301 usb_open(), 293 usb_reap_async(), 302 usb_reap_async_nocancel(), 302 usb_release_interface(), 296 usb_reset(), 296 usb_set_altinterface(), 295 usb_set_configuration(), 295 usb_set_debug(), 293 usb_submit_async(), 302 UsbBuildGetDescriptorRequest(), 267 WaitForSingleObject(), 229 WaitForSingleObjectEx(), 232 WinUsb_AbortPipe(), 280 WinUsb_ControlTransfer(), 280 WinUsb_FlushPipe(), 282 WinUsb_Free(), 280 WinUsb_GetAssociatedInterface(), 283 WinUsb_GetCurrentAlternateSetting(), 283 WinUsb_GetDescriptor(), 283 WinUsb_GetOverlappedResult(), 284 WinUsb_GetPipePolicy(), 284 WinUsb_GetPowerPolicy(), 285 WinUsb_Initialize(), 278 WinUsb_QueryDeviceInformation(), 285 WinUsb_QueryInterfaceSettings(), 286 WinUsb_QueryPipe(), 286 WinUsb_ReadPipe(), 287 WinUsb_ResetPipe(), 288 WinUsb_SetCurrentAlternateSetting(), 288 WinUsb_SetPipePolicy(), 288 WinUsb_SetPowerPolicy(), 288
419
WinUsb_WritePipe(), 289 WriteFile(), 117, 225, 226 WriteFileEx(), 230 funkcje API SDK, 407 asynchroniczne, 301 biblioteki HID.dll, 145 biblioteki LibUSB, 292 biblioteki Setupapi.dll, 182 biblioteki WinUSB, 277 eksportowe, 145, 278 urządzeń klasy HID, 116
G GPIB, General Purpose Interface Bus, 404 GUID, Globally Unique Identifier, 67
H HDMI, 12 HS, High Speed, 18
I IAD, Interface Association Descriptor, 99 identyfikacja kontrolera PCI, 245 urządzeń, 247 identyfikator DeviceInterfaceGUIDs, 272 GUID, 67 identyfikatory interfejsu, 327 producenta VID, 64, 68 produktu PID, 64, 68 sprzętu, hardware ID, 58, 98 urządzenia, 57 zgodności, compatible IDs, 58, 98 IEEE 1394d, 11 IEEE-488, 404 informacje o certyfikacie, 275 o urządzeniach, 81, 196 instalacja urządzenia, 272 integralność danych, 400 interfejs IDeviceFactory, 341 IUnknown, 326 interfejsy, 95, 319 dodawanie funkcji, 336 dodawanie metody, 335 izochroniczna transmisja danych, 11
420
USB. Praktyczne programowanie z Windows API w C++
J jednostki miar, 115
K kabel USB 2.0, 14 USB 3.0, 15 klasa TButton, 224 TForm1, 220 Thread, 382 TInterfacedObject, 330 TProgressBar, 224 TThread, 389 TTrackBar, 224 TUSBDetect, 337 TUSBDevice, 308, 315, 359 klasy instalacji urządzeń, 58 urządzeń, 60, 67 klucz HKEY_LOCAL_MACHINE, 64 KMDF, Kernel-Mode Driver Framework, 105, 271 kod BCD, 83 komenda AT, 406 AT+CCLK?, 411 ATI, 411 komentarze, 70 komponenty wizualne, 336 komunikacja programu z urządzeniem, 104 komunikat o odłączeniu, 188 koncentrator, 84 USB, 247 USB 3.0, 18, 84 konfiguracja urządzeń USB, 75, 100
L linie transmisyjne, 18 lista interfejsów, 367 logiczna struktura typów danych, 254, 259, 261 LPT, 11 LS, Low Speed, 12
Ł łącza szeregowe, 12
M magazyn certyfikatów, 275 magistrala GpIB, 404 makrodefinicja CTL_CODE, 237 menedżer certmgr, 274 urządzeń, 59, 62, 108 MI, Multiple Interfaces, 98 mikroramka, 24 mikrozłącza USB 3.0, 21 ministerownik, minidriver, 105 model ISO OSI, 73 logiczny urządzenia, 303 realizacji interfejsów, 320 warstwowy sterowników, 106, 277, 291 modele architektury, 77 moduł cfgmgr32.h, 176 cstring.h, 199 hidclass.h, 242 hidusage.h, 112 setupapi.h, 151 system.hpp, 326, 381 usb.h, 79, 98 usb100.h, 81, 86, 93, 99 USBDetect.cpp, 338 usbdlib.h, 267 usbioctl.h, 85, 88, 103, 245 usbiodef.h, 174 usbspec.h, 81, 83, 87, 94 usbtypes.h, 90, 96, 97, 100, 102 windows.h, 186 MTP, Media Transfer Protocol, 278
N nazwa symboliczna urządzenia, 65, 192 nazwy zmiennych, 70 numeracja styków USB 2.0 typu A i B, 14 USB 2.0 typu Micro-A i Micro-B, 21 USB 2.0 typu Mini-A i Mini-B, 20 USB 3.0 typu A i B, 16 USB 3.0 typu Micro-A, 22 USB 3.0 typu Micro-B, 22 USB 3.0 typu Powered-B, 19
Skorowidz
421
O OBEX, Object Exchange, 406 obiekt urządzenia, 109 obiektowość, 307 odblokowanie urządzenia, 191 odczyt danych, 198 danych cykliczny, 303 własności przycisków, 208 własności wartości, 213 zawartości deskryptorów, 292 określanie typów urządzeń, 177 opis deskryptorów, 80 oznaczenia urządzeń USB, 13
P pakiet ACK, 37 CSPLIT, 31 instalacyjny, 272 NAK, 34 PING, 34 potwierdzenia, handshake packet, 33, 37 preambuły, preamble packet, 33 SETUP, 31 SOF, 31 SPLIT, 31 SSPLIT, 31 pakiety danych, data packets, 30 USB 2.0, 28 zapowiedzi, token packets, 31 parametry transmisji, 284 PCI Express, 12 PDO, Physical Device Object, 84 PID, Packet Identifier, 29 PID, Product ID, 64 pliki .cat, 274 .dll, 144 .inf, 69, 70, 274 .lib, 144 PnP, Plug and Play, 152 pobieranie raportu wejściowego, 232 podklucz \Class, 65 \Device Parameters, 65 \Driver, 66 \Enum\USB, 64 podłączanie urządzeń, 185 podpisywanie sterowników, 274
pola pakietu SPLIT, 32 pole ADDR, 30 bEndpointAddress, 93, 94 ConnectionIndex, 254 CRC, 30 Dane, 30 ENDP, 30 EOP, 30 PID, 28 SYNC, 28 polecenia typu Execution, 407 Read, 407 Set, 407 Test, 407 połączenia w trybie Super Speed, 77 port adaptera USB/RS 232C, 402 potok, pipe, 78 potoki danych, 77 pozycja Collection, 112 End Collection, 112 Unit, 116 Usage, 111 prędkości transmisji, 402 proces, 379 program certmgr.exe, 274 inf2cat.exe, 274 makecert.exe, 274 signtool.exe, 274 programowanie obiektowe, 307 programy obiektowe, 359 proceduralne, 352 wielowątkowe, 379 protokół MTP, 278 RFCOMM, 406 przekształcanie identyfikatora GUID, 68 punkt końcowy, endpoint, 24, 78, 89
R ramka, frame, 24 raport konfiguracyjny, future report, 113 wejściowy, input report, 113 wyj ściowy, output report, 113 rejestr systemowy, 63 RFCOMM, Radio Frequency Communication, 406 rodzaje raportów, 113
422
USB. Praktyczne programowanie z Windows API w C++
rozkaz ATD, 411 rozkazy IOCTL_Xxx, 242 struktury SetupPacket, 256 z modułu hidclass.h, 242 usbioctl.h, 245 RS-232C, 11, 401
S semafor, semaphore, 397 SIE, System Interface Engine, 76 SIG, Special Interest Group, 405 singleton, 314 słowo kluczowe HIDD_ATTRIBUTES, 118 HIDP_CAPS, 128 HUB_DEVICE_CONFIG_INFO, 103 LPUSB_CONFIGURATION, 101 LPUSB_INTERFACE, 97 PHIDD_ ATTRIBUTES, 118 PHIDP_CAPS, 128 PHUB_ DEVICE_CONFIG_INFO, 103 PUSB_ CONFIGURATION, 101 PUSB_ ID_STRING, 104 PUSB_ INTERFACE, 97 PUSB_DEVICE i LPUSB_DEVICE, 103 PUSB_HUB_ DESCRIPTOR, 86 PUSB_INTERFACE_DESCRIPTOR, 97 PUSB_STRING_DESCRIPTOR, 104 PUSB_SUPERSPEED_ENDPOINT_ COMPANION_DESCRIPTOR, 95 PUSBD_INTERFACE_INFORMATION, 98 PUSBD_PIPE_INFORMATION, 79 USB_CONFIGURATION, 101 USB_CONFIGURATION_DESCRIPTOR, 102 USB_DEVICE, 103 USB_HUB_DESCRIPTOR, 86 USB_ID_STRING, 104 USB_INTERFACE, 97 USB_INTERFACE_DESCRIPTOR, 97 USB_STRING_DESCRIPTOR, 104 USB_SUPERSPEED_ENDPOINT_ COMPANION_ DESCRIPTOR, 95 USBD_INTERFACE_INFORMATION, 98 USBD_PIPE_INFORMATION, 79 USBD_PIPE_TYPE, 79 SOF, Start of Frame, 24 sposoby połączenia urządzeń, 19 SS, Super Speed, 18 stan wątku, 380 standardy łączy szeregowych, 12
stany urządzenia, 149 sterownik, 57 libusb0.sys, 291 Usbccgp.sys, 98, 106 Usbd.sys, 107 winusb.sys, 271 sterowniki filtrujące, filter drivers, 105 klas urządzeń, 105 klienta, client drivers, 105 magistrali danych, bus drivers, 105 operacyjne, function drivers, 105 stos sterowników kontrolera hosta, 107 urządzenia winusb, 277 USB 2.0, 107, 109 USB 3.0, 108, 109 struktura COMMTIMEOUTS, 234 DEV_BROADCAST_DEVICEINTERFACE, 185 DEVICE_DATA, 168, 351, 360, 376 HIDD_ATTRIBUTES, 118, 352 HIDP_CAPS, 127 HUB_DEVICE_CONFIG_INFO, 103 OVERLAPPED, 227 SetupPacket, 256 SP_DEVICE_INTERFACE_DATA, 154 SP_DEVICE_INTERFACE_DETAIL_DATA, 155 SP_DEVINFO_DATA, 153 systemu USB 2.0, 73 USB 3.0, 76 USB_30_HUB_DESCRIPTOR, 87 USB_CONFIGURATION, 101 USB_CONFIGURATION_DESCRIPTOR, 101, 102 USB_DESCRIPTOR_REQUEST, 256 USB_DEVICE, 102 USB_DEVICE_DESCRIPTOR, 82 USB_ENDPOINT, 90 USB_ENDPOINT_DESCRIPTOR, 90-93 USB_HUB_DESCRIPTOR, 86 USB_HUB_INFORMATION, 86 USB_HUB_INFORMATION_EX, 88 USB_ID_STRING, 103 USB_INTERFACE, 97 USB_INTERFACE_ASSOCIATION_ DESCRIPTOR, 99 USB_INTERFACE_DESCRIPTOR, 96 USB_STRING_DESCRIPTOR, 104 USB_SUPERSPEED_ENDPOINT_ COMPANION_DESCRIPTOR, 95
Skorowidz
423
USBD_INTERFACE_INFORMATION, 97 USBD_PIPE_INE ORMATION, 79 wielowątkowego programu, 390 WINUSB_PIPE_INFORMATION, 287 WINUSB_SETUP_PACKET, 281 struktury danych, 168, 351 logiczne programu obiektowego, 360 logiczne urządzenia, 80 suma kontrolna pakietu, 30 szybkość transferu danych, 12, 18
s środowisko graficzne, 366
T Thunderbolt, 11 topologia magistrali USB, 76 systemu USB 3.0, 85 transakcje dzielone, split transactions, 38 izochroniczne, isochronous transactions, 36 kontrolne, control transactions, 36 masowe, bulk transactions, 33 przerwaniowe, interrupt transactions, 35 USB 2.0, 33 transfer izochroniczny, isochronous transfer, 27 kontrolny, control transfer, 28 masowy, bulk transfer, 25, 300 przerwaniowy, interrupt transfer, 26, 301 transmisja asynchroniczna, 407 bezprzewodowa, 407 izochroniczna, 11, 36 szeregowa, 24, 401 tryb pracy asynchroniczny, 241 synchroniczny, 241 tworzenie certyfikatu, 274 komponentu, 335 magazynu certyfikatów, 274 obiektu klasy TUSBDevice, 366 pakietu instalacyjnego, 272, 290 pliku .cat, 275 typ wyliczeniowy HIDP_REPORT_TYPE, 126 USB_DEVICE_SPEED, 83 USB_HUB_TYPE, 84 USBD_PIPE_TYPE, 78
typy danych, 78 sterowników, 105 transferów, 25, 34 urządzeń USB, 177 wtyczek i gniazd, 23
U UMDF, User-Mode Driver Framework, 105, 271 UNC, Universal Naming Convention, 192 unia USB_HUB_CAP_FLAGS, 88 urządzenia, 57, 68 klasy HID, 111 PnP, 68, 152 winusb, 274 xHCI, 108 urządzenie libusb, 291 USBDevice, 335 USB 1.0, 7 USB 2.0, 7 USB 3.0, 7, 11 USB OTG, 7, 20 ustawienia portu adaptera, 403
V VID, Vendor ID, 64
W warstwa fizyczna, physical layer, 74, 76 funkcjonalna, 73 logiczna, 75 łącza, link layer, 76 protokołu, protocol layer, 76 wątek, thread, 379 WDF, Windows Driver Foundation, 271 WDK, Windows Driver Kit, 8, 79, 83, 151 WDM, Windows Driver Model, 105 węzeł, node, 75 wirtualny port szeregowy, 407 wizualizacja danych, 366 właściwości portu adaptera, 402 wymiana informacji, 73 wyprowadzenia w złączach USB 2.0 Mini/Micro A i B, 20 USB 2.0 typu A i B, 15 USB 3.0 Micro-A/AB, 21 USB 3.0 Micro-B, 23 USB 3.0 Powered-B, 18
42 4
USB. Praktyczne programowanie z Windows API w C++
wyprowadzenia w złączach USB 3.0 typu A, 17 USB 3.0 typu B, 17 wysłanie rozkazu nawiązania połączenia, 413 wyszukiwanie sterownika, 58 wywłaszczenie, 380 wzajemne wykluczenie, mutual exclusion, 398 wzorzec fabryki, 341 obserwatora, 343
Z zakres wartości danych, 115 zapis danych, 225 zarządzanie urządzeniem libusb, 293 zliczanie interfejsów urządzeń, 161 odwołań do interfejsu, 326 złącza Micro, 19 Mini, 19
złącze USB 2.0 typu A, 14 USB 2.0 typu B, 14 USB 2.0 typu Micro-B, 20 USB 2.0 typu Mini-A, 20 USB 3.0 typu A, 16 USB 3.0 typu B, 16 USB 3.0 typu Micro-A/AB, 21 USB 3.0 typu Micro-B, 22 USB 3.0 typu Powered-B, 18, 19 znacznik czasu, 24 DeviceIsHub, 259 FILE_ FLAG_OVERLAPPED, 228 SOF, 24 znak nowej linii, 407 powrotu karetki, 407