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
“, “enter StartServiceCtrlDispatcher…”);

//通知服务管理器为每一个服务创建服务线程

if(!StartServiceCtrlDispatcher(dispatchTable))

g_logout.Logout(“%s
“, “StartServiceCtrlDispatcher failed.”);

else

g_logout.Logout(“%s
“, “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
“, “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
“, “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
 
 windows后台服务程序编写-风君雪科技博客windows后台服务程序编写-风君雪科技博客

#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)
“, 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)
“, 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)
“, GetLastError());   

        CloseServiceHandle(schSCManager);  
        return;  
    }  
    else printf(“Service installed successfully
“);   

  
    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