DirectInput学习笔记
组件 用 途
DirectDraw 高速2D图象
DirectSound 短响应时间声音输出
Direct3D 高速3D图象
DirectInput 面向游戏的对游戏杆和其它输入设备的访问
DirectSetup 方便的安装DirectX组件
DirectPlay 面向游戏的通信和网络支持
DirectShow 视频流支持
DirectAnimation 动画录放支持
DirectX是一套可以分别使用的组件,不同组件互相没有联系,它们仅仅是具有相同的设计风格和目标。使用DirectX组件的程序是基于Win32的程序,它们使用普通Win32 API集,并且可以访问所有可以获得的操作系统工具。实际上,DirectX既可以用于GUI程序,也可以用于控制台程序。可以直接用Petzold-style SDK编程开发程序,也可以用基本类库,如MFC。总的说,唯一的要求是大多数DirectX组件在程序中需要HWND,所以至少要有一个窗口。
剖析DirectInput
DirectInput由三个对象组成:DirectInput, DirectInputDevice, 和DirectInputEffect。DirectInput是一个高层的对象,通过DirectInput对象可以对相关的输入设备进行基本的初始化和查找。DirectInput对象最终用来创建低层的DirectInputDevice对象。DirectX中的每个主要组件都采用相同的方法,首先创建高层对象,如DirectInput或DirectSound对象,然后创建低层对象与硬件进行实际的通信。
对象 说明
DirectInput 封装高层DirectInput功能,列举设备并用来创建DirectInputDevice对象。
DirectInputDevice 与物理输入设备的接口,例如游戏杆,包括收集和设置设备状态信息的接口,并且用来创建DirectInputEffect对象 (对于力反馈设备)。
DirectInputEffect 封装能够在力反馈设备上“播放”的简单效果,提供启动、停止和设置力反馈效果等功能。
DirectInput对象是三个对象中最容易理解的。实际上,它在一个接口形式IDirectInput中只提供五个函数。这是DirectInput的一个非常重要的部分,因为这是出发点。
成员函数 说明
CreateDevice 创建一个DirectInputDevice对象并返回一个指向其IdirectInputDevice接口的指针。
EnumDevices 为找到的与给定标准匹配的每个设备调用一个回调函数,每个回调函数提供一个GUID,可以用在CreateDevice中创建DirectInputDevice对象。
GetDeviceStatus 测试物理设备是否连接到系统。
Initialize 如果DirectInput对象是使用CoCreateInstance创建的,那么在使用前必须调用Initialize成员。如果DirectInput对象是使用DirectInputCreate创建的,那么就已经初始化过了。
RunControlPanel 为设备运行Windows Control Panel程序,让用户安装新设备或者更改已有设备的配置。游戏杆校准可以在此处做。
使用DI的步骤:
第一步:创建DirectInput对象
创建DI又有两种方法:A. DirectInputCreate:DINPUT.H
参数列表:HRESULT WINAPI DirectInputCreate(
HINSTANCE hinst,
DWORD dwVersion,
LPDIRECTINPUT * lplpDirectInput,
LPUNKNOWN punkOuter
);
参数说明:第一个参数是应用程序的实例。第二个参数是程序需要的DirectInput版本,通常使用DIRECTINPUT_VERSION宏,定义为当前版本。第三个参数最重要,它是指向IdirectInput接口的指针的地址。程序中应该定义一个LPDIRECTINPUT类型的变量(可以是全局的)并将其地址作为第三个参数传递给DirectInputCreate。最后一个参数叫作punkOuter,与COM技术中的聚合有关,可以用NULL安全的忽略。
返回值: HRESULT,是COM的标准返回类型,可以将返回值与可能的返回值比较,也可以使用COM宏定义SUCCESS或FAILED来检查。
说明:使用DirectInputCreate能够容易地创建高层对象并得到其主接口指针。这是DirectX的又一个设计方法,每个DirectX组件都提供助手函数来创建高层对象,例如DirectInputCreate或DirectDrawCreate。在程序中可以用这些助手函数创建DirectX对象,然而,这些函数实际上创建的是COM对象。
第二种方法:B. CoCreateInstance:标准Win32 API函数
参数列表:STDAPI CoCreateInstance(
REFCLSID rclsid,
LPUNKNOWN pUnkOuter,
DWORD dwClsContext,
REFIID riid,
LPVOID * ppv
);
参数说明:第一个参数是要创建对象的GUID,DirectX定义的GUID是叫作CLSID_DirectInput的GUID结构变量。第二个参数是熟悉的pUnkOuter,同样可以用NULL忽略。第三个参数dwClsContext定义COM对象在何处创建,DirectX只支持进程内服务器,所以必须使用CLSCTX_INPROC_SERVER。第四个参数是请求对象所支持的任何接口,方法是使用为接口预定义的GUID。最后一个参数是程序中接口指针变量的实际地址。
说明:COM对象对外提供接口,与对象本身一样,接口也用GUID识别。使用第一种方法,不能选择得到的接口,总是得到IdirectInput。使用CoCreateInstance可以请求对象所支持的任何接口,方法是使用为接口预定义的GUID。但是在DirectInput这是没有意义的,因为DirectInput对象的唯一有用的接口就是IdirectInput。其它DirectX组件支持多个有用的接口。(例如,DirectDraw对象可以用IdirectDraw或IDirectDraw2接口操作。)
在Win32中用CoCreateInstance创建COM对象非常普遍。如果程序中已经使用CoCreateInstance创建了其他COM对象,开发者可能就会希望也用它来创建DirectX对象。因为COM对象在安装时就在系统中注册过,所以唯一需要知道的就是对象的GUID,用它来创建一个实例。创建DirectX对象需要的全部GUID都在头文件中声明,并在库文件DXGUID.LIB中定义。可以将一个预定义的GUID传递给CoCreateInstance,让Windows为你创建对象。
CoCreateInstance方法还需要另外一步:必须要首先调用一个接口函数初始化对象。DirectInputCreate提供的是一个已经初始化过的DirectInput对象,但CoCreateInstance没有特定于DirectInput的认识,因此必须调用IdirectInput接口的初始化成员函数。假设定义IdirectInput接口指针变量:LPDIRECTINPUT g_lpDI 可调用初始化函数:
g_lpDI->Initialize( hInstance, DIRECTINPUT_VERSION);
既然选择采取这种标准方法创建对象,就不得不注意COM需要的其他标准,例如需要调用CoInitialize和CoUninitialize。
第二步:列举设备,获得游戏杆GUID
EnumDevices:Windows中列举API
以下定义的值可以传递给EnumDevices来选择列举哪种类型的输入设备。另外也支持子类型,见SDK中DIDEVICEINSTANCE结构的文档。
值 说明
DIDEVTYPE_MOUSE 列举鼠标设备 (标准、轨迹球等)
DIDEVTYPE_KEYBOARD 列举键盘设备 (标准、键区等)
DIDEVTYPE_JOYSTICK 列举游戏杆设备 (操纵杆、操纵轮、方向舵等)
DIDEVTYPE_DEVICE 列举其它设备
参数列表:HRESULT EnumDevices(
DWORD dwDevType,
LPDIENUMCALLBACK lpCallback,
LPVOID pvRef,
DWORD dwFlags
);
参数说明:第二个参数是一个回调函数。第三个参数是程序中定义的32位值。第一个参数是想要列举的设备类型,对游戏杆来说,是DIDEVTYPE_JOYSTICK。最后一个参数是详细描述想要列举的设备的标志。现在支持的标志是DIEDFL_ATTACHEDONLY和DIEDFL_ALLDEVICES(这两个标志是互斥独占的),此外还有DIEDFL_FORCEFEEDBACK,此标志表示力反馈设备,能够和另两个标志位或操作。
返回值:HRESULT
说明: 原因是在创建DirectInputDevice对象前需要该设备的GUID,该函数就是得到需要的GUID,Windows没有对游戏杆的预定义GUID。在Windows中,通常都有系统键盘和系统鼠标,另一方面,系统本身并不使用游戏杆。可以安装一个或者多个游戏杆,但系统管理的范围只限于驱动程序级,系统为这些设备指定特殊的系统状态。因此,为游戏杆定义GUID对DirectInput来说是不合理的。
列举系统设备和性能在DirectX中相当普遍。要列举系统中的输入设备,需要使用EnumDevices函数。EnumDevices是IdirectInput接口的一部分,与Windows中其它列举API相同,例如EnumWindows。当EnumDevices列举系统中的输入设备时,反复地调用回调函数。
第三步:设备回调函数
CALLBACK EnumProc:每个设备的回调函数
参数列表:BOOL CALLBACK EnumProc(
LPCDIDEVICEINSTANCE lpddi,
LPVOID pvRef
) ;
参数说明:回调函数接受两个参数。第二个参数是程序定义的传递给EnumDevices的32位值。更重要的是,第一个参数传递指向一个结构的指针,该结构包含关于能够与列举标准匹配的单个设备的许多信息。这是一个DIDEVICEINSTANCE结构。此结构中最重要的一条信息是设备的GUID,保存在结构的guidInstance成员中。
返回值:BOOL
说明:因为回调函数是由用户程序定义并传递给EnumDevices的,所以是调用CreateDevice的最合适地方,直到创建了满足需要的足够DirectInputDevice对象为止。但是回调函数并非一定要如此实现,可以简单的将列举设备的所有GUID保存在一个表中,在以后的代码中使用。
第四步:创建DirectInputDevice对象
CreateDevice:IdirectInput接口五个函数之一
参数列表:HRESULT CreateDevice(
REFGUID rguid,
LPDIRECTINPUTDEVICE *lplpDirectInputDevice,
LPUNKNOWN pUnkOuter
);
参数说明:第一个参数是待创建设备的GUID;第二个参数是设备变量,可以为全局变量;最后一个可以设为NULL。
返回值:HRESULT
说明:DirectInput库为创建DirectInputDevice对象预定义了两个GUID:GUID_SysKeyboard和GUID_SysMouse。将两者之一直接传递给CreateDevice函数,就会得到相应设备的DirectInputDevice对象。而对于游戏杆则没有定义GUID,得到游戏杆GUID的方法由上面的列举函数得到。
调用后能得到一个设备接口对象g_lpdid。
第五步:设置设备的数据格式和协作级别
SetDataFormat:g_lpdid的接口成员函数
参数列表:g_lpdid->SetDataFormat( &c_dfDIJoystick ) ;
参数说明:g_lpdid是指向IdirectInputDevice接口的指针。唯一参数是指向结构DIDATAFORMAT的指针。
返回值:
说明:设置数据格式包括无数可能的决定,包括轴信息、相对或绝对坐标信息、等等。所有这些细节通过一个叫作DIDATAFORMAT的结构传递给此函数。
几个DIDATAFORMAT结构变量,可以用于比较普通的输入设备:c_dfDIKeyboard, c_dfDIMouse, c_dfDIJoystick, 和c_dfDIJoystick2。为普通的力反馈游戏杆设置数据格式,可以使用c_dfDIJoystick。
SetCooperativeLevel:接口成员函数
参数列表:HRESULT SetCooperativeLevel(
HWND hwnd,
DWORD dwFlags
);
参数说明:hwnd是程序的主窗口。标志是下面一些值的或操作的结合: DISCL_BACKGROUND, DISCL_FOREGROUND, DISCL_EXCLUSIVE, DISCL_ NONEXCLUSIVE。
返回值:
说明:这个函数很重要,因为它定义了程序操纵与系统中其它进程有关的硬件的控制级别。同其它DirectX对象一样,只有设置了协作级别才能使DirectInputDevice对象工作。
如果标志参数中或上了DISCL_EXCLUSIVE,则当获得设备后本程序就成为唯一允许访问该物理设备的进程。另一方面,如果选择了DISCL_NONEXCLUSIVE,那么系统中可以有多个进程同时协作获得和使用该设备。如果或上了DISCL_BACKGROUND,程序将不会失去物理设备。然而,象Ctrl+Alt+Del组合键被按下这样的系统事件仍然能够隐含地“unacquire”程序中的设备。如果使用了DISCL_ FOREGROUND,当不是活动窗口时,程序将会自动释放物理设备。这就是将程序主窗口句柄传递给SetCooperativeLevel的意义。DirectX根据窗口是否是系统当前活动窗口自动调整设备共享。
正式发布的产品中应该使用DISCL_FOREGROUND | DISCL_EXCLUSIVE,而在调试版本中应该使用DISCL_BACKGROUND|DISCL_EXCLUSIVE。但是也不总是这样选择。如果设备是系统键盘,那么DirectInputDevice想独占使用而调用SetCooperativeLevel将会失败。这是因为操作系统想要允许用户自由地从一个程序切换到另一个程序。类似的,DirectInputDevice不会允许以协作级别DISCL_BACKGROUND|DISCL_EXCLUSIVE请求系统鼠标。Windows不希望一个程序能够完全将用户与操作系统的联系切断。
使用实例:如果力反馈游戏杆的协作模式是DISCL_FOREGROUND | DISCL_EXCLUSIVE,那么只要程序处于活动状态,就能够从游戏杆读数据并播放力反馈效果(力反馈需要exclusive-level协作)。只要用户选择其它程序,程序就失去对物理设备的控制,新激活的程序就能够访问该设备。这意味着在调试程序时,如果切换到调试器窗口,程序就会因为窗口变为非活动的而失去对游戏杆的控制。
如果将同一游戏杆的协作级别设为DISCL_BACKGROUND | DISCL_EXCLUSIVE,程序将会所有时间都能访问游戏杆,不管窗口的状态。但是现在系统中其它进程就不能获得游戏杆,除非程序释放了游戏杆。
如果设置协作级别时使用DISCL_FOREGROUND标志,那么程序的主窗口不再是系统中的活动窗口时设备将被明确释放。这就是说,在程序调用Acquire和实际试图从设备读取信息之间,能够失去对设备的占有。所以需要检查返回值来捕捉这样的错误,并准备好在任何时间重新获得该设备。
第六步:Acquire函数和Unacquire函数
Acquire函数:此函数是为了获得对物理设备的实际访问(不要和逻辑上的DirectInputDevice对象混了)。
Unacquire函数:释放对物理设备的访问。
Acquire和Unacquire的要点:在能够从物理设备读取信息或向物理设备发送信息之前,必须要用Acquire获得设备。当程序获得独占协作级别的设备时,DirectX拥有该设备。例如,如果鼠标被DirectX(独占)获得,那么程序窗口中的按钮就不会对鼠标做出响应。这就是说,如果想让Windows对设备响应,就应该释放该设备。
获得了设备后,接着就应该开始使用GetDeviceState函数轮流检测输入的数据。当完成与设备对象的操作后,调用Unacquire释放DirectInputDevice对象。换句话说,如果不想让DirectInput从设备中读取数据,就调用Unacquire。但Unacquire并不是失去设备控制的唯一方法,关于这一点上面协作级别函数有说明。
辅助说明:当程序中完全完成DirectInput有关的工作后,就应该调用IdirectInput接口的Release成员。这就告诉DirectInput对象可以释放自己了。在DirectX中,最好养成释放对象的习惯,从低层对象开始,到高层对象结束。正常情况下程序会作为清除或者关闭的例行公事的一部分调用Release。这是使用每个DirectX组件的必要步骤,也是使用每个COM组件的必要步骤。
第七步:检测输入数据
键盘GetDeviceState:IdirectInputDevice接口成员函数
参数列表:GetDeviceState(
256,(LPVOID)
cKeyboardData
) ;
参数说明:在设置完数据格式、协作级别、获得设备以后,就可以读取键盘状态了。GetDeviceState用关于物理设备的状态信息组装一个结构,所组装结构的类型由前面对SetDataFormat的调用决定。对键盘来说,此数据结构是一个简单的256个字节组成的数组。每个字节对应于键盘上的一个键,如果某个键按下,相应字节的高位就被设置。
DirectInput定义了一套以DIK_XXX为前缀的常量,这些常量可以用来索引字节数组以找到关于特定键的数据。例如,如果要检查右Shif键当前是否按下,可以使用DIK_RSHIFT定义:
if(cKeyboardData[DIK_ RSHIFT]&0x80)
DoSomeThing…
CKeyboardData是256个字节的缓冲区。
返回值:
说明:不管GetDeviceState在何时返回DIERR_INPUTLOST,就必须使用Acquire获得设备。还有一点很重要,就是能够请求DirectInput缓冲键盘信息。这要求提供一个缓冲区并使用SetProperty为设备设置缓冲区大小。这一技术在程序不能相当频繁的检查键盘状态时非常有用。用户有可能在程序中两次GetDeviceState调用之间按下又松开了一个键,如果DirectInput不缓冲键盘数据的化,这种击键动作就丢失了。
游戏杆GetDeviceState:IdirectInputDevice接口成员函数
参数列表:
参数说明:
返回值:
说明:将CreateDevice返回的接口升级到IDirectInputDevice2,可以使用QueryInterface调用请求CreateDevice返回新的接口:
hr = lpDIDeviceJoystickTemp->QueryInterface(
IID_IDirectInputDevice2,
(void **) &g_lpDIDeviceJoystick
);
如果成功,就可以释放原来的接口,开始使用新IDirectInputDevice2接口。IDirectInputDevice2接口提供IdirectInputDevice的所有功能,而且还有另外两个重要特性:支持查询设备和支持力反馈设备。其次,需要设置上的一些考虑。还记得SetDataFormat定义了GetDeviceState返回的数据的类型。对于游戏杆设备,使用c_dfDIJoystick或c_dfDIJoystick2两个预定义变量之一,将返回数据的类型设置为DIJOYSTATE或DIJOYSTATE2结构。选择哪种主要取决于要使用游戏杆哪种类型的特性。浏览这些结构中的成员应该对弄清这个问题有帮助。
游戏杆程序应该检查以确保控制的设备能满足要求。可以并且应该调用IdirectInputDevice接口的成员函数GetCapabilities探测。DirectX为多种设备提供广泛的支持,软件开发环境和使用环境可能有很大差别,不同的计算机支持不同水平的DirectX功能。编写好使用DirectX的软件,需要检查硬件的能力。最差的情况下,如果某个功能不支持,可以退出程序。最好的情况当然是程序能够聪明地根据缺少的特性调整本身的需求。
第八步:设置游戏杆属性
SetProperty:设置设备的一个特性。
参数列表:g_lpDIDeviceJoystick->SetProperty(
DIPROP_RANGE,
&dipRange.diph
) ;
参数说明:GUID和指向结构中DIPROPHEADER部分的指针。
返回值:
说明:在开始从设备得到输入之前,需要设置设备的特性。这些特性包括象返回值的范围、游戏杆的中心点等此类的细节。首先,必须使用关于要改变的设置的一些信息填写一个数据结构。每个结构都以一个DIPROPHEADER结构开始,此结构中填写描述要改变的设置的信息。然后,用特定于所改变的设置的数据填写结构中剩余的部分。
参数是下面的代码片段将游戏杆的垂直范围设置为-100到100:
DIPROPRANGE dipRange ;
dipRange.diph.dwSize = sizeof(dipRange);
dipRange.diph.dwHeaderSize = sizeof(dipRange.diph);
dipRange.diph.dwObj = DIJOFS_Y;
dipRange.diph.dwHow = DIPH_BYOFFSET;
dipRange.lMin = -100;
dipRange.lMax = +100;
此结构中最难懂的部分是diph.dwObj和diph.dwHow。diph.dwHow描述diph.dwObj中保存何种信息。diph.dwObj实际描述哪个属性被设置。大多数情况下,diph.dwHow的值是DIPH_BYOFFSET,diph.dwObj的值是传递给SetDataFormat的结构中一个预定义的偏移。
应该指出能够列举设备的对象,包括按钮和其它特点。这一工作由EnumObjects函数完成。这样做时,应该提供一个对象标志符。将此标志符传递给diph.dwObj成员,将diph.dwHow成员填写为DIPH_BYID。
在从设备读取数据之前,至少要为设备的X和Y坐标轴设置最小和最大值。设置好设备属性后,就可以获得设备并开始从设备获得数据。从游戏杆获取数据与从键盘或鼠标获取数据不同,因为游戏杆是查询设备。
键盘和鼠标会引发硬件中断,由系统中的驱动程序处理,并用来更新通过调用GetDeviceState由DirectInput返回的数据。查询设备(如大多数游戏杆)不产生硬件中断,因此,DirectInput必须被告知从设备获取状态信息。这一工作通过调用IDirectInputDevice2接口的Poll成员函数完成。此时也是检查设备是否需要重新获得的适当时机。设备被成功查询后,就可以调用GetDeviceState获取状态信息。
如果调用SetDataFormat时使用c_dfDIJoystick变量,那么GetDeviceState将用游戏杆当前的状态信息填充一个DIJOYSTATE结构。此结构的内容主要取决于物理设备的特性和SetProperty的设置。为了从游戏杆设备中获取数据,程序应该定期查询设备。
浙公网安备 33010602011771号