netlib.narod.ru | < Назад | Оглавление | Далее > |
Microsoft проделала длинный путь, чтобы упростить работу с устройствами ввода. Она настолько проста, что фактически вы можете использовать один и тот же COM-интерфейс (IDirectInputDevice8) для взаимодействия практически с любым устройством ввода, которое вы себе можете представить (и, возможно, с теми, которые вы и представить не можете!). Шаги для создания и использования различных устройств ввода похожи, так что позвольте мне забежать вперед и показать, как это делается. В таблице 3.2 показаны общие этапы в правильном порядке вместе с интерфейсами DirectInput и функциями, которые данный этап реализуют.
Таблица 3.2. Этапы создания и использования устройства
Этап | Интерфейс/Функция |
1. Получение GUID устройства | IDirectInput8::EnumDevices |
2. Создание COM-объекта устройства | IDirectInput8::CreateDevice |
3. Установка формата данных | IDirectInputDevice8::SetDataFormat |
4. Установка уровня кооперации | IDirectInputDevice8::SetCooperativeLevel |
5. Установка различных специальных свойств | IDirectInputDevice8::SetProperty |
6. Захват устройства | IDirectInputDevice8::Acquire |
7. Опрос устройства | IDirectInputDevice8::Poll |
8. Чтение данных | IDirectInputDevice8::GetDeviceState |
Перед тем, как двигаться дальше, убедитесь, что вы объявили объект устройства DirectInput (IDirectInputDevice8):
IDirectInputDevice8 *pDIDevice;
У каждого установленного устройства есть GUID (Глобальный Уникальный Идентификатор) — присвоенное ему число. Чтобы использовать устройство надо сначала узнать его GUID. Проще всего это сделать для системной клавиатуры и мыши. DirectInput определяет их как GUID_SysKeyboard и GUID_SysMouse соответственно. Для всех других устройств вы должны сначала выполнить перечисление, чтобы найти то, которое вам требуется.
Перечисление устройств — это работа функции IDirectInput8::EnumDevices:
HRESULT IDirectInput8::EnumDevices( DWORD dwDevType, // тип искомых устройств LPDIENUMCALLBACK lpCallback, // функция обратного вызова для перечисления LPVOID pvRef, // указатель на пользовательский набор данных DWORD dwFlags); // флаги перечисления
#define INITGUIDВы должны определить INITGUID только один раз в вашем проекте (в одном файле с исходным кодом); если вы сделаете это несколько раз, то при компиляции возникнет ошибка.
Параметр dwDevType — это набор битовых полей, описывающих устройства каого типа будут перечисляться. Возможен диапазон от общих устройств ввода, таких как джойстики и мыши, до более специализированных, таких как трекболы и рукоятки управления. За полным списком устройств обратитесь к документации DX SDK, а в таблице 3.3. приведено несколько наиболее часто используемых значений.
Таблица 3.3. Типы перечисляемых устройств DirectInput
Значение | Описание |
DI8DEVCLASS_ALL | Все устройства |
DI8DEVCLASS_GAMECTRL | Все игровые контроллеры (джойстики) |
DI8DEVCLASS_KEYBOARD | Все клавиатуры |
DI8DEVCLASS_POINTER | Все указывающие устройства (мыши) |
DI8DEVCLASS_DEVICE | Все устройства, не относящиеся к трем предыдущим типам |
DI8DEVTYPE_MOUSE | Мыши и подобные им устройства, такие как трекболы |
DI8DEVTYPE_KEYBOARD | Клавиатуры и подобные им устройства |
DI8DEVTYPE_JOYSTICK | Джойстики и подобные им устройства, такие как рули |
DI8DEVTYPE_DEVICE | Устройства, которые не относятся к трем предыдущим типам |
DI8DEVTYPEMOUSE_TOUCHPAD | Сенсорные панели (подтип) |
DI8DEVTYPEMOUSE_TRACKBALL | Трекболы (подтип) |
Переменная lpCallback — это указатель на функцию перечисления, которая будет вызываться каждый раз, когда в системе обнаружено подходящее устройство. Я опишу ее чуть позже. В параметре pvRef вы задаете указатель на буфер, обычно это структура данных в которой вы храните информацию. При вызове функции перечисления ей будет передан этот указатель на предоставленную вами структуру (или на другие данные), что предоставляет возможность получить или установить данные.
Последний набор флагов, dwFlags, сообщает DirectInput как должно выполняться перечисление устройств. Вы можете указать в аргументе dwFlags любое значение из перечисленных в таблице 3.4.
Таблица 3.4. Флаги перечисления
Значение | Описание |
DIEDFL_ALLDEVICES | Перечисляем все установленные устройства (значение по умолчанию) |
DIEDFL_ATTACHEDONLY | Перечисляем только подключенные устройства |
DIEDFL_FORCEFEEDBACK | Перечисляем только устройства с обратной связью |
DIEDFL_INCLUDEALIASES | Включаем устройства, которые являются псевдонимами других устройств |
DIEDFL_INCLUDEPHANTOMS | Включаем фантомные устройства (заглушки) |
Ранее упомянутый указатель на функцию lpCallback должен указывать на определяемую пользователем функцию перечисления с типом DIEnumDeviceProc, которая должна соответствовать следующему прототипу:
BOOL CALLBACK DIEnumDevicesProc( LPDIDEVICEINSTANCE lpddi, // структура данных устройства LPVOID pvRef); // задаваемый пользователем указатель
Параметр lpddi — это указатель на структуру DIDEVICEINSTANCE, которая содержит информацию об обнаруженном в данном вызове устройстве. Вот как она выглядит:
typedef struct { DWORD dwSize; // Размер структуры GUID guidInstance; // GUID устройства GUID guidProduct; // Предоставляемый OEM GUID устройства DWORD dwDevType; // Тип устройства TCHAR tszInstanceName[MAX_PATH]; // Название устройства TCHAR tszProductName[MAX_PATH]; // Название продукта GUID guidFFDriver; // GUID драйвера обратной связи WORD wUsagePage; // Используемая страница для устройств HID WORD wUsage; // Используемый код для устройств HID } DIDEVICEINSTANCE;
Позвольте мне теперь отнять у вас несколько минут и построить пару функций, которые инициализируют DirectInput и перечисляют все устройства, отображая имена найденных устройств по одному в окне сообщений. При этом вы можете каждый раз выбрать, прекратить перечисление или продолжить, щелкая по кнопке Cancel или OK соответственно.
// Глобальный COM-объект DirectInput IDirectInput8 *g_pDI; // Прототипы функций BOOL InitDIAndEnumAllDevices(HWND hWnd, HINSTANCE hInst); BOOL CALLBACK EnumDevices(LPCDIDEVICEINSTANCE pdInst, LPVOID pvRef); BOOL InitDIAndEnumAllDevices(HWND hWnd, HINSTANCE hInst) { if(FAILED(DirectInput8Create(hInst, DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&g_pDI, NULL))) return FALSE; g_pDI->EnumDevices(DI8DEVCLASS_ALL, EnumDevices, (LPVOID)hWnd, DIEDFL_ALLDEVICES); return TRUE; } BOOL CALLBACK EnumDevices(LPCDIDEVICEINSTANCE pdInst, LPVOID pvRef) { int Result; // Отображаем окно сообщений с именем найденного устройства Result = MessageBox((HWND)pvRef, pdInst->tszInstanceName, "Device Found", MB_OKCANCEL); // Продолжаем перечисление, если нажата кнопка OK if(Result == IDOK) return DIENUM_CONTINUE; // Завершаем перечисление return DIENUM_STOP; }
Просто вставьте приведенный выше фрагмент кода в вашу собственную программу и вызовите функцию InitDIAndEnumAllDevices().
g_pDI->EnumDevices(DI8DEVTYPE_JOYSTICK, EnumDevices, (LPVOID)hWnd, DIEDFL_ATTACHEDONLY);
Теперь, когда вы знаете GUID устройства, можно создать COM-объект IDirectInputDevice8. Это работа функции IDirectInput8::CreateDevice:
HRESULT IDirectInput8::CreateDevice( REFGUID rguid, // GUID создаваемого устройства жестко // заданный или определенный при перечислении LPDIRECTINPUTDEVICE *lplpDirectInputDevice, // Указатель на // создаваемый объект LPUNKNOWN pUnkOuter); // NULL - не используется
Вот пример использования IDirectInput8::CreateDevice:
IDirectInputDevice8 *pDIDevice; HRESULT hr = g_pDI->CreateDevice(DeviceGUID, &pDIDevice, NULL);
Вот еще пример создания объекта системной клавиатуры с использованием предопределенного GUID:
IDirectInputDevice8 *pDIDevice; HRESULT hr = pDI->CreateDevice(GUID_SysKeyboard, &pDIDevice, NULL);
У каждого устройства есть собственный формат данных, используемый при чтении. В нем необходимо учесть множество вещей: клавиши, кнопки, оси и т.д. Чтобы ваша программа могла начать читать данные устройства, необходимо сообщить DirectInput этот формат.
Делается это через функцию IDirectInputDevice8::SetDataFormat:
HRESULT IDirectInputDevice8::SetDataFormat(LPCDIDATAFORMAT lpdf);
У функции SetDataFormat только один аргумент, являющийся указателем на структуру DIDATAFORMAT, определение которой выглядит так:
typedef struct { DWORD dwSize; // Размер структуры DWORD dwObjSize; // Размер структуры DIOBJECTDATAFORMAT DWORD dwFlags; // Флаг, определяющий работает ли устройство // в абсолютном режиме (DIDF_ABSAXIS) // или в относительном режиме (DIDF_RELAXIS) DWORD dwDataSize; // Размер получаемого от устройства // пакета данных (в двойных словах) DWORD dwNumObjs; // Количество объектов в массиве rgodf LPDIOBJECTDATAFORMAT rgodf; // Адрес массива структур // DIOBJECTDATAFORMAT } DIDATAFORMAT, *LPDIDATAFORMAT;
В большинстве случаев вам не придется заниматься инициализацией этой структуры, потому что DirectInput предоставляет вам для использования несколько предопределенных ее экземпляров, которые перечислены в таблице 3.5.
Таблица 3.5. Предустановленные структуры данных устройств DirectInput
Устройство | Пример структуры данных |
Клавиатура | c_dfDIKeyboard pDIDevice->SetDataFormat(&c_dfDIKeyboard); |
Мышь | c_dfDIMouse pDIDevice->SetDataFormat(&c_dfDIMouse); |
Джойстик | c_dfJoystick pDIDevice->SetDataFormat(&c_dfDIJoystick); |
Я не буду обсуждать специфику создания собственного формата данных, поскольку перечисленных в таблице 3.5 структур данных устройств вполне достаточно для этой книги. Если вы хотите создать собственную структуру формата данных устройства, обратитесь за информацией к документации DirectX SDK.
Взглянем в лицо фактам — каждая программа использует несколько устройств ввода. Почти все программы используют клавиатуру и мышь, а некоторые — еще и джойстик. Когда доходит до дела, вы можете использовать совместный с другими запущенными приложениями доступ к этим устройствам, либо по-хулигански полностью захватить доступ к устройству для своего приложения, не позволяя другим программам управлять им, пока вы не закончите свою работу.
Следующий этап в использовании устройства — установка уровня кооперации, за которую отвечает следующая функция:
HRESULT IDirectInputDevice8::SetCooperativeLevel( HWND hWnd, // Дескриптор родительского окна DWORD dwFlags); // Флаги, определяющие вид совместного доступа
В аргументе hWnd функции SetCooperativeLevel укажите дескриптор окна вашего приложения. Чтобы использовать доступ к устройству совместно с другими приложениями, укажите в аргументе dwFlags одно из перечисленных в таблице 3.6 значений, которые определяют вид совместного доступа.
Таблица 3.6. Уровни кооперации IDirectInputDevice8
Уровень | Описание |
DISCL_NONEXCLUSIVE | Использование данного параметра позволяет нескольким приложениям совместно использовать одно устройство ввода не влияя друг на друга |
DISCL_EXCLUSIVE | Этот параметр делает вашу программу захватчиком. При захвате устройства вы получаете единоличный доступ к нему, даже если другие установили эксклюзивный режим использования |
DISCL_FOREGROUND | Приложению требуется доступ при активности. Это означает, что для использования устройства программа должна быть активной. Если ваша программа становится неактивной, устройство будет автоматически освобождено, и вам надо будет восстановить контроль, когда программа вновь получит фокус |
DISCL_BACKGROUND | Вашей программе требуется фоновый доступ. Это значит, что программа может обращаться к устройству, даже когда она неактивна |
DISCL_NOWINKEY | Блокирует клавишу с логотипом Windows |
Когда вы задаете уровень кооперации, то должны указать либо флаг DISCL_EXCLUSIVE, либо флаг DISCL_NONEXCLUSIVE и скомбинировать его с флагом DISCL_BACKGROUND или DISCL_FOREGROUND. Я рекомендую для рассматриваемых устройств использовать комбинацию DISCL_FOREGROUND и DISCL_NONEXCLUSIVE:
pDIDevice->SetCooperativeLevel(hWnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);
Теперь вы можете установить любые специальные свойства для устройства, такие как режимы осей, буферизация или максимальные и минимальные значения диапазона. Для режима осей есть два варианта — относительный и абсолютный. Абсолютный режим сообщает координаты относительно центральной точки. Позиции левее или выше этой точки передаются как отрицательные числа, а позиции правее или ниже — как положительные.
Относительные координаты сообщают о перемещении относительно последней зафиксированной позиции. Например, если указатель мыши находился в точке с координатами 100, 40 и переместился на 5 единиц вправо, то DrectInput вернет значеие 5, а не 105. Ваша задача — использовать эти относительные перемещения так, как считаете нужным.
Что касается буферизации, то вы можете задать количество сохраняемых в буфере данных (если это необходимо). Это позволяет вам читать поступающие от устройства данные в своем собственном темпе и не беспокоиться о том, что они могут быть потеряны. Использовать буферизацию данных удобно, но это не то, в чем вы будете нуждаться при разработке игр. Предпочительнее знать состояние устройства ввода в данный момент времени, поэтому я не буду обсуждать буферизацию.
Последние интересные свойства — это минимальное и максимальное значение диапазона для устройства. Например, отклонение джойстка в крайнюю левую позицию приводит к передаче минимального значения, а при отклонении рукоятки в крайнюю правую позицию будет передано максимальное значение. Вы устанавливаете оба эти значения. Они влияют только на джойстики, так что именно их я буду использовать в качестве примера.
Установкой специальных свойств заведует функция IDirectInputDevice8::SetProperty:
HRESULT IDirectInputDevice8::SetProperty( REFGUID rguidProp, // GUID свойства LPCDIPROPHEADER pdiph); // DIPROPHEADER содержащий данные // об устанавливаемом свойстве
GUID, указаный в rguidProp, представляет свойство, которое вы хотите установить, такое как диапазон сообщаемых джойстиком значений или режим осей. Список GUID свойств приведен в таблице 3.7.
Таблица 3.7. GUID свойств
REFGUID | Описание |
DIPROP_AUTOCENTER | Указывает, является ли устройство самоцентрируемым |
DIPROP_AXISMODE | Задает обсуждавшийся ранее режим осей |
DIPROP_BUFFERSIZE | Определяет размер входного буфера |
DIPROP_CALIBRATIONMODE | Указывает должен ли DirectInput получать от устройства калиброванные данные. По умолчанию все данные калиброваны |
DIPROP_DEADZONE | Размер мертвой зоны, в которой не регистрируются изменения. Может изменяться от 0 (отсутствует) до 10000 (100 процентов) |
DIPROP_RANGE | Устанавливает минимальное и максимальное значение диапазона |
DIPROP_SATURATION | Значение, определяющее максимальный диапазон устройства. Может находиться в диапазоне от 0 до 10000. Например, значение 9500 означает, что устройство, такое как джойстик, отклоненное на 95 процентов заданного диапазона будет считаться полностью отклоненным |
Структура DIPROPHEADER, которая используется в вызове SetProperty, определена следующим образом:
typedef struct { DWORD dwSize; // Размер объемлющей структуры DWORD dwHeaderSize; // Размер данной структуры DWORD dwObj; // Параметр, который мы устанавливаем DWORD dwHow; // Как мы устанавливаем значение } DIPROPHEADER, *LPDIPROPHEADER
Поля dwSize и dwHeaderSize задают размеры структур в байтах, как описано в комментариях. Параметр dwObj может принимать множество различных значений, в зависимости от того, какое свойство мы устанавливаем. Например, чуть позже мы будем указывать значения DIJOFS_X и DIJOFS_Y, которые представляют ось X и ось Y джойстика, соответственно.
Для последнего параметра, dwHow, мы будем задавать значение DIPH_BYOFFSET. DirectX SDK по этому параметру определяет, что будет указано смещение в текущем формате данных для того объекта, к которому мы хотим получить доступ. Это значит, что DIOFS_X и DIOFS_Y — это смещения устанавливаемых значений в текущем формате данных.
Позднее в этой главе я покажу вам, как устанавливать свойства для джойстика.
Перед использованием любое устройство должно быть захвачено (acquired). Захват устройства обеспечивает вашей программе доступ к нему, независимо от того, использует ли ваша программа совместный с другими приложениями доступ, или получает полный контроль над устройством. Будьте бдительны — другие программы могут бороться за те же права и похитить у вас контроль. Чтобы исправить это вы должны заново захватить устройство для использования.
Как узнать, когда нужно захватывать устройство? Во-первых, всегда при создании интерфейса — вы должны выполнить захват перед его использованием. Во-вторых, в любое другое время, когда другая программа перехватывает контроль и DirectInput сообщает об этом вашей программе.
Захват устройства выполняет вызов IDirectInputDevice8::Acquire:
HRESULT IDirectInputDevice8::Acquire();
Когда вы закончите работу, необходимо отпустить устройство. Это работа функции IDirectInputDevice8::Unacquire:
HRESULT IDirectInputDevice8::Unacquire();
Опрос подготавливает устройство и, в ряде случаев, считывает для вас данные устройства, поскольку этот процесс может оказаться критичным по времени. Например, в случае джойстика, компьютер должен послать устройству импульс, чтобы чуть позже прочитать его данные. Это справедливо для некоторых джойстиков и не требуется для мышей и клавиатур.
Это не должно отвращать нас от использования опроса, поскольку с ним ядро кода будет более общим и сможет работать с любым устройством. Не беспокойтесь — опрос устройства, для которого он не требуется, не производит никакого эффекта. В конце концов, код будет более ясным.
Опрос устройства выполняется через IDirectInputDevice8::Poll:
HRESULT IDirectInputDevice8::Poll();
Наконец! Вы достигли цели — завершающего чтения данных устройства, которое выполняет функция IDirectInputDevice8::GetDeviceState. Вы должны передать этой функции буфер данных, в котором будет сохранена информация устройства для использования в вашей программе. Как вы вскоре увидите, у разных устройств эти данные отличаются.
Вот прототип функции:
HRESULT IDirectInputDevice8::GetDeviceState( DWORD cbData, // Размер буфера для хранения данных устройства LPVOID lpvData); // Указатель на буфер для хранения данных устройства
Независимо от устройства, для чтения данных применяется приведенный ниже фрагмент кода. Он учитывает, что вы можете потерять фокус устройства и его потребуется снова захватить. Вы должны передать указатель на буфер, размер которого достаточно большой для сохранения данных устройства, а также количество считываемых данных. В следующих разделах («DirectInput и клавиатура», «DirectInput и мышь» и «DirectInput и джойстик») я покажу вам, как использовать приведенную ниже функцию для получения данных каждого из упомянутых устройств.
BOOL ReadDevice(IDirectInputDevice8 *pDIDevice, void *DataBuffer, long BufferSize) { HRESULT hr; while(1) { // Опрос устройства g_pDIDevice->Poll(); // Чтение состояния if(SUCCEEDED(hr = g_pDIDevice->GetDeviceState(BufferSize, (LPVOID)DataBuffer))) break; // Возвращаем нераспознанную ошибку if(hr != DIERR_INPUTLOST && hr != DIERR_NOTACQUIRED) return FALSE; // Вновь захватываем устойство и пытаемся // получить данные еще раз if(FAILED(g_pDIDevice->Acquire())) return FALSE; } // Сообщаем об успехе return TRUE; }
netlib.narod.ru | < Назад | Оглавление | Далее > |