用 C++ 創(chuàng )建簡(jiǎn)單的 Win32 服務(wù)程序
作者:Nigel Thomson(MSDN 技術(shù)組)
翻譯:Northxizang
原文出處:Creating a Simple Win32 Service in C++
下載 NTService 例子源代碼
下載 NTServCpl 例子源代碼
下載 NTServCtrl 例子源代碼


大多數服務(wù)程序都是使用一個(gè)安裝程序來(lái)安裝,而用另外一個(gè)程序來(lái)卸載。本文我將這些功能內建在服務(wù)程序自身當中,使之一體化,這樣只分發(fā)一個(gè).EXE文件即可。你可以從命令行直接運行服務(wù)程序,并且可以隨心所欲地安裝和卸載或報告其版本信息。NTService 支持下列的命令行參數:
默認情況下,當系統啟動(dòng)該服務(wù)時(shí)沒(méi)有命令行參數傳遞。


下面讓我們仔細一下這些文件,以便弄明白你自己需要創(chuàng )建什么,以及消息編譯器要為你創(chuàng )建些什么。我們不用研究整個(gè)消息集,只要看看其中一二個(gè)如何工作的即可。下面是例子程序消息源文件 NTServMsg.mc 的第一部分:
MessageId=100SymbolicName=EVMSG_INSTALLEDLanguage=EnglishThe %1 service was installed..MessageId=SymbolicName=EVMSG_REMOVEDLanguage=EnglishThe %1 service was removed..MessageId=SymbolicName=EVMSG_NOTREMOVEDLanguage=EnglishThe %1 service could not be removed..
每一條都有一個(gè)消息ID,如果不特別設置,那么 ID 的取值就是指其前面所賦的值。每一條還有一個(gè)代碼中使用的符號名,語(yǔ)言標示符以及消息文本。消息可以跨多個(gè)行,并用含有一個(gè)句號的單獨一行終止。
消息編譯器輸出一個(gè)庫文件,該庫文件被用作應用程序的資源,此外還輸出兩個(gè)要在代碼中包含的文件。下面是我的 .RC 文件:
// NTServApp.rc#include <windows.h>// 包含由消息編譯器(MC)產(chǎn)生的消息表資源腳本#include "NTServMsg.rc"Here''s the .RC file the message compiler generated:LANGUAGE 0x9,0x11 11 MSG00001.bin
正像你所看到的,這些文件中內容不多!
消息編譯器產(chǎn)生的最后一個(gè)文件是你要包含到代碼中的頭文件,下面就是這個(gè)頭文件的部分內容:
[..........]//// MessageId: EVMSG_INSTALLED//// MessageText://// The %1 service was installed.//#define EVMSG_INSTALLED 0x00000064L//// MessageId: EVMSG_REMOVED//// MessageText://// The %1 service was removed.//#define EVMSG_REMOVED 0x00000065L[...........]
你可能已經(jīng)注意到了有幾個(gè)消息包含參數替代項(如 %1)。讓我們看看將消息寫(xiě)入某個(gè)系統日志文件時(shí)如何在代碼中使用消息ID和參數替代項。以事件日志中記錄成功安裝信息的部分安裝代碼為例。也就是 CNTService::IsInstalled 函數部分:
[....]LogEvent(EVENTLOG_INFORMATION_TYPE, EVMSG_INSTALLED, m_szServiceName);[....]
LogEvent 是另一個(gè) CNTService 函數,它使用事件類(lèi)型(信息,警告或錯誤),事件消息的 ID,以及形成日志消息的最多三個(gè)參數的替代串:
// This function makes an entry into the application event log.void CNTService::LogEvent(WORD wType, DWORD dwID,const char* pszS1,const char* pszS2,const char* pszS3){const char* ps[3];ps[0] = pszS1;ps[1] = pszS2;ps[2] = pszS3;int iStr = 0;for (int i = 0; i < 3; i++) {if (ps[i] != NULL) iStr++;}// Check to see if the event source has been registered,// and if not then register it now.if (!m_hEventSource) {m_hEventSource = ::RegisterEventSource(NULL, // local machinem_szServiceName); // source name}if (m_hEventSource) {::ReportEvent(m_hEventSource,wType,0,dwID,NULL, // sidiStr,0,ps,NULL);}} 如你所見(jiàn),其主要工作是由 ReportEvent 系統函數處理。
服務(wù)回調函數
因為 ServiceMain 和 Handler 函數都是由系統來(lái)調用,所以它們必須遵循操作系統的參數傳遞規范和調用規范。也就是說(shuō),它們不能簡(jiǎn)單地作為某個(gè) C++ 類(lèi)的成員函數。這樣就給封裝帶來(lái)一些不便,因為我們想把 Win32 服務(wù)的功能封裝在一個(gè) C++ 類(lèi)中。為了解決這個(gè)問(wèn)題,我將 ServiceMain 和 Handler 函數創(chuàng )建成 CNTService 類(lèi)的靜態(tài)成員。這樣就使我得以創(chuàng )建可以由操作系統調用的函數。 但是,這樣做還沒(méi)有完全解決問(wèn)題,因為系統不允許給被調用的函數傳遞任何形式的用戶(hù)數據,所以我們無(wú)法確定對 C++ 對象特定實(shí)例的 ServiceMain 或 Handler 的調用。用了一個(gè)非常簡(jiǎn)單但有局限的方法來(lái)解決這個(gè)問(wèn)題。我創(chuàng )建一個(gè)包含 C++ 對象指針的靜態(tài)變量。這個(gè)變量是在該對象首次創(chuàng )建是進(jìn)行初始化的。這樣便限制你每個(gè)服務(wù)應用只有一個(gè)C++對象。我覺(jué)得這個(gè)限制并不過(guò)分。下面是 NTService.h 文件中的聲明:
class CNTService{[...]// 靜態(tài)數據static CNTService* m_pThis; // nasty hack to get object ptr[...]};下面是初始化 m_pThis 指針的方法:
CNTService::CNTService(const char* szServiceName){// Copy the address of the current object so we can access it from// the static member callback functions.// WARNING: This limits the application to only one CNTService object.m_pThis = this;[...]}
// myservice.h#include "ntservice.h"class CMyService : public CNTService{public:CMyService();virtual BOOL OnInit();virtual void Run();virtual BOOL OnUserControl(DWORD dwOpcode);void SaveStatus();// Control parametersint m_iStartParam;int m_iIncParam;// Current stateint m_iState;}; 正像你所看到的,CMyService 改寫(xiě)了 CNTService 的 OnInit、Run 和 OnUserControl。它還有一個(gè)函數叫 SaveStatus,這個(gè)函數被用于將數據寫(xiě)入注冊表,那些成員變量用來(lái)保存當前狀態(tài)。例子服務(wù)每隔一定的時(shí)間對一個(gè)整型變量進(jìn)行增量處理。開(kāi)始值和增量值都存在注冊表的參數中。這樣做并沒(méi)有別的意圖。只是為了簡(jiǎn)單示范。下面我們看看這個(gè)服務(wù)是如何實(shí)現的。

int main(int argc, char* argv[]){// 創(chuàng )建服務(wù)對象CMyService MyService;// 解析標準參數 (安裝, 卸載, 版本等.)if (!MyService.ParseStandardArgs(argc, argv)) {// 未發(fā)現任何標準參數,所以啟動(dòng)服務(wù),// 取消下面 DebugBreak 代碼行的注釋?zhuān)?/ 當服務(wù)啟動(dòng)后進(jìn)入調試器,//DebugBreak();MyService.StartService();}// 到這里,服務(wù)已經(jīng)停止return MyService.m_Status.dwWin32ExitCode;} 這里代碼不多,但執行后卻發(fā)生了很多事情,讓我們一步一步來(lái)看。首先,我們創(chuàng )建一個(gè) MyService 類(lèi)的實(shí)例。構造函數設置初始化狀態(tài)和服務(wù)名字(MyService.cpp):CMyService::CMyService():CNTService("NT Service Demonstration"){m_iStartParam = 0;m_iIncParam = 1;m_iState = m_iStartParam;} 接著(zhù)調用 ParseStandardArgs 檢查命令行是否包含服務(wù)安裝(-i)、卸載(-u)以及報告其版本號(-v)的請求。CNTService::ParseStandardArgs 分別調用 CNTService::IsInstalled,CNTService::Install 和 CNTService::Uninstall 來(lái)處理這些請求。如果沒(méi)有可識別的命令行參數,則假設該服務(wù)控制管理器試圖啟動(dòng)該服務(wù)并調用 StartService。該函數直到服務(wù)停止運行才返回。當你調試完代碼,即可把用于調試的代碼行注釋掉或刪除。
安裝和卸載服務(wù)
服務(wù)的安裝由 CNTService::Install 處理,它用 Win32 服務(wù)管理器注冊服務(wù)并在注冊表中建立一個(gè)條目以支持服務(wù)運行時(shí)日志消息。
服務(wù)的卸載由 CNTService::Uninstall 處理,它僅僅通知服務(wù)管理器該服務(wù)已經(jīng)不再需要。CNTService::Uninstall 不會(huì )刪除服務(wù)實(shí)際的可執行文件。

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services
我就是選擇這里來(lái)存儲我的服務(wù)配置信息。我創(chuàng )建了一個(gè) Parameters 鍵,并在此存儲我要保存的值。所以當服務(wù)啟動(dòng)時(shí),OnInit 函數被調用;這個(gè)函數從注冊表中讀取初始設置。
BOOL CMyService::OnInit(){// Read the registry parameters.// Try opening the registry key:// HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\\ParametersHKEY hkey;char szKey[1024];strcpy(szKey, "SYSTEM\\CurrentControlSet\\Services\\");strcat(szKey, m_szServiceName);strcat(szKey, "\\Parameters");if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,szKey,0,KEY_QUERY_VALUE,&hkey) == ERROR_SUCCESS) {// Yes we are installed.DWORD dwType = 0;DWORD dwSize = sizeof(m_iStartParam);RegQueryValueEx(hkey,"Start",NULL,&dwType,(BYTE*)&m_iStartParam,&dwSize);dwSize = sizeof(m_iIncParam);RegQueryValueEx(hkey,"Inc",NULL,&dwType,(BYTE*)&m_iIncParam,&dwSize);RegCloseKey(hkey);}// Set the initial state.m_iState = m_iStartParam;return TRUE;} 現在我們有了服務(wù)參數,我們便可以運行服務(wù)了。void CMyService::Run(){while (m_bIsRunning) {// Sleep for a while.DebugMsg("My service is sleeping (%lu)...", m_iState);Sleep(1000);// Update the current state.m_iState += m_iIncParam;}} 注意,只要服務(wù)不終止,這個(gè)函數就不會(huì )退出。BOOL CMyService::OnUserControl(DWORD dwOpcode){switch (dwOpcode) {case SERVICE_CONTROL_USER + 0:// Save the current status in the registry.SaveStatus();return TRUE;default:break;}return FALSE; // say not handled} SaveStatus 是一個(gè)局部函數,用來(lái)在注冊表中存儲服務(wù)狀態(tài)。
Module Load: WinDebug/NTService.exe (symbol loading deferred)Thread Create: Process=0, Thread=0Module Load: C:\NT351\system32\NTDLL.DLL (symbol loading deferred)Module Load: C:\NT351\system32\KERNEL32.DLL (symbol loading deferred)Module Load: C:\NT351\system32\ADVAPI32.DLL (symbol loading deferred)Module Load: C:\NT351\system32\RPCRT4.DLL (symbol loading deferred)Thread Create: Process=0, Thread=1*** WARNING: symbols checksum is wrong 0x0005830f 0x0005224f for C:\NT351\symbols\dll\NTDLL.DBGModule Load: C:\NT351\symbols\dll\NTDLL.DBG (symbols loaded)Thread Terminate: Process=0, Thread=1, Exit Code=0Hard coded breakpoint hitHard coded breakpoint hit[](130): CNTService::CNTService()Module Load: C:\NT351\SYSTEM32\RPCLTC1.DLL (symbol loading deferred)[NT Service Demonstration](130): Calling StartServiceCtrlDispatcher()Thread Create: Process=0, Thread=2[NT Service Demonstration](174): Entering CNTService::ServiceMain()[NT Service Demonstration](174): Entering CNTService::Initialize()[NT Service Demonstration](174): CNTService::SetStatus(3026680, 2)[NT Service Demonstration](174): Sleeping...[NT Service Demonstration](174): CNTService::SetStatus(3026680, 4)[NT Service Demonstration](174): Entering CNTService::Run()[NT Service Demonstration](174): Sleeping...[NT Service Demonstration](174): Sleeping...[NT Service Demonstration](174): Sleeping...[NT Service Demonstration](130): CNTService::Handler(1)[NT Service Demonstration](130): Entering CNTService::Stop()[NT Service Demonstration](130): CNTService::SetStatus(3026680, 3)[NT Service Demonstration](130): Leaving CNTService::Stop()[NT Service Demonstration](130): Updating status (3026680, 3)[NT Service Demonstration](174): Leaving CNTService::Run()[NT Service Demonstration](174): Leaving CNTService::Initialize()[NT Service Demonstration](174): Leaving CNTService::ServiceMain()[NT Service Demonstration](174): CNTService::SetStatus(3026680, 1)Thread Terminate: Process=0, Thread=2, Exit Code=0[NT Service Demonstration](130): Returned from StartServiceCtrlDispatcher()Module Unload: WinDebug/NTService.exeModule Unload: C:\NT351\system32\NTDLL.DLLModule Unload: C:\NT351\system32\KERNEL32.DLLModule Unload: C:\NT351\system32\ADVAPI32.DLLModule Unload: C:\NT351\system32\RPCRT4.DLLModule Unload: C:\NT351\SYSTEM32\RPCLTC1.DLLThread Terminate: Process=0, Thread=0, Exit Code=0Process Terminate: Process=0, Exit Code=0>

也許用 C++ 創(chuàng )建 Win32 服務(wù)并不是最理想的,但使用單一的類(lèi)來(lái)派生你自己的服務(wù)的確方便了你的服務(wù)開(kāi)發(fā)工作。
聯(lián)系客服