Создание своего Windows Service

    Я решил провести один эксперимент, суть его пока не могу разглашать, но по результатам обязательно опишу его))) Для этого эксперимента, мне нужно написать приложение которое работает как сервис в Windows.

    Думаю описывать как создавать обычный Win32 Console Application проект в Visual Studio нет надобности )))

    С чего начинается сервис?


    Конечно же с ф-ции _tmain:
    int _tmain(int argc, _TCHAR* argv[]) {
      SERVICE_TABLE_ENTRY ServiceTable[1];
      ServiceTable[0].lpServiceName = serviceName;
      ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;

      StartServiceCtrlDispatcher(ServiceTable); 
    }


    SERVICE_TABLE_ENTRY это структура, которая описывает точку входа для сервис менеджера, в данном случаи вход будет происходить через ф-цию ServiceMain. Функция StartServiceCtrlDispatcher собственно связывает наш сервис с SCM (Service Control Manager)

    Точка входа сервиса


    Прежде чем описывать ф-цию нам понадобиться две глобальные переменные:
    SERVICE_STATUS ServiceStatus;
    SERVICE_STATUS_HANDLE hStatus;


    Структура SERVICE_STATUS используется для оповещения SCM текущего статуса сервиса. О полях и их значениях детальней можно прочитать на MSDN
    Ниже приведу полный текст ф-ции ServiceMain:
    void ServiceMain(int argc, char** argv) {
      int error;
      int i = 0;

      serviceStatus.dwServiceType    = SERVICE_WIN32_OWN_PROCESS;
      serviceStatus.dwCurrentState    = SERVICE_START_PENDING;
      serviceStatus.dwControlsAccepted  = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
      serviceStatus.dwWin32ExitCode   = 0;
      serviceStatus.dwServiceSpecificExitCode = 0;
      serviceStatus.dwCheckPoint     = 0;
      serviceStatus.dwWaitHint      = 0;

      serviceStatusHandle = RegisterServiceCtrlHandler(serviceName, (LPHANDLER_FUNCTION)ControlHandler);
      if (serviceStatusHandle == (SERVICE_STATUS_HANDLE)0) {
        return;
      } 

      error = InitService();
      if (error) {
        serviceStatus.dwCurrentState    = SERVICE_STOPPED;
        serviceStatus.dwWin32ExitCode   = -1;
        SetServiceStatus(serviceStatusHandle, &serviceStatus);
        return;
      }
      
      serviceStatus.dwCurrentState = SERVICE_RUNNING;
      SetServiceStatus (serviceStatusHandle, &serviceStatus);

      while (serviceStatus.dwCurrentState == SERVICE_RUNNING)
      {
        char buffer[255];
        sprintf_s(buffer, "%u", i);
        int result = addLogMessage(buffer);
        if (result)  {
          serviceStatus.dwCurrentState    = SERVICE_STOPPED;
          serviceStatus.dwWin32ExitCode   = -1;
          SetServiceStatus(serviceStatusHandle, &serviceStatus);
          return;
        }
        i++;
      }

      return;
    }


    Логика этой ф-ции проста. Сначала регистрируем ф-цию которая будет обрабатывать управляющие запросы от SCM, например, запрос на остановку. Регистрация производиться при помощи ф-ции RegisterServiceCtrlHandler. И при корректном запуске сервиса пишем в файлик значения переменой i.
    Для изменения статуса сервиса используется ф-ция SetServiceStatus.
    Теперь опишем ф-цию по обработке запросов:
    void ControlHandler(DWORD request) {
      switch(request)
      {
        case SERVICE_CONTROL_STOP:
          addLogMessage("Stopped.");

          serviceStatus.dwWin32ExitCode = 0;
          serviceStatus.dwCurrentState = SERVICE_STOPPED;
          SetServiceStatus (serviceStatusHandle, &serviceStatus);
          return;

        case SERVICE_CONTROL_SHUTDOWN:
          addLogMessage("Shutdown.");

          serviceStatus.dwWin32ExitCode = 0;
          serviceStatus.dwCurrentState = SERVICE_STOPPED;
          SetServiceStatus (serviceStatusHandle, &serviceStatus);
          return;
        
        default:
          break;
      }

      SetServiceStatus (serviceStatusHandle, &serviceStatus);

      return;
    }


    ControlHandler вызывается каждый раз, как SCM шлет запросы на изменения состояния сервиса. В основном ее используют для описания корректной завершении работа сервиса.

    Установка сервиса


    Есть несколько вариантов, один из них, при помощи утилита sc. Установка производиться следующей командой:
    sc create SampleService binpath= c:\SampleService.exe

    Удаление сервиса:
    sc delete SampleService

    Данный способ, как по мне, неочень программерский потому опишем установку сервиса в коде. Изменим не много логику ф-ции _tmain:
    int _tmain(int argc, _TCHAR* argv[]) {

      servicePath = LPTSTR(argv[0]);

      if(argc - 1 == 0) {
        SERVICE_TABLE_ENTRY ServiceTable[1];
        ServiceTable[0].lpServiceName = serviceName;
        ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;

        if(!StartServiceCtrlDispatcher(ServiceTable)) {
          addLogMessage("Error: StartServiceCtrlDispatcher");
        }
      } else if( wcscmp(argv[argc-1], _T("install")) == 0) {
        InstallService();
      } else if( wcscmp(argv[argc-1], _T("remove")) == 0) {
        RemoveService();
      } else if( wcscmp(argv[argc-1], _T("start")) == 0 ){
        StartService();
      }
    }


    У нас появиться теперь еще три ф-ции:
    int InstallService() {
      SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
      if(!hSCManager) {
        addLogMessage("Error: Can't open Service Control Manager");
        return -1;
      }
      
      SC_HANDLE hService = CreateService(
         hSCManager,
         serviceName,
         serviceName,
         SERVICE_ALL_ACCESS,
         SERVICE_WIN32_OWN_PROCESS,
         SERVICE_DEMAND_START,
         SERVICE_ERROR_NORMAL,
         servicePath,
         NULL, NULL, NULL, NULL, NULL
      );

      if(!hService) {
        int err = GetLastError();
        switch(err) {
          case ERROR_ACCESS_DENIED:
            addLogMessage("Error: ERROR_ACCESS_DENIED");
            break;
          case ERROR_CIRCULAR_DEPENDENCY:
            addLogMessage("Error: ERROR_CIRCULAR_DEPENDENCY");
            break;
          case ERROR_DUPLICATE_SERVICE_NAME:
            addLogMessage("Error: ERROR_DUPLICATE_SERVICE_NAME");
            break;
          case ERROR_INVALID_HANDLE:
            addLogMessage("Error: ERROR_INVALID_HANDLE");
            break;
          case ERROR_INVALID_NAME:
            addLogMessage("Error: ERROR_INVALID_NAME");
            break;
          case ERROR_INVALID_PARAMETER:
            addLogMessage("Error: ERROR_INVALID_PARAMETER");
            break;
          case ERROR_INVALID_SERVICE_ACCOUNT:
            addLogMessage("Error: ERROR_INVALID_SERVICE_ACCOUNT");
            break;
          case ERROR_SERVICE_EXISTS:
            addLogMessage("Error: ERROR_SERVICE_EXISTS");
            break;
          default:
            addLogMessage("Error: Undefined");
        }
        CloseServiceHandle(hSCManager);
        return -1;
      }
      CloseServiceHandle(hService);
      
      CloseServiceHandle(hSCManager);
      addLogMessage("Success install service!");
      return 0;
    }

    int RemoveService() {
      SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
      if(!hSCManager) {
         addLogMessage("Error: Can't open Service Control Manager");
         return -1;
      }
      SC_HANDLE hService = OpenService(hSCManager, serviceName, SERVICE_STOP | DELETE);
      if(!hService) {
         addLogMessage("Error: Can't remove service");
         CloseServiceHandle(hSCManager);
         return -1;
      }
      
      DeleteService(hService);
      CloseServiceHandle(hService);
      CloseServiceHandle(hSCManager);
      addLogMessage("Success remove service!");
      return 0;
    }

    int StartService() {
      SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
      SC_HANDLE hService = OpenService(hSCManager, serviceName, SERVICE_START);
      if(!StartService(hService, 0, NULL)) {
        CloseServiceHandle(hSCManager);
        addLogMessage("Error: Can't start service");
        return -1;
      }
      
      CloseServiceHandle(hService);
      CloseServiceHandle(hSCManager);
      return 0;
    }


    Теперь мы можем устанавливать, удалять и запускать сервис, не прибегая к различным утилитам:
    SampleService.exe install
    SampleService.exe remove
    SampleService.exe start

    Исходники

    Продолжение следует, если все не забракуют :)
    Поделиться публикацией

    Похожие публикации

    Комментарии 31

      –1
      Срочно уберите последнюю строчку. Съедят. :)
        +1
        Убрал ))))
          +7
          а что было в последней строчке?
          а ну верните )
            +1
            Сьедим!
            0
            и еще как то не солидно выглядят смайлы ")))" в суровой статье ;)
              –1
              можно хотя бы в личку, что там было? Не усну ведь…

              P.S. Что то подсказывает что было что то касающееся линукс )
                0
                почему-то вряд ли.
                0
                можно в личку, что убрали. =) ради интереса.
                +12
                главная интрига топика — а что же было в последней строчке?
                +3
                а что за строчка?))))
                  +1
                  Хм… была тут безумная идея исправить положение в Виндовс с полным отсутствием каких либо глобальных стеков уведомлений потипу Growl или libnotify но к сожалению я полный нуль в части винапи, да и времени нету. Суть такая, имеется готовая либа, которая рисует эти самые нотификации, статья о ней лежиттут вместе с ссылкой на исходники. Можно оформить её в виде сервиса, придумать протокол взаимодействия приложений с ней, можно нативными виндовыми средствами (помоему xml-rpc но неуверен с названием), можно через d-bus (но говорят он в Виндовсе плохо себя чуствует, да и из прог его неудобно доставать будет). А дальше писать плагины к известным программам типа Миранды и пытаться способствовать распространению получившейся системы нотификаций.
                  Интересно насколько такая идея жизненная?
                    0
                    Как вариант, можно использовать pipe.
                      0
                      Или использовать event log, создавая под эти цели отдельный журнал. Это избавит от необходимости писать сервис, принимающий сообщения. Нужен будет только сервис, который будет проверять журнал на наличие новых сообщений и отображать их для пользователя.
                        0
                        Да только вот неполучится без костылей сделать так, чтобы при клике на уведомление фокус перешёл на виджет, который отослал его.
                        Технически это то все реализуемо, вопрос в другом, будет ли это иметь хоть какой то спрос? Иначе затевать бессмысленно.
                        ЗЫ
                        Хабр чето ссылку прожевал, не привык я ещё к его тегам.
                        habrahabr.ru/blogs/qt_software/70571/
                      0
                      Я по ходу не в теме… А почему просто не кастом меседж слать? С указателем на (а тут уже что придумаетсо)?
                      +7
                      Похвально за разбор что и к чему, но порой проще взять готовый правильный пример (%Program Files%\Microsoft SDKs\Windows\v7.0\Samples\WinBase\Service\) и делать на его базе.
                        +2
                        спасибо за статью
                        может быть перенесете в блог «Разработка»? топики из личного блога не попадают на главную
                          +1
                          На тот момент не было кармы достаточно. На следующий пост надеюсь хватит :)
                          0
                          спасибо! Интересная статья.
                            +1
                            Копипаст из MSDN-а? Что-то новенькое…
                              0
                              Для полноты картины можно было бы рассказать про цикл жизни сервиса, про системные утилиты для диагностики и управления ими. Также не заметил нигде упоминания про то, что сервис не обязательно должен быть exe. Там вообще много интересного внутри, людям кстати думаю было бы интересно увидеть краткое сравнение с юниксовыми демонами…
                              Хотя… зачем это всё нужно, открываем msdn и делаем, что тут нового?
                                +1
                                «Эксперимент» — это написание трояна?;)
                                  +2
                                  Как только прочитал слово «Эксперимент» и «Windows Service» — первая возникшая мысль — это создание резидентной программы. :-)

                                  Если это так, то просьба к автору рассказать о скрытии работы программы в системе. Можно под соусом написания кейлоггера для установки только на своем компьютере. Мы поймем :-)
                                    –3
                                    ёбаный смайлофаг…
                                      +1
                                      Эх… Откройте для себя функцию FormatMessage[AW] вместо разлапистого switch :)
                                        0
                                        Спасибки учту :)
                                        0
                                        А почему все начинается с консольного приложения? Что, в визуальных сях нету от чего унаследовать сервис?
                                          +1
                                          Стандартного класса «сервис» в VS нет. Но есть готовые сторонние. А консольное приложение потому, что сервису не нужно GUI, но нужна функция входа, коей здесь и служит _tmain.
                                            0
                                            ATL проект не пробовали?
                                          0
                                          что за функция InitService()?
                                          • НЛО прилетело и опубликовало эту надпись здесь

                                            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                            Самое читаемое