新建自己的windows service

来源:互联网 发布:虚拟充值退款淘宝介入 编辑:程序博客网 时间:2024/04/29 15:09

首先明确一个概念,什么是windows service? windows service 是一些后台运行的服务,我们可以通过控制板面/管理/服务来查看当前计算机中已有的服务,同时可以控制这些服务开启和关闭。所以从使用的角度来看,这里的控制板面/管理/服务实际上是一个service 管理工具。同时windows提供了一个service的管理者SCM service control manager,它传递消息到各个service。

 

从代码的角度看,一个service一般是一个console工程,虽然有带有交互界面的service,但是后台服务还是使用console较多。这里又有两个概念:main和service main。前者很简单,就是console工程的main函数,而service main指的是提供service逻辑的入口。

 

MSDN中的main的例子是这样:

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"));
    }
}

我们暂时专注于红色的两个部分,SERVICE_TABLE_ENTRY是用于记录service name和service main的结构,必须以NULL和NULL结尾(用以判断结束吧,猜测),这个结构传入到StartServiceCtrlDispatcher函数中。这个函数内部会进入循环并等待SCM的消息,如果SCM要求启动某个service,函数就会根据SERVICE_TABLE_ENTRY来调用service main,所以SERVICE_TABLE_ENTRY是一个回调函数表。同时也可以发现,一个main或者说一个exe是可以提供多个service,同时没个service是工作的该进程中的不同线程内的,顺便说一句,main当然是主线程了。

main基本说完了,下面来专注于service main,还是MSDN的例子吧:

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 );
}

service开始服务,前面说了,我们可以控制service开启,暂停,关闭等,那么就需要让SCM有一个能控制service状态的方法(当然是回调了。。。),RegisterServiceCtrlHandler函数就是为服务注册控制回调函数的方法,例子中的SvcCtrlHandler就是回调函数handler。这里的注册最好是service main做的第一件事,因为SCM对于注册时间是有要求的,具体时间记不清了,需要查查。。。

同时service还需要向SCM提供自己状态,在上面的例子是通过ReportSvcStatus函数的,下面是它的实现,来源MSDN:

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 );
}
最主要的函数就是SetServiceStatus, 它负责向SCM提供当前serivce的状态,SERVICE_STATUS (gSvcStatus) 结构体用来封装所有关于service status的信息,里面的一些字段很有趣的, 比如dwCheckPoint , 用于在pending状态下反应service进度。

等等,所谓pending状态,实际上是指service开启,关闭不可能一下子就结束,除非真有人的代码是暴力terminatethread。。。在收到开启消息时,我们可以设置状态为SERVICE_START_PENDING, 等真正完成初始化在设置为SERVICE_RUNNING。最后就是我们的逻辑代码了这里的例子是封装在SvcInit函数里面。

我们再看一下控制函数的例子, 同样来自MSDN:

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;
   }
  
}

这个应该很清楚了,一般都是控制code的处理,这些code可以在MSDN上查到,同时user也可以自定义一些code。

 

最后我们还需要安装service,这里需要的API主要是createservice和OpenSCManager啦,比较简单,下面的代码还是来源于MSDN:

VOID SvcInstall()
{
    SC_HANDLE schSCManager;
    SC_HANDLE schService;
    TCHAR szPath[MAX_PATH];

    if( !GetModuleFileName( NULL, 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);
}


注意在main里面会调用这个函数阿,所以我们通过命令行就可以install某个exe上面的service了

原创粉丝点击