윈도우 서비스(Windows Service)는 백그라운드에서 실행되는 프로그램으로, 일반적인 사용자 인터페이스(User Interface)를 제공하지 않고 있으며, 보통 사용자의 직접적인 개입 없이 일정한 작업을 수행하도록 설계된 프로그램입니다. 주로 웹 서버, 데이터베이스 서버, 보안 솔루션 등에 널리 사용합니다. 이번 포스팅에서는 Visual C++로 윈도우 서비스 애플리케이션을 만들어보겠습니다.
프로젝트 생성
VC++을 사용해 윈도우 서비스 애플리케이션을 만들기 위해 콘솔 애플리케이션 프로젝트를 생성합니다. 이후 서비스 제어 관리자를 통해 서비스의 시작, 중지 등 제어 작업을 수행할 수 있습니다.
서비스 제어 관리자(Service Control Manager)
서비스 제어 관리자(Service Control Manager, SCM)는 윈도우 서비스의 시작과 정지 등을 제어하고 관리하는 시스템 프로세스입니다. 윈도우 서비스를 제어하기 위해서는 반드시 서비스 제어 관리자를 통해 서비스를 제어해야합니다.
서비스 제어 관리자의 주요 기능은 다음과 같습니다. :
- 서비스의 추가/제거
- 윈도우 부팅 시 서비스 자동 시작
- 서비스의 시작, 종료, 일시 중지 등의 제어
- 설치된 서비스 조회
- 실행 중인 서비스에 대한 상태 정보 조회
서비스 제어 관리자는 레지스트리를 통해 윈도우 서비스 데이터를 관리하고 있으며, 서비스 또는 services.msc
명령으로 다음과 같이 서비스 제어 관리자를 확인할 수 있습니다.
( 레지스트리 키 위치 : HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services )
서비스 제어 관리자 열기
윈도우 서비스를 제어하려면 서비스 제어 관리자 핸들(SC_HANDLE)이 필요합니다. SCM 핸들을 구하기 위해 OpenSCManager
함수를 사용합니다. 핸들을 사용한 후에는 반드시 CloseServiceHandle
함수를 통해 자원을 해제해야 합니다.
// 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);
}
윈도우 서비스 제어
OpenSCManager
함수로 구한 SCM 핸들을 통해 윈도우 서비스 제어 작업을 수행할 수 있습니다.
윈도우 서비스 등록
함수를 통해 새로운 윈도우 서비스를 등록할 수 있습니다. 서비스가 시작되면 CreateService
main
함수가 실행되고, 서비스 엔트리 함수(Service Entry Function)가 호출됩니다. 이때 수동 시작 시에는 인수를 넘길 수 있지만, 자동 시작일 경우 부팅 시점에서 자동 실행되므로 사용자가 인수를 넘기지 못합니다. 이런 경우 애플리케이션 경로(lpBinaryPathName)에 인수를 포함하여 전달할 수 있습니다.
// 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"StudioYS Service",
L"StudioYS 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);
}
윈도우 서비스 제거
서비스 제거는 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 참고 )
);
// winsvc.h (include windows.h)
BOOL DeleteService(
[in] SC_HANDLE hService // Service Handle
);
SC_HANDLE service_handle = OpenService(service_manager, L"StudioYS Service",
SERVICE_ALL_ACCESS);
if ( service_handle != NULL )
{
DeleteService(service_handle);
CloseServiceHandle(service_handle);
}
윈도우 서비스 시작
서비스를 시작하려면 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"StudioYS Service",
SERVICE_ALL_ACCESS);
if ( service_handle != NULL )
{
StartService(service_handle, 0, nullptr);
CloseServiceHandle(service_handle);
}
윈도우 서비스 상태 제어
서비스의 상태를 제어하려면 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"StudioYS Service",
SERVICE_ALL_ACCESS);
if ( service_handle != NULL )
{
SERVICE_STATUS service_status;
ControlService(service_handle, SERVICE_CONTROL_STOP, &service_status);
CloseServiceH
}
예제 코드
내 자신을 서비스로 등록/제거/시작/종료하는 프로그램을 만들어보겠습니다. 해당 예제는 spdlog 라이브러리를 사용하여 로그를 기록합니다.
#include <windows.h>
#include "spdlog/spdlog.h"
#include "spdlog/sinks/basic_file_sink.h"
static WCHAR kServiceName[] = L"StudioYS Service";
SC_HANDLE OpenServiceManager(DWORD desiredAccess)
{
SC_HANDLE service_manager = OpenSCManagerW(NULL, NULL, desiredAccess);
if ( service_manager == NULL )
{
spdlog::error("OpenSCManagerW Failed: 0x{:08X}", GetLastError());
}
return service_manager;
}
SC_HANDLE OpenServiceHandle(SC_HANDLE service_manager, DWORD desiredAccess)
{
SC_HANDLE service_handle = OpenService(service_manager, kServiceName, desiredAccess);
if ( service_handle == NULL )
{
spdlog::error("OpenService Failed: 0x{:08X}", GetLastError());
}
return service_handle;
}
void InstallMyService()
{
spdlog::debug("InstallMyService Start");
SC_HANDLE service_manager = OpenServiceManager(SC_MANAGER_CREATE_SERVICE);
if ( service_manager == NULL ) 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: 0x{:08X}", GetLastError());
CloseServiceHandle(service_manager);
return;
}
// 서비스 설명 설정
WCHAR description[] = L"이 서비스는 테스트를 위한 서비스입니다.";
SERVICE_DESCRIPTION sd = { description };
ChangeServiceConfig2W(service_handle, SERVICE_CONFIG_DESCRIPTION, &sd);
// 핸들 닫기
CloseServiceHandle(service_handle);
CloseServiceHandle(service_manager);
spdlog::debug("InstallMyService Finish");
}
void UninstallMyService()
{
spdlog::debug("UninstallMyService Start");
SC_HANDLE service_manager = OpenServiceManager(SC_MANAGER_ALL_ACCESS);
if ( service_manager == NULL ) return;
SC_HANDLE service_handle = OpenServiceHandle(service_manager, SERVICE_ALL_ACCESS);
if ( service_handle == NULL )
{
CloseServiceHandle(service_manager);
return;
}
if ( !DeleteService(service_handle) )
{
spdlog::error("DeleteService Failed: 0x{:08X}", GetLastError());
}
CloseServiceHandle(service_handle);
CloseServiceHandle(service_manager);
spdlog::debug("UninstallMyService Finish");
}
void StartMyService()
{
spdlog::debug("StartMyService Start");
SC_HANDLE service_manager = OpenServiceManager(SC_MANAGER_ALL_ACCESS);
if ( service_manager == NULL ) return;
SC_HANDLE service_handle = OpenServiceHandle(service_manager, SERVICE_START);
if ( service_handle == NULL )
{
CloseServiceHandle(service_manager);
return;
}
if ( !StartService(service_handle, 0, NULL) )
{
spdlog::error("StartService Failed: 0x{:08X}", 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("StartMyService Finish");
}
void StopMyService()
{
spdlog::debug("StopMyService Start");
SC_HANDLE service_manager = OpenServiceManager(SC_MANAGER_ALL_ACCESS);
if ( service_manager == NULL ) return;
SC_HANDLE service_handle = OpenServiceHandle(service_manager, SERVICE_STOP);
if ( service_handle == NULL )
{
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) )
{
spdlog::error("ControlService Failed: 0x{:08X}", GetLastError());
}
Sleep(2000);
}
CloseServiceHandle(service_handle);
CloseServiceHandle(service_manager);
spdlog::debug("StopMyService Finish");
}
int wmain(int argc, wchar_t* argv[])
{
// logger 설정
auto logger = spdlog::basic_logger_mt("StudioYS Service", "log/StudioYS_Service.log");
spdlog::set_default_logger(logger);
spdlog::set_pattern("%Y-%m-%d %H:%M:%S.%e (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();
}
else
{
spdlog::error("Unknown command");
}
}
else
{
spdlog::error("No command provided.");
}
return 0;
}
참고 자료
# 관련 포스트
# 참고 자료