AutoCAD 3DMAX C语言 Pro/E UG JAVA编程 PHP编程 Maya动画 Matlab应用 Android
Photoshop Word Excel flash VB编程 VC编程 Coreldraw SolidWorks A Designer Unity3D
 首页 > VC编程

DirectX揭密

51自学网 2015-08-30 http://www.51zixue.net

 

现在已经用CreateDevice成员函数获得了DirectInputDevice对象的一个接口,为开始处
理与系统连接的实际物理设备做好了准备。
使用DirectInputDevice对象
DirectInputDevice对象的每个实例都与系统中的特定设备相关。此对象提供了对系统硬件
更多的控制和能力,从而使DirectX的允诺实现。下面讨论拥有了DirectInputDevice对象
后下一步干什么。

拥有了IdirectInputDevice接口的一个接口指针,现在干什么?首先,设置设备的数据格
式。通过调用SetDataFormat来完成,该函数是一个接口成员函数。设置数据格式包括无数
可能的决定,包括轴信息、相对或绝对坐标信息、等等。所有这些细节通过一个叫作
DIDATAFORMAT的结构传递给此函数。实际上,SetDataFormat唯一的参数就是指向此结构的
指针。

填写这个结构的细节会使人发憷。值得感谢的是这一工作并不是必须的,因为DirectInput
已经定义了几个DIDATAFORMAT结构变量,可以用于比较普通的输入设备:c_dfDIKeyboard,
c_dfDIMouse, c_dfDIJoystick, 和c_dfDIJoystick2。为普通的力反馈游戏杆设置数据格
式,可以使用下面的调用形式:

lpdid->SetDataFormat( &c_dfDIJoystick ) ;
在此例中,lpdid是指向IdirectInputDevice接口的指针。

设置完设备对象的数据格式后,就需要设置设备的协作级别。因为协作级别在整个DirectX
中很常见,所以这里要做一下说明。大多数直接处理系统硬件的DirectX对象在接口的成员
中都有一个叫作SetCooperativeLevel函数。这个函数很重要,因为它定义了程序操纵与系
统中其它进程有关的硬件的控制级别。同其它DirectX对象一样,只有设置了协作级别才能
使DirectInputDevice对象工作。要理解协作级别,就需要熟悉Acquire函数。调用此函数
是为了获得对物理设备的实际访问(不要和逻辑上的DirectInputDevice对象混了)。相反
的,Unacquire函数释放对物理设备的访问。

下面是函数SetCooperativeLevel的定义:

HRESULT SetCooperativeLevel(
HWND hwnd,
DWORD dwFlags
);

hwnd是程序的主窗口。标志是下面一些值的或操作的结合: DISCL_BACKGROUND,
DISCL_FOREGROUND, DISCL_EXCLUSIVE, DISCL_ NONEXCLUSIVE。

如果标志参数中或上了DISCL_EXCLUSIVE,则当获得设备后本程序就成为唯一允许访问该物
理设备的进程。另一方面,如果选择了DISCL_NONEXCLUSIVE,那么系统中可以有多个进程
同时协作获得和使用该设备。如果或上了DISCL_BACKGROUND,程序将不会失去物理设备。
然而,象Ctrl+Alt+Del组合键被按下这样的系统事件仍然能够隐含地"unacquire"程序中
的设备。如果使用了DISCL_ FOREGROUND,当不是活动窗口时,程序将会自动释放物理设备。
这就是将程序主窗口句柄传递给SetCooperativeLevel的意义。DirectX根据窗口是否是系
统当前活动窗口自动调整设备共享。

那么所有这些值的意义是什么呢?下面举个例子说明。如果力反馈游戏杆的协作模式是
DISCL_FOREGROUND | DISCL_EXCLUSIVE,那么只要程序处于活动状态,就能够从游戏杆读数
据并播放力反馈效果(力反馈需要exclusive-level协作)。只要用户一选择其它程序,程
序就失去对物理设备的控制,新激活的程序就能够访问该设备。这意味着在调试程序时,如
果切换到调试器窗口,程序就会因为窗口变为非活动的而失去对游戏杆的控制。

如果将同一游戏杆的协作级别设为DISCL_BACKGROUND | DISCL_EXCLUSIVE将会是什么情况
呢?程序将会所有时间都能访问游戏杆,不管窗口的状态。但是现在系统中其它进程就不能
获得游戏杆,除非程序释放了游戏杆,不管用户在做什么!

非常明显,在正式发布的产品中应该使用DISCL_FOREGROUND | DISCL_EXCLUSIVE,而在调
试版本中应该使用DISCL_BACKGROUND|DISCL_EXCLUSIVE。但是也不总是这样选择。例如,
如果设备是系统键盘,那么DirectInputDevice想独占使用而调用SetCooperativeLevel将
会失败。这是因为操作系统想要允许用户自由地从一个程序切换到另一个程序。类似的,
DirectInputDevice不会允许以协作级别DISCL_BACKGROUND|DISCL_EXCLUSIVE请求系统鼠
标。Windows不希望一个程序能够完全将用户与操作系统的联系切断。

在能够从物理设备读取信息或向物理设备发送信息之前,必须要用Acquire获得设备。在临
时或永久结束设备使用时要明确地使用Unacquire函数释放设备。但Unacquire并不是失去
设备控制的唯一方法。

如果设置协作级别时使用DISCL_FOREGROUND标志,那么程序的主窗口不再是系统中的活动
窗口时设备将被明确释放。这就是说,在程序调用Acquire和实际试图从设备读取信息之间,
能够失去对设备的占有。所以需要检查返回值来捕捉这样的错误,并准备好在任何时间重新
获得该设备。

关于Acquire和Unacquire的决定性要点:当程序获得独占协作级别的设备时,DirectX拥
有该设备。例如,如果鼠标被DirectX(独占)获得,那么程序窗口中的按钮就不会对鼠标
做出响应。这就是说,如果想让Windows对设备响应,就应该释放该设备。换句话说,如果
不想让DirectInput从设备中读取数据,就调用Unacquire。

设置完设备的协作级别后,接着应该为设备配置其它设置。获得了设备后,接着就应该开始
使用GetDeviceState函数轮流检测输入的数据。当完成与设备对象的操作后,调用Unacquire
释放DirectInputDevice对象。设备与设备之间存在细节上的差别;下面讲解游戏杆和键盘,
应该能为从其它设备读取输入提供足够的基础知识。
键盘
键盘是到目前为止最容易读取的设备。实际上,设置完数据格式、协作级别、获得设备以后,
就可以读取键盘状态了。读取键盘状态要使用IdirectInputDevice接口的GetDeviceState
成员。GetDeviceState用关于物理设备的状态信息组装一个结构,所组装结构的类型由前
面对SetDataFormat的调用决定。对键盘来说,此数据结构是一个简单的256个字节组成的
数组。每个字节对应于键盘上的一个键,如果某个键按下,相应字节的高位就被设置。

DirectInput定义了一套以DIK_XXX为前缀的常量,这些常量可以用来索引字节数组以找到
关于特定键的数据。例如,如果要检查右Shif键当前是否按下,可以使用DIK_RSHIFT定义:

GetDeviceState(256,(LPVOID) cKeyboardData) ;
if(cKeyboardData[DIK_ RSHIFT]&0x80)
DoWhatever() ;

CKeyboardData是256个字节的缓冲区。几乎就是这么简单,但是要记住,不管GetDeviceState
在何时返回DIERR_INPUTLOST,就必须使用Acquire获得设备。这种情况发生在每次用户从
程序切换离开的时候。

还有一点很重要,就是能够请求DirectInput缓冲键盘信息。这要求提供一个缓冲区并使用
SetProperty为设备设置缓冲区大小。在本文中没有篇幅讨论这一技术,但这一技术在程序
不能相当频繁的检查键盘状态时非常有用。用户有可能在程序中两次GetDeviceState调用
之间按下又松开了一个键,如果DirectInput不缓冲键盘数据的化,这种击键动作就丢失了。

游戏杆
游戏杆非常好玩。与其好听的名称(Joystick--原意为欢乐杆)相符,这种设备为游戏体
验添加了许多乐趣,同时也为程序员的体验添加了一些东西。正常情况下,通过调用
IdirectInput接口的CreateDevice成员得到IdirectInputDevice接口(和对象),这对游
戏杆也适用。

但是开发人员都希望立即将接口升级到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功能。编
写好使用DirectX的软件,需要检查硬件的能力。最差的情况下,如果某个功能不支持,可
以退出程序。最好的情况当然是程序能够聪明地根据缺少的特性调整本身的需求。

在开始从设备得到输入之前,需要设置设备的特性。这些特性包括象返回值的范围、游戏杆
的中心点等此类的细节。这一工作由函数SetProperty完成,相当复杂。

SetProperty设置设备的一个特性。首先,必须使用关于要改变的设置的一些信息填写一个
数据结构。请参考Platform SDK中的文档,得到所有数据结构。每个结构都以一个
DIPROPHEADER结构开始,此结构中填写描述要改变的设置的信息。然后,用特定于所改变
的设置的数据填写结构中剩余的部分。最后,调用SetProperty,参数是GUID和指向结构
中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;
g_lpDIDeviceJoystick->SetProperty( DIPROP_RANGE, &dipRange.diph) ;

此结构中最难懂的部分是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的设置。例如,如果结构中的lY成员等于-50,并且Y轴的范围设置为-100到
100,那么就是说游戏杆在垂直方向上处于中心和最顶端的中间。程序中应该确保设备的范
围设置为能合理满足需求的值。为了从游戏杆设备中获取数据,程序应该定期查询设备。
使用DirectInputEffect
首先,应该解释一些力反馈技术。力反馈设备是能够产生用户可以感觉到的力的设备,这些
力叫作效果,例如颠簸效果或者持续的将操纵杆推向右上方的力。这些效果是“播放
”出来的,效果由程序控制播放,或者对函数调用响应,或者对用户按键自动反应。

DirectInput目前支持大约一打不同的效果类型(见表5)。这些效果的范围从完全由程序控
制的低级持续力效果,到由DirectInput或设备自己控制的高级倾斜或波动效果。效果有四
种基本类型:持续力、倾斜效果、周期效果和条件。持续力是单一方向上不改变强度的力。
倾斜效果是强度随时间线性变化的持续的力。周期效果是沿着给定的轴重复变化,其量级或
者力的强度由周期效果定义。条件是对用户与游戏杆的交互作用做出响应的效果。这种效果
可能是象一根弹簧,操纵杆向某个方向推得越远,反弹力就越强。

表5:DirectInput效果的类型
GUID 说明 使用方法注解
GUID_ConstantForce 固定强度、特定方向的持续拉力。 使用DICONSTANT力结构作为
DIEFFECT结构的一部分实现持续力。
GUID_CustomForce 一序列持续力下传到设备,按顺序播放。 DICUSTOMFORCE结构被用来定
义力。
GUID_Damper 随沿坐标轴的移动增加的条件效果。 实现这种效果的特定类型结构是
DICONDITION结构。条件效果通常不支持包。
GUID_Friction 阻碍沿坐标轴移动的条件效果。 实现这种效果的特定类型结构是
DICONDITION结构。条件效果通常不支持包。
GUID_Inertia 随沿坐标轴移动的加速度增加的条件效果。 实现这种效果的特定类型结构是
DICONDITION结构。条件效果通常不支持包。
GUID_RampForce 特定方向上大小线性增加或减小的拉力。 DIRAMPFORCE结构被用来作为
DIEFFECT结构中的类型相关部分。
GUID_SawtoothDown 力瞬间达到最大然后线性减小到最小的周期效果。 需要的特定类型结
构是DIPERIODIC结构。
GUID_SawtoothUp 力从最小线性增加到最大然后瞬间降到最小的周期效果 需要的特定类型
结构是DIPERIODIC结构。
GUID_Sine 力正弦变化的周期效果。 需要的特定类型结构是DIPERIODIC结构。
GUID_Spring 力随到某个中点的相对距离而增大的条件效果。 实现这种效果的特定类型结
构是DICONDITION结构。条件效果通常不支持包。
GUID_Square 力瞬时在最大与最小之间转变的周期效果。 需要的特定类型结构是DIPERIODIC
结构。
GUID_Triangle 力在最大与最小之间线性变化的周期效果。 需要的特定类型结构是
DIPERIODIC结构。


下面所有与力反馈游戏杆有关的工作都是针对Microsoft SideWinder Force Feedback Pro
游戏杆,这就是说,本文中的某些细节对其它设备可能多少会产生一些问题。

在创建力反馈效果以前先获得设备是一个不错的想法。虽然这不是必须的,但是在效果能够
被下传到设备前必须要获得设备。这一点对于播放对用户按下按钮做出反应的力效果尤其重
要。

要创建效果,首先要为每个打算使用的效果创建DirectInputEffect对象的实例。这一工作
通过调用IDirectInputDevice2接口的CreateEffect成员函数完成。此函数需要效果的
GUID,以及指向DIEFFECT结构的指针,该结构中填写的是效果的细节。最后,CreateEffect
返回一个指向IdirectInputEffect接口的指针,该指针的地址是CreateEffect的一个参数。
这个调用的核心部分集中在DIEFFECT结构的填充。

DIEFFECT结构如下定义:

typedef struct {
DWORD dwSize;
DWORD dwFlags;
DWORD dwDuration;
DWORD dwSamplePeriod;
DWORD dwGain;
DWORD dwTriggerButton;
DWORD dwTriggerRepeatInterval;
DWORD cAxes; LPDWORD rgdwAxes;
LPLONG rglDirection;
LPDIENVELOPE lpEnvelope;
DWORD cbTypeSpecificParams;
LPVOID lpvTypeSpecificParams;
} DIEFFECT, *LPDIEFFECT;

dwSize成员是此结构的字节数。DwFlags指出效果使用的坐标类型,以及是使用偏移方法还
是ID方法描述按钮(就向前面说明的SetProperty)。通常情况下,可以设置为
DIEFF_CARTESIAN|DIEFF_OBJECTOFFSETS,即按钮采用偏移描述,坐标使用XYZ坐标形式。

DwDuration说明效果播放多少毫秒。注意dwDuration可以设为INFINITE。DwSamplePeriod
说明效果播放一个周期花费多少毫秒。不同设备支持不同的周期。实际中,SideWinder游
戏杆支持的周期不大于1秒,不小于1/80秒。DwGain可以看作效果的主要量,因为它说明
效果多么有力。此值的范围是0到10000。

DwTriggerButton和dwTriggerRepeatInterval用来设置触发效果播放的按钮,以及重复频
率。当然,可以通过将dwTriggerButton的值设置为DIEB_NOTRIGGER来将效果设置为与按
钮无关。否则,dwFlags定义通过ID还是偏移方式描述按钮。因为偏移方式不需要调用
EnumObjects,所以一般可以将值指定为DIJOFS_ BUTTON0和DIJOFS_BUTTON1。

CAxes成员说明效果将影响几个轴。RgdwAxes指向一个描述所包含的轴的DWORD数组,数组
中每个轴是一个成员。同按钮一样,轴也是用偏移或者ID来指明。一般的偏移值包括DIJOFS_X
和DIJOFS_Y。

同样,rglDirection成员指向一个long型数组,每个轴是一个成员。在笛卡儿坐标中,
(Y=-1,X=1)与(Y=-10,X=10)描述的是同一个方向。这就是说,如果想得到一个不是45
度整数倍方向上的斜的力,就应该调整两个值的相对大小。例如,(Y=-10,X=1)描述与上
面例子在同一象限的方向,但却明显靠近Y轴。

效果也可以有描述它们的包。填充一个DIENVELOPE结构,并将其地址填写到lpEnvelope成
员就可以完成。包可以在一段时间内影响效果的数量或力量。其中,起动水平是效果的开始
变化点,启动时间说明效果达到力量保持阶段花费多少毫秒。衰减水平是效果在包最后达到
的水平,衰减时间是衰减用掉了多少毫秒。包可以用来制造初始状态较强,然后慢慢衰减的
力效果。图1中描绘了包如何改变效果。


DIEFFECT结构的最后两个成员是cbTypeSpecificParams和lpvTypeSpecificParams。它们
保存特定于所创建效果类型的结构的字节数和地址。特定类型的效果使用何种结构的信息见
表5。

填写完这个结构并调用CreateEffect后,就会获得指向IdirectInputEffect接口的指针,
现在可以使用此接口播放效果,改变效果等。如果没有将效果联系到按钮,就必须用
IdirectInputEffect接口的Start和Stop成员播放和停止效果。如果效果与按钮关联,那
么在创建时下传到设备;否则,效果在播放时自动下传到设备。如果程序必须重新获得设备,
那么所有与按钮相关的效果必须通过明确的调用Download成员才能下传到设备。

效果能够用Unload成员卸载,也能够通过向SetParameters成员函数传递新的DIEFFECT结
构重新设置参数。当程序用完效果后,必须调用接口的Release成员。

 
 
说明
:本教程来源互联网或网友上传或出版商,仅为学习研究或媒体推广,51zixue.net不保证资料的完整性。

上一篇:用DirectDraw编写动画程序  下一篇:在VC++6.0下利用消息实现内部进程通讯(IPC)