윈도우 서비스(Windows Service)는 Session 0에서 실행되며, 웹서버, 보안 솔루션, 자동 업데이트 등과 같이 사용자의 간섭을 최소화 하고, 윈도우 종료 시 까지 별도 종료 없이 계속 실행되어 있는 애플리케이션을 말합니다.
이제 Visual C++로 윈도우 서비스 애플리케이션을 만들어보겠습니다.
프로젝트 생성
윈도우 서비스 애플리케이션을 만들기 위해서는 다음과 같이 콘솔 애플리케이션으로 프로젝트를 만들면 됩니다.
서비스 제어 관리자(Service Control Manager)
서비스 제어 관리자(SCM : Service Control Manager)는 윈도우 서비스의 시작과 정지 등을 제어하고 관리하는 윈도우 시스템 프로세스로 윈도우 서비스를 제어하기 위해서 반드시 필요한 프로세스입니다.
서비스 제어 관리자 주요 기능
- 서비스의 추가/제거
- 윈도우 부팅 시 서비스 자동 시작
- 서비스의 시작, 종료, 일시 중지 등의 제어
- 설치된 서비스 조회
- 실행 중인 서비스에 대한 상태 정보 조회
서비스 제어 관리자는 레지스트리를 통해 윈도우 서비스 데이터를 관리하고 있으며, 서비스
또는 services.msc
명령으로 다음과 같이 서비스 제어 관리자를 확인할 수 있습니다.
- 레지스트리 키 : HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services
서비스 제어 관리자 열기
윈도우 서비스를 제어 및 관리하기 위해서 서비스 제어 관리자 핸들을 구해야 합니다. OpenSCManager
함수를 사용하여 서비스 제어 관리자 핸들을(SC_HANDLE
) 구할 수 있습니다.
윈도우 서비스 제어 완료 후 반드시 CloseServiceHandle
함수를 사용하여 SC_HANDLE
을 닫아야 합니다.
Service control manager
// 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를 포함하여 경로를 지정하면 됩니다
Service Entry Point & Service Main
윈도우 서비스 제거
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 : 0x0
Code language: Bash (bash)