講述C語言編寫Windows服務(wù)程序
Windows 服務(wù)被設(shè)計用于需要在后臺運行的應(yīng)用程序以及實現(xiàn)沒有用戶交互的任務(wù)。為了學(xué)習(xí)這種控制臺應(yīng)用程序的基礎(chǔ)知識,C(不是C++)是最佳選擇。
本文將建立并實現(xiàn)一個簡單的服務(wù)程序,其功能是查詢系統(tǒng)中可用物理內(nèi)存數(shù)量,然后將結(jié)果寫入一個文本文件。最后,你可以用所學(xué)知識編寫自己的 Windows 服務(wù)。
當(dāng)初我寫第一個 NT 服務(wù)時,我到 MSDN 上找例子。在那里我找到了一篇 Nigel Thompson 寫的文章:“Creating a Simple Win32 Service in C++”,這篇文章附帶一個 C++ 例子。雖然這篇文章很好地解釋了服務(wù)的開發(fā)過程,但是,我仍然感覺缺少我需要的重要信息。我想理解通過什么框架,調(diào)用什么函數(shù),以及何時調(diào)用,但 C++ 在這方面沒有讓我輕松多少。
面向?qū)ο蟮姆椒ü倘环奖悖捎谟妙悓Φ讓?Win32 函數(shù)調(diào)用進行了封裝,它不利于學(xué)習(xí)服務(wù)程序的基本知識。這就是為什么我覺得 C 更加適合于編寫初級服務(wù)程序或者實現(xiàn)簡單后臺任務(wù)的服務(wù)。在你對服務(wù)程序有了充分透徹的理解之后,用 C++ 編寫才能游刃有余。當(dāng)我離開原來的工作崗位,不得不向另一個人轉(zhuǎn)移我的知識的時候,利用我用 C 所寫的例子就非常容易解釋 NT 服務(wù)之所以然。
服務(wù)是一個運行在后臺并實現(xiàn)勿需用戶交互的任務(wù)的控制臺程序。Windows NT/2000/XP 操作系統(tǒng)提供為服務(wù)程序提供專門的支持。人們可以用服務(wù)控制面板來配置安裝好的服務(wù)程序,也就是 Windows 2000/XP 控制面板|管理工具中的“服務(wù)”(或在“開始”|“運行”對話框中輸入 services.msc /s——譯者注)。可以將服務(wù)配置成操作系統(tǒng)啟動時自動啟動,這樣你就不必每次再重啟系統(tǒng)后還要手動啟動服務(wù)。
本文將首先解釋如何創(chuàng)建一個定期查詢可用物理內(nèi)存并將結(jié)果寫入某個文本文件的服務(wù)。然后指導(dǎo)你完成生成,安裝和實現(xiàn)服務(wù)的整個過程。
主函數(shù)和全局定義
首先,包含所需的頭文件。例子要調(diào)用 Win32 函數(shù)(windows.h)和磁盤文件寫入(stdio.h):
#include
#include
接著,定義兩個常量:
#define SLEEP_TIME 5000
#define LOGFILE "C:\\MyServices\\memstatus.txt"
SLEEP_TIME 指定兩次連續(xù)查詢可用內(nèi)存之間的毫秒間隔。在第二步中編寫服務(wù)工作循環(huán)的時候要使用該常量。
LOGFILE 定義日志文件的路徑,你將會用 WriteToLog 函數(shù)將內(nèi)存查詢的結(jié)果輸出到該文件,WriteToLog 函數(shù)定義如下:
- int WriteToLog(char* str)
- {
- FILE* log;
- log = fopen(LOGFILE, "a+");
- if (log == NULL)
- return -1;
- fprintf(log, "%s ", str);
- fclose(log);
- return 0;
- }
聲明幾個全局變量,以便在程序的多個函數(shù)之間共享它們值。此外,做一個函數(shù)的前向定義:
- SERVICE_STATUS ServiceStatus;
- SERVICE_STATUS_HANDLE hStatus;
- void ServiceMain(int argc, char** argv);
- void ControlHandler(DWORD request);
- int InitService();
現(xiàn)在,準(zhǔn)備工作已經(jīng)就緒,你可以開始編碼了。服務(wù)程序控制臺程序的一個子集。因此,開始你可以定義一個 main 函數(shù),它是程序的入口點。對于服務(wù)程序來說,main 的代碼令人驚訝地簡短,因為它只創(chuàng)建分派表并啟動控制分派機。
- void main()
- {
- SERVICE_TABLE_ENTRY ServiceTable[2];
- ServiceTable[0].lpServiceName = "MemoryStatus";
- ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;
- ServiceTable[1].lpServiceName = NULL;
- ServiceTable[1].lpServiceProc = NULL;
- // 啟動服務(wù)的控制分派機線程
- StartServiceCtrlDispatcher(ServiceTable);
- }
一個程序可能包含若干個服務(wù)。每一個服務(wù)都必須列于專門的分派表中(為此該程序定義了一個 ServiceTable 結(jié)構(gòu)數(shù)組)。這個表中的每一項都要在 SERVICE_TABLE_ENTRY 結(jié)構(gòu)之中。它有兩個域:
lpServiceName: 指向表示服務(wù)名稱字符串的指針;當(dāng)定義了多個服務(wù)時,那么這個域必須指定;lpServiceProc: 指向服務(wù)主函數(shù)的指針(服務(wù)入口點);
分派表的最后一項必須是服務(wù)名和服務(wù)主函數(shù)域的 NULL 指針,文本例子程序中只宿主一個服務(wù),所以服務(wù)名的定義是可選的。
服務(wù)控制管理器(SCM:Services Control Manager)是一個管理系統(tǒng)所有服務(wù)的進程。當(dāng) SCM 啟動某個服務(wù)時,它等待某個進程的主線程來調(diào)用 StartServiceCtrlDispatcher 函數(shù)。將分派表傳遞給 StartServiceCtrlDispatcher。這將把調(diào)用進程的主線程轉(zhuǎn)換為控制分派器。該分派器啟動一個新線程,該線程運行分派表中每個服務(wù)的 ServiceMain 函數(shù)(本文例子中只有一個服務(wù))分派器還監(jiān)視程序中所有服務(wù)的執(zhí)行情況。然后分派器將控制請求從 SCM 傳給服務(wù)。
注意:如果 StartServiceCtrlDispatcher 函數(shù)30秒沒有被調(diào)用,便會報錯,為了避免這種情況,我們必須在 ServiceMain 函數(shù)中(參見本文例子)或在非主函數(shù)的單獨線程中初始化服務(wù)分派表。本文所描述的服務(wù)不需要防范這樣的情況。
分派表中所有的服務(wù)執(zhí)行完之后(例如,用戶通過“服務(wù)”控制面板程序停止它們),或者發(fā)生錯誤時。StartServiceCtrlDispatcher 調(diào)用返回。然后主進程終止。
【編輯推薦】