앨리스의 토끼

javahawk.egloos.com

포토로그 방명록


최근 포토로그


RegisterRawInputDevices by Kircheis

얼마 전에 DEVPIA를 통해 알게된 함수이다.
이 함수는 간단히 말하자면 윈도우의 이벤트 리시버로 등록하는 기능을 수행한다.
이게 뭐냐하면 윈도우에서 발생하는 일부 메시지에 대해 발생하는 즉시 해당 메시지를 받아들이는 기능을 말한다.
말이 좀 어려워서 그렇지만, 돌려 말하면 hooking을 사용하지 않고 시스템 메시지를 받는 다는 말이다.
이 함수를 사용할 경우의 이점은 hooking처럼 별도의 callback thread를 구성하지 않고, 자신의 어플에 있는 윈도우 프로시져에서 바로 시스템 메시지를 받을 수 있다는 점이다.
그만큼 리소스에 있어서의 압력이 줄어든다는 말이지..

그런데, 얘가 받을 수 있는 메시지들은 구분이 되어 있다.

1. HID_DEVICE_SYSTEM_MOUSE
 - 마우스 관련 장비에 대한 이벤트를 받을 수 있다.
 - Usage page는 0x01
 - 0x01은 포인터, 0x02는 마우스

2. HID_DEVICE_SYSTEM_GAME
 - 게임 관련 장비에 대한 이벤트를 받을 수 있다.
 - Usage page는 0x01
 - 0x04는 조이스틱, 0x05는 게임 패드

3. HID_DEVICE_SYSTEM_KEYBOARD
 - 키보드와 관련된 장비에 대한 이벤트
 - Usage page는 0x01
 - 0x06은 키보드, 0x07은 키패드

4. HID_DEVICE_SYSTEM_CONTROL
 - 시스템 컨트롤 메시지
 - Usage page는 0x01
 - 0x80으로 시스템 컨트롤 메시지를 받을 수 있다.

5. HID_DEVICE_SYSTEM_CONSUMER
 - Consumer Audio Control
 - Usage page는 0x0C
 - 0x01로 Audio control 메시지를 받을 수 있다.

이 중에서 실제 사용해 본 기능은 마우스와 키보드이고, 나머지는 MSDN에서 그렇다고 하니 그러려니 하고 있슴둥...

대충 받을 수 있는 메시지를 통해 낌새를 챘는 지 모르겠지만, 이건 전적으로 H/W의 메시지와 관련된 부분이다.
돌려 말하면, hooking으로 처리할 수 있는 범위와는 그 대상이 틀리다는 것.

이와 관련된 문서를 읽어보면 주로 Input, HID(Humal Interface Devices), Smart card devices와 관련하여 다뤄지고 있으며, 깊이는 WDK관련 문서를 참조할 수 있다.
뭐 깊이 가자면 한이 없는 만큼 다음에 기회가 되면 다뤄보도록 하고, 간단히 사용하는 방법이나 알아보자.

일단 RAWINPUTDEVICE 구조체를 받을 메시지의 종류만큼 선언해 준다.
일단 마우스와 키보드의 두가지를 사용할 것이니 2개를 배열로 선언해 주고 초기화 하자.

 RAWINPUTDEVICE rawInputDevice[2];
 ZeroMemory( rawInputDevice, sizeof( RAWINPUTDEVICE ) * 2 );

일단 마우스를 먼저 지정해 보자.

 rawInputDevice[0].usUsagePage = 0x01;
 rawInputDevice[0].usUsage = 0x02;
 rawInputDevice[0].dwFlags = RIDEV_INPUTSINK;
 rawInputDevice[0].hwndTarget = m_hWnd;    //    메시지를 처리할 프로시져의 윈도우 핸들

다음으로 키보드를 지정한다.

 rawInputDevice[1].usUsagePage = 0x01;
 rawInputDevice[1].usUsage = 0x02;
 rawInputDevice[1].dwFlags = RIDEV_INPUTSINK;
 rawInputDevice[1].hwndTarget = m_hWnd;

여기서보면 RAWINPUTDEVICE.dwFlags에 지정된 RIDEV_INPUTSINK가 보인다.
이 값은 다음의 8가지 값을 가질 수 있다.

1. RIDEV_APPKEYS
 - XP SP1 - "If set, the application command keys are handled" 
 - "RIDEV_APPKEYS can be specified only if RIDEV_NOLEGACY is specified for a keyboard device."

2. RIDEV_NOLEGACY
 - "this prevents any devices specified by usUsagePage or usUsage from generating legacy messages"
 - 마우스와 키보드 만 지원한다.

3. RIDEV_CAPTUREMOUSE
 - 이걸 설정하면 마우스 클릭 이벤트를 다른 윈도우에서 받을 수 없게 된단다.

4. RIDEV_EXCLUDE
 - "this specifies the top level collections to exclude when reading a complete usage page"
 - "This flag only affects a TLC whose usage page is already specified with RIDEV_PAGEONLY"

5. RIDEV_INPUTSINK
 - 이걸 사용할 때는 윈도우 핸들은 필수로 정해줘야 한단다.

6. RIDEV_NOHOTKEYS
 - 이걸 사용할 경우 App.에서 정의된 핫키를 핸들링되지 않는단다. 단, 시스템 핫키는 사용이 가능하다.
 - 이 플래그는 대상 윈도우 핸들이 없어도 사용할 수 있단다.

7. RIDEV_PAGEONLY
 - 이건 지정된 UsagePage의 top level 전체에 해당하므로, usUsage가 반드시 NULL값이 되어야 한다.
 - 전체를 다 반영하지 않을 것이라면 RIDEV_EXCLUDE를 사용해야 한다.

8. RIDEV_REMOVE
 - 이건 포함된 메시지 대상 리스트에서 top level collection을 제거한다.
 - 바꿔 말하면, 대상이 되는 top level collection으로 부터 메시지를 읽어들이는 작업을 중지한다는 것을 의미한다.

이제 선언이 끝났으니 함수를 호출한다.

 if( FALSE == RegisterRawInputDevices( rawInputDevice, 2, sizeof( RAWINPUTDEVICE ) ) )
 {
    char szError[256] = { 0 };
    ReportLastError( szError, 256, "WM_INPUT message", false );
    m_pApp->m_Log.WriteLog( "RegisterMessageReceiver", "ERR", "%s", szError );
 }

RegisterRawInputDevices의 두번째 인자 첫번째 인자로 주어진 RAWINPUTDEVICE의 개수를 지정한다.
8개를 지정하고 2개만 사용할 수도 있다는 말이다.

받을 때는 보통의 윈도우 이벤트와 마찬가지로 처리해 준다.
여기서는 WM_INPUT 메시지를 통해서 처리할 수 있다.

일단 헤더에 처리 함수를 추가해 준다.

afx_msg LRESULT OnInput(WPARAM wParam, LPARAM lParam);

다음으로 CPP의 BEGIN_MESSAGE_MAP에 메시지를 등록해 준다.

ON_MESSAGE(WM_INPUT, OnInput)

이제 구현부를 CPP에 추가한다.

LRESULT CMainFrame::OnInput(WPARAM wParam, LPARAM lParam)
{
    RAWINPUT rawInput;
    ZeroMemory( &rawInput, sizeof( RAWINPUT ) );
    UINT unSize = sizeof( RAWINPUT );
    ::GetRawInputData( ( HRAWINPUT )lParam, RID_INPUT, &rawInput, &unSize, sizeof( RAWINPUTHEADER ) );


    switch( rawInput.header.dwType )
    {
    case RIM_TYPEKEYBOARD:
        if( rawInput.data.keyboard.Message == WM_KEYDOWN || rawInput.data.keyboard.Message == WM_SYSKEYDOWN )
            m_nInputKeyStroke = 1;

        break;
    case RIM_TYPEMOUSE:
        m_nInputMouseMoving = 1;

        break;
    case RIM_TYPEHID:
        break;
    default:
        break;
    }

    return 0L;
}

실제 수신되는 메시지는 RAWINPUT 구조체를 사용해 받을 수 있다.
GetRawInputData는 WM_INPUT 메시지로부터 데이터를 받아 RAWINPUT 구조체에 담아주는 역할을 한다.
RAWINPUT 구조체는 다음과 같은 구조로 되어 있다.

typedef struct tagRAWINPUT {
    RAWINPUTHEADER    header;
    union {
             RAWMOUSE    mouse;
             RAWKEYBOARD keyboard;
             RAWHID      hid;
            } data;
} RAWINPUT, *PRAWINPUT; *LPRAWINPUT;

data union에서 장치별로 구분하여 메시지를 받을 수 있다.
마우스와 키보드는 기본 장비이므로 별도로 구분이 되지만, 기타 장비의 경우 RAWHID로 구분된 구조체를 사용한다.

다음은 키보드의 구조체이다.

typedef struct tagRAWKEYBOARD {
    USHORT MakeCode;
    USHORT Flags;
    USHORT Reserved;
    USHORT VKey;
    UINT Message;
    ULONG ExtraInformation;
} RAWKEYBOARD, *PRAWKEYBOARD, *LPRAWKEYBOARD;

실제 메시지 부분은 VKey와 Message를 사용하여 처리할 수 있다.
MakeCode와 Flags 부분은 좀더 깊은 수준을 요하기 대문에 설명을 생략한다.
ExtraInformation은 이벤트에 대한 추가적인 정보란다.

다음은 마우스의 구조체이다.

typedef struct tagRAWMOUSE {
  USHORT    usFlags;
  union {
         ULONG    ulButtons;
             struct {
                       USHORT usButtonFlags;
                       USHORT usButtonData;
                       };
  };
  ULONG ulRawButtons;
  LONG  lLastX;
  LONG  lLastY;
  ULONG ulExtraInformation;
} RAWMOUSE, *PRAWMOUSE, *LPRAWMOUSE;

usFlags는 다음의 값들의 조합이 될 수 있다.

1. MOUSE_ATTRIBUTES_CHANGED
 - Mouse attributes changed; application needs to query the mouse attributes

2. MOUSE_MOVE_RELATIVE
 - 마우스의 이동 데이터가 이전의 마지막 포지션과 연관된다는...
 - 뭐 결국 움지이면 항상 설정된다는...

3. MOUSE_MOVE_ABSOLUTE
 - 절대값 기준으로 봐야한다는...
 - 이건 결국 MOUSE_MOVE_RELATIVE와는 절대 같이 설정될 수 없다는...

4. MOUSE_VIRTUAL_DESKTOP
 - 다중 모니터나 가상 머신의 경우에 해당하는 듯...

usButtonFlags는 마우스의 실제 이벤트로 일반적으로 처리하는 이벤트와 유사한 값들이 많다.
usButtonFlags가 RI_MOUSE_WHEEL을 가지면 usButtonData에 설정되는 값은 wheel delta의 signaled value가 된다.
ulRawButtons는 Raw state of the mouse buttons이다.

다음은 RAWHID 구조체이다.

typedef struct tagRAWHID {
    DWORD dwSizeHid;
    DWORD dwCount;
    BYTE bRawData;
} RAWHID, **LPRAWHID;

 - describes the format of the raw input from a Human Interface Device
dwCount는 bRawData에 포함된 HID input의 개수를 나타낸다.
bRawData의 사이즈는 dwSizeHid * dwCount 로 구할 수 있다.

RAWINPUTHEADER는 또 다음의 구조로 되어 있다.

typedef struct tagRAWINPUTHEADER {
    DWORD dwType;
    DWORD dwSize;
    HANDLE hDevice;
    WPARAM wParam;
} RAWINPUTHEADER, *PRAWINPUTHEADER;

dwType으로 input의 종류를 알 수 있다.
 - RIM_TYPEHID            //    키보드나 마우스는 제외된다.
 - RIM_TYPEKEYBOARD
 - RIM_TYPEMOUSE

dwSize는 input packet의 사이즈를 바이트로 나타낸다.
- This includes RAWINPUT plus possible extra input reports in the RAWHID variable length array

hDevice는 raw input data를 생성한 device의 핸들값이다.

wParam은 WM_INPUT의 wParam과 같다.


덧글

  • 몽몽이 2012/03/06 22:00 #

    좋은 정보 감사합니다
  • Kircheis 2012/03/07 09:38 #

    응?_?
    그새 누군가 방문하셨네...
※ 이 포스트는 더 이상 덧글을 남길 수 없습니다.



메모장