新建自己的windows service

    技术2022-05-19  33

    首先明确一个概念,什么是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了


    最新回复(0)