利用鍵盤鉤子開發按鍵發音程序

1、前言函數

一日,看見我媽正在用電腦練習打字,頻頻低頭看鍵盤,我想:要是鍵盤能發音的話,不就能夠方便她養成"盲打"的好習慣嗎?光想不作可不行,開始行動(您可千萬別急着去拿工具箱啊^_^)...
按鍵能發音,其關鍵就是讓程序可以知道當前鍵盤上是哪一個鍵被按下,並播放相應的聲音,本身的程序固然不在話下,那麼其它程序當前按下哪一個鍵如何得知呢?利用鍵盤鉤子即可以很好地解決。工具

2、掛鉤(HOOK)的基本原理編碼

WINDOWS調用掛接的回調函數時首先會調用位於函數鏈首的函數,咱們只要將本身的回調函數置於鏈首,該回調函數就會首先被調用。那麼如何將咱們本身的回調函數置於函數鏈的鏈首呢?函數SetWindowsHookEx()實現的就是該功能。咱們首先來看一下SetWindowsHookEx函數的原型:spa

HHOOK SetWindowsHookEx(
  int idHook,       
  HOOKPROC lpfn,     
  HINSTANCE hMod,    
  DWORD dwThreadId  
); 

第一個參數:指定鉤子的類型,有WH_MOUSE、WH_KEYBOARD等十多種(具體參見MSDN)線程

第二個參數:標識鉤子函數的入口地址調試

第三個參數:鉤子函數所在模塊的句柄;code

第四個參數:鉤子相關函數的ID用以指定想讓鉤子去鉤哪一個線程,爲0時則攔截整個系統的消息。對象

另外須要注意的是爲了捕獲全部事件,掛鉤函數應該放在動態連接庫DLL中。blog

3、具體實現事件

理論的話就很少說了,運行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 。

相關文章
相關標籤/搜索