键盘钩子原理----开发按键发音程序

1、前言html

一日,看见我妈正在用电脑练习打字,频频低头看键盘,我想:要是键盘能发音的话,不就能够方便她养成"盲打"的好习惯吗?光想不作可不行,开始行动(您可千万别急着去拿工具箱啊^_^)...
按键能发音,其关键就是让程序可以知道当前键盘上是哪一个键被按下,并播放相应的声音,本身的程序固然不在话下,那么其它程序当前按下哪一个键如何得知呢?利用键盘钩子即可以很好地解决。函数

2、挂钩(HOOK)的基本原理工具

WINDOWS调用挂接的回调函数时首先会调用位于函数链首的函数,咱们只要将本身的回调函数置于链首,该回调函数就会首先被调用。那么如何将咱们本身的回调函数置于函数链的链首呢?函数SetWindowsHookEx()实现的就是该功能。咱们首先来看一下SetWindowsHookEx函数的原型:post

复制代码
HHOOK SetWindowsHookEx(
  int idHook,       
  HOOKPROC lpfn,     
  HINSTANCE hMod,    
  DWORD dwThreadId  
); 
复制代码

第一个参数:指定钩子的类型,有WH_MOUSE、WH_KEYBOARD等十多种(具体参见MSDN)this

第二个参数:标识钩子函数的入口地址编码

第三个参数:钩子函数所在模块的句柄;spa

第四个参数:钩子相关函数的ID用以指定想让钩子去钩哪一个线程,为0时则拦截整个系统的消息。线程

另外须要注意的是为了捕获全部事件,挂钩函数应该放在动态连接库DLL中。调试

3、具体实现code

理论的话就很少说了,运行VC++6.0,新建一个MFC AppWizard(dll)工程,命名为Hook,使用默认的建立DLL类型的选项,也就是使用共享MFC DLL,点击完成后开始编写代码:

(1)在Hook.h中定义全局函数

BOOL installhook(); //钩子安装函数
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam);//挂钩函数

(2)在Hook.cpp文件的#endif下添加定义全局变量Hook的代码:

static HHOOK hkb=NULL;
HINSTANCE hins; //钩子函数所在模块的句柄

(3)添加核心代码

BOOL installhook()
{
    hkb=SetWindowsHookEx(WH_KEYBOARD,(HOOKPROC)KeyboardProc,hins,0);
    return TRUE;
}

第一个参数指定钩子的类型,由于咱们只用到键盘操做因此设定为WH_KEYBOARD;第二个参数将钩子函数的入口地址指定为KeyboardProc,当钩子钩到任何消息后便调用这个函数,即当无论系统的哪一个窗口有键盘输入立刻会引发KeyboardProc的动做;第三个参数是钩子函数所在模块的句柄;最后一个参数是钩子相关函数的ID用以指定想让钩子去钩哪一个线程,为0时则拦截整个系统的消息;

如今,就开始定义当键盘上的键按下时程序要作什么了~

KeyboardProc动做:

复制代码
LRESULT CALLBACK KeyboardProc(int nCode,WPARAM wParam,LPARAM lParam)
{ 
    if(((DWORD)lParam&0x40000000) && (HC_ACTION==nCode))
    {
       switch(wParam) //键盘按键标识
        {
        case ''1'':sndPlaySound("1.wav",SND_ASYNC);break; //当数字键1被按下
         case ''2'':sndPlaySound("2.wav",SND_ASYNC);break;
        case ''3'':sndPlaySound("3.wav",SND_ASYNC);break;
        case ''4'':sndPlaySound("4.wav",SND_ASYNC);break;
        ....
        case ''A'':sndPlaySound("a.wav",SND_ASYNC);break; //当字母键A被按下
         case ''B'':sndPlaySound("b.wav",SND_ASYNC);break;
        case ''C'':sndPlaySound("c.wav",SND_ASYNC);break;
        case ''D'':sndPlaySound("d.wav",SND_ASYNC);break;
        ....
        }
     }
     LRESULT RetVal = CallNextHookEx( hkb, nCode, wParam, lParam ); 
     return RetVal;
}
复制代码

上面的代码中咱们用播放声音作为按键被按下后的动做,API函数sndPlaySound的第一个参数定义的声音文件的绝对路径(好比要播放C盘下的a.wav,就定义成"C:\\a.wav");第二参数定义播放模式,SND_ASYNC模式能够及时地释放正在播放的声音文件,马上中止当前声音的播放转去播放新的声音,这样在咱们连续击键时就不会有阻塞感了.为了执行sndPlaySound函数,必须在Hook.cpp的文件头加上:

#include "mmsystem.h"

而且点击VC++菜单上的“工程”-“设置”进入Link属性页,在L对象/库模块下输入:winmm.lib后肯定便可.

(4)添加输出标识

在Hook.def的末尾添加

installhook
KeyboardProc

短短的四步,键盘钩子的制做算是完成了,编译生成后的DLL文件就能够自由的用别的程序来调用了.

在程序中如何调用DLL呢?那就简单了.再用VC++6.0新建一个MFC AppWizard(exe)工程,命名为KeySound,点击"肯定"后选择程序类型为对话框,直接点击肯定便可.

在KeySoundDlg.cpp文件中的OnInitDialog()初始化函数的CDialog::OnInitDialog();下面添加:

//阻止程序反复驻留内存,也为了防止有两个程序同时读取DLL而发生错误.

复制代码
CreateMutex(NULL, FALSE, "KeySound");
if(GetLastError()==ERROR_ALREADY_EXISTS)
   OnOK();

//读取DLL
static HINSTANCE hinstDLL; 
typedef BOOL (CALLBACK *inshook)(); 
inshook instkbhook;
if(hinstDLL=LoadLibrary((LPCTSTR)"Hook.dll"))
{
    instkbhook=(inshook)GetProcAddress(hinstDLL,"installhook");  
    instkbhook();
}
else
{
    MessageBox("当前目录找不到Hook.dll文件,程序初始化失败");
    OnOK();
}
复制代码

将编译生成后的KeySound.exe和Hook.dll放在同一目录下,定义好声音文件,运行KeySound.exe后打开记事本或写字板,体验一下系统为您即时快速地朗读您按下的每个键的快感吧^-^

有一点必须说明,标准键盘有101个键,您想让多少键发声音,就必须在上面的KeyboardProc动做里定义多少个键,经常使用的10个数字键和26个英文字母不会给您带来太大的困难,只要相应的''A''对应A键,''1''对应1键就能够,但若是您但愿能让更多的键都有各类特点音乐的话,极可能会遇到一些键盘编码上的麻烦,好比ESC键就不能简单的用''ESC''来搞定了,得用VK_ESCAPE,又好比Alt键得用VK_MENU来定义,没有个键盘编码表的话会使人至关头疼,这里我介绍一种让程序来告诉您键盘按键名称的方法:

为一个工程添加PreTranslateMessage映射,添加以下代码:

复制代码
char KeyName[50];
ZeroMemory(KeyName,50);
if(pMsg -> message == WM_KEYDOWN)
{ 
   GetKeyNameText(pMsg->lParam,KeyName,50);
   MessageBox(KeyName);
} 
复制代码

那么当程序窗口显示在面前时按下某个键,就会弹出一个消息显示该键的名称,而后用''''包起来就能够了,好比逗号句号,就是'',''和''.'',简单吧:)

到此就所有完成了按键发音程序的编写,经过改变声音文件的名称而不用改动程序自己就能够达到更换按键声音的目的了,只是有个遗憾,声音文件在硬盘中的位置不能变动,从C盘换移动D盘程序就不能播放了,怎么样才能灵活的读取声音文件呢?能够用API函数GetModuleFileName来获得程序所在的目录,具体实现方法以下:

(1)在Hook.h的public:下面添加:

BOOL InitInstance(); //初始化函数

(2)在Hook.cpp的#endif下添加定义全局变量的代码:

char szBuf[256];
char *p;
CString msg;

(3)在Hook.cpp中适当位置添加:

复制代码
BOOL CHookApp::InitInstance ()
{ 
   hins=AfxGetInstanceHandle();
   GetModuleFileName(AfxGetInstanceHandle( ),szBuf,sizeof(szBuf));
   p = szBuf;
   while(strchr(p,''\\'')) 
   { 
        p = strchr(p,''\\''); 
        p++; 
   }
   *p = ''\0''; 
   msg=szBuf;
   return TRUE; 
}
复制代码

(4)新建一个文件夹并命名为Sound;

(5)改变声音文件物理位置定义方式

case ''1'':sndPlaySound(msg+"sound\\1.wav",SND_ASYNC);break; 

msg是获得程序当前所在目录,加上后面的代码就是指播放当前目录下的Sound目录里的1.wav文件,这样就将声音文件的绝对路径改为了灵活的相对路径.您只要把KeySound.exe,Hook.dll和Sound文件夹放在同一个文件夹下,之后只要搬动整个文件夹就能实现声音文件的任意移动了。

调试时须要注意:将Hook.dll、Sound目录放在KeySound.exe的执行目录下。假如编译连接的时候出现unresolved external symbol __imp__sndPlaySoundA@8 这样的信息,请在Project Settings中加入Winmm.lib 。

 

 

出处:https://www.cnblogs.com/MaxWoods/p/3916583.html

相关文章
相关标签/搜索