You are on page 1of 426

Rozdział 4.

♦ Urządzenia klasy HID 129

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
);

Parametr ReportType wskazuje typ wyliczeniowy zawierający dane na temat rodza-


ju raportu. Wskaźnik Report wskazuje bufor danych z raportem odpowiedniego ty-
pu. Użyty jako parametr wejściowy wskaźnik DataLength wskazuje daną zawierającą
rozmiar tablicy struktur. Wymagany rozmiar tablicy można określić za pomocą funk-
cji HidP_MaxDataListLength(). Użyty jako parametr wyjściowy wskaźnik DataLength
wskazuje daną zawierającą liczbę elementów o charakterze dyskretnym w stanie ON
i liczbę elementów o charakterze wartości ciągłych. Wskaźnik PreparsedData wska-
zuje strukturę HIDP_PREPARSED_DATA. Parametr ReportLength określa rozmiar raportu
(w bajtach) wskazywanego przez Report. Powinien być równy długości raportu wy-
specyfikowanego przez funkcję HidP_GetCaps().
//---------------------------------------------------------
PHIDP_DATA DataList;
//...
int main() {
//...
DataList = new HIDP_DATA[dataListLength];
while(true) { //cykliczny odczyt raportu wejściowego
memset(inputReportBuffer, 0x00, capabilities.InputReportByteLength);
ReadFile(/*...*/, inputReportBuffer, capabilities.InputReportByteLength,
&numberOfBytesRead, NULL);

printf("%d %d %d %d %d %d %d\n",
inputReportBuffer[0],inputReportBuffer[1],inputReportBuffer[2],
inputReportBuffer[3],inputReportBuffer[4],inputReportBuffer[5],
inputReportBuffer[6]);

HidP_GetData(HidP_Input, DataList, &dataListLength, preparsedData,


inputReportBuffer, capabilities.InputReportByteLength);
130 USB. Praktyczne programowanie z Windows API w C++

printf("DataIndex=%d\n",DataList[5].DataIndex);
printf("RawValue=%d\n",DataList[5].RawValue);
printf("\n");
//...
//---------------------------------------------------------

Prawidłowo wykonana funkcja HidP_GetData() zwraca wartość o statusie HIDP_STATUS_


SUCCESS.

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
);

Parametr ReportType wskazuje typ wyliczeniowy zawierający dane na temat rodzaju


raportu. Parametr DataIndex jest elementem struktury HIDP_DATA. Wskaźnik Preparsed-
Data wskazuje strukturę HIDP_PREPARSED_DATA. Na wejściu LengthAttributes powinien
wskazywać daną o wartości większej lub równej sizeof(HIDP_EXTENDED_ATTRIBUTES).
Na wyjściu parametr LengthAttributes określa rozmiar bufora danych (w bajtach).

Prawidłowo wykonana funkcja HidP_GetExtendedAttributes() zwraca wartość o sta-


tusie HIDP_STATUS_SUCCESS.

Funkcja HidP_GetLinkCollectionNodes()
Abstrakcyjna kolekcja łączy (ang. link collection) w ramach raportu wprowadza orga-
nizacyjną hierarchię porządkującą elementy kontrolne urządzenia w odpowiednich gru-
pach. Każde takie abstrakcyjne łącze jest reprezentowane przez strukturę HIDP_LINK_
COLLECTION_NODE. Na przykład w konsolach gier typu 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
);

Wskaźnik LinkCollectionNodes wskazuje tablicę struktur.


typedef struct _HIDP_LINK_COLLECTION_NODE {
USAGE LinkUsage;
Rozdział 4. ♦ Urządzenia klasy HID 131

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;

Użyty jako wejściowy parametr LinkCollectionNodesLength określa rozmiar tablicy,


a na wyjściu wskazuje liczbę elementów tablicy. Rozmiar bufora może zostać okre-
ślony za pomocą pola NumberLinkCollectionNodes struktury HIDP_CAPS. Wskaźnik
PreparsedData wskazuje strukturę HIDP_PREPARSED_DATA.
//---------------------------------------------------------
PHIDP_LINK_COLLECTION_NODE linkCollectionNodes;
//...
int main(){
//...
PULONG linkCollectionNodesLength;
linkCollectionNodes = new \
HIDP_LINK_COLLECTION_NODE[capabilities.NumberLinkCollectionNodes];
HidP_GetLinkCollectionNodes(linkCollectionNodes,
linkCollectionNodesLength,
preparsedData);
printf("LinkUsage=%d\nLinkUsagePage=%d\nParent=%d\n"
"NumberOfChildren=%d\nCollectionType=%d\n\n",
linkCollectionNodes->LinkUsage,
linkCollectionNodes->LinkUsagePage,
linkCollectionNodes->Parent,
linkCollectionNodes->NumberOfChildren,
linkCollectionNodes->CollectionType,/*...*/);
//...
//---------------------------------------------------------

Prawidłowo wykonana funkcja HidP_GetLinkCollectionNodes() zwraca wartość o sta-


tusie HIDP_STATUS_SUCCESS.

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++

OUT PLONG UsageValue,


IN PHIDP_PREPARSED_DATA PreparsedData,
IN PCHAR Report,
IN ULONG ReportLength
);

Wskaźnik ReportType wskazuje typ wyliczeniowy HIDP_REPORT_TYPE. Parametr UsagePage


jest identyfikatorem grupy urządzeń. Opcjonalny parametr LinkCollection określa
kryterium wyszukiwania w obrębie deskryptora. Wartość zero oznacza urządzenia
klasy HID. Wskaźnik Report wskazuje konkretny typ raportu, którego długość określa
ReportLength.

Poprawnie wykonana funkcja HidP_GetScaledUsageValue() zwraca wartość o statusie


HIDP_STATUS_SUCCESS. Dla nieprawidłowo określonej wartości logicznej lub fizycznej
funkcja zwraca HIDP_STATUS_BAD_LOG_PHY_VALUES.

Funkcja HidP_GetSpecificButtonCaps()
Funkcja wydobywa wartości binarne, które są częścią jednego z raportów: HidP_Input,
HidP_Output lub HidP_Feature.
NTSTATUS __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

printf("Data index min, max %d..%d\n\n", buttonCaps[i].Range.DataIndexMin,


buttonCaps[i].Range.DataIndexMax);
}
//...
//---------------------------------------------------------

Poprawnie wywołana funkcja HidP_GetSpecificButtonCaps() zwraca wartości o sta-


tusie HIDP_STATUS_SUCCESS. Jej wywołanie z wartościami zerowymi dla UsagePage,
Usage i LinkCollection jest tożsame z użyciem funkcji HidP_ButtonCaps().

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);
}
//---------------------------------------------------------

Poprawnie użyta funkcja HidP_GetSpecificValueCaps() zwraca wartość o statusie


HIDP_STATUS_SUCCESS.
134 USB. Praktyczne programowanie z Windows API w C++

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
);

Wskaźnik ReportType wskazuje typ wyliczeniowy HIDP_REPORT_TYPE. Parametr UsagePage


jest identyfikatorem grupy urządzeń. Parametr LinkCollection określa kryterium wy-
szukiwania. Wskaźnik Report wskazuje konkretny typ raportu, który zawiera żądaną
wartość, a jego rozmiar określa ReportLength. Wskaźnik UsageValue wskazuje bufor
danych wyjściowych.

Prawidłowo wykonana funkcja HidP_GetUsageValue() zwraca wartość o statusie HIDP_


STATUS_SUCCESS.

Funkcja HidP_GetUsageValueArray()
Funkcja wydobywa dane z raportu składającego się z więcej niż jednego pola.
NTSTATUS __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.

Prawidłowo wykonana funkcja HidP_GetUsageValueArray() zwraca wartość o statusie


HIDP_STATUS_SUCCESS.
Rozdział 4. ♦ Urządzenia klasy HID 135

Funkcja HidP_GetUsages()
Funkcja zwraca liczbę dyskretnych elementów sterujących pozostających w stanie ON.
NTSTATUS __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ść.

Aplikacje działające w trybie użytkownika i aplikacje działające w trybie jądra syste-


mu powinny się posługiwać funkcją HidP_MaxUsageListLength() do określenia maksy-
malnej liczby elementów sterujących o charakterze dyskretnym, występujących w ra-
mach określonego typu raportu. W celu prawidłowego określenia wartości UsageLength
można użyć funkcji HidP_GetButtons() z wyzerowanym argumentem UsageList. Ste-
rowniki urządzeń używają pola XXXReportByteLength struktury HIDP_CAPS do określe-
nia długości raportu.
//---------------------------------------------------------
USAGE *UsageList = new USAGE[usageListLength];
//...
ReadFile(/*...*/, inputReportBuffer, capabilities.InputReportByteLength,
&numberOfBytesRead, NULL);
//...
HidP_GetUsages(HidP_Input, 0, 0, UsageList, &usageListLength,
preparsedData, inputReportBuffer,
capabilities.InputReportByteLength);
printf("Liczba wciśniętych przycisków (w stanie ON)=%d\n ",
usageListLength);
//...
//---------------------------------------------------------

Poprawnie użyta funkcja HidP_GetUsages() zwraca wartość o statusie HIDP_STATUS_


SUCCESS.

Funkcja HidP_GetUsagesEx()
Funkcja wydobywa identyfikatory UsagePage i Usage dla wszystkich dyskretnych ele-
mentów sterujących urządzenia pozostających w stanie ON.
136 USB. Praktyczne programowanie z Windows API w C++

NTSTATUS __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
);

Wskaźnik ReportType wskazuje typ wyliczeniowy HIDP_REPORT_TYPE. Wskaźnik But-


tonList wskazuje tablicę struktur:
typedef struct _USAGE_AND_PAGE {
USAGE Usage;
USAGE UsagePage;
} USAGE_AND_PAGE, * PUSAGE_AND_PAGE;

— poprzez którą funkcja zwraca identyfikatory Usage i UsagePage dla przycisków


w stanie ON. Użyty jako parametr wejściowy wskaźnik UsageLength wskazuje rozmiar
tablicy struktur, a jako parametr wyjściowy — liczbę elementów w stanie ON w ra-
porcie danych. Maksymalny rozmiar tablicy może zostać określony za pomocą funk-
cji HidP_MaxUsageListLength(). Wskaźnik PreparsedData wskazuje strukturę HID_
PREPARSED_DATA. Wskaźnik Report wskazuje raport danych o długości ReportLength.
//---------------------------------------------------------
USAGE_AND_PAGE *Usages = new USAGE_AND_PAGE[usageListLength];
while(true) { //cykliczny odczyt danych z wybranego urządzenia
memset(inputReportBuffer, 0x00, capabilities.InputReportByteLength);
ReadFile(/*...*/, inputReportBuffer, capabilities.InputReportByteLength,
&numberOfBytesRead, NULL);
if(numberOfBytesRead==capabilities.InputReportByteLength){
for(USHORT i=0; i<capabilities.InputReportByteLength; i++)
printf("%d ", inputReportBuffer[i]);
printf("\n");

ULONG validUsages = usageListLength;


HidP_GetUsagesEx(HidP_Input, 0, Usages, &validUsages,
preparsedData, inputReportBuffer,
capabilities.InputReportByteLength);

// stan aktualnie używanych przycisków


printf("Zbiór przycisków:\n ");
for(ULONG i=0; i<validUsages; i++) {
printf("UsagePage=%d Usage(Wciśnięty przycisk nr)=%d\n ",
Usages[i].UsagePage, Usages[i].Usage);
}
//...
}
//---------------------------------------------------------

Prawidłowo użyta funkcja HidP_GetUsagesEx() zwraca wartość o statusie HIDP_STATUS_


SUCCESS.
Rozdział 4. ♦ Urządzenia klasy HID 137

Funkcja HidP_GetValueCaps()
Funkcja 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
);

Parametr wejściowy ReportType określa rodzaj raportu. Typy raportów są przechowy-


wane w elementach typu wyliczeniowego HIDP_REPORT_TYPE (patrz tabela 4.5). Jeżeli
ValueCapsLength jest użyty jako parametr wejściowy, to wskazuje rozmiar bufora da-
nych; a jeżeli jako parametr wyjściowy — liczbę danych aktualnie zwracanych przez
funkcję. Parametr ValueCapsLength może być określany przez pole NumberXxxValueCaps
struktury HID_CAPS. Wskaźnik PreparsedData wskazuje strukturę HIDP_PREPARSED_DATA.
//---------------------------------------------------------
if(HidD_GetPreparsedData(hidDeviceObject, &preparsedData)){
HidP_GetCaps(preparsedData, &capabilities);
//...
PULONG valueCapsLength;
HIDP_VALUE_CAPS *valueCaps = new \
HIDP_VALUE_CAPS[capabilities.NumberOutputValueCaps];
HidP_GetValueCaps(HidP_Output, valueCaps, valueCapsLength, preparsedData);
for(USHORT i = 0; i<capabilities.NumberOutputValueCaps;i++) {
printf("ValueCaps[%d].UsagePage %x\n", i, valueCaps[i].UsagePage);
printf("ValueCaps[%d].ReportID %x\n", i, valueCaps[i].ReportID);
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", i, valueCaps[i].ReportCount);
printf("ValueCaps[%d].UnitsExp %d\n\n", i, valueCaps[i].UnitsExp);
//...
}
//...
//---------------------------------------------------------

Poprawnie wywołana funkcja HidP_GetValueCaps() zwraca wartość o statusie HIDP_


STATUS_SUCCESS.

Funkcja HidP_InitializeReportForID()
Funkcja inicjalizuje raport dla urządzeń klasy HID. Oznacza to, że wszystkie dane bę-
dące elementami raportu (zamieszczone w odpowiednich polach raportu) zostaną 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
);

Parametr wejściowy ReportType określa typ raportu wskazywanego przez wskaźnik


Report. Parametr wejściowy ReportID określa identyfikator raportu. Wskaźnik Pre-
parsedData wskazuje strukturę HIDP_PREPARSED_DATA. Parametr ReportLength określa
rozmiar w bajtach raportu wskazywanego przez Report.

Prawidłowo użyta funkcja HidP_InitializeReportForID() zwraca wartość o statusie


HIDP_STATUS_SUCCESS.

Funkcja HidP_MaxDataListLength()
Funkcja wydobywa maksymalny rozmiar tablicy struktur HIDP_DATA zwracanych przez
funkcję HidP_GetData() dla wyspecyfikowanego rodzaju raportu identyfikowanego
przez parametr ReportType.
ULONG 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
);

Wartość parametru UsagePage stanowi kryterium wyszukiwania. Jeżeli UsagePage=HID_


USAGE_PAGE_UNDEFINED, funkcja zwróci liczbę przycisków w urządzeniu obsługujących
dany typ raportu.
//--------------------------------------------------
#define HID_USAGE_PAGE_BUTTON ((USAGE) 0x09) //hidusage.h
//...
USAGE UsagePage = HID_USAGE_PAGE_BUTTON;
Rozdział 4. ♦ Urządzenia klasy HID 139

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
);

Wskaźnik DataList wskazuje tablicę struktur HIDP_DATA. Parametr wejściowy DataLength


określa liczbę elementów w tablicy wskazywanej przez DataList. Parametr ReportLength
określa rozmiar (długość) w bajtach raportu wskazywanego przez Report. Rozmiar ra-
portu powinien odpowiadać wartości zwracanej przez funkcję HidP_GetCaps().

Poprawnie użyta funkcja HidP_SetData() zwraca wartość o statusie HIDP_STATUS_SUCCESS.

Funkcja HidP_SetButtons()
Funkcja HidP_SetButtons() 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
);

Poprawnie użyta funkcja HidP_SetScaledUsageValue() zwraca wartość o statusie HIDP_


STATUS_SUCCESS.
140 USB. Praktyczne programowanie z Windows API w C++

Funkcja HidP_SetUsageValue()
W raporcie 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
);

Poprawnie użyta funkcja HidP_SetUsageValue() zwraca wartość o statusie HIDP_STATUS_


SUCCESS.

Funkcja HidP_SetUsageValueArray()
Funkcja umieszcza w buforze wskazywanym przez UsageValue (o rozmiarze 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
);

Poprawnie użyta funkcja HidP_SetUsageValueArray zwraca wartość o statusie HIDP_


STATUS_SUCCESS.

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

IN OUT PCHAR Report,


IN ULONG ReportLength
);

Parametr ReportType określa typ raportu (HidP_Output lub HidP_Feature). Wskaźnik


Report wskazuje bufor danych z raportem. Parametr ReportLength określa w bajtach
długość raportu, która powinna być równa analogicznej wartości zwracanej przez funk-
cję HidP_GetCaps(). Wskaźnik UsageList wskazuje tablicę danych typu USAGE. Aplikacje
działające w trybie użytkownika i aplikacje działające w trybie jądra systemu powinny
się posługiwać funkcją HidP_MaxUsageListLength() do określenia maksymalnej liczby
elementów sterujących lub kontrolnych występujących w określonym typie raportu.
//---------------------------------------------------------
//...
#define HID_USAGE_PAGE_LED ((USAGE) 0x08) //hidusage.h
#define HID_USAGE_LED_NUM_LOCK ((USAGE) 0x01)
#define HID_USAGE_LED_CAPS_LOCK ((USAGE) 0x02)
#define HID_USAGE_LED_SCROLL_LOCK ((USAGE) 0x03)
//...
char* outputReportBuffer = new char[capabilities.OutputReportByteLength];
assert(outputReportBuffer!=NULL);
memset(outputReportBuffer, 0, capabilities.OutputReportByteLength);
//...
USAGE UsageList[usageListLength];

USAGE Usage1 = HID_USAGE_LED_NUM_LOCK;


USAGE Usage2 = HID_USAGE_LED_CAPS_LOCK;
USAGE Usage3 = HID_USAGE_LED_SCROLL_LOCK;
USAGE Usage4 = /*...*/;
//...
UsageList[0] = Usage1;
UsageList[1] = Usage2;
UsageList[2] = Usage3;
UsageList[3] = Usage4;
//...
ULONG UsageLength = 0;

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++

Poprawnie użyta funkcja HidP_SetUsages() zwraca wartość o statusie HIDP_STATUS_


SUCCESS.

Funkcja HidP_UnsetUsages()
Funkcja umieszcza wartość binarną w stanie OFF w raporcie wyspecyfikowanym przez
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().

Poprawnie użyta funkcja HidP_UnsetUsages() zwraca wartość o statusie HIDP_STATUS_


SUCCESS.

Funkcja HidP_UsageListDifference()
Funkcja wydobywa różnice między zawartościami dwóch tablic przechowujących wła-
ściwości elementów kontrolnych urządzenia. W ten sposób można porównać zmiany
stanu przycisków sterujących podczas kolejnych 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
);

Wskaźnik PreviousUsageList wskazuje tablicę danych, której elementy funkcja porówna


z elementami tablicy wskazywanej przez CurrentUsageList. Parametr BreakUsageList
wskazuje bufor danych, który na wyjściu zawiera listę PreviousUsageList. Wskaźnik
MakeUsageList wskazuje bufor danych, który zawiera listę CurrentUsageList. Parametr
UsageListLength określa rozmiar tablic danych.
//---------------------------------------------------------
USAGE_AND_PAGE *Usages = new USAGE_AND_PAGE[usageListLength];
const ULONG maxPreviousUsages = 14;
USAGE previousUsages[maxPreviousUsages];
Rozdział 4. ♦ Urządzenia klasy HID 143

while(true) { //cykliczny odczyt raportu wejściowego


memset(inputReportBuffer, 0x00, capabilities.InputReportByteLength);
ReadFile(/*...*/);
if(numberOfBytesRead==capabilities.InputReportByteLength){
for(USHORT i=0; i<capabilities.InputReportByteLength; i++)
printf("%d ", inputReportBuffer[i]);
printf("\n");

ULONG validUsages = usageListLength;


HidP_GetUsagesEx(HidP_Input, 0, Usages, &validUsages,
preparsedData, inputReportBuffer,
capabilities.InputReportByteLength);

USAGE *currentUsages = new USAGE[maxPreviousUsages];


USAGE *breakUsages = new USAGE[maxPreviousUsages];
USAGE *makeUsages = new USAGE[maxPreviousUsages];

//stan aktualnie używanych przycisków


memset(currentUsages, 0, sizeof(currentUsages));
printf("Zbiór przycisków: ");
for(ULONG i=0; i<validUsages; i++) {
printf( "UsagePage=%d Usage=%d\n ", Usages[i].UsagePage,
Usages[i].Usage);
currentUsages[i] = Usages[i].Usage;
}

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;
}
//...
//---------------------------------------------------------

Poprawnie użyta funkcja HidP_UsageListDifference() zwraca wartość o statusie HIDP_


STATUS_SUCCESS.

Funkcja HidP_UsageAndPageListDifference()
Funkcja ta jest analogiczna do HidP_UsageListDifference(), z tą różnicą, że jej cztery
pierwsze argumenty wskazują tablicę struktur USAGE_AND_PAGE.
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
);

Poprawnie wykonana funkcja HidP_UsageAndPageListDifference() zwraca wartość


o statusie HIDP_STATUS_SUCCESS.

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);

Parametr lpLibFileName jest wskaźnikiem do ciągu znaków zakończonych zerowym


ogranicznikiem, zawierającym nazwę modułu wykonawczego (plik .dll lub .exe). Po-
dana nazwa dotyczy pliku modułu i nie jest związana z nazwą własną przechowywaną
w module bibliotecznym. Funkcja zgłosi błąd w postaci pustego wskaźnika, jeśli po-
dana nazwa zawiera ścieżkę dostępu do pliku, w którym żądany moduł nie występuje.
Jeżeli nie podano ścieżki dostępu do pliku i pominięto rozszerzenie nazwy pliku, to
domyślnie dołączanym rozszerzeniem jest .dll. W przypadku powodzenia wartością
zwracaną przez funkcję jest identyfikator modułu.

Funkcja LoadLibrary() jest używana głównie w celu dynamicznego odwzorowywania


modułów .dll i pobierania ich identyfikatorów, które z kolei są używane przez funkcję:
FARPROC GetProcAddress(IN HMODULE hModule,IN LPCSTR lpProcName);

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

Do zwalniania identyfikatora przydzielonego za pomocą funkcji LoadLibrary() służy


funkcja FreeLibrary().

W tabeli 4.6 zamieszczono zestawienie funkcji eksportowych dostępnych z poziomu


biblioteki HID.dll, zaś na rysunku 4.7 pokazano umiejscowienie omawianej biblioteki
na stosie sterowników Windows HID.

Tabela 4.6. Podstawowe funkcje eksportowe biblioteki HID.dll


Nazwa funkcji eksportowej Moduł Dostępność
HidD_FlushQueue() hidsdi.h Dostępna
HidD_FreePreparsedData() hidsdi.h Dostępna
HidD_GetAttributes() hidsdi.h Dostępna
HidD_GetConfiguration() hidsdi.h Zarezerwowana do
użytku systemowego
HidD_GetFeature() hidsdi.h Dostępna
HidD_GetHidGuid() hidsdi.h Dostępna
HidD_GetIndexedString() hidsdi.h Dostępna
HidD_GetInputReport() hidsdi.h Dostępna
HidD_GetManufacturerString() hidsdi.h Dostępna
HidD_GetMsGenreDescriptor() hidsdi.h Zarezerwowana do
użytku systemowego
HidD_GetNumInputBuffers() hidsdi.h Dostępna
HidD_GetPhysicalDescriptor() hidsdi.h Dostępna
HidD_GetPreparsedData() hidsdi.h Dostępna
HidD_GetProductString() hidsdi.h Dostępna
HidD_GetSerialNumberString() hidsdi.h Dostępna
HidD_SetConfiguration() hidsdi.h Zarezerwowana do
użytku systemowego
HidD_SetFeature() hidsdi.h Dostępna
HidD_SetNumInputBuffers() hidsdi.h Dostępna
HidD_SetOutputReport() hidsdi.h Dostępna
HidP_FreeCollectionDescription() hidpddi.h Zarezerwowana do
użytku systemowego
HidP_GetButtonCaps() hidpi.h Dostępna
HidP_GetCaps() hidpi.h, hidparse_lib.lib Dostępna
HidP_GetCollectionDescription() hidpddi.h Zarezerwowana do
użytku systemowego
HidP_GetData() hidpi.h Dostępna
HidP_GetExtendedAttributes() hidpi.h, hidparse_lib.lib Dostępna
HidP_GetLinkCollectionNodes() hidpi.h Dostępna
HidP_GetScaledUsageValue() hidpi.h Dostępna
146 USB. Praktyczne programowanie z Windows API w C++

Tabela 4.6. Podstawowe funkcje eksportowe biblioteki HID.dll — ciąg dalszy


Nazwa funkcji eksportowej Moduł Dostępność
HidP_GetSpecificButtonCaps() hidpi.h, hidparse_lib.lib Dostępna
HidP_GetSpecificValueCaps() hidpi.h, hidparse_lib.lib Dostępna
HidP_GetUsageValue() hidpi.h, hidparse_lib.lib Dostępna
HidP_GetUsageValueArray() hidpi.h, hidparse_lib.lib Dostępna
HidP_GetUsages() hidpi.h, hidparse_lib.lib Dostępna
HidP_GetUsagesEx() hidpi.h, hidparse_lib.lib Dostępna
HidP_GetValueCaps() hidpi.h Dostępna
HidP_InitializeReportForID() hidpi.h Dostępna
HidP_MaxDataListLength() hidpi.h, hidparse_lib.lib Dostępna
HidP_MaxUsageListLength() hidpi.h Dostępna
HidP_SetData() hidpi.h, hdparse_lib.lib Dostępna
HidP_SetScaledUsageValue() hidpi.h Dostępna

HidP_SetUsageValue() hidpi.h, hidparse_lib.lib Dostępna


HidP_SetUsageValueArray() hidpi.h, hidparse_lib.lib Dostępna
HidP_SetUsages() hidpi.h, hidparse_lib.lib Dostępna
HidP_SysPowerCaps hidpddi.h Zarezerwowana do
użytku systemowego
HidP_SysPowerEvent() hidpddi.h Zarezerwowana do
użytku systemowego
HidP_TranslateUsagesToI8042ScanCodes() hidpddi.h Zarezerwowana do
użytku systemowego
HidP_UnsetUsages() hidpi.h, hidparse_lib.lib Dostępna
HidP_UsageAndPageListDifference() hidpi.h Dostępna
HidP_UsageListDifference() hidpi.h Dostępna

W tabeli 4.7 zaprezentowano zestawienie możliwości wykorzystania omawianych za-


sobów biblioteki HID.dll w oprogramowaniu uruchamianym w trybie użytkownika lub
w trybie jądra. Biblioteka HID.dll znajduje się w katalogu \System32\ lub \SySWOW64\.

Tabela 4.7. Sposoby wykorzystania funkcji biblioteki HID.dll


Sterowniki Aplikacje
Tryb użytkownika HidD_Xxx() HidD_Xxx(), HidP_Xxx()
Tryb jądra HidD_Xxx() lub rozkazy IOCTL_HID_xxx() ⎯
Rozdział 4. ♦ Urządzenia klasy HID 147

Rysunek 4.7. Umiejscowienie biblioteki HID.dll na stosie sterowników Windows HID

Podsumowanie
W niniejszym rozdziale zaprezentowano główne założenia 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.1. Podstawowe stany urządzenia w trakcie enumeracji


150 USB. Praktyczne programowanie z Windows API w C++

Za pośrednictwem kilkunastu czynności, z których najważniejsze zostały przedstawio-


ne poniżej, system operacyjny wykonuje enumerację urządzenia w ramach poszcze-
gólnych stanów.

Rysunek 5.2. Szczegółowy diagram czynności dla procesu enumeracji urządzeń dołączanych
do magistrali USB

 Użytkownik przyłącza urządzenie do portu USB hosta (macierzystego


komputera) lub huba (koncentratora) — urządzenie pozostaje w stanie
przyłączenia (ang. attached state).
 Po odblokowaniu linii zasilającej urządzenie przechodzi w stan zasilania
(ang. powered state).
 Po sprawdzeniu stanu linii zasilających oprogramowanie hosta przystępuje
do konfigurowania nowego sprzętu.
 Hub poprzez testowanie stanu linii sygnałowych sprawdza, z jaką prędkością
przesyłu danych urządzenie może pracować. Informacja zostaje przekazana
do hosta w odpowiedzi na wysłany przez niego rozkaz GET_STATUS.
 Kiedy nowe urządzenie zostaje rozpoznane, kontroler hosta wysyła do huba
rozkaz SET_FEATURE. Port zostaje zresetowany (przez 10 ms linie sygnałowe
pozostają w niskim stanie logicznym).
 Poprzez dalsze testowanie aktualnego stanu linii sygnałowych host sprawdza,
czy urządzenie pracujące z pełną szybkością przesyłu danych może pracować
też z szybkością wysoką.
 Ponownie wysyłając rozkaz GET_STATUS, host sprawdza, czy urządzenie pozostaje
w stanie Reset. Jeżeli nie, zostaje utworzony potok zerowy przeznaczony do
celów konfiguracji urządzenia. Urządzeniu zostaje przypisany domyślny adres
00h, po czym przechodzi ono do stanu domyślnego (ang. default state).
 Host wysyła rozkaz GET_DESCRIPTOR, aby otrzymać informacje o maksymalnym
rozmiarze pakietu danych, który może być transmitowany potokiem domyślnym.
Rozkaz ten jest kierowany do zerowego punktu końcowego EP0 urządzenia.
Oprogramowanie hosta może identyfikować w danym czasie tylko jedno
urządzenie, zatem tylko jedno urządzenie w danym czasie może pozostawać
z adresem 00h.
Rozdział 5. ♦ Detekcja i identyfikacja urządzeń dołączonych do magistrali USB 151

 W następnej kolejności za pośrednictwem rozkazu SET_ADDRESS urządzeniu


jest przypisywany unikatowy adres — urządzenie przechodzi do stanu
adresowania (ang. addressed state). Nowy adres pozostaje aktualny, dopóki
urządzenie jest przyłączone do portu USB. W momencie odłączenia urządzenia
port jest resetowany.
 W dalszej kolejności za pośrednictwem na nowo adresowanego rozkazu
GET_DESCRIPTOR oprogramowanie hosta pobiera kompletne deskryptory
urządzenia (patrz rysunek 3.9).
 Po odczytaniu deskryptorów urządzenia oprogramowanie hosta wyszukuje
dla urządzenia najlepiej pasujący sterownik i zapisuje odpowiednie informacje
(Vendor ID, Product ID, ...) w pliku .inf.
 Sterownik urządzenia wysyła rozkaz SET_CONFIGURATION w celu ostatecznego
skonfigurowania nowego sprzętu. Od tego momentu urządzenie pozostaje
w stanie skonfigurowania (ang. configured state)1.

Podstawowe zasoby systemowe


Kompilator C++ w module setupapi.h udostępnia szereg użytecznych funkcji i struk-
tur, które w znakomity sposób umożliwiają samodzielne przeprowadzenie detekcji
i identyfikacji ścieżek dostępu do interfejsów oferowanych przez sterownik(i) urządzeń
aktualnie dołączonych do magistrali USB. W tym podrozdziale zostały przedstawione
najważniejsze z nich.

W dalszej części książki ze względu na zwięzłość sformułowań poprzez interfejs urzą-


dzenia będziemy rozumieli interfejs, jaki sterownik urządzenia udostępnia warstwie
aplikacji.

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

IN PSP_DEVINFO_DATA DeviceInfoData, OPTIONAL


IN LPGUID InterfaceClassGuid,
IN DWORD MemberIndex,
OUT PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData
);

Wskaźnik DeviceInfoData wskazuje strukturę SP_DEVINFO_DATA (patrz tabela 5.1), co


umożliwia ograniczenie przeszukiwań istniejących urządzeń. Opcjonalnie funkcji moż-
na przekazać pusty wskaźnik. W takim wypadku funkcję należy wywoływać cyklicz-
nie, tak aby przeszukała wszystkie interfejsy udostępniane przez sterownik danego
urządzenia. Wskaźnik InterfaceClassGuid wskazuje strukturę GUID. Parametr wej-
ściowy MemberIndex jest numerem odpytywanego interfejsu. Jego wartości zaczynają
się od 0 (zerowy indeks pierwszego interfejsu — interfejsu domyślnego). Jeżeli funk-
cja jest wywoływana w pętli cyklicznie, przy każdym wywołaniu należy odpowiednio
zwiększyć wartość MemberIndex. Jeżeli SetupDiEnumDeviceInterfaces() zwróci war-
tość FALSE oraz funkcja GetLastError() zwróci ERROR_NO_MORE_ITEMS, oznacza to, że
nie znaleziono interfejsu o podanym indeksie. Wskaźnik DeviceInterfaceData wska-
zuje strukturę SP_DEVICE_INTERFACE_DATA (patrz tabela 5.2), której rozmiar należy wpi-
sać do pola cbSize:
deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);

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.

Tabela 5.1. Specyfikacja struktury SP_DEVINFO_DATA


Typ Element struktury Znaczenie
DWORD cbSize Rozmiar struktury w bajtach
GUID ClassGuid Identyfikator GUID klasy urządzeń
DWORD DevInst Identyfikator wewnętrznej struktury opisującej urządzenie
w systemie
ULONG_PTR Reserved Zarezerwowane

Windows Driver Kit (WDK) definiuje tę strukturę jako:


typedef struct _SP_DEVINFO_DATA {
DWORD cbSize;
GUID ClassGuid;
DWORD DevInst;
ULONG_PTR Reserved;
} SP_DEVINFO_DATA, *PSP_DEVINFO_DATA;

Definicja ta tworzy dwa nowe słowa kluczowe: SP_DEVINFO_DATA (struktura) i PSP_


DEVINFO_DATA (wskaźnik do struktury).
154 USB. Praktyczne programowanie z Windows API w C++

Funkcje rodziny SetupDiXx(), używając struktury SP_DEVINFO_DATA jako parametru,


automatycznie sprawdzają poprawność określenia jej rozmiaru. Aktualny rozmiar
struktury należy wskazać za pomocą operatora sizeof() i wpisać do pola cbSize.
Jeżeli rozmiar struktury w ogóle nie zostanie określony lub zostanie określony nie-
prawidłowo, to w przypadku użycia struktury jako parametru wejściowego IN zostanie
wygenerowany błąd ERROR_INVALID_PARAMETER, natomiast przy korzystaniu ze struk-
tury jako parametru wyjściowego OUT zostanie wygenerowany błąd ERROR_INVALID_
USER_BUFFER.

Struktura SP_DEVICE_INTERFACE_DATA
Zasoby struktury SP_DEVICE_INTERFACE_DATA zaprezentowane w tabeli 5.2 przechowu-
ją dane interfejsu należącego do klasy urządzeń USB.

Tabela 5.2. Specyfikacja struktury SP_DEVICE_INTERFACE_DATA


Typ Element struktury Znaczenie
DWORD cbSize Rozmiar struktury w bajtach
GUID InterfaceClassGuid Identyfikator GUID interfejsu klasy urządzeń
DWORD Flags Znaczniki interfejsu. Wartość SPINT_ACTIVE oznacza, że
interfejs jest aktualnie dostępny. Wartość SPINT_DEFAULT
oznacza domyślny interfejs dla klasy urządzeń. Wartość
SPINT_REMOVED określa usunięty interfejs
ULONG_PTR Reserved Parametr zarezerwowany i aktualnie nieużywany

WDK definiuje tę strukturę jako:


typedef struct _SP_DEVICE_INTERFACE_DATA {
DWORD cbSize;
GUID InterfaceClassGuid;
DWORD Flags;
ULONG_PTR Reserved;
} SP_DEVICE_INTERFACE_DATA, *PSP_DEVICE_INTERFACE_DATA;

Definicja ta tworzy dwa nowe słowa kluczowe: SP_DEVICE_INTERFACE_DATA (struktura)


i PSP_DEVICE_INTERFACE_DATA (wskaźnik do struktury).

Funkcje zdefiniowane w module setupapi.h, używając struktury SP_DEVICE_INTERFACE_


DATA jako parametru, automatycznie sprawdzają poprawność określenia jej rozmiaru.
Aktualny rozmiar struktury należy wskazać za pomocą operatora sizeof() i wpisać
do pola cbSize. Jeżeli rozmiar struktury w ogóle nie zostanie określony lub zostanie
określony nieprawidłowo, system wygeneruje błąd ERROR_INVALID_USER_BUFFER.
Rozdział 5. ♦ Detekcja i identyfikacja urządzeń dołączonych do magistrali USB 155

Struktura SP_DEVICE_INTERFACE_DETAIL_DATA
Struktura SP_DEVICE_INTERFACE_DETAIL_DATA zawiera informacje o postaci ścieżki do-
stępu do interfejsu wybranego urządzenia USB. W tabeli 5.3 przedstawiono znaczenie
poszczególnych pól tej struktury.

Tabela 5.3. Specyfikacja struktury SP_DEVICE_INTERFACE_DETAIL_DATA


Typ Element struktury Znaczenie
DWORD cbSize Rozmiar struktury w bajtach
TCHAR DevicePath[ANYSIZE_ARRAY] Łańcuch znaków zakończony zerowym ogranicznikiem
(tzw. NULL — ang. terminated string), zawierający pełną
nazwę symboliczną urządzenia (ścieżkę dostępu do
interfejsu udostępnianego przez sterownik urządzenia).
Parametr ten jest wykorzystywany przez funkcję
CreateFile()

WDK definiuje tę strukturę jako:


typedef struct _SP_DEVICE_INTERFACE_DETAIL_DATA {
DWORD cbSize;
TCHAR DevicePath[ANYSIZE_ARRAY];
} SP_DEVICE_INTERFACE_DETAIL_DATA, *PSP_DEVICE_INTERFACE_DETAIL_DATA;

Definicja ta tworzy dwa nowe słowa kluczowe: SP_DEVICE_INTERFACE_DETAIL_DATA


(struktura) i PSP_DEVICE_INTERFACE_DETAIL_DATA (wskaźnik do 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++

Wskaźnik DeviceInfoSet jest zwracany przez funkcję SetupDiGetClassDevs(). Para-


metr DeviceInterfaceData wskazuje strukturę SP_DEVICE_INTERFACE_DATA. Wskaźnik
DeviceInterfaceDetailData wskazuje strukturę SP_DEVICE_INTERFACE_DETAIL_DATA
(patrz tabela 5.3); opcjonalnie zamiast niego do funkcji może być przekazana wartość
NULL. W przypadku jawnego wskazania struktury SP_DEVICE_INTERFACE_DETAIL_DATA
wskaźnik powinien być poprawnie zainicjowany, a jej pole cbSize musi być prawi-
dłowo określone. W przeciwnym razie kompilator zgłosi błędy naruszenia pamięci,
podobnie jak na rysunkach 5.3 i 5.4.

Rysunek 5.3. Błąd naruszenia pamięci dla nieprawidłowo zainicjowanego wskaźnika do struktury
SP_DEVICE_INTERFACE_DETAIL_DATA

Rysunek 5.4. Błąd naruszenia pamięci dla nieprawidłowo określonego rozmiaru pola cbSize struktury
SP_DEVICE_INTERFACE_DETAIL_DATA

Argument DeviceInterfaceDetailDataSize funkcji SetupDiGetDeviceInterfaceDetail()


ma wartość zerową, jeżeli DeviceInterfaceDetailData=NULL; w przeciwnym razie okre-
śla rozmiar bufora: (offsetof(SP_DEVICE_INTERFACE_DETAIL_DATA, DevicePath) +
sizeof(TCHAR)). Parametr RequiredSize jest wskaźnikiem do danej typu DWORD, której
przypisuje się żądany rozmiar bufora wskazywanego przez DeviceInterfaceDetailData.
Parametr DeviceInfoData jest wskaźnikiem do bufora danych przechowującego infor-
macje na temat interfejsu udostępnianego przez sterownik urządzenia. Jeżeli wskaź-
nikowi nie przypisano wartości NULL, rozmiar danych powinien zostać określony za
pomocą operatora sizeof(): DeviceInfoData.cbSize=sizeof(SP_DEVINFO_DATA).
Rozdział 5. ♦ Detekcja i identyfikacja urządzeń dołączonych do magistrali USB 157

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
);

Wskaźnik DeviceInfoSet jest zwracany przez funkcję SetupDiGetClassDevs(). W przy-


padku prawidłowego zwolnienia zasobów funkcja zwraca wartość TRUE, w przeciwnym
razie wartość FALSE. Kod wystąpienia błędu jest zwracany przez funkcję GetLastError().

Detekcja interfejsów urządzeń


Na rysunku 5.5 w postaci diagramu czynności przedstawiono ogólną sieć działań, za
pomocą których można programowo samodzielnie wykonać procedurę detekcji urzą-
dzeń klasy HID aktualnie podłączonych do systemu, co w efekcie powinno skutkować
odzyskaniem pełnych nazw symbolicznych (pełnych ścieżek dostępu do interfejsów)
urządzeń zapisanych w polu DevicePath struktury SP_DEVICE_INTERFACE_DETAIL_DATA.

Rysunek 5.5. Ogólny diagram czynności dla operacji wstępnej enumeracji urządzeń klasy HID
158 USB. Praktyczne programowanie z Windows API w C++

Czynności (w znaczeniu nadawanym przez UML) mogą być interpretowane w zależ-


ności od wybranej perspektywy: jako zestaw pojedynczych metod (z perspektywy
projektowej) lub jako zadanie do wykonania, i to zarówno przez człowieka, jak i przez
komputer (z perspektywy pojęciowej). Diagramów czynności można używać do opi-
su metod rozwiązywania problemów wyrażonych w postaci skończonej sekwencji
kroków — to jest ich cel. Obsługują one wszystkie standardowe konstrukcje stero-
wania wymagane do opisania algorytmów [14].

W pierwszej kolejności należy odczytać postać identyfikatora GUID interfejsu klasy


urządzeń występujących w systemie. Wskaźnik deviceInfoSet wskaże dane zawiera-
jące informacje na temat wszystkich zainstalowanych i aktualnie dostępnych (przyłą-
czonych) urządzeń danej klasy. Następnie wyszukiwane są interfejsy poszczególnych
urządzeń. Poprzez odczytanie zawartości pola DevicePath struktury SP_DEVICE_INTERFACE_
DETAIL_DATA wydobywana jest pełna ścieżka dostępu DevicePath do interfejsu istniejące-
go urządzenia USB. Na koniec dotychczas używane przez program zasoby są zwalniane.

Niektóre z dostępnych kompilatorów języka C++ mogą niewłaściwie obliczać rozmiar


struktur (za pomocą operatora sizeof()). Błędne obliczenie rozmiaru którejkolwiek
z używanych struktur będzie niezmiennie skutkować błędami w trakcie uruchamia-
nia programu. W takich sytuacjach należy zadbać o właściwe ustalenie opcji kompi-
latora na podstawie jego dokumentacji. Stosowana tu konstrukcja:
#pragma option push -a1
//...
#pragma option pop

odpowiada opisanej sytuacji. Inne przykłady rozwiązania tego typu problemów moż-
na znaleźć w artykule dostępnym pod adresem: http://support.codegear.com/
article/35751.

Na listingu 5.1 zamieszczono kod modułu projektu będącego uszczegółowioną imple-


mentacją diagramu z rysunku 5.5.

Listing 5.1. Kod modułu usb_R5_1.cpp jako przykład zaprogramowania wstępnej enumeracji urządzeń
na podstawie identyfikatora GUID klasy urządzeń
#include <windows>
#pragma option push -a1
#include <setupapi>
#pragma option pop
#include <assert>
#include <iostream>

using namespace std;

void displayError(const char* msg){


cout << msg << endl;
system("PAUSE");
exit(0);
};
//---------------------------------------------------------
template <class T>
Rozdział 5. ♦ Detekcja i identyfikacja urządzeń dołączonych do magistrali USB 159

inline void releaseMemory(T &x)


{
assert(x != NULL);
delete [] x;
x = NULL;
}
//---------------------------------------------------------
GUID classGuid;
HMODULE hHidLib;
DWORD /* unsigned long lub ULONG */ memberIndex = 0;
DWORD deviceInterfaceDetailDataSize;
DWORD requiredSize;

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);

while(SetupDiEnumDeviceInterfaces(deviceInfoSet, NULL, &classGuid,


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);
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;
}
//---------------------------------------------------------

Różne odmiany wskaźnika do funkcji FARPROC są zdefiniowane w module windef.h.


W Linuksie FARPROC należy zastąpić wskaźnikiem ogólnym void*. Windows Driver
Kit stosuje konwencję __stdcall, co zapewnia, że funkcja zostanie wywołana zgod-
nie z wymogami systemu operacyjnego. Oznacza to, że w funkcji wywołującej liczba
i typ argumentów muszą być poprawne. Funkcje i typy danych definiowane w zaso-
bach WDK API często korzystają z następujących makrodefinicji:
#define DDKAPI __stdcall
lub
#define DDKAPI_PTR __stdcall*

W tego typu konwencjach deklaracja wskaźnika do funkcji może zostać zapisana na


jeden z dwóch sposobów:
void (DDKAPI *HidD_GetHidGuid)(OUT LPGUID HidGuid);
lub
void (DDKAPI_PTR HidD_GetHidGuid)(OUT LPGUID HidGuid);

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).

Rysunek 5.6. Aplikacja proj_USB_R5_1 w trakcie działania

Odczytane ścieżki dostępu (łącza symboliczne) do poszczególnych interfejsów urządzeń


klasy HID mogą być przekazane do funkcji CreateFile() w celu otrzymania identyfi-
katora pliku sterownika urządzenia wykonawczego USB, które jest aktualnie dostępne
w systemie.
Rozdział 5. ♦ Detekcja i identyfikacja urządzeń dołączonych do magistrali USB 161

Zliczanie interfejsów urządzeń


Dokonując niewielkiej modyfikacji poprzednio prezentowanego algorytmu, można
zbudować uniwersalną funkcję searchInterfaceHidDevices(), która dodatkowo zlicza
interfejsy aktualnie podłączonych do systemu urządzeń klasy HID. Listing 5.2 zawiera
odpowiedni przykład będący modyfikacją kodu z listingu 5.1.

Listing 5.2. Kod modułu usb_R5_2.cpp


#include <windows>
#pragma option push -a1
#include <setupapi>
#pragma option pop
#include <assert>
#include <iostream>

using namespace std;

void displayError(const char* msg){


cout << msg << endl;
system("PAUSE");
exit(0);
};
//---------------------------------------------------------
template <class T>
inline void releaseMemory(T &x)
{
assert(x != NULL);
delete [] x;
x = NULL;
}
//---------------------------------------------------------
int searchInterfaceHidDevices()
{
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;
bool done = false;

void (__stdcall *HidD_GetHidGuid)(OUT LPGUID HidGuid);

hHidLib = LoadLibrary("C:\\Windows\\system32\\HID.DLL");
if (!hHidLib)
displayError("Błąd dołączenia biblioteki HID.DLL.");

(FARPROC&) HidD_GetHidGuid = GetProcAddress(hHidLib,


"HidD_GetHidGuid");
if (!HidD_GetHidGuid){
FreeLibrary(hHidLib);
162 USB. Praktyczne programowanie z Windows API w C++

displayError("Nie znaleziono identyfikatora GUID.");


}

HidD_GetHidGuid (&classGuid);

deviceInfoSet = SetupDiGetClassDevs(&classGuid, NULL, NULL,


(DIGCF_PRESENT | DIGCF_DEVICEINTERFACE));

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
);

Wskaźnik DeviceInfoSet jest zwracany przez funkcję SetupDiGetClassDevs(). Parametr


DeviceInfoData wskazuje strukturę SP_DEVINFO_DATA. Parametr Property identyfikuje
właściwość urządzenia PnP, którą aktualnie chcemy odczytać. Jest on reprezentowany
przez odpowiednie stałe symboliczne, których podzbiór został wykorzystany w kodzie
z listingu 5.3. Kompletny zestaw predefiniowanych stałych symbolicznych można odna-
leźć w plikach pomocy. Opcjonalnie używany wskaźnik PropertyRegDataType wskazu-
je typ danej zawierającej właściwość urządzenia. Parametr PropertyBuffer wskazuje
bufor danych, poprzez który odczytamy właściwości urządzenia. Jeżeli PropertyBuffer
przypiszemy wartość NULL, a parametrowi PropertyBufferSize wartość zero, funkcja
zwróci żądany rozmiar bufora danych przechowywany w zmiennej RequiredSize. Pa-
rametr PropertyBufferSize określa w bajtach rozmiar bufora wskazywanego przez
PropertyBuffer.

Listing 5.3. Kod modułu usb_R5_3.cpp


#include <windows>
#pragma option push -a1
#include <setupapi>
#pragma option pop
#include <assert>
#include <iostream>
#include <cstring>

using namespace std;

void displayError(const char* msg){


164 USB. Praktyczne programowanie z Windows API w C++

cout << msg << endl;


system("PAUSE");
exit(0);
};
//---------------------------------------------------------
template <class T>
inline void releaseMemory(T &x)
{
assert(x != NULL);
delete [] x;
x = NULL;
}
//---------------------------------------------------------
GUID classGuid;
HMODULE hHidLib;
DWORD /*unsigned long*/ memberIndex = 0;
DWORD deviceInterfaceDetailDataSize;
DWORD requiredSize;

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;

SetupDiGetDeviceRegistryProperty(deviceInfoSet, deviceInfoData, property,


/*&propertyRegDataType*/NULL, NULL, 0,
&propertyBufferSize);

//alokowanie pamięci dla bufora danych


propertyBuffer = new char[(propertyBufferSize * sizeof(TCHAR))];

bool result=SetupDiGetDeviceRegistryProperty(deviceInfoSet, deviceInfoData,


property,/*&propertyRegDataType*/NULL,
propertyBuffer, propertyBufferSize,
NULL);
if(!result)
releaseMemory(propertyBuffer);
return propertyBuffer;
}
//---------------------------------------------------------
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&) HidD_GetHidGuid = GetProcAddress(hHidLib,


"HidD_GetHidGuid");
if (!HidD_GetHidGuid){
Rozdział 5. ♦ Detekcja i identyfikacja urządzeń dołączonych do magistrali USB 165

FreeLibrary(hHidLib);
displayError("Nie znaleziono identyfikatora GUID.");
}

HidD_GetHidGuid(&classGuid);

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);

while(SetupDiEnumDeviceInterfaces(deviceInfoSet, NULL, &classGuid,


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);

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;

cout << "\nClassDescr: "<<getRegistryPropertyString(deviceInfoSet,


&deviceInfoData, SPDRP_CLASS);
cout << "\nClassGUID: "<<getRegistryPropertyString(deviceInfoSet,
&deviceInfoData, SPDRP_CLASSGUID);
cout << "\nCompatibileIDs: "<<getRegistryPropertyString(deviceInfoSet,
&deviceInfoData, SPDRP_COMPATIBLEIDS);
cout << "\nConfigFlags: "<<getRegistryPropertyString(deviceInfoSet,
&deviceInfoData, SPDRP_CONFIGFLAGS);
cout << "\nDeviceDescr: "<<getRegistryPropertyString(deviceInfoSet,
&deviceInfoData, SPDRP_DEVICEDESC);
cout << "\nDriver: "<<getRegistryPropertyString(deviceInfoSet,
&deviceInfoData, SPDRP_DRIVER);
//cout << "\nFriendlyName: "<<getRegistryPropertyString(deviceInfoSet,
// &deviceInfoData, SPDRP_FRIENDLYNAME);
cout << "\nHardwareID: "<<getRegistryPropertyString(deviceInfoSet,
&deviceInfoData, SPDRP_HARDWAREID);
cout << "\nMfg: "<<getRegistryPropertyString(deviceInfoSet,
&deviceInfoData, SPDRP_MFG);
cout << "\nEnumeratorName: "<<getRegistryPropertyString(deviceInfoSet,
&deviceInfoData, SPDRP_ENUMERATOR_NAME);
cout << "\nPhysDevObjName: "<<getRegistryPropertyString(deviceInfoSet,
&deviceInfoData, SPDRP_PHYSICAL_DEVICE_OBJECT_NAME);
166 USB. Praktyczne programowanie z Windows API w C++

cout << endl;


releaseMemory(deviceInterfaceDetailData);
}; //koniec while

SetupDiDestroyDeviceInfoList(deviceInfoSet);
FreeLibrary(hHidLib);
cout << endl;
system("PAUSE");
return 0;
}
//---------------------------------------------------------

Używanie funkcji SetupDiGetDeviceRegistryProperty() z reguły nie ogranicza się do


pojedynczego wywołania. W pierwszym wywołaniu określamy wymagany rozmiar
bufora danych, a w następnym odczytujemy żądane właściwości zainstalowanego
w systemie urządzenia PnP. Pokazano to na listingu 5.3 w ciele przykładowej funkcji
getRegistryPropertyString() wydobywającej niektóre właściwości łańcuchowe sprzę-
tu zgodnego z klasą HID. Rysunek 5.7 przedstawia wynik działania programu cyklicz-
nie wywołującego funkcję getRegistryPropertyString() w celu odczytania wybranych
właściwości łańcuchowych urządzeń zainstalowanych w systemie.

Rysunek 5.7.
Aplikacja
proj_USB_R5_3
w trakcie działania
Rozdział 5. ♦ Detekcja i identyfikacja urządzeń dołączonych do magistrali USB 167

W rejestrze systemowym elementami podkluczy tematycznych są dwa typy danych:


łańcuchowe (oznaczone jako REG_SZ lub REG_MULTI_SZ) i liczbowe (oznaczone jako
REG_DWORD).

Testując kod z listingu 5.3, możemy zauważyć, że funkcja getRegistryPropertyString()


wydobywa jedynie właściwości łańcuchowe urządzenia. Aby odzyskać właściwość
liczbową, należy zmodyfikować tę funkcję lub, co jest dużo bardziej pożyteczne, zbu-
dować jej odmianę, która będzie zwracała dane typu DWORD. Na listingu 5.4 zamiesz-
czono odpowiedni przykład funkcji getRegistryPropertyDWORD(), wydobywającej wła-
ściwości liczbowe urządzenia zapisane w rejestrze systemowym.

Listing 5.4. Kod funkcji getRegistryPropertyDWORD() wraz z jej przykładowym wywołaniem


//...
SP_DEVINFO_DATA deviceInfoData;
//---------------------------------------------------------
DWORD getRegistryPropertyDWORD(HDEVINFO deviceInfoSet,
PSP_DEVINFO_DATA deviceInfoData, DWORD property)
{
DWORD propertyBufferSize = 0;
DWORD propertyRegDataType = 0;
DWORD *propertyBuffer = 0;
bool result;

SetupDiGetDeviceRegistryProperty(deviceInfoSet, deviceInfoData, property,


&propertyRegDataType, NULL, 0,
&propertyBufferSize);

//alokowanie pamięci dla bufora danych


propertyBuffer = new DWORD[(propertyBufferSize * sizeof(DWORD))];

result = SetupDiGetDeviceRegistryProperty(deviceInfoSet, deviceInfoData,


property,&propertyRegDataType,
(char*)propertyBuffer, sizeof(propertyBuffer),
NULL);
if(!result)
releaseMemory(propertyBuffer);
return *propertyBuffer;
}
//---------------------------------------------------------
int main(){
//...
cout<<"\nCapabilities" << getRegistryPropertyDWORD(deviceInfoSet,
&deviceInfoData, SPDRP_CAPABILITIES);
//...
}
//---------------------------------------------------------
168 USB. Praktyczne programowanie z Windows API w C++

Struktury danych
Elementy opisu urządzeń USB, które są dostępne w systemie, są często przechowywa-
ne w polach odpowiednio skonstruowanych struktur danych znajdujących się w jednej
przestrzeni nazw. 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;
//---------------------------------------------------------

Jej pola przechowują przykładowe dane zawierające identyfikator sprzętu (HardwareId),


ścieżkę dostępu do interfejsu urządzenia (Path) oraz DeviceInstance, który będzie lo-
kalizował element DevInst struktury USB_DEFINFO_DATA. Zawartość struktury DEVICE_
DATA pozwala wstępnie zidentyfikować urządzenie.

Aby w pełni wykorzystać tak skonstruowany typ danych, należy zadeklarować tablicę
wskaźników do struktur o rozmiarze nie mniejszym niż rzeczywista liczba interfejsów
urządzeń USB w systemie:
PDEVICE_DATA deviceList;
//...
deviceList = (PDEVICE_DATA)new \
DEVICE_DATA[((memberIndex+1)*sizeof(DEVICE_DATA))];

Warto pamiętać, że w przypadku błędnego zaalokowania pamięci dla tablicy struktur


kompilator zgłosi błąd naruszenia pamięci.

Po prawidłowym zaalokowaniu tablicy struktur określamy za pomocą funkcji strlen()


aktualną długość ścieżki dostępu do interfejsu urządzenia:
size_t nLen = strlen(deviceInterfaceDetailData->DevicePath) + 1;

oraz tworzymy tablicę ścieżek:


deviceList[memberIndex].Path = new TCHAR[(nLen*sizeof(TCHAR))];

Ścieżka dostępu do interfejsu urządzenia zostaje przekopiowana za pomocą funkcji


strncpy() do pola Path elementu (o indeksie memberIndex) tablicy struktur DEVICE_DATA.
strncpy(deviceList[memberIndex].Path,
deviceInterfaceDetailData->DevicePath, nLen);

Od tego momentu program dysponuje indeksowaną ścieżką dostępu do interfejsu urzą-


dzenia, którą można wykorzystać na przykład w funkcji uzyskującej dostęp do pliku
sterownika urządzenia:
CreateFile(deviceList[2].Path, ...);

Sposoby wypełniania pozostałych elementów struktury DEVICE_DATA zaprezentowano


w kodzie z listingu 5.5, zawierającym funkcję setGetHidDeviceData(). Na rysunku 5.8
pokazano wynik działania omawianego programu.
Rozdział 5. ♦ Detekcja i identyfikacja urządzeń dołączonych do magistrali USB 169

Listing 5.5. Kod modułu usb_R5_4.cpp


#include <windows>
#pragma option push -a1
#include <setupapi>
#pragma option pop
#include <assert>
#include <iostream>
#include <cstring>

using namespace std;

void displayError(const char* msg){


cout << msg << endl;
system("PAUSE");
exit(0);
};
//---------------------------------------------------------
template <class T>
inline void releaseMemory(T &x)
{
assert(x != NULL);
delete [] x;
x = NULL;
}
//---------------------------------------------------------
typedef struct _DEVICE_DATA {
TCHAR *HardwareId;
TCHAR *Path; //łącze symboliczne
DWORD DeviceInstance;
} DEVICE_DATA, *PDEVICE_DATA;
//---------------------------------------------------------
int setGetHidDeviceData()
{
PDEVICE_DATA deviceList;
DWORD propertyBufferSize = 0;
char *propertyBuffer = NULL;
SP_DEVINFO_DATA deviceInfoData;

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;

void (__stdcall *HidD_GetHidGuid)(OUT LPGUID HidGuid);

hHidLib = LoadLibrary("C:\\Windows\\system32\\HID.DLL");
if (!hHidLib)
displayError("Błąd dołączenia biblioteki HID.DLL.");

(FARPROC&) HidD_GetHidGuid = GetProcAddress(hHidLib,


"HidD_GetHidGuid");
170 USB. Praktyczne programowanie z Windows API w C++

if (!HidD_GetHidGuid){
FreeLibrary(hHidLib);
displayError("Nie znaleziono identyfikatora GUID.");
}

HidD_GetHidGuid (&classGuid);

deviceInfoSet = SetupDiGetClassDevs(&classGuid, NULL, NULL,


(DIGCF_PRESENT | DIGCF_DEVICEINTERFACE));

deviceInterfaceData.cbSize = sizeof(SP_INTERFACE_DEVICE_DATA);

while(!done) {
deviceList = new DEVICE_DATA[((memberIndex+1)*sizeof(DEVICE_DATA))];

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;
}
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[memberIndex].Path = new TCHAR[(nLen*sizeof(TCHAR))];

strncpy(deviceList[memberIndex].Path,
deviceInterfaceDetailData->DevicePath, nLen);

cout <<"\nDeviceList["<<memberIndex<<"].Path: \n" <<


deviceList[memberIndex].Path << endl;

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

//alokowanie pamięci dla bufora danych


propertyBuffer = new char[(propertyBufferSize*sizeof(TCHAR))];

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++

Na rysunku 5.9 zaprezentowano sposób uzyskiwania dostępu do urządzenia USB.


W pierwszej kolejności należy określić ścieżkę dostępu do właściwego dla danej kon-
figuracji interfejsu, jaki sterownik udostępnia warstwie aplikacji, po czym uzyskać
identyfikator pliku sterownika urządzenia.

Rysunek 5.9.
Uzyskiwanie dostępu
do złożonego
urządzenia USB
funkcjonującego
w podstawowym
modelu konfiguracji
(por. rysunek 3.13)

Program użytkownika uzyskuje dostęp do urządzenia poprzez jego interfejs dostar-


czany przez obiekty PDO i FDO. Dla każdego zidentyfikowanego przez system portu
USB oraz dla każdego zidentyfikowanego i przyłączonego urządzenia określony ste-
rownik tworzy odpowiedni obiekt urządzenia, interfejs, wewnętrzną nazwę obiektu
fizycznego (PhysDevObjName — patrz rysunek 5.7) oraz nazwę reprezentującą łącze
symboliczne do pliku sterownika (patrz rysunek 5.8).

Gdy 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

DWORD requiredSize, deviceInterfaceDetailDataSize = 0;


BOOL bResult;

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.

Jeżeli w przypadku urządzeń HID zdecydujemy się posługiwać identyfikatorem GUID_


DEVINTERFACE_HID, w kodzie programu należy zrezygnować z funkcji HidD_GetHidGuid(),
tak jak pokazano na listingu 5.7. Na rysunku 5.10 zaprezentowano program w trakcie
działania.

Listing 5.7. Kod modułu usb_R5_5.cpp


#include <windows>
#pragma option push -a1
#include <setupapi>
#pragma option pop
#include <assert>
#include <iostream>

using namespace std;

void displayError(const char* msg){


cout << msg << endl;
system("PAUSE");
exit(0);
};
//---------------------------------------------------------
template <class T>
inline void releaseMemory(T &x)
{
assert(x != NULL);
delete [] x;
x = NULL;
}
//---------------------------------------------------------
/*A5DCBF10-6530-11D2-901F-00C04FB951ED */
static GUID GUID_DEVINTERFACE_USB_DEVICE =
{0xA5DCBF10, 0x6530, 0x11D2, {0x90, 0x1F, 0x00, 0xC0,
0x4F, 0xB9, 0x51, 0xED}};

/*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");
}

cout << deviceInterfaceDetailData->DevicePath << endl;


releaseMemory(deviceInterfaceDetailData);
};//koniec while

SetupDiDestroyDeviceInfoList(deviceInfoSet);
cout << endl;
system("PAUSE");
return 0;
}
//---------------------------------------------------------
176 USB. Praktyczne programowanie z Windows API w C++

Rysunek 5.10. Detekcja wszystkich urządzeń aktualnie podłączonych do magistrali USB


z GUID_DEVINTERFACE_USB_DEVICE

Moduł cfgmgr32.h
Menedżer konfiguracji oferuje szereg niezwykle użytecznych i prostych w wykorzysta-
niu funkcji rodziny CM_xxx(), które są pomocne w szybkim zdiagnozowaniu aktualnie
dostępnych w systemie urządzeń. W niniejszym podrozdziale zostaną przedstawione
te najbardziej podstawowe. W celu wykorzystania zasobów menedżera do identyfika-
cji urządzeń USB w pierwszej kolejności należy określić interesującą nas klasę insta-
lacji urządzeń w funkcji SetupDiGetClassDevs() oraz odpowiednio wywołać żądaną
funkcję menedżera. 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
);

w celu szybkiego odczytania informacji o zainstalowanych i aktualnie dostępnych urzą-


dzeniach na podstawie zapisów rejestru systemowego. Pierwszym parametrem funkcji
jest pole typu DEVINST. DEVINST jest wewnętrzną strukturą danych reprezentującą urzą-
dzenie w systemie. Dla urządzeń USB pierwszym parametrem wejściowym funkcji
CM_Get_DevNode_Registry_Property() będzie pole DevInst struktury SP_DEVINFO_DATA
przechowującej informacje na temat egzemplarza urządzenia należącego do klasy urzą-
dzeń USB. Parametr wejściowy ulProperty identyfikuje właściwość urządzenia, którą
aktualnie chcemy odczytać. Jest on reprezentowany przez odpowiednie stałe symbo-
liczne CM_DRP_xxx, określające żądane właściwości instalacyjne urządzenia zapisane
w rejestrze systemowym (patrz rozdział 2.). Wartości CM_DRP_xxx są zdefiniowane
w module cfgmgr32.h w następujący sposób:
CM_DRP_DEVICEDESC (0x00000001) //DeviceDesc REG_SZ property(RW)
CM_DRP_HARDWAREID (0x00000002) //HardwareID REG_MULTI_SZ
//property(RW)
CM_DRP_COMPATIBLEIDS (0x00000003) //CompatibleIDs REG_MULTI_SZ
//property(RW)
CM_DRP_UNUSED0 (0x00000004) //unused
CM_DRP_SERVICE (0x00000005) //Service REG_SZ property(RW)
CM_DRP_UNUSED1 (0x00000006) //unused
CM_DRP_UNUSED2 (0x00000007) //unused
CM_DRP_CLASS (0x00000008) //Class REG_SZ property(RW)
Rozdział 5. ♦ Detekcja i identyfikacja urządzeń dołączonych do magistrali USB 177

CM_DRP_CLASSGUID (0x00000009) //ClassGUID REG_SZ property(RW)


CM_DRP_DRIVER (0x0000000A) //Driver REG_SZ property(RW)
CM_DRP_CONFIGFLAGS (0x0000000B) //ConfigFlags REG_DWORD
//property(RW)
CM_DRP_MFG (0x0000000C) //Mfg REG_SZ property(RW)
CM_DRP_FRIENDLYNAME (0x0000000D) //FriendlyName REG_SZ property(RW)
CM_DRP_LOCATION_INFORMATION (0x0000000E) //LocationInformation REG_SZ
//property(RW)
CM_DRP_PHYSICAL_DEVICE_OBJECT_NAME (0x0000000F) //PhysicalDeviceObjectName REG_SZ
//property(R)
CM_DRP_CAPABILITIES (0x00000010) //Capabilities REG_DWORD
//property(R)
CM_DRP_UI_NUMBER (0x00000011) //UiNumber REG_DWORD property(R)
CM_DRP_UPPERFILTERS (0x00000012) //UpperFilters REG_MULTI_SZ
//property(RW)

Opcjonalny wskaźnik pulRegDataType wskazuje wartość NULL, a wskaźnik Buffer —


bufor danych przechowujący łańcuch znaków identyfikujący egzemplarz urządzenia
aktualnie przyłączonego do magistrali USB. Wskaźnik pulLength określa długość bufora
danych. Znacznik ulFlags nie ma istotnego znaczenia i może zawierać wartość NULL.
Prawidłowo wykonane funkcje menedżera konfiguracji zwracają wartość CR_SUCCESS.
W programach x64 zamiast CM_Get_DevNode_Registry_Property() powinno się uży-
wać funkcji SetupDiGetDeviceRegistryProperty(), tak jak pokazano w dalszej części
niniejszego podrozdziału.

Listing 5.8. Określenie typów urządzeń USB aktualnie zainstalowanych w systemie


#include <windows>
#pragma option push -a1
#include <setupapi>
#pragma option pop
#include <iostream>
#include "D:\\WINDDK\\7600.16385.1\\inc\\api\\cfgmgr32.h"

using namespace std;


//---------------------------------------------------------
//Wyświetla listę urządzeń USB zainstalowanych w systemie
string getDeviceDescription(DEVINST devInst)
{
char /*TCHAR*/ buffer[1023];
ULONG bufferLen;
bufferLen = sizeof(buffer);
if ((devInst != 0) && (CM_Get_DevNode_Registry_Property(devInst,
CM_DRP_DEVICEDESC /*CM_DRP_CLASS*/, NULL, buffer, &bufferLen, 0)
== CR_SUCCESS))
return buffer;
};
//---------------------------------------------------------
void printUSBDevices()
{
DWORD memberIndex =0;
HDEVINFO deviceInfoSet;
SP_DEVINFO_DATA deviceInfoData;

deviceInfoSet = SetupDiGetClassDevs (NULL, "USB", NULL,


DIGCF_PRESENT|DIGCF_ALLCLASSES);
178 USB. Praktyczne programowanie z Windows API w C++

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.

Rysunek 5.11. Porównanie działania aplikacji projektu proj_USB_R5_6 z informacjami


przechowywanymi w menedżerze urządzeń Windows 7 z USB 2.0
Rozdział 5. ♦ Detekcja i identyfikacja urządzeń dołączonych do magistrali USB 179

Rysunek 5.12. Porównanie działania aplikacji projektu proj_USB_R5_6 z informacjami


przechowywanymi w menedżerze urządzeń Windows 8 z USB 3.0

W trakcie tworzenia oprogramowania dla urządzeń USB często zachodzi potrzeba


szybkiego odtworzenia drzewa urządzeń zainstalowanych w systemie oraz odczytania
odpowiednich identyfikatorów VID oraz PID. Czynność tę można wykonać bez zna-
jomości właściwych identyfikatorów GUID klas urządzeń. Menedżer konfiguracji ofe-
ruje funkcję:
CMAPI CONFIGRET WINAPI
CM_Get_Device_ID(
IN DEVINST dnDevInst,
OUT PWSTR Buffer,
IN ULONG BufferLen,
OUT ULONG ulFlags
);

przechowującą identyfikatory egzemplarzy aktualnie zainstalowanych w systemie urzą-


dzeń. Dla urządzeń USB pierwszym parametrem funkcji będzie pole DevInst struktu-
ry SP_DEVINFO_DATA przechowującej informacje na temat egzemplarza urządzenia na-
leżącego do klasy urządzeń USB. Wskaźnik Buffer wskazuje bufor danych zawierający
łańcuch znaków opisujący egzemplarz urządzenia aktualnie przyłączonego do magi-
strali USB. Parametr BufferLength określa długość bufora danych. Znacznik ulFlags
nie ma istotnego znaczenia i może zawierać wartość NULL, tak jak pokazano na listin-
gu 5.9. Programy użytkownika mogą dodatkowo korzystać z usług funkcji:
CMAPI CONFIGRET WINAPI
CM_Get_Child(
OUT PDEVINST pdnDevInst,
IN DEVINST dnDevInst ,
IN ULONG ulFlags
);

przechowującej identyfikator urządzenia potomnego w drzewie urządzeń.

Listing 5.9. Kod modułu usb_R5_7.cpp


#include <windows>
#pragma option push -a1
#include <setupapi>
#pragma option pop
180 USB. Praktyczne programowanie z Windows API w C++

#include <iostream>
#include "D:\\WINDDK\\7600.16385.1\\inc\\api\\cfgmgr32.h"

using namespace std;


//---------------------------------------------------------
HDEVINFO deviceInfoSet;
DEVINST devInstChild;
SP_DEVINFO_DATA deviceInfoData;
CONFIGRET configRet;
char /*TCHAR*/ buffer[MAX_DEVICE_ID_LEN];
DWORD /*ULONG*/ propertyBufferSize = 0;
DWORD property;
char *propertyBuffer = NULL;
DWORD propertyRegDataType;
//---------------------------------------------------------
char* printPrperty()
{
SetupDiGetDeviceRegistryProperty(deviceInfoSet, &deviceInfoData,
property, NULL, NULL, 0,
&propertyBufferSize);
propertyBuffer = new char[(propertyBufferSize * sizeof(char/*TCHAR*/))];
if (SetupDiGetDeviceRegistryProperty(deviceInfoSet, &deviceInfoData,
SPDRP_DEVICEDESC,
&propertyRegDataType,
propertyBuffer,
propertyBufferSize,
&propertyBufferSize))
return propertyBuffer;
}
//---Wyświetla drzewo urządzeń USB oraz odczytuje ich identyfikatory VID i PID---
void printUSBDevices()
{
deviceInfoSet = SetupDiGetClassDevs(NULL, "USB", NULL,
DIGCF_PRESENT | DIGCF_ALLCLASSES);
if(deviceInfoSet == INVALID_HANDLE_VALUE)
return;

for(DWORD memberIndex = 0; ; memberIndex++) {


deviceInfoData.cbSize = sizeof (deviceInfoData);
if(!SetupDiEnumDeviceInfo(deviceInfoSet, memberIndex,
&deviceInfoData))
break;

configRet = CM_Get_Device_ID(deviceInfoData.DevInst, buffer, MAX_PATH, 0);


if (configRet == CR_SUCCESS) {
printf("\n%s\n", printPrperty());
printf("%s\n", buffer);
delete [] propertyBuffer;
}
configRet = CM_Get_Child(&devInstChild, deviceInfoData.DevInst, 0);
if(configRet == CR_SUCCESS) {
configRet = CM_Get_Device_ID (devInstChild, buffer, MAX_PATH, 0);
if(configRet == CR_SUCCESS){
//printf(" %s\n", printProperty());
printf(" %s\n", buffer);
//delete [] propertyBuffer;
}
configRet = CM_Get_Child(&devInstChild, devInstChild, 0);
Rozdział 5. ♦ Detekcja i identyfikacja urządzeń dołączonych do magistrali USB 181

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;
}
//---------------------------------------------------------

Na rysunku 5.13 zaprezentowano rezultat działania programu wyświetlającego drzewo


aktualnie dostępnych w systemie urządzeń USB.

Rysunek 5.13.
Detekcja ścieżek
wystąpień wszystkich
obiektów urządzeń
aktualnie
podłączonych do
magistrali USB
182 USB. Praktyczne programowanie z Windows API w C++

Zasoby menedżera konfiguracji umożliwiają samodzielne odtworzenie drzewa urzą-


dzeń. Hierarchiczne drzewo urządzeń zawiera dane o dostępnym w systemie sprzęcie
i zostaje utworzone przez system operacyjny na podstawie informacji otrzymanych
z poszczególnych sterowników. Każdy węzeł drzewa reprezentuje fizyczny obiekt
urządzenia. W celu dokładnego zapoznania się z zasobami sprzętowymi można sko-
rzystać z opcji Urządzenia według połączeń z menu Widok menedżera urządzeń.

Biblioteka Setupapi
Biblioteka Setupapi zawiera m.in. funkcje (będące odpowiednikami funkcji z modułu
setupapi.h) związane z instalacją urządzeń. W tabeli 5.4 przedstawiono funkcje po-
mocne w wyszukaniu konkretnego urządzenia lub klasy urządzeń w systemie i pobie-
rające szczegółowe informacje o interfejsie dostępne z poziomu biblioteki Setupapi
wraz z ich odpowiednikami z modułu setupapi.h.

Tabela 5.4. Podstawowe funkcje eksportowane z biblioteki Setupapi.dll


Setupapi.h Setupapi.dll
SetupDiGetClassDevs() SetupDiGetClassDevsA()
SetupDiEnumDeviceInterfaces() SetupDiEnumDeviceInterfaces()
SetupDiDestroyDeviceInfoList() SetupDiDestroyDeviceInfoList()
SetupDiGetDeviceInterfaceDetail() SetupDiGetDeviceInterfaceDetailA()
SetupDiGetDeviceRegistryProperty() SetupDiGetDeviceRegistryPropertyA()

Bibliotekę Setupapi można łączyć z programem w sposób statyczny, korzystając z mo-


dułu Setupapi.lib lub dynamicznie wykorzystując Setupapi.dll. Należy zwrócić uwagę
na fakt, że niektóre (starsze) kompilatory C++ mogą niewłaściwie odwzorowywać
identyfikator biblioteki łączonej statycznie w przestrzeni adresowej głównego procesu
(programu wykonawczego). Dlatego też dużo bezpieczniejszym sposobem wykorzy-
stania zasobów Setupapi jest dynamiczne odwzorowywanie identyfikatora Setupapi.dll
w przestrzeni adresowej głównego procesu.

Na listingu 5.10 zaprezentowano kod programu posługującego się funkcjami ekspor-


towanymi z biblioteki Setupapi.dll. Kod ten jest również ilustracją kolejnego sposobu
importowania funkcji z biblioteki dołączanej dynamicznie.

Listing 5.10. Kod modułu usb_R5_8.cpp


#include <windows>
#pragma option push -a1
#include <setupapi>
#pragma option pop
#include <assert>
#include <iostream>

using namespace std;


Rozdział 5. ♦ Detekcja i identyfikacja urządzeń dołączonych do magistrali USB 183

void displayError(const char* msg){


cout << msg << endl;
system("PAUSE");
exit(0);
};
//---------------------------------------------------------
template <class T>
inline void releaseMemory(T &x)
{
assert(x != NULL);
delete [] x;
x = NULL;
}
//---------------------------------------------------------
/* A5DCBF10-6530-11D2-901F-00C04FB951ED */
static GUID GUID_DEVINTERFACE_USB_DEVICE =
{0xA5DCBF10, 0x6530, 0x11D2, {0x90, 0x1F, 0x00, 0xC0,
0x4F, 0xB9, 0x51, 0xED}};

/* 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(){

typedef void* (__stdcall *pSetupDiGetClassDevs)


(IN LPGUID ClassGuid, OPTIONAL
IN PCTSTR Enumerator, OPTIONAL
IN HWND hwndParent, OPTIONAL
IN DWORD Flags);

typedef bool (__stdcall* pSetupDiEnumDeviceInterfaces)


(IN HDEVINFO DeviceInfoSet,
IN PSP_DEVINFO_DATA DeviceInfoData, OPTIONAL
IN LPGUID InterfaceClassGuid,
IN DWORD MemberIndex,
OUT PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData);

typedef bool (__stdcall *pSetupDiDestroyDeviceInfoList)(IN void*);

typedef bool (__stdcall *pSetupDiGetDeviceInterfaceDetail)


(IN HDEVINFO DeviceInfoSet,
184 USB. Praktyczne programowanie z Windows API w C++

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.");

pSetupDiGetClassDevs SetupDiGetClassDevsA = NULL;


SetupDiGetClassDevsA = (pSetupDiGetClassDevs)GetProcAddress(hSetupApi,
"SetupDiGetClassDevsA");

pSetupDiEnumDeviceInterfaces SetupDiEnumDeviceInterfaces = NULL;


SetupDiEnumDeviceInterfaces = (pSetupDiEnumDeviceInterfaces)
GetProcAddress(hSetupApi, "SetupDiEnumDeviceInterfaces");

pSetupDiDestroyDeviceInfoList SetupDiDestroyDeviceInfoList = NULL;


SetupDiDestroyDeviceInfoList = (pSetupDiDestroyDeviceInfoList)
GetProcAddress(hSetupApi, "SetupDiDestroyDeviceInfoList");

pSetupDiGetDeviceInterfaceDetail SetupDiGetDeviceInterfaceDetailA = NULL;


SetupDiGetDeviceInterfaceDetailA = (pSetupDiGetDeviceInterfaceDetail)
GetProcAddress(hSetupApi, "SetupDiGetDeviceInterfaceDetailA");

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

cout << deviceInterfaceDetailData->DevicePath << endl;


releaseMemory(deviceInterfaceDetailData);
}; //koniec while
SetupDiDestroyDeviceInfoList(deviceInfoSet);
cout << endl;
FreeLibrary(hSetupApi);
system("PAUSE");
return 0;
}
//---------------------------------------------------------

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.

W trakcie działania programu obsługującego zewnętrzne urządzenie USB istnieje moż-


liwość powiadamiania użytkownika o dołączaniu lub odłączaniu urządzenia. W celu
zaprogramowania tego typu funkcjonalności aplikacji w pierwszej kolejności powinni-
śmy uzyskać interesujący nas identyfikator GUID klasy urządzeń. Następnie należy od-
powiednio wypełnić (patrz tabela 5.5) pola struktury DEV_BROADCAST_DEVICEINTERFACE
z modułu Dbt.h:
typedef struct _DEV_BROADCAST_DEVICEINTERFACE {
DWORD dbcc_size;
DWORD dbcc_devicetype;
DWORD dbcc_reserved;
GUID dbcc_classguid;
TCHAR dbcc_name[1];
} DEV_BROADCAST_DEVICEINTERFACE *PDEV_BROADCAST_DEVICEINTERFACE;

Tabela 5.5. Specyfikacja struktury DEV_BROADCAST_DEVICEINTERFACE


Typ Element struktury Znaczenie
DWORD dbcc_size Rozmiar struktury w bajtach plus długość łańcucha znaków
wpisanego w polu dbcc_name (jeżeli jest używane)
DWORD dbcc_devicetype DBT_DEVTYP_DEVICEINTERFACE
DWORD dbcc_reserved Zarezerwowane
GUID dbcc_classguid Identyfikator GUID klasy urządzeń
TCHAR dbcc_name Łańcuch znaków (zakończony zerowym ogranicznikiem)
określający nazwę urządzenia
186 USB. Praktyczne programowanie z Windows API w C++

Kolejnym krokiem jest odpowiednie zarejestrowanie powiadomienia poprzez zadekla-


rowaną w module windows.h funkcję:
HDEVNOTIFY WINAPI RegisterDeviceNotification(
IN HANDLE hRecipient,
IN LPVOID NotificationFilter,
IN DWORD Flags
);

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).

Na listingu 5.11 zaprezentowano kod umożliwiający realizację powiadomień o dołą-


czaniu lub odłączaniu urządzeń klasy HID w trakcie działania programu. Odpowied-
nie komunikaty są przetwarzane w pętli wywołań funkcji API SDK GetMessage(),
TranslateMessage() oraz DispatchMessage() i wyświetlane bezpośrednio na pulpicie
(program posługuje się funkcją WinMain()). Powiadomienia o zmianie stanu dołącza-
nego lub odłączanego urządzenia USB są generowane w postaci standardowych komu-
nikatów WM_DEVICECHANGE w funkcji API SDK:
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg,
WPARAM wParam, LPARAM lParam);

Wysyła ona komunikat identyfikowany przez uMsg do okna identyfikowanego przez


parametr hwnd.

Listing 5.11. Kod modułu usb_R5_9.cpp


#include <objbase.h>
#include <Dbt.h>
#include <windows.h>

static GUID GUID_DEVINTERFACE_USB_DEVICE =


{0xA5DCBF10, 0x6530, 0x11D2, {0x90, 0x1F, 0x00, 0xC0,
0x4F, 0xB9, 0x51, 0xED}};

//---------------------------------------------------------
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;
}

hWnd = CreateWindowEx(WS_EX_CLIENTEDGE,"DeviceNotifyTest"," ",WS_OVERLAPPEDWINDOW,


CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, NULL, NULL,
hInstance, NULL);
if(hWnd == NULL){
MessageBox(NULL, "Błąd utworzenia okna!", "Błąd!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}

if(!hidDeviceNotify(NULL, GUID_DEVINTERFACE_USB_DEVICE, &hDeviceNotify)){


MessageBox(NULL, "Błąd wykonania funkcji hidDeviceNotify().",
"Błąd!", MB_ICONEXCLAMATION | MB_OK);
return 1;
}
MessageBox(NULL, "Funkcja hidDeviceNotify() wykonana pomyślnie.",
"Uwaga!", MB_ICONEXCLAMATION | MB_OK);

while(GetMessage(&Msg, NULL, 0, 0) > 0){


TranslateMessage(&Msg);
188 USB. Praktyczne programowanie z Windows API w C++

DispatchMessage(&Msg);
}
return Msg.wParam;
}
//---------------------------------------------------------

Analiza kodu pozwala zauważyć, że program zostanie uruchomiony w głównym wąt-


ku systemu operacyjnego i pozostanie tam rezydentny (będzie cyklicznie odbierał ko-
munikaty od sterowników zgodnie z modelem warstwowym systemu USB), dopóki
nie zostanie przez użytkownika usunięty z pamięci lub dopóki system operacyjny nie
zostanie zrestartowany, tak jak pokazano na rysunku 5.14.

Rysunek 5.14.
Komunikaty
o odłączaniu
i przyłączaniu
urządzeń

Niedogodności związane z działaniem i obsługą programu opartego na kodzie z listingu


5.10 można rozwiązać w prosty sposób, który polega na zaprogramowaniu interfejsu
użytkownika w standardowej funkcji main(), tak jak pokazano na listingu 5.12. Na ry-
sunku 5.15 zobrazowano wynik działania tak zmodyfikowanego kodu podczas testów
polegających na przyłączaniu i odłączaniu urządzeń USB w trakcie działania programu.

Listing 5.12. Fragment kodu modułu usb_R5_10.cpp


//---------------------------------------------------------
int main()
{
const char * const className = "DeviceNotifyTest";
HDEVNOTIFY hDeviceNotify;
WNDCLASS wincl = {0};
wincl.hInstance = GetModuleHandle(0);
wincl.lpszClassName = className;
wincl.lpfnWndProc = WindowProc;

if (!RegisterClass(&wincl)) {
DWORD error = GetLastError();
cout << "Błędne wykonanie RegisterClass(), błąd = " << error << endl;
return 1;
}

HWND hwnd = CreateWindowEx(0, className, className,


0, 0, 0, 0, 0, 0, 0, 0, 0);
Rozdział 5. ♦ Detekcja i identyfikacja urządzeń dołączonych do magistrali USB 189

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.

Składnia CreateFile() wygląda następująco:


HANDLE WINAPI CreateFile(IN LPCTSTR lpFileName, DWORD dwDesiredAccess,
IN DWORD dwShareMode,
IN LPSECURITY_ATTRIBUTES lpSecurityAttributes,
IN DWORD dwCreationDisposition,
IN DWORD dwFlagsAndAttributes,
IN HANDLE hTemplateFile);

Pierwszy parametr, lpFileName, jest wskaźnikiem do zadeklarowanego ciągu znaków


zakończonego zerowym ogranicznikiem (dokładniej: do pierwszego znaku tego łań-
cucha), w którym będzie przechowywana pełna nazwa symboliczna urządzenia (patrz
rozdział 5., tabela 5.3).

Jeżeli lpFileName reprezentuje nazwę, to w standardzie ANSI wartość domyślna dłu-


gości łańcucha jest ograniczona do MAX_PATH znaków. Wykorzystując rozszerzoną wer-
sję tej funkcji (standard Unicode), można pokonać ograniczenie co do długości oma-
wianego ciągu znaków, posługując się konwencją nazewnictwa UNC (ang. Universal
Naming Convention).

Parametr dwDesiredAccess zawiera specyfikację rodzaju dostępu do obiektu. Może sta-


nowić kombinację następujących wartości:
 GENERIC_ALL — przyznanie aplikacji praw do zapisu, odczytu
i uruchamiania pliku;
 GENERIC_EXECUTE — dostęp do uruchamiania pliku;
 GENERIC_READ — dostęp do odczytu z pliku lub urządzenia;
 GENERIC_WRITE — dostęp do zapisu do pliku lub urządzenia;
 0 — bez dostępu, na przykład tylko tworzenie nowego pliku.

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.

Opcjonalnie używany parametr lpSecurityAttributes jest wskaźnikiem do struktury


SECURITY_ATTRIBUTES zawierającej deskryptor zabezpieczeń obiektu i określającej, czy
zwracany identyfikator jest dziedziczony.
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych 193

typedef struct _SECURITY_ATTRIBUTES {


DWORD nLength;
LPVOID lpSecurityDescriptor;
BOOL bInheritHandle;
} SECURITY_ATTRIBUTES;

nLength to rozmiar struktury w bajtach, a lpSecurityDescriptor jest wskaźnikiem do


deskryptora zabezpieczeń obiektu. Jeżeli ustalono NULL, zostanie wybrana wartość
domyślna. Pole bInheritHandle pozwala określić, czy zwracany przez CreateFile()
identyfikator jest dziedziczony przy tworzeniu nowego procesu. Wartość TRUE ozna-
cza, że nowy proces dziedziczy ten identyfikator.

Parametr wejściowy dwCreationDisposition funkcji CreateFile() określa rodzaje


operacji wykonywanych na pliku i może być reprezentowany przez następujące stałe
symboliczne:
 CREATE_NEW — utworzenie nowego pliku; funkcja nie będzie wykonana
pomyślnie, jeżeli plik już istnieje;
 CREATE_ALWAYS — utworzenie nowego pliku niezależnie od tego, czy ten
już istnieje; jeżeli plik istnieje, nowy zostanie zapisany na istniejącym;
 OPEN_EXISTING — otwarcie istniejącego pliku; jeżeli plik nie istnieje, funkcja
nie zostanie wykonana pomyślnie;
 OPEN_ALWAYS — otwarcie istniejącego pliku; jeżeli taki nie istnieje, zostanie
stworzony identycznie jak za pomocą CREATE_NEW;
 TRUNCATE_EXISTING — tuż po otwarciu plik zostaje okrojony do rozmiaru
0 bajtów, ale wymagane jest wcześniejsze jego utworzenie przynajmniej
z rodzajem dostępu GENERIC_WRITE; funkcja nie będzie wykonana pomyślnie,
jeżeli plik nie istnieje.

Parametr wejściowy dwFlagsAndAttributes funkcji CreateFile() określa atrybuty


i znaczniki wykorzystywane podczas operacji na plikach. Atrybuty mogą być repre-
zentowane przez następujące stałe symboliczne:
 FILE_ATTRIBUTE_OFFLINE — dane zawarte w pliku nie są bezpośrednio
udostępniane;
 FILE_ATTRIBUTE_READONLY — plik tylko do odczytu;
 FILE_ATTRIBUTE_SYSTEM — plik jest częścią systemu operacyjnego
lub jest używany wyłącznie przez system operacyjny;
 FILE_ATTRIBUTE_TEMPORARY — plik jest używany do czasowego
przechowywania danych i powinien być usunięty, jeżeli nie jest
wykorzystywany.

Znaczniki są reprezentowane następująco:


 FILE_FLAG_WRITE_THROUGH — zawartość pliku zostaje zapisana pośrednio
poprzez bufor;
194 USB. Praktyczne programowanie z Windows API w C++

 FILE_FLAG_OPEN_NO_RECALL — używane dane powinny pozostać na zdalnym


dysku;
 FILE_FLAG_OPEN_REPARSE_POINT — pozwala na podłączenie do lokalnego,
pustego katalogu na partycji NTFS dowolnego katalogu znajdującego się
na lokalnym lub zdalnym dysku;
 FILE_FLAG_OVERLAPPED — stosowany jest w przypadku asynchronicznych
operacji wykonywanych przez dłuższy czas przez funkcje ReadFile(),
WriteFile(), ConnectNamedPipe() i TransactNamedPipe(); w tym kontekście
musi nastąpić odwołanie do struktury OVERLAPPED zawierającej informacje
używane w asynchronicznych operacjach wejścia-wyjścia.

Prawidłowo wywołana funkcja CreateFile() zwraca identyfikator pliku sterownika


urządzenia. Jeżeli plik został już otwarty przed wywołaniem funkcji z CREATE_ALWAYS
lub OPEN_ALWAYS, przypisanymi do dwCreationDisposition, funkcja GetLastError()
zwróci wartość ERROR_ALREADY_EXISTS. Jeżeli plik sterownika nie istnieje, GetLast-
Error() zwraca 0. W przypadku gdy funkcja CreateFile() nie została wykonana po-
myślnie, należy oczekiwać wartości INVALID_HANDLE_VALUE.

W Windows 98 dostęp do pliku sterownika urządzenia HID (np. pliku sterownika


klawiatury) mógł w tym samym czasie uzyskać więcej niż jeden program. Należy być
świadomym faktu, że późniejsze wersje systemu Windows uniemożliwiają aplika-
cjom użytkownika uzyskiwanie swobodnego dostępu do wielu standardowych ste-
rowników, np. sterowników klawiatury czy myszy. Sterowniki tego typu odblokowują
urządzenia w trybie zakazu współdzielenia (ang. exclusive access). Więcej informa-
cji na ten temat należy szukać w dokumentacji systemu operacyjnego.

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.

Przykładowy program środowiska tekstowego


Aby odczytać kompletną listę atrybutów urządzeń, w pełni zidentyfikować ich produ-
centów i pobrać inne istotne informacje, w pierwszej kolejności należy pobrać ścieżki
dostępu do interfejsów (patrz rozdział 5.) i odblokować aktualnie dostępne w systemie
sterowniki urządzeń USB. Na rysunku 6.1 pokazano uogólniony diagram czynności
dla operacji otwarcia do transmisji aktualnie dostępnych urządzeń USB. Prawidło-
we pobranie identyfikatora hidDeviceObject zwracanego przez funkcję CreateFile()
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych 195

jest równoznaczne z odblokowaniem wybranego urządzenia. Po odblokowaniu z urzą-


dzeniem można się komunikować na różne sposoby. W omawianym przykładzie od
aktualnie dostępnych urządzeń klasy HID poprzez bufor danych są pobierane dane na
temat ich identyfikatorów VID/PID oraz numeru wersji i producenta (funkcje HidD_
GetAttributes(), HidD_GetProductString(), HidD_GetManufacturerString()). Ponad-
to program pobiera deskryptory urządzenia: fizyczny i określony przez Microsoft (funk-
cje HidD_GetPhysicalDescriptor() i HidD_GetMsGenreDescriptor()). Po odczytaniu
interesujących nas informacji uprzednio odblokowane sterowniki urządzeń USB zo-
stają zamknięte, a pozostałe zasoby zaalokowane przez program są zwalniane.

Rysunek 6.1. Diagram czynności dla operacji enumeracji z wykorzystaniem funkcji CreateFile()

Na listingu 6.1 zamieszczono kod modułu usb_R6_1.cpp będącego uszczegółowioną


implementacją diagramu z rysunku 6.1. Rysunek 6.2 przedstawia program w trakcie
działania.
196 USB. Praktyczne programowanie z Windows API w C++

Listing 6.1. Odblokowanie i odczytanie podstawowych informacji o urządzeniach klasy HID


#include <windows>
#pragma option push -a1
#include <setupapi>
#pragma option pop
#include <iostream>
#include <assert>

#define bufferLength 256

using namespace std;

void displayError(const char* msg){


cout << msg << endl;
system("PAUSE");
exit(0);
};
//---------------------------------------------------------
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;
//---------------------------------------------------------
typedef struct _HIDD_ATTRIBUTES
{
ULONG Size;
USHORT VendorID;
USHORT ProductID;
USHORT VersionNumber;
} HIDD_ATTRIBUTES, *PHIDD_ATTRIBUTES;
//---------------------------------------------------------
HANDLE hidDeviceObject = INVALID_HANDLE_VALUE;
HIDD_ATTRIBUTES hiddAttributes;
wchar_t buffer[bufferLength];

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_GetHidGuid=GetProcAddress(hHidLib, "HidD_GetHidGuid");


(FARPROC&) HidD_GetAttributes=GetProcAddress(hHidLib, "HidD_GetAttributes");
(FARPROC&) HidD_GetProductString=GetProcAddress(hHidLib,
"HidD_GetProductString");
(FARPROC&) HidD_GetManufacturerString=GetProcAddress(hHidLib,
"HidD_GetManufacturerString");

(FARPROC&) HidD_GetPhysicalDescriptor=GetProcAddress(hHidLib,
"HidD_GetPhysicalDescriptor");

(FARPROC&) HidD_GetMsGenreDescriptor=GetProcAddress(hHidLib,
"HidD_GetMsGenreDescriptor");

if (!HidD_GetHidGuid || !HidD_GetAttributes || !HidD_GetProductString ||


!HidD_GetManufacturerString || !HidD_GetPhysicalDescriptor ||
!HidD_GetMsGenreDescriptor){
FreeLibrary(hHidLib);
displayError("Nie znaleziono żadnych funkcji eksportowych.\n");
}

HidD_GetHidGuid(&classGuid);

deviceInfoSet = SetupDiGetClassDevs(&classGuid, NULL, NULL,


DIGCF_PRESENT | DIGCF_INTERFACEDEVICE);
if (deviceInfoSet == INVALID_HANDLE_VALUE){
FreeLibrary(hHidLib);
displayError("Nie zidentyfikowano podłączonych urządzeń.\n");
}

deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
hiddAttributes.Size = sizeof(HIDD_ATTRIBUTES);

while(SetupDiEnumDeviceInterfaces(deviceInfoSet, NULL, &classGuid,


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");
}
198 USB. Praktyczne programowanie z Windows API w C++

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_GetManufacturerString(hidDeviceObject, buffer, bufferLength);


printf("Producent = %ls\n", buffer);

HidD_GetAttributes(hidDeviceObject, &hiddAttributes);
printf("VID/PID/wersja = %x/%x/%x\n",hiddAttributes.VendorID,
hiddAttributes.ProductID,
hiddAttributes.VersionNumber);

HidD_GetProductString(hidDeviceObject, buffer, bufferLength);


printf("Urządzenie = %ls\n", buffer);

HidD_GetPhysicalDescriptor(hidDeviceObject, buffer, bufferLength);


printf("Deskryptor fizyczny = %ls\n", buffer);

HidD_GetMsGenreDescriptor(hidDeviceObject, buffer, bufferLength);


printf("Deskryptor Microsoft = %ls\n", buffer);

CloseHandle(hidDeviceObject);
}
releaseMemory(deviceInterfaceDetailData);
};//koniec while
SetupDiDestroyDeviceInfoList(deviceInfoSet);
FreeLibrary(hHidLib);
cout << endl;
system("PAUSE");
return 0;
}
//---------------------------------------------------------

Rysunek 6.2. Aplikacja proj_USB_R6_1 w trakcie działania

Odczyt danych w formie raportu


Aplikacje użytkownika mogą korzystać z funkcji HidD_GetXxx() w celu odczytywania
(pobrania) raportów wejściowych i konfiguracyjnych pochodzących z aktualnie do-
stępnego w systemie urządzenia. Niemniej funkcji tych powinno się używać jedynie
do odczytywania aktualnego stanu urządzenia. Należy zwrócić uwagę na fakt, że wiele
urządzeń może nieprawidłowo odpowiadać na cykliczne żądania wysyłane przez funk-
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych 199

cje HidD_GetXxxx(). W tabeli 6.1 zamieszczono wykaz funkcji, którymi aplikacje użyt-
kownika mogą się bezpiecznie posługiwać w trakcie transferu danych z urządzeń przy-
łączonych do magistrali USB.

Tabela 6.1. Funkcje używane podczas transferu raportów wejściowych


Typ raportu Funkcje API WDK/SDK Typ transferu danych
Wejściowy IN (Input) HidD_GetInputReport() Kontrolny z rozkazem Get_Report
ReadFile() Przerwaniowy IN
Konfiguracyjny IN (Feature IN) HidD_GetFeature() Kontrolny z rozkazem Get_Report

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);

Użycie jej w programie zapewni odczytanie wszystkich danych przychodzących do


urządzenia identyfikowanego przez hCommDev. Parametr lpBuffer jest wskaźnikiem do bufo-
ra danych, przez który będziemy odczytywać wszystkie informacje, nNumberOfBytesToRead
określa liczbę bajtów do odebrania, a lpNumberOfBytesRead wskazuje liczbę rzeczywi-
ście odebranych bajtów. Pole danych odbieranego pakietu może posiadać rozmiar
mniejszy, niż jest to zapisane w polu wMaxPacketSize deskryptora punktu końcowe-
go — wówczas taki pakiet jest ostatni w serii. Aby kontrolować liczbę bajtów, które zo-
stały rzeczywiście przeczytane i alokowane w buforze wejściowym odbiornika, funkcja
umieszcza ją w zmiennej lpNumberOfBytesRead, stanowiącej przedostatni parametr.
Wskaźnik lpOverlapped zostanie tymczasowo zignorowany.

Analizując rysunek 6.2, bez trudu zauważymy, że w zaprezentowanym tu systemie


jednym z dostępnych aktualnie urządzeń USB klasy HID jest uniwersalny kontroler
gier. Ze względu na to, że zapewne większość Czytelników posiada tego typu (lub po-
dobne) urządzenie, właśnie je wybierzemy do dalszych testów.

Listing 6.2 prezentuje kompletny kod 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++

if (NULL != strstr(deviceInterfaceDetailData->DevicePath, "vid_22ba")){


//...
}

W omawianym przykładzie zakładamy ponadto, że celem jest synchroniczne i cykliczne


odbieranie raportów wejściowych w trybie przerwaniowego transferu danych, tak jak
pokazano na listingu 6.2. Koniec cyklicznego odczytu danych nastąpi w momencie,
gdy użytkownik naciśnie na panelu sterowniczym urządzenia przycisk Select (przycisk
o indeksie 11 — patrz rysunek 6.4). W testowanym urządzeniu naciśnięcie tego przy-
cisku powoduje umieszczenie w szóstym bajcie (polu) raportu dziesiętnej wartości 64.
Zerowy bajt raportu zawiera jego identyfikator (Raport ID). Na rysunku 6.3 zaprezen-
towano omawiany program w trakcie działania.

Listing 6.2. Kod modułu usb_R6_2.cpp


#include <windows>
#pragma option push -a1
#include <setupapi>
#pragma option pop
#include <iostream>
#include <assert>

#define bufferLength 32

using namespace std;

void displayError(const char* msg){


cout << msg << endl;
system("PAUSE");
exit(0);
};
//---------------------------------------------------------
template <class T>
inline void releaseMemory(T &x)
{
assert(x != NULL);
delete [] x;
x = NULL;
}
//---------------------------------------------------------
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;

BYTE inputReportBuffer[bufferLength]; //bufor danych wejściowych


DWORD numberOfBytesRead = 0;

int main()
{
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych 201

void (__stdcall *HidD_GetHidGuid)(OUT LPGUID HidGuid);


bool (__stdcall*HidD_GetNumInputBuffers)(IN HANDLE HidDeviceObject,
OUT PULONG NumberBuffers);

hHidLib = LoadLibrary("C:\\Windows\\System32\\HID.DLL");
if (!hHidLib)
displayError("Błąd dołączenia biblioteki HID.DLL.");

(FARPROC&) HidD_GetHidGuid=GetProcAddress(hHidLib, "HidD_GetHidGuid");


(FARPROC&) HidD_GetNumInputBuffers=GetProcAddress(hHidLib,
"HidD_GetNumInputBuffers");

if (!HidD_GetHidGuid){
FreeLibrary(hHidLib);
displayError("Nie znaleziono żadnych funkcji eksportowych.\n");
}

HidD_GetHidGuid(&classGuid);

deviceInfoSet = SetupDiGetClassDevs(&classGuid, NULL, NULL,


DIGCF_PRESENT | DIGCF_INTERFACEDEVICE);
if (deviceInfoSet == INVALID_HANDLE_VALUE){
FreeLibrary(hHidLib);
displayError("Nie zidentyfikowano podłączonych urządzeń.\n");
}

deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);

while(SetupDiEnumDeviceInterfaces(deviceInfoSet, NULL, &classGuid,


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,
NULL, NULL)){
releaseMemory(deviceInterfaceDetailData);
SetupDiDestroyDeviceInfoList(deviceInfoSet);
displayError("Nie można pobrać informacji o interfejsie.\n");
}

if (NULL != strstr(deviceInterfaceDetailData->DevicePath, "vid_22ba")){


cout << "\n"<< deviceInterfaceDetailData->DevicePath << "\n";
hidDeviceObject=CreateFile(deviceInterfaceDetailData->DevicePath,
GENERIC_READ, FILE_SHARE_READ,
NULL,OPEN_EXISTING,0,NULL);
if(hidDeviceObject==INVALID_HANDLE_VALUE)
displayError("Nie można otworzyć urządzenia do transmisji.");
else
break;
}
releaseMemory(deviceInterfaceDetailData);
};//koniec while
202 USB. Praktyczne programowanie z Windows API w C++

SetupDiDestroyDeviceInfoList(deviceInfoSet);

ULONG numberBuffers; //pobranie maks. długości raportu wejściowego


HidD_GetNumInputBuffers(hidDeviceObject, &numberBuffers);
printf("liczba buforów wejściowych = %d\n",numberBuffers);

while(true) { //cykliczny odczyt danych


memset(&inputReportBuffer, 0x00, sizeof(inputReportBuffer));
ReadFile(hidDeviceObject, inputReportBuffer, sizeof(inputReportBuffer),
&numberOfBytesRead, NULL);
printf("%d %d %d %d %d %d %d %d\n", inputReportBuffer[0],
inputReportBuffer[1], inputReportBuffer[2], inputReportBuffer[3],
inputReportBuffer[4], inputReportBuffer[5], inputReportBuffer[6],
inputReportBuffer[7]);
//Odczyt raportu wejściowego kończy naciśnięcie przycisku Select / (11)
//uniwersalnego kontrolera gier
if(inputReportBuffer[6]==64)
break;
}
CloseHandle(hidDeviceObject);
FreeLibrary(hHidLib);
cout << endl;
system("PAUSE");
return 0;
}
//---------------------------------------------------------

Rysunek 6.3.
Aplikacja
proj_USB_R6_2
w trakcie cyklicznego
odczytywania
raportów
pochodzących
z uniwersalnego
kontrolera gier

Wynik działania aplikacji proj_USB_R6_2 można w prosty sposób zweryfikować, po-


równując go z dostępnymi z poziomu Panelu sterowania (rysunek 6.4) właściwościa-
mi uniwersalnego kontrolera gier. Na rysunku 6.5 pokazano format raportu wejścio-
wego, za pomocą którego urządzenie (a dokładniej jego sterownik) komunikuje się
z aplikacją użytkownika.

Warto zwrócić uwagę, że konstruując bardziej zaawansowane aplikacje, możemy


w trakcie odczytu zawartości bufora wejściowego cyklicznie wywoływać funkcję
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych 203

Rysunek 6.4. Właściwości uniwersalnego kontrolera gier

Rysunek 6.5. Format raportu wejściowego dla typowego uniwersalnego kontrolera gier

HidP_GetUsagesEx() (patrz rozdział 4.) w celu określenia numeru aktualnie naciśnię-


tego przycisku znajdującego się na panelu sterowniczym urządzenia. W takiej sytuacji
nie trzeba znać zawartości raportu wejściowego, aby w odpowiedzi na naciśnięcie wy-
branego przycisku program wykonał jakąś akcję, na przykład zakończył odczyt danych.

Odczyt długości bufora danych


W kodzie z listingu 6.2 rozmiar bufora danych wejściowych w funkcji ReadFile() zo-
stał ustalony arbitralnie. Istnieją jednak sytuacje, w których takie postępowanie może
się okazać mało efektywne. Projektując oprogramowanie przeznaczone do współpra-
cy z konkretną klasą urządzeń, zawsze można skorzystać z zasobów struktur HIDP_CAPS,
HIDP_PREPARSED_DATA i rodzin współpracujących funkcji. W obecnie omawianym przy-
kładzie aktualny rozmiar bufora danych wejściowych jest odczytywany poprzez pole
InputReportByteLength struktury HIDP_CAPS. Warto zwrócić uwagę, że ten sposób okre-
ślania rozmiaru bufora danych wymaga użycia przynajmniej funkcji: HidP_GetCaps(),
HidD_GetPreparsedData() i HidD_FreePreparsedData(), tak jak zaprezentowano na li-
stingu 6.3.
204 USB. Praktyczne programowanie z Windows API w C++

Listing 6.3. Kod modułu usb_R6_3.cpp


#include <windows>
#pragma option push -a1
#include <setupapi>
#pragma option pop
#include <iostream>
#include <assert>
using namespace std;

void displayError(const char* msg){


cout << msg << endl;
system("PAUSE");
exit(0);
};
//---------------------------------------------------------
template <class T>
inline void releaseMemory(T &x)
{
assert(x != NULL);
delete [] x;
x = NULL;
}
//---------------------------------------------------------
typedef USHORT USAGE, *PUSAGE;
typedef struct _HIDP_PREPARSED_DATA *PHIDP_PREPARSED_DATA;
PHIDP_PREPARSED_DATA preparsedData;

typedef struct _HIDP_CAPS {


USAGE Usage;
USAGE UsagePage;
USHORT InputReportByteLength;
USHORT OutputReportByteLength;
USHORT FeatureReportByteLength;
USHORT Reserved[17];
USHORT NumberLinkCollectionNodes;
USHORT NumberInputButtonCaps;
USHORT NumberInputValueCaps;
USHORT NumberInputDataIndices;
USHORT NumberOutputButtonCaps;
USHORT NumberOutputValueCaps;
USHORT NumberOutputDataIndices;
USHORT NumberFeatureButtonCaps;
USHORT NumberFeatureValueCaps;
USHORT NumberFeatureDataIndices;
} HIDP_CAPS, *PHIDP_CAPS;
//---------------------------------------------------------
typedef struct _HIDP_LINK_COLLECTION_NODE {
USAGE LinkUsage;
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;
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych 205

//---------------------------------------------------------
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;

HANDLE hidDeviceObject = INVALID_HANDLE_VALUE;

BYTE *inputReportBuffer; //bufor danych wejściowych


DWORD numberOfBytesRead = 0;

int main()
{
void (__stdcall *HidD_GetHidGuid)(OUT LPGUID HidGuid);

long (__stdcall* HidP_GetCaps)(IN PHIDP_PREPARSED_DATA PreparsedData,


OUT PHIDP_CAPS Capabilities);
long (__stdcall* HidP_GetLinkCollectionNodes)(OUT PHIDP_LINK_COLLECTION_NODE\
LinkCollectionNodes,
IN OUT PULONG LinkCollectionNodesLength,
IN PHIDP_PREPARSED_DATA PreparsedData);

bool (__stdcall* HidD_GetPreparsedData)(IN HANDLE HidDeviceObject,


OUT PHIDP_PREPARSED_DATA *PreparsedData);

bool (__stdcall* HidD_FreePreparsedData)(IN PHIDP_PREPARSED_DATA PreparsedData);

hHidLib = LoadLibrary("C:\\Windows\\System32\\HID.DLL");
if (!hHidLib)
displayError("Błąd dołączenia biblioteki HID.DLL.");

(FARPROC&) HidD_GetHidGuid=GetProcAddress(hHidLib, "HidD_GetHidGuid");


(FARPROC&) HidP_GetCaps=GetProcAddress(hHidLib,
"HidP_GetCaps");
(FARPROC&) HidD_GetPreparsedData=GetProcAddress(hHidLib,
"HidD_GetPreparsedData");
(FARPROC&) HidD_FreePreparsedData=GetProcAddress(hHidLib,
"HidD_FreePreparsedData");

(FARPROC&) HidP_GetLinkCollectionNodes=GetProcAddress(hHidLib,
"HidP_GetLinkCollectionNodes");

if (!HidD_GetHidGuid || !HidP_GetCaps || !HidD_GetPreparsedData


|| !HidD_FreePreparsedData || !HidP_GetLinkCollectionNodes){
FreeLibrary(hHidLib);
displayError("Nie znaleziono żadnych funkcji eksportowych.\n");
}

HidD_GetHidGuid(&classGuid);

deviceInfoSet = SetupDiGetClassDevs(&classGuid, NULL, NULL,


DIGCF_PRESENT | DIGCF_INTERFACEDEVICE);
206 USB. Praktyczne programowanie z Windows API w C++

if (deviceInfoSet == INVALID_HANDLE_VALUE){
FreeLibrary(hHidLib);
displayError("Nie zidentyfikowano podłączonych urządzeń.\n");
}

deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);

while(SetupDiEnumDeviceInterfaces(deviceInfoSet, NULL, &classGuid,


memberIndex, &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,
NULL, NULL)){
releaseMemory(deviceInterfaceDetailData);
SetupDiDestroyDeviceInfoList(deviceInfoSet);
displayError("Nie można pobrać informacji o interfejsie.\n");
}

if (NULL != strstr(deviceInterfaceDetailData->DevicePath, "vid_22ba")){


cout << "\n"<< deviceInterfaceDetailData->DevicePath << "\n";
hidDeviceObject=CreateFile(deviceInterfaceDetailData->DevicePath,
GENERIC_READ, FILE_SHARE_READ,
NULL,OPEN_EXISTING,0,NULL);
if(hidDeviceObject==INVALID_HANDLE_VALUE)
displayError("Nie można otworzyć urządzenia do transmisji.");
else
break;
}
releaseMemory(deviceInterfaceDetailData);
}; //koniec while
SetupDiDestroyDeviceInfoList(deviceInfoSet);

if(HidD_GetPreparsedData(hidDeviceObject, &preparsedData)){

HidP_GetCaps(preparsedData, &capabilities);
printf("Usage=%d\nUsagePage=%d\nInputReportByteLength=%d\n"
"OutputReportByteLength=%d\nFeatureReportByteLength=%d\n"
"NumberInputButtonCaps=%d\nNumberInputValueCaps=%d\n\n",
capabilities.Usage,capabilities.UsagePage,
capabilities.InputReportByteLength,
capabilities.OutputReportByteLength,
capabilities.FeatureReportByteLength,
capabilities.NumberInputButtonCaps,
capabilities.NumberInputValueCaps);

PULONG linkCollectionNodesLength;
linkCollectionNodes = new \
HIDP_LINK_COLLECTION_NODE[capabilities.NumberLinkCollectionNodes];

HidP_GetLinkCollectionNodes(linkCollectionNodes,
linkCollectionNodesLength,
preparsedData);
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych 207

printf("LinkUsage=%d\nLinkUsagePage=%d\nParent=%d\n"
"NumberOfChildren=%d\nCollectionType=%d\n\n",
linkCollectionNodes->LinkUsage,
linkCollectionNodes->LinkUsagePage,
linkCollectionNodes->Parent,
linkCollectionNodes->NumberOfChildren,
linkCollectionNodes->CollectionType);

inputReportBuffer = new BYTE[capabilities.InputReportByteLength];

while(true) { //cykliczny odczyt danych z wybranego urządzenia


memset(inputReportBuffer, 0x00, capabilities.InputReportByteLength);
ReadFile(hidDeviceObject, inputReportBuffer,
capabilities.InputReportByteLength,
&numberOfBytesRead, NULL);
if(numberOfBytesRead==capabilities.InputReportByteLength){
for(USHORT i=0; i<capabilities.InputReportByteLength; i++)
printf("%d ", inputReportBuffer[i]);
printf("\n");
}
else {
printf("Błędna liczba odebranych bajtów.\n",numberOfBytesRead);
break;
}
//Odczyt raportu wejściowego kończy naciśnięcie przycisku
//Select / (11) uniwersalnego kontrolera gier
if(inputReportBuffer[6]==64) {
HidD_FreePreparsedData(preparsedData);
CloseHandle(hidDeviceObject);
break;
}
}
}
releaseMemory(inputReportBuffer);
releaseMemory(linkCollectionNodes);
FreeLibrary(hHidLib);
cout << endl;
system("PAUSE");
return 0;
}
//---------------------------------------------------------

W trakcie testowania powyższego kodu łatwo zauważymy, że program dodatkowo


odczytuje wybrane właściwości aktualnie testowanego urządzenia. Sposób wykorzy-
stania funkcji HidP_GetLinkCollectionNodes() do pobierania wybranych pól struktury
HIDP_LINK_COLLECTION_NODE pokazuje rysunek 6.6.

Funkcja HidD_GetInputReport()
W przypadku gdy testowane urządzenie mogłoby się posługiwać kontrolnym transfe-
rem danych dla raportów wejściowych (tabela 6.1), w celu odczytania aktualnej po-
staci raportu wejściowego można użyć funkcji HidD_GetInputReport(). Pokazuje to
fragment kodu z listingu 6.4.
208 USB. Praktyczne programowanie z Windows API w C++

Rysunek 6.6.
Aplikacja
proj_USB_R6_3
w trakcie działania

Listing 6.4. Przykład wykorzystania funkcji HidD_GetInputReport()


//---------------------------------------------------------
BYTE *inputReportBuffer; //bufor danych wejściowych
//...
bool (__stdcall *HidD_GetInputReport)(IN HANDLE HidDeviceObject,
IN OUT PVOID ReportBuffer,
IN ULONG ReportBufferLength);
//...
(FARPROC&) HidD_GetInputReport=GetProcAddress(hHidLib,
"HidD_GetInputReport");
//...
inputReportBuffer = new BYTE[capabilities.InputReportByteLength];
HidD_GetInputReport(hidDeviceObject, inputReportBuffer,
capabilities.InputReportByteLength);
printf("%d %d %d %d %d %d %d\n", inputReportBuffer[0],
inputReportBuffer[1],//....);
//---------------------------------------------------------

Odczyt własności przycisków


Na listingu 6.5 pokazano przykład wykorzystania funkcji HidP_GetButtonCaps() i struk-
tury HIDP_BUTTON_CAPS przechowującej informacje na temat własności elementów typu
Button dla raportu wejściowego HidP_Input, którym aktualnie posługuje się dostępne
w systemie urządzenie USB. Przykład ten może być traktowany jako uzupełnienie kodu
z listingu 6.4. Na rysunku 6.7 pokazano wynik działania omawianego kodu.
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych 209

Listing 6.5. Przykład wykorzystania funkcji HidP_GetButtonCaps()


#include <windows>
#pragma option push -a1
#include <setupapi>
#pragma option pop
#include <iostream>
#include <assert>

#define HID_USAGE_PAGE_BUTTON ((USAGE) 0x09)


#define HID_USAGE_PAGE_GENERIC ((USAGE) 0x01) //hidusage.h
#define HID_USAGE_GENERIC_JOYSTICK ((USAGE) 0x04)

using namespace std;

void displayError(const char* msg){


cout << msg << endl;
system("PAUSE");
exit(0);
};
//---------------------------------------------------------
template <class T>
inline void releaseMemory(T &x)
{
assert(x != NULL);
delete [] x;
x = NULL;
}
//---------------------------------------------------------
typedef USHORT USAGE, *PUSAGE;
typedef struct _HIDP_PREPARSED_DATA *PHIDP_PREPARSED_DATA;
//typedef unsigned int NTSTATUS;
PHIDP_PREPARSED_DATA preparsedData;

typedef struct _HIDP_CAPS {


USAGE Usage;
USAGE UsagePage;
USHORT InputReportByteLength;
USHORT OutputReportByteLength;
USHORT FeatureReportByteLength;
USHORT Reserved[17];
USHORT NumberLinkCollectionNodes;
USHORT NumberInputButtonCaps;
USHORT NumberInputValueCaps;
USHORT NumberInputDataIndices;
USHORT NumberOutputButtonCaps;
USHORT NumberOutputValueCaps;
USHORT NumberOutputDataIndices;
USHORT NumberFeatureButtonCaps;
USHORT NumberFeatureValueCaps;
USHORT NumberFeatureDataIndices;
} HIDP_CAPS, *PHIDP_CAPS;
//---------------------------------------------------------
HIDP_CAPS capabilities;
GUID classGuid;
HMODULE hHidLib;
210 USB. Praktyczne programowanie z Windows API w C++

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);

void (__stdcall *HidD_GetHidGuid)(OUT LPGUID HidGuid);

long (__stdcall* HidP_GetCaps)(IN PHIDP_PREPARSED_DATA PreparsedData,


OUT PHIDP_CAPS Capabilities);

bool (__stdcall* HidD_GetPreparsedData)(IN HANDLE HidDeviceObject,


OUT PHIDP_PREPARSED_DATA *PreparsedData);

bool (__stdcall* HidD_FreePreparsedData)(IN PHIDP_PREPARSED_DATA PreparsedData);


Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych 211

hHidLib = LoadLibrary("C:\\Windows\\System32\\HID.DLL");
if (!hHidLib)
displayError("Błąd dołączenia biblioteki HID.DLL.");

(FARPROC&) HidD_GetHidGuid=GetProcAddress(hHidLib, "HidD_GetHidGuid");


(FARPROC&) HidP_GetCaps=GetProcAddress(hHidLib,
"HidP_GetCaps");
(FARPROC&) HidD_GetPreparsedData=GetProcAddress(hHidLib,
"HidD_GetPreparsedData");
(FARPROC&) HidD_FreePreparsedData=GetProcAddress(hHidLib,
"HidD_FreePreparsedData");
(FARPROC&) HidP_GetButtonCaps=GetProcAddress(hHidLib,
"HidP_GetButtonCaps");

if (!HidD_GetHidGuid || !HidP_GetCaps || !HidD_GetPreparsedData


|| !HidP_GetButtonCaps || !HidD_FreePreparsedData){
FreeLibrary(hHidLib);
displayError("Nie znaleziono żadnych funkcji eksportowych.\n");
}

HidD_GetHidGuid(&classGuid);

deviceInfoSet = SetupDiGetClassDevs(&classGuid, NULL, NULL,


DIGCF_PRESENT | DIGCF_INTERFACEDEVICE);
if (deviceInfoSet == INVALID_HANDLE_VALUE){
FreeLibrary(hHidLib);
displayError("Nie zidentyfikowano podłączonych urządzeń.\n");
}

deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);

while(SetupDiEnumDeviceInterfaces(deviceInfoSet, NULL, &classGuid,


memberIndex, &deviceInterfaceData)){
memberIndex++; //inkrementacja numeru interfejsu
SetupDiGetDeviceInterfaceDetail(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,
NULL, NULL)){
releaseMemory(deviceInterfaceDetailData);
SetupDiDestroyDeviceInfoList(deviceInfoSet);
displayError("Nie można pobrać informacji o interfejsie.\n");
}

if (NULL != strstr(deviceInterfaceDetailData->DevicePath, "vid_22ba")){


cout << "\n"<< deviceInterfaceDetailData->DevicePath << "\n";
hidDeviceObject=CreateFile(deviceInterfaceDetailData->DevicePath,
GENERIC_READ, FILE_SHARE_READ,
NULL,OPEN_EXISTING,0,NULL);
if(hidDeviceObject==INVALID_HANDLE_VALUE)
displayError("Nie można otworzyć urządzenia do transmisji.");
else
break;
}
releaseMemory(deviceInterfaceDetailData);
212 USB. Praktyczne programowanie z Windows API w C++

}; //koniec while
SetupDiDestroyDeviceInfoList(deviceInfoSet);

if(HidD_GetPreparsedData(hidDeviceObject, &preparsedData)){
HidP_GetCaps(preparsedData, &capabilities);

PULONG buttonCapsLength;
HIDP_BUTTON_CAPS *buttonCaps = new \
HIDP_BUTTON_CAPS[capabilities.NumberInputButtonCaps];

HidP_GetButtonCaps(HidP_Input, buttonCaps, buttonCapsLength, preparsedData);

for(USHORT i = 0; i<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

Odczyt własności wartości


Kod z listingu 6.6 to przykład wykorzystania funkcji HidP_GetValueCaps() i struktury
HIDP_VALUE_CAPS przechowującej informacje na temat własności elementów typu Value
dla raportu wyjściowego HidP_Output, którym aktualnie posługuje się dostępne w sys-
temie urządzenie USB. Przykład ten może być traktowany jako uzupełnienie kodu z li-
stingu 6.4. Na rysunku 6.8 pokazano wynik działania omawianego kodu.

Listing 6.6. Przykład wykorzystania funkcji HidP_GetValueCaps()


#include <windows>
#pragma option push -a1
#include <setupapi>
#pragma option pop
#include <iostream>
#include <assert>

#define HID_USAGE_PAGE_GENERIC ((USAGE) 0x01) //hidusage.h


#define HID_USAGE_PAGE_LED ((USAGE) 0x08)
#define HID_USAGE_GENERIC_JOYSTICK ((USAGE) 0x04)

using namespace std;

void displayError(const char* msg){


cout << msg << endl;
system("PAUSE");
exit(0);
};
//---------------------------------------------------------
template <class T>
214 USB. Praktyczne programowanie z Windows API w C++

inline void releaseMemory(T &x)


{
assert(x != NULL);
delete [] x;
x = NULL;
}
//---------------------------------------------------------
typedef USHORT USAGE, *PUSAGE;
typedef struct _HIDP_PREPARSED_DATA *PHIDP_PREPARSED_DATA;
//typedef unsigned int NTSTATUS;
PHIDP_PREPARSED_DATA preparsedData;

typedef struct _HIDP_CAPS { //hidpi.h


USAGE Usage;
USAGE UsagePage;
USHORT InputReportByteLength;
USHORT OutputReportByteLength;
USHORT FeatureReportByteLength;
USHORT Reserved[17];
USHORT NumberLinkCollectionNodes;
USHORT NumberInputButtonCaps;
USHORT NumberInputValueCaps;
USHORT NumberInputDataIndices;
USHORT NumberOutputButtonCaps;
USHORT NumberOutputValueCaps;
USHORT NumberOutputDataIndices;
USHORT NumberFeatureButtonCaps;
USHORT NumberFeatureValueCaps;
USHORT NumberFeatureDataIndices;
} HIDP_CAPS, *PHIDP_CAPS;

HIDP_CAPS capabilities;
GUID classGuid;
HMODULE hHidLib;
DWORD memberIndex = 0;
DWORD 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&) HidD_GetHidGuid=GetProcAddress(hHidLib, "HidD_GetHidGuid");


(FARPROC&) HidP_GetCaps=GetProcAddress(hHidLib,
"HidP_GetCaps");
(FARPROC&) HidD_GetPreparsedData=GetProcAddress(hHidLib,
"HidD_GetPreparsedData");
(FARPROC&) HidD_FreePreparsedData=GetProcAddress(hHidLib,
"HidD_FreePreparsedData");

(FARPROC&) HidP_GetValueCaps=GetProcAddress(hHidLib,
"HidP_GetValueCaps");

if (!HidD_GetHidGuid || !HidP_GetCaps || !HidD_GetPreparsedData


|| !HidD_FreePreparsedData || !HidP_GetValueCaps){
FreeLibrary(hHidLib);
displayError("Nie znaleziono żadnych funkcji eksportowych.\n");
}
216 USB. Praktyczne programowanie z Windows API w C++

HidD_GetHidGuid(&classGuid);

deviceInfoSet = SetupDiGetClassDevs(&classGuid, NULL, NULL,


DIGCF_PRESENT | DIGCF_INTERFACEDEVICE);
if (deviceInfoSet == INVALID_HANDLE_VALUE){
FreeLibrary(hHidLib);
displayError("Nie zidentyfikowano podłączonych urządzeń.\n");
}

deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);

while(SetupDiEnumDeviceInterfaces(deviceInfoSet, NULL, &classGuid,


memberIndex, &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,
NULL, NULL)){
releaseMemory(deviceInterfaceDetailData);
SetupDiDestroyDeviceInfoList(deviceInfoSet);
displayError("Nie można pobrać informacji o interfejsie.\n");
}

if (NULL != strstr(deviceInterfaceDetailData->DevicePath, "vid_22ba")){


cout << "\n"<< deviceInterfaceDetailData->DevicePath << "\n";
hidDeviceObject=CreateFile(deviceInterfaceDetailData->DevicePath,
GENERIC_READ, FILE_SHARE_READ,
NULL,OPEN_EXISTING,0,NULL);
if(hidDeviceObject==INVALID_HANDLE_VALUE)
displayError("Nie można otworzyć urządzenia do transmisji.");
else
break;
}
releaseMemory(deviceInterfaceDetailData);
}; //koniec while
SetupDiDestroyDeviceInfoList(deviceInfoSet);

if(HidD_GetPreparsedData(hidDeviceObject, &preparsedData)){
HidP_GetCaps(preparsedData, &capabilities);

PULONG valueCapsLength;
HIDP_VALUE_CAPS *valueCaps = new \
HIDP_VALUE_CAPS[capabilities.NumberOutputValueCaps];

HidP_GetValueCaps(HidP_Output, valueCaps, valueCapsLength, preparsedData);

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

printf("ValueCaps[%d].LinkUsage= x%x\n", i, valueCaps[i].LinkUsage);


printf("ValueCaps[%d].LinkUsagePage= 0x%x\n", i,
valueCaps[i].LinkUsagePage);
printf("ValueCaps[%d].IsRange= %d\n", i, valueCaps[i].IsRange);
printf("ValueCaps[%d].IsStringRange= %d\n", i,
valueCaps[i].IsStringRange);
printf("ValueCaps[%d].IsDesignatorRange= %d\n", i,
valueCaps[i].IsDesignatorRange);
printf("ValueCaps[%d].IsAbsolute= %d\n", i, valueCaps[i].IsAbsolute);
printf("ValueCaps[%d].HasNull= %d\n", i, valueCaps[i].HasNull);
printf("ValueCaps[%d].BitSize= %d\n", i, valueCaps[i].BitSize);
printf("ValueCaps[%d].ReportCount= %d\n", i, valueCaps[i].ReportCount);
printf("ValueCaps[%d].UnitsExp= %d\n", i, valueCaps[i].UnitsExp);
printf("ValueCaps[%d].Units= %d\n", i, valueCaps[i].Units);
printf("ValueCaps[%d].LogicalMin= %d\n", i, valueCaps[i].LogicalMin);
printf("ValueCaps[%d].LogicalMax= %d\n", i, valueCaps[i].LogicalMax);
printf("ValueCaps[%d].PhysicalMin= %d\n", i, valueCaps[i].PhysicalMin);
printf("ValueCaps[%d].PhysicalMax= %d\n", i, valueCaps[i].PhysicalMax);
printf("ValueCaps[%d].Range= %d\n", i, valueCaps[i].Range);
printf("ValueCaps[%d].Range.UsageMin= %d\n", i,
valueCaps[i].Range.UsageMin);
printf("ValueCaps[%d].Range.UsageMax= %d\n", i,
valueCaps[i].Range.UsageMax);
printf("ValueCaps[%d].Range.StringMin= %d\n", i,
valueCaps[i].Range.StringMin);
printf("ValueCaps[%d].Range.StringMax= %d\n", i,
valueCaps[i].Range.StringMax);
printf("ValueCaps[%d].Range.DesignatorMin= %d\n", i,
valueCaps[i].Range.DesignatorMin);
printf("ValueCaps[%d].Range.DesignatorMax= %d\n", i,
valueCaps[i].Range.DesignatorMax);
printf("ValueCaps[%d].Range.DataIndexMin= %d\n", i,
valueCaps[i].Range.DataIndexMin);
printf("ValueCaps[%d].Range.DataIndexMax= %d\n", i,
valueCaps[i].Range.DataIndexMax);
printf("ValueCaps[%d].NotRange= %d\n", i, valueCaps[i].NotRange);
printf("ValueCaps[%d].NotRange.Usage= x%x\n\n", i,
valueCaps[i].NotRange.Usage);

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

Aplikacja środowiska graficznego


Przykładem aplikacji środowiska graficznego, która wykorzystuje omówione do tej
pory zasoby systemu operacyjnego w zakresie transmisji danych w standardzie USB,
jest proj_USB_R6_4. Działanie aplikacji polega na odczytaniu raportu wejściowego
pochodzącego z przykładowego urządzenia, jakim jest uniwersalny kontroler gier, oraz
wizualizacji danych zawartych w wybranych polach tego raportu. Struktura logiczna
omawianego projektu jest pokazana na rysunku 6.9. Wygląd głównego formularza
aplikacji w trakcie uzyskiwania dostępu do urządzenia, odczytu danych oraz zamyka-
nia portu USB do transmisji zaprezentowano na rysunkach 6.10a – 6.10c. Na listin-
gach 6.7 i 6.8 zamieszczono kody głównych modułów projektu.
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych 219

Rysunek 6.9. Struktura logiczna aplikacji proj_USB_R6_4

Rysunek 6.10a. Aplikacja proj_USB_R6_4 w trakcie uzyskiwania dostępu do urządzenia


220 USB. Praktyczne programowanie z Windows API w C++

Rysunek 6.10b. Aplikacja proj_USB_R6_4 w trakcie odczytu danych z urządzenia

Rysunek 6.10c. Aplikacja proj_USB_R6_4 w trakcie zamykania portu do transmisji danych

Listing 6.7. Definicja klasy TForm1 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

void __fastcall Button1Click(TObject *Sender);


void __fastcall FormClose(TObject *Sender, TCloseAction &Action);
private: //Deklaracje użytkownika
HIDD_ATTRIBUTES hiddAttributes;
HIDP_CAPS capabilities;
PHIDP_PREPARSED_DATA preparsedData;
GUID classGuid;
HMODULE hHidLib;
DWORD memberIndex;
DWORD deviceInterfaceDetailDataSize;
HDEVINFO deviceInfoSet;
SP_DEVICE_INTERFACE_DATA deviceInterfaceData;
PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData;
HANDLE hidDeviceObject;
BYTE inputReportBuffer[bufferLength]; //bufor danych wejściowych
DWORD numberOfBytesRead;
TPoint points[5];
void __fastcall displayError(const char* msg);
public: //Deklaracje użytkownika
void __fastcall showCapabilitiesDevice(TObject *Sender);
__fastcall TForm1(TComponent* Owner);
};
//---------------------------------------------------------

Listing 6.8. Kod głównego modułu usb_R6_4.cpp


//---------------------------------------------------------
#include <vcl.h>
#include <sysopen.h>
#pragma hdrstop
#include "usb_R6_4.h"
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
void (__stdcall *HidD_GetHidGuid)(OUT LPGUID HidGuid);
bool (__stdcall *HidD_GetAttributes)(IN HANDLE HidDeviceObject,
OUT PHIDD_ATTRIBUTES Attributes);
long (__stdcall* HidP_GetCaps)(IN PHIDP_PREPARSED_DATA PreparsedData,
OUT PHIDP_CAPS Capabilities);

bool (__stdcall* HidD_GetPreparsedData)(IN HANDLE HidDeviceObject,


OUT PHIDP_PREPARSED_DATA *PreparsedData);

bool (__stdcall* HidD_FreePreparsedData)(IN PHIDP_PREPARSED_DATA PreparsedData);


//---------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
memberIndex = 0;
deviceInterfaceDetailData = NULL;
numberOfBytesRead = 0;
hidDeviceObject = INVALID_HANDLE_VALUE;

hHidLib = LoadLibrary("C:\\Windows\\System32\\HID.DLL");
if (!hHidLib)
displayError("Błąd dołączenia biblioteki HID.DLL.");
222 USB. Praktyczne programowanie z Windows API w C++

(FARPROC&) HidD_GetHidGuid=GetProcAddress(hHidLib, "HidD_GetHidGuid");


(FARPROC&) HidD_GetAttributes=GetProcAddress(hHidLib, "HidD_GetAttributes");
(FARPROC&) HidP_GetCaps=GetProcAddress(hHidLib,
"HidP_GetCaps");
(FARPROC&) HidD_GetPreparsedData=GetProcAddress(hHidLib,
"HidD_GetPreparsedData");
(FARPROC&) HidD_FreePreparsedData=GetProcAddress(hHidLib,
"HidD_FreePreparsedData");

if (!HidD_GetHidGuid) {
FreeLibrary(hHidLib);
displayError("Nie znaleziono żadnych funkcji eksportowych.\n");
}

Canvas->Pen->Mode = pmNotXor;
Canvas->Pen->Color = clRed;
Canvas->Pen->Width = 3;
}
//---------------------------------------------------------
void __fastcall TForm1::displayError(const char* msg){
ShowMessage(msg);
exit(0);
};
//---------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
HidD_GetHidGuid(&classGuid);

deviceInfoSet = SetupDiGetClassDevs(&classGuid, NULL, NULL,


DIGCF_PRESENT | DIGCF_INTERFACEDEVICE);
if (deviceInfoSet == INVALID_HANDLE_VALUE){
FreeLibrary(hHidLib);
displayError("Nie zidentyfikowano podłączonych urządzeń.\n");
}

deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);

while(SetupDiEnumDeviceInterfaces(deviceInfoSet, NULL, &classGuid,


memberIndex, &deviceInterfaceData)){
memberIndex++; //inkrementacja numeru interfejsu
SetupDiGetDeviceInterfaceDetail(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,
NULL, NULL)){
releaseMemory(deviceInterfaceDetailData);
SetupDiDestroyDeviceInfoList(deviceInfoSet);
FreeLibrary(hHidLib);
displayError("Nie można pobrać informacji o interfejsie.\n");
}

if (NULL != strstr(deviceInterfaceDetailData->DevicePath, "vid_22ba")){


ShowMessage(deviceInterfaceDetailData->DevicePath);
hidDeviceObject=CreateFile(deviceInterfaceDetailData->DevicePath,
GENERIC_READ,0,NULL,OPEN_EXISTING,0,NULL);
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych 223

if(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);

AnsiString S = S.Format("VID-PID-Wersja = %x-%x-%x",


OPENARRAY(TVarRec,(hiddAttributes.VendorID,
hiddAttributes.ProductID, hiddAttributes.VersionNumber)));
Memo1->Lines->Add(S);

HidD_GetPreparsedData(hidDeviceObject, &preparsedData);
HidP_GetCaps(preparsedData, &capabilities);
224 USB. Praktyczne programowanie z Windows API w C++

AnsiString S1 = S1.Format("Usage=%d UsagePage=%d\


InputReportByteLength=%d OutputReportByteLength=%d\
FeatureReportByteLength=%d NumberInputButtonCaps=%d\
NumberInputValueCaps=%d NumberInputDataIndices=%d\
NumberOutputButtonCaps=%d NumberOutputValueCaps=%d\
NumberOutputButtonCaps=%d NumberOutputValueCaps=%d\
NumberOutputDataIndices=%d NumberFeatureButtonCaps=%d\
NumberFeatureValueCaps=%d NumberFeatureDataIndices=%d",
OPENARRAY(TVarRec,(capabilities.Usage, capabilities.UsagePage,
capabilities.InputReportByteLength,
capabilities.OutputReportByteLength,
capabilities.FeatureReportByteLength,
capabilities.NumberInputButtonCaps,
capabilities.NumberInputValueCaps,
capabilities.NumberInputDataIndices,
capabilities.NumberOutputButtonCaps,
capabilities.NumberOutputValueCaps,
capabilities.NumberOutputButtonCaps,
capabilities.NumberOutputValueCaps,
capabilities.NumberOutputDataIndices,
capabilities.NumberFeatureButtonCaps,
capabilities.NumberFeatureValueCaps,
capabilities.NumberFeatureDataIndices)));

Memo1->Lines->Add(S1);
HidD_FreePreparsedData(preparsedData);
}
//---------------------------------------------------------

W skład klasy formularza aplikacji wchodzą struktury HIDD_ATTRIBUTES, HIDP_CAPS,


HIDP_PREPARSED_DATA, GUID, SP_DEVICE_INTERFACE_DATA, SP_DEVICE_INTERFACE_DETAIL_
DATA oraz klasy znanych komponentów wizualnych. Obsługę zdarzenia polegającego
na identyfikacji i odblokowaniu do transmisji sterownika wybranego urządzenia zapew-
nia komponent klasy TButton. Dodatkowo z funkcją obsługi zdarzenia przycisku Button1
jest skojarzona funkcja showCapabilitiesDevice(), za pomocą której w obszarze kom-
ponentu klasy TMemo są wyświetlane podstawowe właściwości testowanego urządzenia.

Zakres zmienności danych zawartych w jednym z pól raportu wejściowego (odpowia-


dających ciągłym zmianom położenia drążka lub kierownicy kontrolera gier) jest wi-
zualizowany poprzez odpowiednie zachowanie się własności Position komponentów
klas TProgressBar i TTrackBar (w aplikacjach komponenty te zazwyczaj spełniają funk-
cję elementów sterujących o charakterze ciągłym). Dodatkowo aktualna wartość prze-
chowywana w jednym z pól raportu wejściowego jest wykorzystywana do lokalizowania
w obrębie formularza samodzielnie zaprogramowanego trójkąta będącego przykłado-
wą (i bardzo uproszczoną) emulacją znaku śledzenia, jakim posługuje się oprogramo-
wanie standardowych lokalizatorów.

Koniec odczytu danych jest sygnalizowany poprzez naciśnięcie na panelu sterowni-


czym kontrolera gier przycisku Select (w niektórych urządzeniach oznaczonego nu-
merem 11), co powoduje umieszczenie w odpowiednim polu (bajcie) raportu wejścio-
wego dyskretnej dziesiętnej wartości 64.
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych 225

Zapis danych w formie raportu


Aplikacje użytkownika mogą używać funkcji HidD_SetXxx() w celu zapisywania (wy-
syłania) raportów wyjściowych i konfiguracyjnych do aktualnie dostępnego w systemie
urządzenia. Niemniej funkcji tych powinno się używać jedynie do ustalania aktualnego
stanu urządzenia. Należy zwrócić uwagę na fakt, że wiele urządzeń może nieprawidło-
wo reagować na cykliczne polecenia wysyłane przez funkcje HidD_SetXxx(). W tabeli
6.2 zamieszczono wykaz funkcji, którymi aplikacje użytkownika mogą się bezpiecznie
posługiwać w trakcie transferu danych do urządzeń przyłączonych do magistrali USB.

Tabela 6.2. Funkcje używane podczas transferu raportów wyjściowych


Typ raportu Funkcje API WDK/SDK Typ transferu danych
Wyjściowy OUT (Output) HidD_SetOutputReport() Kontrolny z rozkazem
Set_Report
WriteFile() Przerwaniowy OUT,
ewentualnie kontrolny
z rozkazem Set_Report
Konfiguracyjny OUT (Feature OUT) HidD_SetFeature() Kontrolny z rozkazem
Set_Report

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++

o dodatkowych metodach kontroli transmisji, polegających na sygnalizowaniu aktual-


nego położenia pozycji wskaźnika transmitowanego pliku. Większość elementów tej
struktury jest zarezerwowana przez system operacyjny. Jeżeli jednak chcielibyśmy
z niej skorzystać, należałoby w funkcji CreateFile() przypisać parametrowi dwFlags-
AndAttributes znacznik FILE_FLAG_OVERLAPPED. W tym przykładzie zignorujemy wskaź-
nik lpOverlapped, przypisując mu NULL (do struktury OVERLAPPED powrócimy jeszcze
za chwilę).

Na listingu 6.9 zaprezentowano fragment kodu modułu programu zapisującego przykła-


dowy raport wyjściowy do urządzenia USB identyfikowanego przez hidDeviceObject.

Listing 6.9. Przykład obsługi raportu wyjściowego za pomocą funkcji WriteFile()


//---------------------------------------------------------
char* outputReportBuffer = new char[capabilities.OutputReportByteLength];
assert(outputReportBuffer != NULL);
memset(outputReportBuffer, 0x00, capabilities.OutputReportByteLength);
//...
outputReportBuffer[0] = 0x00; //Raport ID
outputReportBuffer[1] = /*...*/;
outputReportBuffer[2] = /*...*/;
//...
printf("Raport wyjściowy: ");
for(ULONG i=1; i<capabilities.OutputReportByteLength; i++)
printf(" ", outputReportBuffer[i]);
printf("\n");
//...
hidDeviceObject=CreateFile(deviceInterfaceDetailData->DevicePath,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,OPEN_EXISTING, 0, NULL);
//...
DWORD numberOfBytesWrite = 0;
if(!WriteFile(hidDeviceObject, &outputReportBuffer,
capabilities.OutputReportByteLength,
&numberOfBytesWrite, NULL))
printf("Błąd wysłania raportu wyjściowego %d\n", GetLastError());
else
if(numberOfBytesWrite==capabilities.OutputReportByteLength)
printf("Raport wyjściowy został wysłany.\n");
else
printf("Błędna liczba wysłanych bajtów: %d\n",numberOfBytesWrite);
releaseMemory(outputReportBuffer);
//...
//---------------------------------------------------------

Funkcje HidD_SetOutputReport()
oraz HidD_SetFeature()
W przypadku gdy testowane urządzenie mogłoby się posługiwać kontrolnym transfe-
rem danych dla raportów 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

cji HidD_SetOutputReport() i (lub) HidD_SetFeature(). Przykładowy sposób jej wyko-


rzystania pokazuje kod z listingu 6.10.

Listing 6.10. Przykład wykorzystania funkcji HidD_SetOutputReport() oraz HidD_SetFeature()


//---------------------------------------------------------
int main()
{
//...
outputReportBuffer[0] = 0x00; //Raport ID
outputReportBuffer[1] = /*...*/;
outputReportBuffer[2] = /*...*/

outFeatureReportBuffer[0] = 0x00; //Raport ID


outFeatureReportBuffer[1] = /*...*/;
outFeatureReportBuffer[2] = /*...*/;

bool (__stdcall *HidD_SetOutputReport)(IN HANDLE HidDeviceObject,


IN OUT PVOID ReportBuffer,
IN ULONG ReportBufferLength);
bool (__stdcall *HidD_SetFeatureReport)(IN HANDLE HidDeviceObject,
IN OUT PVOID ReportBuffer,
IN ULONG ReportBufferLength);
//...
(FARPROC&) HidD_SetOutputReport=GetProcAddress(hHidLib,
"HidD_SetOutputReport");
(FARPROC&) HidD_GetFeatureReport=GetProcAddress(hHidLib,
"HidD_SetFeatureReport");
//...
HidD_SetOutputReport(hidDeviceObject, outputReportBuffer,
capabilities.OutputReportByteLength);
//...
HidD_SetFeatureReport(hidDeviceObject, outFeatureReportBuffer,
capabilities.FeatureReportByteLength);
//...
//---------------------------------------------------------

Struktura OVERLAPPED
W dotychczas omawianych przykładach urządzenie USB pozostawało odblokowane
w standardowym trybie działania synchronicznego. Aby odblokować urządzenie dla
asynchronicznych operacji odczytu i zapisu danych, należy utworzyć obiekt na bazie
struktury kontrolującej asynchroniczne operacje I/O (wejścia-wyjścia):
typedef struct _OVERLAPPED {
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
HANDLE hEvent;
} OVERLAPPED;
228 USB. Praktyczne programowanie z Windows API w C++

Znaczenie poszczególnych pól tej struktury jest następujące:


 Internal — zarezerwowane dla systemu operacyjnego. Staje się istotne,
gdy funkcja GetOverlappedResult() zwróci rezultat asynchronicznych
operacji I/O dla pliku, potoku lub urządzenia zewnętrznego.
 InternalHigh — zarezerwowane dla systemu. Specyfikuje długość
przesyłanych danych. Staje się istotne, gdy funkcja GetOverlappedResult()
zwraca wartość TRUE.
 Offset — określa wskaźnik położenia pliku przeznaczonego do transferu.
Traktowany jest jako przesunięcie wyznaczone w stosunku do początku pliku.
 OffsetHigh — bardziej znacząca część przesunięcia.
 hEvent — określenie sposobu oznaczenia końca transferu danych.

Jako jeden z argumentów funkcji 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);

Dla każdego żądania wykonania asynchronicznej operacji przez funkcje DeviceIoCon-


trol(), ReadFile() i WriteFile() należy używać osobnego obiektu struktury, przeka-
zywanego do tych funkcji jako ostatni argument (patrz listing nieco dalej).

W trakcie wykonywania asynchronicznych operacji I/O zdarzają się sytuacje, w któ-


rych powinno się diagnozować czasy przeterminowania dla odczytu i (lub) zapisu da-
nych pochodzących z łącza USB. Windows SDK API definiuje funkcję często wyko-
rzystywaną w tym celu:
DWORD WaitForSingleObject(IN HANDLE hEvent,
IN DWORD dwMilliseconds);

W zależności od kontekstu użycia omawianej funkcji, dwMilliseconds określa w mili-


sekundach czas przeterminowania (ang. time out) albo czas oczekiwania na zdarzenie
(ang. break time). Funkcja zwraca czas, który upłynął, nawet jeżeli stan obiektu nie jest
w żaden sposób sygnalizowany. Jeżeli parametr ten jest równy zero, funkcja natych-
miast testuje stan obiektu. W przypadku gdy zostanie przypisana mu wartość INFINITE
(nieskończoność), stan obiektu nie będzie testowany.

Parametr hEvent jest identyfikatorem określonego obiektu zdarzenia. Z reguły należy


mu przydzielić odpowiednią wartość, korzystając z funkcji SDK API:
HANDLE CreateEvent(IN OUT LPSECURITY_ATTRIBUTES lpEventAttributes,
IN BOOL bManualReset,
IN BOOL bInitialState,
IN OUT LPCTSTR lpName);
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych 229

Parametr lpEventAttributes jest wskaźnikiem do struktury zabezpieczeń obiektu


SECURITY_ATTRIBUTES, określającej, czy zwracany identyfikator może być dziedziczo-
ny przez procesy potomne. Jeżeli przypiszemy mu wartość NULL, identyfikator ten nie
będzie dziedziczony. Parametr bManualReset określa, czy i kiedy występuje automa-
tyczne lub ręczne zwolnienie stworzonego obiektu zdarzenia. Jeżeli przypiszemy mu
wartość FALSE, Windows automatycznie zwolni obiekt w przypadku zakończenia da-
nego procesu lub wystąpienia zdarzenia. Parametr lpName jest wskaźnikiem do łańcu-
cha liczącego co najwyżej MAX_PATH znaków i zakończonego zerowym ogranicznikiem.
Łańcuch ten określa konkretną nazwę obiektu zdarzenia.

Funkcja WaitForSingleObject() w przypadku niepomyślnego wykonania zwraca war-


tość WAIT_FAILED. Jeżeli zostanie wykonana pomyślnie, należy się spodziewać nastę-
pujących wartości:
 WAIT_ABANDONED — wyspecyfikowany jest obiekt wzajemnego wykluczania
(ang. mutex), tj. sekcji krytycznej współdzielonej przez wiele procesów,
który nie został zwolniony przez dany wątek;
 WAIT_OBJECT_0 — sygnalizowany jest aktualny stan wyspecyfikowanego
obiektu;
 WAIT_TIMEOUT — czas przeterminowania wybranej operacji upłynął i aktualny
stan obiektu nie będzie sygnalizowany.

Jeżeli w funkcji CreateEvent() parametrowi bManualReset zostanie przypisana wartość


TRUE, należy skorzystać z funkcji Windows SDK API:
BOOL ResetEvent(IN HANDLE hEvent);

Funkcja ta uniemożliwia sygnalizowanie stanu obiektu zdarzenia.

Na listingu 6.11 zaprezentowano przykładową funkcję readUSBReport(), która w spo-


sób asynchroniczny kontroluje operację pobierania raportów z zewnętrznego urządze-
nia USB. Funkcję tę można wywoływać cyklicznie.

Listing 6.11. Fragment kodu modułu usb_R6_5.cpp, ilustrujący wykorzystanie struktury


OVERLAPPED w trakcie pobierania raportu wejściowego
//---------------------------------------------------------
bool readUSBReport(HANDLE, void*, USHORT intputReportByteLength)
{
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(hidDeviceObject, inputReportBuffer, intputReportByteLength,
&numberOfBytesRead, overlapped)) {
if(GetLastError() == ERROR_IO_PENDING) {
230 USB. Praktyczne programowanie z Windows API w C++

result = WaitForSingleObject(overlapped->hEvent, 100);


if(result == WAIT_TIMEOUT) {
CancelIo(hidDeviceObject);
return false;
}
else
if(result == WAIT_FAILED){
displayError("Błąd odczytu danych.");
return false;
}
GetOverlappedResult(hidDeviceObject, overlapped,
&numberOfBytesRead, FALSE);
}
else
displayError("Błąd odczytu danych.");
}
ResetEvent(overlapped->hEvent);
if(numberOfBytesRead==capabilities.InputReportByteLength){
for(USHORT i=0; i<capabilities.InputReportByteLength; i++)
printf("%d ", inputReportBuffer[i]);
printf("\n");
}
else {
printf("Błędna liczba odebranych bajtów.\n",numberOfBytesRead);
}
releaseMemory(overlapped);
return true;
}
//---------------------------------------------------------

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 WriteFileEx() asynchronicznie zapisuje (wysyła) dane do pliku identyfikowa-


nego przez hFile. Identyfikator ten jest zwracany poprzez funkcję CreateFile() i po-
winien być przydzielony z atrybutami FILE_FLAG_OVERLAPPED oraz GENERIC_WRITE. Pa-
rametr nNumberOfBytesToWrite określa liczbę bajtów przeznaczoną do zapisu (wysłania).
W trakcie wykonywania standardowych operacji plikowych koniec wysyłanego pliku
można określić, posługując się funkcją SetEndOfFile(HANDLE hFile).
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych 231

Funkcja GetLastError() zwraca ERROR_INVALID_USER_BUFFER, gdy operacja zapisu do


pliku została wykonana nieprawidłowo, lub ERROR_NOT_ENOUGH_MEMORY, gdy napotka-
no zbyt wiele procesów jednocześnie żądających wykonania asynchronicznych opera-
cji wejścia-wyjścia. W celu przerwania zapisu do pliku można użyć funkcji CancelIo
(HANDLE hFile).

Funkcja ReadFileEx()
BOOL ReadFileEx(IN HANDLE hFile,
OUT LPVOID lpBuffer,
IN DWORD nNumberOfBytesToRead,
IN OUT LPOVERLAPPED lpOverlapped,
IN LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);

Funkcja ReadFileEx() asynchronicznie odczytuje (pobiera) dane z pliku identyfiko-


wanego przez hFile. Identyfikator ten jest zwracany poprzez funkcję CreateFile()
i powinien być przydzielony z atrybutami FILE_FLAG_OVERLAPPED oraz GENERIC_READ.

Parametr nNumberOfBytesToRead określa liczbę bajtów przeznaczoną do odczytu (ode-


brania). W przypadku wykonywania standardowych operacji na pliku koniec odbiera-
nego pliku można określić, posługując się funkcją SetEndOfFile(HANDLE hFile).

Funkcja GetLastError() zwraca ERROR_INVALID_USER_BUFFER, gdy operacja odczytu z pli-


ku została wykonana nieprawidłowo, lub ERROR_NOT_ENOUGH_MEMORY, gdy napotkano zbyt
wiele procesów jednocześnie żądających wykonania asynchronicznych operacji wejścia-
-wyjścia. W celu przerwania odczytu można użyć funkcji CancelIo(HANDLE hFile).

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);

Parametr dwNumberOfBytesTransferred jest liczbą transferowanych bajtów (w przypadku


wystąpienia błędów w transmisji parametr ten jest równy zero). Parametr dwErrorCode
reprezentuje status wykonania operacji wejścia-wyjścia i może przyjąć następujące
wartości:
 0 — operacja wejścia-wyjścia została wykonana prawidłowo;
 ERROR_HANDLE_EOF — funkcja ReadFileEx() próbuje odczytać dane
zawarte poza znacznikiem końca pliku (EOF).
232 USB. Praktyczne programowanie z Windows API w C++

Funkcja WaitForSingleObjectEx()
DWORD WaitForSingleObjectEx(IN HANDLE hHandle,
IN DWORD dwMilliseconds,
IN BOOL bAlertable);

W zależności od kontekstu użycia omawianej funkcji, dwMilliseconds określa w mili-


sekundach czas przeterminowania (ang. Time Out) lub czas oczekiwania na zdarzenie
(ang. Break Time). Funkcja zwraca czas, który upłynął, nawet jeżeli stan obiektu nie jest
w żaden sposób sygnalizowany. Jeżeli parametr ten jest równy zero, funkcja natych-
miast testuje stan obiektu. W przypadku gdy zostanie przypisana mu wartość INFINITE
(nieskończoność), stan obiektu nie będzie testowany. Parametr hHandle jest identyfi-
katorem określonego obiektu (wątek, muteks, semafor, proces, zdarzenie). Parametro-
wi bAlertable należy przypisać wartość TRUE, jeżeli wykonywane w wątku operacje
wejścia-wyjścia bezwzględnie powinny się zakończyć powodzeniem.

Funkcja SleepEx()
DWORD SleepEx(IN DWORD dwMilliseconds,
IN BOOL bAlertable);

Jeżeli parametr bAlertable przyjmuje wartość FALSE, funkcja SleepEx() wstrzymuje


działanie bieżącego wątku na czas określony parametrem dwMilliseconds. W przypad-
ku gdy parametr bAlertable przyjmuje wartość TRUE, a funkcje ReadFileEx(), Write-
FileEx() i SleepEx() są wywoływane w tym samym wątku, SleepEx() kończy dzia-
łanie albo w momencie upłynięcia czasu dwMilliseconds, albo po wykonaniu funkcji
FileIOCompletionRoutine().

Na listingu 6.12 zaprezentowano jeden z możliwych sposobów wykorzystania oma-


wianych funkcji. W programie głównym funkcje te są używane pośrednio poprzez wy-
wołanie readUSBReportEx().

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);

//do bufora z potoku jest asynchronicznie pobierany raport wejściowy


if (!ReadFileEx(hidDeviceObject, &inputReportBuffer,
sizeof(inputReportBuffer), overlapped,
FileIOCompletionRoutine))
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych 233

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().

Tabela 6.3. Informacje zawarte w strukturze COMMTIMEOUTS


Typ Element struktury Opis
DWORD ReadIntervalTimeout Określa maksymalny czas (w milisekundach) między
pojawieniem się na linii komunikacyjnej dwóch
znaków. W trakcie wykonywania ReadFile() czas
jest liczony od momentu pojawienia się pierwszego
znaku. Jeżeli przedział czasu między nadejściem
dwóch znaków przekracza tę wartość, oznacza to,
że operacja ReadFile() jest zakończona. Wartość 0
oznacza, że nie ustalono wymaganego okresu między
nadejściem dwóch kolejnych znaków. Przypisanie
wartości MAXDWORD powoduje, że czytany znak jest
pobierany z bufora natychmiast, gdy się tam pojawi
DWORD ReadTotalTimeoutMultiplier Określa mnożnik (w milisekundach) użyty
do obliczenia całkowitego przedziału czasu
(przeterminowania) dla operacji czytania (odbioru).
Dla wszystkich takich operacji wartość ta jest
mnożona przez liczbę bajtów przewidzianych
do odebrania z dysku lub łącza komunikacyjnego
DWORD ReadTotalTimeoutConstant Określa stałą (w milisekundach) użytą do obliczania
czasu przeterminowania operacji czytania. Dla
wszystkich takich operacji wartość ta jest dodawana
do ReadTotal TimeoutMultiplier i do oczekiwanej
liczby nadchodzących bajtów
DWORD WriteTotalTimeoutMultiplier Określa mnożnik (w milisekundach) użyty
do obliczenia całkowitego przedziału czasu
(przeterminowania) dla operacji zapisywania
(wysyłania). Dla wszystkich takich operacji wartość
ta jest mnożona przez liczbę bajtów przewidzianych
do wysłania (zapisywania). Wartość 0 oznacza, że nie
ustalono czasu przeterminowania dla operacji zapisu
na dysku lub do łącza komunikacyjnego
DWORD WriteTotalTimeoutConstant Określa stałą (w milisekundach) użytą do obliczania
czasu przeterminowania operacji wysyłania. Dla
wszystkich takich operacji wartość ta jest dodawana
do WriteTotalTimeoutMultiplier oraz do
oczekiwanej liczby wysyłanych bajtów. Wartość 0
oznacza, że nie ustalono czasu przeterminowania
dla operacji zapisu (wysyłania)
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych 235

Windows SDK API definiuje tę strukturę jako:


typedef struct _COMMTIMEOUTS {
DWORD ReadIntervalTimeout;
DWORD ReadTotalTimeoutMultiplier;
DWORD ReadTotalTimeoutConstant;
DWORD WriteTotalTimeoutMultiplier;
DWORD WriteTotalTimeoutConstant;
} COMMTIMEOUTS,*LPCOMMTIMEOUTS;

Definicja ta tworzy dwa nowe słowa kluczowe: COMMTIMEOUTS (struktura) i LPCOMMTIMEOUTS


(wskaźnik do struktury).

Funkcje GetCommTimeouts() i SetCommTimeouts()


Aktualne parametry przeterminowania operacji zapisu i odczytu, na przykład z portu
komunikacyjnego, odczytamy za pomocą funkcji:
BOOL GetCommTimeouts(HANDLE hCommDev, LPCOMMTIMEOUTS
lpCommTimeouts);

Własne ustawienia wpiszemy, korzystając z:


BOOL SetCommTimeouts(HANDLE hCommDev, LPCOMMTIMEOUTS
lpCommTimeouts);

W obu przypadkach lpCommTimeouts jest wskaźnikiem struktury opisanej w tabeli 6.3.


Najprostszym sposobem użycia przedstawionych powyżej instrukcji jest fragment ko-
du z listingu 6.13.

Listing 6.13. Przykładowe wykorzystanie struktury COMMTIMEOUTS


//...
COMMTIMEOUTS commTimeouts;
//...
//---------------------------------------------------------
bool setCommTimeouts(ULONG ReadIntervalTimeout,
ULONG ReadTotalTimeoutMultiplier,
ULONG ReadTotalTimeoutConstant,
ULONG WriteTotalTimeoutMultiplier,
ULONG WriteTotalTimeoutConstant)
{
if(GetCommTimeouts(hidDeviceObject, &commTimeouts)==0)
return false;
commTimeouts.ReadIntervalTimeout=ReadIntervalTimeout;
commTimeouts.ReadTotalTimeoutConstant=ReadTotalTimeoutConstant;
commTimeouts.ReadTotalTimeoutMultiplier=
ReadTotalTimeoutMultiplier;
commTimeouts.WriteTotalTimeoutConstant=WriteTotalTimeoutConstant;
commTimeouts.WriteTotalTimeoutMultiplier=
WriteTotalTimeoutMultiplier;

if(SetCommTimeouts(hidDeviceObject, &commTimeouts) == 0){


cout << "Błąd wykonania funkcji SetCommTimeouts().\n";
CloseHandle(hidDeviceObject);
236 USB. Praktyczne programowanie z Windows API w C++

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);

Znaczenie parametrów tej funkcji jest następujące:


 hDevice — jest identyfikatorem urządzenia wykonującego daną operację;
identyfikator ten jest zwracany przez funkcję CreateFile();
 dwIoControlCode — reprezentuje unikalny kod kontrolny IOCTL_Xxx
(ang. I/O control codes) zdefiniowanej przez użytkownika operacji
wejścia-wyjścia (I/O);
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych 237

 lpInBuffer — wskaźnik do bufora danych wejściowych, który zawiera


informacje potrzebne do przeprowadzenia wybranej operacji;
 nInBufferSize — rozmiar bufora danych wejściowych;
 lpOutBuffer — wskaźnik do bufora danych wyjściowych, który zawiera
informacje zwrócone podczas wykonywania danej operacji;
 nOutBufferSize — rozmiar bufora danych wyjściowych;
 lpBytesReturned — wskaźnik do danej typu DWORD, zawierający liczbę
bajtów zwróconych do lpOutBuffer;
 lpOverlapped — wskaźnik do struktury OVERLAPPED.

Funkcja DeviceIoControl() w przypadku prawidłowego wykonania zwraca wartość


niezerową. Kod błędu wykonania można odzyskać za pomocą funkcji GetLastError().

Funkcja DeviceIoControl() wysyła rozkazy z odpowiednim kodem IOCTL_Xxx do ste-


rownika urządzenia, który przekazuje je do samego urządzenia (patrz rozdział 3.). Ko-
dy kontrolne, których format jest pokazany na rysunku 6.11, są tworzone za pomocą
makrodefinicji CTL_CODE:
#define CTL_CODE(DeviceType, Function, Method, Access) (
((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method)
)

Rysunek 6.11. Format rozkazu IOCTL_Xxx

Znaczenie poszczególnych elementów pokazanych na rysunku 6.11 i zaadaptowanych


w kodzie pokazanym na listingu 6.14 jest następujące:
 Device type — definiuje typ urządzenia, reprezentowany przez odpowiednią
stałą symboliczną, do którego będą wysyłane rozkazy;
 Access — zawiera rodzaj dostępu do pliku sterownika urządzenia;
 Function — zawiera kod funkcji dla kategorii urządzeń; kody z zakresu
0 – 2047 są zarezerwowane przez system operacyjny;
 Method — określa sposoby buforowania danych (lub brak buforowania).

Listing 6.14. Kod modułu usb_R6_6.cpp


#include <windows>
#pragma option push -a1
#include <setupapi>
#pragma option pop
#include <iostream>
#include <assert>
238 USB. Praktyczne programowanie z Windows API w C++

#define bufferLength 126

#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)

#define IOCTL_GET_PHYSICAL_DESCRIPTOR HID_OUT_CTL_CODE(102)


#define IOCTL_HID_FLUSH_QUEUE HID_CTL_CODE(101)
#define IOCTL_HID_GET_COLLECTION_DESCRIPTOR HID_CTL_CODE(100)
#define IOCTL_HID_GET_COLLECTION_INFORMATION HID_BUFFER_CTL_CODE(106)
#define IOCTL_HID_GET_FEATURE HID_OUT_CTL_CODE(100)
#define IOCTL_HID_GET_HARDWARE_ID HID_OUT_CTL_CODE(103)
#define IOCTL_HID_GET_INDEXED_STRING HID_OUT_CTL_CODE(120)
#define IOCTL_HID_GET_INPUT_REPORT HID_OUT_CTL_CODE(104)
#define IOCTL_HID_GET_MANUFACTURER_STRING HID_OUT_CTL_CODE(110)
#define IOCTL_GET_NUM_DEVICE_INPUT_BUFFERS HID_BUFFER_CTL_CODE(104)
#define IOCTL_HID_GET_POLL_FREQUENCY_MSEC HID_BUFFER_CTL_CODE(102)
#define IOCTL_HID_GET_PRODUCT_STRING HID_OUT_CTL_CODE(111)
#define IOCTL_HID_GET_SERIALNUMBER_STRING HID_OUT_CTL_CODE(112)
#define IOCTL_HID_SET_FEATURE HID_IN_CTL_CODE(100)
#define IOCTL_SET_NUM_DEVICE_INPUT_BUFFERS HID_BUFFER_CTL_CODE(105)
#define IOCTL_HID_SET_OUTPUT_REPORT HID_IN_CTL_CODE(101)
#define IOCTL_HID_SET_POLL_FREQUENCY_MSEC HID_BUFFER_CTL_CODE(103)

#define IOCTL_HID_GET_DRIVER_CONFIG HID_BUFFER_CTL_CODE(100)


#define IOCTL_HID_SET_DRIVER_CONFIG HID_BUFFER_CTL_CODE(101)
#define IOCTL_HID_GET_MS_GENRE_DESCRIPTOR HID_OUT_CTL_CODE(121)

using namespace std;

void displayError(const char* msg){


cout << msg << endl;
system("PAUSE");
exit(0);
};
//---------------------------------------------------------
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;
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;

typedef struct _HIDP_CAPS {


USAGE Usage;
USAGE UsagePage;
USHORT InputReportByteLength;
USHORT OutputReportByteLength;
USHORT FeatureReportByteLength;
USHORT Reserved[17];
USHORT NumberLinkCollectionNodes;
USHORT NumberInputButtonCaps;
USHORT NumberInputValueCaps;
USHORT NumberInputDataIndices;
USHORT NumberOutputButtonCaps;
USHORT NumberOutputValueCaps;
USHORT NumberOutputDataIndices;
USHORT NumberFeatureButtonCaps;
USHORT NumberFeatureValueCaps;
USHORT NumberFeatureDataIndices;
} HIDP_CAPS, *PHIDP_CAPS;

HIDP_CAPS capabilities;
//---------------------------------------------------------
int main()
{
long (__stdcall* HidP_GetCaps)(IN PHIDP_PREPARSED_DATA PreparsedData,
OUT PHIDP_CAPS Capabilities);

void (__stdcall *HidD_GetHidGuid)(OUT LPGUID HidGuid);


240 USB. Praktyczne programowanie z Windows API w C++

hHidLib = LoadLibrary("C:\\Windows\\System32\\HID.DLL");
if (!hHidLib)
displayError("Błąd dołączenia biblioteki HID.DLL.");

(FARPROC&) HidD_GetHidGuid=GetProcAddress(hHidLib, "HidD_GetHidGuid");


(FARPROC&) HidP_GetCaps=GetProcAddress(hHidLib, "HidP_GetCaps");

if (!HidD_GetHidGuid || !HidP_GetCaps){
FreeLibrary(hHidLib);
displayError("Nie znaleziono żadnych funkcji eksportowych.\n");
}

HidD_GetHidGuid(&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);
hiddAttributes.Size = sizeof(HIDD_ATTRIBUTES);

while(SetupDiEnumDeviceInterfaces(deviceInfoSet, NULL, &classGuid,


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");
}

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;
}
//---------------------------------------------------------

Podobnie jak WriteFile() i ReadFile(), funkcja DeviceIoControl() może pracować


w dwóch trybach:
 synchronicznym — odpowiedź sterownika urządzenia można odczytać
natychmiast po zakończeniu działania DeviceIoControl();
 asynchronicznym — sterownik urządzenia odpowie w stosownym czasie.
W tym przypadku należy zdefiniować zdarzenie hEvent, które wystąpi
w trakcie odpowiedzi sterownika (listing 6.9).
242 USB. Praktyczne programowanie z Windows API w C++

Na listingu 6.14 zaprezentowano przykłady wykorzystania funkcji DeviceIoControl()


pracującej w trybie synchronicznym z rozkazami IOCTL_HID_GET_MANUFACTURER_STRING,
IOCTL_HID_GET_COLLECTION_INFORMATION oraz IOCTL_HID_GET_COLLECTION_DESCRIPTOR
(patrz rozdział 4.). Wynik działania programu obrazuje rysunek 6.12.

Rysunek 6.12.
Wynik działania
aplikacji
proj_USB_R6_6

Rozkazy z modułu hidclass.h


Windows Driver Kit w module hidclass.h udostępnia szereg standardowych rozkazów
umożliwiających wykonywanie z niskiego poziomu operacji wejścia-wyjścia na urzą-
dzeniach klasy HID. Na rysunku 6.13 przedstawiono kilka z nich.

Rysunek 6.13.
Standardowe rozkazy
IOCTL_Xxx dostępne
z poziomu modułu
hidclass.h

Listing 6.15 przedstawia przykład zdefiniowanej w module usb_R6_7.cpp funkcji


getCollectionDescriptor() pracującej w trybie synchronicznym, za pomocą której
przy wykorzystaniu rozkazu IOCTL_HID_GET_COLLECTION_INFORMATION można odczytać
wszystkie zasoby przechowywane aktualnie w elementach struktury HID_COLLECTION_
INFORMATION. Na rysunku 6.14 pokazano wynik działania programu.

Listing 6.15. Jeden ze sposobów odczytania zawartości struktury HID_COLLECTION_INFORMATION


#include <windows>
#pragma option push -a1
#include <setupapi>
#pragma option pop
#include <iostream>
#include <assert>
#include "C:\\WINDDK\\7600.16385.1\\inc\\ddk\\hidclass.h"
using namespace std;
//---------------------------------------------------------
void displayError(const char* msg){
cout << msg << endl;
system("PAUSE");
exit(0);
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych 243

};
//---------------------------------------------------------
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.");

(FARPROC&) HidD_GetHidGuid=GetProcAddress(hHidLib, "HidD_GetHidGuid");

if (!HidD_GetHidGuid){
FreeLibrary(hHidLib);
displayError("Nie znaleziono żadnych \
funkcji eksportowych.\n");
}

HidD_GetHidGuid(&classGuid);
244 USB. Praktyczne programowanie z Windows API w C++

deviceInfoSet = SetupDiGetClassDevs(&classGuid, NULL, NULL,


DIGCF_PRESENT | DIGCF_INTERFACEDEVICE);
if (deviceInfoSet == INVALID_HANDLE_VALUE){
FreeLibrary(hHidLib);
displayError("Nie zidentyfikowano podłączonych urządzeń.\n");
}

deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);

while(SetupDiEnumDeviceInterfaces(deviceInfoSet, NULL, &classGuid,


memberIndex, &deviceInterfaceData)){
memberIndex++; //inkrementacja numeru interfejsu
SetupDiGetDeviceInterfaceDetail(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");
}

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

Rozkazy z modułu usbioctl.h


Moduł usbioctl.h zawiera rozkazy, za pomocą których można odczytać kompletne in-
formacje o wszystkich urządzeniach przyłączonych do magistrali USB. Na listingu
6.16 zaprezentowano nieskomplikowany przykład praktycznego wykorzystania w apli-
kacji użytkownika dwóch rozkazów: IOCTL_GET_HCD_DRIVERKEY_NAME oraz IOCTL_USB_
GET_ROOT_HUB_NAME, identyfikujących standardowe rozszerzone kontrolery PCI do USB,
zaś na rysunku 6.15 pokazano program w trakcie działania.

Listing 6.16. Identyfikacja standardowego rozszerzonego kontrolera PCI do USB


#include <windows>
#pragma option push -a1
#include <setupapi>
#pragma option pop
#include <iostream>
#include "D:\\WINDDK\\7600.16385.1\\inc\\api\\usbioctl.h"

using namespace std;

//---------------------------------------------------------
void getRootHubName(HANDLE devHandle)
{
USB_ROOT_HUB_NAME rootHubName;
PUSB_ROOT_HUB_NAME pRootHubName;
DWORD outBufferSize, bytesReturned;
if (DeviceIoControl(devHandle, IOCTL_USB_GET_ROOT_HUB_NAME,
&rootHubName, sizeof(rootHubName), &rootHubName,
sizeof(rootHubName), &outBufferSize, NULL)) {
outBufferSize = rootHubName.ActualLength;
pRootHubName = new USB_ROOT_HUB_NAME[outBufferSize];
if (DeviceIoControl(devHandle, IOCTL_USB_GET_ROOT_HUB_NAME,
&rootHubName, sizeof(rootHubName), pRootHubName,
outBufferSize, &bytesReturned, NULL)) {
printf("\nRoot Hub USB:\n%ls\n",&pRootHubName->RootHubName[0]);
};
delete [] pRootHubName;
};
return;
};
//-----------------------------------------------------
246 USB. Praktyczne programowanie z Windows API w C++

void getHCDDriverKeyName(HANDLE devHandle)


{
USB_HCD_DRIVERKEY_NAME driverKeyName;
PUSB_HCD_DRIVERKEY_NAME pDriverKeyName;
DWORD outBufferSize, bytesReturned;
if (DeviceIoControl(devHandle, IOCTL_GET_HCD_DRIVERKEY_NAME,
&driverKeyName, sizeof(driverKeyName), &driverKeyName,
sizeof(driverKeyName), &outBufferSize, NULL)) {
outBufferSize = driverKeyName.ActualLength;
pDriverKeyName = new USB_HCD_DRIVERKEY_NAME[outBufferSize];
if (DeviceIoControl(devHandle, IOCTL_GET_HCD_DRIVERKEY_NAME,
&driverKeyName, sizeof(driverKeyName), pDriverKeyName,
outBufferSize, &bytesReturned, NULL)) {
printf("\nIdentyfikacja standardowego rozszerzonego kontrolera
PCI do USB: \n%ls\n",&pDriverKeyName->DriverKeyName[0]);
}
delete [] pDriverKeyName;
};
};
//---------------------------------------------------------
int main()
{
HANDLE HCDHandle; //HCD — Host Controller Device
char HCDName[] = "\\\\.\\HCD0";
UINT i = 0;
do {
HCDName[7] = i + '0';
HCDHandle = CreateFile(HCDName, GENERIC_WRITE, FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0 , NULL);

if (HCDHandle != INVALID_HANDLE_VALUE) {
getRootHubName(HCDHandle);
getHCDDriverKeyName(HCDHandle);
CloseHandle(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

Listing 6.17. Identyfikacja urządzeń podłączonych do koncentratora USB


#include <windows>
#include <initguid>
#pragma option push -a1
#include <setupapi>
#pragma option pop
#include <iostream>
#include "D:\\WINDDK\\7600.16385.1\\inc\\api\\usbioctl.h"
//#include "D:\\Program Files\\Windows Kits\\8.0\\include\\shared\\usbioctl.h"
using namespace std;
//-----------------------------------------------------
void displayError(const char* msg){
cout << msg << endl;
system("PAUSE");
exit(0);
};
//---------------------------------------------------------
void getDSPortConnectionIndex(HANDLE devHandle, UCHAR connectionIndex)
248 USB. Praktyczne programowanie z Windows API w C++

{
USB_NODE_CONNECTION_NAME connectionName;
PUSB_NODE_CONNECTION_NAME pConnectionName;
ULONG outBufferSize = 0;
ULONG bytesReturned;
bool success;
memset(&connectionName, 0, sizeof(connectionName));
connectionName.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;

ULONG nBytes = sizeof(USB_NODE_CONNECTION_INFORMATION) +


sizeof(USB_PIPE_INFO) * 30; //15 EP IN + 15 EP OUT

pConnectionInformation = new USB_NODE_CONNECTION_INFORMATION[nBytes];


Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych 249

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

stringDescRequest, nBytes, stringDescRequest,


nBytes, &bytesReturned, NULL);
if (!success) {
return NULL;
}
stringDescNode = new USB_STRING_DESCRIPTOR[(sizeof(USB_STRING_DESCRIPTOR)+
stringDescriptor->bLength)];
if (stringDescNode == NULL) {
return NULL;
}
memcpy(stringDescNode, stringDescriptor, stringDescriptor->bLength);
return stringDescNode;
}
//---------------------------------------------------------
PUSB_CONFIGURATION_DESCRIPTOR cfgDescriptor(HANDLE devHandle,
ULONG connectionIndex,
UCHAR descriptorIndex)
{
PUSB_DESCRIPTOR_REQUEST stringDescRequest = NULL;
PUSB_CONFIGURATION_DESCRIPTOR cfgDesc = NULL, cfgDescNode = NULL;
bool success;
ULONG nBytes;
ULONG bytesReturned;

UCHAR stringDescReqBuf[sizeof(USB_DESCRIPTOR_REQUEST) +
sizeof(USB_CONFIGURATION_DESCRIPTOR)];

nBytes = sizeof(stringDescReqBuf);
stringDescRequest = (PUSB_DESCRIPTOR_REQUEST)stringDescReqBuf;
cfgDesc= (PUSB_CONFIGURATION_DESCRIPTOR)(stringDescRequest+1);
memset(stringDescRequest, 0, nBytes);
stringDescRequest->ConnectionIndex = connectionIndex;
stringDescRequest->SetupPacket.bRequest=USB_REQUEST_GET_CONFIGURATION;
stringDescRequest->SetupPacket.wValue=
USB_DESCRIPTOR_MAKE_TYPE_AND_INDEX(USB_CONFIGURATION_DESCRIPTOR_TYPE,
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++

HANDLE openDevice(const GUID& devGUID, const char* vid)


{
DWORD memberIndex = 0;
DWORD deviceInterfaceDetailDataSize;
DWORD requiredSize;
HDEVINFO deviceInfoSet;
SP_DEVICE_INTERFACE_DATA deviceInterfaceData;
PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData = NULL;
HANDLE devObject = INVALID_HANDLE_VALUE;

deviceInfoSet = SetupDiGetClassDevs(&devGUID, NULL, NULL,


DIGCF_PRESENT | DIGCF_INTERFACEDEVICE);
if (deviceInfoSet == INVALID_HANDLE_VALUE)
displayError("Nie zidentyfikowano podłączonych urządzeń.\n");

deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
while(SetupDiEnumDeviceInterfaces(deviceInfoSet, NULL,
&devGUID, memberIndex,
&deviceInterfaceData)){
memberIndex++; //inkrementacja numeru interfejsu
SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &deviceInterfaceData,
NULL, 0, &deviceInterfaceDetailDataSize, NULL);
deviceInterfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)
new DWORD[deviceInterfaceDetailDataSize];
deviceInterfaceDetailData->cbSize=sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);

if (!SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &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);

for (int i = 1; i <= nDSPorts; i++) {


getDSPortConnectionIndex(deviceObject, i);
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych 253

}
printf("\nLiczba portów huba = %d\n",nDSPorts);
for (int i = 1; i<= nDSPorts; i++) {
/*
PUSB_STRING_DESCRIPTOR getStrDesc = getStringDescriptor1(deviceObject, i, 2);
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;
}
//---------------------------------------------------------

W celu dokonania identyfikacji tak skonfigurowanych urządzeń w pierwszej kolejno-


ści należy określić liczbę dostępnych portów koncentratora. W tym celu najwygodniej
jest wykorzystać rozkaz IOCTL_USB_GET_NODE_INFORMATION, za pomocą którego można
odczytać wszystkie zasoby przechowywane aktualnie w elementach struktury:
typedef struct _USB_NODE_INFORMATION {
USB_HUB_NODE NodeType;
union {
USB_HUB_INFORMATION HubInformation;
USB_MI_PARENT_INFORMATION MiParentInformation;
} u;
} USB_NODE_INFORMATION, *PUSB_NODE_INFORMATION;

— gdzie elementy typu wyliczeniowego


typedef enum _USB_HUB_NODE {
UsbHub = 0,
UsbMIParent = 1
} USB_HUB_NODE;

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++

opisane w rozdziale 3. (patrz tabela 3.7). Jednoelementowa struktura USB_MI_PARENT_


INFORMATION przechowuje w polu ULONG NumberOfInterfaces liczbę interfejsów urzą-
dzenia złożonego. Na rysunku 6.17 zaprezentowano logiczną strukturę typów danych
wykorzystywanych przez rozkaz IOCTL_USB_GET_NODE_INFORMATION.

Rysunek 6.17. Logiczna struktura typów danych wykorzystywanych przez rozkaz IOCTL_USB_GET_
NODE_INFORMATION

Zaprezentowana na listingu 6.17 funkcja getNumberOfPorts() odczytuje liczbę aktu-


alnie dostępnych portów huba.

W następnej kolejności program powinien otrzymać informację o tym, czy i do któ-


rych portów huba zostały przyłączone zewnętrzne urządzenia wykonawcze. W tym
celu aplikacje używają rozkazu IOCTL_USB_GET_NODE_CONNECTION_NAME, za pomocą któ-
rego można odczytać wszystkie aktualnie zapisane zasoby w elementach struktury:
typedef struct _USB_NODE_CONNECTION_NAME {
ULONG ConnectionIndex;
ULONG ActualLength;
WCHAR NodeName[1];
} USB_NODE_CONNECTION_NAME, *PUSB_NODE_CONNECTION_NAME;

— gdzie pole ConnectionIndex przechowuje kolejne wartości liczbowe (począwszy


od 1) przypisane do poszczególnych portów huba. Jeżeli ConnectionIndex = 0 oznacza
to, że do określonego portu koncentratora jest aktualnie dołączone aktywne urządzenie.
Pole ActualLength określa długość łańcucha znaków reprezentującego łącze symbo-
liczne identyfikujące dołączony hub. Tablica NodeName przechowuje łańcuch znaków
w formacie Unicode, reprezentujący łącze symboliczne, które identyfikuje przyłączo-
ne do portu huba urządzenie. Jeżeli do huba nie przyłączono urządzeń lub do urządze-
nia (urządzeń) nie są przypisane łącza symboliczne, lub gdy przyłączone urządzenie
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych 255

nie jest hubem (hubem na poziomie 0+n), zerowy indeks tablicy NodeName[0] zawiera
wartość NULL. Zaprezentowana na listingu 6.17 funkcja getDSPortConnectionIndex()
odczytuje stan aktualnie dostępnych portów huba.

Bardzo przydatną informacją bywa odczytanie wybranego indeksu deskryptora łańcu-


chowego dla każdego z przyłączonych urządzeń2. W tym celu programy używają roz-
kazu IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION oraz struktury danych:
typedef struct _USB_DESCRIPTOR_REQUEST {
ULONG ConnectionIndex;
struct {
UCHAR bmRequest; //bmRequestType zgodnie z dokumentacją USB [15, 16]
UCHAR bRequest;
USHORT wValue;
USHORT wIndex;
USHORT wLength;
} SetupPacket;
UCHAR Data[];
} USB_DESCRIPTOR_REQUEST, *PUSB_DESCRIPTOR_REQUEST;

— gdzie dla standardowych urządzeń USB pola bmRequest (oznaczanego jako 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),

— gdzie d jest typem deskryptora, zaś i jego indeksem.

W przypadku wyboru USB_CONFIGURATION_DESCRIPTOR_TYPE w tablicy Data zostanie


umieszczony nie tylko deskryptor konfiguracji, ale też wszystkie powiązane z nim
(agregujące) deskryptory interfejsów i punktów końcowych oraz ewentualnie deskryp-
tory producenta i specyficzne dla klasy urządzenia (patrz rozdział 3., rysunek 3.9).
Z tego powodu należy zadbać o odpowiednie ustalenie rozmiaru bufora danych w roz-
kazie IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION, gdyż w przeciwnym wypad-
ku rozkaz ten nie będzie wykonany pomyślnie.

Dla deskryptorów łańcuchowych pole wIndex przechowuje identyfikator języka [17],


w którym jest zapisana informacja o urządzeniu, lub jest równe zero dla innych typów
deskryptorów. Wskaźnik Data wskazuje bufor danych zawierający opis deskryptora.

2
Niektóre urządzenia mogą nie zawierać deskryptorów łańcuchowych.
256 USB. Praktyczne programowanie z Windows API w C++

Pole wLength określa rozmiar pola danych w drugiej fazie przekazu kontrolnego (patrz
rozdział 1.). W tabelach 6.4 i 6.5 przedstawiono odpowiednio opisy standardowych
i właściwych dla urządzeń klasy HID rozkazów struktury USB_DESCRIPTOR_REQUEST.

Tabela 6.4. Opis standardowych rozkazów struktury SetupPacket oraz pola Data struktury
USB_DESCRIPTOR_REQUEST
bmRequest bRequest wValue wIndex wLength Data
00000000b 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

00000000b USB_REQUEST_ 0 0 6 Wartości


SET_SEL (0x30) czasu [μs],
po których
urządzenie
USB 3.0
przechodzi
do stanów
U1 oraz U2
00000000b USB_REQUEST_ Opóźnienie [ns] 0 0 Nie zawiera
IZOCH_DELAY w transmisji potokiem danych
(0x31) izochronicznym USB 3.0,
liczone od momentu
wysłania pakietu przez
host do momentu jego
otrzymania przez
urządzenie

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

10000001b GET_DESCRIPTOR Typ deskryptora (bardziej 0 lub ID Długość Zawartość


(0x81) (0x06) znaczący bajt) oraz języka opisu deskryptora deskryptora
indeks deskryptora (mniej deskryptora
znaczący bajt) łańcuchowego
00000001b SET_DESCRIPTOR Typ deskryptora (bardziej 0 lub ID Długość Zawartość
(0x01) (0x07) znaczący bajt) oraz języka opisu deskryptora deskryptora
indeks deskryptora (mniej deskryptora
znaczący bajt) łańcuchowego

Na listingu 6.17 zaprezentowano trójargumentową funkcję getStringDescriptor(),


która pobiera wybrany indeks deskryptora urządzenia USB. W materiałach dołączonych
do książki, które można pobrać z witryny wydawnictwa Helion (moduł usb_R6_9.cpp),
zamieszczono dodatkowo przykładową funkcję getStringDescriptor1(), która od-
czytuje deskryptor urządzenia umieszczony w polu bString omawianej w rozdziale 3.
struktury USB_STRING_DESCRIPTOR.

Kolejną z zaprezentowanych w kodzie z listingu 6.17 funkcji jest getDSPortData(),


która korzystając z rozkazu IOCTL_USB_GET_NODE_CONNECTION_INFORMATION, pobiera in-
formacje zapisane w deskryptorze urządzenia przyłączonego do portu huba (rysunek
6.18). Deskryptor urządzenia jest elementem struktury:
typedef struct _USB_NODE_CONNECTION_INFORMATION {
ULONG ConnectionIndex;
USB_DEVICE_DESCRIPTOR DeviceDescriptor;
UCHAR CurrentConfigurationValue;
BOOLEAN LowSpeed;
BOOLEAN DeviceIsHub;
USHORT DeviceAddress;
ULONG NumberOfOpenPipes;
USB_CONNECTION_STATUS ConnectionStatus;
USB_PIPE_INFO PipeList[];
} USB_NODE_CONNECTION_INFORMATION, *PUSB_NODE_CONNECTION_INFORMATION;

— gdzie pole ConnectionIndex specyfikuje 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

Jeżeli konfiguracja systemu przewiduje występowanie zarówno koncentratorów USB


2.0, jak i USB 3.0, należy wykorzystać omówioną w rozdziale 3. (patrz tabela 3.9)
strukturę USB_HUB_INFORMATION_EX oraz rozkaz IOCTL_USB_GET_HUB_INFORMATION_EX.
Sposób postępowania został zaprezentowany w poniższym fragmencie kodu w funk-
cji getHubInformation(). Na rysunku 6.20 przedstawiono strukturę logiczną typów
danych wykorzystywanych przez rozkaz IOCTL_USB_GET_HUB_INFORMATION_EX.
#include "D:\\Program Files\\Windows Kits\\8.0\\include\\shared\\usbspec.h"
//---------------------------------------------------------
void getHubInformation(HANDLE devHandle)
{
ULONG bytesReturned;
bool success;
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych 261

Rysunek 6.20. Logiczna struktura typów danych wykorzystywanych przez rozkaz IOCTL_USB_GET_
HUB_INFORMATION_EX w systemie Windows 8 z USB 3.0

USB_HUB_INFORMATION_EX hubInformation;

success = DeviceIoControl(devHandle,
IOCTL_USB_GET_HUB_INFORMATION_EX, &hubInformation,
sizeof(hubInformation), &hubInformation,
sizeof(hubInformation), &bytesReturned, NULL);
if (success) {
printf("NumberOfPorts hub USB 2.0: %d\n",
hubInformation.u.UsbHubDescriptor.bNumberOfPorts);
printf("DescriptorType hub USB 2.0: %2.2x\n",
hubInformation.u.UsbHubDescriptor.bDescriptorType);
printf("PowerOnToPowerGood hub USB 2.0: %u\n",
hubInformation.u.UsbHubDescriptor.bPowerOnToPowerGood);
printf("NumberOfPorts hub USB 3.0: %d\n",
hubInformation.u.UsbHub30Descriptor.bNumberOfPorts);
//...
printf("HubControlCurrent: %u\n",
hubInformation.HighestPortNumber);
}
else
return;
}
//---------------------------------------------------------

W katalogu instalacyjnym WDK \7600.16385.1\...\Debuggers znajduje się aplikacja


usbview.exe, z pomocą której można w bardzo wygodny sposób otrzymać kompletne
informacje na temat aktualnie przyłączonych do komputera urządzeń USB. Program
ten może też stanowić dobry punkt odniesienia dla samodzielnie tworzonego oprogra-
mowania.
262 USB. Praktyczne programowanie z Windows API w C++

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.

Tabela 6.6. Specyfikacja struktury URB


Typ strukturalny Element unii Znaczenie
_URB_HEADER UrbHeader Struktura przechowuje podstawowe informacje
na temat żądań wysyłanych do sterownika
kontrolera hosta:
struct _URB_HEADER {
USHORT Length ;
USHORT Function ;
...
USBD_STATUS Status ;
...
} ;
Najważniejszymi elementami tej struktury są
pola Length, określające jej rozmiar, i Function,
specyfikujące kod żądania o samokomentującym
się nazewnictwie:
URB_FUNCTION_SELECT_CONFIGURATION,
URB_FUNCTION_SELECT_INTERFACE,
URB_FUNCTION_ABORT_PIPE,
URB_FUNCTION_GET_CURRENT_FRAME_NUMBER,
URB_FUNCTION_CONTROL_TRANSFER,
URB_FUNCTION_CONTROL_TRANSFER_EX,
URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER,
URB_FUNCTION_ISOCH_TRANSFER,
URB_FUNCTION_RESET_PIPE,
URB_FUNCTION_SYNC_RESET_PIPE,
URB_FUNCTION_SYNC_CLEAR_STALL,
URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE,
URB_FUNCTION_GET_DESCRIPTOR_FROM_ENDPOINT,
URB_FUNCTION_SET_DESCRIPTOR_TO_DEVICE,
URB_FUNCTION_SET_DESCRIPTOR_TO_ENDPOINT,
URB_FUNCTION_SET_FEATURE_TO_DEVICE,
URB_FUNCTION_SET_FEATURE_TO_INTERFACE,
URB_FUNCTION_SET_FEATURE_TO_ENDPOINT,
URB_FUNCTION_SET_FEATURE_TO_OTHER,
URB_FUNCTION_CLEAR_FEATURE_TO_DEVICE,
URB_FUNCTION_CLEAR_FEATURE_TO_INTERFACE,
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych 263

Tabela 6.6. Specyfikacja struktury URB — ciąg dalszy


Typ strukturalny Element unii Znaczenie
URB_FUNCTION_CLEAR_FEATURE_TO_ENDPOINT,
URB_FUNCTION_CLEAR_FEATURE_TO_OTHER,
URB_FUNCTION_GET_STATUS_FROM_DEVICE,
URB_FUNCTION_GET_STATUS_FROM_INTERFACE,
URB_FUNCTION_GET_STATUS_FROM_ENDPOINT,
URB_FUNCTION_GET_STATUS_FROM_OTHER,
URB_FUNCTION_VENDOR_DEVICE,
URB_FUNCTION_VENDOR_INTERFACE,
URB_FUNCTION_VENDOR_ENDPOINT,
URB_FUNCTION_VENDOR_OTHER,
URB_FUNCTION_CLASS_DEVICE,
URB_FUNCTION_CLASS_INTERFACE,
URB_FUNCTION_CLASS_ENDPOINT,
URB_FUNCTION_CLASS_OTHER,
URB_FUNCTION_GET_CONFIGURATION,
URB_FUNCTION_GET_INTERFACE
_URB_SELECT_ UrbSelectInterface Specyfikuje format rozkazów dla urządzenia
INTERFACE USB:
struct _URB_SELECT_INTERFACE {
struct _URB_HEADER Hdr;
USBD_CONFIGURATION_HANDLE
ConfigurationHandle ;
USBD_INTERFACE_INFORMATION
Interface ;
} ;
_URB_SELECT_ UrbSelectConfiguration Określa format rozkazów konfiguracyjnych
CONFIGURATION dla urządzenia USB:
struct _URB_SELECT_CONFIGURATION {
struct _URB_HEADER Hdr;
PUSB_CONFIGURATION_DESCRIPTOR
ConfigurationDescriptor;
USBD_CONFIGURATION_HANDLE
ConfigurationHandle;
USBD_INTERFACE_INFORMATION
Interface;
}
_URB_PIPE_REQUEST UrbPipeRequest Określa format rozkazów służących do
resetowania zawartości potoku, który zawiera
pakiet z informacjami o wystąpieniu błędu
wewnętrznego urządzenia w przypadku
transferu masowego lub przerwaniowego:
struct _URB_PIPE_REQUEST {
struct _URB_HEADER Hdr;
USBD_PIPE_HANDLE PipeHandle ;
} ;
264 USB. Praktyczne programowanie z Windows API w C++

Tabela 6.6. Specyfikacja struktury URB — ciąg dalszy


Typ strukturalny Element unii Znaczenie
_URB_FRAME_LENGTH_ UrbFrameLengthControl Obecnie nieużywane
CONTROL
_URB_GET_FRAME_ UrbGetFrameLength Obecnie nieużywane
LENGTH
_URB_SET_FRAME_ UrbSetFrameLength Obecnie nieużywane
LENGTH
_URB_GET_CURRENT_ UrbGetCurrentFrame Przechowuje aktualny numer ramki:
FRAME_NUMBER ´Number struct _URB_GET_CURRENT_FRAME_NUMBER {
struct _URB_HEADER Hdr;
ULONG FrameNumber ;
}
_URB_CONTROL_ UrbControlTransfer Definiuje format rozkazów dla danych
TRANSFER przesyłanych za pomocą transferu kontrolnego:
struct _URB_CONTROL_TRANSFER {
struct _URB_HEADER Hdr;
USBD_PIPE_HANDLE PipeHandle;
ULONG TransferFlags;
ULONG TransferBufferLength;
PVOID TransferBuffer;
PMDL TransferBufferMDL;
struct _URB *UrbLink;
...
UCHAR SetupPacket[8];
} ;
_URB_BULK_OR_ UrbBulkOrInterrupt Definiuje format rozkazów dla danych
INTERRUPT_TRANSFER ´Transfer wysyłanych za pomocą transferu masowego
lub przerwaniowego oraz dla danych
odbieranych przy użyciu transferu masowego:
struct _URB_BULK_OR_INTERRUPT_TRANSFER {
struct _URB_HEADER Hdr;
USBD_PIPE_HANDLE PipeHandle;
ULONG TransferFlags;
ULONG TransferBufferLength;
PVOID TransferBuffer;
PMDL TransferBufferMDL;
struct _URB *UrbLink;
...
};
_URB_ISOCH_TRANSFER UrbIsochronousTransfer Definiuje format rozkazów dla danych
wysyłanych lub odbieranych za pomocą
transferu izochronicznego:
struct _URB_ISOCH_TRANSFER {
struct _URB_HEADER Hdr;
USBD_PIPE_HANDLE PipeHandle;
ULONG TransferFlags;
ULONG TransferBufferLength;
PVOID TransferBuffer;
PMDL TransferBufferMDL;
...
ULONG StartFrame;
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych 265

Tabela 6.6. Specyfikacja struktury URB — ciąg dalszy


Typ strukturalny Element unii Znaczenie
ULONG NumberOfPackets;
ULONG ErrorCount;
USBD_ISO_PACKET_DESCRIPTOR
IsoPacket[1];
};
_URB_CONTROL_ UrbControlDescriptor Elementy struktury umożliwiają pobranie
DESCRIPTOR_REQUEST ´Request lub odczytanie deskryptora urządzenia USB:
struct _URB_CONTROL_DESCRIPTOR_REQUEST {
struct _URB_HEADER Hdr;
...
ULONG TransferBufferLength ;
PVOID TransferBuffer ;
PMDL TransferBufferMDL ;
struct _URB *UrbLink ;
...
UCHAR Index ;
UCHAR DescriptorType ;
USHORT LanguageId ;
...
} ;
_URB_CONTROL_GET_ UrbControlGetStatus Umożliwia pobranie statusu urządzenia, punktu
STATUS_REQUEST ´Request końcowego, interfejsu lub innego elementu
urządzenia zdefiniowanego przez użytkownika:
struct _URB_CONTROL_GET_STATUS_REQUEST {
struct _URB_HEADER Hdr;
...
ULONG TransferBufferLength ;
PVOID TransferBuffer ;
PMDL TransferBufferMDL ;
struct _URB *UrbLink ;
...
USHORT Index ;
...
} ;
_URB_CONTROL_ UrbControlFeature Umożliwia ustalenie lub wyzerowanie danych
FEATURE_REQUEST ´Request konfiguracyjnych urządzenia, punktu końcowego
lub interfejsu:
struct _URB_CONTROL_FEATURE_REQUEST {
struct _URB_HEADER Hdr;
...
struct _URB *UrbLink ;
...
USHORT FeatureSelector ;
USHORT Index ;
...
} ;
_URB_CONTROL_VENDOR_ UrbControlVendorClass Określa zdefiniowany przez producenta lub
OR_CLASS_REQUEST ´Request właściwy dla klasy urządzenia format rozkazów
dla żądań wysyłanych do urządzenia, punktu
końcowego lub interfejsu:
266 USB. Praktyczne programowanie z Windows API w C++

Tabela 6.6. Specyfikacja struktury URB — ciąg dalszy


Typ strukturalny Element unii Znaczenie
struct
_URB_CONTROL_VENDOR_OR_CLASS_REQUEST {
struct _URB_HEADER Hdr;
...
ULONG TransferFlags ;
ULONG TransferBufferLength ;
PVOID TransferBuffer ;
PMDL TransferBufferMDL ;
struct _URB *UrbLink ;
...
UCHAR RequestTypeReservedBits;
UCHAR Request;
USHORT Value;
USHORT Index;
...
};
_URB_CONTROL_GET_ UrbControlGetInterface Umożliwia pobranie interfejsu dla aktualnej
INTERFACE_REQUEST ´Request konfiguracji urządzenia:
struct _URB_CONTROL_GET_INTERFACE_REQUEST {
struct _URB_HEADER s;
...
ULONG TransferBufferLength ;
PVOID TransferBuffer ;
PMDL TransferBufferMDL ;
struct _URB *UrbLink ;
...
USHORT Interface;
...
} ;
_URB_CONTROL_GET_ UrbControlGetConfigu Umożliwia pobranie aktualnej konfiguracji
CONFIGURATION_ ´rationRequest urządzenia:
REQUEST struct
_URB_CONTROL_GET_CONFIGURATION_REQUEST {
struct _URB_HEADER Hdr;
...
ULONG TransferBufferLength;
PVOID TransferBuffer;
PMDL TransferBufferMDL;
struct _URB *UrbLink;
...
} ;

WDK definiuje tę strukturę jako:


typedef struct _URB {
union {
struct _URB_HEADER UrbHeader;
struct _URB_SELECT_INTERFACE UrbSelectInterface;
struct _URB_SELECT_CONFIGURATION UrbSelectConfiguration;
struct _URB_PIPE_REQUEST UrbPipeRequest;
struct _URB_FRAME_LENGTH_CONTROL UrbFrameLengthControl;
struct _URB_GET_FRAME_LENGTH UrbGetFrameLength;
Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych 267

struct _URB_SET_FRAME_LENGTH UrbSetFrameLength;


struct _URB_GET_CURRENT_FRAME_NUMBER UrbGetCurrentFrameNumber;
struct _URB_CONTROL_TRANSFER UrbControlTransfer;
struct _URB_BULK_OR_INTERRUPT_TRANSFER UrbBulkOrInterruptTransfer;
struct _URB_ISOCH_TRANSFER UrbIsochronousTransfer;
struct _URB_CONTROL_DESCRIPTOR_REQUEST UrbControlDescriptorRequest;
struct _URB_CONTROL_GET_STATUS_REQUEST UrbControlGetStatusRequest;
struct _URB_CONTROL_FEATURE_REQUEST UrbControlFeatureRequest;
struct _URB_CONTROL_VENDOR_OR_CLASS_REQUEST UrbControlVendorClassRequest;
struct _URB_CONTROL_GET_INTERFACE_REQUEST UrbControlGetInterfaceRequest;
struct _URB_CONTROL_GET_CONFIGURATION_REQUEST UrbControlGetConfigurationRequest;
}
} URB, *PURB ;

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.

O praktycznych aspektach wykorzystania struktury URB i funkcji UsbBuildGetDescrip-


torRequest() można przeczytać w artykule Kosty Koemana [12].
268 USB. Praktyczne programowanie z Windows API w C++

Podsumowanie
Niniejszy rozdział należy traktować jako rozwinięcie dwóch poprzednich. Zawarto
w nim opis praktycznych metod wykorzystywania w działających programach zaso-
bów systemowych, które są odpowiedzialne za transmisję danych w standardzie USB.
Dotyczy to zarówno urządzeń klasy HID (z wykorzystaniem funkcji eksportowych
biblioteki 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.

Listing 6.18. Przykład drukowania łańcucha znaków


#include <windows>
#include <initguid>
#include <iostream>
#pragma option push -a1
#include <setupapi>
#pragma option pop

#define MAX_BUFFER_SIZE 64

using namespace std;


//-----------------------------------------------------
HANDLE openDevice(const GUID& devGUID, const char* vid)
{
// Patrz funkcja openDevice() — listing 6.17
return devObject;
}
//---------------------------------------------------------
const GUID GUID_DEVINTERFACE_USBPRINT =
{0x28d78fad,0x5a12,0x11D1,0xae,0x5b,0x00,0x00,0xf8,0x03,0xa8,0xc2};
//---------------------------------------------------------
int main()
{
const char* VID = "vid_0482"; //Identyfikatory drukarki
const char* PID = "pid_03c5";
HANDLE deviceObject = INVALID_HANDLE_VALUE;
DWORD numberOfBytesWritten = 0;
char bufferOut[MAX_BUFFER_SIZE];

deviceObject = openDevice(GUID_DEVINTERFACE_USBPRINT, VID);


Rozdział 6. ♦ Odblokowanie urządzenia do transmisji. Odczyt i zapis danych 269

cout <<endl;

char *text = "Tekst przeznaczony do transmisji";


strcpy(bufferOut, text);
printf("\n%s", bufferOut);
//lub sprintf(...);
if(WriteFile(deviceObject, &bufferOut[0], strlen(bufferOut),
&numberOfBytesWritten, NULL))
printf("\nLiczba wysłanych bajtów %d\n", numberOfBytesWritten);

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.

Tabela 7.1. Wersje biblioteki KMDF używane przez systemy operacyjne


Wersja biblioteki KMDF
System operacyjny
1.0 1.1 1.5 1.7 1.9 1.11
Windows 8 ― ― ― ― √ √
Windows 7 ― ― ― ― √ √
Windows Vista ― ― √ √ √ √
Windows XP √ √ √ √ √ √

Tabela 7.2. Zgodność współinstalatorów WinUSB oraz KMDF


Współinstalator WinUSB Wersja biblioteki KMDF Współinstalator KMDF
Winusbcoinstaller.dll 1.5 lub późniejsza Wdfcoinstaller01005.dll
Wdfcoinstaller01007.dll
Wdfcoinstaller01009.dll
WDFUpdate_0100X.dll
Winusbcoinstaller2.dll 1.9 lub późniejsza Wdfcoinstaller01009.dll
WDFUpdate_01009.dll
Winusbcoinstaller2.dll 1.11 WdfCoInstaller01011.dll

Przygotowanie pakietu instalacyjnego


Aby winusb.sys stał się sterownikiem funkcyjnym danego urządzenia, należy samo-
dzielnie stworzyć katalog instalacyjny, zawierający właściwe dla używanej wersji syste-
mu biblioteki współinstalatorów oraz odpowiedni plik .inf, tak jak pokazano na rysunku
7.4. Plik .inf powinien zawierać poprawne identyfikatory VID oraz PID pochodzące
z deskryptora urządzenia oraz samodzielnie wygenerowany przez użytkownika (np.
za pomocą kombinacji Ctrl+Shift+G)1 identyfikator DeviceInterfaceGUIDs, który jest
wykorzystywany do identyfikacji odpowiedniego interfejsu. Na listingu 7.1 pokazano
zawartość przykładowego pliku .inf instalującego urządzenie klasy HID2 jako urządze-
nie winusb w 64-bitowym systemie Windows 7.

Listing 7.1. Zawartość przykładowego pliku .inf dla urządzenia winusb


[Version]
Signature = "$Windows NT$"
Class = HIDClass
ClassGUID={745A17A0-74D3-11D0-B6FE-00A0C90F57DA} ; patrz rozdział 2., tabela 2.4
Provider = %ProviderName%
DriverVer=09/18/2012,1.0.0

1
Microsoft Visual Studio generuje 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++

; =================Copy Files Section================


[_CopyFiles_sys]
winusb.sys
; ==============Destination Directories==============
[DestinationDirs]
DefaultDestDir = 12 ; %SystemRoot%\system32\drivers
_CopyFiles_sys = 12
; =================== Strings =======================
[Strings]
ProviderName="Praktyczne programowanie"
USB\MyDevice.DeviceDesc="WinUSB Device"
WinUSB_SvcDesc="WinUSB Device"
DISK_NAME="My Install Disk"

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.

Tworząc własne pakiety instalacyjne przeznaczone do użycia w 64-bitowych syste-


mach operacyjnych, warto pamiętać, że systemy te charakteryzują się wbudowanymi
mechanizmami kontroli zabezpieczeń, które m.in. wymagają posługiwania się podpi-
sanymi cyfrowo sterownikami. Bardzo często zdarza się, że użytkownicy, którzy mają
jeden niepodpisany sterownik, próbują całościowo wyłączać systemowe mechanizmy
kontroli zabezpieczeń. Należy być świadomym faktu, że Windows Driver Kit oferuje
wszystkie niezbędne narzędzia służące do cyfrowego podpisywania sterowników.
W tym celu można użyć programów narzędziowych:
 inf2cat.exe — tworzy plik .cat dla katalogu sterownika,
 makecert.exe — generuje odpowiedni certyfikat,
 certmgr.exe — menedżer zarządzający certyfikatami,
 signtool.exe — umożliwia podpisanie pliku .cat.

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).

Na rysunku 7.2 zaprezentowano sekwencję poleceń tworzących magazyn certyfikatów,


umieszczenie w nim wygenerowanego certyfikatu oraz podpisanie pliku .cat.

Szczegóły utworzonego certyfikatu można obejrzeć, korzystając z menedżera certmgr,


tak jak zaprezentowano na rysunku 7.3.

Dodatkowe wiadomości na temat metod podpisywania sterowników są dostępne w pli-


kach pomocy MSDN oraz WDK właściwych dla używanych wersji systemu operacyj-
nego. Na rysunku 7.4 pokazano zawartość typowego katalogu instalacyjnego urządze-
nia winusb w 64-bitowym systemie operacyjnym.
Rozdział 7. ♦ Biblioteki WinUSB oraz LibUSB 275

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.2. Utworzenie przykładowego magazynu certyfikatów myCertStore, przygotowanie


i umieszczenie w nim certyfikatu myTestCert.cer oraz podpisanie pliku myUSBDevice.cat
w 64-bitowym systemie Windows 7. W Windows 8 czynności te przebiegają tak samo

Rysunek 7.3. Szczegółowe informacje na temat wygenerowanego przykładowego certyfikatu


276 USB. Praktyczne programowanie z Windows API w C++

Rysunek 7.4. Zawartość typowego katalogu instalacyjnego urządzenia winusb w 64-bitowym


systemie operacyjnym. Nazwa podkatalogu amd64 odnosi się jedynie do określenia stosowanej
64-bitowej architektury. Dla 64-bitowych komponentów Microsoft Windows stosuje zamiennie
oznaczenia x64 oraz amd64

Po prawidłowym skonfigurowaniu zawartości pakietu instalacyjnego urządzenie na-


leży podłączyć i samodzielnie zainstalować. Poprawnie zainstalowany i gotowy do
użycia sprzęt powinien być widoczny w menedżerze urządzeń, tak jak pokazano na
rysunku 7.5. Rysunek 7.6 przedstawia zawartość typowego stosu sterowników urzą-
dzenia winusb.

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

Biblioteka winusb.dll implementuje zestaw funkcji eksportowych, umożliwiający pro-


gramiście bezpośrednie wykonywanie operacji wejścia-wyjścia bez konieczności jaw-
nego posługiwania się funkcjami oraz rozkazami opisanymi w rozdziale 6. Prezento-
wane w dalszej części podrozdziału funkcje opakowują rozkazy używane na poziomie
jądra systemu i przekazują je bezpośrednio do sterownika winusb.sys w celu dalszego
przetwarzania. Sterownik winusb.sys tworzy w trybie jądra operacyjny obiekt urządze-
nia FDO, tak jak obrazowo pokazano na rysunku 7.7.

Rysunek 7.7.
Model warstwowy
sterowników
urządzenia winusb

Funkcje eksportowe biblioteki WinUSB


Zasoby biblioteki są dostępne w module winusb.h oraz bibliotekach winusb.dll i winusb.
lib. Bibliotekę winusb.dll można łączyć z programem w trybie użytkownika w sposób
statyczny, korzystając z modułu winusb.lib, lub dynamiczny, wykorzystując winusb.dll.
Należy zwrócić uwagę na fakt, że niektóre popularne kompilatory C++ mogą niewłaści-
wie odwzorowywać identyfikator biblioteki łączonej statycznie winusb.lib w przestrzeni
278 USB. Praktyczne programowanie z Windows API w C++

adresowej głównego procesu (programu wykonawczego). Z tego powodu bezpiecz-


niejszym sposobem wykorzystania zasobów WinUSB jest często dynamiczne odwzo-
rowywanie identyfikatora winusb.dll w przestrzeni adresowej głównego procesu oraz
pobieranie funkcji eksportowych biblioteki.

Prawidłowo wykonane funkcje eksportowe biblioteki winusb.dll zwracają wartość TRUE.


Kody możliwych błędów powinny być diagnozowane za pomocą funkcji GetLastError().

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
);

Funkcja inicjalizuje wewnętrzne struktury biblioteki WinUSB oraz pobiera identyfi-


kator InterfaceHandle pierwszego interfejsu (interfejsu podstawowego) urządzenia
identyfikowanego przez DeviceHandle. Identyfikator DeviceHandle sterownika urzą-
dzenia wykonawczego jest zwracany przez funkcję CreateFile(). Większość zasobów
biblioteki WinUSB jest wywoływana asynchronicznie, dlatego też należy pamiętać
o ustaleniu w funkcji CreateFile() znacznika FILE_FLAG_OVERLAPPED.

Na listingu 7.2 zaprezentowano jeden z możliwych sposobów dynamicznego łączenia


biblioteki winusb.dll z programem głównym, odczytania adresów funkcji eksportowych
WinUsb_Initialize() i WinUsb_Free() oraz ich wywołania w programie głównym.

Listing 7.2. Inicjalizacja biblioteki WinUSB


#include <windows>
#include "C:\\WinDDK\\7600.16385.1\\inc\\ddk\winusb.h"
#include "C:\\WinDDK\\7600.16385.1\\inc\\api\\Usb100.h"
#include <iostream>
#include <assert>
#include <initguid>
#pragma option push -a1
#include <setupapi>
#pragma option pop

using namespace std;

//Identyfikator GUID interfejsu urządzenia powinien być poprawnie


//wygenerowany i odczytany z właściwego pliku .inf
Rozdział 7. ♦ Biblioteki WinUSB oraz LibUSB 279

DEFINE_GUID(InterfaceClassGuidConstant, 0x45CA71ED, 0xCE1C, 0x44F2, 0x82,\


0xDE, 0x87, 0xD8, 0xD8, 0xFF, 0x6C, 0x1E);
GUID InterfaceClassGuid = InterfaceClassGuidConstant;
//...
HMODULE hWinUSBLib;
WINUSB_INTERFACE_HANDLE interfaceHandle = INVALID_HANDLE_VALUE;
HANDLE deviceHandle = INVALID_HANDLE_VALUE;
BOOL bResult;
//--------------------------------------------------------------
int main(){
//Odwzorowanie identyfikatora hWinUSBLib biblioteki winusb.dll w
//przestrzeni adresowej głównego procesu
hWinUSBLib = LoadLibrary("C:\\Windows\\System32\\WinUSB.DLL");
if (!hWinUSBLib)
displayError("Błąd dołączenia biblioteki WinUSB.DLL.");

BOOL (__stdcall *WinUsb_Initialize)(IN HANDLE DeviceHandle,


OUT PWINUSB_INTERFACE_HANDLE InterfaceHandle);
BOOL (__stdcall *WinUsb_Free)(IN WINUSB_INTERFACE_HANDLE InterfaceHandle);
//Pobranie adresów funkcji eksportowych
(FARPROC&) WinUsb_Initialize=GetProcAddress(hWinUSBLib, "WinUsb_Initialize");
(FARPROC&) WinUsb_Free=GetProcAddress(hWinUSBLib, "WinUsb_Free");

if (! WinUsb_Initialize || ! WinUsb_Free){
FreeLibrary(hWinUSBLib);
displayError("Nie znaleziono żadnych funkcji eksportowych.\n");
}
//-----------------------------------------------------------
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 zwalnia identyfikator interfejsu przydzielony uprzednio za pomocą WinUsb_Ini-


tialize() lub kolejne identyfikatory wskazane przez WinUsb_GetAssociatedInterface().

Funkcja WinUsb_AbortPipe()
BOOL __stdcall WinUsb_AbortPipe(
IN WINUSB_INTERFACE_HANDLE InterfaceHandle,
IN UCHAR PipeID
);

Wywoływana asynchronicznie funkcja przerywa (anuluje) transfer danych realizowa-


ny do punktu końcowego urządzenia wykonawczego. Parametr wejściowy Interface-
Handle jest identyfikatorem interfejsu dla podstawowej konfiguracji urządzenia wyko-
nawczego. Parametr ten jest zwracany przez funkcję WinUsb_Initialize(). Parametr
wejściowy PipeID jest identyfikatorem punktu końcowego dla danych konfiguracyj-
nych, identyfikujących i kontrolnych urządzenia. Parametr zawiera 7-bitowy adres
punktu końcowego oraz bit identyfikujący kierunek transferu danych. PipeID odpo-
wiada zawartości pola bEndpointAddress w deskryptorze punktu końcowego (patrz
rozdział 3., tabela 3.12).

Funkcja WinUsb_ControlTransfer()
BOOL __stdcall WinUsb_ControlTransfer(
IN WINUSB_INTERFACE_HANDLE InterfaceHandle,
IN WINUSB_SETUP_PACKET SetupPacket,
OUT PUCHAR Buffer,
IN ULONG BufferLength,
OUT PULONG LengthTransferred,
IN LPOVERLAPPED Overlapped
);

Funkcja realizuje kontrolny transfer danych. Parametr wejściowy InterfaceHandle


identyfikuje interfejs dla danej konfiguracji. Identyfikator pierwszego interfejsu w kon-
figuracji (interfejsu podstawowego) powinien być uzyskany przez wywołanie funkcji
WinUsb_Initialize(). Identyfikatory alternatywnych interfejsów są zwracane przez
funkcję WinUsb_GetAssociatedInterface(). Parametr SetupPacket jest egzemplarzem
struktury WINUSB_SETUP_PACKET opisanej w tabeli 7.3.
Rozdział 7. ♦ Biblioteki WinUSB oraz LibUSB 281

Tabela 7.3. Zawartość struktury WINUSB_SETUP_PACKET


Typ Element struktury Opis
UCHAR RequestType Typ rozkazu (żądania) jest zapisany w poszczególnych bitach słowa:
D7: kierunek transferu danych
0b — host → urządzenie (OUT)
1b — urządzenie → host (IN)
D6 – D5: typ żądania
00b — standardowe
01b — właściwe danej klasie urządzeń
10b — producenta
11b — zarezerwowane
D4 – D0: adresat
000000b — urządzenie
000001b — interfejs
000010b — punkt końcowy
000011b — inne
W module usb100.h zdefiniowano makrodefinicje pomocne
w określaniu zawartości pola RequestType (bmRequest według
WDK lub bmRequestType zgodnie z dokumentacją USB):
//kierunek transferu
BMREQUEST_HOST_TO_DEVICE 0
BMREQUEST_DEVICE_TO_HOST 1
//typ żądania
BMREQUEST_STANDARD 0
BMREQUEST_CLASS 1
BMREQUEST_VENDOR 2
//adresat
BMREQUEST_TO_DEVICE 0
BMREQUEST_TO_INTERFACE 1
BMREQUEST_TO_ENDPOINT 2
BMREQUEST_TO_OTHER 3
UCHAR Request Kod (numer) rozkazu:
USB_REQUEST_CLEAR_FEATURE (0x01) — wyzerowanie
określonej właściwości interfejsu lub punktu końcowego;
USB_REQUEST_GET_CONFIGURATION (0x08) — pobranie
aktualnej konfiguracji urządzenia;
USB_REQUEST_GET_DESCRIPTOR (0x06) — pobranie aktualnego
deskryptora urządzenia lub konfiguracji;
USB_REQUEST_GET_INTERFACE (0x0A) — pobranie aktualnego
ustawienia interfejsu;
USB_REQUEST_GET_STATUS (0x00) — pobranie statusu
urządzenia, interfejsu lub punktu końcowego;
USB_REQUEST_SET_ADDRESS (0x05) — wybór adresu
urządzenia;
USB_REQUEST_SET_CONFIGURATION (0x09) — wybór
konfiguracji;
282 USB. Praktyczne programowanie z Windows API w C++

Tabela 7.3. Zawartość struktury WINUSB_SETUP_PACKET — ciąg dalszy


Typ Element struktury Opis
USB_REQUEST_SET_DESCRIPTOR (0x07) — uaktualnienie
lub dodanie nowego deskryptora;
USB_REQUEST_SET_FEATURE (0x03) — ustalenie określonej
właściwości interfejsu lub punktu końcowego;
USB_REQUEST_SET_INTERFACE (0x0B) — wybór ustawienia
interfejsu;
USB_REQUEST_SYNC_FRAME (0x0C) — odczyt numeru ramki
synchronizacyjnej
USHORT Value Zawartość pola zależy od rozkazu — patrz rozdział 6., tabele 6.4 i 6.5
USHORT Index Zawartość pola zależy od typu adresata rozkazu (patrz rysunki 7.8
oraz 7.9). Szczegółowe informacje wykorzystywane przez strukturę
USB_DESCRIPTOR_REQUEST są przedstawione w rozdziale 6.
w tabelach 6.4 i 6.5
USHORT Length Pole określa długość danych przesyłanych w trakcie transferu
kontrolnego w drugiej fazie przekazu (DATA OUT lub DATA IN)
— patrz rozdział 1., rysunki 1.46 – 1.47 oraz 1.68 – 1.69

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

Parametr wyjściowy Buffer wskazuje bufor zawierający transferowane dane. Parametr


BufferLength zawiera liczbę transferowanych bajtów z wyłączeniem pakietu SETUP.
Liczba transferowanych bajtów nie powinna być większa od rozmiaru bufora danych.
Opcjonalny wskaźnik LengthTransferred wskazuje aktualną liczbę transferowanych baj-
tów (może wskazywać wartość NULL), zaś wskaźnik Overlapped — strukturę OVERLAPPED.

Funkcja WinUsb_FlushPipe()
BOOL __stdcall WinUsb_FlushPipe(
IN WINUSB_INTERFACE_HANDLE InterfaceHandle,
IN UCHAR PipeID
);
Rozdział 7. ♦ Biblioteki WinUSB oraz LibUSB 283

Wywoływana asynchronicznie funkcja usuwa dane z bufora punktu końcowego. Zna-


czenie jej parametrów jest identyczne jak w przypadku WinUsb_AbortPipe().

Funkcja WinUsb_GetAssociatedInterface()
BOOL __stdcall WinUsb_GetAssociatedInterface(
IN WINUSB_INTERFACE_HANDLE InterfaceHandle,
IN UCHAR AssociatedInterfaceIndex,
OUT PWINUSB_INTERFACE_HANDLE AssociatedInterfaceHandle
);

Wywoływana asynchronicznie funkcja odzyskuje kolejne identyfikatory interfejsów


właściwe danej konfiguracji. Inaczej rzecz ujmując, funkcja kontynuuje proces wyszu-
kiwania identyfikatorów interfejsów rozpoczęty przez wywołanie WinUsb_Initialize().
Parametr InterfaceHandle wskazuje identyfikator pierwszego interfejsu (interfejsu
podstawowego) pobranego za pomocą WinUsb_Initialize(). Parametr Associated-
InterfaceIndex przechowuje indeks identyfikatora aktualnego interfejsu. Wskaźnik
AssociatedInterfaceHandle wskazuje identyfikator kolejnego interfejsu właściwego
danej konfiguracji urządzenia wykonawczego. Kiedy identyfikator nie jest używany,
powinien zostać zwolniony za pomocą funkcji WinUsb_Free().

Funkcja WinUsb_GetCurrentAlternateSetting()
BOOL __stdcall WinUsb_GetCurrentAlternateSetting(
IN WINUSB_INTERFACE_HANDLE InterfaceHandle,
OUT PUCHAR AlternateSetting
);

Asynchronicznie wywoływana funkcja jest wykorzystywana do określania alterna-


tywnych ustawień interfejsu identyfikowanego przez InterfaceHandle. Alternatywne
ustawienia interfejsu są identyfikowane przez wskaźnik 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
);

Asynchronicznie wywoływana funkcja służy do odczytu żądanego typu deskryptora


DescriptorType, który może być jednym z: USB_DEVICE_DESCRIPTOR_TYPE, USB_CONFI-
GURATION_DESCRIPTOR_TYPE lub USB_STRING_DESCRIPTOR_TYPE. Parametr Index jest in-
deksem deskryptora, a LanguageID identyfikuje język opisu, jeżeli żądanym deskryptorem
jest deskryptor łańcuchowy [17]. Wskaźnik Buffer wskazuje bufor danych przechowują-
cy zawartość deskryptora, BufferLength określa rozmiar (w bajtach) bufora, zaś wskaź-
nik LengthTransferred wskazuje liczbę bajtów rzeczywiście umieszczonych w buforze.
284 USB. Praktyczne programowanie z Windows API w C++

Funkcja WinUsb_GetOverlappedResult()
BOOL __stdcall WinUsb_GetOverlappedResult(
IN WINUSB_INTERFACE_HANDLE InterfaceHandle,
IN LPOVERLAPPED lpOverlapped,
OUT LPDWORD lpNumberOfBytesTransferred,
IN BOOL bWait
);

Funkcja pod postacią wskaźnika lpNumberOfBytesTransferred zwraca liczbę aktualnie


transferowanych bajtów podczas wykonywania synchronicznych operacji zapisu lub
odczytu danych do lub z portu. Znaczenie pozostałych parametrów było już omawia-
ne w tym podrozdziale.

Funkcja WinUsb_GetPipePolicy()
BOOL __stdcall WinUsb_GetPipePolicy(
IN WINUSB_INTERFACE_HANDLE InterfaceHandle,
IN UCHAR PipeID,
IN ULONG PolicyType,
INOUT PULONG ValueLength,
OUT PVOID Value
);

Asynchronicznie wywoływana funkcja pobiera parametry transmisji PolicyType wła-


ściwe dla określonego punktu końcowego identyfikowanego przez PipeID. Wartością
domyślną wszystkich elementów jest FALSE — 0, ale można im przypisać następujące
wartości:
 SHORT_PACKET_TERMINATE (0x01) — jeżeli pole danych pakietu posiada
rozmiar mniejszy, niż jest to zapisane w polu wMaxPacketSize deskryptora
punktu końcowego, oznacza to, że pakiet tego typu będzie kończył każde
żądanie zapisu lub transmisji danych.
 AUTO_CLEAR_STALL (0x02) — reset pakietu STALL (patrz rozdział 1., tabela 1.11).
 PIPE_TRANSFER_TIMEOUT (0x03) — czas przeterminowania operacji wyrażony
w milisekundach. Sterownik kontrolera hosta anuluje transfer danych, jeżeli
nie został on skompletowany w określonym przedziale czasu.
 IGNORE_SHORT_PACKETS (0x04) — włączenie tej opcji powoduje ignorowanie
przez sterownik kontrolera hosta pakietu z polem danych mniejszym, niż jest
to zapisane w polu wMaxPacketSize deskryptora punktu końcowego. Zakończenie
operacji czytania z portu (skompletowanie danych wejściowych) nastąpi dopiero
po odebraniu określonej liczby bajtów.
 ALLOW_PARTIAL_READS (0x05) — wyłączenie tej opcji (FALSE) powoduje
niepowodzenie odczytu danych, jeżeli masowy lub przerwaniowy punkt
końcowy zwróci pakiet niemieszczący się w buforze odbiorczym. Włączenie
opcji (TRUE) powoduje odrzucenie nadmiarowej porcji danych, w przypadku
gdy masowy lub przerwaniowy punkt końcowy wysyła ich więcej, niż może
pomieścić bufor odbiornika.
Rozdział 7. ♦ Biblioteki WinUSB oraz LibUSB 285

 AUTO_FLUSH (0x06) — opcja używana razem z ALLOW_PARTIAL_READS powoduje


usuwanie z bufora odbiornika nadmiarowej porcji informacji.
 MAXIMUM_TRANSFER_SIZE (0x08) — maksymalny rozmiar transmitowanego
pakietu.
 RESET_PIPE_ON_RESUME (0x09) — resetowanie punktu końcowego.

Parametr PipeID odpowiada wartości zapisanej w polu bEndpointAddress deskryptora


punktu końcowego (patrz rozdział 3., tabela 3.12). Wskaźnik ValueLength wskazuje
rozmiar bufora danych przechowującego parametry transmisji i wskazywanego przez
wskaźnik ogólny Value.

Funkcja WinUsb_GetPowerPolicy()
BOOL __stdcall WinUsb_GetPowerPolicy(
IN WINUSB_INTERFACE_HANDLE InterfaceHandle,
IN ULONG PolicyType,
INOUT PULONG ValueLength,
OUT PVOID Value
);

Asynchronicznie wywoływana funkcja pobiera parametry opisujące sposób zasilania


urządzenia. Parametr PolicyType może mieć wartość AUTO_SUSPEND (0x81) lub SUSPEND_
DELAY (0x83). Jeżeli zostanie wybrany pierwszy parametr, funkcja pod postacią wskaź-
nika Value zwróci wartość odpowiadającą przejściu urządzenia do stanu wstrzymania
działania (ang. suspend). Wybór drugiej opcji powoduje zwrócenie przez funkcję pod
postacią wskaźnika Value wartości informującej o wyspecyfikowaniu minimalnego
przedziału czasu (wyrażonego w milisekundach), w którym sterownik WinUSB powi-
nien umożliwić kontynuację transferu danych, zanim urządzenie przejdzie do stanu
wstrzymania działania. Wskaźnik ValueLength wskazuje zmienną określającą rozmiar
bufora danych przechowującego wartość Value.

Funkcja WinUsb_QueryDeviceInformation()
BOOL __stdcall WinUsb_QueryDeviceInformation(
IN WINUSB_INTERFACE_HANDLE InterfaceHandle,
IN ULONG InformationType,
INOUT PULONG BufferLength,
OUT PVOID Buffer
);

Funkcja pobiera informacje na temat urządzenia, którego interfejs jest identyfikowany


poprzez InterfaceHandle. Parametrowi InformationType należy przypisać wartość
DEVICE_SPEED (0x01). Wskaźnik BufferLength wskazuje zmienną określającą maksy-
malną liczbę bajtów umieszczonych w buforze wskazywanym przez Buffer. Funkcja
umieszcza w buforze wskazywanym przez Buffer wartość określającą szybkość trans-
feru danych, którym może się posługiwać urządzenie: wartość 0x03 oznacza możliwość
uzyskania transferu HS lub SS, zaś 0x01 oznacza urządzenie FS lub LS.
286 USB. Praktyczne programowanie z Windows API w C++

Funkcja WinUsb_QueryInterfaceSettings()
BOOL __stdcall WinUsb_QueryInterfaceSettings(
IN WINUSB_INTERFACE_HANDLE InterfaceHandle,
IN UCHAR AlternateSettingNumber,
OUT PUSB_INTERFACE_DESCRIPTOR UsbAltInterfaceDescriptor
);

Funkcja pobiera deskryptor interfejsu dla specyficznych ustawień alternatywnych.


Parametr AlternateSettingNumber przechowuje wartość kolejnych alternatywnych
ustawień interfejsu w tablicy interfejsów danej konfiguracji. Wskaźnik UsbAltInter
´faceDescriptor wskazuje strukturę USB_INTERFACE_DESCRIPTOR opisującą deskryptor
interfejsu urządzenia (patrz rozdział 3., tabela 3.14).
//--------------------------------------------------------------
USB_INTERFACE_DESCRIPTOR *interfaceDescriptor = new \
USB_INTERFACE_DESCRIPTOR[sizeof(USB_INTERFACE_DESCRIPTOR)];
WINUSB_PIPE_INFORMATION *pipeInformation = new \
WINUSB_PIPE_INFORMATION[sizeof(WINUSB_PIPE_INFORMATION)];
//...
bResult = WinUsb_QueryInterfaceSettings(interfaceHandle, 0,
interfaceDescriptor);
if (bResult) {
for (int numEP = 0; numEP < interfaceDescriptor->bNumEndpoints; numEP++){
bResult = WinUsb_QueryPipe(interfaceHandle, 0, numEP, pipeInformation);
if (bResult){
if (pipeInformation->PipeType == UsbdPipeTypeControl){ //transfer kontrolny
printf("EP: %d typ potoku: ID potoku: %d.\n", numEP,
pipeInformation->PipeType, pipeInformation->PipeId);
//...
}
//--------------------------------------------------------------

Funkcja WinUsb_QueryPipe()
BOOL __stdcall WinUsb_QueryPipe(
IN WINUSB_INTERFACE_HANDLE InterfaceHandle,
IN UCHAR AlternateInterfaceNumber,
IN UCHAR PipeIndex,
OUT PWINUSB_PIPE_INFORMATION PipeInformation
);

Funkcja pobiera informacje na temat punktu końcowego identyfikowanego przez


PipeIndex. Zakres wartości PipeIndex powinien być mniejszy od wartości zapisanych
w polu bNumEndpoints struktury opisującej deskryptor interfejsu (patrz rozdział 3., ta-
bela 3.14). Parametr AlternateInterfaceNumber określa numer alternatywnego inter-
fejsu dla danej konfiguracji. Wskaźnik PipeInformation wskazuje strukturę WINUSB_
PIPE_INFORMATION opisaną w tabeli 7.4.
//--------------------------------------------------------------
WINUSB_PIPE_INFORMATION *pipeInformation = new \
WINUSB_PIPE_INFORMATION[sizeof(WINUSB_PIPE_INFORMATION)];
//...
bResult = WinUsb_QueryPipe(interfaceHandle, 0, (UCHAR) 0, pipeInformation);
if (USB_ENDPOINT_DIRECTION_IN(pipeInformation->PipeId)) == TRUE {
Rozdział 7. ♦ Biblioteki WinUSB oraz LibUSB 287

Tabela 7.4. Zasoby struktury WINUSB_PIPE_INFORMATION


Typ Element struktury Opis
USBD_PIPE_TYPE PipeType Patrz rozdział 3., tabela 3.1. Biblioteka WinUSB
nie obsługuje potoków izochronicznych
UCHAR PipeID Identyfikator punktu końcowego. W module usb100.h
są zdefiniowane dwie makrodefinicje pomocne
w określaniu kierunku transferu danych związanego
z określonymi typami punktów końcowych EP IN
oraz EP OUT:
USB_ENDPOINT_DIRECTION_IN(PipeId)
USB_ENDPOINT_DIRECTION_OUT(PipeId)
USHORT MaximumPacketSize Patrz rozdział 3., tabela 3.2
UCHAR Interval Patrz rozdział 3., tabela 3.2

//EP IN
}
if (USB_ENDPOINT_DIRECTION_OUT(pipeInformation->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
);

Funkcja pobiera (czyta) dane z bufora punktu końcowego identyfikowanego przez


PipeID. Parametr PipeID odpowiada zawartości pola bEndpointAddress struktury opisu-
jącej deskryptor punktu końcowego (patrz rozdział 3., tabela 3.12). Wskaźnik Buffer
wskazuje bufor danych wejściowych. Parametr BufferLength określa maksymalną
liczbę bajtów umieszczanych w buforze wejściowym. Wskaźnik LengthTransferred
wskazuje liczbę aktualnie przeczytanych bajtów. Liczba faktycznie odebranych bajtów
może być mniejsza niż deklarowany rozmiar bufora; może to oznaczać zakończenie
odbierania danych (patrz opis znacznika SHORT_PACKET_TERMINATE). Liczbę faktycznie
odebranych bajtów funkcja umieszcza w zmiennej wskazywanej przez LengthTrans-
ferred, stanowiącej przedostatni parametr. W ten sposób działa mechanizm ochrony
dla odbieranych danych. Wskaźnik Overlapped wskazuje strukturę OVERLAPPED.
//--------------------------------------------------------------
ULONG bufferSize = 0;
UCHAR* buffer = new UCHAR[bufferSize*sizeof(UCHAR)];
ULONG lengthTransferred = 0;
WINUSB_PIPE_INFORMATION *pipeInformation = new \
WINUSB_PIPE_INFORMATION[sizeof(WINUSB_PIPE_INFORMATION)];
WinUsb_ReadPipe(interfaceHandle,
288 USB. Praktyczne programowanie z Windows API w C++

USB_ENDPOINT_DIRECTION_IN(pipeInformation->PipeId),
buffer, bufferSize, &lengthTransferred, 0);
printf("Odczyt z potoku %d: %s \nbajty przeczytane: %d.\n",
USB_ENDPOINT_DIRECTION_IN(pipeInformation->PipeId),
buffer, lengthTransferred);
//--------------------------------------------------------------

Funkcja WinUsb_ResetPipe()
BOOL __stdcall WinUsb_ResetPipe(
IN WINUSB_INTERFACE_HANDLE InterfaceHandle,
IN UCHAR PipeID
);

Funkcja resetuje zawartość bufora punktu końcowego identyfikowanego przez PipeID.

Funkcja WinUsb_SetCurrentAlternateSetting()
BOOL __stdcall WinUsb_SetCurrentAlternateSetting(
IN WINUSB_INTERFACE_HANDLE InterfaceHandle,
IN UCHAR AlternateSetting
);

Funkcja umieszcza alternatywne ustawienia interfejsu identyfikowanego przez Inter-


faceHandle w tablicy interfejsów danej konfiguracji urządzenia wykonawczego.

Funkcja WinUsb_SetPipePolicy()
BOOL __stdcall WinUsb_SetPipePolicy(
IN WINUSB_INTERFACE_HANDLE InterfaceHandle,
IN UCHAR PipeID,
IN ULONG PolicyType,
IN ULONG ValueLength,
IN PVOID Value
);

Asynchronicznie wywoływana funkcja konfiguruje parametry transmisyjne związane


z wybranym punktem końcowym. Znaczenie poszczególnych parametrów jest iden-
tyczne jak dla WinUsb_GetPipePolicy().
//--------------------------------------------------------------
WINUSB_PIPE_INFORMATION *pipeInformation = new \
WINUSB_PIPE_INFORMATION[sizeof(WINUSB_PIPE_INFORMATION)];
ULONG timeOut = 1000;
//Deklarowany czas przeterminowania wynosi 1000 milisekund
bResult = WinUsb_SetPipePolicy(interfaceHandle, pipeInformation->PipeId,
PIPE_TRANSFER_TIMEOUT,
sizeof(timeOut), &timeOut);
//--------------------------------------------------------------

Funkcja WinUsb_SetPowerPolicy()
BOOL __stdcall WinUsb_SetPowerPolicy(
IN WINUSB_INTERFACE_HANDLE InterfaceHandle,
Rozdział 7. ♦ Biblioteki WinUSB oraz LibUSB 289

IN ULONG PolicyType,
IN ULONG ValueLength,
IN PVOID Value
);

Funkcja ustala parametry opisujące sposób zasilania urządzenia. Znaczenie poszcze-


gólnych parametrów jest identyczne jak w przypadku WinUsb_GetPowerPolicy().

Funkcja WinUsb_WritePipe()
BOOL __stdcall WinUsb_WritePipe(
IN WINUSB_INTERFACE_HANDLE InterfaceHandle,
IN UCHAR PipeID,
IN PUCHAR Buffer,
IN ULONG BufferLength,
OUT PULONG LengthTransferred,
IN LPOVERLAPPED Overlapped
);

Funkcja wysyła (zapisuje) dane do bufora punktu końcowego identyfikowanego przez


PipeID. Parametr ten odpowiada zawartości pola bEndpointAddress struktury opisują-
cej deskryptor punktu końcowego (patrz rozdział 3., tabela 3.12). Wskaźnik Buffer
wskazuje bufor danych wyjściowych. Parametr BufferLength określa maksymalną
liczbę bajtów umieszczanych w buforze wyjściowym. Wskaźnik LengthTransferred
wskazuje liczbę aktualnie wysłanych bajtów. Liczba faktycznie wysłanych bajtów może
być mniejsza niż deklarowany rozmiar bufora; może to oznaczać zakończenie transmisji
danych (patrz opis znacznika SHORT_PACKET_TERMINATE). Liczbę faktycznie przetrans-
mitowanych bajtów funkcja umieszcza w zmiennej wskazywanej przez LengthTrans-
ferred, stanowiącej przedostatni parametr. W ten sposób działa mechanizm ochrony
dla wysyłanych danych. Wskaźnik Overlapped wskazuje strukturę OVERLAPPED.
//--------------------------------------------------------------
UCHAR buffer[] = "xxxxxx";
ULONG bufferLength = strlen(buffer);
ULONG lengthTransferred = 0;
WinUsb_WritePipe(interfaceHandle,
USB_ENDPOINT_DIRECTION_OUT(pipeInformation->PipeId),
buffer, bufferLength, &lengthTransferred, 0);
//--------------------------------------------------------------

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

Prawidłowo zainstalowany sprzęt jest widoczny w menedżerze urządzeń jako urządze-


nie libusb, tak jak pokazano na rysunku 7.13.
Rozdział 7. ♦ Biblioteki WinUSB oraz LibUSB 291

Rysunek 7.13.
Umiejscowienie
urządzenia libusb
w menedżerze
urządzeń

Poprawnie zainstalowany sterownik libusb0.sys tworzy w trybie jądra operacyjny obiekt


urządzenia FDO, tak jak obrazowo pokazano na rysunku 7.14. Biblioteka libusb0.dll
implementuje zestaw funkcji eksportowych, umożliwiający programiście bezpośred-
nie wykonywanie operacji wejścia-wyjścia, bez konieczności jawnego posługiwania
się funkcjami oraz rozkazami opisanymi w rozdziale 6. Prezentowane w dalszej części
podrozdziału funkcje opakowują rozkazy używane na poziomie jądra systemu i prze-
kazują je bezpośrednio do sterownika libusb0.sys w celu dalszego przetwarzania.

Rysunek 7.14.
Model warstwowy
sterowników
urządzenia libusb

Definicje wszystkich struktur oraz funkcji biblioteki znajdują się w pliku nagłówko-
wym 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.

Podczas kompilacji programów korzystających z LibUSB należy pamiętać o statycz-


nym łączeniu ich z biblioteką importową libusb.lib, która jest dostępna w katalogu in-
stalacyjnym \lib dla różnych wersji kompilatorów C++.

Struktury danych używane przez bibliotekę LibUSB, które opisują urządzenia, interfej-
sy, punkty końcowe i konfiguracje oraz odpowiadające im deskryptory, są w większo-
ści identyczne z przedstawionymi w rozdziale 3. W katalogu instalacyjnym \bin\x86\
292 USB. Praktyczne programowanie z Windows API w C++

znajduje się aplikacja testlibusb-win.exe, za pomocą której można odczytać zawartość


poszczególnych deskryptorów urządzenia, tak jak pokazano na rysunku 7.15.

Rysunek 7.15.
Odczyt zawartości
deskryptorów
urządzenia libusb

W dalszej części podrozdziału zamieszczono wykaz funkcji jądra biblioteki, funkcji


wykorzystywanych do zarządzania urządzeniami USB oraz funkcji kontrolujących
transfer danych w systemie.

Funkcje jądra biblioteki


Funkcja usb_init()
void usb_init(void);

Inicjalizuje wszystkie wewnętrzne struktury biblioteki LibUSB i powinna być wywo-


ływana jako pierwsza w programie.

Funkcja usb_find_busses()
int usb_find_busses(void);

Zwraca liczbę portów magistrali USB dostępnych w systemie i powinna być wywo-
ływana każdorazowo po funkcji usb_init().
Rozdział 7. ♦ Biblioteki WinUSB oraz LibUSB 293

Funkcja usb_find_devices()
int usb_find_devices(void);

Zwraca liczbę urządzeń USB, które są aktualnie przyłączone do portów magistrali,


określonych za pomocą funkcji usb_find_busses().

Funkcja usb_get_busses()
struct usb_bus *usb_get_busses(void);

Zwraca wskaźnik do struktury.


struct usb_bus
{
struct usb_bus *next, *prev;
char dirname[LIBUSB_PATH_MAX];
struct usb_device *devices;
unsigned long location;
struct usb_device *root_dev;
};

Funkcja usb_set_debug()
void usb_set_debug(int level);

Umożliwia ustalenie poziomu debugowania; parametrowi level można przypisać na-


stępujące wartości: 0 — LOG_OFF, 1 — LOG_ERROR, 2 — LOG_WARNING, 3 — LOG_INFO lub
4 — LOG_DEBUG.

Funkcje do zarządzania urządzeniem libusb


Funkcja usb_open()
usb_dev_handle *usb_open(struct usb_device *dev);

Otwiera urządzenie libusb do transmisji (otwiera połączenie z urządzeniem) i zwraca


jego identyfikator. Argumentem funkcji jest wskaźnik do struktury:
struct usb_device
{
struct usb_device *next, *prev;
char filename[LIBUSB_PATH_MAX];
struct usb_bus *bus;
struct usb_device_descriptor descriptor;
struct usb_config_descriptor *config;
void *dev; //Darwin support
unsigned char devnum;
unsigned char num_children;
struct usb_device **children;
};
294 USB. Praktyczne programowanie z Windows API w C++

Funkcja usb_close()
int usb_close(usb_dev_handle *dev);

Zamyka do transmisji urządzenie (zamyka połączenie z urządzeniem), którego identy-


fikator został uprzednio przydzielony za pomocą funkcji usb_open().

Na listingu 7.3 zaprezentowano przykład posługiwania się funkcjami usb_init(), usb_


find_busses(), usb_find_devices(), usb_get_busses(), usb_open() oraz usb_close()
w celu odczytania zawartości deskryptora urządzenia libusb.

Listing 7.3. Odczytanie fragmentu deskryptora urządzenia zainstalowanego jako urządzenie libusb
#include "lusb0_usb.h"
#include <iostream>
using namespace std;

#pragma comment(lib, "libusb.lib")

#define VID 0x22ba


#define PID 0x0108
//---------------------------------------------------------
int getDevice(struct usb_device *usbDev)
{
usb_dev_handle *devHandle = NULL;
char buffer[256];
int success;

devHandle = usb_open(usbDev);
if (devHandle) {
if (usbDev->descriptor.iManufacturer) {
success = usb_get_string_simple(devHandle,
usbDev->descriptor.iManufacturer,
buffer, sizeof(buffer));
if (success > 0) {
printf("Manufacturer: %s\n",buffer);
}
}
if (usbDev->descriptor.iProduct) {
success = usb_get_string_simple(devHandle,
usbDev->descriptor.iProduct,
buffer, sizeof(buffer));
if (success > 0) {
printf("Product %s\n",buffer);
}
}
printf("Descriptor Length = %u\n",usbDev->descriptor.bLength);
printf("NumConfigurations = %d\n",usbDev->descriptor.bNumConfigurations);
printf("MaxPacketSize0 = %d\n",usbDev->descriptor.bMaxPacketSize0);
printf("Descriptor Type = %u\n",usbDev->descriptor.bDescriptorType);
printf("SerialNumber = %d\n",usbDev->descriptor.iSerialNumber);
}
if (devHandle)
usb_close(devHandle);
return 0;
}
Rozdział 7. ♦ Biblioteki WinUSB oraz LibUSB 295

//---------------------------------------------------------
int main()
{
usb_bus *bus;

usb_init();
usb_find_busses();
usb_find_devices();

for (bus = usb_get_busses(); bus; bus = bus->next) {


struct usb_device *device;
for (device = bus->devices; device; device = device->next) {
if ((device->descriptor.idProduct == PID) ||
(device->descriptor.idVendor == VID)){
printf("Device:\nPID %x\nVID %x\n",device->descriptor.idProduct,
device->descriptor.idVendor);
getDevice(device);
}
}
}
system("PAUSE");
return 0;
}
//---------------------------------------------------------

Funkcja usb_set_configuration()
int usb_set_configuration(usb_dev_handle *dev, int configuration);

Przypisuje urządzeniu identyfikowanemu przez wskaźnik dev numer konfiguracji okre-


ślony parametrem configuration. Numer żądanej konfiguracji jest przechowywany
w polu bConfigurationValue struktury usb_config_descriptor opisującej deskryptor
konfiguracji urządzenia libusb. Prawidłowo wykonana funkcja zwraca 0, a w przypad-
ku wystąpienia błędu — wartość ujemną.

Funkcja usb_set_altinterface()
int usb_set_altinterface(usb_dev_handle *dev, int alternate);

Poprzez parametr 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);

Resetuje ustawienia punktu końcowego o adresie identyfikowanym przez parametr


ep. Adres punktu końcowego jest przechowywany w polu bEndpointAddress struktury
296 USB. Praktyczne programowanie z Windows API w C++

opisującej deskryptor punktu końcowego. Prawidłowo wykonana funkcja zwraca 0,


a w przypadku wystąpienia błędu — wartość ujemną.

Funkcja usb_reset()
int usb_reset(usb_dev_handle *dev);

Resetuje urządzenie poprzez wysłanie komunikatu RESET do portu, do którego jest


przyłączone urządzenie identyfikowane przez wskaźnik dev. Prawidłowo wykonana
funkcja zwraca wartość 0. Po wywołaniu funkcji usb_reset() należy powtórnie prze-
prowadzić proces enumeracji urządzeń oraz wywołać funkcję usb_open().

Funkcja usb_claim_interface()
int usb_claim_interface(usb_dev_handle *dev, int interface);

Przypisuje żądany numer interfejsu interface do urządzenia. Numer dostępnego inter-


fejsu jest przechowywany w polu bInterfaceNumber struktury usb_interface_descriptor
opisującej deskryptor interfejsu urządzenia libusb. Funkcja powinna być używana przed
wywołaniami innych funkcji uzyskujących dostęp do deskryptora interfejsu, np. usb_
set_altinterface() lub usb_bulk_write(). Prawidłowo wykonana funkcja zwraca
wartość 0. Możliwe kody błędów to: EBUSY — interfejs niedostępny dla urządzenia,
ENOMEM — niewystarczająca ilość pamięci do przeprowadzenia operacji przypisania
interfejsu do urządzenia.

Funkcja usb_release_interface()
int usb_release_interface(usb_dev_handle *dev, int interface);

Zwalnia interfejs uprzednio przydzielony za pomocą usb_claim_interface(). Prawidło-


wo wykonana funkcja zwraca 0, a w przypadku wystąpienia błędu — wartość ujemną.

Funkcja usb_control_msg()
int usb_control_msg(usb_dev_handle *dev, int requesttype, int request, int value,
int index, char *bytes, int size, int timeout);

Wysyła poprzez domyślny (zerowy) potok kontrolny dane do urządzenia identyfiko-


wanego wskaźnikiem dev. Parametr requesttype identyfikuje typ rozkazu, parametr
request jest kodem rozkazu, wartość parametru value zależy od typu rozkazu, a war-
tość parametru index — od typu adresata rozkazu (patrz rozdział 6., tabele 6.4 i 6.5
oraz tabela 7.3 w tym rozdziale).

Standardowe kody rozkazów LibUSB to:


USB_REQ_GET_STATUS 0x00
USB_REQ_CLEAR_FEATURE 0x01
//zarezerwowane 0x02
USB_REQ_SET_FEATURE 0x03
//zarezerwowane 0x04
USB_REQ_SET_ADDRESS 0x05
Rozdział 7. ♦ Biblioteki WinUSB oraz LibUSB 297

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

Standardowe typy żądań LibUSB to:


USB_TYPE_STANDARD (0x00 << 5)
USB_TYPE_CLASS (0x01 << 5)
USB_TYPE_VENDOR (0x02 << 5)
USB_TYPE_RESERVED (0x03 << 5)

Standardowe typy adresatów LibUSB to:


USB_RECIP_DEVICE 0x00
USB_RECIP_INTERFACE 0x01
USB_RECIP_ENDPOINT 0x02
USB_RECIP_OTHER 0x03

Kierunek transferu danych jest określony jako:


USB_ENDPOINT_IN 0x80 //10000000b (patrz rozdział 3., tabela 3.12)
USB_ENDPOINT_OUT 0x00 //00000000b

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);

Umieszcza w buforze wskazywanym przez buf deskryptor łańcuchowy urządzenia


wskazywanego przez dev. Parametr index określa indeks deskryptora łańcuchowego
(patrz rozdział 6., listing 6.17), a parametr langid jest identyfikatorem języka użytego
do opisu deskryptora [17]. Prawidłowo wykonana funkcja zwraca liczbę odczytanych
bajtów, a w przypadku niepowodzenia — wartość ujemną.

Poniżej zamieszczono przykłady odczytu wybranego indeksu deskryptora łańcucho-


wego urządzenia za pomocą funkcji usb_control_msg() oraz usb_get_string().
//-------------------------------------------------------
typedef struct _LIB_USB_DESCRIPTPR_REQUEST { //samodzielnie zdefiniowana
//struktura
struct {
int requesttype;
int request;
int value;
int index;
} setupPacket;
char bytes[255]; //lub Data
int size;
int timeout;
} LIB_USB_DESCRIPTPR_REQUEST;

LIB_USB_DESCRIPTPR_REQUEST descRequest;
descRequest.setupPacket.requesttype = USB_TYPE_STANDARD |
USB_RECIP_DEVICE |
USB_ENDPOINT_IN;
descRequest.setupPacket.request = USB_REQ_GET_DESCRIPTOR;
descRequest.setupPacket.value = (USB_DT_STRING << 8) | 1;//indeks deskryptora = 1
descRequest.setupPacket.index = 0x0409;//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);

Odczytuje deskryptor łańcuchowy urządzenia, używając językowych ustawień domyśl-


nych, i konwertuje go na łańcuch ASCII.

Funkcja usb_get_descriptor()
int usb_get_descriptor(usb_dev_handle *dev, unsigned char type,
unsigned char index, void *buf, int size);

Umieszcza w buforze wskazywanym przez buf deskryptor urządzenia określony przez


parametry type oraz index. Typy deskryptorów urządzeń libusb są definiowane w na-
stępujący sposób:
USB_DT_DEVICE 0x01
USB_DT_CONFIG 0x02
USB_DT_STRING 0x03
USB_DT_INTERFACE 0x04
USB_DT_ENDPOINT 0x05
USB_DT_HID 0x21
USB_DT_REPORT 0x22
USB_DT_PHYSICAL 0x23
USB_DT_HUB 0x29

Parametr size określa rozmiar deskryptora i w bibliotece LibUSB jest definiowany


następująco:
USB_DT_DEVICE_SIZE 18
USB_DT_CONFIG_SIZE 9
USB_DT_INTERFACE_SIZE 9
USB_DT_ENDPOINT_SIZE 7
USB_DT_ENDPOINT_AUDIO_SIZE 9
USB_DT_HUB_NONVAR_SIZE 7

Zawartość deskryptora jest przesyłana domyślnym potokiem kontrolnym. Prawidłowo


wykonana funkcja zwraca liczbę odczytanych bajtów, a w przypadku niepowodzenia
— wartość ujemną. Poniżej zamieszczono przykład odczytania fragmentu deskryptora
urządzenia.
//-------------------------------------------------------
nBytes = usb_get_descriptor(devHandle, USB_DT_DEVICE, 0,
&usbDev->descriptor,
USB_DT_DEVICE_SIZE);
printf("\nBytes = %d\n", nBytes);
printf("\nbMaxPacketSize0 = %d\n", usbDev->descriptor.bMaxPacketSize0);
printf("\nidVendor = %x\n", usbDev->descriptor.idVendor);
printf("\nidProduct = %x\n", usbDev->descriptor.idProduct);
//-------------------------------------------------------

Funkcja usb_get_descriptor_by_endpoint()
int usb_get_descriptor_by_endpoint(usb_dev_handle *dev, int ep,
unsigned char type, unsigned char index,
void *buf, int size);
300 USB. Praktyczne programowanie z Windows API w C++

Umieszcza w buforze wskazywanym przez buf zawartość deskryptora urządzenia.


Postać deskryptora jest określona parametrami type oraz index. Zawartość deskrypto-
ra jest przesyłana potokiem kontrolnym identyfikowanym przez parametr ep (patrz
opis pola bmAttributes w tabeli 3.12). Prawidłowo wykonana funkcja zwraca liczbę
odczytanych bajtów, a w przypadku niepowodzenia — wartość ujemną. Poniżej za-
mieszczono przykład odczytania fragmentu deskryptora urządzenia poprzez domyślny
potok kontrolny.
//-------------------------------------------------------
nBytes = usb_get_descriptor_by_endpoint(devHandle, USB_ENDPOINT_TYPE_CONTROL,
USB_DT_DEVICE, 0,
&usbDev->descriptor,
USB_DT_DEVICE_SIZE);
printf("\nBytes = %d\n", nBytes);
printf("\nbNumConfigurations = %u\n", usbDev->descriptor.bNumConfigurations);
//-------------------------------------------------------

Funkcje realizujące transfer masowy


Funkcja usb_bulk_write()
int usb_bulk_write(usb_dev_handle *dev, int ep, char *bytes, int size, int timeout);

Realizuje masowy transfer danych wskazywanych przez wskaźnik bytes do punktu


końcowego o adresie identyfikowanym przez parametr ep. Adres punktu końcowego
jest przechowywany w polu bEndpointAddress struktury opisującej deskryptor punktu
końcowego.

Adresy oraz typy punktów końcowych można określić, korzystając z makrodefinicji:


USB_ENDPOINT_ADDRESS_MASK 0x0f //bEndpointAddress, patrz tabela 3.12
USB_ENDPOINT_DIR_MASK 0x80
USB_ENDPOINT_TYPE_MASK 0x03 //bmAttributes, patrz tabela 3.12
USB_ENDPOINT_TYPE_CONTROL 0
USB_ENDPOINT_TYPE_ISOCHRONOUS 1
USB_ENDPOINT_TYPE_BULK 2
USB_ENDPOINT_TYPE_INTERRUPT 3

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ą.

Funkcje realizujące transfer przerwaniowy


Funkcja usb_interrupt_write()
int usb_interrupt_write(usb_dev_handle *dev, int ep, char *bytes, int size,
int timeout);

Realizuje transfer przerwaniowy danych wskazywanych przez wskaźnik bytes do


punktu końcowego o adresie identyfikowanym przez parametr ep. Parametr size jest
rozmiarem transmitowanych danych, zaś timeout identyfikuje czas przeterminowania
operacji zapisu danych do bufora punktu końcowego ep. Prawidłowo wykonana funkcja
zwraca liczbę wysłanych bajtów, a w przypadku niepowodzenia — wartość ujemną.

Funkcja usb_interrupt_read()
int usb_interrupt_read(usb_dev_handle *dev, int ep, char *bytes, int size,
int timeout);

Odczytuje dane transmitowane potokiem przerwaniowym, których źródłem jest punkt


końcowy ep urządzenia identyfikowanego przez wskaźnik dev. Odebrane dane są umiesz-
czane w buforze wskazywanym przez wskaźnik bytes. Prawidłowo wykonana funkcja
zwraca liczbę odebranych bajtów, a w przypadku niepowodzenia — wartość ujemną.

Funkcje asynchroniczne
Funkcja usb_isochronous_setup_async()
int usb_isochronous_setup_async(usb_dev_handle *dev, void **context,
unsigned char ep, int pktsize);

Kieruje żądanie alokowania bufora danych o rozmiarze pktsize do izochronicznego


punktu końcowego o adresie ep urządzenia wskazywanego przez dev. Rozmiar bufo-
ra nie może być większy niż wartość zapisana w polu wMaxPacketSize struktury usb_
endpoint_descriptor opisującej deskryptor punktu końcowego urządzenia libusb. Ro-
dzaj żądania jest przechowywany pod postacią wskaźnika context odgrywającego rolę
nazwy wyróżniającej dla grupy logicznie powiązanych, następujących po sobie ope-
racji kierowanych do określonego punktu końcowego. Po zakończeniu wykonywania
302 USB. Praktyczne programowanie z Windows API w C++

operacji kierowanych do wyróżnionego punktu końcowego wskaźnik context powi-


nien zostać zwolniony. Prawidłowo wykonana funkcja zwraca 0, a w przypadku nie-
powodzenia — wartość ujemną.

Funkcja usb_bulk_setup_async()
int usb_bulk_setup_async(usb_dev_handle *dev, void **context, unsigned char ep);

Kieruje żądanie transmisji potokiem masowym do punktu końcowego o adresie ep


urządzenia wskazywanego przez dev. Rodzaj żądania jest przechowywany pod posta-
cią wskaźnika context. Prawidłowo wykonana funkcja zwraca 0, a w przypadku nie-
powodzenia — wartość ujemną.

Funkcja usb_interrupt_setup_async()
int usb_interrupt_setup_async(usb_dev_handle *dev, void **context,
unsigned char ep);

Kieruje żądanie transmisji potokiem przerwaniowym do punktu końcowego o adresie


ep urządzenia wskazywanego przez dev. Rodzaj żądania jest przechowywany pod po-
stacią wskaźnika context. Prawidłowo wykonana funkcja zwraca 0, a w przypadku
niepowodzenia — wartość ujemną.

Funkcja usb_submit_async()
int usb_submit_async(void *context, char *bytes, int size);

W ramach uprzednio określonego kontekstu wysyła komunikat z bufora wskazywane-


go przez wskaźnik bytes. Rozmiar bufora określa parametr size. W zależności od ty-
pu punktu końcowego potoku określonego w ramach kontekstu zdefiniowanego funk-
cjami usb_xxx_setup_async(), dane będą zapisywane do (EP OUT) lub czytane z (EP
IN) wybranego punktu końcowego. Prawidłowo wykonana funkcja zwraca 0, a w przy-
padku niepowodzenia — wartość ujemną.

Funkcja usb_reap_async()
int usb_reap_async(void *context, int timeout);

Po upływie czasu przeterminowania timeout (wyrażonego w milisekundach) przery-


wa operacje wykonywane w ramach określonego kontekstu. Prawidłowo wykonana
funkcja zwraca liczbę wysłanych lub odebranych bajtów.

Funkcja usb_reap_async_nocancel()
int usb_reap_async_nocancel(void *context, int timeout);

Anuluje rozkaz przerwania operacji wykonywanych w ramach kontekstu po upływie


czasu przeterminowania.
Rozdział 7. ♦ Biblioteki WinUSB oraz LibUSB 303

Funkcja usb_cancel_async()
int usb_cancel_async(void *context);

Bezwarunkowo wstrzymuje wykonywanie operacji transmisji danych w ramach okre-


ślonego kontekstu.

Funkcja usb_free_async()
int usb_free_async(void **context);

Zwalnia wskaźnik context, co oznacza zwolnienie pamięci przydzielonej dla kontek-


stu określonego funkcjami usb_xxx_setup_async().

Na listingu 7.4 zaprezentowano przykład zdefiniowania kontekstu dla operacji cyklicz-


nego odczytu danych z określonego punktu końcowego oraz posługiwania się wybra-
nymi funkcjami biblioteki LibUSB, które są wywoływane w sposób asynchroniczny
w celu odczytania zawartości bufora wyjściowego urządzenia libusb, komunikującego się
z komputerem za pośrednictwem potoku przerwaniowego. Na rysunku 7.16 zaprezen-
towano model logiczny urządzenia, którego działanie kontroluje program z listingu 7.4.
Wynik działania programu jest podobny do pokazanego w rozdziale 6. na rysunku 6.3.

Rysunek 7.16.
Model logiczny
urządzenia, którego
działanie kontroluje
program z listingu 7.4

Listing 7.4. Cykliczny odczyt danych napływających potokiem przerwaniowym z punktu końcowego
urządzenia libusb
#include "lusb0_usb.h"
#include <iostream>
using namespace std;

#pragma comment(lib, "libusb.lib")

#define VID 0x22ba


#define PID 0x0108
#define INTERFACE 0
#define CONFIGURATION 1
#define BUFFER_SIZE 8
#define EP1_IN 0x81

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++

Rysunek 8.1. Statyczny diagram opisujący elementy składowe klasy TUSBDevice

(na bazie których w przyszłości powstaną obiekty), należy się kierować zasadą, zgod-
nie z którą rodzaje odpowiedzialności obiektów za własne działania powinny być ści-
śle określone.

Dlatego też jako przykłady odpowiedzialności obiektu reprezentującego urządzenia


USB, które są dostępne w systemie, można podać:
 dokonanie — na podstawie identyfikatorów GUID — identyfikacji i selekcji
dołączonych urządzeń klasy HID,
 wybór konkretnego przyrządu, na przykład z wykorzystaniem jego
identyfikatora VID,
 uzyskanie ścieżki dostępu do interfejsu urządzenia,
 otwarcie portu USB do transmisji danych (uzyskanie dostępu do sterownika
urządzenia),
 zaimplementowanie reguł odczytu danych z bufora wejściowego.

Użytkownik (klient) jest reprezentowany przez funkcję main() (program główny).


W omawianym przypadku do zakresów jego odpowiedzialności należy:
 stworzenie obiektu na bazie klasy TUSBDevice,
 w odpowiedniej kolejności wywołanie funkcji składowych (metod) tego obiektu,
 na podstawie samodzielnie odczytanych właściwości urządzenia zaalokowanie
pamięci i ustalenie rozmiaru odpowiednich buforów danych,
 prezentacja odczytanych raportów wejściowych,
 zwolnienie zaalokowanych przez program zasobów.
Rozdział 8. ♦ Programowanie obiektowe transmisji USB 309

Na listingu 8.1 zaprezentowano definicje odpowiadające statycznemu diagramowi klas


z rysunku 8.1. Klasa TUSBDevice agreguje struktury HIDD_ATTRIBUTES, SP_DEVICE_IN-
TERFACE_DETAIL_DATA, SP_DEVICE_INTERFACE_DATA oraz GUID. Z kolei klasa TUSBDevice
oraz struktury HIDP_CAPS i HIDP_PREPARSED_DATA są deklarowane i używane w funkcji
main(), dlatego też pozostają w relacjach zależności z programem klienta reprezento-
wanym przez tę funkcję.

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"

using namespace std;


//--------------------------------------------------------------
TUSBDevice::TUSBDevice()
{
memberIndex = 0;
deviceInterfaceDetailData = NULL;
hHidLib = NULL;
hHidLib = LoadLibrary("C:\\Windows\\SySWOW64\\HID.DLL");
if (!hHidLib)
Rozdział 8. ♦ Programowanie obiektowe transmisji USB 311

displayError("Błąd dołączenia biblioteki HID.DLL.");

(FARPROC&) HidD_GetHidGuid=GetProcAddress(hHidLib, "HidD_GetHidGuid");


(FARPROC&) HidD_GetAttributes=GetProcAddress(hHidLib, "HidD_GetAttributes");

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);

SetupDiEnumDeviceInterfaces(deviceInfoSet, NULL, &classGuid,


memberIndex, &deviceInterfaceData);
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,
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;

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);

TUSBDevice *usbDevice = new TUSBDevice();

(FARPROC&) HidP_GetCaps=GetProcAddress(hHidLib, "HidP_GetCaps");


(FARPROC&) HidD_GetPreparsedData=GetProcAddress(hHidLib,
"HidD_GetPreparsedData");
(FARPROC&) HidD_FreePreparsedData=GetProcAddress(hHidLib,
"HidD_FreePreparsedData");

if (!HidP_GetCaps || !HidD_GetPreparsedData || !HidD_FreePreparsedData){


FreeLibrary(hLib);
usbDevice->displayError("Nie znaleziono żadnych funkcji" \
" eksportowych.\n");
}

//Synchroniczne operacje odczytu


usbDevice->setUshortVid(8890);
hidDeviceObject = usbDevice->synchOpenUSBDevice();
if(HidD_GetPreparsedData(hidDeviceObject, &preparsedData)){
HidP_GetCaps(preparsedData, &capabilities);
inputReportBuffer = new BYTE[capabilities.InputReportByteLength];

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

W klasie TUSBDevice jest zadeklarowany statyczny atrybut instance o dostępie prywat-


nym, który będzie stanowić odwołanie do egzemplarza klasy. Początkowo odwołanie
to powinno być zainicjowane pustym wskaźnikiem (NULL). Następnie należy zadeklaro-
wać statyczną metodę getInstance() o dostępie publicznym, która tworzy egzemplarz
klasy, w przypadku gdy odwołanie instance = NULL, a w dalszej kolejności odpowied-
nio inicjuje instance i zwraca jego wartość. Konstruktor klasy jest zadeklarowany
w sekcji prywatnej, aby zapobiec możliwości wywołania go bezpośrednio przez klienta
(z poziomu programu głównego).

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
//--------------------------------------------------------------

Listing 8.4. Kod modułu usb_R8_2.cpp z implementacją funkcji składowych klasy


#include <windows>
#pragma option push -a1
#include <setupapi>
#pragma option pop
#include <iostream>
#include "usb_R8_2.h"

using namespace std;


//--------------------------------------------------------------
TUSBDevice* TUSBDevice::getInstance(){
if (TUSBDevice::instance == NULL) {
instance = new TUSBDevice();
}
return instance;
}
//--------------------------------------------------------------
TUSBDevice::TUSBDevice()
{
memberIndex = 0;
Rozdział 8. ♦ Programowanie obiektowe transmisji USB 317

hHidLib = NULL;
deviceInterfaceDetailData = NULL;
hHidLib = LoadLibrary("C:\\Windows\\System32\\HID.DLL");
if (!hHidLib)
displayError("Błąd dołączenia biblioteki HID.DLL.");

(FARPROC&) HidD_GetHidGuid=GetProcAddress(hHidLib, "HidD_GetHidGuid");

if (!HidD_GetHidGuid /*|| !HidD_GetAttributes*/){


FreeLibrary(hHidLib);
displayError("Nie znaleziono żadnych funkcji eksportowych.\n");
}
}
//---------------------------------------------------------------
TUSBDevice::~TUSBDevice()
{
if(hHidLib != NULL)
FreeLibrary(hHidLib);
delete TUSBDevice::instance;
}
//--------------------------------------------------------------
void TUSBDevice::displayError(const char* msg){
cout << msg << endl;
system("PAUSE");
exit(0);
};
//--------------------------------------------------------------
void TUSBDevice::setCharVid(const char* vid)
{
chVid = 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);

SetupDiEnumDeviceInterfaces(deviceInfoSet, NULL, &classGuid,


memberIndex, &deviceInterfaceData);
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,
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];

TUSBDevice *usbDevice = TUSBDevice::getInstance();


//Asynchroniczne operacje odczytu
usbDevice->setCharVid("22ba");
hidDeviceObject = usbDevice->asynchOpenUSBDevice();
while(true) {
usbDevice->asynchReadUSBReport(hidDeviceObject, inputReportBuffer,
sizeof(inputReportBuffer));
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){
break;
}
}//koniec while
CloseHandle(hidDeviceObject);
Rozdział 8. ♦ Programowanie obiektowe transmisji USB 319

system("PAUSE");
return 0;
}
//--------------------------------------------------------------

Rysunek 8.4.
Aplikacja
proj_USB_R8_2
w trakcie działania

Testując powyższy program, łatwo zauważymy, że singleton jest obiektem bezstano-


wym, tzn. sposób działania metody statycznej getInstance() nie zależy od stanu, w ja-
kim znajduje się program: klient otrzymuje egzemplarz klasy na żądanie niezależnie
od tego, czy został on utworzony wcześniej, czy nie. Singleton pozwala również stoso-
wać dziedziczenie w celu zmiany tworzonej przez siebie klasy i zwracać egzemplarze
podklas. Dołączenie podklasy do wzorca nie wymaga modyfikacji po stronie klienta.

Interfejsy
W dotychczas omawianych przykładach klient bezpośrednio tworzył obiekt interesującej
go klasy. Elementy składowe klasy TUSBDevice zawierały przykładowe implementacje
dziedziny problemu. Prezentacja danych 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++

Na rysunku 8.5 pokazano uogólniony model konstruowania interfejsów. Listing 8.5


zawiera implementację diagramu 8.5.

Rysunek 8.5. Model realizacji interfejsów przez klasę TUSBDevice

Podstawowy związek między klasą a interfejsem nazywamy realizacją (chociaż klasy


i interfejsy mogą pozostawać również w innych relacjach). Klasa realizuje każdą ope-
rację interfejsu poprzez implementację operacji o tej samej nazwie oraz tych samych
argumentach i tym samym sposobie wywoływania. C++ automatycznie dopasowuje
operacje klasy do operacji danego interfejsu. Uzyskanie dostępu do elementów skła-
dowych obiektu stworzonego na bazie danej klasy drogą inną niż poprzez funkcje in-
terfejsu nie jest możliwe. Na rysunku 8.5 pokazano diagram odpowiadający listingowi
8.5, w którym klasa TUSBDevice realizuje dwa przykładowe interfejsy: ISynchronous
i IAsynchronous. Są one odpowiedzialne za transmisję danych przez obiekt klasy TUSB-
Device odpowiednio w wariantach synchronicznym lub asynchronicznym. Na listingu
8.6 i rysunku 8.6 zaprezentowano kod głównego modułu i wynik działania omawia-
nej aplikacji.

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++

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);
};
//--------------------------------------------------------------
#endif
//--------------------------------------------------------------

Listing 8.6. Kod modułu usb_R8_3.cpp


#include <windows>
#pragma option push -a1
#include <setupapi>
#pragma option pop
#include <iostream>
#include "usb_R8_3.h"
using namespace std;
//--------------------------------------------------------------
TUSBDevice::TUSBDevice()
{
memberIndex = 0;
hHidLib = NULL;
deviceInterfaceDetailData = NULL;
hHidLib = LoadLibrary("C:\\Windows\\System32\\HID.DLL");
if (!hHidLib)
displayError("Błąd dołączenia biblioteki HID.DLL.");

(FARPROC&) HidD_GetHidGuid=GetProcAddress(hHidLib, "HidD_GetHidGuid");


(FARPROC&) HidD_GetAttributes=GetProcAddress(hHidLib, "HidD_GetAttributes");

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);

SetupDiEnumDeviceInterfaces(deviceInfoSet, NULL, &classGuid,


memberIndex, &deviceInterfaceData);
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,
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];

//Synchroniczne operacje odczytu


ISynchronous *iSynchUsbDevice = new TUSBDevice();
iSynchUsbDevice->setUshortVid(8890);
hidDeviceObject = iSynchUsbDevice->synchOpenUSBDevice();
if (hidDeviceObject != INVALID_HANDLE_VALUE) {
while(true) {
iSynchUsbDevice->synchReadUSBReport(hidDeviceObject, inputReportBuffer,
sizeof(inputReportBuffer));
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){
break;
}
}//koniec while
}
/*
//Asynchroniczne operacje odczytu
IAsynchronous *iAsynchUsbDevice = new TUSBDevice();
iAsynchUsbDevice->setCharVid("22ba");
hidDeviceObject = iAsynchUsbDevice->asynchOpenUSBDevice();
if (hidDeviceObject != INVALID_HANDLE_VALUE) {
while(true) {
iAsynchUsbDevice->asynchReadUSBReport(hidDeviceObject, inputReportBuffer,
sizeof(inputReportBuffer));
printf("%d %d %d %d %d %d %d %d\n", inputReportBuffer[0],
inputReportBuffer[1], inputReportBuffer[2],
inputReportBuffer[3], inputReportBuffer[4],
inputReportBuffer[5], inputReportBuffer[6],
inputReportBuffer[7]);
if(inputReportBuffer[6]==64 || \
hidDeviceObject == INVALID_HANDLE_VALUE){
break;
}
}//koniec while
}
*/
326 USB. Praktyczne programowanie z Windows API w C++

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

Nowe interfejsy mogą być deklarowane poprzez dziedziczenie po istniejących inter-


fejsach (rysunek 8.7). Podobnie jak wszystkie klasy dziedziczą po TObject, wszystkie
interfejsy dziedziczą po IUnknown.

IUnknown jest odpowiednikiem interfejsu IInterface z modułu system.hpp. Podczas


tworzenia aplikacji niezależnych od platformy systemowej należy używać IInterface.
IUnknown jest zarezerwowany dla platformy Win32/64.

Zliczanie odwołań do interfejsu


Interfejs IUnknown deklaruje trzy metody: AddRef(), Release() i QueryInterface().
Pierwsze dwie zarządzają zliczaniem odwołań w czasie życia obiektu implementują-
cego interfejs. AddRef() zwiększa licznik odwołań do interfejsu, natomiast Release()
zmniejsza go. QueryInterface() kontaktuje się z innymi interfejsami, które może im-
plementować obiekt. Do użycia QueryInterface() niezbędny jest identyfikator inter-
fejsu. W celu wydobycia identyfikatora zawsze można podać nazwę interfejsu. C++
automatycznie konwertuje nazwę interfejsu na jego identyfikator. W celu utworzenia
początkowo niezainicjowanego obiektu używamy funkcji CreateInstance().
Rozdział 8. ♦ Programowanie obiektowe transmisji USB 327

Rysunek 8.7. Realizacja identyfikowanych interfejsów

Identyfikator interfejsu
Pełniejszą realizację interfejsu można uzyskać poprzez wydobycie jego identyfikatora.
Typ strukturowy GUID, zdefiniowany w pliku nagłówkowym mapiguid.h, przechowuje
globalnie unikatowy identyfikator GUID.
typedef struct _GUID
{
unsigned long Data1;
unsigned short Data2;
unsigned short Data3;
unsigned char Data4[8];
} GUID;

System operacyjny Windows, generując nowy identyfikator GUID, gwarantuje, że bę-


dzie on unikatowy w skali wszystkich identyfikatorów tego typu wygenerowanych na
całym świecie. C++ używa wartości GUID do identyfikowania interfejsów. Środowisko
328 USB. Praktyczne programowanie z Windows API w C++

IDE w systemie Windows generuje automatycznie nowy identyfikator GUID po naci-


śnięciu kombinacji klawiszy Ctrl+Shift+G; podobnie jest w przypadku Linuksa, na
przykład:
['{E7D1FDE1-8A61-11D9-8C68-00E07D843852}']

Poszczególne elementy identyfikatora należy wpisać w postaci szesnastkowej do po-


szczególnych pól struktury GUID, tak jak pokazano na listingu 8.7.

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
//--------------------------------------------------------------

Wywołania funkcji AddRef() i Release() pozwalają zarządzać czasem życia interfej-


sów. W celu użycia mechanizmu automatycznego zliczania odwołań należy zadekla-
rować wskaźnik do typu bazowego interfejsu. Po jego zainicjowaniu wywołujemy
metodę AddRef() z funkcją InterlockedIncrement(), zwiększającą licznik odwołań
Addend do interfejsu. Kiedy obiekt kończy swoje operacje, wywoływana jest funkcja
330 USB. Praktyczne programowanie z Windows API w C++

Release() z funkcją InterlockedDecrement(), zmniejszającą licznik odwołań do inter-


fejsu. Funkcję QueryInterface() można zaimplementować w dowolny sposób. W po-
wyższym przykładzie zastosowano rozwiązanie preferowane przez klasę TInterfa-
cedObject. Na listingu 8.8 zaprezentowano kod modułu z implementacją zarządzania
czasem życia interfejsów.

Funkcji InterlockedIncrement() i InterlockedDecrement(), zdefiniowanych w mo-


dule sysutils.hpp, należy używać tylko na platformie linuksowej. Aplikacje Windows
korzystają z analogicznych funkcji API zdefiniowanych w module windows.hpp.

Listing 8.8. Kod modułu usb_R8_4.cpp z implementacją zarządzania czasem życia interfejsów
#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.");

(FARPROC&) HidD_GetHidGuid=GetProcAddress(hHidLib, "HidD_GetHidGuid");


(FARPROC&) HidD_GetAttributes=GetProcAddress(hHidLib, "HidD_GetAttributes");

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

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);

SetupDiEnumDeviceInterfaces(deviceInfoSet, NULL, &classGuid,


memberIndex, &deviceInterfaceData);
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,
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();

//Synchroniczne operacje odczytu


334 USB. Praktyczne programowanie z Windows API w C++

ISynchronous *pISynchronous = NULL;


hr = pUnknown->QueryInterface(iidISynchronous, (void**)&pISynchronous);
if (SUCCEEDED(hr)) {
pISynchronous->setUshortVid(8890);
hidDeviceObject = pISynchronous->synchOpenUSBDevice();
while(true) {
pISynchronous->synchReadUSBReport(hidDeviceObject,
inputReportBuffer, sizeof(inputReportBuffer));
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){
break;
}
} //koniec while
pISynchronous->Release();
};
/*
//Asynchroniczne operacje odczytu
IAsynchronous *pIAynchronous = NULL;
hr = pUnknown->QueryInterface(iidIAsynchronous, (void**)&pIAynchronous);
if (SUCCEEDED(hr)) {
pIAynchronous->setCharVid("22ba");
hidDeviceObject = pIAynchronous->asynchOpenUSBDevice();
while(true) {
pIAynchronous->asynchReadUSBReport(hidDeviceObject,
inputReportBuffer, sizeof(inputReportBuffer));
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){
break;
}
} //koniec while
pIAynchronous->Release();
};
*/
CloseHandle(hidDeviceObject);
pUnknown->Release();
system("PAUSE");
return 0;
}
//--------------------------------------------------------------

Podczas testowania powyższego programu z łatwością zauważymy, że system opera-


cyjny lokalizuje interfejsy, biorąc pod uwagę ich identyfikatory, a nie nazwę. Aby
zatem interfejsy były rozpoznawane przez system, wystarczy, że będą mieć odmienne
identyfikatory.

Praktyczne wykorzystywanie tak skonstruowanych interfejsów jest czynnością nie-


zbyt skomplikowaną. Program klienta posiada funkcję main(), w której deklarowane
są wskaźniki do istniejących interfejsów. Metoda CreateInstance() służy do tworze-
Rozdział 8. ♦ Programowanie obiektowe transmisji USB 335

nia nowego komponentu USBDevice i pobierania z niego wskaźnika do interfejsu bazo-


wego IUnknown, tak jak pokazano na diagramie komponentów z rysunku 8.8.

Rysunek 8.8.
Diagram
komponentów
odpowiadający
przykładowi
z listingu 8.8

Kiedy programista potrzebuje dodać nową metodę do istniejącego interfejsu, np. IAsyn
´chronous, tworzy nową wersję interfejsu IAsynchronous_1, tak jak pokazano na rysunku
8.9. Nowo utworzony interfejs powinien być zaimplementowany z nowym identyfika-
torem, powiedzmy iidIAsynchronous_1. Po tej modyfikacji programista ma do dyspo-
zycji nową wersję komponentu modelującego urządzenie USBDevice. Uprzednio skon-
struowany program kliencki, który nie wie o nowej metodzie asynchWriteUSBReport()
interfejsu, może dalej używać starszej wersji komponentu. Nie ma potrzeby rekompi-
lacji starego klienta. Nowy program kliencki wie o nowej metodzie asynchWriteUSB
´Report(), więc może swobodnie używać nowej wersji komponentu, tak jak pokazano
na rysunku 8.10.

Rysunek 8.9. Dodawanie nowej metody do interfejsu


336 USB. Praktyczne programowanie z Windows API w C++

Rysunek 8.10. Dodawanie nowych funkcji do istniejących interfejsów

Podczas pracy z identyfikowanymi interfejsami programista powinien pamiętać, że


zawsze należy stworzyć nową wersję interfejsu, gdy modyfikacji podlega:
 liczba funkcji w interfejsie,
 kolejność funkcji w interfejsie,
 liczba parametrów w funkcji interfejsu,
 kolejność parametrów w funkcji interfejsu,
 typ parametrów w funkcji interfejsu,
 typ wartości zwracanej przez funkcję interfejsu.

W systemach o strukturze komponentowej komponenty reprezentują wiedzę o dzie-


dzinie problemu, zaś interfejsy — wiedzę o istnieniu komponentów.

Komponenty wizualne
Komponent jest wymienną częścią systemu implementującą co najmniej jedną klasę.

Jako przykład wykorzystania gotowych komponentów w aplikacji obsługującej inter-


fejs USB zostanie zaprezentowany działający komponent, którego zadaniem będzie
diagnozowanie dołączania urządzeń do magistrali USB lub ich odłączania. Podstawo-
wa wersja klasy komponentu USBDetect składa się z jednego pola (zmiennej), pięciu
funkcji składowych (metod) i dwóch własności.

Kluczowym elementem definicji klasy tworzącej komponent jest operacja device


´AttachDetach(), której zadaniem jest diagnozowanie zdarzeń polegających na sygnali-
zowaniu dołączania lub odłączania urządzeń zewnętrznych. Określenie definicji takiej
operacji pociąga za sobą konieczność posłużenia się wskaźnikiem do funkcji obsługi
zdarzeń:
typedef void __fastcall (__closure *TNotifyEvent)
(System::TObject *Sender);
Rozdział 8. ♦ Programowanie obiektowe transmisji USB 337

Należy również delegować własności USBAttach i USBDetach do odpowiednich funkcji


obsługi zdarzeń, tak jak pokazano na listingu 8.9 i odpowiadającym mu rysunku 8.11.

Rysunek 8.11.
Statyczny diagram
klas odpowiadający
przykładowi
z listingu 8.9

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>

static GUID GUID_DEVINTERFACE_USB_DEVICE =


{0xA5DCBF10, 0x6530, 0x11D2, {0x90, 0x1F, 0x00, 0xC0,
0x4F, 0xB9, 0x51, 0xED}};

//--------------------------------------------------------------
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++

Na listingu 8.10 zamieszczono kompletny kod modułu implementującego funkcje skła-


dowe komponentu USBDetect. Proces wykrywania dołączenia lub odłączenia urządzenia
odbywa się w ciele funkcji deviceAttachDetach(), wywoływanej na rzecz WindowProc(),
w momencie gdy system wykryje zmianę stanu urządzenia USB.

Listing 8.10. Kod modułu USBDetect.cpp implementującego elementy składowe klasy komponentu
//--------------------------------------------------------------
#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

if ((msg.WParam==DBT_DEVICEARRIVAL) || //dołączone lub


(msg.WParam==DBT_DEVICEREMOVECOMPLETE)) { //odłączone
hdr = (PDEV_BROADCAST_HDR)msg.LParam;
devType = hdr->dbch_devicetype;
if (devType==DBT_DEVTYP_DEVICEINTERFACE) {//urządzenie USB
if (msg.WParam==DBT_DEVICEARRIVAL) { //urządzenia przyłączone
if (FUSBAttach)
FUSBAttach(this);
}
else {
if (FUSBDetach)
FUSBDetach(this);
};
};
};
};
//--------------------------------------------------------------
bool __fastcall TUSBDetect::USBDeviceNotify()
{
void *result;
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;
result = RegisterDeviceNotification(FWindowHandle, &NotificationFilter,
DEVICE_NOTIFY_WINDOW_HANDLE);
if(!result)
return false;
return true;
}
//--------------------------------------------------------------

Na listingu 8.11 i rysunku 8.12 pokazano aplikację implementującą funkcje obsługi


zdarzeń komponentu USBDetect oraz wynik działania aplikacji w momencie wykrycia
nowo przyłączanego urządzenia USB.

Listing 8.11. Kod głównego modułu projektu usb_R8_5.cpp


//--------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop

#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.");
}
//--------------------------------------------------------------

Rysunek 8.12. Aplikacja projektu proj_USB_R8_5 w trakcie działania

Podsumowanie
Rozdział zawiera krótkie wprowadzenie do metod obiektowego projektowania i imple-
mentacji oprogramowania sterującego łączem USB. Dla osób pragnących 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

Na listingu 8.12 zamieszczono szkielet przykładowej implementacji fabryki z rysun-


ku 8.13.
342 USB. Praktyczne programowanie z Windows API w C++

Listing 8.12. Kod modułu usb_R8_C1.cpp


#include <iostream>

using namespace std;

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).

W klasie TSubject są zdefiniowane metody pozwalające na dołączanie i odłączanie


obserwatorów; każdy zainteresowany obiekt może się zarejestrować jako obserwator.
Metoda notify() służy do powiadamiania wszystkich zarejestrowanych obserwato-
rów poprzez odpowiednie wywołanie w pętli metody update() zdefiniowanej w inter-
fejsie IObserver. Metoda ta jest wykorzystywana do powiadamiania o zmianie stanu
obserwowanego obiektu. Na rysunku 8.14 pokazano statyczny diagram klas wzorca
obserwatora. Konkretnym obserwatorem jest klasa TResultReadReport realizująca in-
terfejs IObserver, a rolę konkretnego przedmiotu odgrywa klasa TUSBDevice dziedzi-
cząca po TSubject. Na listingu 8.13 zamieszczono szkielet przykładowej implementa-
cji wzorca obserwatora pokazanego na rysunku 8.14.
344 USB. Praktyczne programowanie z Windows API w C++

Rysunek 8.14.
Ogólna struktura
wzorca obserwatora
zastosowana
do obsługi
urządzenia USB

Listing 8.13. Kod modułu usb_R8_C2.cpp


#include <iostream>
#include <iterator>
#include <list>

using namespace std;

class TSubject;

class IObserver { //Obserwator


public:
virtual void update(TSubject* ) = 0;
virtual ~IObserver() {};
};
//--------------------------------------------------------------
class TSubject { //Przedmiot
public:
TSubject();
virtual void attach(IObserver*); //dołącza (rejestruje) obiekty
//klasy obserwatora
virtual void detach(IObserver*); //usuwa obiekty
//klasy obserwatora
virtual void notify(); //powiadamia o wystąpieniu zdarzenia
virtual void setChange(); //rejestruje zmianę stanu pojedynczego
//obiektu
private:
list<IObserver*> observers;//dwukierunkowa lista obiektów
//klasy obserwatora; list jest kontenerem
//dwukierunkowym

bool change; //reprezentuje stan pojedynczego obiektu


};
//--------------------------------------------------------------
void TSubject::setChange() {
change = true ;
}
//--------------------------------------------------------------
TSubject::TSubject(): change(false) {} //początkowo przedmiot
//jest w stanie nieaktywnym
//--------------------------------------------------------------
Rozdział 8. ♦ Programowanie obiektowe transmisji USB 345

void TSubject::attach (IObserver *o) {


observers.push_back(o); //dodaje obserwator na końcu kontenera
}
//--------------------------------------------------------------
void TSubject::detach (IObserver* o) {
observers.remove(o);
}
//--------------------------------------------------------------
void TSubject::notify () {
//powiadamia obserwator o zmianie stanu
if (change) {
list<IObserver*>::iterator i = observers.begin();
for ( ; i!= observers.end(); i++ ) {
(*i)->update(this);
}
change = false;
}
}
//--------------------------------------------------------------
class TUSBDevice: public TSubject {//konkretnym przedmiotem jest
//urządzenie USB
private:
int report;
public:
TUSBDevice();
void readReport(); //odczytuje raport
int getReport(); //zwraca wynik odczytu
};
//--------------------------------------------------------------
TUSBDevice::TUSBDevice() : report(0) {}
//--------------------------------------------------------------
void TUSBDevice::readReport() {
report++;
setChange();
}
//--------------------------------------------------------------
int TUSBDevice::getReport() {
return report; //urządzenie zwraca raport
}
//--------------------------------------------------------------
class TResultReadReport: public IObserver {//wynik odczytu jako
//konkretny obserwator
private:
void update(TSubject*);
};
//--------------------------------------------------------------
void TResultReadReport::update(TSubject *o) {
//polimorficzne rzutowanie czasu wykonania
TUSBDevice *m = dynamic_cast<TUSBDevice *>(o);
if (m) {
cout << "R = " << m->getReport();
}
}
//--------------------------------------------------------------
TUSBDevice *usbDevice;
IObserver *resultReadReport;
//--------------------------------------------------------------
346 USB. Praktyczne programowanie z Windows API w C++

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

W klasie TUSBDevice zdefiniowano operacje delegationToTAsynchReadWrite() oraz


delegationToTSynchReadWrite(). Ich wywołanie określa obiekty klas, do których obiekt
Rozdział 8. ♦ Programowanie obiektowe transmisji USB 347

klasy TUSBDevice będzie delegował dalsze wywołania. W efekcie TAsynchReadWrite


oraz TSynchReadWrite będą spełniać rolę superklas klasy TUSBDevice. Zrealizowanie
tego mechanizmu poprzez wspólny interfejs daje pewność, że wywoływane metody
zostały prawidłowo zaimplementowane w każdej z klas. Łańcuch kolejnych delegacji
poprzez wspólny interfejs może mieć dowolną długość. Wystarczy, aby obiekt klasy,
który ma być delegowany, miał interfejs w swojej ścieżce realizacji, tak jak pokazano
na rysunku 8.15 oraz listingu 8.14. Delegowanie operacji interfejsu jest możliwe dzięki
zagregowaniu interfejsu z klasą, w której są zaimplementowane operacje delegujące.
Dzięki temu operacje delegujące z klasy TUSBDevice mają dostęp do wskaźników wska-
zujących inne obiekty w systemie, które realizują wspólny interfejs.

Listing 8.14. Kod modułu usb_R8_C3.cpp


#include <iostream>
using namespace std;

class IUSBDevice { //Interfejs


public:
virtual void readUSBData() = 0;
virtual void writeUSBData() = 0;
virtual ~IUSBDevice() {
cout <<"Wirtualny destruktor interfejsu\n";
}
};
//---------------------------------------------------------
class TAsynchReadWrite : public IUSBDevice {
public:
void readUSBData() {
cout << "TAsynchReadWrite::readUSBData()\n";
}
void writeUSBData() {
cout << "TAsynchReadWrite::writeUSBData()\n";
}
TAsynchReadWrite() {
cout << "Konstruktor TAsynchReadWrite\n";
}
~TAsynchReadWrite() {
cout << "Destruktor TAsynchReadWrite\n\n";
}
};
//---------------------------------------------------------
class TSynchReadWrite : public IUSBDevice {
public:
void readUSBData() {
cout << "TSynchReadWrite::readUSBData()\n";
}
void writeUSBData() {
cout << "TSynchReadWrite::writeUSBData()\n";
}
TSynchReadWrite() {
cout << "Konstruktor TSynchReadWrite\n";
}
~TSynchReadWrite() {
cout << "Destruktor TSynchReadWrite\n\n";
}
348 USB. Praktyczne programowanie z Windows API w C++

};
//---------------------------------------------------------
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;
}
//---------------------------------------------------------

Na rysunku 8.16 pokazano diagram sekwencji odpowiadający implementacji kodu


z listingu 8.14 i odzwierciedlający działanie programu po uruchomieniu. Diagram ten
jednoznacznie wyjaśnia sposób korzystania z mechanizmów wielokrotnych delegacji
obiektów realizujących wspólny interfejs. Z przebiegu diagramu sekwencji pokazane-
go na rysunku 8.16 z łatwością odczytamy, że w trakcie delegowania jest tworzona
hierarchia obiektów, a nie klas, i hierarchia ta może zostać zmodyfikowana w dowol-
nym momencie działania programu.
Rozdział 8. ♦ Programowanie obiektowe transmisji USB 349

Rysunek 8.16. Diagram sekwencji odpowiadający implementacji kodu z listingu 8.14

Uzupełnij kod przedstawiony na listingu 8.14 w taki sposób, aby realizował transmisję
danych z zewnętrznym urządzeniem USB.
350 USB. Praktyczne programowanie z Windows API w C++
Rozdział 9.
Wewnętrzne
struktury danych
Elementy opisu dostępnych w systemie urządzeń USB są często zamieszczane w 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;
//--------------------------------------------------------------

Pola tej struktury mogą przechowywać podstawowe dane zawierające identyfikator


sprzętu (HardwareId), ścieżkę dostępu do interfejsu urządzenia (Path) oraz DeviceIn-
stance, który będzie lokalizował element DevInst struktury USB_DEFINFO_DATA. Struk-
tury tego typu powinny również zawierać pola przechowujące wskaźniki do buforów
danych wejściowych, wyjściowych i konfiguracyjnych wraz z ich rozmiarami. Łatwo
zauważyć, że zawartość DEVICE_DATA pozwala dokładniej zidentyfikować i opisać pod-
stawowe i ogólne parametry urządzenia dzięki zagregowaniu struktur PHIDP_PREPARSED_
DATA oraz HIDP_CAPS.
352 USB. Praktyczne programowanie z Windows API w C++

W tym rozdziale prześledzimy możliwości ewolucji programu wykorzystującego we-


wnętrzną strukturę danych w celu enkapsulacji informacji o urządzeniu HID USB.

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).

Rysunek 9.1. Statyczny diagram programu proceduralnego

Na listingu 9.1 zaprezentowano kod odpowiadający diagramowi z rysunku 9.1. Warto


w tym miejscu zauważyć, że na diagramie 9.1 nie uwzględniono zależności klienta od
struktur SP_DEVICE_INTERFACE_DETAIL_DATA, SP_DEVICE_INTERFACE_DATA, SP_DEVINFO_
DATA oraz GUID z tej przyczyny, że wymienione struktury są wewnętrzne względem
jednej z funkcji programu.

Listing 9.1. Kod modułu usb_R9_1.cpp


#include <windows>
#pragma option push -a1
#include <setupapi>
#pragma option pop
#include <assert>
Rozdział 9. ♦ Wewnętrzne struktury danych 353

#include <iostream>

using namespace std;

void displayError(const char* msg){


cout << msg << endl;
system("PAUSE");
exit(0);
};
//---------------------------------------------------------
template <class T>
inline void releaseMemory(T &x)
{
assert(x != NULL);
delete [] x;
x = NULL;
}
//---------------------------------------------------------
typedef USHORT USAGE, *PUSAGE;
typedef struct _HIDP_PREPARSED_DATA *PHIDP_PREPARSED_DATA;
//--------------------------------------------------------
typedef 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;
BYTE *outputReportBuffer;
USHORT outputReportByteLength;
BYTE *featureReportBuffer;
USHORT featureReportByteLength;
/*...*/
PHIDP_PREPARSED_DATA preparsedData;
HIDP_CAPS capabilities;
} DEVICE_DATA, *PDEVICE_DATA;
//---------------------------------------------------------
PDEVICE_DATA deviceList;
//---------------------------------------------------------
354 USB. Praktyczne programowanie z Windows API w C++

BOOL getHidDeviceCapabilities(UINT memberIndex)


{
HMODULE hHidLib;
bool status;

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\\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");

if (!HidP_GetCaps || !HidD_GetPreparsedData || !HidD_FreePreparsedData){


FreeLibrary(hHidLib);
displayError("Nie znaleziono żadnych funkcji eksportowych.\n");
}
status=HidD_GetPreparsedData(deviceList[memberIndex].hidDeviceObject,
&deviceList->preparsedData);
if(status){
HidP_GetCaps(deviceList->preparsedData, &deviceList->capabilities);
deviceList[memberIndex].inputReportByteLength =
deviceList->capabilities.InputReportByteLength;
deviceList[memberIndex].outputReportByteLength =
deviceList->capabilities.OutputReportByteLength;
deviceList[memberIndex].featureReportByteLength =
deviceList->capabilities.FeatureReportByteLength;
HidD_FreePreparsedData(deviceList->preparsedData);
}
FreeLibrary(hHidLib);
return status;
}
//---------------------------------------------------------
UINT setGetHidDeviceData()
{
DWORD propertyBufferSize = 0;
char *propertyBuffer = NULL;
HMODULE hHidLib;
SP_DEVINFO_DATA deviceInfoData;
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;
Rozdział 9. ♦ Wewnętrzne struktury danych 355

DWORD searchMaxDevice = 100; //maksymalna liczba interfejsów urządzeń


bool done = false;

void (__stdcall *HidD_GetHidGuid)(OUT LPGUID HidGuid);

hHidLib = LoadLibrary("C:\\Windows\\SySWOW64\\HID.DLL");
if (!hHidLib)
displayError("Błąd dołączenia biblioteki HID.DLL.");

(FARPROC&) HidD_GetHidGuid = GetProcAddress(hHidLib,


"HidD_GetHidGuid");
if (!HidD_GetHidGuid){
FreeLibrary(hHidLib);
displayError("Nie znaleziono identyfikatora GUID.");
}

HidD_GetHidGuid (&classGuid);

deviceInfoSet = SetupDiGetClassDevs(&classGuid, NULL, NULL,


(DIGCF_PRESENT | DIGCF_DEVICEINTERFACE));

deviceInterfaceData.cbSize = sizeof(SP_INTERFACE_DEVICE_DATA);

while(!done) {
deviceList = new \
DEVICE_DATA[((memberIndex+1)*sizeof(DEVICE_DATA))];

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;
}
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;


356 USB. Praktyczne programowanie z Windows API w C++

deviceList[memberIndex].Path = new TCHAR[(nLen*sizeof(TCHAR))];


strncpy(deviceList[memberIndex].Path,
deviceInterfaceDetailData->DevicePath, nLen);

cout <<"\nDeviceList["<<memberIndex<<"].Path: \n" <<


deviceList[memberIndex].Path << endl;

deviceList[memberIndex].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[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++

UINT interfaceIndex;//numer interfejsu urządzenia HID

//enumeracja aktualnie podłączonych urządzeń HID


setGetHidDeviceData();

cout <<"\n\n\Podaj nr istniejącego interfejsu urządzenia = ";


cin >> interfaceIndex;

cout <<"\nDeviceList["<<interfaceIndex<<"].Path: \n" <<


deviceList[interfaceIndex].Path << endl;

//otwarcie portu USB


openHidUSBDevice(interfaceIndex);

//odczyt właściwości urządzenia


if (getHidDeviceCapabilities(interfaceIndex)) {

//alokacja pamięci dla bufora wejściowego


deviceList[interfaceIndex].inputReportBuffer = \
new BYTE[deviceList[interfaceIndex].inputReportByteLength];

while(true) { //cykliczny odczyt raportów z wybranego urządzenia


readUSBReport(interfaceIndex);
if(deviceList[interfaceIndex].inputReportBuffer[6]==64) {
closeHidUSBDevice(deviceList[interfaceIndex].hidDeviceObject);
break;
}
}
//zwolnienie pamięci dla bufora wejściowego
releaseMemory(deviceList[interfaceIndex].inputReportBuffer);
}
//zwolnienie pamięci dla elementów struktury DEVICE_DATA
//releaseMemory(deviceList[interfaceIndex].Path);
releaseMemory(deviceList);
cout << endl;
system("PAUSE");
return 0;
}
//---------------------------------------------------------

Klient w funkcji main() w programie głównym najpierw enumeruje aktualnie dołączo-


ne do komputera urządzenia klasy HID. Każdemu ze znalezionych interfejsów urzą-
dzenia zostaje przypisany unikatowy indeks (numer interfejsu). Następnie na podsta-
wie tego numeru zostaje wywołana funkcja otwierająca (odblokowująca) urządzenia
do transmisji danych. W przypadku gdy klient prawidłowo uzyska dostęp do sterowni-
ka urządzenia, przystępuje do pobrania podstawowych parametrów eksploatacyjnych
urządzenia, wywołując funkcję getHidDeviceCapabilities(). Po odczytaniu interesu-
jących go informacji klient wywołuje funkcję czytającą raporty wejściowe z urządze-
nia. Na koniec wszystkie zaalokowane przez program zasoby w miarę możliwości po-
winny zostać prawidłowo zwolnione.

W trakcie analizy i testowania powyższego kodu zauważymy, że zewnętrzne urządze-


nie USB jest rzeczywiście rozpoznawane po numerze jego interfejsu, tak jak pokaza-
no na rysunku 9.2.
Rozdział 9. ♦ Wewnętrzne struktury danych 359

Rysunek 9.2. Aplikacja proj_USB_R9_1 w trakcie działania

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.

Na listingu 9.2 zaprezentowano przykład implementacji diagramu z rysunku 9.3, a na


rysunku 9.4 — wynik działania programu.

Listing 9.2. Kod modułu usb_R9_2.cpp


#include <windows>
#pragma option push -a1
#include <setupapi>
#pragma option pop
#include <assert>
360 USB. Praktyczne programowanie z Windows API w C++

Rysunek 9.3. Struktura logiczna programu obiektowego posługującego się strukturą DEVICE_DATA

#include <iostream>

#define searchMaxDevices 10 //maksymalna liczba interfejsów urządzeń

using namespace std;


//---------------------------------------------------------
template <class T>
inline void releaseMemory(T &x)
{
assert(x != NULL);
delete [] x;
x = NULL;
}
//---------------------------------------------------------
typedef USHORT USAGE, *PUSAGE;
typedef struct _HIDP_PREPARSED_DATA *PHIDP_PREPARSED_DATA;
//---------------------------------------------------------
typedef 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;
Rozdział 9. ♦ Wewnętrzne struktury danych 361

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++

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\\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");

if (!HidP_GetCaps || !HidD_GetPreparsedData || !HidD_FreePreparsedData){


FreeLibrary(hHidLib);
displayError("Nie znaleziono żadnych funkcji eksportowych.\n");
}
status=HidD_GetPreparsedData(deviceList[memberIndex].hidDeviceObject,
&deviceList->preparsedData);
if(status){
HidP_GetCaps(deviceList->preparsedData, &deviceList->capabilities);
deviceList[memberIndex].inputReportByteLength =
deviceList->capabilities.InputReportByteLength;
deviceList[memberIndex].outputReportByteLength =
deviceList->capabilities.OutputReportByteLength;
deviceList[memberIndex].featureReportByteLength =
deviceList->capabilities.FeatureReportByteLength;
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;
bool done = false;

void (__stdcall *HidD_GetHidGuid)(OUT LPGUID HidGuid);

hHidLib = LoadLibrary("C:\\Windows\\SySWOW64\\HID.DLL");
Rozdział 9. ♦ Wewnętrzne struktury danych 363

if (!hHidLib)
displayError("Błąd dołączenia biblioteki HID.DLL.");

(FARPROC&) HidD_GetHidGuid = GetProcAddress(hHidLib,


"HidD_GetHidGuid");
if (!HidD_GetHidGuid){
FreeLibrary(hHidLib);
displayError("Nie znaleziono identyfikatora GUID.");
}

HidD_GetHidGuid (&classGuid);

deviceInfoSet = SetupDiGetClassDevs(&classGuid, NULL, NULL,


(DIGCF_PRESENT | DIGCF_DEVICEINTERFACE));

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;
}

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);
364 USB. Praktyczne programowanie z Windows API w C++

//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;
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

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;
}
//---------------------------------------------------------
int main(){
UINT interfaceIndex = 1;//numer istniejącego interfejsu urządzenia HID
//utworzenie obiektu klasy TUSBDevice
TUSBDevice *usbDevice = new TUSBDevice(interfaceIndex);
//enumeracja aktualnie podłączonych urządzeń klasy HID
usbDevice->setGetHidDeviceData();
cout <<"\nDeviceList["<<interfaceIndex<<"].Path: \n" <<
usbDevice->deviceList[interfaceIndex].Path << endl;
//otwarcie portu USB
usbDevice->openHidUSBDevice(interfaceIndex);
//odczyt właściwości urządzenia
if (usbDevice->getHidDeviceCapabilities(interfaceIndex)) {
usbDevice->deviceList[interfaceIndex].inputReportBuffer = new \
BYTE[usbDevice->deviceList[interfaceIndex].inputReportByteLength];
366 USB. Praktyczne programowanie z Windows API w C++

while(true) { //cykliczny odczyt raportu wejściowego


usbDevice->readUSBReport(interfaceIndex);
if(usbDevice->deviceList[interfaceIndex].inputReportBuffer[6]==64)
break;
}
usbDevice->closeHidUSBDevice(usbDevice->deviceList[interfaceIndex].\
hidDeviceObject);
}
releaseMemory(usbDevice->deviceList[interfaceIndex].Path);
releaseMemory(usbDevice->deviceList[interfaceIndex].inputReportBuffer);
//wywołanie destruktora klasy TUSBDevice
delete usbDevice;
return 0;
}
//---------------------------------------------------------

Rysunek 9.4.
Aplikacja
proj_USB_R9_2
w trakcie
pobierania
raportów
wejściowych

Aplikacja środowiska graficznego


Głównym elementem aplikacji środowiska graficznego jest klasa formularza będąca
właścicielem wszystkich komponentów wizualnych tworzących graficzną warstwę
programu. Przejście od aplikacji konsolowej, posługującej się klasą TUSBDevice opisu-
jącą zachowanie urządzenia, do aplikacji środowiska graficznego jest czynnością nie-
zwykle prostą. W celu wykonania takiego przejścia wystarczy odpowiednio powiązać
(lub zagregować) klasę reprezentującą warstwę implementacji z główną klasą repre-
zentującą warstwę prezentacji, tak jak przedstawia to rysunek 9.5. Łatwo zauważyć,
że warstwa implementacji pozostała praktycznie niezmieniona, a z klasą formularza
TForm1 zagregowano pięć klas komponentów wizualnych. Obiekt stworzony na bazie
klasy TButton będzie jedynym elementem sterującym pracą programu. Obiekty stwo-
rzone na bazie klas TListView i TListItem posłużą do wyświetlania i przechowywania
indeksowanych ścieżek dostępu do interfejsów urządzeń klasy HID sekwencyjnie wy-
krywanych w procesie enumeracji. Tworzenie obiektu klasy TUSBDevice i proces enu-
meracji dołączonych urządzeń są wykonywane w konstruktorze TForm1() klasy for-
mularza (rysunek 9.6). Wizualizacją danych odczytywanych z jednego z pól raportu
Rozdział 9. ♦ Wewnętrzne struktury danych 367

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++

Rysunek 9.7. Aplikacja proj_USB_R9_3 w trakcie odczytu raportów wejściowych

Listing 9.3. Kod modułu usb_R9_3.h


//---------------------------------------------------------
#ifndef usb_R9_3H
#define usb_R9_3H
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <ComCtrls.hpp>
#pragma option push -a1
#include <setupapi.h>
#pragma option pop
#include <assert.h>
//---------------------------------------------------------
#define searchMaxDevices 10
//---------------------------------------------------------
template <class T>
inline void releaseMemory(T &x)
{
assert(x != NULL);
delete [] x;
x = NULL;
}
//---------------------------------------------------------
typedef USHORT USAGE, *PUSAGE;
typedef struct _HIDP_PREPARSED_DATA *PHIDP_PREPARSED_DATA;
//---------------------------------------------------------
typedef struct _HIDP_CAPS {
USAGE Usage;
USAGE UsagePage;
USHORT InputReportByteLength;
USHORT OutputReportByteLength;
USHORT FeatureReportByteLength;
USHORT Reserved[17];
USHORT NumberLinkCollectionNodes;
USHORT NumberInputButtonCaps;
Rozdział 9. ♦ Wewnętrzne struktury danych 369

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
//---------------------------------------------------------

Listing 9.4. Kod modułu usb_R9_3.cpp


//---------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "usb_R9_3.h"
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------
TUSBDevice::TUSBDevice(UINT memberIndex)
{
fMemberIndex = memberIndex;
deviceList = new \
DEVICE_DATA[((fMemberIndex+1)*sizeof(DEVICE_DATA))];
}
//---------------------------------------------------------
TUSBDevice::~TUSBDevice()
{
releaseMemory(deviceList);
ShowMessage("Pamięć zwolniona prawidłowo.");
}
//---------------------------------------------------------
void TUSBDevice::displayError(const char* msg)
{
ShowMessage(msg);
exit(0);
};
//---------------------------------------------------------
BOOL TUSBDevice::getHidDeviceCapabilities(UINT memberIndex)
{
HMODULE hHidLib;
bool status;

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\\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

if (!HidP_GetCaps || !HidD_GetPreparsedData || !HidD_FreePreparsedData){


FreeLibrary(hHidLib);
displayError("Nie znaleziono żadnych funkcji eksportowych.");
}
status=HidD_GetPreparsedData(deviceList[memberIndex].hidDeviceObject,
&deviceList->preparsedData);
if(status){
HidP_GetCaps(deviceList->preparsedData, &deviceList->capabilities);
deviceList[memberIndex].inputReportByteLength =
deviceList->capabilities.InputReportByteLength;
deviceList[memberIndex].outputReportByteLength =
deviceList->capabilities.OutputReportByteLength;
deviceList[memberIndex].featureReportByteLength =
deviceList->capabilities.FeatureReportByteLength;

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;

void (__stdcall *HidD_GetHidGuid)(OUT LPGUID HidGuid);

hHidLib = LoadLibrary("C:\\Windows\\SySWOW64\\HID.DLL");
if (!hHidLib)
displayError("Błąd dołączenia biblioteki HID.DLL.");

(FARPROC&) HidD_GetHidGuid = GetProcAddress(hHidLib,


"HidD_GetHidGuid");
if (!HidD_GetHidGuid){
FreeLibrary(hHidLib);
displayError("Nie znaleziono identyfikatora GUID.");
}
372 USB. Praktyczne programowanie z Windows API w C++

HidD_GetHidGuid (&classGuid);

deviceInfoSet = SetupDiGetClassDevs(&classGuid, NULL, NULL,


(DIGCF_PRESENT | DIGCF_DEVICEINTERFACE));

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;
}

size_t nLen = strlen(deviceInterfaceDetailData->DevicePath) + 1;


deviceList[fMemberIndex].Path = new TCHAR[(nLen*sizeof(TCHAR))];
strncpy(deviceList[fMemberIndex].Path,
deviceInterfaceDetailData->DevicePath, nLen);

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);

//alokowanie pamięci dla bufora danych


propertyBuffer = new char[(propertyBufferSize*sizeof(TCHAR))];

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.");

//odczyt właściwości urządzenia


if(usbDevice->getHidDeviceCapabilities(ListView1->Selected->Index)){
//alokowanie pamięci dla bufora wejściowego
usbDevice->deviceList[ListView1->Selected->Index].inputReportBuffer =
new BYTE[usbDevice->deviceList[ListView1->Selected->Index].\
inputReportByteLength];

while(true) { //cykliczny odczyt raportu wejściowego


Rozdział 9. ♦ Wewnętrzne struktury danych 375

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++

i kontrolnych w raportach, za pomocą których urządzenie komunikuje się z macierzy-


stym komputerem. Na rysunku 9.8 pokazano przykładowy diagram zawierający struk-
turę DEVICE_DATA opisującą właściwości urządzenia oraz agregujące z nią struktury
opisujące zawartość raportów, którymi posługuje się urządzenie. Na listingu 9.5 za-
mieszczono szkielet implementacji zestawu struktur z rysunku 9.8.

Rysunek 9.8. Struktury opisujące urządzenie klasy HID i raporty, którymi się posługuje

Listing 9.5. Przykładowa implementacja diagramu z rysunku 9.8


//---------------------------------------------------------
typedef struct _HID_DATA
{
USAGE UsagePage;
ULONG ReportID;
union {
struct {
ULONG UsageMin;
ULONG UsageMax;
ULONG MaxUsageLength;
PUSAGE Usages; //lista przycisków w stanie ON
//...
} ButtonData;
struct {
USAGE Usage;
ULONG Value;
//...
} ValueData;
};
} HID_DATA, *PHID_DATA;
//---------------------------------------------------------
Rozdział 9. ♦ Wewnętrzne struktury danych 377

typedef struct _DEVICE_DATA {


TCHAR *HardwareId;
TCHAR *Path;
DWORD DeviceInstance;
HANDLE hidDeviceObject;
//---
BYTE *inputReportBuffer;
USHORT inputReportByteLength;
PHID_DATA inputData;
PHIDP_BUTTON_CAPS inputButtonCaps;
PHIDP_VALUE_CAPS inputValueCaps;
//---
BYTE *outputReportBuffer;
USHORT outputReportByteLength;
PHID_DATA outputData;
PHIDP_BUTTON_CAPS outputButtonCaps;
PHIDP_VALUE_CAPS outputValueCaps;
//---
BYTE *featureReportBuffer;
USHORT featureReportByteLength;
PHID_DATA featureData;
PHIDP_BUTTON_CAPS featureButtonCaps;
PHIDP_VALUE_CAPS featureValueCaps;
//...
PHIDP_PREPARSED_DATA preparsedData;
HIDP_CAPS capabilities;
HIDD_ATTRIBUTES attributes;
} DEVICE_DATA, *PDEVICE_DATA;
//---------------------------------------------------------

Zmodyfikuj kody przedstawione w tym rozdziale, wykorzystując implementację po-


wyższych struktur.

Ć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.

Uogólniając, współbieżność odrębnych procesów może być realizowana na jednym


z trzech poziomów:
 sprzętowym (komputer posiadający architekturę wieloprocesorową
lub wielordzeniową),
 systemowym,
 aplikacji (podział czasu procesora między różne elementy tej samej aplikacji).
380 USB. Praktyczne programowanie z Windows API w C++

W dalszej części rozdziału zostaną omówione niektóre aspekty współbieżności reali-


zowanej na poziomie systemu operacyjnego oraz aplikacji.

System operacyjny zarządza wątkami na podstawie ich priorytetu. Wątki o wyższym


priorytecie mają pierwszeństwo w wykonaniu przed wątkami o niższym priorytecie.
Na poziomie tego samego priorytetu wątki są zarządzane w taki sposób, aby każdy
z nich mógł być wykonany. System może wstrzymać wykonanie wątku (sytuację taką
nazywa się wywłaszczeniem), aby przekazać czas procesora na rzecz innego wątku.
W systemie operacyjnym MS Windows są definiowane trzy podstawowe kategorie
określające stan wątku:
 wątek wykonujący się,
 wątek gotowy,
 wątek blokowany.

Każdy wątek może być wykonany, pod warunkiem że w danej chwili ma dostęp do
rejestrów procesora. System operacyjny współdzieli tyle 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).

Jądro systemu Windows działa w trybie z wywłaszczaniem. Na rysunku 10.1 pokaza-


no uproszczony diagram klas dla systemu z jądrem działającym w trybie z wywłasz-
czaniem [7].

Rysunek 10.1.
Uproszczony
schemat dla systemu
operacyjnego
działającego w trybie
z wywłaszczaniem
Rozdział 10. ♦ Programy wielowątkowe 381

Projektując aplikację zawierającą elementy wielowątkowości, należy zadbać o to, aby


wykonywane wątki jak najmniej czasu spędzały w stanie zawieszenia (zablokowania)
i jak najwięcej w stanie wykonywania.

Funkcja CreateThread()
Podstawową funkcją Windows API, tworzącą nowy watek, jest CreateThread(). Funk-
cja wątku przyjmuje parametr typu LPVOID i zwraca wartość typu całkowitego, będącą
kodem zakończenia wątku. 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);

Funkcja CreateThread() rozpoczyna wątek w programie wielowątkowym poprzez


wywołanie funkcji ThreadFunc() w kontekście nowego wątku. Wątek zostaje zakoń-
czony w chwili powrotu z funkcji. CreateThread() zwraca identyfikator (tzw. uchwyt)
nowego wątku lub pusty wskaźnik, jeżeli Windows nie jest w stanie utworzyć nowe-
go wątku. CreateThread() definiuje ponadto parametry ThreadFunc i odwołaniowy
ThreadID (tzw. pseudoidentyfikator wątku) w stylu języka C++. Jeżeli parametr Cre-
ationFlags zawiera STACK_SIZE_IS_A_RESERVATION, to StackSize jest rozmiarem sto-
su zarezerwowanym dla nowego wątku. Parametr CreationFlags podaje odpowiedni
znacznik kontroli sposobu utworzenia nowego wątku. Jeżeli wyspecyfikujemy parametr
CREATE_SUSPENDED, działanie wątku będzie zawieszone (wstrzymane) do czasu wywo-
łania funkcji API Windows ResumeThread(). Jeżeli zostanie mu przypisana wartość 0,
nowy wątek zostanie natychmiast uruchomiony.

Podobnie jak w przypadku innych zasobów systemu Windows, po zakończeniu wątku


należy obowiązkowo wywołać funkcję CloseHandle(), aby mieć pewność, że Windows
zwolni wszystkie zasoby związane z wątkiem. C++ powoduje mały przeciek zasobów,
jeżeli wątek zostanie uruchomiony w trybie zawieszenia, a następnie zamknięty bez
ponowienia pracy. Aby uniknąć tego przecieku, należy zawsze wznawiać pracę wątku
przed jego zakończeniem.

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)

ThreadFunc definiuje pewien wskaźnik do funkcji typu DWORD z argumentem w postaci


wskaźnika ogólnego. Nazwa funkcji w momencie użycia jest traktowana jako adres
startowy nowego wątku (obiektu Win32 lub Win64).
382 USB. Praktyczne programowanie z Windows API w C++

Ilustrację wykorzystania w praktyce funkcji CreateThread() stanowi stworzenie nie-


skomplikowanej aplikacji, której zadaniem będzie wyświetlenie w osobnym wątku
w oknie tekstowym zawartości kolejnych raportów wejściowych. W tym celu zostanie
zaprojektowana abstrakcyjna klasa Thread, zawierająca czysto wirtualną funkcję run-
Thread(), statyczną funkcję ThreadFunc() i funkcję beginThread(), której wartością
powrotną jest wynik wykonania funkcji CreateThread(). Sposób wykonania tego za-
dania został przedstawiony na rysunku 10.2 i listingu 10.1. Kod wątku zostanie zapi-
sany w implementacji funkcji runThread() w klasie dziedziczącej TThreadUSBPort, tak
jak pokazano na listingu 10.2.

Rysunek 10.2. Logiczny diagram klas programu wielowątkowego

Listing 10.1. Kod modułu usb_R10_1.h zawierający implementację funkcji składowych klas
z rysunku 10.2
#ifndef usb_R10_1H
#define usb_R10_1H
#include <windows>
#pragma option push -a1
#include <setupapi>
#pragma option pop
#include <assert>
#include <iostream>

#define searchMaxDevices 10 //maksymalna liczba interfejsów urządzeń

//--------------------------------------------------------------
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

Listing 10.2. Kod modułu usb_R10_1.cpp


#include <iostream>
#include "usb_R10_1.h"
using namespace std;
//--------------------------------------------------------------
TUSBDevice::TUSBDevice(UINT memberIndex)
{
fMemberIndex = memberIndex;
deviceList = new \
DEVICE_DATA[((fMemberIndex+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;

long (__stdcall* HidP_GetCaps)(IN PHIDP_PREPARSED_DATA PreparsedData,


OUT PHIDP_CAPS Capabilities);
bool (__stdcall* HidD_GetPreparsedData)(IN HANDLE HidDeviceObject,
OUT PHIDP_PREPARSED_DATA *PreparsedData);

bool (__stdcall* HidD_FreePreparsedData)


(IN PHIDP_PREPARSED_DATA PreparsedData);
Rozdział 10. ♦ Programy wielowątkowe 385

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");

if (!HidP_GetCaps || !HidD_GetPreparsedData || !HidD_FreePreparsedData){


FreeLibrary(hHidLib);
displayError("Nie znaleziono żadnych funkcji eksportowych.\n");
}
status=HidD_GetPreparsedData(deviceList[memberIndex].hidDeviceObject,
&deviceList->preparsedData);
if(status){
HidP_GetCaps(deviceList->preparsedData, &deviceList->capabilities);
deviceList[memberIndex].inputReportByteLength =
deviceList->capabilities.InputReportByteLength;
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;
bool done = false;

void (__stdcall *HidD_GetHidGuid)(OUT LPGUID HidGuid);

hHidLib = LoadLibrary("C:\\Windows\\System32\\HID.DLL");
if (!hHidLib)
displayError("Błąd dołączenia biblioteki HID.DLL.");

(FARPROC&) HidD_GetHidGuid = GetProcAddress(hHidLib,"HidD_GetHidGuid");


if (!HidD_GetHidGuid){
FreeLibrary(hHidLib);
displayError("Nie znaleziono identyfikatora GUID.");
}
HidD_GetHidGuid (&classGuid);
deviceInfoSet = SetupDiGetClassDevs(&classGuid, NULL, NULL,
(DIGCF_PRESENT | DIGCF_DEVICEINTERFACE));
deviceInterfaceData.cbSize = sizeof(SP_INTERFACE_DEVICE_DATA);
386 USB. Praktyczne programowanie z Windows API w C++

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();

cout <<"\nDeviceList["<<interfaceIndex<<"].Path: \n" <<


usbDevice->deviceList[interfaceIndex].Path << endl;

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.

Na listingach 10.3 i 10.4 zaprezentowano odpowiednio kod modułu zawierającego


implementację klas i struktur z rysunku 10.4 oraz kod modułu z implementacją funk-
cji składowych omawianych klas.

Listing 10.3. Moduł usb_R10_2.h jako implementacja logicznej struktury klas i struktur z rysunku 10.3
#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++

Rysunek 10.4. Logiczna struktura wielowątkowego programu korzystającego z abstrakcyjnej


klasy TThread

template <class T>


inline void releaseMemory(T &x)
{
assert(x != NULL);
delete [] x;
x = NULL;
}
//--------------------------------------------------------------
template <class T>
inline void releaseThread(T &x)
{
assert(x != NULL);
x->Terminate();
delete x;
x = NULL;
}
//--------------------------------------------------------------
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;
Rozdział 10. ♦ Programy wielowątkowe 391

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

Listing 10.4. Kod modułu usb_R10_2.cpp


#include <windows>
#pragma option push -a1
#include <setupapi>
#pragma option pop
#include <iostream>
#include "usb_R10_2.h"

using namespace std;


392 USB. Praktyczne programowanie z Windows API w C++

//--------------------------------------------------------------
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;

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_GetCaps=GetProcAddress(hHidLib, "HidP_GetCaps");


(FARPROC&) HidD_GetPreparsedData=GetProcAddress(hHidLib,
"HidD_GetPreparsedData");
(FARPROC&) HidD_FreePreparsedData=GetProcAddress(hHidLib,
"HidD_FreePreparsedData");

if (!HidP_GetCaps || !HidD_GetPreparsedData || !HidD_FreePreparsedData){


FreeLibrary(hHidLib);
displayError("Nie znaleziono żadnych funkcji eksportowych.\n");
}
status=HidD_GetPreparsedData(deviceList[memberIndex].hidDeviceObject,
&deviceList->preparsedData);
if(status){
HidP_GetCaps(deviceList->preparsedData, &deviceList->capabilities);
deviceList[memberIndex].inputReportByteLength =
deviceList->capabilities.InputReportByteLength;
HidD_FreePreparsedData(deviceList->preparsedData);
}
FreeLibrary(hHidLib);
return status;
}
Rozdział 10. ♦ Programy wielowątkowe 393

//--------------------------------------------------------------
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;

void (__stdcall *HidD_GetHidGuid)(OUT LPGUID HidGuid);

hHidLib = LoadLibrary("C:\\Windows\\System32\\HID.DLL");
if (!hHidLib)
displayError("Błąd dołączenia biblioteki HID.DLL.");

(FARPROC&) HidD_GetHidGuid = GetProcAddress(hHidLib,


"HidD_GetHidGuid");
if (!HidD_GetHidGuid){
FreeLibrary(hHidLib);
displayError("Nie znaleziono identyfikatora GUID.");
}
HidD_GetHidGuid (&classGuid);
deviceInfoSet = SetupDiGetClassDevs(&classGuid, NULL, NULL,
(DIGCF_PRESENT | DIGCF_DEVICEINTERFACE));
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)) {
394 USB. Praktyczne programowanie z Windows API w C++

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;
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++

TThreadUSBPort::TThreadUSBPort(BOOL CreateSuspended, UINT *runThread,


UINT memberIndex): TThread(CreateSuspended)
{
fMemberIndex = memberIndex;
fRunThread = runThread;
*fRunThread = true;
usbDevice = new TUSBDevice(fMemberIndex);
}
//--------------------------------------------------------------
void __fastcall TThreadUSBPort::Execute()
{
try {
//enumeracja aktualnie podłączonych urządzeń klasy HID
usbDevice->setGetHidDeviceData();

cout <<"\nDeviceList["<<fMemberIndex<<"].Path: \n" <<


usbDevice->deviceList[fMemberIndex].Path << endl;
//otwarcie portu USB
if(!usbDevice->openHidUSBDevice(fMemberIndex))
return;
else {
usbDevice->getHidDeviceCapabilities(fMemberIndex);
usbDevice->deviceList[fMemberIndex].inputReportBuffer = new \
BYTE[usbDevice->deviceList[fMemberIndex].inputReportByteLength];

while(!Terminated && *fRunThread != false &&


usbDevice->deviceList[fMemberIndex].inputReportBuffer[6]!=64){
//odczyt raportów wejściowych
usbDevice->readUSBReport(fMemberIndex);
}
}
}
__finally {
usbDevice->closeHidUSBDevice(usbDevice->deviceList[fMemberIndex].\
hidDeviceObject);
releaseMemory(usbDevice->deviceList[fMemberIndex].Path);
releaseMemory(usbDevice->deviceList[fMemberIndex].inputReportBuffer);
//wywołanie destruktora klasy TUSBDevice
delete usbDevice;
}
}
//--------------------------------------------------------------
TThreadUSBPort *threadUSBPort = NULL;
//--------------------------------------------------------------
int main(){
UINT interfaceIndex = 1;//numer indeksu istniejącego interfejsu urządzenia HID
UINT runUSBThread;
if (runUSBThread != true){
threadUSBPort = new TThreadUSBPort(true, &runUSBThread, interfaceIndex);
//threadUSBPort1->Priority = tpTimeCritical; //ustaw priorytet wątku
threadUSBPort->Resume(); //uruchomienie wątku 1.
}
cin.get();
releaseThread(threadUSBPort);
return 0;
}
//--------------------------------------------------------------
Rozdział 10. ♦ Programy wielowątkowe 397

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.

Więcej bardziej wyczerpujących informacji na temat nowoczesnych metod programo-


wania wielowątkowego można znaleźć w publikacjach [6, 7].

Ć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);

Wątek tworzący semafor specyfikuje wartości wstępną i maksymalną licznika wy-


wołań. Inne wątki uzyskują dostęp do semafora za pomocą funkcji OpenSemaphore().
Po zakończeniu pracy w sekcji krytycznej wątek zwalnia semafor za pomocą funkcji
ReleaseSemaphore(), tak jak pokazano na listingu 10.5.

Listing 10.5. Przykład wykorzystania funkcji CreateSemaphore()


#include <iostream>
//...
using namespace std;
//Deklaracje zmiennych globalnych
unsigned long threadFuncSend(void* parameter);
unsigned long threadFuncReceive(void* parameter);
//----------------------------------------------------
int main()
{
bufferIn=(char*)HeapAlloc(GetProcessHeap(),
HEAP_ZERO_MEMORY,
strlen(bufferOut)+1);
//Enumeracja urządzeń
hSemaphore=CreateSemaphore(NULL, 0, 1,
"FILE_EXISTS");
// tworzymy dwa wątki
// 1. do wysyłania danych
hThread[0] = CreateThread(NULL, 0,
(LPTHREAD_START_ROUTINE)threadFuncSend,
&hSemaphore, 0, &threadID1);
398 USB. Praktyczne programowanie z Windows API w C++

// 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;
}
//----------------------------------------------------

Uzupełnij oraz zmodyfikuj powyższy przykład na potrzeby kodu nadzorującego trans-


misję USB.

Ć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);

Listing 10.6. Przykład wykorzystania funkcji CreateMutex()


#include <iostream>
//...
using namespace std;
//Deklaracje zmiennych globalnych
unsigned long threadFuncSend(void* parameter);
unsigned long threadFuncReceive(void* parameter);
//----------------------------------------------------
int main()
{
bufferIn=(char*)HeapAlloc(GetProcessHeap(),
HEAP_ZERO_MEMORY,
strlen(bufferOut)+1);
//Enumeracja urządzeń
hMutex=CreateMutex(NULL, TRUE, "FILE_EXISTS");
//tworzymy dwa wątki
//1. do wysyłania danych
hThread[0] = CreateThread(NULL, 0,
(LPTHREAD_START_ROUTINE)threadFuncSend,
&hMutex, 0, &threadID1);
//2. do czytania danych
hThread[1] = CreateThread(NULL, 0,
(LPTHREAD_START_ROUTINE)threadFuncReceive,
&hMutex, 0, &threadID2);
ReleaseMutex(hMutex);
WaitForMultipleObjects(2, hThread, TRUE, 10);

CloseHandle(hMutex);
CloseHandle(hThread[0]);
CloseHandle(hThread[1]);
HeapFree(GetProcessHeap(),0,bufferIn);
cin.get();
return 0;
}
//---------------------------------------------------
unsigned long threadFuncSend(void* parameter)
{
void* hMutex = &parameter;
WaitForSingleObject(hMutex, INFINITE);
//Wysyłanie danych do urządzenia wykonawczego
ReleaseMutex(hMutex);
return TRUE;
}
//----------------------------------------------------
400 USB. Praktyczne programowanie z Windows API w C++

unsigned long threadFuncReceive(void* parameter)


{
void* hMutex = &parameter;
WaitForSingleObject(hMutex, INFINITE);
//Odbiór danych pochodzących z urządzenia wykonawczego
ReleaseMutex(hMutex);
return TRUE;
}
//----------------------------------------------------

Uzupełnij oraz zmodyfikuj powyższy przykład na potrzeby kodu nadzorującego trans-


misję USB.

Ćwiczenie 10.3
Jednym z zasadniczych problemów programowania wielowątkowego jest konieczność
zachowania integralności danych. Kiedy zachodzi potrzeba ochrony dostępu do 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.

Adaptery USB/RS 232C


Jednym ze standardów transmisji szeregowej, który wśród urządzeń laboratoryjnych
i przemysłowych dalej konkuruje z USB, jest RS 232C. Sytuacja taka ma miejsce
głównie ze względu na jego użyteczność w zewnętrznych urządzeniach, która w dużej
mierze jest związana z prostotą implementacji i obsługi [6].

Dostępne na rynku adaptery USB/RS 232C pozwalają na podłączenie urządzeń za-


opatrzonych w interfejs szeregowy RS 232C do portów USB w komputerze. Adaptery
posiadają połączone odcinkiem kabla złącze USB typu A oraz DSUB9 lub DSUB25
(port RS 232C), w którego obudowie znajduje się układ adaptera zasilanego z portu
USB komputera (rysunek 11.1).

Rysunek 11.1.
Wygląd typowego
adaptera
USB/RS 232C
402 USB. Praktyczne programowanie z Windows API w C++

W zależności od producenta adaptery tego typu instalują się jako urządzenia PnP lub
za poś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.

Właściwości portu adaptera


Po prawidłowym zainstalowaniu urządzenie adaptera jest widoczne w menedżerze urzą-
dzeń. Po przejściu do opcji Porty (COM i LPT) możemy z łatwością odszukać wybra-
ny wirtualny port szeregowy USB (rysunek 11.2).

Rysunek 11.2. Port adaptera USB/RS 232C

Jeśli z kolei wybierzemy Ustawienia portu (rysunek 11.3), możemy się przekonać, że
do złudzenia przypominają one swoje odpowiedniki ze standardowych portów szere-
gowych. Mamy zatem możliwość odpowiedniego wyboru prędkości transmisji, długo-
ści ramki danych, parzystości, bitów stopu i typu kontroli przepływu danych. Należy
w tym miejscu zauważyć, że korzystając z portu USB, nie można ustalić wszystkich
prędkości transmisji (b/s) deklarowanych w systemie Windows: 110, 300, 1200, 2400,
Rozdział 11. ♦ Adaptery USB 403

4800, 9600, 19200, 38400, 57600, 115200, 230400, 460800 oraz 921200. Ponieważ
niektóre systemy Windows nie 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

Po wybraniu zaawansowanych ustawień portu adaptera (opcja Zaawansowane…) zo-


staje uaktywniona zakładka pokazana na rysunku 11.4.

Rysunek 11.4.
Ustawienia portu
adaptera
404 USB. Praktyczne programowanie z Windows API w C++

Oprócz możliwości ustalania rozmiarów buforów transmisyjnych (tak jak w standar-


dowym łączu szeregowym) istnieje dodatkowa opcja umożliwiająca przypisanie por-
towi USB wartości łącza COM od 1 do 256. Standardowo w momencie instalacji ste-
rowników portowi USB jest przypisywany pierwszy wolny port COM, z którego od
tej chwili możemy swobodnie korzystać. Jeżeli w przyszłości zechcemy, aby aplikacja
nie posługiwała się portem domyślnym, a — powiedzmy — portem COM156, należy
w tym miejscu wybrać właśnie taką wartość portu. Często mówimy, że port należy za-
rejestrować. Samodzielne wybranie wartości portu bezpośrednio w aplikacji w przy-
padku USB nie przyniesie oczekiwanych efektów. Możliwość ustalenia wyższych war-
tości dla portów USB stanowi spore udogodnienie dla użytkownika, gdyż w znacznym
stopniu ogranicza prawdopodobieństwo wystąpienia konfliktu łącza USB z innymi
portami szeregowymi.

Adaptery USB/IEEE-488
IEEE-488 (znany jako GPIB — ang. General Purpose Interface Bus) jest standardem
interfejsu równoległego o krótkim zasięgu, wykorzystywanym w automatycznych sys-
temach pomiarowych.

Urządzenia, w których jest zaimplementowany ten interfejs, dzieli się na nadajniki,


odbiorniki i kontrolery, porozumiewające się między sobą za pośrednictwem dwóch
rodzajów komunikatów: interfejsu i urządzeń. Funkcję nadajnika pełni urządzenie prze-
syłające komunikaty do jednego lub większej liczby odbiorników. Przed nawiązaniem
połączenia kontroler adresuje nadajnik i odbiornik, a w trakcie transmisji monitoruje
sieć połączeń. W momencie gdy zauważy, że nadajnik zgłasza się do przesłania infor-
macji, łączy go z odpowiednim odbiornikiem, umożliwiając transmisję. Jeżeli w sys-
temie występuje tylko jeden nadajnik, a pozostałe urządzenia są odbiornikami, stoso-
wanie kontrolera nie jest konieczne. Komputer wyposażony w kartę interfejsu GPIB
może być kontrolerem, nadajnikiem lub odbiornikiem.

Do magistrali GPIB można przyłączyć do 15 urządzeń, wśród których może występo-


wać kilka kontrolerów nadzorujących pracę systemu, ale w danym momencie tylko je-
den z nich powinien być aktywny. Urządzenia współpracujące z magistralą korzystają
z dostępu do niej na identycznych zasadach, bez konieczności pośrednictwa centralnej
jednostki sterującej. Wszystkie rozkazy i większość danych są przesyłane w formie
7-bitowych kodów ASCII.

Na rysunku 11.5 pokazano wygląd typowego konwertera USB/GPIB. Konwertery tego


typu posiadają połączone odcinkiem kabla złącze USB typu A oraz wtyczkę IEEE-488,
w której obudowie znajduje się układ konwertera zasilanego z portu USB komputera.
W zależności od producenta konwertery tego typu są instalowane jako urządzenia PnP
lub za pośrednictwem własnych sterowników. Dostarczane wraz z konwerterem ste-
rowniki są instalowane w systemach Windows, dzięki czemu uzyskujemy dostęp do
dodatkowego 8-bitowego wirtualnego portu COM, którego można używać tak jak stan-
dardowego portu (identycznie jak w przypadku konwertera USB/RS 232C).
Rozdział 11. ♦ Adaptery USB 405

Rysunek 11.5.
Wygląd typowego
adaptera USB/GPIB

Adaptery USB/Bluetooth
Dynamiczny rozwój systemów informatycznych oraz coraz większa liczba pojawiają-
cych się na rynku urządzeń zdolnych do wzajemnej wymiany informacji w czasie rze-
czywistym skłoniły producentów sprzętu i oprogramowania do opracowania standardu
bezprzewodowego przesyłu danych Bluetooth, który zapewnia prostotę wykrywania
urządzeń przez systemy, a zarazem zadawalającą uniwersalność i funkcjonalność ob-
sługi. 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

Dokumentacja standardu Bluetooth opracowana przez organizację SIG (ang. Special


Interest Group) nie zawiera specyfikacji konkretnego interfejsu programistycznego
API właściwego danej platformie systemowej. SIG zakłada, że w trakcie wdrażania
technologii Bluetooth na danej platformie potrzeba opracowania właściwego API po-
winna spoczywać na programistach. Z powyższych względów postanowiono nie opra-
cowywać oddzielnych API dla poszczególnych systemów operacyjnych. Zamiast tego
za pomocą profili zdefiniowano wszystkie funkcje niezbędne programistom tworzącym
oprogramowanie współpracujące z konkretnymi aplikacjami Bluetooth na danej plat-
formie systemowej. Tym samym, mimo że specyfikacja standardu nie zawiera właści-
wego API, dostarcza jednak programistom aplikacji wszystkich niezbędnych wska-
zówek, które pozwalają w prosty sposób przekształcić je w API konkretnej platformy.

Zdefiniowane przez SIG profile określają funkcje urządzenia. Obejmują one różne war-
stwy i protokoły służące zapewnieniu kompatybilności między aplikacjami oraz urzą-
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++

są uporządkowane w grupach i mogą być zależne od innych, jeżeli wykorzystują de-


klaracje profili nadrzędnych. Wśród profili, które powinny szczególnie zainteresować
Czytelników niniejszej książki, należy wymienić profil urządzeń interfejsu HID. Profil
ten został zaadaptowany ze specyfikacji klasy HID urządzeń USB. Informacje o akcjach
użytkownika na elementach sterujących urządzenia Bluetooth (takich jak naciśnięcie
klawisza) są przesyłane do komputera w czasie rzeczywistym. Profil ten umożliwia
na przykład zdalne sterowanie za pomocą klawiatury telefonu komórkowego aplika-
cjami uruchomionymi na komputerze.

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).

RFCOMM jest jednym z protokołów transportowych zdefiniowanych przez SIG. Bar-


dzo często protokół ten jest określany mianem emulatora standardowego portu szere-
gowego RS 232C. Połączeniowy, strumieniowy protokół komunikacyjny RFCOMM
zapewnia użytkownikowi prosty i niezawodny dostęp do strumienia danych przezna-
czonych zarówno do wysłania, jak i odbioru. Jest on stosowany bezpośrednio przez
wiele profili związanych z telefonią jako nośnik komend AT, a także jako warstwa
transportowa dla usług wymiany danych w postaci obiektów OBEX (ang. Object Ex-
change). Wiele współczesnych aplikacji Bluetooth używa RFCOMM ze względu na
jego szerokie wsparcie techniczne w postaci publicznego API dostępnego w większo-
ści systemów operacyjnych. Warto też zdawać sobie sprawę z faktu, że aplikacje uży-
wające standardowego portu szeregowego mogą być szybko zaadaptowane na potrze-
by RFCOMM. Maksymalna liczba jednocześnie dostępnych połączeń wynosi 60.

Komendy AT to zestaw poleceń, które po raz pierwszy zastosowała w swoich urządze-


niach (w celu ujednolicenia obsługi sprzętu, z którym miał współpracować komputer)
znana z produkcji modemów firma Hayes. Pierwotnie polecenia te miały służyć jedy-
nie do sterowania pracą modemów analogowych. Jednak wraz z upowszechnieniem
się technologii GSM bardzo szybko zostały zaadaptowane do obsługi modemów wbu-
dowanych w telefony komórkowe. Obecnie każde urządzenie bazujące na technologii
GSM posiada wbudowany interpreter komend AT i wykonuje je zgodnie z normą
przyjętą przez producentów. Oznacza to, że implementacje komend AT dla konkret-
nych urządzeń mogą się nieznacznie różnić pomiędzy sobą, co nie zmienia faktu, że
zarówno składnia rozkazów, jak i wynik ich realizacji są znormalizowane. Na rysun-
ku 11.7 pokazano ogólną klasyfikację komend AT.

Rysunek 11.7.
Klasyfikacja
poleceń AT
Rozdział 11. ♦ Adaptery USB 407

Tak jak pokazano na rysunku 11.7, komendy AT dzielą się na cztery podstawowe grupy:
 Polecenia typu Test (testowe) — służą do sprawdzania, czy dana komenda
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

Zgodnie ze standardem każde polecenie rozpoczyna się od prefiksu AT i kończy zna-


kiem powrotu karetki CR (13 lub \r). Komenda nie będzie realizowana, dopóki urzą-
dzenie GSM nie odbierze znaku CR. Przyjęcie polecenia do realizacji przez urządze-
nie jest potwierdzane znakiem nowej linii LF (10 lub \n). Więcej informacji na temat
komend AT można znaleźć w publikacji J. Bogusza, Programowanie i obsługa modu-
łów GSM, „Elektronika Praktyczna” 2002, nr 8; http://ep.com.pl/files/8073.pdf.

Na listingu 11.1 zaprezentowano przykładowy program kontrolujący w sposób asyn-


chroniczny operacje wysyłania za pośrednictwem wirtualnego portu szeregowego po-
leceń ATI (typ urządzenia) oraz AT+CCLK? (zapytanie o aktualną datę i czas), a następnie
pobierania informacji zwrotnych z urządzenia GSM z funkcją Bluetooth. Transmisja
danych jest programowana za pomocą standardowych funkcji API SDK.

Należy zwrócić uwagę, że do poprawnego działania przedstawionych algorytmów


programowej realizacji bezprzewodowej transmisji danych w standardzie Bluetooth
wymagane jest, aby urządzenie wykonawcze było wcześniej poprawnie zestawione
(sparowane) i uwierzytelnione przez główny moduł radiowy zaimplementowany na
przykład w komputerze lub w postaci adaptera USB/Bluetooth.

Listing 11.1. Przykład asynchronicznej transmisji danych poprzez wirtualny port szeregowy
pomiędzy głównym modułem radiowym urządzenia nadrzędnego (komputer, adapter USB/Bluetooth)
a pozostającym w zasięgu urządzeniem wykonawczym
#include <iostream>
#include <windows>

#define cbInQueue 1024


#define cbOutQueue 1024

using namespace std;

void* hCommDev;
408 USB. Praktyczne programowanie z Windows API w C++

DCB dcb;
COMMTIMEOUTS commTimeouts;

//-----prototypy funkcji--------
void closeSerialPort();
int readSerialPort(void *buffer, unsigned long numberOfBytesToRead);
int writeSerialPort(void *buffer, unsigned long numberOfBytesToWrite);
bool openSerialPort(const char* portName);
bool setCommTimeouts(unsigned long ReadIntervalTimeout,
unsigned long ReadTotalTimeoutMultiplier,
unsigned long ReadTotalTimeoutConstant,
unsigned long WriteTotalTimeoutMultiplier,
unsigned long WriteTotalTimeoutConstant);
bool setTransmissionParameters(unsigned long BaudRate,int ByteSize,
unsigned long fParity,int Parity,
int StopBits);
//---------------------------------------------------------
int main()
{
openSerialPort("COM5"); //wirtualny port szeregowy COM5
setTransmissionParameters(CBR_9600, 8, true, ODDPARITY, ONESTOPBIT);
setCommTimeouts(0xFFFFFFFF, 10, 0, 10, 0);
char bufferIn[24];
char bufferOut[64] = {0};

char *text;
//text = "ATI\r"; //przykładowe komendy AT
text = "AT+CCLK?\r";
strcpy(bufferIn, text);
writeSerialPort(bufferIn, strlen(bufferIn));

cout << "Otrzymano bajtów: " << readSerialPort(bufferOut,


sizeof(bufferOut)) << endl;
cout << bufferOut;

closeSerialPort();
system("PAUSE");
return 0;
}
//----ciała funkcji----------------------------
bool openSerialPort(const char* portName)
{
hCommDev = CreateFile(portName,GENERIC_READ | GENERIC_WRITE, 0,
NULL,OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL |
FILE_FLAG_OVERLAPPED, NULL);
if(hCommDev==INVALID_HANDLE_VALUE){
cout <<"Błąd otwarcia portu " << portName << \
" lub port 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;

result = WriteFile(hCommDev, buffer, numberOfBytesToWrite,


&numberOfBytesWritten, &overlapped);
if (!result) {
if(GetLastError() == ERROR_IO_PENDING) {
while(!GetOverlappedResult(hCommDev,
&overlapped,
&numberOfBytesWritten, FALSE )) {
lastError = GetLastError();
if(lastError == ERROR_IO_INCOMPLETE){
410 USB. Praktyczne programowanie z Windows API w C++

numberOfBytesWritten += bytesSent;
continue;
}
else {
ClearCommError(hCommDev, &errors, &comStat);
break;
}
}
numberOfBytesWritten += bytesSent;
}
else {
ClearCommError(hCommDev, &errors, &comStat);
}
}
else
numberOfBytesWritten += bytesSent;
FlushFileBuffers(hCommDev);
return numberOfBytesWritten;
}
//----------------------------------------------
int readSerialPort(void *buffer, unsigned long numberOfBytesToRead)
{
BOOL result;
COMSTAT comStat ;
unsigned long errors;
unsigned long bytesRead = 0;
unsigned long numberOfBytesRead = 0;
unsigned long lastError;
OVERLAPPED overlapped;

ClearCommError(hCommDev, &errors, &comStat);


bytesRead = numberOfBytesToRead;
if(bytesRead > 0) {
result = ReadFile(hCommDev, buffer, bytesRead, &bytesRead,
&overlapped);
if(!result) {
if(GetLastError() == ERROR_IO_PENDING) {
while(!GetOverlappedResult(hCommDev, &overlapped,
&bytesRead, TRUE)) {
lastError = GetLastError();
if(lastError == ERROR_IO_INCOMPLETE)
{
numberOfBytesRead += bytesRead;
continue;
}
else {
ClearCommError(hCommDev, &errors, &comStat);
}
break;
}
numberOfBytesRead += bytesRead;
}
}
else numberOfBytesRead += bytesRead;
}
else
ClearCommError(hCommDev, &errors, &comStat);
Rozdział 11. ♦ Adaptery USB 411

return numberOfBytesRead;
}
//---------------------------------------------------------
void closeSerialPort()
{
if (CloseHandle(hCommDev))
cout << "\n\nPort został zamknięty do transmisji.\n\n";
return;
}
//---------------------------------------------------------

Na rysunkach 11.8 i 11.9 pokazano omawiany program w trakcie działania.

Rysunek 11.8.
Odpowiedź
urządzenia
zewnętrznego
na odebraną
komendę ATI

Rysunek 11.9.
Odpowiedź
urządzenia
zewnętrznego
na odebraną
komendę
AT+CCLK?

Na listingu 11.2 zaprezentowano przykładowy program wysyłający do telefonu GSM


zestawionego z komputerem rozkaz ATD <numer> dzwonienia pod wybrany numer.
Transmisja danych jest programowana za pomocą funkcji z biblioteki WinSock. Na
rysunku 11.10 zobrazowano zasadę działania przedstawionego algorytmu.

Listing 11.2. Wykorzystanie biblioteki WinSock


#include <iostream>
#include <initguid>
#pragma option push -a1
#include <winsock2>
#include "D:\\WINDDK\\7600.16385.1\\inc\\api\\Ws2bth.h"
#include "D:\\WINDDK\\7600.16385.1\\inc\\api\\BluetoothAPIs.h"
#pragma option pop
using namespace std;
//---------------------------------------------------------
void showError()
{
LPVOID lpMsgBuf;
FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
412 USB. Praktyczne programowanie z Windows API w C++

NULL, GetLastError(), 0,
(LPTSTR) &lpMsgBuf, 0, NULL );
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);

Sleep(10000); //próba łączenia przez ok. 10 sekund

result = shutdown(s, SD_SEND);


if (result == SOCKET_ERROR) {
Rozdział 11. ♦ Adaptery USB 413

printf("Błąd wykonania shutdown: %ld\n", WSAGetLastError());


closesocket(s);
WSACleanup();
return 1;
}
WSACleanup();
system("PAUSE");
return 0;
}
//---------------------------------------------------------

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++

[16] Universal Serial Bus 3.0 Specification, http://www.usb.org/developers/docs/.


[17] USB IF, Language Identifiers (LANGIDs), 3/29/00 Version 1.0, 2000.
[18] Wireless Universal Serial Bus Specification 1.1, http://www.usb.org/
developers/wusb/docs.
Skorowidz
A bus driver, 105
bus interval, 24
adaptery
USB, 401
USB/Bluetooth, 405
C
USB/GPIB, 405 C++ Compiler 5.5, 8
USB/IEEE-488, 404 certyfikat, 274
USB/RS 232C, 401 CRC, Cyclic Redundancy Check, 24
agregujące klasyfikatory, 81 czas
API, Application Programming Interface, 71 oczekiwania na zdarzenie, 228
aplikacja środowiska graficznego, 218, 366 przeterminowania, 228
asynchroniczna transmisja danych, 407 czynności, 158

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++

dostęp do HidD_GetIndexedString(), 122


odczytu, 192 HidD_GetInputReport(), 116, 119, 207
pliku sterownika, 194 HidD_GetManufacturerString(), 121
strumienia danych, 406 HidD_GetMsGenreDescriptor(), 124
uruchamiania pliku, 192 HidD_GetNumInputBuffers(), 121
urządzenia USB, 172 HidD_GetPhysicalDescriptor(), 124
zapisu, 192 HidD_GetPreparsedData(), 123
drukowanie łańcucha znaków, 268 HidD_GetProductString(), 122
HidD_GetSerialNumberString(), 122
HidD_SetFeature(), 119, 226
E HidD_SetNumInputBuffers(), 120
edytor rejestru, 64 HidD_SetOutputReport(), 116, 120, 226
elementy HidP_GetButtonCaps(), 125, 209
kontrolne, 114 HidP_GetButtons(), 125
sterujące, 114 HidP_GetButtonsEx(), 125
enumeracja urządzeń, 149 HidP_GetCaps(), 126
EP, endpoints, 24 HidP_GetData(), 129
HidP_GetExtendedAttributes(), 130
HidP_GetLinkCollectionNodes(), 130
F HidP_GetScaledUsageValue(), 131
FDO, Functional Device Objects, 84 HidP_GetSpecificButtonCaps(), 132
Filter DO, Filter Device Objects, 84 HidP_GetSpecificValueCaps(), 133
FireWire, 11 HidP_GetUsages(), 135
format HidP_GetUsagesEx(), 135, 203
danych, 115 HidP_GetUsageValue(), 134
rozkazu, 237 HidP_GetUsageValueArray(), 134
FS, Full Speed, 18 HidP_GetValueCaps(), 137, 213
funkcja HidP_InitializeReportForID(), 137
AddRef(), 329 HidP_MaxDataListLength(), 138
CloseHandle(), 194 HidP_MaxUsageListLength(), 138
CM_Get_DevNode_Registry_Property(), 176 HidP_SetButtons(), 139
CreateEvent(), 229 HidP_SetData(), 139
CreateFile(), 160, 192, 228 HidP_SetScaledUsageValue(), 139
CreateInstance(), 326 HidP_SetUsages(), 140
CreateMutex(), 399 HidP_SetUsageValue(), 140
CreateSemaphore(), 397 HidP_SetUsageValueArray(), 140
CreateThread(), 381 HidP_UnsetUsages(), 142
DeviceIoControl(), 236 HidP_UsageAndPageListDifference(), 143
FileIOCompletionRoutine(), 231 HidP_UsageListDifference(), 142
FreeLibrary(), 145 InterlockedIncrement(), 329
getCollectionDescriptor(), 242 LoadLibrary(), 144
GetCommTimeouts(), 235 openDevice(), 259
getDSPortConnectionIndex(), 255 ReadFile(), 117, 199, 203
getDSPortData(), 258 ReadFileEx(), 231
getHubInformation(), 260 Release(), 329
GetLastError(), 231 searchInterfaceHidDevices(), 161
getRegistryPropertyDWORD(), 167 SetCommTimeouts(), 235
getStringDescriptor(), 258 SetupDiDestroyDeviceInfoList(), 157
HidD_FlushQueue(), 123 SetupDiEnumDeviceInterfaces(), 152
HidD_FreePreparsedData(), 124 SetupDiGetClassDevs(), 152
HidD_GetAttributes(), 117 SetupDiGetDeviceInterfaceDetail(), 155
HidD_GetFeature(), 118 SetupDiGetDeviceRegistryProperty(), 163
HidD_GetHidGuid(), 119 SleepEx(), 232
strstr(), 199
Skorowidz 419

Synchronize(), 400 WinUsb_WritePipe(), 289


ThreadFunc(), 382 WriteFile(), 117, 225, 226
usb_bulk_read(), 301 WriteFileEx(), 230
usb_bulk_setup_async(), 302 funkcje
usb_bulk_write(), 300 API SDK, 407
usb_cancel_async(), 303 asynchroniczne, 301
usb_claim_interface(), 296 biblioteki HID.dll, 145
usb_clear_halt(), 295 biblioteki LibUSB, 292
usb_close(), 294 biblioteki Setupapi.dll, 182
usb_control_msg(), 296 biblioteki WinUSB, 277
usb_find_busses(), 292 eksportowe, 145, 278
usb_find_devices(), 293 urządzeń klasy HID, 116
usb_free_async(), 303
usb_get_busses(), 293
usb_get_descriptor(), 299
G
usb_get_descriptor_by_endpoint(), 299 GPIB, General Purpose Interface Bus, 404
usb_get_string(), 298 GUID, Globally Unique Identifier, 67
usb_get_string_simple(), 299
usb_init(), 292
usb_interrupt_read(), 301 H
usb_interrupt_setup_async(), 302 HDMI, 12
usb_interrupt_write(), 301 HS, High Speed, 18
usb_isochronous_setup_async(), 301
usb_open(), 293
usb_reap_async(), 302 I
usb_reap_async_nocancel(), 302
usb_release_interface(), 296 IAD, Interface Association Descriptor, 99
usb_reset(), 296 identyfikacja
usb_set_altinterface(), 295 kontrolera PCI, 245
usb_set_configuration(), 295 urządzeń, 247
usb_set_debug(), 293 identyfikator
usb_submit_async(), 302 DeviceInterfaceGUIDs, 272
UsbBuildGetDescriptorRequest(), 267 GUID, 67
WaitForSingleObject(), 229 identyfikatory
WaitForSingleObjectEx(), 232 interfejsu, 327
WinUsb_AbortPipe(), 280 producenta VID, 64, 68
WinUsb_ControlTransfer(), 280 produktu PID, 64, 68
WinUsb_FlushPipe(), 282 sprzętu, hardware ID, 58, 98
WinUsb_Free(), 280 urządzenia, 57
WinUsb_GetAssociatedInterface(), 283 zgodności, compatible IDs, 58, 98
WinUsb_GetCurrentAlternateSetting(), 283 IEEE 1394d, 11
WinUsb_GetDescriptor(), 283 IEEE-488, 404
WinUsb_GetOverlappedResult(), 284 informacje
WinUsb_GetPipePolicy(), 284 o certyfikacie, 275
WinUsb_GetPowerPolicy(), 285 o urządzeniach, 81, 196
WinUsb_Initialize(), 278 instalacja urządzenia, 272
WinUsb_QueryDeviceInformation(), 285 integralność danych, 400
WinUsb_QueryInterfaceSettings(), 286 interfejs
WinUsb_QueryPipe(), 286 IDeviceFactory, 341
WinUsb_ReadPipe(), 287 IUnknown, 326
WinUsb_ResetPipe(), 288 interfejsy, 95, 319
WinUsb_SetCurrentAlternateSetting(), 288 dodawanie funkcji, 336
WinUsb_SetPipePolicy(), 288 dodawanie metody, 335
WinUsb_SetPowerPolicy(), 288 izochroniczna transmisja danych, 11
420 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

O pola pakietu SPLIT, 32


pole
OBEX, Object Exchange, 406 ADDR, 30
obiekt urządzenia, 109 bEndpointAddress, 93, 94
obiektowość, 307 ConnectionIndex, 254
odblokowanie urządzenia, 191 CRC, 30
odczyt Dane, 30
danych, 198 ENDP, 30
danych cykliczny, 303 EOP, 30
własności przycisków, 208 PID, 28
własności wartości, 213 SYNC, 28
zawartości deskryptorów, 292 polecenia typu
określanie typów urządzeń, 177 Execution, 407
opis deskryptorów, 80 Read, 407
oznaczenia urządzeń USB, 13 Set, 407
Test, 407
połączenia w trybie Super Speed, 77
P port adaptera USB/RS 232C, 402
pakiet potok, pipe, 78
ACK, 37 potoki danych, 77
CSPLIT, 31 pozycja
instalacyjny, 272 Collection, 112
NAK, 34 End Collection, 112
PING, 34 Unit, 116
potwierdzenia, handshake packet, 33, 37 Usage, 111
preambuły, preamble packet, 33 prędkości transmisji, 402
SETUP, 31 proces, 379
SOF, 31 program
SPLIT, 31 certmgr.exe, 274
SSPLIT, 31 inf2cat.exe, 274
pakiety makecert.exe, 274
danych, data packets, 30 signtool.exe, 274
USB 2.0, 28 programowanie obiektowe, 307
zapowiedzi, token packets, 31 programy
parametry transmisji, 284 obiektowe, 359
PCI Express, 12 proceduralne, 352
PDO, Physical Device Object, 84 wielowątkowe, 379
PID, Packet Identifier, 29 protokół
PID, Product ID, 64 MTP, 278
pliki RFCOMM, 406
.cat, 274 przekształcanie identyfikatora GUID, 68
.dll, 144 punkt końcowy, endpoint, 24, 78, 89
.inf, 69, 70, 274
.lib, 144 R
PnP, Plug and Play, 152
pobieranie raportu wejściowego, 232 ramka, frame, 24
podklucz raport
\Class, 65 konfiguracyjny, future report, 113
\Device Parameters, 65 wejściowy, input report, 113
\Driver, 66 wyjściowy, output report, 113
\Enum\USB, 64 rejestr systemowy, 63
podłączanie urządzeń, 185 RFCOMM, Radio Frequency Communication, 406
podpisywanie sterowników, 274 rodzaje raportów, 113
422 USB. Praktyczne programowanie z Windows API w C++

rozkaz ATD, 411 stany urządzenia, 149


rozkazy sterownik, 57
IOCTL_Xxx, 242 libusb0.sys, 291
struktury SetupPacket, 256 Usbccgp.sys, 98, 106
z modułu Usbd.sys, 107
hidclass.h, 242 winusb.sys, 271
usbioctl.h, 245 sterowniki
RS-232C, 11, 401 filtrujące, filter drivers, 105
klas urządzeń, 105
klienta, client drivers, 105
S magistrali danych, bus drivers, 105
semafor, semaphore, 397 operacyjne, function drivers, 105
SIE, System Interface Engine, 76 stos sterowników
SIG, Special Interest Group, 405 kontrolera hosta, 107
singleton, 314 urządzenia winusb, 277
słowo kluczowe USB 2.0, 107, 109
HIDD_ATTRIBUTES, 118 USB 3.0, 108, 109
HIDP_CAPS, 128 struktura
HUB_DEVICE_CONFIG_INFO, 103 COMMTIMEOUTS, 234
LPUSB_CONFIGURATION, 101 DEV_BROADCAST_DEVICEINTERFACE,
LPUSB_INTERFACE, 97 185
PHIDD_ ATTRIBUTES, 118 DEVICE_DATA, 168, 351, 360, 376
PHIDP_CAPS, 128 HIDD_ATTRIBUTES, 118, 352
PHUB_ DEVICE_CONFIG_INFO, 103 HIDP_CAPS, 127
PUSB_ CONFIGURATION, 101 HUB_DEVICE_CONFIG_INFO, 103
PUSB_ ID_STRING, 104 OVERLAPPED, 227
PUSB_ INTERFACE, 97 SetupPacket, 256
PUSB_DEVICE i LPUSB_DEVICE, 103 SP_DEVICE_INTERFACE_DATA, 154
PUSB_HUB_ DESCRIPTOR, 86 SP_DEVICE_INTERFACE_DETAIL_DATA,
PUSB_INTERFACE_DESCRIPTOR, 97 155
PUSB_STRING_DESCRIPTOR, 104 SP_DEVINFO_DATA, 153
PUSB_SUPERSPEED_ENDPOINT_ systemu
COMPANION_DESCRIPTOR, 95 USB 2.0, 73
PUSBD_INTERFACE_INFORMATION, 98 USB 3.0, 76
PUSBD_PIPE_INFORMATION, 79 USB_30_HUB_DESCRIPTOR, 87
USB_CONFIGURATION, 101 USB_CONFIGURATION, 101
USB_CONFIGURATION_DESCRIPTOR, USB_CONFIGURATION_DESCRIPTOR,
102 101, 102
USB_DEVICE, 103 USB_DESCRIPTOR_REQUEST, 256
USB_HUB_DESCRIPTOR, 86 USB_DEVICE, 102
USB_ID_STRING, 104 USB_DEVICE_DESCRIPTOR, 82
USB_INTERFACE, 97 USB_ENDPOINT, 90
USB_INTERFACE_DESCRIPTOR, 97 USB_ENDPOINT_DESCRIPTOR, 90–93
USB_STRING_DESCRIPTOR, 104 USB_HUB_DESCRIPTOR, 86
USB_SUPERSPEED_ENDPOINT_ USB_HUB_INFORMATION, 86
COMPANION_ DESCRIPTOR, 95 USB_HUB_INFORMATION_EX, 88
USBD_INTERFACE_INFORMATION, 98 USB_ID_STRING, 103
USBD_PIPE_INFORMATION, 79 USB_INTERFACE, 97
USBD_PIPE_TYPE, 79 USB_INTERFACE_ASSOCIATION_
SOF, Start of Frame, 24 DESCRIPTOR, 99
sposoby połączenia urządzeń, 19 USB_INTERFACE_DESCRIPTOR, 96
SS, Super Speed, 18 USB_STRING_DESCRIPTOR, 104
stan wątku, 380 USB_SUPERSPEED_ENDPOINT_
standardy łączy szeregowych, 12 COMPANION_DESCRIPTOR, 95
Skorowidz 423

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++

wyprowadzenia w złączach złącze


USB 3.0 typu A, 17 USB 2.0 typu A, 14
USB 3.0 typu B, 17 USB 2.0 typu B, 14
wysłanie rozkazu nawiązania połączenia, 413 USB 2.0 typu Micro-B, 20
wyszukiwanie sterownika, 58 USB 2.0 typu Mini-A, 20
wywłaszczenie, 380 USB 3.0 typu A, 16
wzajemne wykluczenie, mutual exclusion, 398 USB 3.0 typu B, 16
wzorzec USB 3.0 typu Micro-A/AB, 21
fabryki, 341 USB 3.0 typu Micro-B, 22
obserwatora, 343 USB 3.0 typu Powered-B, 18, 19
znacznik
czasu, 24
Z DeviceIsHub, 259
zakres wartości danych, 115 FILE_ FLAG_OVERLAPPED, 228
zapis danych, 225 SOF, 24
zarządzanie urządzeniem libusb, 293 znak
zliczanie nowej linii, 407
interfejsów urządzeń, 161 powrotu karetki, 407
odwołań do interfejsu, 326
złącza
Micro, 19
Mini, 19
Notatki

You might also like