zoukankan      html  css  js  c++  java
  • 如何写一个windows服务?参考win sys program 13章,补充一些书中遗漏的注意点

    1. 创建windows console应用程序。vs自带的windows service模板创建出来的项目看不懂。

    2. _tmain函数这样写:

    Code: Select all
    /*  Main routine that starts the service control dispatcher */
    VOID _tmain (int argc, LPTSTR argv[])
    {
       SERVICE_TABLE_ENTRY DispatchTable[] =
       {
          { ServiceName,            ServiceMain   },
          { NULL,                  NULL }
       };

       StartServiceCtrlDispatcher (DispatchTable);
       return;
    }


    关键就是ServiceMain函数在这里定义了。

    3. 然后就是ServiceMain函数:

    Code: Select all
    /*   ServiceMain entry point, called when the service is created by
       the main program.  */
    void WINAPI ServiceMain (DWORD argc, LPTSTR argv[])
    {
       HANDLE hFile = NULL;
       DWORD Context = 1;
       size_t bytes_need_write = 0;
       DWORD bytes_written = 0;
       TCHAR write_buffer[255];
       SYSTEMTIME cur_time;
       DWORD n;
       LARGE_INTEGER file_size;

       // init logger
       logger_set_root_directory(TEXT("C:\\"));

       hServStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
       hServStatus.dwCurrentState = SERVICE_START_PENDING;
       hServStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
       hServStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
       hServStatus.dwServiceSpecificExitCode = 0;
       hServStatus.dwCheckPoint = 0;
       // in 2000ms, we should increment dwCheckPoint or set new status
       // while current status is a PENDING start/continue/stop state
       hServStatus.dwWaitHint = 5000;

       /* Warning. Older VC++ version do not have RegisterServiceCtrlHandlerEx
        * defined. You can use RegisterServiceCtrlHandler just as well */
    #ifdef RegisterServiceCtrlHandlerEx
       hSStat = RegisterServiceCtrlHandlerEx(ServiceName, ServerCtrlHandlerEx, &Context);
    #else
       hSStat = RegisterServiceCtrlHandler(ServiceName, ServerCtrlHandler);
    #endif

       if (hSStat == NULL) {
          GET_ERROR_CODE(n);
          UTILS_RIF_WITH_LOG(FALSE, LOG_LEVEL_ERROR, __SDFILE__, __LINE__,
             TEXT("Register service ctrl handle failed. Reason: %s\n"),
             utils_format_error_string(n));
       }

       // PENDING start
       if (!SetServiceStatus (hSStat, &hServStatus)) {
          GET_ERROR_CODE(n);
          UTILS_RIF_WITH_LOG(FALSE, LOG_LEVEL_ERROR, __SDFILE__, __LINE__,
             TEXT("Set service status failed. Reason: %s\n"),
             utils_format_error_string(n));
       }

       // Open the file and log current time
       hFile = CreateFile(TEXT("C:\\testserv.txt"), GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
       if (hFile == INVALID_HANDLE_VALUE) {
          GET_ERROR_CODE(n);
          UTILS_RIF_WITH_LOG(FALSE, LOG_LEVEL_ERROR, __SDFILE__, __LINE__,
             TEXT("Create testserv file failed. Reason: %s\n"),
             utils_format_error_string(n));
       }

       // Seek to the end of file
       UTILS_RETURN_IF_FAIL(GetFileSizeEx(hFile, &file_size));
       UTILS_RETURN_IF_FAIL(SetFilePointerEx(hFile, file_size, NULL, FILE_BEGIN));

       GetLocalTime(&cur_time);
       StringCchPrintf(write_buffer, _countof(write_buffer), TEXT("TestServ started, write time: %d-%d-%d %d:%d:%d\n"),
          cur_time.wYear, cur_time.wMonth, cur_time.wDay, cur_time.wHour, cur_time.wMinute, cur_time.wSecond);
       StringCchLength(write_buffer, _countof(write_buffer), &bytes_need_write);
       WriteFile(hFile, write_buffer, bytes_need_write * sizeof(TCHAR), &bytes_written, NULL);

       hServStatus.dwCheckPoint = 0;
       hServStatus.dwWin32ExitCode = NO_ERROR;
       hServStatus.dwServiceSpecificExitCode = 0;
       hServStatus.dwCurrentState = SERVICE_RUNNING;
       if (!SetServiceStatus(hSStat, &hServStatus)) {
          GET_ERROR_CODE(n);
          UTILS_RIF_WITH_LOG(FALSE, LOG_LEVEL_ERROR, __SDFILE__, __LINE__,
             TEXT("Set service status failed. Reason: %s\n"),
             utils_format_error_string(n));
       }

       // block here until service stop
       while (!service_terminate) {
          Sleep(1000);
       }

       // stop service
       GetLocalTime(&cur_time);
       StringCchPrintf(write_buffer, _countof(write_buffer), TEXT("TestServ stopped, write time: %d-%d-%d %d:%d:%d\n"),
          cur_time.wYear, cur_time.wMonth, cur_time.wDay, cur_time.wHour, cur_time.wMinute, cur_time.wSecond);
       StringCchLength(write_buffer, _countof(write_buffer), &bytes_need_write);
       WriteFile(hFile, write_buffer, bytes_need_write * sizeof(TCHAR), &bytes_written, NULL);
       CloseHandle(hFile);

       hServStatus.dwCheckPoint = 0;
       hServStatus.dwWin32ExitCode = NO_ERROR;
       hServStatus.dwServiceSpecificExitCode = 0;
       hServStatus.dwCurrentState = SERVICE_STOPPED;
       if (!SetServiceStatus(hSStat, &hServStatus)) {
          GET_ERROR_CODE(n);
          UTILS_RIF_WITH_LOG(FALSE, LOG_LEVEL_ERROR, __SDFILE__, __LINE__,
             TEXT("Set service status failed. Reason: %s\n"),
             utils_format_error_string(n));
       }
       
       return;
    }


    有几个关键点:

    A. hServStatus是一个SERVICE_STRUCTURE,这个structure非常重要,定义了很多关键的数据。主要是:
    (1) dwControlAccepted,不支持PAUSE和CONTINUE就不要写上。

    (2) dwWin32ExitCode这个member容易混淆。如果当前service的状态是PENDING(start pending/pause pending/continue pending/stop pending),那么可以设置为ERROR_SERVICE_SPECIFIC_ERROR, 这表示pending的时候如果出错了,让windows SCM去check dwServiceSpecificExitCode member来得到出错码。但是如果状态已经成功转换到了RUNNING/STOPPED,那么,要设置这个dwWin32ExitCode为 NO_ERROR,此时windows SCM认为service状态转换没有错误发生,dwServiceSpecificExitCode的值被ignore。如果 dwWin32ExitCode还为ERROR_SERVICE_SPECIFIC_ERROR的话,windows SCM就会认为service状态转换出错,然后把dwServiceSpecificExitCode作为出错码取出来报告。这就是为什么上面代码 中,RUNNING和STOPPED状态转换完成的时候,要设置这两个member的原因。

    (3) dwCheckPoint,也是用于pending状态的。pending状态的时候,windows SCM要知道service当前是否alive,依靠的就是周期性的check这个dwCheckPoint member。所以,我们的service在pending状态的时候,要经常使用SetServiceStatus来更新这个 dwCheckPoint,否则windows SCM会认为service down,从而强制stop service。此外,当service完成pending状态切换到新状态后,记得重置dwCheckPoint为0.

    (4) dwWaitHint,这个member定义一个时间(单位毫秒),在service处于pending状态时,如果过了这里定义的时间,windows SCM发现service的dwCheckPoint没有更新,或者service没有切换到新的非pending状态,那么,windows SCM就会强制stop service。

    B. RegisterServiceCtrlHandlerEx函数。用来注册SCM动作的响应函数。也就是stop/continue这些命令的响应。例子:

    Code: Select all
    /*   Control Handler Function */
    #ifdef RegisterServiceCtrlHandlerEx
    DWORD WINAPI ServerCtrlHandlerEx( DWORD dwControl, DWORD dwEventType,
                           LPVOID lpEventData, LPVOID lpContext)
    #else
    DWORD WINAPI ServerCtrlHandler( DWORD dwControl)
    #endif
    // requested control code
    {
       DWORD n;

       switch (dwControl) {
       case SERVICE_CONTROL_SHUTDOWN:
       case SERVICE_CONTROL_STOP:
          service_terminate = TRUE;
          hServStatus.dwCheckPoint++;
          hServStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
          hServStatus.dwServiceSpecificExitCode = 0;
          hServStatus.dwCurrentState = SERVICE_STOP_PENDING;
          if (!SetServiceStatus(hSStat, &hServStatus)) {
             GET_ERROR_CODE(n);
             UTILS_RVIF_WITH_LOG(FALSE, NO_ERROR, LOG_LEVEL_ERROR, __SDFILE__, __LINE__,
                TEXT("Set service status failed. Reason: %s\n"),
                utils_format_error_string(n));
          }
          return NO_ERROR;
       case SERVICE_CONTROL_PAUSE:
          break;
       case SERVICE_CONTROL_CONTINUE:
          break;
       case SERVICE_CONTROL_INTERROGATE:
          return NO_ERROR;
       default:
          if (dwControl > 127 && dwControl < 256) /* User Defined */
          break;
       }
       return ERROR_CALL_NOT_IMPLEMENTED;
    }


    (1) 对于没有handle的control命令,return ERROR_CALL_NOT_IMPLEMENTED,对于handle的,返回NO_ERROR。例外是对于 SERVICE_CONTROL_INTERROGATE,也要返回NO_ERROR,即使我们没有handle这个control。更具体的信息查看 MSDN。

    (2) SERVICE_CONTROL_SHUTDOWN是系统关机的时候SCM发送给service的。和SERVICE_CONTROL_STOP一样,可以在这里设置一个pending状态,在ServiceMain中真正stop了,设置STOPPED状态。

    4. service本身的关键代码就是这些,下面是使用SCM如何创建一个service:

    Code: Select all
    #define SVCNAME TEXT("EricTestService")

    int _tmain(int argc, PTSTR argv[], PTSTR env[])
    {
       DWORD errcode = 0;
       SC_HANDLE hManager = NULL;
       SC_HANDLE hService = NULL;

       setlocale(LC_ALL, "CHS");

       // Open SCManager
       hManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
       if (NULL == hManager) {
          errcode = GetLastError();
          _tprintf(TEXT("Open service manager failed. Reason: %s\n"), utils_format_error_string(errcode));
          goto failed;
       }

       // create service
       hService = CreateService(
            hManager,                  // SCM database
            SVCNAME,                   // name of service
            SVCNAME,                   // service name to display
            SERVICE_ALL_ACCESS,        // desired access
            SERVICE_WIN32_OWN_PROCESS, // service type
            SERVICE_AUTO_START,      // start type
            SERVICE_ERROR_NORMAL,      // error control type
          TEXT("C:\\ScheduleDownload\\Practise\\TestServ\\Debug\\TestServ.exe"),  // path to service's binary
            NULL,                      // no load ordering group
            NULL,                      // no tag identifier
            NULL,                      // no dependencies
            NULL,                      // LocalSystem account
            NULL);                     // no password

       if (NULL == hService) {
          errcode = GetLastError();
          _tprintf(TEXT("Create service failed. Reason: %s\n"), utils_format_error_string(errcode));
          CloseServiceHandle(hManager);
          goto failed;
       }

       CloseServiceHandle(hService);
       CloseServiceHandle(hManager);

       return 0;

    failed:
       // wait key
       _getch();
       return 1;
    }


    这 就比较简单了,使用OpenSCManager打开SCM,使用CreateService创建一个service,此时在windows的服务中就能看 到了。start type中,设定为SERVICE_AUTO_START就会在开机时启动(不需要用户登录就会启动的,已经测试过了)。其他的函数比如 StartService/DeleteService/QueryService...看看MSDN就OK了。
  • 相关阅读:
    jQuery 语法
    jQuery 简介
    把数据存储到 XML 文件
    XML 注意事项
    XML DOM (Document Object Model) 定义了访问和操作 XML 文档的标准方法。
    通过 PHP 生成 XML
    XML 命名空间(XML Namespaces)
    XML to HTML
    XMLHttpRequest 对象
    使用 XSLT 显示 XML
  • 原文地址:https://www.cnblogs.com/super119/p/2011347.html
Copyright © 2011-2022 走看看