Professional Documents
Culture Documents
Funkcja HidP_GetData()
Za pośrednictwem wskaźnika DataList funkcja wydobywa tablicę struktur:
typedef struct _HIDP_DATA
{
USHORT DataIndex;
USHORT Reserved;
union {
ULONG RawValue; // wartości
BOOLEAN On; // przyciski w stanie ON (true)
};
} 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 __stdcall
HidP_GetData(
IN HIDP_REPORT_TYPE ReportType,
OUT PHIDP_DATA DataList,
IN OUT PULONG DataLength,
IN PHIDP_PREPARSED_DATA PreparsedData,
IN PCHAR Report,
IN ULONG ReportLength
);
printf("%d %d %d %d %d %d %d\n",
inputReportBuffer[0],inputReportBuffer[1],inputReportBuffer[2],
inputReportBuffer[3],inputReportBuffer[4],inputReportBuffer[5],
inputReportBuffer[6]);
printf("DataIndex=%d\n",DataList[5].DataIndex);
printf("RawValue=%d\n",DataList[5].RawValue);
printf("\n");
//...
//---------------------------------------------------------
Funkcja HidP_GetExtendedAttributes()
Za pośrednictwem wskaźnika Attributes funkcja zwraca rozszerzone atrybuty elemen-
tu kontrolnego urządzenia HID.
NTSTATUS __stdcall
HidP_GetExtendedAttributes(
IN HIDP_REPORT_TYPE ReportType,
IN USHORT DataIndex,
IN PHIDP_PREPARSED_DATA PreparsedData,
OUT PHIDP_EXTENDED_ATTRIBUTES Attributes,
PULONG LengthAttributes
);
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 gamepad 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
HidP_GetLinkCollectionNodes(
OUT PHIDP_LINK_COLLECTION_NODE LinkCollectionNodes,
IN OUT PULONG LinkCollectionNodesLength,
IN PHIDP_PREPARSED_DATA PreparsedData
);
USAGE LinkUsagePage;
USHORT Parent;
USHORT NumberOfChildren;
USHORT NextSibling;
USHORT FirstChild;
ULONG CollectionType: 8;
ULONG IsAlias: 1;
ULONG Reserved: 23;
PVOID UserContext;
} HIDP_LINK_COLLECTION_NODE, *PHIDP_LINK_COLLECTION_NODE;
Funkcja HidP_GetScaledUsageValue()
Poprzez parametr UsageValue funkcja wydobywa wybraną i przeskalowaną wartość
będącą elementem raportu dla urządzenia z danej grupy urządzeń. Dane są ekstrapolo-
wane w sposób liniowy między wartościami fizycznego maksimum (minimum) i lo-
gicznego maksimum (minimum). Wartość logiczna jest zwracana przez urządzenie,
a fizyczna przez funkcję.
NTSTATUS __stdcall
HidP_GetScaledUsageValue(
IN HIDP_REPORT_TYPE ReportType,
IN USAGE UsagePage,
IN USHORT LinkCollection OPTIONAL,
IN USAGE Usage,
132 USB. Praktyczne programowanie z Windows API w C++
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 __stdcall
HidP_GetSpecificButtonCaps(
IN HIDP_REPORT_TYPE ReportType,
IN USAGE UsagePage,
IN USHORT LinkCollection,
IN USAGE Usage,
OUT PHIDP_BUTTON_CAPS ButtonCaps,
IN OUT PULONG ButtonCapsLength,
IN PHIDP_PREPARSED_DATA PreparsedData
);
Niezerowy parametr UsagePage pełni funkcję kryterium wyszukiwania dla danej grupy
urządzeń. Parametr LinkCollection określa kryterium wyszukiwania w obrębie de-
skryptora. Parametr wejściowy Usage pełni funkcję kryterium wyszukiwania dla wy-
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 *buttonCaps = new \
HIDP_BUTTON_CAPS[capabilities.NumberInputButtonCaps];
USAGE UsagePage = HID_USAGE_PAGE_BUTTON;
USHORT LinkCollection = 0;
USAGE Usage = 0;
HidP_GetSpecificButtonCaps(HidP_Input, UsagePage, LinkCollection, Usage,
buttonCaps, buttonCapsLength, preparsedData);
for(USHORT i = 0; i<capabilities.NumberInputButtonCaps;i++) {
printf("ButtonCaps[%d].UsagePage %x\n\n", i, buttonCaps[i].UsagePage);
if(buttonCaps[i].IsRange) {
printf("Usages min, max %d..%d\n\n", buttonCaps[i].Range.UsageMin,
buttonCaps[i].Range.UsageMax);
Rozdział 4. ♦ Urządzenia klasy HID 133
Funkcja HidP_GetSpecificValueCaps()
Funkcja wydobywa z raportu właściwość tablicową wartości o charakterze ciągłym,
spełniającą wybrane kryteria wyszukiwania.
NTSTATUS __stdcall
HidP_GetSpecificValueCaps(
IN HIDP_REPORT_TYPE ReportType,
IN USAGE UsagePage,
IN USHORT LinkCollection,
IN USAGE Usage,
OUT PHIDP_VALUE_CAPS ValueCaps,
IN OUT PULONG ValueCapsLength,
IN PHIDP_PREPARSED_DATA PreparsedData
);
Niezerowy parametr 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. Wskaźnik ValueCapsLength wskazuje daną zawierającą rozmiar bufora danych.
//---------------------------------------------------------
#define HID_USAGE_PAGE_LED ((USAGE) 0x08) //hidusage.h
//...
PULONG valueCapsLength;
HIDP_VALUE_CAPS *valueCaps = new \
HIDP_VALUE_CAPS[capabilities.NumberOutputValueCaps];
USAGE UsagePage = HID_USAGE_PAGE_LED;
USHORT LinkCollection = 0;
USAGE Usage = 0;
HidP_GetSpecificValueCaps(HidP_Output,UsagePage, LinkCollection, Usage,
valueCaps, valueCapsLength, preparsedData);
for(USHORT i = 0; i<capabilities.NumberOutputValueCaps;i++) {
printf("ValueCaps[%d].UsagePage %x\n", i, valueCaps[i].UsagePage);
printf("ValueCaps[%d].PhysicalMin %d\n", i, valueCaps[i].PhysicalMin);
printf("ValueCaps[%d].PhysicalMax %d\n", i, valueCaps[i].PhysicalMax);
printf("ValueCaps[%d].BitSize %d\n", i, valueCaps[i].BitSize);
printf("ValueCaps[%d].ReportCount %d\n\n", i, valueCaps[i].ReportCount);
}
//---------------------------------------------------------
Funkcja HidP_GetUsageValue()
Funkcja wydobywa dane z raportu spełniającego wybrane kryteria wyszukiwania.
NTSTATUS __stdcall
HidP_GetUsageValue(
IN HIDP_REPORT_TYPE ReportType,
IN USAGE UsagePage,
IN USHORT LinkCollection,
IN USAGE Usage,
OUT PULONG UsageValue,
IN PHIDP_PREPARSED_DATA PreparsedData,
IN PCHAR Report,
IN ULONG ReportLength
);
Funkcja HidP_GetUsageValueArray()
Funkcja wydobywa dane z raportu składającego się z więcej niż jednego pola.
NTSTATUS __stdcall
HidP_GetUsageValueArray(
IN HIDP_REPORT_TYPE ReportType,
IN USAGE UsagePage,
IN USHORT LinkCollection OPTIONAL,
IN USAGE Usage,
OUT PCHAR UsageValue,
IN USHORT UsageValueByteLength,
IN PHIDP_PREPARSED_DATA PreparsedData,
IN PCHAR Report,
IN ULONG ReportLength
);
Wskaźnik ReportType wskazuje typ raportu; może być HidP_Output lub HidP_Feature.
Parametr 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 BitSize i ReportCount struktury HIDP_VALUE_CAPS.
Funkcja HidP_GetUsages()
Funkcja zwraca liczbę dyskretnych elementów sterujących pozostających w stanie ON.
NTSTATUS __stdcall
HidP_GetUsages(
IN HIDP_REPORT_TYPE ReportType,
IN USAGE UsagePage,
IN USHORT LinkCollection,
OUT USAGE* UsageList,
IN OUT ULONG* UsageLength,
IN PHIDP_PREPARSED_DATA PreparsedData,
IN PCHAR Report,
IN ULONG ReportLength
);
Wskaźnik UsageList wskazuje bufor danych, poprzez który funkcja zwraca wynik dzia-
łania. Użyty jako parametr wejściowy UsageLength określa rozmiar bufora danych,
a użyty jako parametr wyjściowy podaje liczbę elementów w stanie ON. Wskaźnik
Report wskazuje rodzaj raportu (HidP_Input lub HidP_Feature), a ReportLength okre-
śla jego długość.
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 __stdcall
HidP_GetUsagesEx(
IN HIDP_REPORT_TYPE ReportType,
IN USHORT LinkCollection OPTIONAL,
IN OUT PUSAGE_AND_PAGE ButtonList,
IN OUT ULONG * UsageLength,
IN PHIDP_PREPARSED_DATA PreparsedData,
IN PCHAR Report,
IN ULONG ReportLength
);
Funkcja HidP_GetValueCaps()
Funkcja zwraca wskaźnik ValueCaps do struktury HIDP_VALUE_CAPS przechowującej
informacje na temat właściwości wartości niebinarnych (o charakterze ciągłym), bę-
dących elementami wyspecyfikowanego raportu urządzeń klasy HID.
NTSTATUS
HidP_GetValueCaps(
IN HIDP_REPORT_TYPE ReportType,
OUT PHIDP_VALUE_CAPS ValueCaps,
IN OUT PULONG ValueCapsLength,
IN PHIDP_PREPARSED_DATA PreparsedData
);
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ą wy-
zerowane albo zostanie im przypisany pusty wskaźnik NULL.
NTSTATUS __stdcall
HidP_InitializeReportForID(
IN HIDP_REPORT_TYPE ReportType,
138 USB. Praktyczne programowanie z Windows API w C++
IN UCHAR ReportID,
IN PHIDP_PREPARSED_DATA PreparsedData,
IN OUT PCHAR Report,
IN ULONG ReportLength
);
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 HidP_MaxDataListLength(
HIDP_REPORT_TYPE ReportType,
PHIDP_PREPARSED_DATA PreparsedData
);
//---------------------------------------------------------
ULONG dataListLength =
HidP_MaxDataListLength(HidP_Input, preparsedData);
printf("MaxDataListLength=%d\n", dataListLength);
//---------------------------------------------------------
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
HidP_MaxUsageListLength(
IN HIDP_REPORT_TYPE ReportType,
IN USAGE UsagePage OPTIONAL,
IN PHIDP_PREPARSED_DATA PreparsedData
);
ULONG usageListLength =
HidP_MaxUsageListLength(HidP_Input, UsagePage, preparsedData);
printf("\n%d", usageListLength);
//--------------------------------------------------
Funkcja HidP_SetData()
Funkcja umieszcza w raporcie identyfikowanym przez ReportType wyspecyfikowany
zbiór danych.
NTSTATUS __stdcall
HidP_SetData(
IN HIDP_REPORT_TYPE ReportType,
IN PHIDP_DATA DataList,
IN OUT PULONG DataLength,
IN PHIDP_PREPARSED_DATA PreparsedData,
IN OUT PCHAR Report,
IN ULONG ReportLength
);
Funkcja HidP_SetButtons()
Funkcja HidP_SetButtons() jest synonimem funkcji HidP_SetUsages().
#define HidP_SetButtons(Rty, Up, Lco, ULi, ULe, Ppd, Rep, Rle) \
HidP_SetUsages(Rty, Up, Lco, ULi, ULe, Ppd, Rep, Rle)
Funkcja HidP_SetScaledUsageValue()
Funkcja przekształca wybraną wielkość fizyczną UsageValue na odpowiadającą jej war-
tość logiczną i umieszcza w wyspecyfikowanym przez ReportType raporcie.
NTSTATUS __stdcall
HidP_SetScaledUsageValue(
IN HIDP_REPORT_TYPE ReportType,
IN USAGE UsagePage,
IN USHORT LinkCollection OPTIONAL,
IN USAGE Usage,
IN LONG UsageValue,
IN PHIDP_PREPARSED_DATA PreparsedData,
IN OUT PCHAR Report,
IN ULONG ReportLength
);
Funkcja HidP_SetUsageValue()
W raporcie wyspecyfikowanym przez ReportType funkcja umieszcza określone war-
tości, korzystając z pól struktury HIDP_VALUE_CAPS.
NTSTATUS __stdcall
HidP_SetUsageValue(
IN HIDP_REPORT_TYPE ReportType,
IN USAGE UsagePage,
IN USHORT LinkCollection,
IN USAGE Usage,
IN ULONG UsageValue,
IN PHIDP_PREPARSED_DATA PreparsedData,
IN OUT PCHAR Report,
IN ULONG ReportLength
);
Funkcja HidP_SetUsageValueArray()
Funkcja umieszcza w buforze wskazywanym przez UsageValue (o rozmiarze UsageVa-
lueByteLength) tablicę danych przeznaczonych dla danego typu raportu HidP_Output
lub HidP_Feature. Raport powinien się składać z więcej niż jednego pola.
NTSTATUS __stdcall
HidP_SetUsageValueArray(
HIDP_REPORT_TYPE ReportType,
USAGE UsagePage,
USHORT LinkCollection,
USAGE Usage,
PCHAR UsageValue,
USHORT UsageValueByteLength,
PHIDP_PREPARSED_DATA PreparsedData,
PCHAR Report,
ULONG ReportLength
);
Funkcja HidP_SetUsages()
Funkcja umieszcza w raporcie wartości odpowiadające stanowi ON dla elementów
kontrolnych lub sterujących urządzenia.
NTSTATUS __stdcall
HidP_SetUsages(
IN HIDP_REPORT_TYPE ReportType,
IN USAGE UsagePage,
IN USHORT LinkCollection,
IN OUT PUSAGE UsageList,
IN OUT PULONG UsageLength,
IN PHIDP_PREPARSED_DATA PreparsedData,
Rozdział 4. ♦ Urządzenia klasy HID 141
if(Usage1!=0)
UsageLength++;
if(Usage2!=0)
UsageLength++;
if(Usage3!=0)
UsageLength++;
if(Usage4!=0)
UsageLength++;
//...
HidP_SetUsages(HidP_Output, HID_USAGE_PAGE_LED, 0, UsageList,
&UsageLength, preparsedData, outputReportBuffer,
capabilities.OutputReportByteLength);
//...
DWORD numberOfBytesWrite = 0;
WriteFile(/*...*/, &outputReportBuffer,
capabilities.OutputReportByteLength,
&numberOfBytesWrite, NULL))
//...
//---------------------------------------------------------
142 USB. Praktyczne programowanie z Windows API w C++
Funkcja HidP_UnsetUsages()
Funkcja umieszcza wartość binarną w stanie OFF w raporcie wyspecyfikowanym przez
parametr ReportType.
NTSTATUS __stdcall
HidP_UnsetUsages(
IN HIDP_REPORT_TYPE ReportType,
IN USAGE UsagePage,
IN USHORT LinkCollection OPTIONAL,
IN OUT PUSAGE UsageList,
IN OUT PULONG UsageLength,
IN PHIDP_PREPARSED_DATA PreparsedData,
IN OUT PCHAR Report,
IN ULONG ReportLength
);
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 powinna być równa analogicznej wartości
zwracanej przez funkcję HidP_GetCaps().
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 wywołań takich funkcji jak HidP_
GetUsagesEx() lub HidP_GetButtons() w trakcie odczytu raportu wejściowego.
NTSTATUS __stdcall
HidP_UsageListDifference(
IN PUSAGE PreviousUsageList,
IN PUSAGE CurrentUsageList,
OUT PUSAGE BreakUsageList,
OUT PUSAGE MakeUsageList,
IN ULONG UsageListLength
);
HidP_UsageListDifference(previousUsages, currentUsages,
breakUsages, makeUsages, usageListLength);
printf("BreakList: ");
for(ULONG i=0; i<usageListLength; i++){
if(breakUsages[i]==0) break;
printf("%d ", breakUsages[i]);
}
printf("MakeList: ");
for(ULONG i=0; i<usageListLength; i++){
if(makeUsages[i]==0) break;
printf("%d ", makeUsages[i]);
}
printf("\n\n");
//zapis poprzednich ustawień
memcpy(previousUsages, currentUsages, usageListLength*sizeof(USAGE));
delete [] currentUsages;
delete [] breakUsages;
delete [] makeUsages;
}
//...
//---------------------------------------------------------
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.
144 USB. Praktyczne programowanie z Windows API w C++
NTSTATUS __stdcall
HidP_UsageAndPageListDifference(
IN PUSAGE_AND_PAGE PreviousUsageList,
IN PUSAGE_AND_PAGE CurrentUsageList,
OUT PUSAGE_AND_PAGE BreakUsageList,
OUT PUSAGE_AND_PAGE MakeUsageList,
IN ULONG UsageListLength
);
Biblioteka HID.dll
Wszystkie aplikacje tworzone w środowisku Windows mają 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 wykonania 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) wymagają wywołań funkcji LoadLibrary(), GetProcAddress()
i FreeLibrary().
Funkcja LoadLibrary() odwzorowuje wskazany moduł wykonawczy (plik .dll lub .exe)
na przestrzeń adresową wykonywanego procesu.
HINSTANCE LoadLibrary(IN LPCTSTR lpLibFileName);
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 wy-
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
Podsumowanie
W niniejszym rozdziale zaprezentowano główne założenia transmisji danych z urzą-
dzeniami klasy HID. Przedstawiono najważniejsze struktury danych przechowujące
informacje o organizacji oraz najistotniejszych parametrach urządzeń klasy HID. Omó-
wiono zasoby biblioteki HID.dll oraz zaprezentowano formaty danych transmitowa-
nych przez łącze USB. Rozdział zawiera też opis podstawowych funkcji pomocnych
w samodzielnej realizacji transmisji danych z urządzeniami klasy HID. Wiele prak-
tycznych sposobów wykorzystania omówionych zasobów systemowych zostanie przed-
stawionych w dalszej części książki. Dodatkowe informacje na temat 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 enumeracji powoduje przejście urządzenia między podstawowymi stanami, jak
pokazano na rysunku 5.1.
Rysunek 5.2. Szczegółowy diagram czynności dla procesu enumeracji urządzeń dołączanych
do magistrali USB
Windows Driver Kit jest 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 lub __in w celu oznaczenia parametrów wejściowych, OUT lub __out
dla oznaczenia parametrów wyjściowych lub __opt dla oznaczenia parametrów
opcjonalnych. Możliwe jest również występowanie oznaczeń będących kombinacją
tych parametrów, np. __inout lub __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 systemowym w kluczu HKEY_LOCAL_MACHINE (patrz
rozdział 2.).
HDEVINFO
SetupDiGetClassDevs(
IN LPGUID ClassGuid, OPTIONAL
IN PCTSTR Enumerator, OPTIONAL
IN HWND hwndParent, OPTIONAL
IN DWORD Flags
);
Parametr ClassGuid wskazuje strukturę GUID klasy urządzeń. Użycie tego parametru jest
opcjonalne. Aplikacje użytkownika mogą pobierać adres identyfikatora GUID dla klasy
urządzeń HID za pomocą 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 parametru w programie
jest opcjonalne. Jeżeli wskaźnikowi 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.
Kod ewentualnego błędu można zdiagnozować za pomocą funkcji GetLastError().
Szczegółowe kody błędów są dostępne na stronie http://msdn.microsoft.com/en-us/
library/windows/desktop/ms681382(v=vs.85).aspx.
Funkcja SetupDiEnumDeviceInterfaces()
Funkcja wyszukuje interfejsy urządzeń identyfikowanych przez wskaźnik DeviceInfoSet
zwracany przez funkcję SetupDiGetClassDevs().
WINSETUPAPI BOOL WINAPI
SetupDiEnumDeviceInterfaces(
IN HDEVINFO DeviceInfoSet,
Rozdział 5. ♦ Detekcja i identyfikacja urządzeń dołączonych do magistrali USB 153
Struktura SP_DEVINFO_DATA
W polach struktury są przechowywane informacje na temat egzemplarza urządzenia
należącego do klasy urządzeń USB. W tabeli 5.1 zamieszczono jej opis.
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.
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.
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
SetupDiGetDeviceInterfaceDetail(
IN HDEVINFO DeviceInfoSet,
IN PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData,
OUT PSP_DEVICE_INTERFACE_DETAIL_DATA DeviceInterfaceDetailData, OPTIONAL
IN DWORD DeviceInterfaceDetailDataSize,
OUT PDWORD RequiredSize, OPTIONAL
OUT PSP_DEVINFO_DATA DeviceInfoData OPTIONAL
);
156 USB. Praktyczne programowanie z Windows API w C++
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
Funkcja SetupDiDestroyDeviceInfoList()
Funkcja usuwa wszystkie 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
SetupDiDestroyDeviceInfoList(
IN HDEVINFO DeviceInfoSet
);
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++
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.
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ń
#include <windows>
#pragma option push -a1
#include <setupapi>
#pragma option pop
#include <assert>
#include <iostream>
HDEVINFO deviceInfoSet;
SP_DEVICE_INTERFACE_DATA deviceInterfaceData;
PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData = NULL;
int main(){
//Odwzorowanie identyfikatora biblioteki HID.dll w przestrzeni
//adresowej głównego procesu
hHidLib = LoadLibrary("C:\\Windows\\system32\\HID.DLL");
if (!hHidLib)
displayError("Błąd dołączenia biblioteki HID.DLL.");
//Pobranie adresu funkcji eksportowej HidD_GetHidGuid()
void (__stdcall *HidD_GetHidGuid)(OUT LPGUID HidGuid);
/*(void __stdcall*/(FARPROC&) HidD_GetHidGuid = GetProcAddress(hHidLib,
"HidD_GetHidGuid");
if (!HidD_GetHidGuid){
FreeLibrary(hHidLib);
displayError("Nie znaleziono identyfikatora GUID.");
}
//Wywołanie funkcji HidD_GetHidGuid()
HidD_GetHidGuid(&classGuid);
//Procedury enumeracji urządzeń
deviceInfoSet = SetupDiGetClassDevs(&classGuid, 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);
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, &deviceInterfaceData,
deviceInterfaceDetailData, deviceInterfaceDetailDataSize,
&requiredSize, NULL)){
releaseMemory(deviceInterfaceDetailData);
160 USB. Praktyczne programowanie z Windows API w C++
//SetupDiDestroyDeviceInfoList(deviceInfoSet);
//displayError("Nie można pobrać informacji o interfejsie.\n");
}
// deviceInterfaceDetailData->DevicePath jest łączem symbolicznym
// do interfejsu urządzenia
cout << deviceInterfaceDetailData->DevicePath << endl;
releaseMemory(deviceInterfaceDetailData);
}; //koniec while
SetupDiDestroyDeviceInfoList(deviceInfoSet);
FreeLibrary(hHidLib);
cout << endl;
system("PAUSE");
return 0;
}
//---------------------------------------------------------
Na 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).
hHidLib = LoadLibrary("C:\\Windows\\system32\\HID.DLL");
if (!hHidLib)
displayError("Błąd dołączenia biblioteki HID.DLL.");
HidD_GetHidGuid (&classGuid);
deviceInterfaceData.cbSize = sizeof(SP_INTERFACE_DEVICE_DATA);
while(!done) {
for(; memberIndex < searchMaxDevice; memberIndex++) {
if(SetupDiEnumDeviceInterfaces(deviceInfoSet,0,&classGuid,
memberIndex,&deviceInterfaceData)) {
SetupDiGetDeviceInterfaceDetail(deviceInfoSet,&deviceInterfaceData,
NULL,0,&deviceInterfaceDetailDataSize,
NULL);
requiredSize = deviceInterfaceDetailDataSize;
deviceInterfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)\
new DWORD[deviceInterfaceDetailDataSize];
if(deviceInterfaceDetailData) {
deviceInterfaceDetailData->cbSize=\
sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA);
}
else {
SetupDiDestroyDeviceInfoList(deviceInfoSet);
releaseMemory(deviceInterfaceDetailData);
return 0;
}
if(!SetupDiGetDeviceInterfaceDetail(deviceInfoSet,
&deviceInterfaceData,deviceInterfaceDetailData,
requiredSize,&deviceInterfaceDetailDataSize,NULL)){
SetupDiDestroyDeviceInfoList(deviceInfoSet);
releaseMemory(deviceInterfaceDetailData);
return 0;
}
}
else {
if(ERROR_NO_MORE_ITEMS == GetLastError()){
done = TRUE;
break;
}
}
cout << deviceInterfaceDetailData->DevicePath << endl;
releaseMemory(deviceInterfaceDetailData);
}
}
SetupDiDestroyDeviceInfoList(deviceInfoSet);
FreeLibrary(hHidLib);
return memberIndex;
}
//---------------------------------------------------------
int main(){
cout << "\nLiczba interfejsów urządzeń klasy HID w systemie = "\
<< searchInterfaceHidDevices() << endl;
cout << endl;
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() wy-
dobywa właściwości zainstalowanych urządzeń PnP. Właściwości te można również
odczytać za pomocą edytora rejestru (patrz rozdział 2., rysunki 2.4 i 2.5).
WINSETUPAPI BOOL WINAPI
SetupDiGetDeviceRegistryProperty(
IN HDEVINFO DeviceInfoSet,
IN PSP_DEVINFO_DATA DeviceInfoData,
IN DWORD Property,
OUT PDWORD PropertyRegDataType, OPTIONAL
OUT PBYTE PropertyBuffer,
IN DWORD PropertyBufferSize,
OUT PDWORD RequiredSize OPTIONAL
);
HDEVINFO deviceInfoSet;
SP_DEVICE_INTERFACE_DATA deviceInterfaceData;
PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData = NULL;
SP_DEVINFO_DATA deviceInfoData;
//---------------------------------------------------------
string getRegistryPropertyString(HDEVINFO deviceInfoSet,
PSP_DEVINFO_DATA deviceInfoData, DWORD property)
{
DWORD propertyBufferSize = 0;
//DWORD propertyRegDataType = 0;
char *propertyBuffer = NULL;
hHidLib = LoadLibrary("C:\\Windows\\system32\\HID.DLL");
if (!hHidLib)
displayError("Błąd dołączenia biblioteki HID.DLL.");
FreeLibrary(hHidLib);
displayError("Nie znaleziono identyfikatora GUID.");
}
HidD_GetHidGuid(&classGuid);
deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &deviceInterfaceData,
NULL, 0, &deviceInterfaceDetailDataSize, NULL);
deviceInterfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)
new DWORD[deviceInterfaceDetailDataSize];
deviceInterfaceDetailData->cbSize=sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
deviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
if (!SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &deviceInterfaceData,
deviceInterfaceDetailData, deviceInterfaceDetailDataSize,
&requiredSize, &deviceInfoData)){
releaseMemory(deviceInterfaceDetailData);
SetupDiDestroyDeviceInfoList(deviceInfoSet);
//displayError ("Nie można pobrać informacji o interfejsie.\n");
}
//cout << deviceInterfaceDetailData->DevicePath << endl;
SetupDiDestroyDeviceInfoList(deviceInfoSet);
FreeLibrary(hHidLib);
cout << endl;
system("PAUSE");
return 0;
}
//---------------------------------------------------------
Rysunek 5.7.
Aplikacja
proj_USB_R5_3
w trakcie działania
Rozdział 5. ♦ Detekcja i identyfikacja urządzeń dołączonych do magistrali USB 167
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. Na listingu 5.4 pokazano przykładową strukturę DEVICE_DATA:
typedef struct _DEVICE_DATA {
TCHAR *HardwareId; //identyfikator sprzętu
TCHAR *Path; //łącze symboliczne
DWORD DeviceInstance;
} DEVICE_DATA, *PDEVICE_DATA;
//---------------------------------------------------------
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 deviceList;
//...
deviceList = (PDEVICE_DATA)new \
DEVICE_DATA[((memberIndex+1)*sizeof(DEVICE_DATA))];
HMODULE hHidLib;
HDEVINFO deviceInfoSet;
SP_INTERFACE_DEVICE_DATA deviceInterfaceData;
DWORD memberIndex = 0;
GUID classGuid;
PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData = NULL;
DWORD requiredSize = 0;
DWORD deviceInterfaceDetailDataSize = 0;
DWORD searchMaxDevice = 100; //maksymalna liczba interfejsów urządzeń
bool done = false;
hHidLib = LoadLibrary("C:\\Windows\\system32\\HID.DLL");
if (!hHidLib)
displayError("Błąd dołączenia biblioteki HID.DLL.");
if (!HidD_GetHidGuid){
FreeLibrary(hHidLib);
displayError("Nie znaleziono identyfikatora GUID.");
}
HidD_GetHidGuid (&classGuid);
deviceInterfaceData.cbSize = sizeof(SP_INTERFACE_DEVICE_DATA);
while(!done) {
deviceList = new DEVICE_DATA[((memberIndex+1)*sizeof(DEVICE_DATA))];
if(deviceInterfaceDetailData) {
deviceInterfaceDetailData->cbSize=\
sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA);
}
else {
SetupDiDestroyDeviceInfoList(deviceInfoSet);
releaseMemory(deviceInterfaceDetailData);
return 0;
}
deviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
if (!SetupDiGetDeviceInterfaceDetail(deviceInfoSet,
&deviceInterfaceData, deviceInterfaceDetailData,
deviceInterfaceDetailDataSize,
&requiredSize, &deviceInfoData)) {
SetupDiDestroyDeviceInfoList(deviceInfoSet);
releaseMemory(deviceInterfaceDetailData);
return 0;
}
strncpy(deviceList[memberIndex].Path,
deviceInterfaceDetailData->DevicePath, nLen);
deviceList[memberIndex].DeviceInstance = deviceInfoData.DevInst;
SetupDiGetDeviceRegistryProperty(deviceInfoSet, &deviceInfoData,
SPDRP_HARDWAREID, NULL, NULL, 0,
&propertyBufferSize);
Rozdział 5. ♦ Detekcja i identyfikacja urządzeń dołączonych do magistrali USB 171
SetupDiGetDeviceRegistryProperty(deviceInfoSet, &deviceInfoData,
SPDRP_HARDWAREID,NULL,
propertyBuffer, propertyBufferSize,
NULL);
deviceList[memberIndex].HardwareId = propertyBuffer;
cout <<"\nDeviceList["<<memberIndex<<"].HardwareId: \n" <<
deviceList[memberIndex].HardwareId << endl;
}
else {
if(ERROR_NO_MORE_ITEMS == GetLastError()){
done = TRUE;
break;
}
}
releaseMemory(propertyBuffer);
releaseMemory(deviceInterfaceDetailData);
releaseMemory(deviceList[memberIndex].Path);
}
releaseMemory(deviceList);
}
SetupDiDestroyDeviceInfoList(deviceInfoSet);
FreeLibrary(hHidLib);
return memberIndex;
}
//---------------------------------------------------------
int main(){
cout << setGetHidDeviceData() << endl;
cout << endl;
system("PAUSE");
return 0;
}
//---------------------------------------------------------
Rysunek 5.8.
Aplikacja
proj_USB_R5_4
w trakcie działania
172 USB. Praktyczne programowanie z Windows API w C++
Rysunek 5.9.
Uzyskiwanie dostępu
do złożonego
urządzenia USB
funkcjonującego
w podstawowym
modelu konfiguracji
(por. rysunek 3.13)
Gdy programista zna numer 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
#include <initguid>
//...
//---------------------------------------------------------
DEFINE_GUID(devInterfaceGUIDConstant, 0x4d1e55b2, 0xf16f, 0x11cf, \
0x88, 0xcb, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30);
GUID devInterfaceGUID = devInterfaceGUIDConstant;
//---------------------------------------------------------
char* getDevicePath(LPGUID devInterfaceGUID, /*UINT interfaceIndex*/)
{
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 173
deviceInfoSet = SetupDiGetClassDevs(devInterfaceGUID,
NULL, NULL,
(DIGCF_PRESENT |
DIGCF_DEVICEINTERFACE));
if(deviceInfoSet == INVALID_HANDLE_VALUE) {
//błąd
exit(1);
}
deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
bResult = SetupDiEnumDeviceInterfaces(deviceInfoSet, NULL,
devInterfaceGUID,
1, /*interfaceIndex numer interfejsu*/
&deviceInterfaceData);
if(bResult == FALSE) {
//błąd
SetupDiDestroyDeviceInfoList(deviceInfoSet);
exit(1);
}
SetupDiGetDeviceInterfaceDetail(deviceInfoSet,&deviceInterfaceData,
NULL,0,&deviceInterfaceDetailDataSize,
NULL);
deviceInterfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)
new DWORD[deviceInterfaceDetailDataSize];
if(deviceInterfaceDetailData == NULL) {
SetupDiDestroyDeviceInfoList(deviceInfoSet);
//błąd alokacji pamięci
exit(1);
}
deviceInterfaceDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
bResult = SetupDiGetDeviceInterfaceDetail(deviceInfoSet,
&deviceInterfaceData,
deviceInterfaceDetailData,
deviceInterfaceDetailDataSize,
&requiredSize,NULL);
if(bResult == FALSE) {
//błąd
SetupDiDestroyDeviceInfoList(deviceInfoSet);
delete [] deviceInterfaceDetailData;
exit(1);
}
return deviceInterfaceDetailData->DevicePath;
}
//---------------------------------------------------------
int main()
{
cout << getDevicePath(&devInterfaceGUID, /*...*/);
devObject = CreateFile(getDevicePath(&devInterfaceGUID),/*...*/);
//Patrz rozdział 6.
//...
}
//---------------------------------------------------------
174 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 moduł usbiodef.h, w którym m.in. zdefiniowanych jest wiele
identyfikatorów GUID, za pomocą których uzyskuje się ścieżki dostępu do interfejsów
wszystkich urządzeń USB aktualnie dostępnych w systemie.
/*3ABF6F2D-71C4-462a-8A92-1E6861E6AF27*/
static GUID GUID_DEVINTERFACE_USB_HOST_CONTROLLER =
{0x3abf6f2d, 0x71c4, 0x462a, {0x8a, 0x92, 0x1e, \
0x68, 0x61, 0xe6, 0xaf, 0x27}};
/*F18A0E88-C30C-11D0-8815-00A0C906BED8*/
static GUID GUID_DEVINTERFACE_USB_HUB =
{0xf18a0e88, 0xc30c, 0x11d0,{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-001111000030*/
static GUID GUID_DEVINTERFACE_HID =
{0x4D1E55B2, 0xF16F, 0x11CF, {0x88, 0xCB, 0x00, \
0x11, 0x11, 0x00, 0x00, 0x30}};
//---------------------------------------------------------
DWORD memberIndex = 0;
DWORD deviceInterfaceDetailDataSize;
DWORD requiredSize;
HDEVINFO deviceInfoSet;
SP_DEVICE_INTERFACE_DATA deviceInterfaceData;
PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData = NULL;
int main(){
deviceInfoSet = SetupDiGetClassDevs(&GUID_DEVINTERFACE_USB_DEVICE,
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,
&GUID_DEVINTERFACE_USB_DEVICE,
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, &deviceInterfaceData,
deviceInterfaceDetailData, deviceInterfaceDetailDataSize,
&requiredSize, NULL)){
releaseMemory(deviceInterfaceDetailData);
SetupDiDestroyDeviceInfoList(deviceInfoSet);
//displayError ("Nie można pobrać informacji o interfejsie.\n");
}
SetupDiDestroyDeviceInfoList(deviceInfoSet);
cout << endl;
system("PAUSE");
return 0;
}
//---------------------------------------------------------
176 USB. Praktyczne programowanie z Windows API w C++
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. Na listingu 5.8 zaprezentowano przykład wykorzystania funkcji:
CMAPI CONFIGRET
WINAPI CM_Get_DevNode_Registry_Property(
IN DEVINST dnDevInst,
IN ULONG ulProperty,
INOUT PULONG pulRegDataType,
INOUT PVOID Buffer,
INOUT PULONG pulLength,
IN ULONG ulFlags
);
if (deviceInfoSet == INVALID_HANDLE_VALUE)
return;
for(memberIndex = 0; ; memberIndex++) {
deviceInfoData.cbSize = sizeof (deviceInfoData);
if(!SetupDiEnumDeviceInfo(deviceInfoSet, memberIndex,
&deviceInfoData))
break;
cout << getDeviceDescription(deviceInfoData.DevInst) << endl;
}
return;
}
//---------------------------------------------------------
int main()
{
cout << "Rodzaj kontrolera USB: \n";
printUSBDevices();
cout << endl;
system("PAUSE");
return 0;
}
//---------------------------------------------------------
Na rysunkach 5.11 oraz 5.12 zaprezentowano wynik działania programu z listingu 5.8
odpowiednio w systemach Windows 7 z USB 2.0 oraz Windows 8 z USB 3.0.
#include <iostream>
#include "D:\\WINDDK\\7600.16385.1\\inc\\api\\cfgmgr32.h"
if(configRet == CR_SUCCESS) {
configRet = CM_Get_Device_ID(devInstChild, buffer, MAX_PATH, 0);
if(configRet == CR_SUCCESS) {
//printf(" %s\n", printProperty());
printf(" %s\n", buffer);
//delete [] propertyBuffer;
}
}
else {
continue;
}
}
else {
continue;
}
}
return;
}
//---------------------------------------------------------
int main()
{
printUSBDevices();
cout << endl;
system("PAUSE");
return 0;
}
//---------------------------------------------------------
Rysunek 5.13.
Detekcja ścieżek
wystąpień wszystkich
obiektów urządzeń
aktualnie
podłączonych do
magistrali USB
182 USB. Praktyczne programowanie z Windows API w C++
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 poziomu biblioteki Setupapi
wraz z ich odpowiednikami z modułu setupapi.h.
/* 3ABF6F2D-71C4-462a-8A92-1E6861E6AF27 */
static GUID GUID_DEVINTERFACE_USB_HOST_CONTROLLER =
{0x3abf6f2d, 0x71c4, 0x462a, {0x8a, 0x92, 0x1e, \
0x68, 0x61, 0xe6, 0xaf, 0x27}};
/* F18A0E88-C30C-11D0-8815-00A0C906BED8 */
static GUID GUID_DEVINTERFACE_USB_HUB =
{0xf18a0e88, 0xc30c, 0x11d0,{0x88, 0x15, 0x00, \
0xa0, 0xc9, 0x06, 0xbe, 0xd8}};
//---------------------------------------------------------
DWORD memberIndex = 0;
DWORD deviceInterfaceDetailDataSize;
DWORD requiredSize;
HMODULE hSetupApi;
HDEVINFO deviceInfoSet;
SP_DEVICE_INTERFACE_DATA deviceInterfaceData;
PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData = NULL;
int main(){
IN PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData,
OUT PSP_DEVICE_INTERFACE_DETAIL_DATA DeviceInterfaceDetailData,
IN DWORD DeviceInterfaceDetailDataSize,
OUT PDWORD RequiredSize, OPTIONAL
OUT PSP_DEVINFO_DATA DeviceInfoData OPTIONAL);
hSetupApi = LoadLibrary("C:\\Windows\\System32\\SETUPAPI.DLL");
if (!hSetupApi)
displayError("Błąd dołączenia biblioteki SETUPAPI.DLL.");
if (!SetupDiGetClassDevsA || !SetupDiEnumDeviceInterfaces ||
!SetupDiGetDeviceInterfaceDetailA || !SetupDiDestroyDeviceInfoList) {
FreeLibrary(hSetupApi);
displayError("Nie znaleziono funkcji eksportowych.");
}
deviceInfoSet = SetupDiGetClassDevsA(&GUID_DEVINTERFACE_USB_HUB,
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,
&GUID_DEVINTERFACE_USB_HUB,
memberIndex, &deviceInterfaceData)){
memberIndex++; //inkrementacja numeru interfejsu
SetupDiGetDeviceInterfaceDetailA(deviceInfoSet, &deviceInterfaceData,
NULL, 0, &deviceInterfaceDetailDataSize, NULL);
deviceInterfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)
new DWORD[deviceInterfaceDetailDataSize];
deviceInterfaceDetailData->cbSize=sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
if (!SetupDiGetDeviceInterfaceDetailA(deviceInfoSet, &deviceInterfaceData,
deviceInterfaceDetailData, deviceInterfaceDetailDataSize,
&requiredSize, NULL)){
releaseMemory(deviceInterfaceDetailData);
SetupDiDestroyDeviceInfoList(deviceInfoSet);
}
Rozdział 5. ♦ Detekcja i identyfikacja urządzeń dołączonych do magistrali USB 185
Powiadamianie o dołączaniu
i odłączaniu urządzeń
Podłączanie urządzeń LS i FS powoduje zmiany napięcia odpowiednio na liniach
D– i D+ (urządzenia HS oraz SS podłącza się tak samo jak FS). Każde urządzenie HS
jest na początku traktowane jak urządzenie FS. Dopiero w czasie konfiguracji hub HS
sprawdza, czy z tym urządzeniem możliwa jest komunikacja z wysoką 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.
Parametr hRecipient jest identyfikatorem obiektu (np. okna w programie), który otrzy-
ma powiadomienie. Parametr NotificationFilter 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).
//---------------------------------------------------------
bool hidDeviceNotify(HWND hwnd, GUID GUID_DEVINTERFACE_HID,
HDEVNOTIFY *hidDeviceNotify)
{
DEV_BROADCAST_DEVICEINTERFACE NotificationFilter;
ZeroMemory(&NotificationFilter, sizeof(NotificationFilter));
NotificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
NotificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
NotificationFilter.dbcc_classguid = GUID_DEVINTERFACE_USB_DEVICE;
*hidDeviceNotify = RegisterDeviceNotification(hwnd, &NotificationFilter,
DEVICE_NOTIFY_WINDOW_HANDLE);
if(!hidDeviceNotify)
Rozdział 5. ♦ Detekcja i identyfikacja urządzeń dołączonych do magistrali USB 187
return false;
return true;
}
//---------------------------------------------------------
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg) {
case WM_DEVICECHANGE:
MessageBox(NULL, "Jedno z urządzeń USB zostało odłączone/przyłączone!",
"Uwaga!", MB_ICONEXCLAMATION | MB_OK);
break;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}
//---------------------------------------------------------
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int)
{
HWND hWnd;
WNDCLASSEX wndClassEx;
HDEVNOTIFY hDeviceNotify;
MSG Msg;
wndClassEx.cbSize = sizeof(WNDCLASSEX);
wndClassEx.style = 0;
wndClassEx.lpfnWndProc = WindowProc;
wndClassEx.cbClsExtra = 0;
wndClassEx.cbWndExtra = 0;
wndClassEx.hInstance = hInstance;
wndClassEx.lpszClassName = "DeviceNotifyTest";
if(!RegisterClassEx(&wndClassEx)){
MessageBox(NULL, "Błąd rejestracji okna!", "Błąd!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
DispatchMessage(&Msg);
}
return Msg.wParam;
}
//---------------------------------------------------------
Rysunek 5.14.
Komunikaty
o odłączaniu
i przyłączaniu
urządzeń
if (!RegisterClass(&wincl)) {
DWORD error = GetLastError();
cout << "Błędne wykonanie RegisterClass(), błąd = " << error << endl;
return 1;
}
if (!hwnd) {
DWORD error = GetLastError();
cout << "Błędne wykonanie CreateWindowEx(), błąd = " << error << endl;
return 1;
}
if(!hidDeviceNotify(NULL, GUID_DEVINTERFACE_USB_DEVICE, &hDeviceNotify)){
cout << "Błąd wykonania funkcji hidDeviceNotify().\n";
return 1;
}
cout << "Funkcja hidDeviceNotify() wykonana pomyślnie.\n";
cout << "Oczekiwanie na powiadamianie o odłączaniu/"
"przyłączaniu urządzenia.\n"
"Aby zakończyć program, naciśnij Ctrl+C\n" << endl;
for (;;) {
MSG msg;
BOOL bRet = GetMessage(&msg, hwnd, 0, 0);
if ((bRet == 0) || (bRet == -1))
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
//---------------------------------------------------------
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 jest ł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. We wszyst-
kich operacjach wejścia-wyjścia zamiast 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 otwarcia pliku reprezentującego urządzenie (sterownik).
Zwraca 32- lub 64-bitowy identyfikator urządzenia przechowywany we właściwości
HANDLE, do którego będą adresowane wszystkie komunikaty i raporty.
Parametr dwShareMode wyszczególnia, w jaki 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.
Funkcja CloseHandle()
Przed zakończeniem działania programu należy zamknąć otwarty plik sterownika
urządzenia USB i zwolnić obszar pamięci przydzielony na jego identyfikator, korzy-
stając z funkcji:
BOOL WINAPI CloseHandle(IN HANDLE hObject);
Parametr wejściowy hObject jest zwracany przez funkcję CreateFile() i w pełni iden-
tyfikuje aktualnie używany sterownik urządzenia.
Rysunek 6.1. Diagram czynności dla operacji enumeracji z wykorzystaniem funkcji CreateFile()
int main()
{
void (__stdcall *HidD_GetHidGuid)(OUT LPGUID HidGuid);
bool (__stdcall *HidD_GetAttributes)(IN HANDLE HidDeviceObject,
OUT PHIDD_ATTRIBUTES Attributes);
bool (__stdcall *HidD_GetProductString)(IN HANDLE HidDeviceObject,
OUT PVOID Buffer,
IN ULONG BufferLength);
bool (__stdcall *HidD_GetManufacturerString)(IN HANDLE HidDeviceObject,
OUT PVOID Buffer,
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 (!hHidLib)
displayError("Błąd dołączenia biblioteki HID.DLL.");
(FARPROC&) HidD_GetPhysicalDescriptor=GetProcAddress(hHidLib,
"HidD_GetPhysicalDescriptor");
(FARPROC&) HidD_GetMsGenreDescriptor=GetProcAddress(hHidLib,
"HidD_GetMsGenreDescriptor");
HidD_GetHidGuid(&classGuid);
deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
hiddAttributes.Size = sizeof(HIDD_ATTRIBUTES);
hidDeviceObject=CreateFile(deviceInterfaceDetailData->DevicePath,
GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, 0, NULL );
if(hidDeviceObject != INVALID_HANDLE_VALUE) {
cout << "\n"<< deviceInterfaceDetailData->DevicePath << "\n";
HidD_GetAttributes(hidDeviceObject, &hiddAttributes);
printf("VID/PID/wersja = %x/%x/%x\n",hiddAttributes.VendorID,
hiddAttributes.ProductID,
hiddAttributes.VersionNumber);
CloseHandle(hidDeviceObject);
}
releaseMemory(deviceInterfaceDetailData);
};//koniec while
SetupDiDestroyDeviceInfoList(deviceInfoSet);
FreeLibrary(hHidLib);
cout << endl;
system("PAUSE");
return 0;
}
//---------------------------------------------------------
cje HidD_GetXxxx(). W tabeli 6.1 zamieszczono wykaz funkcji, którymi aplikacje użyt-
kownika mogą się bezpiecznie posługiwać w trakcie transferu danych z urządzeń przy-
łączonych do magistrali USB.
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 ReadFile(IN HANDLE hCommDev,
OUT LPVOID lpBuffer,
IN DWORD nNumberOfBytesToRead,
OUT LPDWORD lpNumberOfBytesRead,
IN OUT LPOVERLAPPED lpOverlapped);
Listing 6.2 prezentuje kompletny kod modułu programu odbierającego z łącza USB
raport wysył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.
Za pomocą funkcji strstr() z moduł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++
#define bufferLength 32
HDEVINFO deviceInfoSet;
SP_DEVICE_INTERFACE_DATA deviceInterfaceData;
PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData = NULL;
int main()
{
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych 201
hHidLib = LoadLibrary("C:\\Windows\\System32\\HID.DLL");
if (!hHidLib)
displayError("Błąd dołączenia biblioteki HID.DLL.");
if (!HidD_GetHidGuid){
FreeLibrary(hHidLib);
displayError("Nie znaleziono żadnych funkcji eksportowych.\n");
}
HidD_GetHidGuid(&classGuid);
deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
SetupDiDestroyDeviceInfoList(deviceInfoSet);
Rysunek 6.3.
Aplikacja
proj_USB_R6_2
w trakcie cyklicznego
odczytywania
raportów
pochodzących
z uniwersalnego
kontrolera gier
Rysunek 6.5. Format raportu wejściowego dla typowego uniwersalnego kontrolera gier
//---------------------------------------------------------
HIDP_CAPS capabilities;
PHIDP_LINK_COLLECTION_NODE linkCollectionNodes;
GUID classGuid;
HMODULE hHidLib;
DWORD memberIndex = 0;
DWORD deviceInterfaceDetailDataSize;
HDEVINFO deviceInfoSet;
SP_DEVICE_INTERFACE_DATA deviceInterfaceData;
PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData = NULL;
int main()
{
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&) HidP_GetLinkCollectionNodes=GetProcAddress(hHidLib,
"HidP_GetLinkCollectionNodes");
HidD_GetHidGuid(&classGuid);
if (deviceInfoSet == INVALID_HANDLE_VALUE){
FreeLibrary(hHidLib);
displayError("Nie zidentyfikowano podłączonych urządzeń.\n");
}
deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
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);
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
DWORD memberIndex = 0;
DWORD deviceInterfaceDetailDataSize;
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_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);
hHidLib = LoadLibrary("C:\\Windows\\System32\\HID.DLL");
if (!hHidLib)
displayError("Błąd dołączenia biblioteki HID.DLL.");
HidD_GetHidGuid(&classGuid);
deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
}; //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];
for(USHORT i = 0; i<capabilities.NumberInputButtonCaps;i++) {
printf("ButtonCaps[%d].UsagePage %x\n", i, buttonCaps[i].UsagePage);
printf("ButtonCaps[%d].ReportID= x%x\n", i, buttonCaps[i].ReportID);
printf("ButtonCaps[%d].IsAlias= x%x\n", i, buttonCaps[i].IsAlias);
printf("ButtonCaps[%d].BitField= %d\n", i, buttonCaps[i].BitField);
printf("ButtonCaps[%d].LinkCollection= x%x\n", i,
buttonCaps[i].LinkCollection);
printf("ButtonCaps[%d].LinkUsage= x%x\n", i, buttonCaps[i].LinkUsage);
printf("ButtonCaps[%d].LinkUsagePage= 0x%x\n", i,
buttonCaps[i].LinkUsagePage);
printf("ButtonCaps[%d].IsRange= %d\n\n", i, buttonCaps[i].IsRange);
if(buttonCaps[i].IsRange) {
printf("Usages min, max %d..%d\n\n", buttonCaps[i].Range.UsageMin,
buttonCaps[i].Range.UsageMax);
printf("Data index min, max %d..%d\n\n", buttonCaps[i].Range.DataIndexMin,
buttonCaps[i].Range.DataIndexMax);
switch (buttonCaps[i].UsagePage) {
case HID_USAGE_PAGE_BUTTON:
printf("\Usage Page -> Button Page\n");
break;
/*...*/
default :
printf("...");
}
switch (buttonCaps[i].LinkUsage) {
case HID_USAGE_GENERIC_JOYSTICK:
printf("\Link Usage -> Generic Joystick Page\n");
break;
/*...*/
default :
printf("...");
}
switch (buttonCaps[i].LinkUsagePage) {
case HID_USAGE_PAGE_GENERIC:
printf("\Link Usage Page -> Generic Desktop Page\n");
break;
/*...*/
default :
printf("...");
}
}
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
HIDP_CAPS capabilities;
GUID classGuid;
HMODULE hHidLib;
DWORD memberIndex = 0;
DWORD deviceInterfaceDetailDataSize;
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;
//---------------------------------------------------------
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");
if (!hHidLib)
displayError("Błąd dołączenia biblioteki HID.DLL.");
(FARPROC&) HidP_GetValueCaps=GetProcAddress(hHidLib,
"HidP_GetValueCaps");
HidD_GetHidGuid(&classGuid);
deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
if(HidD_GetPreparsedData(hidDeviceObject, &preparsedData)){
HidP_GetCaps(preparsedData, &capabilities);
PULONG valueCapsLength;
HIDP_VALUE_CAPS *valueCaps = new \
HIDP_VALUE_CAPS[capabilities.NumberOutputValueCaps];
for(USHORT i = 0; i<capabilities.NumberOutputValueCaps;i++) {
printf("ValueCaps[%d].UsagePage= x%x\n", i, valueCaps[i].UsagePage);
printf("ValueCaps[%d].ReportID= x%x\n", i, valueCaps[i].ReportID);
printf("ValueCaps[%d].IsAlias= x%x\n", i, valueCaps[i].IsAlias);
printf("ValueCaps[%d].BitField= %d\n", i, valueCaps[i].BitField);
printf("ValueCaps[%d].LinkCollection= x%x\n", i,
valueCaps[i].LinkCollection);
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych 217
switch (valueCaps[i].UsagePage) {
case HID_USAGE_PAGE_LED:
printf("\Usage Page -> LEDs Page\n");
break;
/*...*/
default :
printf("...");
}
switch (valueCaps[i].LinkUsage) {
case HID_USAGE_GENERIC_JOYSTICK:
printf("\Link Usage -> Generic Joystick Page\n");
break;
/*...*/
default :
printf("...");
}
switch (valueCaps[i].LinkUsagePage) {
case HID_USAGE_PAGE_GENERIC:
printf("\Link Usage Page -> Generic Desktop Page\n\n");
218 USB. Praktyczne programowanie z Windows API w C++
break;
/*...*/
default :
printf("...");
}
}
releaseMemory(valueCaps);
HidD_FreePreparsedData(preparsedData);
CloseHandle(hidDeviceObject);
}
FreeLibrary(hHidLib);
cout << endl;
system("PAUSE");
return 0;
}
//---------------------------------------------------------
Rysunek 6.8.
Odczyt wybranych
własności elementu
sterującego
o charakterze
ciągłym
Listing 6.7. Definicja klasy TForm1 aplikacji proj_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
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++
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);
deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
if(hidDeviceObject==INVALID_HANDLE_VALUE)
displayError("Nie można otworzyć urządzenia do transmisji.");
else
break;
}
releaseMemory(deviceInterfaceDetailData);
};//koniec while
SetupDiDestroyDeviceInfoList(deviceInfoSet);
showCapabilitiesDevice(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);
HidD_GetPreparsedData(hidDeviceObject, &preparsedData);
HidP_GetCaps(preparsedData, &capabilities);
224 USB. Praktyczne programowanie z Windows API w C++
Memo1->Lines->Add(S1);
HidD_FreePreparsedData(preparsedData);
}
//---------------------------------------------------------
Funkcja WriteFile()
Zasadniczą częścią kodu wysyłającego dane do urządzenia USB będzie zdefiniowana
w Windows SDK API funkcja:
BOOL WriteFile(IN HANDLE hCommDev,
IN LPCVOID lpBuffer,
IN DWORD nNumberOfBytesToWrite,
OUT LPDWORD lpNumberOfBytesWritten,
IN OUT LPOVERLAPPED lpOverlapped);
Ogólnie rzecz biorąc, może ona zapisywać dane do dowolnego urządzenia (pliku) jed-
noznacznie wskazanego przez identyfikator hCommDev. W przypadku transmisji danych
może być z powodzeniem stosowana zarówno do jej wariantu przerwaniowego, jak
i kontrolnego (patrz tabela 6.2). Funkcja ta zapisuje dane do obszaru pamięci (bufora
danych) identyfikowanego przez wskaźnik lpBuffer. Deklaracja LPCVOID lpBuffer
odpowiada klasycznej deklaracji wskaźnika ogólnego (adresowego) stałej, czyli const
void *Buffer. Rozmiar bufora ustala się w zależności od potrzeb, zasobów pamięci
komputera i pojemności bufora danych urządzenia zewnętrznego. Następny parametr
— nNumberOfBytesToWrite — określa liczbę bajtów do wysłania, a wskaźnik lpNum-
berOfBytesWritten wskazuje liczbę rzeczywiście wysłanych bajtów. Pole danych
transmitowanego pakietu może posiadać rozmiar mniejszy, niż jest to zapisane w polu
wMaxPacketSize deskryptora punktu końcowego. Wówczas taki pakiet jest 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 jest wskaźnikiem struktury OVERLAPPED. Zawiera ona informacje
226 USB. Praktyczne programowanie z Windows API w C++
Funkcje HidD_SetOutputReport()
oraz HidD_SetFeature()
W przypadku gdy testowane urządzenie mogłoby się posługiwać kontrolnym transfe-
rem danych dla raportów wyjściowych (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
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++
Jako jeden z argumentów funkcji CreateFile() powinien zostać użyty znacznik FILE_
FLAG_OVERLAPPED:
hidDeviceObject=CreateFile(deviceInterfaceDetailData->DevicePath,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED,
NULL);
Funkcje xxxEx
Funkcje WriteFile() i ReadFile() mogą być wykorzystywane zarówno w trakcie trans-
misji asynchronicznej, jak i synchronicznej. Transmisja asynchroniczna może być trak-
towana jako szczególna metoda przesyłania danych. Z tego względu Windows 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 ReadFileEx()
BOOL ReadFileEx(IN HANDLE hFile,
OUT LPVOID lpBuffer,
IN DWORD nNumberOfBytesToRead,
IN OUT LPOVERLAPPED lpOverlapped,
IN LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
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 WriteFile() i ReadFile(), funkcje WriteFileEx() 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 CALLBACK FileIOCompletionRoutine(IN DWORD dwErrorCode,
IN DWORD dwNumberOfBytesTransferred,
IN LPOVERLAPPED lpOverlapped);
Funkcja WaitForSingleObjectEx()
DWORD WaitForSingleObjectEx(IN HANDLE hHandle,
IN DWORD dwMilliseconds,
IN BOOL bAlertable);
Funkcja SleepEx()
DWORD SleepEx(IN DWORD dwMilliseconds,
IN BOOL bAlertable);
Listing 6.12. Fragment kodu programu asynchronicznie pobierającego z potoku raport wejściowy
//...
BYTE inputReportBuffer[7]; //bufor danych wejściowych
DWORD status = ERROR_SUCCESS;
//---------------------------------------------------------
void CALLBACK FileIOCompletionRoutine(const DWORD errorCode,
const DWORD numberOfBytesTransferred,
OVERLAPPED* overlapped)
{
status = errorCode;
if(ERROR_SUCCESS == status) {
assert(sizeof(inputReportBuffer) == numberOfBytesTransferred);
status = GetLastError();
}
}
//---------------------------------------------------------
DWORD readUSBReportEx(HANDLE, void*)
{
OVERLAPPED *overlapped = NULL;
if(overlapped == NULL){
overlapped = new OVERLAPPED;
overlapped->Offset = 0;
overlapped->OffsetHigh = 0;
}
if(!ReadFileEx(hidDeviceObject, inputReportBuffer,
sizeof(inputReportBuffer), overlapped,
FileIOCompletionRoutine)) {
status = GetLastError();
}
else {
while(ERROR_SUCCESS == status) {
SleepEx(INFINITE,TRUE);
printf("%d %d %d %d %d %d %d\n", inputReportBuffer[0],
inputReportBuffer[1], inputReportBuffer[2],
inputReportBuffer[3], inputReportBuffer[4],
inputReportBuffer[5], inputReportBuffer[6]);
if(inputReportBuffer[6]==64)
break;
}
}
releaseMemory(overlapped);
return status;
}
//---------------------------------------------------------
int main()
{
//...
while(SetupDiEnumDeviceInterfaces(deviceInfoSet, NULL, &classGuid,
memberIndex, &deviceInterfaceData)){
//...
};//koniec while
SetupDiDestroyDeviceInfoList(deviceInfoSet);
readUSBReportEx(hidDeviceObject, inputReportBuffer);
CloseHandle(hidDeviceObject);
FreeLibrary(hHidLib);
cout << endl;
system("PAUSE");
return 0;
}
//---------------------------------------------------------
234 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 of transmission). W trakcie transmisji asynchronicznej COMMTIMEOUTS determinuje
zachowanie takich funkcji jak ReadFile(), WriteFile(), ReadFileEx() i WriteFileEx().
return false;
}
return true;
}
//---------------------------------------------------------
int main()
{
//...
if(HidD_GetPreparsedData(hidDeviceObject, &preparsedData)){
//...
//Upewnij się, że ewentualne czasy przeterminowania zostały zmodyfikowane
//zgodnie z obsługiwanymi przez urządzenie wartościami.
setCommTimeouts(MAXDWORD, 0, 0, 0, 0);
readUSBReport(hidDeviceObject, inputReportBuffer,
capabilities.InputReportByteLength);
//...
}
//---------------------------------------------------------
Powyższe zapisy oznaczają, że dane powinny być pobierane z bufora wejściowego na-
tychmiast po tym, jak 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ę CreateFile() uzyskuje się identyfikator
pliku reprezentującego wybrane urządzenie (sterownik). Następnie można wykorzystać
funkcje WriteFile(), 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 DeviceIoControl(IN HANDLE hDevice,
IN DWORD dwIoControlCode,
IN LPVOID lpInBuffer,
IN DWORD nInBufferSize,
OUT LPVOID lpOutBuffer,
IN DWORD nOutBufferSize,
OUT LPDWORD lpBytesReturned,
IN OUT LPOVERLAPPED lpOverlapped);
#define HID_CTL_CODE(id) \
CTL_CODE (FILE_DEVICE_KEYBOARD, (id), METHOD_NEITHER, FILE_ANY_ACCESS)
#define HID_BUFFER_CTL_CODE(id) \
CTL_CODE (FILE_DEVICE_KEYBOARD, (id), METHOD_BUFFERED, FILE_ANY_ACCESS)
#define HID_IN_CTL_CODE(id) \
CTL_CODE (FILE_DEVICE_KEYBOARD, (id), METHOD_IN_DIRECT, FILE_ANY_ACCESS)
#define HID_OUT_CTL_CODE(id) \
CTL_CODE (FILE_DEVICE_KEYBOARD, (id), METHOD_OUT_DIRECT, FILE_ANY_ACCESS)
HDEVINFO deviceInfoSet;
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych 239
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;
HIDP_CAPS capabilities;
//---------------------------------------------------------
int main()
{
long (__stdcall* HidP_GetCaps)(IN PHIDP_PREPARSED_DATA PreparsedData,
OUT PHIDP_CAPS Capabilities);
hHidLib = LoadLibrary("C:\\Windows\\System32\\HID.DLL");
if (!hHidLib)
displayError("Błąd dołączenia biblioteki HID.DLL.");
if (!HidD_GetHidGuid || !HidP_GetCaps){
FreeLibrary(hHidLib);
displayError("Nie znaleziono żadnych funkcji eksportowych.\n");
}
HidD_GetHidGuid(&classGuid);
deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
hiddAttributes.Size = sizeof(HIDD_ATTRIBUTES);
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
hiddAttributes.ProductID = collectionInformation.ProductID;
hiddAttributes.VersionNumber = collectionInformation.VersionNumber;
printf("VID/PID/wersja = %x/%x/%x\n",hiddAttributes.VendorID,
hiddAttributes.ProductID,
hiddAttributes.VersionNumber);
/*if(!DeviceIoControl(hidDeviceObject, IOCTL_HID_GET_COLLECTION_INFORMATION,
NULL,0,&collectionInformation,
sizeof(HID_COLLECTION_INFORMATION),
&lpBytesReturned, NULL))
break;
*/
preparsedData = (PHIDP_PREPARSED_DATA)\
new DWORD[collectionInformation.DescriptorSize];
DeviceIoControl(hidDeviceObject, IOCTL_HID_GET_COLLECTION_DESCRIPTOR,
NULL,0,preparsedData,collectionInformation.DescriptorSize,
&lpBytesReturned, NULL);
HidP_GetCaps(preparsedData, &capabilities);
printf("Usage=%x\nUsagePage=%x\nInputReportByteLength=%d\n"
"OutputReportByteLength=%d\nFeatureReportByteLength=%d\n"
"NumberLinkCollectionNodes=%d\n"
"NumberInputButtonCaps=%d\nNumberInputValueCaps=%d\n\n",
capabilities.Usage, capabilities.UsagePage,
capabilities.InputReportByteLength,
capabilities.OutputReportByteLength,
capabilities.FeatureReportByteLength,
capabilities.NumberLinkCollectionNodes,
capabilities.NumberInputButtonCaps,
capabilities.NumberInputValueCaps);
releaseMemory(preparsedData);
CloseHandle(hidDeviceObject);
}
releaseMemory(deviceInterfaceDetailData);
};//koniec while
SetupDiDestroyDeviceInfoList(deviceInfoSet);
FreeLibrary(hHidLib);
cout << endl;
system("PAUSE");
return 0;
}
//---------------------------------------------------------
Rysunek 6.12.
Wynik działania
aplikacji
proj_USB_R6_6
Rysunek 6.13.
Standardowe rozkazy
IOCTL_Xxx dostępne
z poziomu modułu
hidclass.h
};
//---------------------------------------------------------
template <class T>
inline void releaseMemory(T &x)
{
assert(x != NULL);
delete [] x;
x = NULL;
}
//---------------------------------------------------------
GUID classGuid;
HMODULE hHidLib;
DWORD memberIndex = 0;
DWORD deviceInterfaceDetailDataSize;
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()
{
PHID_COLLECTION_INFORMATION descriptor;
descriptor = new \
HID_COLLECTION_INFORMATION[(sizeof(HID_COLLECTION_INFORMATION))];
descriptor->DescriptorSize = sizeof(HID_COLLECTION_INFORMATION);
DWORD lpBytesReturned=0;
bool result;
result = DeviceIoControl(hidDeviceObject,
IOCTL_HID_GET_COLLECTION_INFORMATION,
descriptor,descriptor->DescriptorSize, descriptor,
descriptor->DescriptorSize, &lpBytesReturned, NULL);
if(!result)
releaseMemory(descriptor);
return result ? descriptor : NULL;
}
//---------------------------------------------------------
int main()
{
void (__stdcall *HidD_GetHidGuid)(OUT LPGUID HidGuid);
hHidLib = LoadLibrary("C:\\Windows\\System32\\HID.DLL");
if (!hHidLib)
displayError("Błąd dołączenia biblioteki HID.DLL.");
if (!HidD_GetHidGuid){
FreeLibrary(hHidLib);
displayError("Nie znaleziono żadnych \
funkcji eksportowych.\n");
}
HidD_GetHidGuid(&classGuid);
244 USB. Praktyczne programowanie z Windows API w C++
deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
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(deviceInterfaceDetailData);
};//koniec while
SetupDiDestroyDeviceInfoList(deviceInfoSet);
FreeLibrary(hHidLib);
cout << endl;
system("PAUSE");
return 0;
}
//---------------------------------------------------------
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych 245
Rysunek 6.14.
Aplikacja
proj_USB_R6_7
w trakcie działania
//---------------------------------------------------------
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++
if (HCDHandle != INVALID_HANDLE_VALUE) {
getRootHubName(HCDHandle);
getHCDDriverKeyName(HCDHandle);
CloseHandle(HCDHandle);
}
i++;
} while(HCDHandle != INVALID_HANDLE_VALUE);
system("PAUSE");
return 0;
}
//---------------------------------------------------------
Rysunek 6.15.
Aplikacja
proj_USB_R6_8
w trakcie działania
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych 247
Identyfikacja urządzeń
przyłączonych do koncentratora USB
Na 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średnictwem 4-portowego koncentratora USB) trzech urządzeń: kamery cyfro-
wej, miernika uniwersalnego (podłączonego za pomocą adaptera USB/RS 232C) oraz
standardowej klawiatury.
Rysunek 6.16.
Fizyczna konfiguracja
systemu, w ramach
którego funkcjonuje
program z listingu
6.17
{
USB_NODE_CONNECTION_NAME connectionName;
PUSB_NODE_CONNECTION_NAME pConnectionName;
ULONG outBufferSize = 0;
ULONG bytesReturned;
bool success;
memset(&connectionName, 0, sizeof(connectionName));
connectionName.ConnectionIndex = connectionIndex;
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.\
HubDescriptor.bNumberOfPorts;
return nDSPorts;
}
//---------------------------------------------------------
void getDSPortData(HANDLE devHandle, UCHAR connectionIndex)
{
ULONG bytesReturned;
bool success;
PUSB_NODE_CONNECTION_INFORMATION pConnectionInformation = NULL;
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(" MaxPacketSize0: %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];
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.wValue =
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 251
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,
descriptorIndex);
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;
}
//---------------------------------------------------------
252 USB. Praktyczne programowanie z Windows API w C++
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, &deviceInterfaceData,
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 transmisji.");
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);
}
printf("\nLiczba portów huba = %d\n",nDSPorts);
for (int i = 1; i<= nDSPorts; i++) {
/*
PUSB_STRING_DESCRIPTOR getStrDesc = getStringDescriptor1(deviceObject, i, 2);
if (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);
if (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 <<endl;
CloseHandle(deviceObject);
system("PAUSE");
return 0;
}
//---------------------------------------------------------
określają, czy jest to koncentrator (UsbHub = 0), czy też urządzenie złożone z wielo-
ma interfejsami (UsbMIParent = 1). Zasoby struktury USB_HUB_INFORMATION zostały
254 USB. Praktyczne programowanie z Windows API w C++
Rysunek 6.17. Logiczna struktura typów danych wykorzystywanych przez rozkaz IOCTL_USB_GET_
NODE_INFORMATION
nie jest hubem (hubem na poziomie 0+n), zerowy indeks tablicy NodeName[0] zawiera
wartość NULL. Zaprezentowana na listingu 6.17 funkcja getDSPortConnectionIndex()
odczytuje stan aktualnie dostępnych portów huba.
— gdzie dla standardowych urządzeń USB pola bmRequest (oznaczanego jako bmRequ-
estType 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 wValue 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),
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 USB_REQUEST_ Selektor właściwości. 0 0 Nie zawiera
(0x00) CLEAR_FEATURE Może przyjąć jedną Numer danych
(0x01) z następujących wartości: interfejsu
00000001b
(0x01) ENDPOINT_HALT — 0 Numer punktu
adresatem jest punkt końcowego
00000010b
końcowy
(0x02)
FUNCTION_SUSPEND — 0
adresatem jest interfejs
U1_ENABLE — 48
U2_ENABLE — 49
LTM_ENABLE — 50
adresatem jest urządzenie
10000000b USB_REQUEST_ 0 0 1 Numer
(0x80) GET_CONFIGURATION konfiguracji
(0x08)
10000000b USB_REQUEST_ Typ deskryptora (bardziej 0 lub ID Długość Zawartość
(0x80) GET_DESCRIPTOR znaczący bajt) oraz języka opisu deskryptora deskryptora
(0x06) indeks deskryptora (mniej deskryptora
znaczący bajt) łańcuchowego
(patrz listing
6.17)
10000001b USB_REQUEST_ 0 Numer 1 Numer
(0x81) GET_INTERFACE interfejsu alternatyw-
(0x0A) nego
ustawienia
interfejsu
00000000b USB_REQUEST_ 0 0 2 Interfejs
(0x00) GET_STATUS (0x00) Numer urządzenia
interfejsu lub status
00000001b
Numer punktu punktu
(0x01) końcowego
końcowego
00000010b
(0x02)
00000000b USB_REQUEST_ Adres urządzenia 0 0 Nie zawiera
(0x00) SET_ADDRESS danych
(0x05)
00000000b USB_REQUEST_ Numer konfiguracji 0 0 Nie zawiera
(0x00) SET_CONFIGURATION danych
(0x09)
00000000b USB_REQUEST_ Typ deskryptora (bardziej 0 lub ID Długość Zawartość
(0x00) SET_DESCRIPTOR znaczący bajt) oraz języka opisu deskryptora deskryptora
(0x07) indeks deskryptora (mniej deskryptora
znaczący bajt) łańcuchowego
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 USB_REQUEST_ Selektor właściwości 0 0 Nie zawiera
(0x00) SET_FEATURE Numer danych
(0x03) interfejsu
00000001b
(0x01) Numer punktu
końcowego
00000010b
(0x02)
00000001b USB_REQUEST_ Ustawienia alternatywne Numer 0 Nie zawiera
(0x02) SET_INTERFACE interfejsu danych
(0x0B)
10000010b USB_REQUEST_ 0 Numer punktu 2 Numer
(0x82) SYNC_FRAME (0x0C) końcowego ramki
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 GET_REPORT (0x01) Typ raportu (bardziej Numer Długość Zawartość
(0xA1) znaczący bajt) oraz ID interfejsu raportu raportu
raportu (mniej znaczący
bajt)
00100001b SET_REPORT (0x09) Typ raportu (bardziej Numer Długość Zawartość
(0x21) znaczący bajt) oraz ID interfejsu raportu raportu
raportu (mniej znaczący
bajt)
10100001b GET_IDLE (0x02) 0 (bardziej znaczący bajt) Numer 1 Czas
(0xA1) oraz ID raportu (mniej interfejsu trwania
znaczący bajt) stanu braku
aktywności
00100001b SET_IDLE (0x0A) Czas trwania stanu braku Numer 0 Nie zawiera
(0x21) aktywności (bardziej interfejsu danych
znaczący bajt) oraz ID
raportu (mniej znaczący
bajt)
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 GET_PROTOCOL 0 Numer 1 0—
(0xA1) (0x03) interfejsu protokół
bootujący
1—
protokół
raportu
00100001b SET_PROTOCOL 0 — protokół bootujący Numer 0 Nie zawiera
(0x21) (0x0B) 1 — protokół raportu interfejsu danych
— gdzie pole ConnectionIndex specyfikuje numer portu huba, do którego jest przy-
łączone urządzenie wykonawcze, a DeviceDescriptor jest 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ż jest w stanie transmitować dane
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych 259
Rysunek 6.18. Logiczna struktura typów danych wykorzystywanych przez rozkaz IOCTL_USB_GET_
NODE_CONNECTION_INFORMATION(_EX)
z większymi prędkościami (FALSE)3. Znacznik DeviceIsHub określa, czy przyłączone
do portu huba urządzenie jest 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. Wskaź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 harmo-
nogramowania odstępu czasowego ponawiania kolejnych przekazów izochronicznych
i przerwaniowych.
Na 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. Na rysunku 6.19 za-
prezentowano omawiany program w trakcie działania.
Rysunek 6.19.
Aplikacja projektu
proj_USB_R6_9
w trakcie działania
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;
}
//---------------------------------------------------------
Struktura URB
Zdefiniowana w zasobach WDK w module usb.h struktura URB (ang. USB Request
Block) definiuje formaty dla wszystkich możliwych rozkazów, które mogą być wysy-
łane do urządzenia USB za pośrednictwem sterownika kontrolera hosta. W tabeli 6.6
przedstawiono specyfikację tej struktury.
Definicja ta tworzy dwa nowe słowa kluczowe: URB (struktura) i PURB (wskaźnik do
struktury).
Funkcja UsbBuildGetDescriptorRequest()
Zdefiniowana w module usbdlib.h (WDK) funkcja UsbBuildGetDescriptorRequest()
formatuje elementy struktury URB z parametrami 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. Parametr DescriptorType określa jedną z następujących wartości: USB_DEVICE_
DESCRIPTOR_TYPE (deskryptory urządzenia, interfejsu, punktu końcowego), USB_CONFI-
GURATION_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 powinien wskazywać strukturę MDL (ang. Memory Descriptor
List). Parametr TransferBufferLength określa rozmiar buforów wskazywanych przez
TransferBuffer lub TransferBufferMDL. Wskaźnik Link powinien wskazywać wartość
pustą NULL.
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 HID), jak i innych rodzajów urządzeń (z wykorzystaniem rozkazów IOCTL).
Omawiane kody zostały przedstawione w formach 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
Na listingu 6.18 zaprezentowano nieskomplikowany przykład kodu programu wyko-
rzystującego funkcję WriteFile() w celu wysłania do standardowej drukarki USB (ste-
rownik usbprint.sys) polecenia wydrukowania łańcucha znaków.
#define MAX_BUFFER_SIZE 64
cout <<endl;
CloseHandle(deviceObject);
system("PAUSE");
return 0;
}
//---------------------------------------------------------
Ć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 powinni standardowo wraz ze sprzętem dostarczać odpo-
wiednie sterowniki, które pozwolą użytkownikowi na uzyskanie swobodnego dostępu
do urządzenia wykonawczego. Tego typu sterowniki były tworzone standardowo w ar-
chitekturze WDM (ang. Windows Driver Model). WDM 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 jest wykorzystywany przez funkcje API, takie jak ReadFile(), WriteFile()
czy DeviceIoControl(). Tego rodzaju podejście bywa stosowane wówczas, gdy opro-
gramowywane są złożone, wielofunkcyjne urządzenia, mogące występować w syste-
mie w postaci wielu obiektów, z którymi aplikacje mogą się komunikować zarówno
w trybie synchronicznym, jak i asynchronicznym.
Biblioteka WinUSB
Biblioteka Windows USB (WinUSB) oferuje ogólny sterownik dla urządzeń, które
nie wymagają standardowego sterownika klienta. Architektura WinUSB składa się
ze sterownika działającego w trybie jądra winusb.sys, który jest uzupełnieniem ist-
niejących sterowników tworzonych (począwszy od Windows XP) w technologii WDF
(ang. Windows Driver Foundation), oraz biblioteki winusb.dll łączonej z programem
w trybie użytkownika. WDF obejmuje biblioteki sterowników wykorzystywanych
w trybie jądra KMDF (ang. Kernel-Mode Driver Framework) oraz w trybie użytkow-
nika UMDF (ang. User-Mode Driver Framework). Dodatkowe biblioteki współin-
stalatorów WinUSB oraz KMDF (tzw. co-installers) są włączone do pakietu instala-
cyjnego WDK znajdującego się w katalogu \Redist\Winusb. Sterownik winusb.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
KMDF oraz współinstalatorów.
1
Microsoft Visual Studio generuje GUID za 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.
Rozdział 7. ♦ Biblioteki WinUSB oraz LibUSB 273
CatalogFile=myusbdevice.cat
; ========== Manufacturer/Models sections ===========
[Manufacturer]
%ProviderName% = MyDevice_WinUSB, NTamd64
[MyDevice_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.CoInstallers]
AddReg=CoInstallers_AddReg
CopyFiles=CoInstallers_CopyFiles
[CoInstallers_AddReg]
HKR,,CoInstallers32,0x00010000,"WinUSBCoInstaller2.dll","WUDFUpdate_01009.dll",
"WdfCoInstaller01009.dll, WdfCoInstaller"
[CoInstallers_CopyFiles]
WinUSBCoInstaller2.dll
WdfCoInstaller01009.dll
WUDFUpdate_01009.dll
[DestinationDirs]
CoInstallers_CopyFiles=11
; ========== Source Media Section ===================
[SourceDisksNames.amd64]
1 = %DISK_NAME%,,,\amd64
[SourceDisksFiles.amd64]
WinUSBCoInstaller2.dll=1
WdfCoInstaller01009.dll=1
WUDFUpdate_01009.dll=1
274 USB. Praktyczne programowanie z Windows API w C++
W celu dostosowania zawartości pliku .inf 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 .inf dla urządzeń winusb są dostępne na stro-
nie MSDN: http://msdn.microsoft.com/en-us/library/windows/hardware/ff540283(v=
vs.85).aspx pod hasłem WinUSB (Winusb.sys) Installation.
Na rysunku 7.1 pokazano przykład utworzenia pliku .cat. Trzeba zwrócić uwagę, że
nazwa tego pliku powinna być wcześniej poprawnie określona w sekcji [Version] pli-
ku .inf (patrz listing 7.1).
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
Rysunek 7.5.
Prawidłowo
zainstalowane
w systemie
urządzenie winusb
Rozdział 7. ♦ Biblioteki WinUSB oraz LibUSB 277
Rysunek 7.6.
Stos sterowników
urządzenia winusb
Rysunek 7.7.
Model warstwowy
sterowników
urządzenia winusb
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 Protocol) 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
);
if (! WinUsb_Initialize || ! WinUsb_Free){
FreeLibrary(hWinUSBLib);
displayError("Nie znaleziono żadnych funkcji eksportowych.\n");
}
//-----------------------------------------------------------
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, &deviceInterfaceData)){
//Procedury enumeracji dołączonych urządzeń — patrz diagram z rysunku 5.1
//...
}
//-----------------------------------------------------------
//Wybranie właściwej ścieżki dostępu DevicePath do interfejsu urządzenia
//i pobranie identyfikatora deviceHandle pliku sterownika urządzenia
//wykonawczego
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) {
//Wywołanie funkcji WinUsb_Initialize() z aktualnymi parametrami
bResult = WinUsb_Initialize(deviceHandle, &interfaceHandle);
if(!bResult)
printf("Błąd inicjalizacji WinUsb_Initialize()%d.\n", GetLastError());
}
//...
280 USB. Praktyczne programowanie z Windows API w C++
system("PAUSE");
CloseHandle(deviceHandle);
WinUsb_Free(interfaceHandle);
return 0;
}
//--------------------------------------------------------------
Funkcja WinUsb_Free()
BOOL __stdcall WinUsb_Free(
IN WINUSB_INTERFACE_HANDLE InterfaceHandle
);
Funkcja WinUsb_AbortPipe()
BOOL __stdcall WinUsb_AbortPipe(
IN WINUSB_INTERFACE_HANDLE InterfaceHandle,
IN UCHAR PipeID
);
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
);
Rysunek 7.8. Format pola Index struktury WINUSB_SETUP_PACKET, w przypadku gdy adresatem
rozkazu jest punkt końcowy w ramach interfejsu
Rysunek 7.9. Format pola Index struktury WINUSB_SETUP_PACKET, w przypadku gdy adresatem
rozkazu jest interfejs w ramach danej konfiguracji
Funkcja WinUsb_FlushPipe()
BOOL __stdcall WinUsb_FlushPipe(
IN WINUSB_INTERFACE_HANDLE InterfaceHandle,
IN UCHAR PipeID
);
Rozdział 7. ♦ Biblioteki WinUSB oraz LibUSB 283
Funkcja WinUsb_GetAssociatedInterface()
BOOL __stdcall WinUsb_GetAssociatedInterface(
IN WINUSB_INTERFACE_HANDLE InterfaceHandle,
IN UCHAR AssociatedInterfaceIndex,
OUT PWINUSB_INTERFACE_HANDLE AssociatedInterfaceHandle
);
Funkcja WinUsb_GetCurrentAlternateSetting()
BOOL __stdcall WinUsb_GetCurrentAlternateSetting(
IN WINUSB_INTERFACE_HANDLE InterfaceHandle,
OUT PUCHAR AlternateSetting
);
Funkcja WinUsb_GetDescriptor()
BOOL __stdcall WinUsb_GetDescriptor(
IN WINUSB_INTERFACE_HANDLE InterfaceHandle,
IN UCHAR DescriptorType,
IN UCHAR Index,
IN USHORT LanguageID,
OUT PUCHAR Buffer,
IN ULONG BufferLength,
OUT PULONG LengthTransferred
);
Funkcja WinUsb_GetOverlappedResult()
BOOL __stdcall WinUsb_GetOverlappedResult(
IN WINUSB_INTERFACE_HANDLE InterfaceHandle,
IN LPOVERLAPPED lpOverlapped,
OUT LPDWORD lpNumberOfBytesTransferred,
IN BOOL bWait
);
Funkcja WinUsb_GetPipePolicy()
BOOL __stdcall WinUsb_GetPipePolicy(
IN WINUSB_INTERFACE_HANDLE InterfaceHandle,
IN UCHAR PipeID,
IN ULONG PolicyType,
INOUT PULONG ValueLength,
OUT PVOID Value
);
Funkcja WinUsb_GetPowerPolicy()
BOOL __stdcall WinUsb_GetPowerPolicy(
IN WINUSB_INTERFACE_HANDLE InterfaceHandle,
IN ULONG PolicyType,
INOUT PULONG ValueLength,
OUT PVOID Value
);
Funkcja WinUsb_QueryDeviceInformation()
BOOL __stdcall WinUsb_QueryDeviceInformation(
IN WINUSB_INTERFACE_HANDLE InterfaceHandle,
IN ULONG InformationType,
INOUT PULONG BufferLength,
OUT PVOID Buffer
);
Funkcja WinUsb_QueryInterfaceSettings()
BOOL __stdcall WinUsb_QueryInterfaceSettings(
IN WINUSB_INTERFACE_HANDLE InterfaceHandle,
IN UCHAR AlternateSettingNumber,
OUT PUSB_INTERFACE_DESCRIPTOR UsbAltInterfaceDescriptor
);
Funkcja WinUsb_QueryPipe()
BOOL __stdcall WinUsb_QueryPipe(
IN WINUSB_INTERFACE_HANDLE InterfaceHandle,
IN UCHAR AlternateInterfaceNumber,
IN UCHAR PipeIndex,
OUT PWINUSB_PIPE_INFORMATION PipeInformation
);
//EP IN
}
if (USB_ENDPOINT_DIRECTION_OUT(pipeInformation->PipeId)) == 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
);
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 WinUsb_SetCurrentAlternateSetting()
BOOL __stdcall WinUsb_SetCurrentAlternateSetting(
IN WINUSB_INTERFACE_HANDLE InterfaceHandle,
IN UCHAR AlternateSetting
);
Funkcja WinUsb_SetPipePolicy()
BOOL __stdcall WinUsb_SetPipePolicy(
IN WINUSB_INTERFACE_HANDLE InterfaceHandle,
IN UCHAR PipeID,
IN ULONG PolicyType,
IN ULONG ValueLength,
IN PVOID Value
);
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 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
);
Biblioteka LibUSB
Zasoby biblioteki LibUSB są dostępne na zasadach opisanych regułami 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 pomocą której można utworzyć
plik .inf, 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
Rysunek 7.11.
Dane konfiguracyjne
urządzenia
Rysunek 7.12.
Potwierdzenie
utworzenia pakietu
instalacyjnego
urządzenia libusb
Rysunek 7.13.
Umiejscowienie
urządzenia libusb
w menedżerze
urządzeń
Rysunek 7.14.
Model warstwowy
sterowników
urządzenia libusb
Definicje wszystkich struktur oraz funkcji biblioteki znajdują się w pliku nagłówko-
wym lusb0_usb.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.
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++
Rysunek 7.15.
Odczyt zawartości
deskryptorów
urządzenia libusb
Funkcja usb_find_busses()
int usb_find_busses(void);
Zwraca liczbę portów magistrali USB dostępnych w systemie i powinna być wywo-
ływana każdorazowo po funkcji usb_init().
Rozdział 7. ♦ Biblioteki WinUSB oraz LibUSB 293
Funkcja usb_find_devices()
int usb_find_devices(void);
Funkcja usb_get_busses()
struct usb_bus *usb_get_busses(void);
Funkcja usb_set_debug()
void usb_set_debug(int level);
Funkcja usb_close()
int usb_close(usb_dev_handle *dev);
Listing 7.3. Odczytanie fragmentu deskryptora urządzenia zainstalowanego jako urządzenie libusb
#include "lusb0_usb.h"
#include <iostream>
using namespace std;
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_init();
usb_find_busses();
usb_find_devices();
Funkcja usb_set_configuration()
int usb_set_configuration(usb_dev_handle *dev, int configuration);
Funkcja usb_set_altinterface()
int usb_set_altinterface(usb_dev_handle *dev, int alternate);
Poprzez parametr alternate określa alternatywne ustawienia interfejsu dla danej kon-
figuracji urządzenia identyfikowanego przez wskaźnik dev. Alternatywne ustawienia
interfejsu są zapisane w polu bAlternateSetting struktury usb_interface_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);
Funkcja usb_reset()
int usb_reset(usb_dev_handle *dev);
Funkcja usb_claim_interface()
int usb_claim_interface(usb_dev_handle *dev, int interface);
Funkcja usb_release_interface()
int usb_release_interface(usb_dev_handle *dev, int interface);
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);
USB_REQ_GET_DESCRIPTOR 0x06
USB_REQ_SET_DESCRIPTOR 0x07
USB_REQ_GET_CONFIGURATION 0x08
USB_REQ_SET_CONFIGURATION 0x09
USB_REQ_GET_INTERFACE 0x0A
USB_REQ_SET_INTERFACE 0x0B
USB_REQ_SYNCH_FRAME 0x0C
W przypadku transferu typu OUT wskaźnik bytes wskazuje bufor z danymi wyjś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ść ujemną 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ń mogą 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);
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;//langID;
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);
Funkcja usb_get_descriptor()
int usb_get_descriptor(usb_dev_handle *dev, unsigned char type,
unsigned char index, void *buf, int size);
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++
Parametr size jest 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 identyfikowanym przez parametr 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ą.
Funkcja usb_interrupt_read()
int usb_interrupt_read(usb_dev_handle *dev, int ep, char *bytes, int size,
int timeout);
Funkcje asynchroniczne
Funkcja usb_isochronous_setup_async()
int usb_isochronous_setup_async(usb_dev_handle *dev, void **context,
unsigned char ep, int pktsize);
Funkcja usb_bulk_setup_async()
int usb_bulk_setup_async(usb_dev_handle *dev, void **context, unsigned char ep);
Funkcja usb_interrupt_setup_async()
int usb_interrupt_setup_async(usb_dev_handle *dev, void **context,
unsigned char ep);
Funkcja usb_submit_async()
int usb_submit_async(void *context, char *bytes, int size);
Funkcja usb_reap_async()
int usb_reap_async(void *context, int timeout);
Funkcja usb_reap_async_nocancel()
int usb_reap_async_nocancel(void *context, int timeout);
Funkcja usb_cancel_async()
int usb_cancel_async(void *context);
Funkcja usb_free_async()
int usb_free_async(void **context);
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 <iostream>
using namespace std;
usb_dev_handle *getDevice();
usb_dev_handle* libUsbDeviceSetup()
304 USB. Praktyczne programowanie z Windows API w C++
{
usb_dev_handle *devHandle = NULL;
usb_init();
usb_find_busses();
usb_find_devices();
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.get();
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 transmisji.\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_interface(dev, INTERFACE);
}
//----------------------------------------------------------
int main()
{
usb_dev_handle *devHandle;
if ((devHandle = libUsbDeviceSetup()) == NULL) {
exit(-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-
portowych 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 temat wykorzystania zasobów biblioteki WinUSB podczas tworzenia
oprogramowania dla przykładowego urządzenia wykonawczego można znaleźć w ob-
szernym artykule How to Access a USB Device 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 De-
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 — jak 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). Wielką 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 zdefiniowania sposobu ich wywoływania. Wyko-
rzystując tę regułę, na obiekty można spojrzeć poprzez jedną z trzech perspektyw za-
proponowanych przez Fowlera [8]:
na poziomie koncepcji obiekt jest zbiorem różnego rodzaju odpowiedzialności,
na poziomie specyfikacji obiekt jest zbiorem metod (zachowań), które mogą
być wywoływane przez jego metody lub metody innych obiektów,
na poziomie implementacji obiekt składa się z kodu i danych oraz interakcji
między nimi.
Na rysunku 8.1 pokazano przykład statycznego diagramu klas dającego ogólny pogląd
na logiczną konstrukcję kodu analizowanego z poziomu koncepcji. Projektując klasy
308 USB. Praktyczne programowanie z Windows API w C++
(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.
Na 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 pomocą 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. Na 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
#ifndef usb_R8_1H
#define usb_R8_1H
#define searchMaxDevice 10
//--------------------------------------------------------------
template <class T>
inline void releaseMemory(T &x)
{
assert(x != NULL);
delete [] x;
x = NULL;
}
//--------------------------------------------------------------
typedef struct _HIDD_ATTRIBUTES {
ULONG Size;
USHORT VendorID;
USHORT ProductID;
USHORT VersionNumber;
} HIDD_ATTRIBUTES, *PHIDD_ATTRIBUTES;
//--------------------------------------------------------------
void (__stdcall *HidD_GetHidGuid)(OUT LPGUID HidGuid);
bool (__stdcall *HidD_GetAttributes)(IN HANDLE HidDeviceObject,
OUT PHIDD_ATTRIBUTES Attributes);
//--------------------------------------------------------------
class TUSBDevice {
private:
string pathUSBDevice;
DWORD memberIndex;
HIDD_ATTRIBUTES hiddAttributes;
PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData;
DWORD numberOfBytesRead;
DWORD result;
GUID classGuid;
DWORD deviceInterfaceDetailDataSize;
HDEVINFO deviceInfoSet;
SP_DEVICE_INTERFACE_DATA deviceInterfaceData;
USHORT usVid;
const char *chVid;
310 USB. Praktyczne programowanie z Windows API w C++
public:
TUSBDevice();
~TUSBDevice();
void setUshortVid(USHORT vid);
void displayError(const char* msg);
bool synchReadUSBReport(HANDLE hidDevObject, void *inputReportBuffer,
ULONG inputReportBufferLength);
HANDLE synchOpenUSBDevice();
string getUSBDevicePath(int);
};
//--------------------------------------------------------------
typedef USHORT USAGE, *PUSAGE;
typedef struct _HIDP_PREPARSED_DATA *PHIDP_PREPARSED_DATA;
HMODULE hHidLib; //Identyfikator biblioteki HID jako zmienna globalna
//--------------------------------------------------------------
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;
//--------------------------------------------------------------
#endif
//--------------------------------------------------------------
Listing 8.2. Kod modułu usb_R8_1.cpp, implementującego funkcje zadeklarowane w klasie TUSBDevice
#include <windows>
#pragma option push -a1
#include <setupapi>
#pragma option pop
#include <iostream>
#include <assert>
#include <cstring>
#include "usb_R8_1.h"
if (!HidD_GetHidGuid || !HidD_GetAttributes){
FreeLibrary(hHidLib);
displayError("Nie znaleziono żadnych funkcji eksportowych.\n");
}
}
//--------------------------------------------------------------
TUSBDevice::~TUSBDevice()
{
if(hHidLib != NULL)
FreeLibrary(hHidLib);
}
//--------------------------------------------------------------
void TUSBDevice::displayError(const char* msg){
cout << msg << endl;
system("PAUSE");
exit(0);
};
//--------------------------------------------------------------
void TUSBDevice::setUshortVid(USHORT vid)
{
usVid = vid;
}
//--------------------------------------------------------------
string TUSBDevice::getUSBDevicePath(UINT)
{
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);
if (SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &deviceInterfaceData,
deviceInterfaceDetailData, deviceInterfaceDetailDataSize,
NULL, NULL)) {
pathUSBDevice = deviceInterfaceDetailData->DevicePath;
//cout << pathUSBDevice << endl;
}
releaseMemory(deviceInterfaceDetailData);
SetupDiDestroyDeviceInfoList(deviceInfoSet);
return pathUSBDevice;
}
312 USB. Praktyczne programowanie z Windows API w C++
//--------------------------------------------------------------
HANDLE TUSBDevice::synchOpenUSBDevice()
{
string devUSBpath;
HANDLE hidDevObject = INVALID_HANDLE_VALUE;
while((devUSBpath = getUSBDevicePath(memberIndex++)) != "" ){
hidDevObject = CreateFile(devUSBpath.c_str(), GENERIC_READ | \
GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL );
HidD_GetAttributes(hidDevObject, &hiddAttributes);
printf("VID/PID/wersja = %d/%d/%d\n",hiddAttributes.VendorID,
hiddAttributes.ProductID,
hiddAttributes.VersionNumber);
if(hiddAttributes.VendorID == usVid){
return hidDevObject;
break;
}
else
if(memberIndex > searchMaxDevice)
displayError("Nie znaleziono urządzenia o podanym VID.");
}
return INVALID_HANDLE_VALUE;
}
//--------------------------------------------------------------
bool TUSBDevice::synchReadUSBReport(HANDLE hidDevObject,
void *inputReportBuffer,
ULONG inputReportBufferLength)
{
result = 0;
numberOfBytesRead = 0;
OVERLAPPED *overlapped = NULL;
if(overlapped == NULL){
overlapped = new OVERLAPPED;
overlapped->hEvent = CreateEvent(NULL, TRUE, TRUE, "");
overlapped->Offset = 0;
overlapped->OffsetHigh = 0;
}
if(!ReadFile(hidDevObject, inputReportBuffer, inputReportBufferLength,
&numberOfBytesRead, overlapped)) {
if(GetLastError() == ERROR_IO_PENDING) {
result = WaitForSingleObject(overlapped->hEvent, INFINITE);
if(result == WAIT_TIMEOUT) {
CancelIo(hidDevObject);
return false;
}
else
if(result == WAIT_FAILED){
cout << "Błąd odczytu danych.";
return false;
}
GetOverlappedResult(hidDevObject, overlapped,
&numberOfBytesRead, FALSE);
}
else
displayError("Błąd odczytu danych.");
}
Rozdział 8. ♦ Programowanie obiektowe transmisji USB 313
ResetEvent(overlapped->hEvent);
delete overlapped;
return true;
}
//--------------------------------------------------------------
int main()
{
HIDP_CAPS capabilities;
PHIDP_PREPARSED_DATA preparsedData;
HMODULE hLib = NULL;
HANDLE hidDeviceObject;
BYTE *inputReportBuffer;
while(true) {
usbDevice->synchReadUSBReport(hidDeviceObject, inputReportBuffer,
capabilities.InputReportByteLength);
printf("%d %d %d %d %d %d %d\n", inputReportBuffer[0],
inputReportBuffer[1], inputReportBuffer[2],
inputReportBuffer[3], inputReportBuffer[4],
inputReportBuffer[5], inputReportBuffer[6]);
if(inputReportBuffer[6]==64 || \
hidDeviceObject == INVALID_HANDLE_VALUE){
HidD_FreePreparsedData(preparsedData);
releaseMemory(inputReportBuffer);
break;
}
} //koniec while
}
CloseHandle(hidDeviceObject);
FreeLibrary(hLib);
delete usbDevice;
314 USB. Praktyczne programowanie z Windows API w C++
system("PAUSE");
return 0;
}
//--------------------------------------------------------------
Rysunek 8.2.
Aplikacja
proj_USB_R8_1
w trakcie działania
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
informatycznego można znacznie przyspieszyć, korzystając z gotowych rozwiązań
wdrożonych przy okazji rozwiązywania innych problemów. Wzorce projektowe [9]
stanowią zbiór ogólnych rozwiązań często spotykanych problemów projektowych
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 dostosowywania ich do wcześniejszych projektów, mamy więc pewność, że
są one wystarczająco dobrze sprawdzone.
Singleton
Programowanie zorientowane obiektowo pozwala na tworzenie teoretycznie nieskoń-
czenie wielu 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-
mych danych. Bardzo często dzieje się tak przy tworzeniu połączeń z portami komu-
nikacyjnymi. Tworzenie wielu połączeń nie ułatwia 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. Obiekt ten powinien być globalnie dostępny i nie powinien być kontrolowany
Rozdział 8. ♦ Programowanie obiektowe transmisji USB 315
przez inne obiekty. Klient tworzy egzemplarz singletonu wyłącznie za pomocą meto-
dy getInstance(). Na rysunku 8.3 pokazano uproszczoną adaptację wzorca singletonu
na potrzeby klasy TUSBDevice.
Rysunek 8.3.
Klasa TUSBDevice
jako singleton
Na listingach 8.3 i 8.4 pokazano odpowiednio definicję klasy TUSBDevice oraz przykła-
dową, uproszczoną implementację zdefiniowanych 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. Na 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
#ifndef usb_R8_2H
#define usb_R8_2H
#include <cstring>
#include <assert>
using namespace std;
#define bufferLength 32
#define searchMaxDevice 10
//--------------------------------------------------------------
template <class T>
inline void releaseMemory(T &x)
{
assert(x != NULL);
316 USB. Praktyczne programowanie z Windows API w C++
delete [] x;
x = NULL;
}
//--------------------------------------------------------------
void (__stdcall *HidD_GetHidGuid)(OUT LPGUID HidGuid);
//--------------------------------------------------------------
class TUSBDevice {
private:
static TUSBDevice *instance;
TUSBDevice();
string pathUSBDevice;
DWORD memberIndex;
PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData;
DWORD numberOfBytesRead;
DWORD result;
GUID classGuid;
HMODULE hHidLib;
DWORD deviceInterfaceDetailDataSize;
HDEVINFO deviceInfoSet;
SP_DEVICE_INTERFACE_DATA deviceInterfaceData;
USHORT usVid;
const char *chVid;
public:
static TUSBDevice *getInstance();
~TUSBDevice();
void setCharVid(const char *vid);
void displayError(const char* msg);
bool asynchReadUSBReport(HANDLE hidDevObject, void *inputReportBuffer,
ULONG inputReportBufferLength);
HANDLE asynchOpenUSBDevice();
string getUSBDevicePath(UINT);
};
//--------------------------------------------------------------
#endif
//--------------------------------------------------------------
hHidLib = NULL;
deviceInterfaceDetailData = NULL;
hHidLib = LoadLibrary("C:\\Windows\\System32\\HID.DLL");
if (!hHidLib)
displayError("Błąd dołączenia biblioteki HID.DLL.");
deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
if (SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &deviceInterfaceData,
deviceInterfaceDetailData, deviceInterfaceDetailDataSize,
NULL, NULL)) {
pathUSBDevice = deviceInterfaceDetailData->DevicePath;
//cout << pathUSBDevice << endl;
}
318 USB. Praktyczne programowanie z Windows API w C++
releaseMemory(deviceInterfaceDetailData);
SetupDiDestroyDeviceInfoList(deviceInfoSet);
return pathUSBDevice;
}
//--------------------------------------------------------------
HANDLE TUSBDevice::asynchOpenUSBDevice()
{
string devUSBpath;
HANDLE hidDevObject = INVALID_HANDLE_VALUE;
while((devUSBpath = getUSBDevicePath(memberIndex++)) != "" ){
if (NULL != strstr(devUSBpath.c_str(), chVid)){
cout << endl << devUSBpath <<endl;
hidDevObject = CreateFile(devUSBpath.c_str(), GENERIC_READ | \
GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL );
if(hidDevObject != INVALID_HANDLE_VALUE)
return hidDevObject;
break;
}
else
if (memberIndex > searchMaxDevice)
displayError("Nie znaleziono urządzenia o podanym VID.\n");
}
return INVALID_HANDLE_VALUE;
}
//--------------------------------------------------------------
bool TUSBDevice::asynchReadUSBReport(HANDLE hidDevObject, void *inputReportBuffer,
ULONG inputReportBufferLength)
{
numberOfBytesRead = 0;
return ReadFile(hidDevObject, inputReportBuffer,
inputReportBufferLength, &numberOfBytesRead, NULL);
}
//--------------------------------------------------------------
TUSBDevice *TUSBDevice::instance = NULL;
//--------------------------------------------------------------
int main()
{
HANDLE hidDeviceObject;
BYTE inputReportBuffer[bufferLength];
system("PAUSE");
return 0;
}
//--------------------------------------------------------------
Rysunek 8.4.
Aplikacja
proj_USB_R8_2
w trakcie działania
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 wejściowych była dokonywana w głównej
funkcji programu. Jednak takie podejście nie zawsze bywa 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 wyma-
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 interface, należy jednak pamiętać, że jest to jedynie makro-
definicja (wprowadzona w celu zachowania zgodności z językiem IDL), której użycie
wymaga włączenia pliku nagłówkowego objbase.h.
320 USB. Praktyczne programowanie z Windows API w C++
Listing 8.5. Kod pliku nagłówkowego usb_R8_3.h zawierającego definicje odpowiadające rysunkowi 8.5
#ifndef usb_R8_3H
#define usb_R8_3H
#include <objbase.h>
#include <cstring>
#include <assert>
using namespace std;
#define bufferLength 32
Rozdział 8. ♦ Programowanie obiektowe transmisji USB 321
#define searchMaxDevice 10
//--------------------------------------------------------------
template <class T>
inline void releaseMemory(T &x)
{
assert(x != NULL);
delete [] x;
x = NULL;
}
//--------------------------------------------------------------
typedef struct _HIDD_ATTRIBUTES {
ULONG Size;
USHORT VendorID;
USHORT ProductID;
USHORT VersionNumber;
} HIDD_ATTRIBUTES, *PHIDD_ATTRIBUTES;
//--------------------------------------------------------------
void (__stdcall *HidD_GetHidGuid)(OUT LPGUID HidGuid);
bool (__stdcall *HidD_GetAttributes)(IN HANDLE HidDeviceObject,
OUT PHIDD_ATTRIBUTES Attributes);
//--------------------------------------------------------------
interface ISynchronous {
public:
virtual bool __stdcall synchReadUSBReport(HANDLE hidDevObject,
void *inputReportBuffer, ULONG inputReportBufferLength) = 0;
virtual HANDLE __stdcall synchOpenUSBDevice() = 0;
virtual void __stdcall setUshortVid(USHORT vid) = 0;
virtual ~ISynchronous() {}; //wirtualny destruktor
};
//--------------------------------------------------------------
interface IAsynchronous {
public:
virtual bool __stdcall asynchReadUSBReport(HANDLE hidDevObject,
void *inputReportBuffer, ULONG inputReportBufferLength)= 0;
virtual HANDLE __stdcall asynchOpenUSBDevice() = 0;
virtual void __stdcall setCharVid(const char* vid) = 0;
virtual ~IAsynchronous() {}; //wirtualny destruktor
};
//--------------------------------------------------------------
class TUSBDevice: public ISynchronous, public IAsynchronous {
private:
string pathUSBDevice;
DWORD memberIndex;
HIDD_ATTRIBUTES hiddAttributes;
PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData;
DWORD numberOfBytesRead;
DWORD result;
GUID classGuid;
HMODULE hHidLib;
DWORD deviceInterfaceDetailDataSize;
HDEVINFO deviceInfoSet;
SP_DEVICE_INTERFACE_DATA deviceInterfaceData;
USHORT usVid;
const char *chVid;
public:
TUSBDevice();
~TUSBDevice();
void __stdcall setUshortVid(USHORT vid);
322 USB. Praktyczne programowanie z Windows API w C++
if (!HidD_GetHidGuid || !HidD_GetAttributes){
FreeLibrary(hHidLib);
displayError("Nie znaleziono żadnych funkcji eksportowych.\n");
}
}
//--------------------------------------------------------------
TUSBDevice::~TUSBDevice()
{
if(hHidLib != NULL)
FreeLibrary(hHidLib);
}
//--------------------------------------------------------------
void __stdcall TUSBDevice::displayError(const char* msg){
cout << msg << endl;
system("PAUSE");
exit(0);
};
//--------------------------------------------------------------
void __stdcall TUSBDevice::setUshortVid(USHORT vid)
{
usVid = vid;
Rozdział 8. ♦ Programowanie obiektowe transmisji USB 323
}
//--------------------------------------------------------------
void __stdcall TUSBDevice::setCharVid(const char* vid)
{
chVid = vid;
}
//--------------------------------------------------------------
string __stdcall TUSBDevice::getUSBDevicePath(UINT)
{
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);
if (SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &deviceInterfaceData,
deviceInterfaceDetailData, deviceInterfaceDetailDataSize,
NULL, NULL)) {
pathUSBDevice = deviceInterfaceDetailData->DevicePath;
//cout << pathUSBDevice << endl;
}
releaseMemory(deviceInterfaceDetailData);
SetupDiDestroyDeviceInfoList(deviceInfoSet);
return pathUSBDevice;
}
//--------------------------------------------------------------
HANDLE __stdcall TUSBDevice::asynchOpenUSBDevice()
{
string devUSBpath;
HANDLE hidDevObject = INVALID_HANDLE_VALUE;
while((devUSBpath = getUSBDevicePath(memberIndex++)) != "" ){
if (NULL != strstr(devUSBpath.c_str(), chVid)){
cout << endl << devUSBpath <<endl;
hidDevObject = CreateFile(devUSBpath.c_str(), GENERIC_READ | \
GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL );
if(hidDevObject != INVALID_HANDLE_VALUE)
return hidDevObject;
break;
}
else
if (memberIndex > searchMaxDevice)
displayError("Nie znaleziono urządzenia o podanym VID.\n");
}
return INVALID_HANDLE_VALUE;
}
324 USB. Praktyczne programowanie z Windows API w C++
//--------------------------------------------------------------
HANDLE __stdcall TUSBDevice::synchOpenUSBDevice()
{
string devUSBpath;
HANDLE hidDevObject = INVALID_HANDLE_VALUE;
while((devUSBpath = getUSBDevicePath(memberIndex++)) != "" ){
hidDevObject = CreateFile(devUSBpath.c_str(), GENERIC_READ | \
GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL );
HidD_GetAttributes(hidDevObject, &hiddAttributes);
printf("VID/PID/wersja = %d/%d/%d\n",hiddAttributes.VendorID,
hiddAttributes.ProductID,
hiddAttributes.VersionNumber);
if(hiddAttributes.VendorID == usVid){
return hidDevObject;
break;
}
else
if(memberIndex > searchMaxDevice)
displayError("Nie znaleziono urządzenia o podanym VID.");
}
return INVALID_HANDLE_VALUE;
}
//--------------------------------------------------------------
bool __stdcall TUSBDevice::asynchReadUSBReport(HANDLE hidDevObject,
void *inputReportBuffer, ULONG inputReportBufferLength)
{
numberOfBytesRead = 0;
return ReadFile(hidDevObject, inputReportBuffer,
inputReportBufferLength, &numberOfBytesRead, NULL);
}
//--------------------------------------------------------------
bool __stdcall TUSBDevice::synchReadUSBReport(HANDLE hidDevObject,
void *inputReportBuffer, ULONG inputReportBufferLength)
{
result = 0;
numberOfBytesRead = 0;
OVERLAPPED *overlapped = NULL;
if(overlapped == NULL){
overlapped = new OVERLAPPED;
overlapped->hEvent = CreateEvent(NULL, TRUE, TRUE, "");
overlapped->Offset = 0;
overlapped->OffsetHigh = 0;
}
if(!ReadFile(hidDevObject, inputReportBuffer, inputReportBufferLength,
&numberOfBytesRead, overlapped)) {
if(GetLastError() == ERROR_IO_PENDING) {
result = WaitForSingleObject(overlapped->hEvent, INFINITE /*1000*/);
if(result == WAIT_TIMEOUT) {
CancelIo(hidDevObject);
return false;
}
else
if(result == WAIT_FAILED){
cout << "Błąd odczytu danych.";
Rozdział 8. ♦ Programowanie obiektowe transmisji USB 325
return false;
}
GetOverlappedResult(hidDevObject, overlapped,
&numberOfBytesRead, FALSE);
}
else
displayError("Błąd odczytu danych.");
}
ResetEvent(overlapped->hEvent);
delete overlapped;
return true;
}
//--------------------------------------------------------------
int main()
{
HANDLE hidDeviceObject;
BYTE inputReportBuffer[bufferLength];
CloseHandle(hidDeviceObject);
//delete iAsynchUsbDevice;
delete iSynchUsbDevice;
system("PAUSE");
return 0;
}
//--------------------------------------------------------------
Rysunek 8.6.
Aplikacja
proj_USB_R8_3
w trakcie
cyklicznego
odczytu raportu
wejściowego
z urządzenia
wykonawczego
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.
typedef struct _GUID
{
unsigned long Data1;
unsigned short Data2;
unsigned short Data3;
unsigned char Data4[8];
} GUID;
Listing 8.7. Kod pliku nagłówkowego usb_R8_4.h zawierającego definicje odpowiadające rysunkowi 8.7
#ifndef usb_R8_4H
#define usb_R8_4H
#include <objbase.h>
#include <cstring>
#include <assert>
using namespace std;
#define bufferLength 32
#define searchMaxDevice 10
//--------------------------------------------------------------
template <class T>
inline void releaseMemory(T &x)
{
assert(x != NULL);
delete [] x;
x = NULL;
}
//--------------------------------------------------------------
typedef struct _HIDD_ATTRIBUTES {
ULONG Size;
USHORT VendorID;
USHORT ProductID;
USHORT VersionNumber;
} HIDD_ATTRIBUTES, *PHIDD_ATTRIBUTES;
//--------------------------------------------------------------
void (__stdcall *HidD_GetHidGuid)(OUT LPGUID HidGuid);
bool (__stdcall *HidD_GetAttributes)(IN HANDLE HidDeviceObject,
OUT PHIDD_ATTRIBUTES Attributes);
//--------------------------------------------------------------
interface ISynchronous: public IUnknown {
public:
virtual bool __stdcall synchReadUSBReport(HANDLE hidDevObject,
void *inputReportBuffer, ULONG inputReportBufferLength) = 0;
virtual HANDLE __stdcall synchOpenUSBDevice() = 0;
virtual void __stdcall setUshortVid(USHORT vid) = 0;
virtual ~ISynchronous() {};
};
//--------------------------------------------------------------
interface IAsynchronous: public IUnknown {
public:
virtual bool __stdcall asynchReadUSBReport(HANDLE hidDevObject,
void *inputReportBuffer, ULONG inputReportBufferLength)= 0;
virtual HANDLE __stdcall asynchOpenUSBDevice() = 0;
virtual void __stdcall setCharVid(const char* vid) = 0;
virtual ~IAsynchronous() {};
Rozdział 8. ♦ Programowanie obiektowe transmisji USB 329
};
//--------------------------------------------------------------
//deklaracje identyfikatorów GUID
//['{E7D1FDE1-8A61-11D9-8C68-00E07D843852}']
static GUID iidISynchronous =
{ 0xE7D1FDE1, 0x8A61, 0x11D9, { 0x8C, 0x68, 0x00,
0xE0, 0x7D, 0x84, 0x38, 0x52 } };
//['{E7D1FDE2-8A61-11D9-8C68-00E07D843852}']
static GUID iidIAsynchronous =
{ 0xE7D1FDE2, 0x8A61, 0x11D9, { 0x8C, 0x68, 0x00,
0xE0, 0x7D, 0x84, 0x38, 0x52 } };
//--------------------------------------------------------------
class TUSBDevice: public ISynchronous, public IAsynchronous {
private:
LONG Addend; //licznik odwołań do interfejsu
string pathUSBDevice;
DWORD memberIndex;
HIDD_ATTRIBUTES hiddAttributes;
PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData;
DWORD numberOfBytesRead;
DWORD result;
GUID classGuid;
HMODULE hHidLib;
DWORD deviceInterfaceDetailDataSize;
HDEVINFO deviceInfoSet;
SP_DEVICE_INTERFACE_DATA deviceInterfaceData;
USHORT usVid;
const char *chVid;
public:
TUSBDevice();
~TUSBDevice();
void __stdcall setUshortVid(USHORT vid);
void __stdcall setCharVid(const char *vid);
void __stdcall displayError(const char* msg);
bool __stdcall synchReadUSBReport(HANDLE hidDevObject,
void *inputReportBuffer, ULONG inputReportBufferLength);
bool __stdcall asynchReadUSBReport(HANDLE hidDevObject,
void *inputReportBuffer, ULONG inputReportBufferLength);
HANDLE __stdcall synchOpenUSBDevice();
HANDLE __stdcall asynchOpenUSBDevice();
string __stdcall getUSBDevicePath(UINT);
virtual HRESULT __stdcall
QueryInterface(const IID& iid, void **Obj);
virtual ULONG __stdcall AddRef();
virtual ULONG __stdcall Release();
};
//--------------------------------------------------------------
#endif
//--------------------------------------------------------------
Listing 8.8. Kod modułu usb_R8_4.cpp z implementacją zarządzania czasem życia interfejsów
#include <windows>
#pragma option push -a1
#include <setupapi>
#pragma option pop
#include <iostream>
#include "usb_R8_4.h"
using namespace std;
//--------------------------------------------------------------
TUSBDevice::TUSBDevice()
{
memberIndex = 0;
deviceInterfaceDetailData = NULL;
hHidLib = NULL;
hHidLib = LoadLibrary("C:\\Windows\\System32\\HID.DLL");
if (!hHidLib)
displayError("Błąd dołączenia biblioteki HID.DLL.");
if (!HidD_GetHidGuid || !HidD_GetAttributes){
FreeLibrary(hHidLib);
displayError("Nie znaleziono żadnych funkcji eksportowych.\n");
}
}
//--------------------------------------------------------------
TUSBDevice::~TUSBDevice()
{
if(hHidLib != NULL)
FreeLibrary(hHidLib);
}
//--------------------------------------------------------------
void __stdcall TUSBDevice::displayError(const char* msg){
cout << msg << endl;
system("PAUSE");
exit(0);
};
//--------------------------------------------------------------
void __stdcall TUSBDevice::setUshortVid(USHORT vid)
{
usVid = vid;
}
//--------------------------------------------------------------
Rozdział 8. ♦ Programowanie obiektowe transmisji USB 331
deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
if (SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &deviceInterfaceData,
deviceInterfaceDetailData, deviceInterfaceDetailDataSize,
NULL, NULL)) {
pathUSBDevice = deviceInterfaceDetailData->DevicePath;
//cout << pathUSBDevice << endl;
}
releaseMemory(deviceInterfaceDetailData);
SetupDiDestroyDeviceInfoList(deviceInfoSet);
return pathUSBDevice;
}
//--------------------------------------------------------------
HANDLE __stdcall TUSBDevice::asynchOpenUSBDevice()
{
string devUSBpath;
HANDLE hidDevObject = INVALID_HANDLE_VALUE;
while((devUSBpath = getUSBDevicePath(memberIndex++)) != "" ){
if (NULL != strstr(devUSBpath.c_str(), chVid)){
cout << endl << devUSBpath <<endl;
hidDevObject = CreateFile(devUSBpath.c_str(), GENERIC_READ | \
GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL );
if(hidDevObject != INVALID_HANDLE_VALUE)
return hidDevObject;
break;
}
else
if (memberIndex > searchMaxDevice)
displayError("Nie znaleziono urządzenia o podanym VID.\n");
}
return NULL;
}
//--------------------------------------------------------------
HANDLE __stdcall TUSBDevice::synchOpenUSBDevice()
{
332 USB. Praktyczne programowanie z Windows API w C++
string devUSBpath;
HANDLE hidDevObject = INVALID_HANDLE_VALUE;
while((devUSBpath = getUSBDevicePath(memberIndex++)) != "" ){
hidDevObject = CreateFile(devUSBpath.c_str(), GENERIC_READ | \
GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
HidD_GetAttributes(hidDevObject, &hiddAttributes);
printf("VID/PID/wersja = %d/%d/%d\n",hiddAttributes.VendorID,
hiddAttributes.ProductID,
hiddAttributes.VersionNumber);
if(hiddAttributes.VendorID == usVid){
return hidDevObject;
break;
}
else
if(memberIndex > searchMaxDevice)
displayError("Nie znaleziono urządzenia o podanym VID.");
}
return NULL;
}
//--------------------------------------------------------------
bool __stdcall TUSBDevice::asynchReadUSBReport(HANDLE hidDevObject,
void *inputReportBuffer, ULONG inputReportBufferLength)
{
numberOfBytesRead = 0;
return ReadFile(hidDevObject, inputReportBuffer,
inputReportBufferLength, &numberOfBytesRead, NULL);
}
//--------------------------------------------------------------
bool __stdcall TUSBDevice::synchReadUSBReport(HANDLE hidDevObject,
void *inputReportBuffer, ULONG inputReportBufferLength)
{
result = 0;
numberOfBytesRead = 0;
OVERLAPPED *overlapped = NULL;
if(overlapped == NULL){
overlapped = new OVERLAPPED;
overlapped->hEvent = CreateEvent(NULL, TRUE, TRUE, "");
overlapped->Offset = 0;
overlapped->OffsetHigh = 0;
}
if(!ReadFile(hidDevObject, inputReportBuffer, inputReportBufferLength,
&numberOfBytesRead, overlapped)) {
if(GetLastError() == ERROR_IO_PENDING) {
result = WaitForSingleObject(overlapped->hEvent, INFINITE);
if(result == WAIT_TIMEOUT) {
CancelIo(hidDevObject);
return false;
}
else
if(result == WAIT_FAILED){
cout << "Błąd odczytu danych.";
return false;
}
GetOverlappedResult(hidDevObject, overlapped,
&numberOfBytesRead, FALSE);
Rozdział 8. ♦ Programowanie obiektowe transmisji USB 333
}
else
displayError("Błąd odczytu danych.");
}
ResetEvent(overlapped->hEvent);
delete overlapped;
return true;
}
//--------------------------------------------------------------
IUnknown *CreateInstance()
{
IUnknown *p = (ISynchronous*)new TUSBDevice();
p->AddRef();
return p;
}
//---------------------------------------------------------------
HRESULT _stdcall TUSBDevice::QueryInterface(const IID& iid,
void **Obj)
{
if (iid == iidISynchronous)
*Obj = (ISynchronous*)this; //wywołuje elementy
//interfejsu ISynchronous
else
if (iid == iidIAsynchronous)
*Obj = (IAsynchronous*)this; //wywołuje elementy
//interfejsu IAsynchronous
else {
Obj = NULL;
return E_NOINTERFACE;
}
((IUnknown*)(*Obj))->AddRef();
return S_OK;
}
//--------------------------------------------------------------
ULONG _stdcall TUSBDevice::AddRef()
{
return InterlockedIncrement(&Addend);
}
//--------------------------------------------------------------
ULONG _stdcall TUSBDevice::Release()
{
if (InterlockedDecrement(&Addend) == 0) {
delete this;
return 0;
}
return Addend;
}
//--------------------------------------------------------------
int main()
{
HANDLE hidDeviceObject;
BYTE inputReportBuffer[bufferLength];
HRESULT hr;
//Inicjalizacja komponentu
IUnknown *pUnknown = CreateInstance();
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
´Report(), więc może swobodnie używać nowej wersji komponentu, tak jak pokazano
na rysunku 8.10.
Komponenty wizualne
Komponent jest wymienną częścią systemu implementującą co najmniej jedną klasę.
Rysunek 8.11.
Statyczny diagram
klas odpowiadający
przykładowi
z listingu 8.9
Listing 8.9. Definicja klasy TUSBDetect komponentu USBDetect zawarta w pliku USBDetect.h
//--------------------------------------------------------------
#ifndef USBDetectH
#define USBDetectH
//--------------------------------------------------------------
#include <SysUtils.hpp>
#include <Controls.hpp>
#include <Classes.hpp>
#include <Forms.hpp>
#include <objbase.h>
#include <Dbt.h>
//--------------------------------------------------------------
class PACKAGE TUSBDetect : public TComponent {
private:
HWND FWindowHandle;
TNotifyEvent FUSBAttach;
TNotifyEvent FUSBDetach;
void __fastcall WindowProc(TMessage &msg);
bool __fastcall USBDeviceNotify();
protected:
void __fastcall deviceAttachDetach(TMessage &msg);
public:
__fastcall TUSBDetect(TComponent* Owner);
__fastcall virtual ~TUSBDetect();
__published:
__property TNotifyEvent USBAttach = {read=FUSBAttach,
write=FUSBAttach};
__property TNotifyEvent USBDetach = {read=FUSBDetach,
write=FUSBDetach};
};
//--------------------------------------------------------------
#endif
//--------------------------------------------------------------
338 USB. Praktyczne programowanie z Windows API w C++
Listing 8.10. Kod modułu USBDetect.cpp implementującego elementy składowe klasy komponentu
//--------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "USBDetect.h"
#pragma package(smart_init)
//--------------------------------------------------------------
static inline void ValidCtrCheck(TUSBDetect *)
{
new TUSBDetect(NULL);
}
//--------------------------------------------------------------
__fastcall TUSBDetect::TUSBDetect(TComponent* Owner)
: TComponent(Owner)
{
FWindowHandle = AllocateHWnd(WindowProc);
USBDeviceNotify();
}
//--------------------------------------------------------------
namespace Usbdetect
{
void __fastcall PACKAGE Register() {
TComponentClass classes[1] = {__classid(TUSBDetect)};
RegisterComponents("Samples", classes, 0);
}
}
//--------------------------------------------------------------
__fastcall TUSBDetect::~TUSBDetect()
{
DeallocateHWnd(FWindowHandle);
}
//--------------------------------------------------------------
void __fastcall TUSBDetect::WindowProc(TMessage &msg)
{
if (msg.Msg = WM_DEVICECHANGE) {
try {
deviceAttachDetach(msg);
}
catch(...) {
Application->HandleException(this);
}
}
else
msg.Result = DefWindowProc(FWindowHandle, msg.Msg,
msg.WParam, msg.LParam);
};
//--------------------------------------------------------------
void __fastcall TUSBDetect::deviceAttachDetach(TMessage &msg)
{
UINT devType;
PDEV_BROADCAST_HDR hdr;
Rozdział 8. ♦ Programowanie obiektowe transmisji USB 339
#include "usb_R8_5.h"
//--------------------------------------------------------------
#pragma package(smart_init)
#pragma link "USBDetect"
#pragma resource "*.dfm"
TForm1 *Form1;
//--------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//--------------------------------------------------------------
void __fastcall TForm1::USBDetect1USBAttach(TObject *Sender)
340 USB. Praktyczne programowanie z Windows API w C++
{
ShowMessage("Urządzenie USB zostało przyłączone.");
}
//--------------------------------------------------------------
void __fastcall TForm1::USBDetect1USBDetach(TObject *Sender)
{
ShowMessage("Urządzenie USB zostało odłączone.");
}
//--------------------------------------------------------------
Podsumowanie
Rozdział zawiera krótkie wprowadzenie do metod obiektowego projektowania i imple-
mentacji oprogramowania sterującego łączem USB. Dla osób pragnących poekspery-
mentować 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
Wzorzec projektowy fabryki abstrakcyjnej (ang. abstract factory) pozwala na enkap-
sulację grupy metod fabrykujących dotyczących tego samego zagadnienia. Z reguły
fabryka abstrakcyjna jest przedstawiana w postaci interfejsu. Następnie (w oprogramo-
waniu 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 implementacji 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,
wyboru różnych sterowników itp.).
Każ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 wy-
konujących transmisję danych. Przykładowo można wyobrazić sobie interfejs IDevice-
Factory 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
jak różnego rodzaju kontrolery gier), które mogą się posługiwać protokołami transmi-
sji danych RS 232C i USB.
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
class IDeviceA {
public:
virtual void displayDevice() = 0 ;
virtual ~IDeviceA() {};
};
//--------------------------------------------------------------
class IDeviceB {
public:
virtual void displayDevice() = 0 ;
virtual ~IDeviceB() {};
};
//--------------------------------------------------------------
class TRSDeviceA : public IDeviceA {
private:
void displayDevice() {
cout << "RS 232C Device A" << endl ;
}
};
//--------------------------------------------------------------
class TRSDeviceB : public IDeviceB {
private:
void displayDevice() {
cout << "RS 232C Device B" << endl;
}
};
//--------------------------------------------------------------
class TUSBDeviceB : public IDeviceB {
private:
void displayDevice() {
cout << "USB Device B" << endl ;
}
};
//--------------------------------------------------------------
class TUSBDeviceA : public IDeviceA {
private:
void displayDevice() {
cout << "USB Device A" << endl ;
}
};
//--------------------------------------------------------------
class IDeviceFactory {
public:
virtual IDeviceA* fDeviceA() = 0;
virtual IDeviceB* fDeviceB() = 0;
virtual ~IDeviceFactory() {};
};
//--------------------------------------------------------------
class TRS232CDeviceFactory : public IDeviceFactory {
private:
IDeviceA* fDeviceA() {
return new TRSDeviceA ;
}
Rozdział 8. ♦ Programowanie obiektowe transmisji USB 343
IDeviceB* fDeviceB() {
return new TRSDeviceB ;
}
};
//--------------------------------------------------------------
class TUSBDeviceFactory : public IDeviceFactory {
private:
IDeviceA* fDeviceA() {
return new TUSBDeviceA ;
}
IDeviceB* fDeviceB() {
return new TUSBDeviceB ;
}
};
//--------------------------------------------------------------
IDeviceFactory *df;
IDeviceB *id ;
//--------------------------------------------------------------
int main()
{
df = new TUSBDeviceFactory;
//df = new TRS232CDeviceFactory;
id = df->fDeviceB();
id->displayDevice();
delete 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
Wzorzec projektowy obserwatora (ang. observer) służy do tworzenia relacji typu je-
den-do-wielu łączącej grupę obiektów. Wzorzec 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).
Rysunek 8.14.
Ogólna struktura
wzorca obserwatora
zastosowana
do obsługi
urządzenia USB
class TSubject;
int main()
{
//konkretny przedmiot
usbDevice = new TUSBDevice();
//konkretny obserwator
resultReadReport = new TResultReadReport();
//dołączenie (zarejestrowanie) konkretnego obserwatora
usbDevice->attach(resultReadReport);
for(int i = 0; i < 10; i++) {
usbDevice->readReport();
//powiadamia o odczycie
usbDevice->notify();
cout << endl;
}
//usunięcie (wyrejestrowanie) konkretnego obserwatora
usbDevice->detach(resultReadReport);
delete usbDevice;
delete resultReadReport;
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 modelująca konkretne urządzenie
wykonawcze TUSBDevice, tak jak pokazano na diagramie z rysunku 8.15.
Rysunek 8.15.
Delegowanie interfejsu
};
//---------------------------------------------------------
class TUSBDevice : public IUSBDevice {
public:
//Konstruktor TUSBDevice() wywołuje konstruktor TAsynchReadWrite()
TUSBDevice() : ptrIUSBDevice( new TAsynchReadWrite() ) {
cout << "Konstruktor TUSBDevice\n";
}
//Destruktor klasy TUSBDevice
~TUSBDevice() { delete ptrIUSBDevice; }
void readUSBData() { ptrIUSBDevice->readUSBData(); }
void writeUSBData() { ptrIUSBDevice->writeUSBData(); }
void delegationToTSynchReadWrite() {
delete ptrIUSBDevice;
ptrIUSBDevice = new TSynchReadWrite();
}
void delegationToTAsynchReadWrite() {
delete ptrIUSBDevice;
ptrIUSBDevice = new TAsynchReadWrite();
}
private:
//Agregacja interfejsu IUSBDevice z klasą TUSBDevice
IUSBDevice* ptrIUSBDevice;
};
//---------------------------------------------------------
int main() {
//Tworzony jest obiekt anonimowy klasy TAsynchReadWrite,
//a następnie kończona jest konstrukcja nazwanego obiektu
//usbDevice klasy TUSBDevice
TUSBDevice usbDevice;
//Wywoływane są metody klasy TAsynchReadWrite
usbDevice.readUSBData();
usbDevice.writeUSBData();
//Wywoływany jest destruktor klasy TAsynchReadWrite
//i tworzony jest obiekt anonimowy klasy TSynchReadWrite
usbDevice.delegationToTSynchReadWrite();
//Wywoływane są metody klasy TSynchReadWrite
usbDevice.readUSBData();
usbDevice.writeUSBData();
//Łańcuch delegacji może być powtarzany wielokrotnie
usbDevice.delegationToTAsynchReadWrite();
usbDevice.readUSBData();
usbDevice.writeUSBData();
cin.get();
return 0;
}
//---------------------------------------------------------
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 jed-
nej przestrzeni nazw w polach odpowiednio konstruowanych struktur danych. Poniżej
zamieszczono przykładową strukturę DEVICE_DATA:
//--------------------------------------------------------------
typedef struct _DEVICE_DATA {
TCHAR *HardwareId;
TCHAR *Path;
DWORD DeviceInstance;
HANDLE hidDeviceObject;
//---
BYTE *inputReportBuffer;
USHORT inputReportByteLength;
//---
BYTE *outputReportBuffer;
USHORT outputReportByteLength;
//---
BYTE *featureReportBuffer;
USHORT featureReportByteLength;
/*...*/
PHIDP_PREPARSED_DATA preparsedData;
HIDP_CAPS capabilities;
} DEVICE_DATA, *PDEVICE_DATA;
//--------------------------------------------------------------
Program proceduralny
Kod programu proceduralnego jest 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 przechowują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).
#include <iostream>
hHidLib = LoadLibrary("C:\\Windows\\SySWOW64\\HID.DLL");
if (!hHidLib)
displayError("Błąd dołączenia biblioteki HID.DLL.");
(FARPROC&) HidP_GetCaps=GetProcAddress(hHidLib,
"HidP_GetCaps");
(FARPROC&) HidD_GetPreparsedData=GetProcAddress(hHidLib,
"HidD_GetPreparsedData");
(FARPROC&) HidD_FreePreparsedData=GetProcAddress(hHidLib,
"HidD_FreePreparsedData");
hHidLib = LoadLibrary("C:\\Windows\\SySWOW64\\HID.DLL");
if (!hHidLib)
displayError("Błąd dołączenia biblioteki HID.DLL.");
HidD_GetHidGuid (&classGuid);
deviceInterfaceData.cbSize = sizeof(SP_INTERFACE_DEVICE_DATA);
while(!done) {
deviceList = new \
DEVICE_DATA[((memberIndex+1)*sizeof(DEVICE_DATA))];
if(deviceInterfaceDetailData) {
deviceInterfaceDetailData->cbSize=\
sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA);
}
else {
SetupDiDestroyDeviceInfoList(deviceInfoSet);
releaseMemory(deviceInterfaceDetailData);
return 0;
}
deviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
if (!SetupDiGetDeviceInterfaceDetail(deviceInfoSet,
&deviceInterfaceData, deviceInterfaceDetailData,
deviceInterfaceDetailDataSize,
&requiredSize, &deviceInfoData)) {
SetupDiDestroyDeviceInfoList(deviceInfoSet);
releaseMemory(deviceInterfaceDetailData);
return 0;
}
deviceList[memberIndex].DeviceInstance = deviceInfoData.DevInst;
SetupDiGetDeviceRegistryProperty(deviceInfoSet, &deviceInfoData,
SPDRP_HARDWAREID, NULL, NULL, 0,
&propertyBufferSize);
SetupDiGetDeviceRegistryProperty(deviceInfoSet, &deviceInfoData,
SPDRP_HARDWAREID,NULL,
propertyBuffer, propertyBufferSize,
NULL);
deviceList[memberIndex].HardwareId = propertyBuffer;
cout <<"\nDeviceList["<<memberIndex<<"].HardwareId: \n" <<
deviceList[memberIndex].HardwareId << endl;
}
else {
if(ERROR_NO_MORE_ITEMS == GetLastError()){
done = TRUE;
break;
}
}
releaseMemory(deviceInterfaceDetailData);
releaseMemory(propertyBuffer);
//releaseMemory(deviceList[memberIndex].Path);
}
//releaseMemory(deviceList);
}
SetupDiDestroyDeviceInfoList(deviceInfoSet);
FreeLibrary(hHidLib);
return memberIndex;
}
//---------------------------------------------------------
HANDLE openHidUSBDevice(UINT memberIndex)
{
deviceList[memberIndex].hidDeviceObject == INVALID_HANDLE_VALUE;
deviceList[memberIndex].hidDeviceObject =
CreateFile(deviceList[memberIndex].Path,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,OPEN_EXISTING,FILE_FLAG_OVERLAPPED,NULL);
if(deviceList[memberIndex].hidDeviceObject != INVALID_HANDLE_VALUE)
return deviceList[memberIndex].hidDeviceObject;
else
return INVALID_HANDLE_VALUE;
}
Rozdział 9. ♦ Wewnętrzne struktury danych 357
//---------------------------------------------------------
BOOL closeHidUSBDevice(HANDLE devHandle)
{
if((devHandle == 0) ||
(devHandle == INVALID_HANDLE_VALUE)){
return false;
}
else
return CloseHandle(devHandle);
}
//---------------------------------------------------------
BOOL readUSBReport(UINT memberIndex)
{
DWORD result = 0;
DWORD numberOfBytesRead = 0;
OVERLAPPED *overlapped = NULL;
if(overlapped == NULL){
overlapped = new OVERLAPPED;
overlapped->hEvent = CreateEvent(NULL, TRUE, TRUE, "");
overlapped->Offset = 0;
overlapped->OffsetHigh = 0;
}
if(!ReadFile(deviceList[memberIndex].hidDeviceObject,
deviceList[memberIndex].inputReportBuffer,
deviceList[memberIndex].inputReportByteLength,
&numberOfBytesRead, overlapped)) {
if(GetLastError() == ERROR_IO_PENDING) {
result = WaitForSingleObject(overlapped->hEvent, 100);
if(result == WAIT_TIMEOUT) {
CancelIo(deviceList[memberIndex].hidDeviceObject);
return false;
}
else
if(result == WAIT_FAILED){
displayError("Błąd odczytu danych.");
return false;
}
GetOverlappedResult(deviceList[memberIndex].hidDeviceObject,
overlapped, &numberOfBytesRead, FALSE);
}
else
displayError("Błąd odczytu danych.");
}
ResetEvent(overlapped->hEvent);
if(numberOfBytesRead == deviceList[memberIndex].inputReportByteLength){
for(USHORT i=0; i< deviceList[memberIndex].inputReportByteLength; i++)
printf("%d ", deviceList[memberIndex].inputReportBuffer[i]);
printf("\n");
}
else {
printf("Błędna liczba odebranych bajtów.\n",numberOfBytesRead);
}
delete overlapped;
return true;
}
//---------------------------------------------------------
int main(){
358 USB. Praktyczne programowanie z Windows API w C++
Program obiektowy
Programowanie obiektowe jest metodą tworzenia programów za pomocą 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 komputerowy jest
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. Na rysunku 9.3 zaprezentowano statyczny dia-
gram będący obiektową wersją programu proceduralnego z listingu 9.1, który uzupeł-
niono o klasę TUSBDevice, której jeden 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ą jak 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.
Rysunek 9.3. Struktura logiczna programu obiektowego posługującego się strukturą DEVICE_DATA
#include <iostream>
USHORT NumberFeatureValueCaps;
USHORT NumberFeatureDataIndices;
} HIDP_CAPS, *PHIDP_CAPS;
//---------------------------------------------------------
typedef struct _DEVICE_DATA {
TCHAR *HardwareId;
TCHAR *Path;
DWORD DeviceInstance;
HANDLE hidDeviceObject;
BYTE *inputReportBuffer;
USHORT inputReportByteLength;
/*...*/
USHORT outputReportByteLength;
USHORT featureReportByteLength;
/*...*/
PHIDP_PREPARSED_DATA preparsedData;
HIDP_CAPS capabilities;
} DEVICE_DATA, *PDEVICE_DATA;
//---------------------------------------------------------
class TUSBDevice {
private:
UINT fMemberIndex;
void displayError(const char* msg);
public:
PDEVICE_DATA deviceList;
BOOL getHidDeviceCapabilities(UINT memberIndex);
UINT setGetHidDeviceData();
HANDLE openHidUSBDevice(UINT memberIndex);
BOOL readUSBReport(UINT memberIndex);
BOOL closeHidUSBDevice(HANDLE devHandle);
TUSBDevice(UINT memberIndex);
~TUSBDevice();
};
//---------------------------------------------------------
TUSBDevice::TUSBDevice(UINT memberIndex)
{
fMemberIndex = memberIndex;
deviceList = new \
DEVICE_DATA[((fMemberIndex+1)*sizeof(DEVICE_DATA))];
}
//---------------------------------------------------------
TUSBDevice::~TUSBDevice()
{
releaseMemory(deviceList);
system("PAUSE");
}
//---------------------------------------------------------
void TUSBDevice::displayError(const char* msg)
{
cout << msg << endl;
system("PAUSE");
exit(0);
};
//---------------------------------------------------------
BOOL TUSBDevice::getHidDeviceCapabilities(UINT memberIndex)
{
HMODULE hHidLib;
bool status;
362 USB. Praktyczne programowanie z Windows API w C++
hHidLib = LoadLibrary("C:\\Windows\\SySWOW64\\HID.DLL");
if (!hHidLib)
displayError("Błąd dołączenia biblioteki HID.DLL.");
(FARPROC&) HidP_GetCaps=GetProcAddress(hHidLib,
"HidP_GetCaps");
(FARPROC&) HidD_GetPreparsedData=GetProcAddress(hHidLib,
"HidD_GetPreparsedData");
(FARPROC&) HidD_FreePreparsedData=GetProcAddress(hHidLib,
"HidD_FreePreparsedData");
hHidLib = LoadLibrary("C:\\Windows\\SySWOW64\\HID.DLL");
Rozdział 9. ♦ Wewnętrzne struktury danych 363
if (!hHidLib)
displayError("Błąd dołączenia biblioteki HID.DLL.");
HidD_GetHidGuid (&classGuid);
deviceInterfaceData.cbSize = sizeof(SP_INTERFACE_DEVICE_DATA);
while(!done) {
for(; fMemberIndex < maxDevice; fMemberIndex++) {
if(SetupDiEnumDeviceInterfaces(deviceInfoSet,0,&classGuid,
fMemberIndex,&deviceInterfaceData)) {
SetupDiGetDeviceInterfaceDetail(deviceInfoSet,&deviceInterfaceData,
NULL,0,&deviceInterfaceDetailDataSize,
NULL);
requiredSize = deviceInterfaceDetailDataSize;
deviceInterfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)\
new DWORD[deviceInterfaceDetailDataSize];
if(deviceInterfaceDetailData) {
deviceInterfaceDetailData->cbSize=\
sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA);
}
else {
SetupDiDestroyDeviceInfoList(deviceInfoSet);
releaseMemory(deviceInterfaceDetailData);
return 0;
}
deviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
if(!SetupDiGetDeviceInterfaceDetail(deviceInfoSet,
&deviceInterfaceData, deviceInterfaceDetailData,
deviceInterfaceDetailDataSize,
&requiredSize, &deviceInfoData)) {
SetupDiDestroyDeviceInfoList(deviceInfoSet);
releaseMemory(deviceInterfaceDetailData);
return 0;
}
deviceList[fMemberIndex].DeviceInstance = deviceInfoData.DevInst;
SetupDiGetDeviceRegistryProperty(deviceInfoSet, &deviceInfoData,
SPDRP_HARDWAREID, NULL, NULL, 0,
&propertyBufferSize);
364 USB. Praktyczne programowanie z Windows API w C++
SetupDiGetDeviceRegistryProperty(deviceInfoSet, &deviceInfoData,
SPDRP_HARDWAREID,NULL,
propertyBuffer, propertyBufferSize,
NULL);
deviceList[fMemberIndex].HardwareId = propertyBuffer;
cout <<"\nDeviceList["<<fMemberIndex<<"].HardwareId: \n" <<
deviceList[fMemberIndex].HardwareId << endl;
releaseMemory(deviceInterfaceDetailData);
releaseMemory(propertyBuffer);
}
else {
if(ERROR_NO_MORE_ITEMS == GetLastError()){
done = TRUE;
break;
}
}
}
}
SetupDiDestroyDeviceInfoList(deviceInfoSet);
FreeLibrary(hHidLib);
return fMemberIndex;
}
//---------------------------------------------------------
HANDLE TUSBDevice::openHidUSBDevice(UINT memberIndex)
{
deviceList[memberIndex].hidDeviceObject == INVALID_HANDLE_VALUE;
deviceList[memberIndex].hidDeviceObject =
CreateFile(deviceList[memberIndex].Path,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,OPEN_EXISTING,FILE_FLAG_OVERLAPPED,NULL);
if(deviceList[memberIndex].hidDeviceObject != INVALID_HANDLE_VALUE)
return deviceList[memberIndex].hidDeviceObject;
else
return INVALID_HANDLE_VALUE;
}
//---------------------------------------------------------
BOOL TUSBDevice::closeHidUSBDevice(HANDLE devHandle)
{
if((devHandle == 0) ||
(devHandle == INVALID_HANDLE_VALUE)){
return false;
}
else
return CloseHandle(devHandle);
}
//---------------------------------------------------------
BOOL TUSBDevice::readUSBReport(UINT memberIndex)
{
DWORD result = 0;
DWORD numberOfBytesRead = 0;
Rozdział 9. ♦ Wewnętrzne struktury danych 365
Rysunek 9.4.
Aplikacja
proj_USB_R9_2
w trakcie
pobierania
raportów
wejściowych
Rysunek 9.5. Statyczny diagram klas dla programu zorientowanego obiektowo, posługującego
się strukturą DEVICE_DATA 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.
Na listingach 9.3 i 9.4 pokazano odpowiednio definicję struktury klas z rysunku 9.5
oraz implementację funkcji składowych tych klas. Wynik działania programu przed-
stawiono na rysunkach 9.6 i 9.7.
368 USB. Praktyczne programowanie z Windows API w C++
USHORT NumberInputValueCaps;
USHORT NumberInputDataIndices;
USHORT NumberOutputButtonCaps;
USHORT NumberOutputValueCaps;
USHORT NumberOutputDataIndices;
USHORT NumberFeatureButtonCaps;
USHORT NumberFeatureValueCaps;
USHORT NumberFeatureDataIndices;
} HIDP_CAPS, *PHIDP_CAPS;
//---------------------------------------------------------
typedef struct _DEVICE_DATA {
TCHAR *HardwareId;
TCHAR *Path;
DWORD DeviceInstance;
HANDLE hidDeviceObject;
BYTE *inputReportBuffer;
USHORT inputReportByteLength;
/*...*/
USHORT outputReportByteLength;
USHORT featureReportByteLength;
/*...*/
PHIDP_PREPARSED_DATA preparsedData;
HIDP_CAPS capabilities;
} DEVICE_DATA, *PDEVICE_DATA;
//---------------------------------------------------------
class TUSBDevice {
private:
UINT fMemberIndex;
void displayError(const char* msg);
public:
PDEVICE_DATA deviceList;
BOOL getHidDeviceCapabilities(UINT memberIndex);
UINT setGetHidDeviceData();
HANDLE openHidUSBDevice(UINT memberIndex);
BOOL readUSBReport(UINT memberIndex);
BOOL closeHidUSBDevice(HANDLE devHandle);
TUSBDevice(UINT memberIndex);
~TUSBDevice();
};
//---------------------------------------------------------
class TForm1 : public TForm
{
__published: //Komponenty IDE (zintegrowanego środowiska programisty)
TButton *Button1;
TTrackBar *TrackBar1;
TMemo *Memo1;
TListView *ListView1;
void __fastcall Button1Click(TObject *Sender);
void __fastcall FormClose(TObject *Sender,
TCloseAction &Action);
private: //Deklaracje użytkownika
TUSBDevice *usbDevice;
public: //Deklaracje użytkownika
__fastcall TForm1(TComponent* Owner);
TListItem *ListItem;
};
//---------------------------------------------------------
extern PACKAGE TForm1 *Form1;
370 USB. Praktyczne programowanie z Windows API w C++
//---------------------------------------------------------
#endif
//---------------------------------------------------------
hHidLib = LoadLibrary("C:\\Windows\\SySWOW64\\HID.DLL");
if (!hHidLib)
displayError("Błąd dołączenia biblioteki HID.DLL.");
(FARPROC&) HidP_GetCaps=GetProcAddress(hHidLib,
"HidP_GetCaps");
(FARPROC&) HidD_GetPreparsedData=GetProcAddress(hHidLib,
"HidD_GetPreparsedData");
(FARPROC&) HidD_FreePreparsedData=GetProcAddress(hHidLib,
"HidD_FreePreparsedData");
Rozdział 9. ♦ Wewnętrzne struktury danych 371
AnsiString S1 = S1.Format("InputReportByteLength=%d\
OutputReportByteLength=%d\
FeatureReportByteLength=%d",
OPENARRAY(TVarRec,(deviceList[memberIndex].inputReportByteLength,
deviceList[memberIndex].outputReportByteLength,
deviceList[memberIndex].featureReportByteLength)));
Form1->Memo1->Lines->Add(S1);
HidD_FreePreparsedData(deviceList->preparsedData);
}
FreeLibrary(hHidLib);
return status;
}
//---------------------------------------------------------
UINT TUSBDevice::setGetHidDeviceData()
{
DWORD propertyBufferSize = 0;
char *propertyBuffer = NULL;
HMODULE hHidLib;
SP_DEVINFO_DATA deviceInfoData;
HDEVINFO deviceInfoSet;
SP_INTERFACE_DEVICE_DATA deviceInterfaceData;
fMemberIndex = 0;
GUID classGuid;
PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData = NULL;
DWORD requiredSize = 0;
DWORD deviceInterfaceDetailDataSize = 0;
DWORD maxDevice = searchMaxDevices; //maksymalna liczba interfejsów urządzeń
bool done = false;
hHidLib = LoadLibrary("C:\\Windows\\SySWOW64\\HID.DLL");
if (!hHidLib)
displayError("Błąd dołączenia biblioteki HID.DLL.");
HidD_GetHidGuid (&classGuid);
deviceInterfaceData.cbSize = sizeof(SP_INTERFACE_DEVICE_DATA);
while(!done) {
for(; fMemberIndex < maxDevice; fMemberIndex++) {
if(SetupDiEnumDeviceInterfaces(deviceInfoSet,0,&classGuid,
fMemberIndex,&deviceInterfaceData)) {
SetupDiGetDeviceInterfaceDetail(deviceInfoSet,&deviceInterfaceData,
NULL,0,&deviceInterfaceDetailDataSize,
NULL);
requiredSize = deviceInterfaceDetailDataSize;
deviceInterfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)\
new DWORD[deviceInterfaceDetailDataSize];
if(deviceInterfaceDetailData) {
deviceInterfaceDetailData->cbSize=\
sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA);
}
else {
SetupDiDestroyDeviceInfoList(deviceInfoSet);
releaseMemory(deviceInterfaceDetailData);
return 0;
}
deviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
if (!SetupDiGetDeviceInterfaceDetail(deviceInfoSet,
&deviceInterfaceData, deviceInterfaceDetailData,
deviceInterfaceDetailDataSize,
&requiredSize, &deviceInfoData)) {
SetupDiDestroyDeviceInfoList(deviceInfoSet);
releaseMemory(deviceInterfaceDetailData);
return 0;
}
Form1->ListItem = Form1->ListView1->Items->Add();
Form1->ListItem->Caption = deviceList[fMemberIndex].Path;
Form1->ListItem->SubItems->Add(deviceList[fMemberIndex].Path);
deviceList[fMemberIndex].DeviceInstance = deviceInfoData.DevInst;
SetupDiGetDeviceRegistryProperty(deviceInfoSet, &deviceInfoData,
SPDRP_HARDWAREID, NULL, NULL, 0,
&propertyBufferSize);
SetupDiGetDeviceRegistryProperty(deviceInfoSet, &deviceInfoData,
SPDRP_HARDWAREID,NULL,
Rozdział 9. ♦ Wewnętrzne struktury danych 373
propertyBuffer, propertyBufferSize,
NULL);
deviceList[fMemberIndex].HardwareId = propertyBuffer;
}
else {
if(ERROR_NO_MORE_ITEMS == GetLastError()){
done = TRUE;
break;
}
}
releaseMemory(deviceInterfaceDetailData);
releaseMemory(propertyBuffer);
}
}
SetupDiDestroyDeviceInfoList(deviceInfoSet);
FreeLibrary(hHidLib);
return fMemberIndex;
}
//---------------------------------------------------------
HANDLE TUSBDevice::openHidUSBDevice(UINT memberIndex)
{
deviceList[memberIndex].hidDeviceObject == INVALID_HANDLE_VALUE;
deviceList[memberIndex].hidDeviceObject =
CreateFile(deviceList[memberIndex].Path,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,NULL);
if(deviceList[memberIndex].hidDeviceObject != INVALID_HANDLE_VALUE)
return deviceList[memberIndex].hidDeviceObject;
else
return INVALID_HANDLE_VALUE;
}
//---------------------------------------------------------
BOOL TUSBDevice::closeHidUSBDevice(HANDLE devHandle)
{
if((devHandle == 0) ||
(devHandle == INVALID_HANDLE_VALUE)){
return false;
}
else
return CloseHandle(devHandle);
}
//---------------------------------------------------------
BOOL TUSBDevice::readUSBReport(UINT memberIndex)
{
DWORD result = 0;
DWORD numberOfBytesRead = 0;
OVERLAPPED *overlapped = NULL;
if(overlapped == NULL){
overlapped = new OVERLAPPED;
overlapped->hEvent = CreateEvent(NULL, TRUE, TRUE, "");
overlapped->Offset = 0;
overlapped->OffsetHigh = 0;
}
374 USB. Praktyczne programowanie z Windows API w C++
if(!ReadFile(deviceList[memberIndex].hidDeviceObject,
deviceList[memberIndex].inputReportBuffer,
deviceList[memberIndex].inputReportByteLength,
&numberOfBytesRead, overlapped)) {
if(GetLastError() == ERROR_IO_PENDING) {
result = WaitForSingleObject(overlapped->hEvent, 100);
if(result == WAIT_TIMEOUT) {
CancelIo(deviceList[memberIndex].hidDeviceObject);
return false;
}
else
if(result == WAIT_FAILED){
displayError("Błąd odczytu danych.");
return false;
}
GetOverlappedResult(deviceList[memberIndex].hidDeviceObject,
overlapped, &numberOfBytesRead, FALSE);
}
else
displayError("Błąd odczytu danych.");
}
ResetEvent(overlapped->hEvent);
if(numberOfBytesRead == (UINT)deviceList[memberIndex].inputReportByteLength){
Form1->TrackBar1->Position = deviceList[memberIndex].inputReportBuffer[1];
}
else {
displayError("Błędna liczba odebranych bajtów.");
}
delete overlapped;
return true;
}
//---------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
ListView1->ViewStyle = vsSmallIcon;
usbDevice = NULL;
usbDevice = new TUSBDevice(searchMaxDevices);
usbDevice->setGetHidDeviceData();
}
//---------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
if(ListView1->Selected) {//jeżeli zaznaczono interfejs
if(usbDevice->openHidUSBDevice(ListView1->Selected->Index)!= \
INVALID_HANDLE_VALUE)
ShowMessage("Urządzenie odblokowane do transmisji.");
else
ShowMessage("Urządzenia nie można odblokować do transmisji.");
usbDevice->readUSBReport(ListView1->Selected->Index);
if(usbDevice->deviceList[ListView1->Selected->Index].\
inputReportBuffer[6]==64) {
//zwolnienie pamięci dla bufora wejściowego
releaseMemory(usbDevice->deviceList[ListView1->\
Selected->Index].inputReportBuffer);
Form1->Caption = "Koniec odczytu danych.";
break;
}
}
usbDevice->closeHidUSBDevice(usbDevice->deviceList[ListView1->\
Selected->Index].hidDeviceObject);
}
}
else
ShowMessage("Nie wybrano interfejsu urządzenia.");
}
//---------------------------------------------------------
void __fastcall TForm1::FormClose(TObject *Sender,
TCloseAction &Action)
{
if(ListView1->Selected){
releaseMemory(usbDevice->deviceList[ListView1->\
Selected->Index].Path);
}
delete usbDevice;
Action = caFree;
}
//---------------------------------------------------------
Podsumowanie
W rozdziale zaprezentowano różne techniki projektowania i implementacji oprogra-
mowania wykorzystującego wewnętrzną strukturę danych, opisującą podstawowe pa-
rametry urządzenia USB. Prześledziliśmy jedną 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 zaawansowanymi 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++
Rysunek 9.8. Struktury opisujące urządzenie klasy HID i raporty, którymi się posługuje
Ćwiczenie 9.2
W zasobach WDK 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 wyświetlającą listę wszyst-
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 Windows oraz wyjaśnia, w jaki sposób efektywnie je wyko-
rzystać do programowania współbieżnego.
Wątki i procesy
Wątek (ang. thread) jest definiowany jako odrębny przebieg aplikacji. Aplikacja pi-
sana dla systemu Windows może zawierać wiele wątków, każdy z własnym stosem,
własnym identyfikatorem i kopią rejestrów procesora. W komputerach wieloproceso-
rowych poszczególne procesory są w stanie wykonywać dane wątki w sposób nieza-
leżny. W komputerach jednoprocesorowych 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 jest 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 mogą współdzielić różne zasoby aplikacji (lub systemu), ta-
kie jak otwarte pliki lub uruchomione aplikacje, oraz odwoływać się do prawidłowo
wybranego adresu pamięci w przestrzeni adresowej procesora.
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 wykonują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 jest 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 jest blokowany,
jeżeli oczekuje na wykonanie konkretnej operacji. Zawsze można jawnie zablokować
wątek przez jego zawieszenie (ang. suspend). Zawieszony wątek będzie oczekiwał
w nieskończoność (ang. infinite) lub do momentu jego wznowienia (ang. resume).
Rysunek 10.1.
Uproszczony
schemat dla systemu
operacyjnego
działającego w trybie
z wywłaszczaniem
Rozdział 10. ♦ Programy wielowątkowe 381
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. Moduł system.hpp definiuje omawianą funkcję w następu-
jący sposób:
HANDLE CreateThread(
IN LPSECURITY_ATTRIBUTES lpThreadAttributes,
IN DWORD dwStackSize,
IN LPTHREAD_START_ROUTINE lpStartAddress,
IN LPVOID lpParameter,
IN DWORD dwCreationFlags,
OUT LPDWORD lpThreadId);
Wielką 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-
wana jako osobny wątek. Rolę takiej funkcji z powodzeniem może odgrywać wskaź-
nik do funkcji:
DWORD WINAPI ThreadFunc(IN LPVOID parameter)
Listing 10.1. Kod modułu usb_R10_1.h zawierający implementację funkcji składowych klas
z rysunku 10.2
#ifndef usb_R10_1H
#define usb_R10_1H
#include <windows>
#pragma option push -a1
#include <setupapi>
#pragma option pop
#include <assert>
#include <iostream>
//--------------------------------------------------------------
template <class T>
inline void releaseMemory(T &x)
{
assert(x != NULL);
delete [] x;
x = NULL;
Rozdział 10. ♦ Programy wielowątkowe 383
}
//--------------------------------------------------------------
typedef USHORT USAGE, *PUSAGE;
typedef struct _HIDP_PREPARSED_DATA *PHIDP_PREPARSED_DATA;
//--------------------------------------------------------------
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;
//--------------------------------------------------------------
typedef struct _DEVICE_DATA {
TCHAR *HardwareId;
TCHAR *Path;
DWORD DeviceInstance;
HANDLE hidDeviceObject;
BYTE *inputReportBuffer;
USHORT inputReportByteLength;
PHIDP_PREPARSED_DATA preparsedData;
HIDP_CAPS capabilities;
} DEVICE_DATA, *PDEVICE_DATA;
//--------------------------------------------------------------
class TUSBDevice {
private:
UINT fMemberIndex;
void displayError(const char* msg);
public:
PDEVICE_DATA deviceList;
BOOL getHidDeviceCapabilities(UINT memberIndex);
UINT setGetHidDeviceData();
HANDLE openHidUSBDevice(UINT memberIndex);
BOOL readUSBReport(UINT memberIndex);
BOOL closeHidUSBDevice(HANDLE devHandle);
TUSBDevice(UINT memberIndex);
~TUSBDevice();
};
//--------------------------------------------------------------
class Thread {
private:
static DWORD WINAPI ThreadFunc(void* parameter) {
(reinterpret_cast<Thread *>(parameter))->runThread();
return 0;
}
public:
typedef DWORD threadID;
384 USB. Praktyczne programowanie z Windows API w C++
HANDLE beginThread(){
threadID id;
return CreateThread(NULL, 0, ThreadFunc, this,
CREATE_SUSPENDED, &id);
}
virtual void runThread() = 0;
void resumeThread() {ResumeThread(beginThread());}
void closeHandle() {CloseHandle(beginThread());}
};
//--------------------------------------------------------------
class TThreadUSBPort: public Thread
{
public:
UINT interfaceIndex;
void runThread();
};
//--------------------------------------------------------------
#endif
hHidLib = LoadLibrary("C:\\Windows\\System32\\HID.DLL");
if (!hHidLib)
displayError("Błąd dołączenia biblioteki HID.DLL.");
(FARPROC&) HidP_GetCaps=GetProcAddress(hHidLib,"HidP_GetCaps");
(FARPROC&) HidD_GetPreparsedData=GetProcAddress(hHidLib,
"HidD_GetPreparsedData");
(FARPROC&) HidD_FreePreparsedData=GetProcAddress(hHidLib,
"HidD_FreePreparsedData");
hHidLib = LoadLibrary("C:\\Windows\\System32\\HID.DLL");
if (!hHidLib)
displayError("Błąd dołączenia biblioteki HID.DLL.");
while(!done) {
for(; fMemberIndex < maxDevice; fMemberIndex++) {
if(SetupDiEnumDeviceInterfaces(deviceInfoSet,0,&classGuid,
fMemberIndex,&deviceInterfaceData)) {
SetupDiGetDeviceInterfaceDetail(deviceInfoSet,&deviceInterfaceData,
NULL,0,&deviceInterfaceDetailDataSize,
NULL);
requiredSize = deviceInterfaceDetailDataSize;
deviceInterfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)\
new DWORD[deviceInterfaceDetailDataSize];
if(deviceInterfaceDetailData) {
deviceInterfaceDetailData->cbSize=\
sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA);
}
else {
SetupDiDestroyDeviceInfoList(deviceInfoSet);
releaseMemory(deviceInterfaceDetailData);
return 0;
}
deviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
if(!SetupDiGetDeviceInterfaceDetail(deviceInfoSet,
&deviceInterfaceData, deviceInterfaceDetailData,
deviceInterfaceDetailDataSize,
&requiredSize, &deviceInfoData)) {
SetupDiDestroyDeviceInfoList(deviceInfoSet);
releaseMemory(deviceInterfaceDetailData);
return 0;
}
size_t nLen = strlen(deviceInterfaceDetailData->DevicePath) + 1;
deviceList[fMemberIndex].Path = new TCHAR[(nLen*sizeof(TCHAR))];
strncpy(deviceList[fMemberIndex].Path,
deviceInterfaceDetailData->DevicePath, nLen);
deviceList[fMemberIndex].DeviceInstance = deviceInfoData.DevInst;
SetupDiGetDeviceRegistryProperty(deviceInfoSet, &deviceInfoData,
SPDRP_HARDWAREID, NULL, NULL, 0,
&propertyBufferSize);
//alokowanie pamięci dla bufora danych
propertyBuffer = new char[(propertyBufferSize*sizeof(TCHAR))];
SetupDiGetDeviceRegistryProperty(deviceInfoSet, &deviceInfoData,
SPDRP_HARDWAREID,NULL,
propertyBuffer, propertyBufferSize,
NULL);
deviceList[fMemberIndex].HardwareId = propertyBuffer;
releaseMemory(deviceInterfaceDetailData);
releaseMemory(propertyBuffer);
}
else {
if(ERROR_NO_MORE_ITEMS == GetLastError()){
done = TRUE;
break;
}
}
}
Rozdział 10. ♦ Programy wielowątkowe 387
}
SetupDiDestroyDeviceInfoList(deviceInfoSet);
FreeLibrary(hHidLib);
return fMemberIndex;
}
//--------------------------------------------------------------
HANDLE TUSBDevice::openHidUSBDevice(UINT memberIndex)
{
deviceList[memberIndex].hidDeviceObject == INVALID_HANDLE_VALUE;
deviceList[memberIndex].hidDeviceObject =
CreateFile(deviceList[memberIndex].Path,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,OPEN_EXISTING,FILE_FLAG_OVERLAPPED,NULL);
if(deviceList[memberIndex].hidDeviceObject != INVALID_HANDLE_VALUE)
return deviceList[memberIndex].hidDeviceObject;
else
return INVALID_HANDLE_VALUE;
}
//--------------------------------------------------------------
BOOL TUSBDevice::closeHidUSBDevice(HANDLE devHandle)
{
if((devHandle == 0) ||
(devHandle == INVALID_HANDLE_VALUE)){
return false;
}
else
return CloseHandle(devHandle);
}
//--------------------------------------------------------------
BOOL TUSBDevice::readUSBReport(UINT memberIndex)
{
DWORD result = 0;
DWORD numberOfBytesRead = 0;
OVERLAPPED *overlapped = NULL;
if(overlapped == NULL){
overlapped = new OVERLAPPED;
overlapped->hEvent = CreateEvent(NULL, TRUE, TRUE, "");
overlapped->Offset = 0;
overlapped->OffsetHigh = 0;
}
memset(deviceList[memberIndex].inputReportBuffer, 0x00,
deviceList[memberIndex].inputReportByteLength);
if(!ReadFile(deviceList[memberIndex].hidDeviceObject,
deviceList[memberIndex].inputReportBuffer,
deviceList[memberIndex].inputReportByteLength,
&numberOfBytesRead, overlapped)) {
if(GetLastError() == ERROR_IO_PENDING) {
result = WaitForSingleObject(overlapped->hEvent, INFINITE);
if(result == WAIT_TIMEOUT) {
CancelIo(deviceList[memberIndex].hidDeviceObject);
return false;
}
else
if(result == WAIT_FAILED){
displayError("Błąd odczytu danych.");
return false;
}
388 USB. Praktyczne programowanie z Windows API w C++
GetOverlappedResult(deviceList[memberIndex].hidDeviceObject,
overlapped, &numberOfBytesRead, FALSE);
}
else
displayError("Błąd odczytu danych.");
}
ResetEvent(overlapped->hEvent);
if(numberOfBytesRead == (UINT)deviceList[memberIndex].inputReportByteLength){
for(USHORT i=0; i< deviceList[memberIndex].inputReportByteLength; i++)
printf("%d ", deviceList[memberIndex].inputReportBuffer[i]);
printf("\n");
}
else {
printf("Błędna liczba odebranych bajtów.\n",numberOfBytesRead);
}
delete overlapped;
return true;
}
//--------------------------------------------------------------
void TThreadUSBPort::runThread() {
//Stworzenie anonimowego obiektu klasy TUSBDevice z aktualnym indeksem
//numeru interfejsu
TUSBDevice *usbDevice = new TUSBDevice(interfaceIndex);
usbDevice->setGetHidDeviceData();
usbDevice->openHidUSBDevice(interfaceIndex);
if (usbDevice->getHidDeviceCapabilities(interfaceIndex)) {
usbDevice->deviceList[interfaceIndex].inputReportBuffer =
(char*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,
usbDevice->deviceList[interfaceIndex].inputReportByteLength+1);
while(usbDevice->deviceList[interfaceIndex].inputReportBuffer[6]!=64){
usbDevice->readUSBReport(interfaceIndex);
}
usbDevice->closeHidUSBDevice(usbDevice->deviceList[interfaceIndex].\
hidDeviceObject);
}
releaseMemory(usbDevice->deviceList[interfaceIndex].Path);
releaseMemory(usbDevice->deviceList[interfaceIndex].inputReportBuffer);
//HeapFree(GetProcessHeap(),0,usbDevice->deviceList[interfaceIndex].\
// inputReportBuffer);
delete usbDevice;
}
//--------------------------------------------------------------
TThreadUSBPort *usbPortThread = NULL;
//--------------------------------------------------------------
int main()
{
usbPortThread = new TThreadUSBPort();
//Należy wpisać żądany indeks interfejsu dla wybranej konfiguracji
//testowanego urządzenia USB (patrz rozdział 6., rysunek 6.1)
usbPortThread->interfaceIndex = 1;
usbPortThread->beginThread();
Rozdział 10. ♦ Programy wielowątkowe 389
usbPortThread->resumeThread();
cout <<"Aby zakończyć odczyt, naciśnij przycisk [11] na konsoli"
" oraz naciśnij Enter...\n";
cin.get();
usbPortThread->closeHandle();
delete usbPortThread;
return 0;
}
//--------------------------------------------------------------
Rysunek 10.3.
Aplikacja
proj_USB_R10_1
w trakcie cyklicznego
odczytu raportu
wejściowego
Klasa TThread
Kolejnym sposobem stworzenia aplikacji wielowątkowej w C++ jest skorzystanie z kla-
sy wątku dziedziczącej po abstrakcyjnej klasie TThread.
class TThread : public System::TObject
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 egzemplarz klasy jest wykonywany jako oddzielny
wątek z własnym stosem.
Listing 10.3. Moduł usb_R10_2.h jako implementacja logicznej struktury klas i struktur z rysunku 10.3
#ifndef usb_R10_2H
#define usb_R10_2H
#include <assert>
#include <classes.hpp>
#define searchMaxDevices 10 //maksymalna liczba interfejsów urządzeń
using namespace std;
//--------------------------------------------------------------
390 USB. Praktyczne programowanie z Windows API w C++
USHORT NumberOutputValueCaps;
USHORT NumberOutputDataIndices;
USHORT NumberFeatureButtonCaps;
USHORT NumberFeatureValueCaps;
USHORT NumberFeatureDataIndices;
} HIDP_CAPS, *PHIDP_CAPS;
//--------------------------------------------------------------
typedef struct _DEVICE_DATA {
TCHAR *HardwareId;
TCHAR *Path;
DWORD DeviceInstance;
HANDLE hidDeviceObject;
BYTE *inputReportBuffer;
USHORT inputReportByteLength;
PHIDP_PREPARSED_DATA preparsedData;
HIDP_CAPS capabilities;
} DEVICE_DATA, *PDEVICE_DATA;
//--------------------------------------------------------------
class TUSBDevice {
private:
UINT fMemberIndex;
void displayError(const char* msg);
public:
PDEVICE_DATA deviceList;
BOOL getHidDeviceCapabilities(UINT memberIndex);
UINT setGetHidDeviceData();
HANDLE openHidUSBDevice(UINT memberIndex);
BOOL readUSBReport(UINT memberIndex);
BOOL closeHidUSBDevice(HANDLE devHandle);
TUSBDevice(UINT memberIndes);
~TUSBDevice();
};
//--------------------------------------------------------------
class TThreadUSBPort: public TThread
{
private:
TUSBDevice *usbDevice;
UINT *fRunThread;
UINT fMemberIndex;
protected:
void __fastcall Execute();
public:
TThreadUSBPort(BOOL CreateSuspended, UINT *runThread,
UINT memberIndex);
};
//--------------------------------------------------------------
#endif
//--------------------------------------------------------------
TUSBDevice::TUSBDevice(UINT memberIndex)
{
fMemberIndex = memberIndex;
deviceList = new \
DEVICE_DATA[((memberIndex+1)*sizeof(DEVICE_DATA))];
}
//--------------------------------------------------------------
TUSBDevice::~TUSBDevice()
{
releaseMemory(deviceList);
}
//--------------------------------------------------------------
void TUSBDevice::displayError(const char* msg)
{
cout << msg << endl;
system("PAUSE");
exit(0);
};
//--------------------------------------------------------------
BOOL TUSBDevice::getHidDeviceCapabilities(UINT memberIndex)
{
HMODULE hHidLib;
bool status;
hHidLib = LoadLibrary("C:\\Windows\\System32\\HID.DLL");
if (!hHidLib)
displayError("Błąd dołączenia biblioteki HID.DLL.");
//--------------------------------------------------------------
UINT TUSBDevice::setGetHidDeviceData()
{
DWORD propertyBufferSize = 0;
char *propertyBuffer = NULL;
HMODULE hHidLib;
SP_DEVINFO_DATA deviceInfoData;
HDEVINFO deviceInfoSet;
SP_INTERFACE_DEVICE_DATA deviceInterfaceData;
fMemberIndex = 0;
GUID classGuid;
PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData = NULL;
DWORD requiredSize = 0;
DWORD deviceInterfaceDetailDataSize = 0;
DWORD maxDevice = searchMaxDevices;
bool done = false;
hHidLib = LoadLibrary("C:\\Windows\\System32\\HID.DLL");
if (!hHidLib)
displayError("Błąd dołączenia biblioteki HID.DLL.");
while(!done) {
for(; fMemberIndex < maxDevice; fMemberIndex++) {
if(SetupDiEnumDeviceInterfaces(deviceInfoSet,0,&classGuid,
fMemberIndex,&deviceInterfaceData)) {
SetupDiGetDeviceInterfaceDetail(deviceInfoSet,&deviceInterfaceData,
NULL,0,&deviceInterfaceDetailDataSize,
NULL);
requiredSize = deviceInterfaceDetailDataSize;
deviceInterfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)\
new DWORD[deviceInterfaceDetailDataSize];
if(deviceInterfaceDetailData) {
deviceInterfaceDetailData->cbSize=\
sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA);
}
else {
SetupDiDestroyDeviceInfoList(deviceInfoSet);
releaseMemory(deviceInterfaceDetailData);
return 0;
}
deviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
if(!SetupDiGetDeviceInterfaceDetail(deviceInfoSet,
&deviceInterfaceData, deviceInterfaceDetailData,
deviceInterfaceDetailDataSize,
&requiredSize, &deviceInfoData)) {
394 USB. Praktyczne programowanie z Windows API w C++
SetupDiDestroyDeviceInfoList(deviceInfoSet);
releaseMemory(deviceInterfaceDetailData);
return 0;
}
deviceList[fMemberIndex].DeviceInstance = deviceInfoData.DevInst;
SetupDiGetDeviceRegistryProperty(deviceInfoSet, &deviceInfoData,
SPDRP_HARDWAREID, NULL, NULL, 0,
&propertyBufferSize);
SetupDiGetDeviceRegistryProperty(deviceInfoSet, &deviceInfoData,
SPDRP_HARDWAREID,NULL,
propertyBuffer, propertyBufferSize,
NULL);
deviceList[fMemberIndex].HardwareId = propertyBuffer;
cout <<"\nDeviceList["<<fMemberIndex<<"].HardwareId: \n" <<
deviceList[fMemberIndex].HardwareId << endl;
releaseMemory(deviceInterfaceDetailData);
releaseMemory(propertyBuffer);
}
else {
if(ERROR_NO_MORE_ITEMS == GetLastError()){
done = TRUE;
break;
}
}
}
}
SetupDiDestroyDeviceInfoList(deviceInfoSet);
FreeLibrary(hHidLib);
return fMemberIndex;
}
//--------------------------------------------------------------
HANDLE TUSBDevice::openHidUSBDevice(UINT memberIndex)
{
deviceList[memberIndex].hidDeviceObject == INVALID_HANDLE_VALUE;
deviceList[memberIndex].hidDeviceObject =
CreateFile(deviceList[memberIndex].Path,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,OPEN_EXISTING,FILE_FLAG_OVERLAPPED,NULL);
if(deviceList[memberIndex].hidDeviceObject != INVALID_HANDLE_VALUE)
return deviceList[memberIndex].hidDeviceObject;
else
return INVALID_HANDLE_VALUE;
}
Rozdział 10. ♦ Programy wielowątkowe 395
//--------------------------------------------------------------
BOOL TUSBDevice::closeHidUSBDevice(HANDLE devHandle)
{
if((devHandle == 0) ||
(devHandle == INVALID_HANDLE_VALUE)){
return false;
}
else
return CloseHandle(devHandle);
}
//--------------------------------------------------------------
BOOL TUSBDevice::readUSBReport(UINT memberIndex)
{
DWORD result = 0;
DWORD numberOfBytesRead = 0;
OVERLAPPED *overlapped = NULL;
if(overlapped == NULL){
overlapped = new OVERLAPPED;
overlapped->hEvent = CreateEvent(NULL, TRUE, TRUE, "");
overlapped->Offset = 0;
overlapped->OffsetHigh = 0;
}
memset(deviceList[memberIndex].inputReportBuffer, 0x00,
deviceList[memberIndex].inputReportByteLength);
if(!ReadFile(deviceList[memberIndex].hidDeviceObject,
deviceList[memberIndex].inputReportBuffer,
deviceList[memberIndex].inputReportByteLength,
&numberOfBytesRead, overlapped)) {
if(GetLastError() == ERROR_IO_PENDING) {
result = WaitForSingleObject(overlapped->hEvent, 100);
if(result == WAIT_TIMEOUT) {
CancelIo(deviceList[memberIndex].hidDeviceObject);
return false;
}
else
if(result == WAIT_FAILED){
displayError("Błąd odczytu danych.");
return false;
}
GetOverlappedResult(deviceList[memberIndex].hidDeviceObject,
overlapped, &numberOfBytesRead, FALSE);
}
else
displayError("Błąd odczytu danych.");
}
ResetEvent(overlapped->hEvent);
if(numberOfBytesRead == (UINT)deviceList[memberIndex].inputReportByteLength){
for(USHORT i=0; i< deviceList[memberIndex].inputReportByteLength; i++)
printf("%d ", deviceList[memberIndex].inputReportBuffer[i]);
printf("\n");
}
else {
printf("Błędna liczba odebranych bajtów.\n",numberOfBytesRead);
}
delete overlapped;
return true;
}
//--------------------------------------------------------------
396 USB. Praktyczne programowanie z Windows API w C++
Podsumowanie
Niniejszy rozdział był poświęcony przedstawieniu 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 CreateThread(). W dalszej części rozdziału przedstawiono jeden ze
sposobów posługiwania się w programie klasą TThread.
Ćwiczenia
Ćwiczenie 10.1
Semafor (ang. semaphore) działa jak bramka kontrolująca liczbę wątków wykonują-
cych dany fragment kodu. Nowy semafor jest tworzony w funkcji:
HANDLE CreateSemaphore(IN LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
IN LONG lInitialCount,
IN LONG lMaximumCount,
IN LPCTSTR lpName);
// 2. do odbioru danych
hThread[1] = CreateThread(NULL, 0,
(LPTHREAD_START_ROUTINE)threadFuncReceive,
&hSemaphore, 0, &threadID2);
ReleaseSemaphore(hSemaphore, 1, NULL);
WaitForMultipleObjects(2, hThread, TRUE, 10);
CloseHandle(hSemaphore);
CloseHandle(hThread[0]);
CloseHandle(hThread[1]);
HeapFree(GetProcessHeap(),0,bufferIn);
cin.get();
return 0;
}
//----------------------------------------------------
unsigned long threadFuncSend(void* parameter)
{
void* hSemaphore =
OpenSemaphore(SEMAPHORE_ALL_ACCESS, 1,
"FILE_EXISTS");
WaitForSingleObject(hSemaphore, INFINITE);
//Wysyłanie danych do urządzenia wykonawczego
ReleaseSemaphore(hSemaphore ,1 ,NULL);
return TRUE;
}
//----------------------------------------------------
unsigned long threadFuncReceive(void* parameter)
{
void* hSemaphore =
OpenSemaphore(SEMAPHORE_ALL_ACCESS, 1,
"FILE_EXISTS");
WaitForSingleObject(hSemaphore, INFINITE);
//Odbiór danych pochodzących z urządzenia wykonawczego
ReleaseSemaphore(hSemaphore, 1,NULL);
return TRUE;
}
//----------------------------------------------------
Ćwiczenie 10.2
Wzajemne wykluczenie (ang. mutual exclusion) jest sekcją krytyczną, która może być
współdzielona przez wiele procesów i może działać pomiędzy wieloma procesami.
Programy próbują tworzyć wzajemne wykluczenie pod określoną nazwą lpName, wy-
korzystując w tym celu funkcję API Windows:
HANDLE WINAPI CreateMutex(
IN LPSECURITY_ATTRIBUTES lpMutexAttributes,
IN BOOL bInitialOwner,
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. Na 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 jest wykonywane poprzez funkcję API:
BOOL ReleaseMutex(IN HANDLE hMutex);
CloseHandle(hMutex);
CloseHandle(hThread[0]);
CloseHandle(hThread[1]);
HeapFree(GetProcessHeap(),0,bufferIn);
cin.get();
return 0;
}
//---------------------------------------------------
unsigned long threadFuncSend(void* parameter)
{
void* hMutex = ¶meter;
WaitForSingleObject(hMutex, INFINITE);
//Wysyłanie danych do urządzenia wykonawczego
ReleaseMutex(hMutex);
return TRUE;
}
//----------------------------------------------------
400 USB. Praktyczne programowanie z Windows API w C++
Ćwiczenie 10.3
Jednym z zasadniczych problemów programowania wielowątkowego jest konieczność
zachowania integralności danych. Kiedy zachodzi potrzeba ochrony dostępu do bi-
bliotek komponentów wizualnych, można użyć metody Synchronize() klasy TThread.
Przyjmuje ona bezparametrową 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-
musza na wątku głównym wywołanie żądanej funkcji. Po jej zakończeniu kontrola
jest przekazywana z powrotem do bieżącego wątku. Wszystkie 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ś muszą 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 pomocą funkcji Synchronize(). Przykłady wy-
korzystania funkcji Synchronize() chroniącej dostęp do bibliotek komponentów wi-
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. Współ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ę stanowią 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.
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średnictwem 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 konwertera 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.
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 pozwalają 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
zadeklarowanych w systemie 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
Rysunek 11.4.
Ustawienia portu
adaptera
404 USB. Praktyczne programowanie z Windows API w C++
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-
temach pomiarowych.
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. Na 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
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ą-
dzeniami Bluetooth pochodzącymi od różnych producentów. Urządzenia Bluetooth
mogą współpracować ze sobą jedynie w obrębie wspólnych profili. Profile Bluetooth
406 USB. Praktyczne programowanie z Windows API w C++
Ważną cechą standardu Bluetooth jest to, że oprócz aplikacji dedykowanych na po-
trzeby technologii istnieje możliwość korzystania ze starszego oprogramowania kon-
struowanego pod kątem korzystania z mechanizmów transmisji danych różnych niż
Bluetooth. Jest to możliwe dzięki zdefiniowaniu przez SIG takich protokołów jak
RFCOMM (ang. Radio Frequency Communication). W praktyce oznacza to, że pro-
gramy pierwotnie zaprojektowane do pracy z wykorzystaniem standardowego portu
szeregowego będą mogły po niewielkich modyfikacjach korzystać z Bluetooth dzięki
oferowanym przez protokół RFCOMM mechanizmom emulacji portu szeregowego
(podobnie jak w przypadku klasycznego adaptera USB/RS 232C).
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
jest obsługiwana przez urządzenie, czy też nie.
Składnia: AT<polecenie>=?
Polecenia typu Read (zapytania) — służą do uzyskiwania informacji na temat
aktualnych ustawień urządzenia zewnętrznego.
Składnia: AT<polecenie>?
Polecenia typu Set (zestawy poleceń) — służą do modyfikowania wybranych
parametrów ustawień urządzenia zewnętrznego.
Składnia: AT<polecenie>=wartość1, wartość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<polecenie>=parametr1, parametr2, …, parametrN
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 <iostream>
#include <windows>
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;
//text = "ATI\r"; //przykładowe komendy AT
text = "AT+CCLK?\r";
strcpy(bufferIn, text);
writeSerialPort(bufferIn, strlen(bufferIn));
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 jest 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 409
dcb.DCBlength = sizeof(dcb);
GetCommState(hCommDev, &dcb);
dcb.BaudRate =BaudRate;
dcb.ByteSize = ByteSize;
dcb.Parity =Parity ;
dcb.StopBits =StopBits;
dcb.fBinary=true;
dcb.fParity=fParity;
//...
if(SetCommState(hCommDev, &dcb) == 0){
cout << "Błąd wykonania funkcji SetCommState().\n";
CloseHandle(hCommDev);
return false;
}
return true;
}
//---------------------------------------------------------
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.ReadTotalTimeoutMultiplier = ReadTotalTimeoutMultiplier;
commTimeouts.WriteTotalTimeoutConstant = WriteTotalTimeoutConstant;
commTimeouts.WriteTotalTimeoutMultiplier = WriteTotalTimeoutMultiplier;
if (SetCommTimeouts(hCommDev, &commTimeouts) == 0) {
cout << "Błąd wykonania funkcji SetCommTimeouts().\n";
CloseHandle(hCommDev);
return false;
}
return true;
}
//---------------------------------------------------------
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;
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;
return numberOfBytesRead;
}
//---------------------------------------------------------
void closeSerialPort()
{
if (CloseHandle(hCommDev))
cout << "\n\nPort został zamknięty do transmisji.\n\n";
return;
}
//---------------------------------------------------------
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?
NULL, GetLastError(), 0,
(LPTSTR) &lpMsgBuf, 0, NULL );
fprintf(stderr, "\n%s\n", lpMsgBuf);
free(lpMsgBuf);
cin.get();
}
//---------------------------------------------------------
int main() {
WORD wVersionRequested;
WSADATA wsaData;
int result;
//inicjalizacja 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 <numer>[;] — 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);
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). Dla tego typu adapterów dostępne są sterowniki zarówno w wersjach
PnP, jak i standardowych, dzięki czemu spełniają one wymagania wszystkich typo-
wych aplikacji.
414 USB. Praktyczne programowanie z Windows API w C++
Literatura
[1] Axelson J., USB Complete, Everything You Need to Develop Custom USB
Peripherals, Lakeview Research, Madison 2005.
[2] Axelson J., USB Complete: The Developer’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 of Patterns, John Wiley and Sons, Chichester 1996.
[5] Cant C., Writing Windows WDM Device Drivers, CMP, London 1999.
[6] Daniluk A., RS 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
programisty, Helion, Gliwice 2006.
[8] Fowler M., Analysis Patterns. Reusable Object Models, 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 Host Controller Interface for Universal Serial
Bus (xHCI), 2010.
[12] Koeman K., Universal Serial Bus. Handling The IRP_MN_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 Modeling Language Reference
Manual, 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++
B D
bezprzewodowa transmisja danych, 407 delegowanie interfejsu, 346
biblioteka deskryptory
HID.dll, 144 interfejsów urządzeń, 95
KMDF, 272 koncentratorów, 84
LibUSB, 289, 291 konfiguracji, 100
kody rozkazów, 296 punktu końcowego, 89
typy adresatów, 297 raportu, 111, 113
typy żądań, 297 tekstowe, 104
libusb0.dll, 291 urządzeń, 80
Setupapi, 182 detekcja
STL, 8 interfejsów urządzeń, 157
Usbdex.lib, 107 ścieżek, 181
WinSock, 411 urządzeń, 149
WinUSB, 271, 278 diagram
winusb.dll, 271 czynności, 157, 195
bity identyfikatora pakietu, 29 klas, 337, 367, 382
Bluetooth, 405 komponentów, 335
błąd naruszenia pamięci, 156 programu proceduralnego, 352
bufor z danymi sekwencji, 349
wejściowymi, 110 DisplayPort, 12
wyjściowymi, 109 długość bufora danych, 203
418 USB. Praktyczne programowanie z Windows API w C++
J M
jednostki miar, 115 magazyn certyfikatów, 275
magistrala GPIB, 404
makrodefinicja CTL_CODE, 237
K menedżer
kabel certmgr, 274
USB 2.0, 14 urządzeń, 59, 62, 108
USB 3.0, 15 MI, Multiple Interfaces, 98
klasa mikroramka, 24
TButton, 224 mikrozłącza USB 3.0, 21
TForm1, 220 ministerownik, minidriver, 105
Thread, 382 model
TInterfacedObject, 330 ISO OSI, 73
TProgressBar, 224 logiczny urządzenia, 303
TThread, 389 realizacji interfejsów, 320
TTrackBar, 224 warstwowy sterowników, 106, 277, 291
TUSBDetect, 337 modele architektury, 77
TUSBDevice, 308, 315, 359 moduł
klasy cfgmgr32.h, 176
instalacji urządzeń, 58 cstring.h, 199
urządzeń, 60, 67 hidclass.h, 242
klucz HKEY_LOCAL_MACHINE, 64 hidusage.h, 112
KMDF, Kernel-Mode Driver Framework, 105, setupapi.h, 151
271 system.hpp, 326, 381
kod BCD, 83 usb.h, 79, 98
komenda usb100.h, 81, 86, 93, 99
AT, 406 USBDetect.cpp, 338
AT+CCLK?, 411 usbdlib.h, 267
ATI, 411 usbioctl.h, 85, 88, 103, 245
komentarze, 70 usbiodef.h, 174
komponenty wizualne, 336 usbspec.h, 81, 83, 87, 94
komunikacja programu z urządzeniem, 104 usbtypes.h, 90, 96, 97, 100, 102
komunikat o odłączeniu, 188 windows.h, 186
koncentrator, 84 MTP, Media Transfer Protocol, 278
USB, 247
USB 3.0, 18, 84 N
konfiguracja urządzeń USB, 75, 100
nazwa symboliczna urządzenia, 65, 192
nazwy zmiennych, 70
L numeracja styków
linie transmisyjne, 18 USB 2.0 typu A i B, 14
lista interfejsów, 367 USB 2.0 typu Micro-A i Micro-B, 21
logiczna struktura typów danych, 254, 259, 261 USB 2.0 typu Mini-A i Mini-B, 20
LPT, 11 USB 3.0 typu A i B, 16
LS, Low Speed, 12 USB 3.0 typu Micro-A, 22
USB 3.0 typu Micro-B, 22
USB 3.0 typu Powered-B, 19
Ł
łącza szeregowe, 12
Skorowidz 421
USBD_INTERFACE_INFORMATION, 97 typy
USBD_PIPE_INFORMATION, 79 danych, 78
wielowątkowego programu, 390 sterowników, 105
WINUSB_PIPE_INFORMATION, 287 transferów, 25, 34
WINUSB_SETUP_PACKET, 281 urządzeń USB, 177
struktury wtyczek i gniazd, 23
danych, 168, 351
logiczne programu obiektowego, 360
logiczne urządzenia, 80 U
suma kontrolna pakietu, 30 UMDF, User-Mode Driver Framework, 105, 271
szybkość transferu danych, 12, 18 UNC, Universal Naming Convention, 192
unia USB_HUB_CAP_FLAGS, 88
Ś urządzenia, 57, 68
klasy HID, 111
środowisko graficzne, 366 PnP, 68, 152
winusb, 274
T xHCI, 108
urządzenie
Thunderbolt, 11 libusb, 291
topologia USBDevice, 335
magistrali USB, 76 USB 1.0, 7
systemu USB 3.0, 85 USB 2.0, 7
transakcje USB 3.0, 7, 11
dzielone, split transactions, 38 USB OTG, 7, 20
izochroniczne, isochronous transactions, 36 ustawienia portu adaptera, 403
kontrolne, control transactions, 36
masowe, bulk transactions, 33
przerwaniowe, interrupt transactions, 35 V
USB 2.0, 33 VID, Vendor ID, 64
transfer
izochroniczny, isochronous transfer, 27
kontrolny, control transfer, 28 W
masowy, bulk transfer, 25, 300
przerwaniowy, interrupt transfer, 26, 301 warstwa
transmisja fizyczna, physical layer, 74, 76
asynchroniczna, 407 funkcjonalna, 73
bezprzewodowa, 407 logiczna, 75
izochroniczna, 11, 36 łącza, link layer, 76
szeregowa, 24, 401 protokołu, protocol layer, 76
tryb pracy wątek, thread, 379
asynchroniczny, 241 WDF, Windows Driver Foundation, 271
synchroniczny, 241 WDK, Windows Driver Kit, 8, 79, 83, 151
tworzenie WDM, Windows Driver Model, 105
certyfikatu, 274 węzeł, node, 75
komponentu, 335 wirtualny port szeregowy, 407
magazynu certyfikatów, 274 wizualizacja danych, 366
obiektu klasy TUSBDevice, 366 właściwości portu adaptera, 402
pakietu instalacyjnego, 272, 290 wymiana informacji, 73
pliku .cat, 275 wyprowadzenia w złączach
typ wyliczeniowy USB 2.0 Mini/Micro A i B, 20
HIDP_REPORT_TYPE, 126 USB 2.0 typu A i B, 15
USB_DEVICE_SPEED, 83 USB 3.0 Micro-A/AB, 21
USB_HUB_TYPE, 84 USB 3.0 Micro-B, 23
USBD_PIPE_TYPE, 78 USB 3.0 Powered-B, 18
424 USB. Praktyczne programowanie z Windows API w C++