有時候,我們需要自己寫的程序在沒有用戶登陸的情況下,只要Windows系統啟動就運行,那我們可以把我們的程序寫成一個Windows服務。
服務是能夠為各種用戶(包括本地用戶和遠程用戶)所用的,擁有用戶授權級進行管理的能力,並且不論用戶是否物理的與正在運行該應用程序的計算機相連都能正常執行。
下面,我將用一個簡單的例子說明如何用ATL來編寫Windows服務程序。
首先,我們新建一個Project。如圖一所示,選擇 "ATL COM AppWizard",工程名為:ServiceDemo。
圖一
點擊 "OK ", 出現圖二,選擇Service [EXE]。點擊 Finish。
圖二
完成以上的步驟,一個"什麼也不做"的服務就完成了!編譯… 打開"控制面板"->"管理工具"
->"服務",嗯?我們寫的服務怎麼沒有在服務管理器(service control manager ,簡稱(SCM))裡面列出來呢?呵呵,被我騙了?不要著急,我們還需要做一些工作。
首先先大概介紹一下向導為我們生成的代碼:
程序的進入點是全局函數_tWinMain, 仔細看一下這個函數,我們會發現當我們運行程序時,可以加上參數,例如: ServiceDemo /RegServer 或者 ServiceDemo -RegServer,這個是用來本地服務器注冊(Register as Local S Register as Service erver); ServiceDemo / Service 或者 ServiceDemo -Service,這個是服務的注冊(Register as Service);ServiceDemo /UnRegServer 或者 ServiceDemo -UnRegServer ,這個是服務的刪除。所以,當我們寫好了服務程序,只要運行的時候加上參數 Service ,這個時候在SCM中就會看到我們的服務了。可以試一下在SCM中對這個什麼也不做的服務"啟動","停止",改變一下它的啟動方式。
每次編碼後測試都要在命令行中加參數運行服務才可以在SCM中列出來是不是很麻煩呢?我再介紹一個偷懶的方法,選擇VC IDE的菜單Project -> Setting, 再選擇Custom Build 面板,如圖三:
圖三
在"$(TargetPath)" /RegServer的下面加上:"$(TargetPath)" /Service,這樣當我們每次編碼後編譯程序,就不用再在命令行中去加參數執行我們的服務程序完成服務的注冊了。
繼續介紹向導生成的代碼:向導為我們建立了一個類:CServiceModule,全局變量_Module就是這個類的實例。
Init():這個函數用於完成一些初始化工作;
Run():這個函數就是服務開始運行後的內容,我們接下來要修改的內容也就是從這裡入手。
Install():
看一下Install()的這一部分:
SC_HANDLE hService = ::CreateService(hSCM,
m_szServiceName,
m_szServiceName,SERVICE_ALL_ACCESS,
SERVICE_WIN32_OWN_PROCESS,
SERVICE_DEMAND_START,
SERVICE_ERROR_NORMAL,
szFilePath,
NULL,
NULL,
_T("RPCSS\0"),
NULL,
NULL);
函數的原型如下:
SC_HANDLE CreateService(
SC_HANDLE hSCManager, // handle to SCM database
LPCTSTR lpServiceName, // name of service to start
LPCTSTR lpDisplayName, // display name
DWORD dwDesiredAccess, // type of access to service
DWORD dwServiceType, // type of service
DWORD dwStartType, // when to start service
DWORD dwErrorControl, // severity of service failure
LPCTSTR lpBinaryPathName, // name of binary file
LPCTSTR lpLoadOrderGroup, // name of load ordering group
LPDWORD lpdwTagId, // tag identifier
LPCTSTR lpDependencies, // array of dependency names
LPCTSTR lpServiceStartName, // account name
LPCTSTR lpPassword // account password
);
具體的細節可以查一下MSDN,我只說一下第六個和第十一個參數。第六個參數是服務的啟動類型。
SERVICE_DEMAND_START是手動啟動,SERVICE_AUTO_START是自動啟動。第十一個參數是服務的依存關系,比如說服務的啟動想要依存SQL Server的啟動,那我們可以把這個參數寫成:
_T("MSSQLSERVER\0");
如果我們寫的服務不依存於其他的任何服務,那我們就將此參數設置為NULL就可以了。
接下來,我們為上面的"什麼也不做"的服務添加一個簡單的功能:做數字的累加,並且把結果寫到系統的"應用程序日志"中去。
首先,我們在類CServiceModule中添加一個成員變量:int n; 在Init()中對n進行初始化:
n = 0;
然後在類CServiceModule中添加一個成員函數Adder():
void CServiceModule::Adder()
{
n ++;
CString str;
str.Format("%i",n);
LogEvent(str);
}
編譯…出錯了。??,提示 CString 沒有定義,難道在ATL中無法用 MFC 嗎?讓我們看看設置:菜單Project->Setting ,General面板,默認的設置是:Use MFC in a Static Library。那為什麼不可以用MFC中的類呢?原來是頭文件沒有包含,這個不知道算不算 VC 的一個 Bug : ,設置中默認是用MFC,可是卻沒有包含相應的頭文件。那我們就自己加上好了。在StdAfx.h中加上:#include ,注意要加到#include 的前面,要不然又要編譯出錯了。接下來,我們在程序中再添加一個Timer,讓這個Timer每兩秒鐘調用一次Adder,做一次累加。在:
MSG msg;
while (GetMessage(&msg, 0, 0, 0))
DispatchMessage(&msg);
的前面加上代碼:
SetTimer(NULL,1,2000,(TIMERPROC)OnTimerProc);
注意一定要加在前面,因為要是加到while循環的下面,就沒有機會執行了。再添加一個全局的回調函數OnTimerProc 如下:
VOID CALLBACK OnTimerProc(HWND hwnd,UINT uMsg,UINT_PTR idEvent,DWORD dwTime)
{
_Module.Adder();
}
好了,大功告成。編譯,然後在SCM中啟動我們的服務。在控制面板中打開"事件查看器",看一下運行的結果,如下圖四:
圖四
好了,就寫到這裡吧,其他的內容大家自己深究吧。祝各位編程愉快!
本文配套源碼