【ZT】DirectInput 键盘编程入门

DirectX简介 第五篇 DirectInput简介 DirectInput简介 1.       DirectInput的得名是因为它直接与设备驱动器通讯,对鼠标和键盘也就意味着立即响应硬件中断,而不是等待Windows发送消息证明已发生输入事件 2.       DirectInput概念 (1)       设备 1.       键盘 2.       鼠标(触摸板、跟踪球以及相应的按键) 3. 阅读详情
2007年10月12日 01:52:00

作者:樊一鹏

游戏编程可不仅仅是图形程序的开发工作,实际上包含了许多方面,本文所要讲述的就是关于如何使用 DirectInput 来对键盘编程的问题。

在 DOS 时代,我们一般都习惯于接管键盘中断来加入自己的处理代码。但这一套生存方式在万恶的 Windows 社会下是行不通的,我们只能靠领 API 或者 DirectInput 的救济金过活。

在 Windows 的 API 中,有一个 GetAsyncKeyState() 的函数可以返回一个指定键的当前状态是按下还是松开。这个函数还能返回该指定键在上次调用 GetAsyncKeyState() 函数以后,是否被按下过。虽然这个函数听上去很不错,但现在领这种救济金的程序员是越来越少了。原因无它,只因为 DirectInput 的救济金比这丰厚,而且看上去似乎更专业?

为了早日成为职业的救济金用户,我们就从学习 DirectInput 的键盘编程开始吧。

DIRECTINPUT 的初始化

前面讲 DirectDraw 时,曾经提到,微软是按 COM 来设计DirectX的,所以就有了一个 DIRECTINPUT 对象来表示输入设备,而某个具体的设备由 DIRECTINPUTDEVICE 对象来表示。

实际的建立过程是先创建一个 DIRECTINPUT 对象,然后在通过此对象的 CreateDevice 方法来创建 DIRECTINPUTDEVICE 对象。

示例如下:

#include >dinput.h<

#define DINPUT_BUFFERSIZE 16

LPDIRECTINPUT lpDirectInput;
// DirectInput object
LPDIRECTINPUTDEVICE lpKeyboard; // DirectInput device

BOOL InitDInput(HWND hWnd)
{
HRESULT hr;

// 创建一个 DIRECTINPUT 对象
hr = DirectInputCreate(hInstanceCopy, DIRECTINPUT_VERSION, &lpDirectInput, NULL);

if FAILED(hr)
{
// 失败
return FALSE;
}

// 创建一个 DIRECTINPUTDEVICE 界面
hr = lpDirectInput-<CreateDevice(GUID_SysKeyboard, &lpKeyboard, NULL);
if FAILED(hr)
{
// 失败
return FALSE;
}

// 设定为通过一个 256 字节的数组返回查询状态值
hr = lpKeyboard-<SetDataFormat(&c_dfDIKeyboard);
if FAILED(hr)
{
// 失败
return FALSE;
}

// 设定协作模式
hr = lpKeyboard-<SetCooperativeLevel(hWnd, DISCL_NONEXCLUSIVE | DISCL_FOREGROUND);
if FAILED(hr)
{
// 失败
return FALSE;
}

// 设定缓冲区大小
// 如果不设定,缓冲区大小默认值为 0,程序就只能按立即模式工作
// 如果要用缓冲模式工作,必须使缓冲区大小超过 0
DIPROPDWORD property;

property.diph.dwSize
= sizeof(DIPROPDWORD);
property.diph.dwHeaderSize
= sizeof(DIPROPHEADER);
property.diph.dwObj
= 0;
property.diph.dwHow
= DIPH_DEVICE;
property.dwData
= DINPUT_BUFFERSIZE;

hr
= lpKeyboard-<SetProperty(DIPROP_BUFFERSIZE, &property.diph);

if FAILED(hr)
{
// 失败
return FALSE;
}


hr
= lpKeyboard-<Acquire();
if FAILED(hr)
{
// 失败
return FALSE;
}

return TRUE;
}

  在这段代码中,我们首先定义了 lpDirectInput 和 lpKeyboard 两个指针,前者用来指向 DIRECTINPUT 对象,后者指向一个 DIRECTINPUTDEVICE 界面。

通过 DirectInputCreate(), 我们为 lpDirectInput 创建了一个 DIRECTINPUT 对象。然后我们调用 CreateDevice 来建立一个 DIRECTINPUTDEVICE 界面。参数 GUID_SysKeyboard 指明了建立的是键盘对象。

接下来 SetDataFormat 设定数据格式,SetCooperativeLevel 设定协作模式,SetProperty 设定缓冲区模式。因为这些函数方法的参数很多,我就不逐个去详细解释其作用了,请直接查看 DirectX 的帮助信息,那里面写得非常清楚。

完成这些工作以后,我们便调用 DIRECTINPUTDEVICE 对象的 Acquire 方法来激活对设备的访问权限。在此要特别说明一点,任何一个 DIRECTINPUT 设备,如果未经 Acquire,是无法进行访问的。还有,当系统切换到别的进程时,必须用 Unacquire 方法来释放访问权限,在系统切换回本进程时再调用 Acquire 来重新获得访问权限。

所以,我们通常要在 WindowProc 中做如下处理:

long FAR PASCAL WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch(message)
{
case WM_ACTIVATEAPP:
if(bActive)
{
if(lpKeyboard) lpKeyboard-<Acquire();
}
else
{
if(lpKeyboard) lpKeyboard-<Unacquire();
}
break;
....
}

  哦,对了,前一段例程中还提到了立即模式和缓冲模式。在 DirectINPUT 中,这两种工作模式是有区别的。

如果使用立即模式的话,在查询数据时,只能返回查询时的设备状态。而缓冲模式则将记录所有设备状态变化过程。就个人喜好而言,笔者偏好后者,因为这样一般不会丢失任何按键信息。对应的,如果在使用前者时的查询频度太低,则很难保证采集数据的完整性。

DIRECTINPUT 的数据查询

立即模式的数据查询比较简单,请看下面的示例:

BYTE diks[256]; // DirectInput keyboard state buffer 键盘状态数据缓冲区

HRESULT UpdateInputState(
void)
{
if(lpKeyboard != NULL) // 如果 lpKeyboard 对象界面存在
{
HRESULT hr;

hr
= DIERR_INPUTLOST; // 为循环检测做准备

// if input is lost then acquire and keep trying
while(hr == DIERR_INPUTLOST)
{
// 读取输入设备状态值到状态数据缓冲区
hr = lpKeyboard-<GetDeviceState(sizeof(diks), &diks);

if(hr == DIERR_INPUTLOST)
{
// DirectInput 报告输入流被中断
// 必须先重新调用 Acquire 方法,然后再试一次
hr = lpKeyboard-<Acquire();
if(FAILED(hr))
return hr;
}
}

if(FAILED(hr))
return hr;
}

return S_OK;
}

  在上面的示例中,关键处就是使用 GetDeviceState 方法来读取输入设备状态值以及对异常情况的处理。通过使用 GetDeviceState 方法,我们把输入设备的状态值放在了一个 256 字节的数组里。如果该数组中某个数组元素的最高位为 1,则表示相应编码的那个键此时正被按下。例如,如果 diks[1]&0x80<0,那么就表示 ESC 键正被按下。

学会了立即模式的数据查询以后,下面我们开始研究缓冲模式的情况:

HRESULT UpdateInputState(void)
{
DWORD i;

if(lpKeyboard != NULL)
{
DIDEVICEOBJECTDATA didod[DINPUT_BUFFERSIZE];
// Receives buffered data
DWORD dwElements;
HRESULT hr;

hr
= DIERR_INPUTLOST;

while(hr != DI_OK)
{
dwElements
= DINPUT_BUFFERSIZE;
hr
= lpKeyboard-<GetDeviceData(sizeof(DIDEVICEOBJECTDATA), didod, &dwElements, 0);
if (hr != DI_OK)
{
// 发生了一个错误
// 这个错误有可能是 DI_BUFFEROVERFLOW 缓冲区溢出错误
// 但不管是哪种错误,都意味着同输入设备的联系被丢失了

// 这种错误引起的最严重的后果就是如果你按下一个键后还未松开时
// 发生了错误,就会丢失后面松开该键的消息。这样一来,你的程序
// 就可能以为该键尚未被松开,从而发生一些意想不到的情况

// 现在这段代码并未处理该错误

// 解决该问题的一个办法是,在出现这种错误时,就去调用一次
// GetDeviceState(),然后把结果同程序最后所记录的状态进行
// 比较,从而修正可能发生的错误

hr
= lpKeyboard-<Acquire();
if(FAILED(hr))
return hr;
}
}

if(FAILED(hr))
return hr;
}

// GetDeviceData() 同 GetDeviceState() 不一样,调用它之后,
// dwElements 将指明此次调用共读取到了几条缓冲区记录

// 我们再用一个循环来处理每条记录

for(int i=0; i>dwElements; i++)
{
// 此处放入处理代码
// didod[i].dwOfs 表示那个键被按下或松开
// didod[i].dwData 记录此键的状态,低字节最高位是 1 表示按下,0 表示松开
// 一般用 didod[i].dwData&0x80 来测试
}
return S_OK;
}

  其实,每条记录还有 dwTimeStamp 和 dwSequence 两个字段来记录消息发生的时间和序列编号,以便作更复杂的处理。本文是针对初学者写的,就不打算去谈论这些内容了。

DIRECTINPUT 的结束处理

我们在使用 DIRECTINPUT 时,还要注意的一件事就是当程序结束时,必须要进行释放处理,其演示代码如下:

void ReleaseDInput(void)
{
if (lpDirectInput)
{
if(lpKeyboard)
{
// Always unacquire the device before calling Release().
lpKeyboard-<Unacquire();
lpKeyboard
-<Release();
lpKeyboard
= NULL;
}
lpDirectInput
-<Release();
lpDirectInput
= NULL;
}
}

  这段代码很简单,就是对 DIRECTINPUT 的各个对象去调用 Release 方法来释放资源。这种过程同使用 DIRECTX 的其它部分时是基本上相同的。



Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1821042


掌握DirectX和DirectInput —— 力反馈游戏杆 掌握DirectX和DirectInput——力反馈游戏杆Jason Clark     不知不觉中,Windows下的游戏和多媒体程序已经开始流行。硬件变得越来越快,Windows也变得更加灵活。自从Microsoft发布了DirectX,游戏开发人员对其它平台已经越来越不感兴趣了。许多游戏开发者也已经将他们的开发工作完全移植到了Windows下。     为PC开发游戏从来就没有轻 阅读详情

相关推荐

Unity内存溢出闪退排查:Profiler与日志分析实战指南

内存管理是游戏开发中的核心挑战,尤其在Unity引擎中,内存溢出导致的闪退问题直接影响应用稳定性和用户体验。其原理涉及托管堆、本地堆、GPU内存等多层内存池的分配与回收机制。有效的内存管理技术价值在于降低崩溃率、提升用户留存,并确保应用在性能受限的移动设备上流畅运行。应用场景广泛覆盖移动端游戏、性能敏感应用及复杂3D项目。本文聚焦于通过Unity Profiler进行实时内存监控与日志分析工具的事后诊断,结合托管内存泄漏、AssetBundle管理等热词,提供一套从定位到解决System out of me

weixin_29011667的博客 298

DirectInput(zz)

(一)介绍DirectInput DirectInput是一套输入设备的API,其中包括鼠标、键盘、手柄及其他游戏控制器,如力回馈(输入/输出)设备。 本节对DirectInput的功能作了简要说明,并简要介绍如何创建一个DirectInput应用程序。主要包含以下内容: DirectInput的能力 从DirectInput开始 要...

qinglangee的专栏 1250

DirectX:DirectX输入设备管理(DirectInput)技术教程_2024-07-20_07-02-41.Tex

初始化DirectInput:创建对象。获取设备:使用方法获取特定类型的设备。设置数据格式:通过方法定义设备的数据传输格式。设置设备属性:使用方法调整设备的特性,如缓冲区大小。枚举设备:通过方法和回调函数来发现所有连接的输入设备。这些步骤是构建DirectInput输入系统的基础,确保应用程序能够正确地识别和响应用户的输入。通过上述解决方案,可以有效地解决DirectInput在设备兼容性和性能优化方面的问题。在实际应用中,应根据具体需求和设备特性选择合适的优化策略,以达到最佳的输入设备管理效果。

917

游戏输入控制利器:DirectInput

------------------------------------------------------------------------------------------------------------------------------ 在本篇文章中,我们一起详细探索了DirectInput这套在PC游戏即时控制方面一手遮天的API。下面先来看一下这篇一万多字文章的大体...

szmtjs10的博客 6997

Profiler摘要日志框架

目的 打印系统的性能分析日志,用于分析系统性能 一.maven依赖 <dependency> <groupId>com.duowan.common</groupId> <artifactId>duowan-common-log</artifactId> <ver...

weixin_30457065的博客 157

MongoDB profiler慢查询日志

在查询记录方面,MySQL有slowlog和general log,而MongoDB同样可以使用profiler来记录操作日志。不同的是,profiler要么记录所有操作日志,或者慢查询日志,而不能想MySQL一样slow log和general log分开设置。 profiler的概念: 1.慢查询阈值,slowms; 2.profiling级别,可分为0,1,2三级;0表示关闭profiler...

灯火觅阑珊 907

mongo profiler 进行慢日志分析

profiler 是mongo 提供的用于记录慢日志的命令的攻击,默认是关闭状态。 如果开启:启动参数里直接进行设置,启动MongoDB时加上–profile=级别。    也可以在shell调用db.setProfilingLevel() 命令来实时配置。 获取level等级:db.getProfilingLevel() 设置level等级:db.setProfili...

yyhero1的专栏 382

MongoDB慢日志Profiler

MongoDB的Profiler类似MySQL的SlowLog和GeneralLog的结合,通过设置不同的Profiling级别,来决定记录哪些实例执行过的CURD、配置和管理命令。Profiler会将搜集的内容记录在每个被Profiled的数据库中,名为system.profile的capped集合中。开启Profiling会对数据库性能造成一定的损耗并占用一定的磁盘空间,设置的Profiling级别越高,资源消耗越大。

qq_38008567的博客 690

Unity Profiler真机日志使用全攻略

Unity Profiler真机日志功能如同"体检医生",可对移动端游戏进行深度性能诊断。基础配置包括:构建开发版本时勾选Development Build和Autoconnect Profiler,Android需adb forward建立端口转发,iOS直接USB连接即可。环境适配方面,可能需替换libunity.so文件或修改AndroidManifest.xml以支持深度调试。该功能通过建立数据通道,让开发者能实时获取真机运行状态,实现精准性能优化,是移动游戏开发的必备诊断工具。

本博客聚焦游戏开发技巧、创业实战心得及大学热门课程干货。这里既有技术深度,也有思维广度,无论你是开发者、创业者还是高校学子,都能在这里找到适合自己的成长之路! 672

Unity 打包程序后PC或Android真机调试Debug日志及调出的Profiler面板

Unity BuildSettings中勾选Copy PDB files、Development Build、Autoconnect profiler、Script Debugging选项。 打包程序后,Unity会自动连接Profiler,打开Unity中的Profiler即可查看cpu、gpu、内存等使用情况。 在Unity的Console中的Editor会自动连接运行中程序的Debug日志并输出,若没有连接,可尝试输入目标主机的IP地址,来回切换一下Unity界面和程序,验证正常输出日志后,再进行

Star_MengMeng的博客 1万+

解锁async-profiler日志系统:从调试到问题排查的完整指南

async-profiler是一款强大的Java性能分析工具,它通过AsyncGetCallTrace和perf_events实现CPU和堆内存的采样分析。日志系统作为async-profiler的重要组成部分,为开发者提供了从调试到错误级别的全面信息,帮助用户深入了解工具运行状态和应用性能瓶颈。本文将详细解析async-profiler的日志系统,带你轻松掌握日志级别配置与调试信息利用的实用技巧

gitblog_01107的博客 838

unity中Profiler

unity提供了从脚本来控制性能分析器的类Profiler~

Never give up~ 5316

Profiler的介绍

一, 概述: profiler 可以让你查看在游戏各方面花费的时间,分析GPU,CPU和内存,渲染和音频的性能。 使用时要关注消耗时间最多的部分 ,比较代码更改前后的分析结果,但是会影响帧率。二,窗口信息 打开方式:Windows-profiler 当游戏运行且探查器正在收集数据时,单击任何这些传输控件会暂停游戏,"current"传输按钮使配置文件统计窗口显示实时收集的数据,ctive Profiler弹出菜单允许您选择是在编辑器中还是在单独的播放器中进行性能分析(例如,在连接的移动设备上运行的

shengyin714959的博客 1669

【MongoDB】MongoDB之优化器Profiler

在mysql数据库中,慢查询日志经常作为优化数据库的依据, mongodb中依然有类似的功能。Mongodb自带的profiler,可以方便地记录所有耗时的操作,以便于调优; 一、开始profiler功能 开启profier功能有两种: 第一种就是直接在启动参数里面进行设置,就在茄冬mongodb时候添加-profile=级别 第二种就是在客户端执行“db.setProfilingLeve

夜深人静后的码农 2746

Laravel中使用Profiler随时掌握调试信息

在许多框架中,都会有Profiling相关的东西,Laravel也不例外,而且,它相当的好用。 Profiler是一个用于性能调试的工具,它提供记录各种程序运行指标的工具以及查看他们的视图。Profiler在Laravel 3以前是以一个bundle方式存在的(https://github.com/loic-sharma/Laravel-Profiler),但现在已经集成进核心库了,原来那个re

'尛東峝學. 4204

【Azure Application Insights】如果关闭 Profiler 和 Snapshot Debugger 功能,是否会影响日志收集或查询

关闭Profiler和SnapshotDebugger不会影响Azure Application Insights的日志收集与查询功能。这两个功能分别用于性能分析和异常诊断:Profiler捕获方法级性能数据,SnapshotDebugger获取异常快照。日志数据(请求、异常等)由SDK独立收集。建议在生产环境中按需启用这些功能,既可避免资源浪费,又能在需要时快速定位性能瓶颈和异常问题。Azure的监控服务通过模块化设计,确保各功能既可协同工作又能独立运行。

编码者卢布,国家认证程序员,十八年开发人员,目前生活在云环境。 532

【Python line_profiler & memory_profiler】分析每一行代码的耗时及内存占用情况

Python借助内存分析库memory_profiler分析每一行代码的内存占用; 借助line_profiler分析每一行代码的耗时,然后针对性的进行分析;

qq_40985985的博客 1407
上一篇: 【ZT】DirectInput 鼠标编程入门
下一篇: AutoCAD环境下实现地质图形自动屏幕数字化功能
softart
博客等级 码龄19年 66粉丝 2569原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值