這兩天想學習關於如何編寫windows服務程序的知識,就上網查了些資料。看了一篇文章《用C 語言編寫Windows 服務程序的五個步驟》。但可能由於譯者的疏忽,忘記了將關鍵代碼放入該文檔,導致初學者可能會看不懂。所以又查閱了相關資料。自己完成了《用C 語言編寫Windows 服務程序的五個步驟》中的樣例程序。
在本文中我希望能給初學者一些幫助,大致講一下編寫windows服務程序需要的知識。
首先Microsoft Windows 服務(即,以前的NT 服務)使您能夠創建在它們自己的Windows 會話中可長時間運行的可執行應用程序。這些服務可以在計算機啟動時自動啟動,可以暫停和重新啟動而且不顯示任何用戶界面。這使服務非常適合在服務器上使用,或任何時候,為了不影響在同一台計算機上工作的其他用戶,需要長時間運行功能時使用。還可以在不同於登錄用戶的特定用戶帳戶或默認計算機帳戶的安全上下文中運行服務。
服務是有狀態的,當我們使用windows自帶的服務管理程序sc.exe查看服務狀態時可以顯示服務的當前狀態,這個狀態是由我們在程序代碼中進行控制的。你最好在服務初始化的時候將服務設置為SERVICE_START_PENDING,當初始化完畢時設為SERVICE_RUNNING,這些狀態是系統自定義的狀態,可通過msdn查看其他狀態。這個狀態信息你會在sc.exe中看到。
在編寫windows服務程序過程中你需要關注的函數有:
1.首先是main函數,由於windows服務不需要界面,所以大部分程序為win32控制台應用程序,所以程序主函數為main 而不是WinMain()。在主函數要做的主要工作就是初始化一個SERVICE_TABLE_ENTRY 分派表結構體,然後調用StartServiceCtrlDispatcher();這將把調用進程的主線程轉換為控制分派器。該分派器啟動一個新線程,該線程運行分派表中對應於你的服務的ServiceMain()函數。ServiceMain()函數將在下面提到。
此過程示例代碼如下:
SERVICE_TABLE_ENTRY entrytable[2];
entrytable[0].lpServiceName="testservice";
entrytable[0].lpServiceProc=(LPSERVICE_MAIN_FUNCTION)ServiceMain;
entrytable[1].lpServiceName=NULL;
entrytable[1].lpServiceProc=NULL;
StartServiceCtrlDispatcher(entrytable);
在這之後系統將自動創建一個線程去執行ServiceMain函數的內容,你應該將你要執行的任務在ServiceMain中循環,這樣服務就開始運行了。
2.ServiceMain函數為void WINAPI ServiceMain(int argc, char** argv)格式的函數,函數名字可以任意定義。它的作用就是:將你需要執行的任務放到該函數中循環執行即可。這就是服務程序的工作函數。在ServiceMain執行你的任務前,需要給SERVICE_TABLE_ENTRY 分派表結構體進行賦值,注意由於此時服務還沒有開始執行你的任務所以我們將服務的狀態設置為SERVICE_START_PENDING,即正在初始化。我們進行如下賦值:
servicestatus.dwServiceType = SERVICE_WIN32;
servicestatus.dwCurrentState = SERVICE_START_PENDING;
servicestatus.dwControlsAccepted=SERVICE_ACCEPT_SHUTDOWN|SERVICE_ACCEPT_STOP;
//在本例中只接受系統關機和停止服務兩種控制命令
servicestatus.dwWin32ExitCode = 0;
servicestatus.dwServiceSpecificExitCode = 0;
servicestatus.dwCheckPoint = 0;
servicestatus.dwWaitHint = 0;
hstatus = ::RegisterServiceCtrlHandler("testservice", CtrlHandler);
CtrlHandler為void WINAPI CtrlHandler(DWORD request)型的函數,函數名字可以任意設定。將在下一點講到。
Hstatus為SERVICE_STATUS_HANDLE類型的全局變量。當需要改變服務狀態時SetServiceStatus()函數需要它做為參數來標識一個服務。
3. void WINAPI CtrlHandler(DWORD request),函數的主要功能是,接收系統傳遞的控制命令,比如當你通過sc.exe關閉服務時,該函數會收到SERVICE_CONTROL_STOP消息,你就可以對服務進行必要的管理。在本例子程序中就只接收SERVICE_ACCEPT_SHUTDOWN和SERVICE_ACCEPT_STOP消息,這是通過前面給servicestatus賦值設定的。
這樣一個基本的服務程序就完成了。
下面貼出我的示例代碼僅供參考。該代碼在vs2008中調試通過。本文結束的時候會附上如何安裝服務。
#include <stdio.h>
#include <Windows.h>
#define SLEEP_TIME 5000 //間隔時間
#define FILE_PATH "C:\\log.txt" //信息輸出文件
bool brun=false;
SERVICE_STATUS servicestatus;
SERVICE_STATUS_HANDLE hstatus;
int WriteToLog(char* str);
void WINAPI ServiceMain(int argc, char** argv);
void WINAPI CtrlHandler(DWORD request);
int InitService();
int WriteToLog(char* str)
{
FILE* pfile;
fopen_s(&pfile,FILE_PATH,"a+");
if (pfile==NULL)
{
return -1;
}
fprintf_s(pfile,"%s\n",str);
fclose(pfile);
return 0;
}
void WINAPI ServiceMain(int argc, char** argv)
{
servicestatus.dwServiceType = SERVICE_WIN32;
servicestatus.dwCurrentState = SERVICE_START_PENDING;
servicestatus.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN|SERVICE_ACCEPT_STOP;//在本例中只接受系統關機和停止服務兩種控制命令
servicestatus.dwWin32ExitCode = 0;
servicestatus.dwServiceSpecificExitCode = 0;
servicestatus.dwCheckPoint = 0;
servicestatus.dwWaitHint = 0;
hstatus = ::RegisterServiceCtrlHandler("testservice", CtrlHandler);
if (hstatus==0)
{
WriteToLog("RegisterServiceCtrlHandler failed");
return;
}
WriteToLog("RegisterServiceCtrlHandler success");
//向SCM 報告運行狀態
servicestatus.dwCurrentState = SERVICE_RUNNING;
SetServiceStatus (hstatus, &servicestatus);
//下面就開始任務循環了,你可以添加你自己希望服務做的工作
brun=true;
MEMORYSTATUS memstatus;
char str[100];
memset(str,'\0',100);
while (brun)
{
GlobalMemoryStatus(&memstatus);
int availmb=memstatus.dwAvailPhys/1024/1024;
sprintf_s(str,100,"available memory is %dMB",availmb);
WriteToLog(str);
Sleep(SLEEP_TIME);
}
WriteToLog("service stopped");
}
void WINAPI CtrlHandler(DWORD request)
{
switch (request)
{
case SERVICE_CONTROL_STOP:
brun=false;
servicestatus.dwCurrentState = SERVICE_STOPPED;
break;
case SERVICE_CONTROL_SHUTDOWN:
brun=false;
servicestatus.dwCurrentState = SERVICE_STOPPED;
break;
default:
break;
}
SetServiceStatus (hstatus, &servicestatus);
}
void main()
{
SERVICE_TABLE_ENTRY entrytable[2];
entrytable[0].lpServiceName="testservice";
entrytable[0].lpServiceProc=(LPSERVICE_MAIN_FUNCTION)ServiceMain;
entrytable[1].lpServiceName=NULL;
entrytable[1].lpServiceProc=NULL;
StartServiceCtrlDispatcher(entrytable);
}
如何安裝服務:
運行命令提示符cmd.exe
輸入sc create testservicename binpath= D:\test.exe
輸入sc start testservicename 啟動服務
輸入sc query 會在最底部顯示你的服務當前的狀態
輸入sc stop testservicename 停止服務
輸入sc delete testservicename刪除服務,該服務將在下次重啟後刪除,在重啟之前將不能注冊同一個名字的服務。
張鵬
HikVision
2011/11/22