摘要: 本文給出了一種通過設置系統熱鍵來呼出在系統後台隱藏運行的服務程序的一種方法,通過這種方法,可以實現後台服務程序在必要的時候同用戶的交互設置。
引言
通常情況下,用于後台監控的服務程序(Service)是沒有界面的,甚至也沒有提供任務欄圖標,因此絕大多數情況下服務程序是無法同用戶進行交互的。但是在實際應用中,這些服務程序雖然絕大多數時間是在後台運行,但是在某些必要的情況下還是需要用戶的幹預並同用戶進行一些必要的交互操作。但是由于服務程序沒有提供任何可供交互操作之用的界面,因此如何將其從後台激活(即呼出)成爲解決此問題的一個要害。本文下面就給出一種通過設置系統熱鍵的方法來激活運行于後台的服務程序。
設計思路
盡管從理論上可以有許多方法來激活後台運行的服務程序,比如可以通過尋找服務程序的窗口標題名而得到其窗口指針,然後再向此窗口發送消息使其出現到前台;也可以通過系統快照對當前系統進程進行枚舉,然後再將其激活到前台。但是以上這些方法都需要另外編寫應用程序,對後台服務程序的激活實際是再這些應用程序中進行的,這樣的處理方式顯然十分不便,最好的方法是對程序的激活和隱藏處理均在服務程序內部完成。因此可以考慮接收系統發出的消息,假如通過設置全局鈎子對設置事件進行攔截捕捉,顯然是相當煩瑣的。在此考慮使用系統熱鍵來激活後台服務程序,其實現過程非常簡單,只需先向操作系統添加一個全局原子(Atom),然後再向操作系統登記一個熱鍵,當程序在後台運行期間一旦有此熱鍵按下,操作系統將會抛出系統消息WM_HOTKEY,所以服務程序只需在 WM_HOTKEY消息響應函數中添加相應代碼即可實現服務程序的後台激活。
系統熱鍵的注冊
根據前面的介紹,不難寫出爲後台服務程序添加對系統熱鍵響應的功能代碼。首先通過函數GlobalFindAtom()查詢本服務程序所對應的全局原子是否已存在于全局原子表中,假如發現,則說明系統中已經存在有此服務,程序退出。假如沒有發現,則通過GlobalAddAtom()函數向全局原子表添加一個字串,並獲取得到一個唯一標識此字串的原子。以上兩函數原型分別爲:
以下是引用片段:
ATOM GlobalFindAtom(LPCTSTR lpString);
ATOM GlobalAddAtom(LPCTSTR lpString);
其中,輸入參數爲一個描述原子的字符串,假如GlobalFindAtom()從全局原子表中找到了指定的字串,那麽將返回此字串對應的原子,否則返回0。GlobalAddAtom()假如創建成功,將返回一個新創建的原子。
接下來,爲了能在程序運行期間捕捉到系統熱鍵,需要通過RegisterHotKey()定義一個系統範圍的熱鍵。該函數原形如下:
以下是引用片段:
BOOL RegisterHotKey(HWND hWnd, // 接收熱鍵響應的窗口句柄
int id, // 熱鍵的標識
UINT fsModifiers, // 控制鍵標志
UINT vk // 虛擬鍵值
);
其中,熱鍵標識id必須是一個範圍在0xC000到0xFFFF之間的全局唯一的值,爲了避免可能引起的熱鍵沖突,通常把GlobalAddAtom ()返回的原子作爲參數傳入,而且GlobalAddAtom()返回值的範圍同id參數的答應範圍是完全一致的。參數fsModifiers定義了同虛擬鍵值vk同時按下而産生出系統熱鍵消息WM_HOTKEY的控制鍵組合,如MOD_ALT、MOD_CONTROL、MOD_SHIFT和 MOD_WIN等。在本例中將要設定的系統熱鍵爲Alt+Ctrl+R,因此,參數fsModifiers和vk分別設置爲MOD_ALT MOD_CONTROL和VK_R。有關系統熱鍵的注冊實現方法可以整理如下:
以下是引用片段:
// 獲取當前窗口句柄
HWND handle = GetSafeHwnd();
// 尋找HotKey對應的原子是否存在于原子列表
if(GlobalFindAtom("Hotkey") == 0)
{
// 假如沒有存在于原子列表,則創建一個原子
id = GlobalAddAtom("Hotkey");
//注冊全局熱鍵Ctrl + Alt + R
RegisterHotKey(handle, id, CONTROL + ALT, R);
}
else // 假如HotKey已經存在于原子列表,則終止程序運行
PostQuitMessage(0);
服務程序的隱藏與激活
服務程序除了被激活後同用戶的交互,絕大部分時間都是在後台隱藏運行的,不僅界面是不可視的,而且在任務列表中也不應當出現。關于界面的隱藏比較簡單,可以通過向ShowWindow()函數設置SW_HIDE參數來實現,而在任務列表中的隱身則一般的做法是通過調用系統內核Kernel32.DLL的RegisterServiceProcess()函數將其設置成爲一個服務進程,這樣,在任務列表中也實現了隱身。但是RegisterServiceProcess()函數並非一個標准的API函數,使用起來有點煩瑣。首先要通過 GetModuleHandle()函數得到Kernel32.DLL模塊的句柄,並由此通過GetProcAddress()函數進一步得出 RegisterServiceProcess()函數在Kernel32.DLL中的入口地址,最後才能使用 RegisterServiceProcess()函數。該函數原型聲明如下:
以下是引用片段:
DWORD RegisterServiceProcess(DWORD dwProcessId,DWORD dwType);
其第一個參數指定了將要注冊爲服務進程的進程標識,參數dwType指定是去注冊一個服務進程(爲1時)還是去卸載一個服務進程(爲0時)。其具體服務注冊過程如下:
typedef DWORD (WINAPI *RSP)(DWORD dwProcessId,DWORD dwType);
// 獲取Kernel32.DLL模塊句柄
HMODULE m_hKernel = ::GetModuleHandle("Kernel32.DLL");
// 得到RegisterServiceProcess()函數入口地址
RSP m_rsp = (RSP)::GetProcAddress(m_hKernel, "RegisterServiceProcess");
// 注冊當前進程爲服務進程
m_rsp(::GetCurrentProcessId(),1);
在服務程序後台運行期間,一旦有系統熱鍵Alt+Ctrl+R按下,將發出系統熱鍵消息WM_HOTKEY,該消息的消息響應函數不能通過 ClassWizard來添加,而只能手工完成消息映射。在消息響應函數中,通過對消息參數 wParam的判定可以確定出是否是本服務程序所設定的系統熱鍵,假如是,通過ShowWindow(SW_SHOW)將程序界面顯示出來,以進行同用戶的交互操作:
以下是引用片段:
void CServiceDlg::OnHotKey(WPARAM wParam, LPARAM lParam)
{
// 判定是否是本服務程序設置的系統熱鍵
if (wParam == id)
{
……
// 在此發送WM_PAINT消息,在OnPain()中通過
// ShowWindow(SW_SHOW)將界面設置爲可視
PostMessage(WM_PAINT, 0, 0);
}
}
系統熱鍵的卸載
由于前面將系統熱鍵、全局原子等都注冊到系統,因此必須在服務程序退出之前將其卸載,否則將導致下次注冊時的失敗。函數UnregisterHotKey()負責完成對系統熱鍵的釋放,GlobalDeleteAtom()將全局原子從全局原子列表刪除。
小結
通過本文所述方法爲後台運行的系統服務程序添加此熱鍵呼出功能可以真正實現程序的後台隱蔽運行、熱鍵激活,非常有利于治理員和用戶的治理與使用。本文所述程序在windows 2000 Professional下,由Microsoft Visual C++ 6.0編譯通過。