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完整例子代码:




[cpp] view plain copy

 

 ​
​​

  1. #include <windows.h>  
  2. #include <tchar.h>  
  3. #include <strsafe.h>  
  4. #include "sample.h"  
  5.   
  6. #pragma comment(lib, "advapi32.lib")  
  7.   
  8. #define SVCNAME TEXT("SvcName")  
  9.   
  10. SERVICE_STATUS          gSvcStatus;   
  11. SERVICE_STATUS_HANDLE   gSvcStatusHandle;   
  12. HANDLE                  ghSvcStopEvent = NULL;  
  13.   
  14. VOID SvcInstall(void);  
  15. VOID WINAPI SvcCtrlHandler( DWORD );   
  16. VOID WINAPI SvcMain( DWORD, LPTSTR * );   
  17.   
  18. VOID ReportSvcStatus( DWORD, DWORD, DWORD );  
  19. VOID SvcInit( DWORD, LPTSTR * );   
  20. VOID SvcReportEvent( LPTSTR );  
  21.   
  22.   
  23. //  
  24. // Purpose:   
  25. //   Entry point for the process  
  26. //  
  27. // Parameters:  
  28. //   None  
  29. //   
  30. // Return value:  
  31. //   None  
  32. //  
  33. void __cdecl _tmain(int argc, TCHAR *argv[])   
  34. {   
  35.     // If command-line parameter is "install", install the service.   
  36.     // Otherwise, the service is probably being started by the SCM.  
  37.   
  38.     if( lstrcmpi( argv[1], TEXT("install")) == 0 )  
  39.     {  
  40.         SvcInstall();  
  41.         return;  
  42.     }  
  43.   
  44.     // TO_DO: Add any additional services for the process to this table.  
  45.     SERVICE_TABLE_ENTRY DispatchTable[] =   
  46.     {   
  47.         { SVCNAME, (LPSERVICE_MAIN_FUNCTION) SvcMain },   
  48.         { NULL, NULL }   
  49.     };   
  50.    
  51.     // This call returns when the service has stopped.   
  52.     // The process should simply terminate when the call returns.  
  53.   
  54.     if (!StartServiceCtrlDispatcher( DispatchTable ))   
  55.     {   
  56.         SvcReportEvent(TEXT("StartServiceCtrlDispatcher"));   
  57.     }   
  58. }   
  59.   
  60. //  
  61. // Purpose:   
  62. //   Installs a service in the SCM database  
  63. //  
  64. // Parameters:  
  65. //   None  
  66. //   
  67. // Return value:  
  68. //   None  
  69. //  
  70. VOID SvcInstall()  
  71. {  
  72.     SC_HANDLE schSCManager;  
  73.     SC_HANDLE schService;  
  74.     TCHAR szPath[MAX_PATH];  
  75.   
  76.     if( !GetModuleFileName( "", szPath, MAX_PATH ) )  
  77.     {  
  78.         printf("Cannot install service (%d)\n", GetLastError());  
  79.         return;  
  80.     }  
  81.   
  82.     // Get a handle to the SCM database.   
  83.    
  84.     schSCManager = OpenSCManager(   
  85.         NULL,                    // local computer  
  86.         NULL,                    // ServicesActive database   
  87.         SC_MANAGER_ALL_ACCESS);  // full access rights   
  88.    
  89.     if (NULL == schSCManager)   
  90.     {  
  91.         printf("OpenSCManager failed (%d)\n", GetLastError());  
  92.         return;  
  93.     }  
  94.   
  95.     // Create the service  
  96.   
  97.     schService = CreateService(   
  98.         schSCManager,              // SCM database   
  99.         SVCNAME,                   // name of service   
  100.         SVCNAME,                   // service name to display   
  101.         SERVICE_ALL_ACCESS,        // desired access   
  102.         SERVICE_WIN32_OWN_PROCESS, // service type   
  103.         SERVICE_DEMAND_START,      // start type   
  104.         SERVICE_ERROR_NORMAL,      // error control type   
  105.         szPath,                    // path to service's binary   
  106.         NULL,                      // no load ordering group   
  107.         NULL,                      // no tag identifier   
  108.         NULL,                      // no dependencies   
  109.         NULL,                      // LocalSystem account   
  110.         NULL);                     // no password   
  111.    
  112.     if (schService == NULL)   
  113.     {  
  114.         printf("CreateService failed (%d)\n", GetLastError());   
  115.         CloseServiceHandle(schSCManager);  
  116.         return;  
  117.     }  
  118.     else printf("Service installed successfully\n");   
  119.   
  120.     CloseServiceHandle(schService);   
  121.     CloseServiceHandle(schSCManager);  
  122. }  
  123.   
  124. //  
  125. // Purpose:   
  126. //   Entry point for the service  
  127. //  
  128. // Parameters:  
  129. //   dwArgc - Number of arguments in the lpszArgv array  
  130. //   lpszArgv - Array of strings. The first string is the name of  
  131. //     the service and subsequent strings are passed by the process  
  132. //     that called the StartService function to start the service.  
  133. //   
  134. // Return value:  
  135. //   None.  
  136. //  
  137. VOID WINAPI SvcMain( DWORD dwArgc, LPTSTR *lpszArgv )  
  138. {  
  139.     // Register the handler function for the service  
  140.   
  141.     gSvcStatusHandle = RegisterServiceCtrlHandler(   
  142.         SVCNAME,   
  143.         SvcCtrlHandler);  
  144.   
  145.     if( !gSvcStatusHandle )  
  146.     {   
  147.         SvcReportEvent(TEXT("RegisterServiceCtrlHandler"));   
  148.         return;   
  149.     }   
  150.   
  151.     // These SERVICE_STATUS members remain as set here  
  152.   
  153.     gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;   
  154.     gSvcStatus.dwServiceSpecificExitCode = 0;      
  155.   
  156.     // Report initial status to the SCM  
  157.   
  158.     ReportSvcStatus( SERVICE_START_PENDING, NO_ERROR, 3000 );  
  159.   
  160.     // Perform service-specific initialization and work.  
  161.   
  162.     SvcInit( dwArgc, lpszArgv );  
  163. }  
  164.   
  165. //  
  166. // Purpose:   
  167. //   The service code  
  168. //  
  169. // Parameters:  
  170. //   dwArgc - Number of arguments in the lpszArgv array  
  171. //   lpszArgv - Array of strings. The first string is the name of  
  172. //     the service and subsequent strings are passed by the process  
  173. //     that called the StartService function to start the service.  
  174. //   
  175. // Return value:  
  176. //   None  
  177. //  
  178. VOID SvcInit( DWORD dwArgc, LPTSTR *lpszArgv)  
  179. {  
  180.     // TO_DO: Declare and set any required variables.  
  181.     //   Be sure to periodically call ReportSvcStatus() with   
  182.     //   SERVICE_START_PENDING. If initialization fails, call  
  183.     //   ReportSvcStatus with SERVICE_STOPPED.  
  184.   
  185.     // Create an event. The control handler function, SvcCtrlHandler,  
  186.     // signals this event when it receives the stop control code.  
  187.   
  188.     ghSvcStopEvent = CreateEvent(  
  189.                          NULL,    // default security attributes  
  190.                          TRUE,    // manual reset event  
  191.                          FALSE,   // not signaled  
  192.                          NULL);   // no name  
  193.   
  194.     if ( ghSvcStopEvent == NULL)  
  195.     {  
  196.         ReportSvcStatus( SERVICE_STOPPED, NO_ERROR, 0 );  
  197.         return;  
  198.     }  
  199.   
  200.     // Report running status when initialization is complete.  
  201.   
  202.     ReportSvcStatus( SERVICE_RUNNING, NO_ERROR, 0 );  
  203.   
  204.     // TO_DO: Perform work until service stops.  
  205.   
  206.     while(1)  
  207.     {  
  208.         // Check whether to stop the service.  
  209.   
  210.         WaitForSingleObject(ghSvcStopEvent, INFINITE);  
  211.   
  212.         ReportSvcStatus( SERVICE_STOPPED, NO_ERROR, 0 );  
  213.         return;  
  214.     }  
  215. }  
  216.   
  217. //  
  218. // Purpose:   
  219. //   Sets the current service status and reports it to the SCM.  
  220. //  
  221. // Parameters:  
  222. //   dwCurrentState - The current state (see SERVICE_STATUS)  
  223. //   dwWin32ExitCode - The system error code  
  224. //   dwWaitHint - Estimated time for pending operation,   
  225. //     in milliseconds  
  226. //   
  227. // Return value:  
  228. //   None  
  229. //  
  230. VOID ReportSvcStatus( DWORD dwCurrentState,  
  231.                       DWORD dwWin32ExitCode,  
  232.                       DWORD dwWaitHint)  
  233. {  
  234.     static DWORD dwCheckPoint = 1;  
  235.   
  236.     // Fill in the SERVICE_STATUS structure.  
  237.   
  238.     gSvcStatus.dwCurrentState = dwCurrentState;  
  239.     gSvcStatus.dwWin32ExitCode = dwWin32ExitCode;  
  240.     gSvcStatus.dwWaitHint = dwWaitHint;  
  241.   
  242.     if (dwCurrentState == SERVICE_START_PENDING)  
  243.         gSvcStatus.dwControlsAccepted = 0;  
  244.     else gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;  
  245.   
  246.     if ( (dwCurrentState == SERVICE_RUNNING) ||  
  247.            (dwCurrentState == SERVICE_STOPPED) )  
  248.         gSvcStatus.dwCheckPoint = 0;  
  249.     else gSvcStatus.dwCheckPoint = dwCheckPoint++;  
  250.   
  251.     // Report the status of the service to the SCM.  
  252.     SetServiceStatus( gSvcStatusHandle, &gSvcStatus );  
  253. }  
  254.   
  255. //  
  256. // Purpose:   
  257. //   Called by SCM whenever a control code is sent to the service  
  258. //   using the ControlService function.  
  259. //  
  260. // Parameters:  
  261. //   dwCtrl - control code  
  262. //   
  263. // Return value:  
  264. //   None  
  265. //  
  266. VOID WINAPI SvcCtrlHandler( DWORD dwCtrl )  
  267. {  
  268.    // Handle the requested control code.   
  269.   
  270.    switch(dwCtrl)   
  271.    {    
  272.       case SERVICE_CONTROL_STOP:   
  273.          ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0);  
  274.   
  275.          // Signal the service to stop.  
  276.   
  277.          SetEvent(ghSvcStopEvent);  
  278.          ReportSvcStatus(gSvcStatus.dwCurrentState, NO_ERROR, 0);  
  279.            
  280.          return;  
  281.    
  282.       case SERVICE_CONTROL_INTERROGATE:   
  283.          break;   
  284.    
  285.       default:   
  286.          break;  
  287.    }   
  288.      
  289. }  
  290.   
  291. //  
  292. // Purpose:   
  293. //   Logs messages to the event log  
  294. //  
  295. // Parameters:  
  296. //   szFunction - name of function that failed  
  297. //   
  298. // Return value:  
  299. //   None  
  300. //  
  301. // Remarks:  
  302. //   The service must have an entry in the Application event log.  
  303. //  
  304. VOID SvcReportEvent(LPTSTR szFunction)   
  305. {   
  306.     HANDLE hEventSource;  
  307.     LPCTSTR lpszStrings[2];  
  308.     TCHAR Buffer[80];  
  309.   
  310.     hEventSource = RegisterEventSource(NULL, SVCNAME);  
  311.   
  312.     if( NULL != hEventSource )  
  313.     {  
  314.         StringCchPrintf(Buffer, 80, TEXT("%s failed with %d"), szFunction, GetLastError());  
  315.   
  316.         lpszStrings[0] = SVCNAME;  
  317.         lpszStrings[1] = Buffer;  
  318.   
  319.         ReportEvent(hEventSource,        // event log handle  
  320.                     EVENTLOG_ERROR_TYPE, // event type  
  321.                     0,                   // event category  
  322.                     SVC_ERROR,           // event identifier  
  323.                     NULL,                // no security identifier  
  324.                     2,                   // size of lpszStrings array  
  325.                     0,                   // no binary data  
  326.                     lpszStrings,         // array of strings  
  327.                     NULL);               // no binary data  
  328.   
  329.         DeregisterEventSource(hEventSource);  
  330.     }  
  331. }