Войти
ПрограммированиеСтатьиОбщее

DirectInput: Все, что нужно знать...

Автор:

Привет!

Любая программа без пользовательского ввода превращается в статическую картинку, а то и в слайд шоу или кино. Но ведь мы смотрим не телевизор - даешь интерактивность! Именно эту проблему мы и будем сегодня обсуждать. Для нашего обсуждения я не стал сочинять чего-нибудь необычного - я воспользовался примерами из DirectX 7 SDK. Они достаточно хорошо иллюстрируют то, с чем мы сегодня столкнемся. Для начала обговорим условия компиляции и линковки приложений - кроме используемой нами DINPUT.LIB, при линковке тебе нужно будет включить в проект DXGUID.LIB для приложений, у которых не определена INITGUID (Использование INITGUID - старый стиль включения GUID номеров в приложение, использующее COM модель).

Ну, а для начала немного теории. Для чего нужны функции DirectInput, ты уже знаешь. Чем же DirectInput API лучше, чем стандартные возможности, предоставляемые приложению ядром? Ну, конечно своей скоростью и своими возможностями:

Получаемый формат данных:

  • Буферизированные (buffered) - использование буфера-очереди для сохранения там данных.
  • Непосредственные (immediate) - состояние устройства на момент запроса. Очень хорошо работает при опросе в приложении более 30-40 раз в секунду. При меньшей частоте возможны потери данных.
  • Схемы получения данных:

  • Опрос - опрос устройства через некоторые промежутки времени.
  • Оповещение - более подходит для многопоточных приложений или приложения ожидающего от пользователя нажатия на клавишу и не продолжающего выполнение пока он этого не сделает - опрашивающий поток блокируется до получения некоего сигнала.
  • Теперь тебе не обязательно зависеть от "оконной" процедуры и сообщений Windows - ты можешь в любом месте программы и любой промежуток времени прочитать данные с устройства.

    Рассмотрим схему инициализации для DirectInput. Поскольку DirectInput как и практически все компоненты DirectX построен на основе модели COM от Microsoft, общая структура работы с API ничем не будет отличаться от работы с другими компонентами DirectX API. Первое, что должно сделать приложение, использующее DirectInput API, это получить доступ к объекту DirectInput через соответствующий интерфейс. Сделать это можно вызовом DirectInputCreate() или DirectinputCreateEx(). Во всех примерах MS кроме одного используется первый вариант. Вариант два появился в версии DirectX 7.0. Рассмотрим параметры этих функций. Для этого ты можешь воспользоваться документацией.

    DirectInputCreate( HINSTANCE hInst, DWORD dwVer,
                         LPDIRECTINPUT *lplpDI, LPUNKNOWN pU);

    hInst - идентификатор экземпляра самого приложения получаемый в WinMain().
    dwVer - версия объекта DirectInput. Обычно подставляется макроопределение DIRECTINPUT_VERSION заранее определенное в DINPUT.H для установленной версии DirectX SDK.
    lplpDI - указатель на указатель интерфейса, который примет в себя информацию.
    pU - указатель наследования или агрегирования COM. Нас он не интересует - достаточно передать NULL.

    DirectInputCreateEx( HINSTANCE hInst, DWORD dwVer,REFIID ri,
                           LPVOID *ppO, LPUNKNOWN pU)

    hInst - то же, что и для варианта DirectInputCreate()
    dwVer - то же, что и для варианта DirectInputCreate()
    ri - идентификатор требуемого интерфейса. Может принимать значения IID_IDirectInput,IID_IDirectInput2,IID_IDirectInput7.
    ppO - указатель на указатель интерфейса, который примет в себя информацию. Аналогичен с последним параметром предыдущей функции.
    pU - указатель наследования или агрегирования COM. Аналогичен с последним параметром предыдущей функции.

    Как ты видишь — все достаточно просто. После получения указателя на интерфейс, нужно "создать" устройство от которого ты хочешь получать данные. Под устройством подразумевается все, что поддерживает DirectInput, например клавиатура, мышь, джойстики и т.п. Для создания устройств также существуют две функции - CreateDevice() и ее -Ex вариант (появился в 7 версии DirectX):

    CreateDevice(REFGUID rg,LPDIRECTINPUTDEVICE *lplpDI,LPUNKNOWN pU);

    rg - уникальный идентификатор устройства. Если ты еще не привык, что вся работа идет с некими Global Unique IDentifer'ами, советую поскорее привыкнуть ;). Для стандартных устройств заранее определены значения: GUID_SysKeyboard для клавиатуры; GUID_SysMouse для мыши. Остальные идентификаторы устройств (например джойстики) можно получить вызовом EnumDevices().
    lplpDI - указатель на указатель интерфейса, с помощью которого мы будем впоследствии работать с устройством.
    pU - все то же агрегирование.

    CreateDeviceEx(REFGUID rg,REFIID ri,LPVIOD *ppO,LPUNKNOWN pU);

    rg - то же, что и в первом случае.
    ri - идентификатор требуемого интерфейса. Может принимать значения IID_IDirectInput,IID_IDirectInput2,IID_IDirectInput7.
    ppO - указатель на указатель интерфейса, который примет в себя информацию. Аналогичен параметру lplpDI предыдущей функции.
    pU - указатель наследования или агрегирования COM. Аналогичен с последним параметром предыдущей функции.

    После того, как мы получили указатель на интерфейс устройства, воспользуемся им для установки формата данных для этого устройства. Функция SetDataFormat() получает всего один параметр - указатель на структуру описывающую формат данных для устройства. Для стандартных устройств заранее определены глобальные переменные: c_dfDIKeyboard, c_dfDIMouse, c_dfDIMouse2, c_dfDIJoystick, c_dfDIJoystick2. Итак - формат данных установлен - DirectInput знает тип устройства. Что же теперь? А теперь мы должны установить уровень доступа. Довольно важно определиться заранее (как и с форматом принимаемых данных) - какой доступ необходим.

    Уровни кооперации:

    * Эксклюзивный (exclusive)/Совместный (non-exclusive) - первое чем хорош эксклюзивный режим, это то, что он "подавляет" большинство системных комбинаций (кроме Alt+TAB и Alt+Ctrl+Del, кстати говоря - есть и отдельный флажок при установке уровня кооперации запрещающий системные комбинации Windows), позволяя обработать нажатие, скажем, Ctrl+Esc. Следует помнить, что ни одно приложение не сможет получить эксклюзивного доступа к устройству, если такой доступ уже был получен другим приложением, однако сможет получить совместный.
    Активный (foreground)/Фоновый (background) - при фоновом доступе приложение сможет читать данные с устройства даже когда окно приложения не активно. В случае же активного доступа приложение сможет читать данные, только если его окно обладает "фокусом" (активно).

    * Уровень доступа устройства устанавливается вызовом SetCooperativeLevel(). У функции два параметра: первый из них это идентификатор окна приложения, второй - комбинации флагов. И последнее - как и в случае с DirectDraw, когда приложение могло "потерять" память поверхностей - приложение DirectInput перед чтением данных с устройства должно "захватить"(Acquire) его. При чтении данных обязательно проверь результат - приложение может "уступить" (Unacquire) устройство, например, при потере фокуса приложением (Кстати, это происходит автоматически, если приложение имеет "активный" доступ к устройству). Для захвата и освобождения устройств существуют функции Acquire() и Unacquire() соответственно.

    Что еще полезно знать:

    * По умолчанию клавиатура использует непосредственные (не буферизированные) данные - для того, чтобы прочитать состояние клавиатуры нужно воспользоваться функцией GetDeviceState(). Параметрами для этой функции в случае непосредственных данных будет являться размер массива (равный 256) и указатель на массив из 256 байт для данных о всех клавишах. Для удобства работы для каждого индекса массива определено макроопределение с именем клавиши. Например, строка:

    char cDummy = cBuff[DIK_ESCAPE];

    занесет в сDummy байт данных по клавише Esc. Для определения нажата ли клавиша, достаточно проверить последний бит этого байта:

    if (cBuff[DIK_ESCAPE] & 0x80)
    {
      // Клавиша нажата!
    }

    Для того, что бы установить буферизированный ввод с клавиатуры, после установки уровня кооперации нужно установить размер буфера:

     
      DIPROPDWORD dipdw;
    
      dipdw.diph.dwSize = sizeof(DIPROPDWORD);
      dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
      dipdw.diph.dwObj = 0;
      dipdw.diph.dwHow = DIPH_DEVICE;
      dipdw.dwData = BUFFER_SIZE;
    
      hr = g_pKeyboard->SetProperty( DIPROP_BUFFERSIZE, &dipdw.diph );

    где BUFFER_SIZE - размер буфера.

    По умолчанию мышь возвращает относительные координаты, а джойстик абсолютные.
    По завершении приложения ты должен "убрать за собой" - "уступить" все устройства, и удалить объекты DirectInput вызовом Release().

    Это был лишь небольшой обзор, и в нем много чего было опущено (например, те же устройства с обратной связью (Force Feedback)). Не смотря на это, он должен был дать тебе достаточно информации для дальнейшего глубокого изучения.

    #DirectInput, #DirectX

    19 февраля 2002

    Комментарии [9]