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

C++*
Я решил провести один эксперимент, суть его пока не могу разглашать, но по результатам обязательно опишу его))) Для этого эксперимента, мне нужно написать приложение которое работает как сервис в 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

Исходники

Продолжение следует, если все не забракуют :)
+30
5 октября 2009, 22:08
73
Goliath 29,5

комментарии (30)

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

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

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

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