程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> 利用鍵盤鉤子開發按鍵發音程序

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

編輯:關於VC++

本文配套源碼

一、前言

一日,看見我媽正在用電腦練習打字,頻頻低頭看鍵盤,我想:要是鍵盤能發音的話,不就可以方便她養成"盲打"的好習慣嗎?光想不做可不行,開始行動(您可千萬別急著去 拿工具箱啊^_^)...

按鍵能發音,其關鍵就是讓程序能夠知道當前鍵盤上是哪個鍵被按下,並播 放相應的聲音,自己的程序當然不在話下,那麼其它程序當前按下哪個鍵如何得知呢?利用鍵盤鉤子便可 以很好地解決。

下載本文的全部源代碼 大小:552K

二、掛鉤(HOOK)的基本原理

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

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

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

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

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

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

另外需要注意的是為了捕獲所有事件,掛鉤函數應該放在動態鏈接庫DLL中。

三、具體實現

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

  1. 上一頁:
  2. 下一頁:
欄目導航
Copyright © 程式師世界 All Rights Reserved