Pull to refresh

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

Reading time 7 min
Views 86K
Я решил провести один эксперимент, суть его пока не могу разглашать, но по результатам обязательно опишу его))) Для этого эксперимента, мне нужно написать приложение которое работает как сервис в 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

Исходники

Продолжение следует, если все не забракуют :)
Tags:
Hubs:
+30
Comments 35
Comments Comments 35

Articles