RAW Input

Tym razem będzie znów coś związanego z systemem Windows i programowaniem gier, czyli obsługa myszy za pomocą RAW Input. Jest to jeden z trzech popularnych sposobów obsługi tych urządzeń. Pozostałymi są komunikaty procedury okna oraz DirectInput, z tym że ten ostatni jest niezalecany i już nie jest wspierany przez Microsoft na rzecz właśnie RAW Input. Jednak dlaczego nie te dwa?

Komunikaty procedury okna nie są tak naprawdę takie złe, co więcej bardzo dobrze nadają się do małych gier casualowych, najlepiej tych odpalanych w oknie, ponieważ wartościami otrzymywanymi od systemu (w przypadku myszy) jest pozycja kursora, a pozycja jest poprawiana tak, aby znalazł się on w miejscu w którym użytkownik się tego spodziewa. Niestety dane otrzymywane w ten sposób nie są dokładne więc w bardziej wymagających grach mogą okazać się nieprzydatne.
DirectInput jest natomiast sposobem na uzyskanie dokładniejszych danych, jednak zasada jego działania opiera się na utworzeniu dodatkowego wątku, podpięcia się do wskazanego okna i odczytywaniu danych bezpośrednio ze sterownika, czyli surowych danych (RAW). Najprawdopodobniej jest to przyczyną braku wsparcia dla tej metody, dodatkowo niektóre antywirusy mogą ostrzegać że aplikacja którą napisaliśmy jest keyloggerem.

RAW Input umożliwia aplikacji uzyskanie surowych danych bezpośrednio ze sterownika. Zasada działania opiera się tutaj, tak samo jak w pierwszej metodzie, na komunikatach procedury okna. Różnica polega na tym, że chęć otrzymywania tych danych należy wcześniej w systemie zarejestrować. Jest to póki co najlepsza metoda uzyskiwania dokładnych danych z myszy, ponieważ dostarcza informacje na temat przesunięć, a nie pozycji kursora. Otrzymywanie danych z klawiatury również jest możliwe, ale w tym przypadku dokładność nie ma sensu.

Do rejestracji urządzeń, których dane chcielibyśmy otrzymywać tą metodą, służy funkcja:

<a href="http://msdn.microsoft.com/en-us/library/ms645600%28VS.85%29.aspx">BOOL WINAPI RegisterRawInputDevices(
  __in  PCRAWINPUTDEVICE pRawInputDevices,
  __in  UINT uiNumDevices,
  __in  UINT cbSize
);
</a>

Pierwszym parametrem tej funkcji jest tablica obiektów struktury RAWINPUTDEVICE, która opisuje rejestrowane urządzenie. Pozostałe parametry to ilość urządzeń i rozmiar struktury, czyli sizeof(RAWINPUTDEVICE).

Poniższy przykład pokazuje rejestrację myszy:

RAWINPUTDEVICE rawStructure;
rawStructure.usUsagePage = 1;                            // Typ urządzenia (w tym przypadku GENERIC)
rawStructure.hwndTarget = m_hWnd;                    // Uchwyt okna
rawStructure.usUsage = 2;                                   // Rodzaj urządzenia (2 - mysz)
rawStructure.dwFlags = RIDEV_NOLEGACY;        // Więcej info w opisie struktury
 
if(!RegisterRawInputDevices(&amp;rawStructure, 1, sizeof(RAWINPUTDEVICE)))
{
    // Obsługa błędu
}

Wartości usUsage i usUsagePage można znaleźć w tym dokumencie.

Jeśli rejestracja urządzenia się powiedzie, to aplikacja przestanie otrzymywać standardowe komunikaty związane z tym urządzeniem, a zacznie otrzymywać komunikaty WM_INPUT z danymi RAW tego urządzenia.
Dane można otrzymywać na dwa sposoby: pojedynczo, bezpośrednio w trakcie obsługi komunikatu lub zbuforowane. Poniżej podam przykład na standardową obsługę komunikatu WM_INPUT, więcej info na temat tej i drugiej metody można znaleźć na tej stronie.

LRESULT WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
     switch(msg)
     {
     case WM_INPUT:
          {
               RAWINPUT riStruct;
               DWORD dwSize = sizeof(RAWINPUT);
 
               if(GetRawInputData((HRAWINPUT)lParam, RID_INPUT, &amp;riStruct, &amp;dwSize, sizeof(RAWINPUTHEADER)) == (UINT)-1)
               {
                    // Obsługa błędu
               }
 
               if(riStruct.header.dwType == RIM_TYPEMOUSE)
               {
                    riStruct.data.mouse.lLastX;     // Przesuniecie X
                    riStruct.data.mouse.lLastY;     // Przesuniecie Y
 
                    riStruct.data.mouse.ulButtons;     // Przyciski myszy
 
                    riStruct.data.mouse.usButtonData;     // Rolka myszy
               }
          }
          break;
     }
}

Dziwić może to, że funkcja przyjmuje wskaźnik na zmienną do rozmiaru struktury RAWINPUT. Jest to spowodowane tym, że różne urządzenia mają różne struktury i podanie niewłaściwego rozmiaru skutkuje błędem oraz wpisaniem wymaganego rozmiaru pod podanym adresem.

Wyrejestrowanie urządzenia następuje po wywołaniu funkcji RegisterRawInputDevices podając jako w flagę w przekazywanej strukturze RIDEV_REMOVE.

Ogólnie rzecz biorąc RAW Input jest najlepszym sposobem na otrzymanie dokładnych danych z urządzeń wejściowych. Obsługa klawiatury jest trochę bardziej skomplikowana, a szczerze mówiąc nie widzę sensu korzystania z tego sposobu. Lepiej pozostać przy komunikatach. Osobiście uważam, że odpytywanie o stan inputu jest lepsze niż odpowiadanie na komunikaty, ponieważ aplikacja jest wtedy bardziej przewidywalna.

3 Comments

  1. Reg:

    Raw Input czy nie, odpytywanie ma dużą wadę w porównaniu z kolejką komunikatów. Kiedy gra działa wolno a użytkownik ma dobry refleks, wciśnięcie klawisza albo kliknięcie myszką może zostać przez program niezauważone, bo pojawi się tylko w czasie między dwoma odpytaniami.

  2. Netrix:

    No tak, tego nie wziąłem pod uwagę. W takim razie pozostaje chyba tylko generowanie zdarzeń na podstawie tych komunikatów.

  3. I:

    Na szczęście nie trzeba rezygnować ze standardowych komunikatów – wystarczy ustawić pole dwFlags struktury RAWINPUTDEVICE na 0. Dzięki temu jednocześnie możemy odbierać komunikaty o położeniu kursora i przesunięciu myszki.

Leave a comment