콘텐츠로 건너뛰기

Windows Service 구현 – 01. 서비스 제어하기

윈도우 서비스(Windows Service)는 Session 0에서 실행되며, 웹서버, 보안 솔루션, 자동 업데이트 등과 같이 사용자의 간섭을 최소화 하고, 윈도우 종료 시 까지 별도 종료 없이 계속 실행되어 있는 애플리케이션을 말합니다.

이제 Visual C++로 윈도우 서비스 애플리케이션을 만들어보겠습니다.

프로젝트 생성

윈도우 서비스 애플리케이션을 만들기 위해서는 다음과 같이 콘솔 애플리케이션으로 프로젝트를 만들면 됩니다.

그림 1. 프로젝트 만들기

서비스 제어 관리자(Service Control Manager)

서비스 제어 관리자(SCM : Service Control Manager)는 윈도우 서비스의 시작과 정지 등을 제어하고 관리하는 윈도우 시스템 프로세스로 윈도우 서비스를 제어하기 위해서 반드시 필요한 프로세스입니다.

서비스 제어 관리자 주요 기능

  • 서비스의 추가/제거
  • 윈도우 부팅 시 서비스 자동 시작
  • 서비스의 시작, 종료, 일시 중지 등의 제어
  • 설치된 서비스 조회
  • 실행 중인 서비스에 대한 상태 정보 조회

서비스 제어 관리자는 레지스트리를 통해 윈도우 서비스 데이터를 관리하고 있으며, 서비스 또는 services.msc 명령으로 다음과 같이 서비스 제어 관리자를 확인할 수 있습니다.

  • 레지스트리 키 : HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services
그림 2. 서비스 제어 관리자

서비스 제어 관리자 열기

윈도우 서비스를 제어 및 관리하기 위해서 서비스 제어 관리자 핸들을 구해야 합니다. OpenSCManager 함수를 사용하여 서비스 제어 관리자 핸들을(SC_HANDLE) 구할 수 있습니다.

윈도우 서비스 제어 완료 후 반드시 CloseServiceHandle 함수를 사용하여 SC_HANDLE을 닫아야 합니다.

// winsvc.h (include Windows.h)
SC_HANDLE OpenSCManager(
  [in, optional] LPCTSTR lpMachineName,  // 대상 컴퓨터의 이름. 로컬 PC의 경우 NULL 입력
  [in, optional] LPCTSTR lpDatabaseName, // 서비스 DB 이름 - 일반적으로 NULL
  [in]           DWORD   dwDesiredAccess // 액세스 권한 - MSDN 참고
);

// 관련 코드
SC_HANDLE service_manager = OpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS);

if ( service_manager != NULL )
{
  // To Do Something

  CloseServiceHandle(service_manager);
}
Code language: C++ (cpp)

윈도우 서비스 제어

OpenSCManager 함수를 통해 생성한 SC_HANDLE를 통해 윈도우 서비스를 제어할 수 있습니다.

윈도우 서비스 등록

CreateService 함수를 통해서 윈도우 서비스를 생성할 수 있습니다.

윈도우 서비스 제어 완료 후 반드시 CloseServiceHandle 함수를 사용하여 SC_HANDLE을 닫아야 합니다.

// winsvc.h (include Windows.h)
SC_HANDLE CreateService(
  [in]            SC_HANDLE hSCManager,          // Service Contrl Manager 핸들
  [in]            LPCTSTR   lpServiceName,       // 서비스 이름 ( key )
  [in, optional]  LPCTSTR   lpDisplayName,       // 서비스 이름 ( 화면에 표시 )
  [in]            DWORD     dwDesiredAccess,     // 액세스 권한 ( MSDN 참고 )
  [in]            DWORD     dwServiceType,       // 서비스 종류 ( MSDN 참고 )
  [in]            DWORD     dwStartType,         // 시작 유형 ( MSDN 참고 )
  [in]            DWORD     dwErrorControl,
  [in, optional]  LPCTSTR   lpBinaryPathName,    // 애플리케이션 경로 ( argument 포함 가능 )
  [in, optional]  LPCTSTR   lpLoadOrderGroup,
  [out, optional] LPDWORD   lpdwTagId,
  [in, optional]  LPCTSTR   lpDependencies,
  [in, optional]  LPCTSTR   lpServiceStartName,
  [in, optional]  LPCTSTR   lpPassword
);

// 관련 코드
SC_HANDLE service_handle = CreateServiceW(service_manager, L"whalec Service",
  L"whalec Service", SERVICE_ALL_ACCESS, SERVICE_WIN32_SHARE_PROCESS,
  SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, file_path, NULL, NULL, NULL, NULL, NULL);

if ( service_handle != NULL )
{
  // Do Something

  CloseServiceHandle(service_handle);
}Code language: C++ (cpp)

윈도우 서비스가 실행되면 main 함수에서 Service Entry Function을 호출합니다. 이 때 main 함수의 Argument를 Service Entry Function에 넘길 수 있는데, 수동으로 실행할 경우 args를 넘길 수 있으나, 서비스의 시작 유형이 자동이면 윈도우 부팅 시점에서 자동으로 실행되기 때문에 사용자가 argument를 넘길 수 없습니다. 이 경우 lpBinaryPathName 에서 애플리케이션 경로를 입력할 때 다음과 같이 argument를 포함하여 경로를 지정하면 됩니다

윈도우 서비스 제거

OpenService 함수를 통해서 서비스 핸들을 구하고, DeleteService 함수를 사용하여 서비스를 제거할 수 있습니다.

// winsvc.h (include Windows.h)
SC_HANDLE OpenService(
  [in] SC_HANDLE hSCManager,       // Service Contrl Manager 핸들
  [in] LPCTSTR   lpServiceName,    // 서비스 이름 ( Key )
  [in] DWORD     dwDesiredAccess   // 액세스 권한 ( MSDN 참고 )
);

BOOL DeleteService(
  [in] SC_HANDLE hService  // Service Handle
);

// 관련 코드
SC_HANDLE service_handle = OpenService(service_manager, L"whalec Service", 
  SERVICE_ALL_ACCESS);

if ( service_handle != NULL )
{
  DeleteService(service_handle);
  CloseServiceHandle(service_handle);
}
Code language: C++ (cpp)

윈도우 서비스 시작

OpenService 함수를 사용하여 서비스 핸들을 구하고, StartService 함수를 사용하여 윈도우 서비스를 시작할 수 있습니다.

// winsvc.h (include Windows.h)
BOOL StartService(
  [in]           SC_HANDLE hService,             // service handle
  [in]           DWORD     dwNumServiceArgs,     // lpServiceArgVectors 배열의 수
  [in, optional] LPCTSTR   *lpServiceArgVectors  // ServiceMain args ( null-terminated )
);

// 관련 코드
SC_HANDLE service_handle = OpenService(service_manager, L"whalec Service", 
  SERVICE_ALL_ACCESS);

if ( service_handle != NULL )
{
  StartService(service_handle, 0, nullptr);
  CloseServiceHandle(service_handle);
}
Code language: C++ (cpp)

윈도우 서비스 상태 제어

OpenService 함수를 사용하여 서비스 핸들을 구하고, ControlService 함수를 사용하여 서비스의 상태를 제어할 수 있습니다.

// winsvc.h (include Windows.h)
BOOL ControlService(
  [in]  SC_HANDLE        hService,         // service handle
  [in]  DWORD            dwControl,        // control codes ( MSDN 참고 )
  [out] LPSERVICE_STATUS lpServiceStatus   // SERVICE_STATUS structure pointer
);

// 관련 코드
SC_HANDLE service_handle = OpenService(service_manager, L"whalec Service", 
  SERVICE_ALL_ACCESS);

if ( service_handle != NULL )
{
  SERVICE_STATUS service_status;
  ControlService(service_handle, SERVICE_CONTROL_STOP, &service_status);
  CloseServiceHandle(service_handle);
}
Code language: C++ (cpp)

예제 코드

위에서 배운 내용을 토대로 command를 사용하여 내 자신을 서비스로 등록/제거/시작/종료하는 프로그램을 만들어보겠습니다.

#include <windows.h>

#include "spdlog/spdlog.h"
#include "spdlog/sinks/basic_file_sink.h"

static WCHAR kServiceName[] = L"whalec Service";

void InstallMyService()
{
  spdlog::debug("{} Start", __FUNCTION__);

  SC_HANDLE service_manager = OpenSCManagerW(NULL, NULL, SC_MANAGER_CREATE_SERVICE);

  if ( service_manager == NULL )
  {
    spdlog::error("{} OpenSCManagerW Failed : {:08X}", __FUNCTION__, GetLastError());
    return;
  }

  WCHAR file_path[MAX_PATH] = { 0, };
  GetModuleFileNameW(NULL, file_path, _countof(file_path));

  SC_HANDLE service_handle = CreateServiceW(service_manager, kServiceName, kServiceName,
    SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, 
    SERVICE_ERROR_NORMAL, file_path, NULL, NULL, NULL, NULL, NULL);

  if ( service_handle == NULL )
  {
    spdlog::error("{} CreateServiceW Failed : {:08X}", __FUNCTION__, GetLastError());

    CloseServiceHandle(service_manager);
    return;
  }

  WCHAR description[] = L"이 서비스는 테스트를 위한 서비스입니다.";

  SERVICE_DESCRIPTION sd;
  sd.lpDescription = description;

  ChangeServiceConfig2W(service_handle, SERVICE_CONFIG_DESCRIPTION, &sd);

  CloseServiceHandle(service_handle);
  CloseServiceHandle(service_manager);

  spdlog::debug("{} Finish", __FUNCTION__);

  return;
}

void UninstallMyService()
{
  spdlog::debug("{} Start", __FUNCTION__);

  SC_HANDLE service_manager = OpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS);

  if ( service_manager == NULL )
  {
    spdlog::error("{} OpenSCManagerW Failed : 0x{:08X}", __FUNCTION__, GetLastError());
    return;
  }

  SC_HANDLE service_handle = OpenService(service_manager, kServiceName, 
    SERVICE_ALL_ACCESS);

  if ( service_handle == NULL )
  {
    spdlog::error("{} OpenService Failed : {0x{:08X}", __FUNCTION__, GetLastError());

    CloseServiceHandle(service_manager);
    return;
  }

  if ( !DeleteService(service_handle) )
  {
    spdlog::error("{} DeleteService Failed : 0x{:08X}", __FUNCTION__, GetLastError());
  }

  CloseServiceHandle(service_handle);
  CloseServiceHandle(service_manager);

  spdlog::debug("{} Finish", __FUNCTION__);

  return;
}

void StartMyService()
{
  spdlog::debug("{} Start", __FUNCTION__);

  SC_HANDLE service_manager = OpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS);

  if ( service_manager == NULL )
  {
    spdlog::error("{} OpenSCManagerW Failed : 0x{:08X}", __FUNCTION__, GetLastError());
    return;
  }

  SC_HANDLE service_handle = OpenService(service_manager, kServiceName, 
    SERVICE_ALL_ACCESS);

  if ( service_handle == NULL )
  {
    spdlog::error("{} OpenService Failed : 0x{:08X}", __FUNCTION__, GetLastError());

    CloseServiceHandle(service_manager);
    return;
  }

  if ( !StartService(service_handle, 0, NULL) )
  {
    spdlog::error("{} StartService Failed : 0x{:08X}", __FUNCTION__, GetLastError());

    CloseServiceHandle(service_handle);
    CloseServiceHandle(service_manager);

    return;
  }

  SERVICE_STATUS service_status;

  BOOL query = QueryServiceStatus(service_handle, &service_status);

  while ( query && service_status.dwCurrentState != SERVICE_RUNNING )
  {
    Sleep(service_status.dwWaitHint);
    query = QueryServiceStatus(service_handle, &service_status);
  }

  CloseServiceHandle(service_handle);
  CloseServiceHandle(service_manager);

  spdlog::debug("{} Finish", __FUNCTION__);

  return;
}

void StopMyService()
{
  spdlog::debug("{} Start", __FUNCTION__);

  SC_HANDLE service_manager = OpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS);

  if ( service_manager == NULL )
  {
    spdlog::error("{} OpenSCManagerW Failed : {0:x}", __FUNCTION__, GetLastError());
    return;
  }

  SC_HANDLE service_handle = OpenService(service_manager, kServiceName, 
    SERVICE_ALL_ACCESS);

  if ( service_handle == NULL )
  {
    spdlog::error("{} OpenService Failed : {0:x}", __FUNCTION__, GetLastError());

    CloseServiceHandle(service_manager);
    return;
  }

  SERVICE_STATUS service_status;

  BOOL query = QueryServiceStatus(service_handle, &service_status);

  if ( query && service_status.dwCurrentState != SERVICE_STOPPED )
  {
    if ( !ControlService(service_handle, SERVICE_CONTROL_STOP, &service_status) )
    {
      CloseServiceHandle(service_handle);
      CloseServiceHandle(service_manager);

      return;
    }

    Sleep(2000);
  }

  CloseServiceHandle(service_handle);
  CloseServiceHandle(service_manager);

  spdlog::debug("{} Finish", __FUNCTION__);

  return;
}

int wmain(int argc, wchar_t* argv[])
{
  // logger
  auto logger = spdlog::basic_logger_mt("whaleService", "log/whalecService.log");
  spdlog::set_default_logger(logger);

  spdlog::set_pattern("%Y-%m-%d %H:%I:%M.%e\t(P:%P,T:%t) [%L][%n] %v");
  spdlog::set_level(spdlog::level::debug);
  spdlog::flush_on(spdlog::level::debug);

  if ( argc >= 2 )
  {
    if ( _wcsicmp(argv[1], L"-install") == 0 )
    {
      InstallMyService();
    }
    else if ( _wcsicmp(argv[1], L"-uninstall") == 0 )
    {
      UninstallMyService();
    }
    else if ( _wcsicmp(argv[1], L"-start") == 0 )
    {
      StartMyService();
    }
    else if ( _wcsicmp(argv[1], L"-stop") == 0 )
    {
      StopMyService();
    }
  }

  return 0;
}
Code language: C++ (cpp)
> whalecService.exe -install
> sc query "whalec Service"
SERVICE_NAME: whalec Service
종류                : 10  WIN32_OWN_PROCESS
상태                : 1  STOPPED
WIN32_EXIT_CODE    : 1077  (0x435)
SERVICE_EXIT_CODE  : 0  (0x0)
검사점               : 0x0
WAIT_HINT          : 0x0Code language: Bash (bash)

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다