Windows后台服務程序編寫
1. 為什么要編寫后台服務程序
工作中有一個程序需要寫成后台服務的形式,摸索了一下,跟大家分享。
在windows操作系統中后台進程被稱為 service。 服務是一種應用程序類型,它在后台運行,通常沒有交互界面。服務應用程序通常可以 在本地和通過網絡為用戶提供一些功能,是為其它進程服務的。通過將程序注冊為服務,可以使自己的程序隨系統啟動而最先運行,隨系統關閉而最后停止。也可以windows服務管理器手動控制服務的啟動、關閉。
2. 編寫后台服務程序步驟
Windows提供了一套后台服務程序編程接口,用戶在編寫后台服務程序時需要遵循一定的編程框架,否則服務程序不能正常運行。
服務程序通常編寫成控制台類型的應用程序,總的來說,一個遵守服務控制管理程序接口要求的程序 包含下面三個函數:
1)服務程序主函數(main):調用系統函數 StartServiceCtrlDispatcher 連接程序主線程到服務控制管理程序。
和其它進程一樣,Main函數是服務進程的入口函數,服務控制管理器(SCM)在啟動服務程序時,會從服務程序的main函數開始執行。在進入點函數里面要完成ServiceMain的初始化,准確點說是初始化一個SERVICE_TABLE_ENTRY結構數組,這個結構記錄了這個服務程序里面所包含的所有服務的名稱和服務的進入點函數。然后再調用接口StartServiceCtrlDispatcher 。
Main函數的函數框架如下:
int _tmain(int argc, _TCHAR* argv[])
{
//服務入口點函數表
SERVICE_TABLE_ENTRY dispatchTable[]=
{
{TEXT(SZSERVICENAME),(LPSERVICE_MAIN_FUNCTION)Service_Main},
{ NULL,NULL}
};
if((argc>1)&&((*argv[1]=='-')||(argv[1]=="/")))
{
/*
參數個數大於1是安裝或者刪除服務,該操作是由用戶來執行的
當然也可以講這一部分功能另寫一個程序來實現
*/
if(_stricmp("install",argv[1]+1)==0)
{
installService();
}
else if(_stricmp("remove",argv[1]+1)==0)
{
removeService();
}
else if(_stricmp("debug",argv[1]+1)==0)
{
bDebugServer=true;
debugService(argc,argv);
}
}
else
{
/*
如果未能和上面的如何參數匹配,則可能是服務控制管理程序來啟動該程序。 立即調用StartServiceCtrlDispatcher 函數
*/
g_logout.Logout("%s\n", "enter StartServiceCtrlDispatcher...");
//通知服務管理器為每一個服務創建服務線程
if(!StartServiceCtrlDispatcher(dispatchTable))
g_logout.Logout("%s\n", "StartServiceCtrlDispatcher failed.");
else
g_logout.Logout("%s\n", "StartServiceCtrlDispatcher OK.");
}
return 0;
}
SCM啟動一個服務程序之后,它會等待該程序的主線程去調StartServiceCtrlDispatcher。如果那個函數在兩分鍾內沒有被調用,SCM將會認為這個服務有問題,並調用TerminateProcess去殺死這個進程。這就要求你的主線程要盡可能快的調用StartServiceCtrlDispatcher。
2)服務入口點函數(ServiceMain):執行服務初始化任務,同時執行多個服務的服務進程有多個服務入口函數。
在服務入口函數里,必須立即注冊服務控制回調函數。然后調用函數SetServiceStatus 通知SCM 服務現在的狀態,否則SCM會認為服務啟動失敗。
ServiceMain函數框架如下:
void WINAPI Service_Main(DWORD dwArgc, LPTSTR *lpszArgv)
{
//注冊服務控制處理函數
sshStatusHandle=RegisterServiceCtrlHandler(TEXT(SZSERVICENAME),Service_Ctrl);
//如果注冊失敗
if(!sshStatusHandle)
{
g_logout.Logout("%s\n", "RegisterServiceCtrlHandler failed...");
return;
}
//初始化 SERVICE_STATUS 結構中的成員
ssStatus.dwServiceType=SERVICE_WIN32_OWN_PROCESS; //可執行文件中只有一個單獨的服務
ssStatus.dwServiceSpecificExitCode=0;
ssStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; //允許用SCP去停止該服務
//更新服務狀態
if(ReportStatusToSCMgr(SERVICE_START_PENDING,//服務狀態,服務仍在初始化
NO_ERROR,
3000)) //等待時間
SvcInit( dwArgc, lpszArgv ); //服務初始化函數
else
g_logout.Logout("%s\n", "ReportStatusToSCMgr SERVICE_START_PENDING failed...");
}
服務初始化函數SvcInit:
該函數的寫法比較重要。在函數中創建一個等待事件,然后一直等待該事件。該線程在服務接到請求之前一直處於掛起狀態,直到接到服務停止消息。
VOID SvcInit( DWORD dwArgc, LPTSTR *lpszArgv)
{
/*創建事件*/
ghSvcStopEvent = CreateEvent(
NULL, // default security attributes
TRUE, // manual reset event
FALSE, // not signaled
NULL); // no name
if ( ghSvcStopEvent == NULL)
{
ReportSvcStatus( SERVICE_STOPPED, NO_ERROR, 0 );
return;
}
// Report running status when initialization is complete.
ReportSvcStatus( SERVICE_RUNNING, NO_ERROR, 0 );
// 在這里執行服務線程的創建...
while(1)
{
// 等待停止事件被觸發
WaitForSingleObject(ghSvcStopEvent, INFINITE);
ReportSvcStatus( SERVICE_STOPPED, NO_ERROR, 0 );
return;
}
}
3)控制服務處理程序函數(Handler):在服務程序收到控制請求時由控制分發線程引用。(此處是Service_Ctrl)。
void WINAPI Service_Ctrl(DWORD dwCtrlCode)
{
//處理控制請求碼
switch(dwCtrlCode)
{
//先更新服務狀態為 SERVICDE_STOP_PENDING,再停止服務。
case SERVICE_CONTROL_STOP:
ReportStatusToSCMgr(SERVICE_STOP_PENDING,NO_ERROR,500);
ServiceStop(); //由具體的服務程序實現
/*ssStatus.dwCurrentState=SERVICE_STOPPED;*/
//其它控制請求...
default:
break;
}
}
3. 注意事項
1)安裝服務可以另寫一個程序,也可以並在服務程序當中,比較簡單,這里就不介紹了。
2)服務初始化函數SvcInit里創建等待事件比較重要,在服務接收到服務停止消息后,觸發該事件。
3)Service_Main在等待事件觸發后立即返回,服務進程就會退出了。
附msdn完整例子代碼:
- #include <windows.h>
- #include <tchar.h>
- #include <strsafe.h>
- #include "sample.h"
- #pragma comment(lib, "advapi32.lib")
- #define SVCNAME TEXT("SvcName")
- SERVICE_STATUS gSvcStatus;
- SERVICE_STATUS_HANDLE gSvcStatusHandle;
- HANDLE ghSvcStopEvent = NULL;
- VOID SvcInstall(void);
- VOID WINAPI SvcCtrlHandler( DWORD );
- VOID WINAPI SvcMain( DWORD, LPTSTR * );
- VOID ReportSvcStatus( DWORD, DWORD, DWORD );
- VOID SvcInit( DWORD, LPTSTR * );
- VOID SvcReportEvent( LPTSTR );
- //
- // Purpose:
- // Entry point for the process
- //
- // Parameters:
- // None
- //
- // Return value:
- // None
- //
- void __cdecl _tmain(int argc, TCHAR *argv[])
- {
- // If command-line parameter is "install", install the service.
- // Otherwise, the service is probably being started by the SCM.
- if( lstrcmpi( argv[1], TEXT("install")) == 0 )
- {
- SvcInstall();
- return;
- }
- // TO_DO: Add any additional services for the process to this table.
- SERVICE_TABLE_ENTRY DispatchTable[] =
- {
- { SVCNAME, (LPSERVICE_MAIN_FUNCTION) SvcMain },
- { NULL, NULL }
- };
- // This call returns when the service has stopped.
- // The process should simply terminate when the call returns.
- if (!StartServiceCtrlDispatcher( DispatchTable ))
- {
- SvcReportEvent(TEXT("StartServiceCtrlDispatcher"));
- }
- }
- //
- // Purpose:
- // Installs a service in the SCM database
- //
- // Parameters:
- // None
- //
- // Return value:
- // None
- //
- VOID SvcInstall()
- {
- SC_HANDLE schSCManager;
- SC_HANDLE schService;
- TCHAR szPath[MAX_PATH];
- if( !GetModuleFileName( "", szPath, MAX_PATH ) )
- {
- printf("Cannot install service (%d)\n", GetLastError());
- return;
- }
- // Get a handle to the SCM database.
- schSCManager = OpenSCManager(
- NULL, // local computer
- NULL, // ServicesActive database
- SC_MANAGER_ALL_ACCESS); // full access rights
- if (NULL == schSCManager)
- {
- printf("OpenSCManager failed (%d)\n", GetLastError());
- return;
- }
- // Create the service
- schService = CreateService(
- schSCManager, // SCM database
- SVCNAME, // name of service
- SVCNAME, // service name to display
- SERVICE_ALL_ACCESS, // desired access
- SERVICE_WIN32_OWN_PROCESS, // service type
- SERVICE_DEMAND_START, // start type
- SERVICE_ERROR_NORMAL, // error control type
- szPath, // path to service's binary
- NULL, // no load ordering group
- NULL, // no tag identifier
- NULL, // no dependencies
- NULL, // LocalSystem account
- NULL); // no password
- if (schService == NULL)
- {
- printf("CreateService failed (%d)\n", GetLastError());
- CloseServiceHandle(schSCManager);
- return;
- }
- else printf("Service installed successfully\n");
- CloseServiceHandle(schService);
- CloseServiceHandle(schSCManager);
- }
- //
- // Purpose:
- // Entry point for the service
- //
- // Parameters:
- // dwArgc - Number of arguments in the lpszArgv array
- // lpszArgv - Array of strings. The first string is the name of
- // the service and subsequent strings are passed by the process
- // that called the StartService function to start the service.
- //
- // Return value:
- // None.
- //
- VOID WINAPI SvcMain( DWORD dwArgc, LPTSTR *lpszArgv )
- {
- // Register the handler function for the service
- gSvcStatusHandle = RegisterServiceCtrlHandler(
- SVCNAME,
- SvcCtrlHandler);
- if( !gSvcStatusHandle )
- {
- SvcReportEvent(TEXT("RegisterServiceCtrlHandler"));
- return;
- }
- // These SERVICE_STATUS members remain as set here
- gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
- gSvcStatus.dwServiceSpecificExitCode = 0;
- // Report initial status to the SCM
- ReportSvcStatus( SERVICE_START_PENDING, NO_ERROR, 3000 );
- // Perform service-specific initialization and work.
- SvcInit( dwArgc, lpszArgv );
- }
- //
- // Purpose:
- // The service code
- //
- // Parameters:
- // dwArgc - Number of arguments in the lpszArgv array
- // lpszArgv - Array of strings. The first string is the name of
- // the service and subsequent strings are passed by the process
- // that called the StartService function to start the service.
- //
- // Return value:
- // None
- //
- VOID SvcInit( DWORD dwArgc, LPTSTR *lpszArgv)
- {
- // TO_DO: Declare and set any required variables.
- // Be sure to periodically call ReportSvcStatus() with
- // SERVICE_START_PENDING. If initialization fails, call
- // ReportSvcStatus with SERVICE_STOPPED.
- // Create an event. The control handler function, SvcCtrlHandler,
- // signals this event when it receives the stop control code.
- ghSvcStopEvent = CreateEvent(
- NULL, // default security attributes
- TRUE, // manual reset event
- FALSE, // not signaled
- NULL); // no name
- if ( ghSvcStopEvent == NULL)
- {
- ReportSvcStatus( SERVICE_STOPPED, NO_ERROR, 0 );
- return;
- }
- // Report running status when initialization is complete.
- ReportSvcStatus( SERVICE_RUNNING, NO_ERROR, 0 );
- // TO_DO: Perform work until service stops.
- while(1)
- {
- // Check whether to stop the service.
- WaitForSingleObject(ghSvcStopEvent, INFINITE);
- ReportSvcStatus( SERVICE_STOPPED, NO_ERROR, 0 );
- return;
- }
- }
- //
- // Purpose:
- // Sets the current service status and reports it to the SCM.
- //
- // Parameters:
- // dwCurrentState - The current state (see SERVICE_STATUS)
- // dwWin32ExitCode - The system error code
- // dwWaitHint - Estimated time for pending operation,
- // in milliseconds
- //
- // Return value:
- // None
- //
- VOID ReportSvcStatus( DWORD dwCurrentState,
- DWORD dwWin32ExitCode,
- DWORD dwWaitHint)
- {
- static DWORD dwCheckPoint = 1;
- // Fill in the SERVICE_STATUS structure.
- gSvcStatus.dwCurrentState = dwCurrentState;
- gSvcStatus.dwWin32ExitCode = dwWin32ExitCode;
- gSvcStatus.dwWaitHint = dwWaitHint;
- if (dwCurrentState == SERVICE_START_PENDING)
- gSvcStatus.dwControlsAccepted = 0;
- else gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
- if ( (dwCurrentState == SERVICE_RUNNING) ||
- (dwCurrentState == SERVICE_STOPPED) )
- gSvcStatus.dwCheckPoint = 0;
- else gSvcStatus.dwCheckPoint = dwCheckPoint++;
- // Report the status of the service to the SCM.
- SetServiceStatus( gSvcStatusHandle, &gSvcStatus );
- }
- //
- // Purpose:
- // Called by SCM whenever a control code is sent to the service
- // using the ControlService function.
- //
- // Parameters:
- // dwCtrl - control code
- //
- // Return value:
- // None
- //
- VOID WINAPI SvcCtrlHandler( DWORD dwCtrl )
- {
- // Handle the requested control code.
- switch(dwCtrl)
- {
- case SERVICE_CONTROL_STOP:
- ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0);
- // Signal the service to stop.
- SetEvent(ghSvcStopEvent);
- ReportSvcStatus(gSvcStatus.dwCurrentState, NO_ERROR, 0);
- return;
- case SERVICE_CONTROL_INTERROGATE:
- break;
- default:
- break;
- }
- }
- //
- // Purpose:
- // Logs messages to the event log
- //
- // Parameters:
- // szFunction - name of function that failed
- //
- // Return value:
- // None
- //
- // Remarks:
- // The service must have an entry in the Application event log.
- //
- VOID SvcReportEvent(LPTSTR szFunction)
- {
- HANDLE hEventSource;
- LPCTSTR lpszStrings[2];
- TCHAR Buffer[80];
- hEventSource = RegisterEventSource(NULL, SVCNAME);
- if( NULL != hEventSource )
- {
- StringCchPrintf(Buffer, 80, TEXT("%s failed with %d"), szFunction, GetLastError());
- lpszStrings[0] = SVCNAME;
- lpszStrings[1] = Buffer;
- ReportEvent(hEventSource, // event log handle
- EVENTLOG_ERROR_TYPE, // event type
- 0, // event category
- SVC_ERROR, // event identifier
- NULL, // no security identifier
- 2, // size of lpszStrings array
- 0, // no binary data
- lpszStrings, // array of strings
- NULL); // no binary data
- DeregisterEventSource(hEventSource);
- }
- }
http://blog.csdn.net/chence19871/article/details/42169443