이번 장에서는 실제로 서비스 프로그램이 어떻게 구성되어 있는지 간단한 샘플을 이용하여 살펴보고자 한다.
샘플에 구현된 기능은 다음과 같다.
1. 설치/제거
2. 시작/중지/일시중지/재시작
3. 설치시 각종 파라미터 설정
4. 디버깅을 위한 간단한 조작
5. 전체 서비스 프로그램의 흐름
6. 기능은 지난 서비스 설명시 잠깐 나온 땡땡거리는 서비스를 기반으로 한다.
소스의 구성 품목은 다음과 같다.
1. main.cpp 서비스 구조를 가진 소스
2. ServiceUtil.h / cpp 서비스를 개발할 때 간혹 쓸모있는 함수 모음 클래스
3. ServiceMgr.h / cpp 서비스의 설치나 동작을 제어하는 기능을 가진 모음 클래스
아래는 접어놓은 부분은 main.cpp 와 두개 클래스의 헤더만 설명을 위하여 올려 놓았다.
전체 샘플은 압축하여 첨부 파일로 추가할 것이다.
#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <conio.h>
#include "ServiceUtil.h"
#include "ServiceMgr.h"
DWORD WINAPI service_handler(DWORD fdwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext)
{
CServiceManager manager;
switch (fdwControl)
{
case SERVICE_CONTROL_PAUSE:
manager.SetServiceRunStatus(SERVICE_PAUSE_PENDING);
// 서비스를 일시 중지 시킨다.
manager.SetServiceRunStatus(SERVICE_PAUSED);
break;
case SERVICE_CONTROL_CONTINUE:
manager.SetServiceRunStatus(SERVICE_CONTINUE_PENDING);
// 일시 중지 시킨 서비스를 재개한다.
manager.SetServiceRunStatus(SERVICE_RUNNING);
break;
case SERVICE_CONTROL_STOP:
manager.SetServiceRunStatus(SERVICE_STOP_PENDING);
// 서비스를 멈춘다 (즉, 종료와 같은 의미)
// 서비스를 종료하면, service_main 는 절대로 리턴하지 않는다.
// 그러므로 해제하려면 작업이 있으면 모든것을 이곳에서 처리한다.
manager.SetServiceRunStatus(SERVICE_STOPPED);
break;
default:
break;
}
return NO_ERROR;
}
int service_main(INT ARGC, LPSTR* ARGV)
{
CServiceManager manager;
CServiceUtility utility;
if(utility.IsServiceMode() == TRUE)
{
manager.SetServiceHandler(RegisterServiceCtrlHandlerEx(
CServiceManager::m_InstallParam.lpServiceName, service_handler, NULL));
if (manager.GetServiceHandler() == NULL)
return 0;
}
else
{
printf("run debug mode\npress any key close...\n");
}
manager.SetServiceRunStatus(SERVICE_START_PENDING);
// bla bla initialize...
Sleep(1000);
// start ok, i'm ready receive event
manager.SetServiceRunStatus(SERVICE_RUNNING);
// 무한 루프를 돌면서 띵띵~ 소리를 낸다.
while(manager.GetServiceRunStatus() != SERVICE_STOPPED)
{
if(manager.GetServiceRunStatus() == SERVICE_PAUSED)
{
Sleep(1000);
continue;
}
if(utility.IsServiceMode() == FALSE && _kbhit())
{
printf("stop debug mode\n");
break;
}
::MessageBeep(0xFFFFFFFF);
printf("ting...\n");
Sleep(1000);
}
return 0;
}
int main(int argc, char** argv)
{
#if 1
/////////////////////////////////////////////////////////////////////
// test code
/////////////////////////////////////////////////////////////////////
CServiceUtility u;
u.ServiceUserBlankPassword(FALSE);
u.UserPrivileges("Administrtor", L"SeServiceLogonRight");
u.ProcessPrivileges(GetCurrentProcess(), SE_SHUTDOWN_NAME, TRUE);
PWTS_PROCESS_INFO sinfo = NULL;
DWORD count = 0;
u.WTSEnumProcesses(sinfo, &count);
u.WTSFree(sinfo);
PWTS_SESSION_INFO info = NULL;
count = 0;
u.WTSEnumSessions(info, &count);
u.WTSFree(info);
CServiceUtility::CWTSSession ws(WTS_CURRENT_SERVER_HANDLE, 0);
char buffer[512] = {0};
if(u.GetOSDisplayString(buffer))
printf("%s\n", buffer);
CServiceUtility su;
STARTUPINFO si = {0};
PROCESS_INFORMATION pi = {0};
u.CreateProcessToDesktop("C:\\Windows\\System32\\cmd.exe", NULL, si, pi, 0);
WaitForSingleObject(pi.hProcess, INFINITE);
#endif
CServiceManager manager;
CServiceUtility utility;
/* 설정하지 않으면, 기본 파일값으로
파일 이름을 잘라다가 자동으로 설정한다. */
manager.ConfigServiceName("CROWBACK");
manager.ConfigServiceDisp("CROWBACK SERVICE");
manager.ConfigServiceDesc("CROWBACK's test service, forever ting... ting...");
if(utility.IsServiceMode() == FALSE)
{
char ch = 0;
if(argc != 2)
{
printf("sample.exe [option: i, u, s, t, p, c]\n");
printf(" i - install\n");
printf(" u - uninstall\n");
printf(" s - start\n");
printf(" t - stop\n");
printf(" p - pause\n");
printf(" c - continue\n\n");
printf("input command: ");
ch = getch();
}
else
ch = argv[1][0];
switch(ch)
{
case 'i':
manager.Install();
return 0;
case 'u':
manager.Uninstall();
return 0;
case 's':
manager.Start();
return 0;
case 't':
manager.Stop();
return 0;
case 'p':
manager.Pause();
return 0;
case 'c':
manager.Continue();
return 0;
default:
return service_main(argc, argv);
}
}
SERVICE_TABLE_ENTRY STE[] =
{
{(char*)manager.m_InstallParam.lpServiceName, (LPSERVICE_MAIN_FUNCTION)service_main},
{NULL,NULL}
};
if(StartServiceCtrlDispatcher(STE) == FALSE)
return -1;
return 0;
}
// ServiceUtil.h: interface for the CServiceUtil class.
//
//////////////////////////////////////////////////////////////////////
#if !defined(AFX_SERVICEUTIL_H__33AC0EB8_F77D_4B21_BEE9_F025DA3F0D50__INCLUDED_)
#define AFX_SERVICEUTIL_H__33AC0EB8_F77D_4B21_BEE9_F025DA3F0D50__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#ifndef _WINDOWS_
# include <windows.h>
#endif
#ifndef _WINSVC_
# include <winsvc.h>
#endif
#include <Ntsecapi.h>
#include <WtsApi32.h>
class CServiceUtility
{
public:
CServiceUtility();
virtual ~CServiceUtility();
/*------------------------------------------
| Service and Account authority Functions |
------------------------------------------*/
// allow blank password user
BOOL ServiceUserBlankPassword(BOOL bAllow);
// get user SID by username
BOOL GetUserSID(PSID sid, const char* username);
// add privilege to sid user
BOOL UserPrivileges(const char* username, wchar_t* pszPrivilege);
// enable/disale privilege to pid process
BOOL ProcessPrivileges(HANDLE pid, char* pszPrivilege, BOOL bEnable);
// check under service mode
BOOL IsServiceMode();
// create user application to session
BOOL CreateProcessToDesktop(char* pszExecute, char* args, STARTUPINFO& si,
PROCESS_INFORMATION& pi, UINT SessionID);
/*------------------------------------
| Terminal Server Utility Functions |
------------------------------------*/
// enumerate windows terminal sessions
BOOL WTSEnumSessions(PWTS_SESSION_INFO& info, LPDWORD count);
// enumerate windows terminal process
BOOL WTSEnumProcesses(PWTS_PROCESS_INFO& info, LPDWORD count);
// free memory (WTSEnumSessions, WTSEnumServers, WTSEnumProcesses 1'st argument)
void WTSFree(void* pData);
// query session detail info
// ex) CServiceUtil::CWTSSession ws(WTS_CURRENT_SERVER_HANDLE, session id);
class CWTSSession
{
public:
CWTSSession(HANDLE hWTS, DWORD SessionID);
~CWTSSession();
CHAR IPAddress[16];
USHORT HorizontalResolution;
USHORT VerticalResolution;
USHORT ColorDepth;
USHORT ProtocolType;
LPTSTR ClientName;
LPTSTR DomainName;
LPTSTR UserName;
LPTSTR WinStation;
protected:
void Query(HANDLE hWTS, DWORD SessionID);
void Init();
void Free();
};
// from msdn: http://msdn.microsoft.com/en-us/library/ms724429(VS.85).aspx
BOOL GetOSDisplayString( LPTSTR pszOS);
};
// ServiceMgr.h: interface for the CServiceMgr class.
//
//////////////////////////////////////////////////////////////////////
#if !defined(AFX_SERVICEMGR_H__D5D636CE_8FB0_44BA_BD03_3E14E22C668A__INCLUDED_)
#define AFX_SERVICEMGR_H__D5D636CE_8FB0_44BA_BD03_3E14E22C668A__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#include "ServiceUtil.h"
typedef struct
{
CHAR lpServiceName[MAX_PATH];
CHAR lpDisplayName[MAX_PATH];
CHAR lpDescription[MAX_PATH];
CHAR lpBinaryPathName[MAX_PATH];
CHAR lpModulePath[MAX_PATH];
DWORD dwDesiredAccess;
/* #define SERVICE_QUERY_CONFIG 0x0001
#define SERVICE_CHANGE_CONFIG 0x0002
#define SERVICE_QUERY_STATUS 0x0004
#define SERVICE_ENUMERATE_DEPENDENTS 0x0008
#define SERVICE_START 0x0010
#define SERVICE_STOP 0x0020
#define SERVICE_PAUSE_CONTINUE 0x0040
#define SERVICE_INTERROGATE 0x0080
#define SERVICE_USER_DEFINED_CONTROL 0x0100
#define SERVICE_ALL_ACCESS 0x01FF */
DWORD dwServiceType;
/* #define SERVICE_FILE_SYSTEM_DRIVER 0x0002
#define SERVICE_KERNEL_DRIVER 0x0001
#define SERVICE_WIN32_OWN_PROCESS 0x0010
#define SERVICE_WIN32_SHARE_PROCESS 0x0020
#define SERVICE_INTERACTIVE_PROCESS 0x0100 */
DWORD dwStartType;
/* #define SERVICE_BOOT_START 0x0000
#define SERVICE_SYSTEM_START 0x0001
#define SERVICE_AUTO_START 0x0002
#define SERVICE_DEMAND_START 0x0003
#define SERVICE_DISABLED 0x0004 */
DWORD dwErrorControl;
/* #define SERVICE_ERROR_IGNORE 0x0000
#define SERVICE_ERROR_NORMAL 0x0001
#define SERVICE_ERROR_SEVERE 0x0002
#define SERVICE_ERROR_CRITICAL 0x0003 */
CHAR lpDependencies[MAX_PATH];
/* Example) "LanmanWorkstation\0LanmanServer\0LmHosts\0\0" */
CHAR lpServiceStartName[MAX_PATH];
/* if lpServiceStartName is Not NULL
LSA_HANDLE Policy = GetPolicyHandle();
BYTE buffer[1024] = {0};
PSID sid = (PSID)buffer;
if(GetUserSID(sid, "user name"))
UserPrivileges(sid, Policy, L"SeServiceLogonRight"); */
CHAR lpPassword[MAX_PATH];
/* if lpServiceStartName is Not NULL and lpPassword is NULL
HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Lsa -> limitblankpassworduse (DWORD)
0 Disable - Blank Password use
1 Enable - No. */
SERVICE_FAILURE_ACTIONS sfa;
SC_ACTION sfaAction[3];
/* if service failed, what do you want?
ChangeServiceConfig2 (SERVICE_CONFIG_FAILURE_ACTIONS)
SC_ACTION::Type
SC_ACTION_NONE, -- default,
SC_ACTION_RESTART,
SC_ACTION_REBOOT,
SC_ACTION_RUN_COMMAND
SC_ACTION::Delay
if Type != SC_ACTION_NONE
default delay 2000 ms
remark) index begin 0 - 1 - 2 */
CHAR sfaReBootMessage[MAX_PATH];
CHAR sfaRetryCommand[MAX_PATH];
UINT sfaFailureCounter;
SERVICE_STATUS_HANDLE sshHandler;
} SRVARG, *LPSRVARG;
class CServiceManager
{
class CSrvHandle
{
public:
CSrvHandle()
{
hSrv = hScm = NULL;
}
~CSrvHandle()
{
if(hSrv) CloseServiceHandle(hSrv);
if(hScm) CloseServiceHandle(hScm);
}
SC_HANDLE hSrv, hScm;
};
public:
CServiceManager();
virtual ~CServiceManager();
/*----------------------------
| Service Control Functions |
----------------------------*/
// install service with configured param
DWORD Install();
// uninstall service, if force is true, and timeover -> kill process
DWORD Uninstall(BOOL bForce = FALSE, DWORD dwWait = 30 /* sec */);
// start service with timeout
DWORD Start(DWORD dwWait = 30 /* sec */);
// stop service with timeout
DWORD Stop(BOOL bForce = FALSE, DWORD dwWait = 30 /* sec */);
// pause service with timeout
DWORD Pause(DWORD dwWait = 30 /* sec */);
// continue service with timeout
DWORD Continue(DWORD dwWait = 30 /* sec */);
// retrive service run status
DWORD GetServiceRunStatus(SERVICE_STATUS& ss);
DWORD GetServiceRunStatus();
// set service run status
DWORD SetServiceRunStatus(DWORD dwStatus);
// set service event handle store
VOID SetServiceHandler(SERVICE_STATUS_HANDLE ssh);
SERVICE_STATUS_HANDLE GetServiceHandler();
/*-----------------------------------------
| Service Install Param Config Functions |
-----------------------------------------*/
// set service unique name ( must be, default fill)
VOID ConfigServiceName(LPCTSTR lpszStr);
// set service display name ( must be, default fill)
VOID ConfigServiceDisp(LPCTSTR lpszStr);
// set service description ( optional )
VOID ConfigServiceDesc(LPCTSTR lpszStr);
// set service execute file full path ( must be, default fill)
VOID ConfigServiceExec(LPCTSTR lpszStr);
// specfic startup username and password ( optional )
VOID ConfigStartUser(LPCTSTR lpszUser, LPCTSTR lpszPassword);
// dependency other services ( optional )
VOID ConfigDependencies(LPCTSTR lpszStr);
// if service start failed, next step? ( optional, default fill)
VOID ConfigSfaAction(UINT uIDX, SC_ACTION_TYPE action, UINT delay);
VOID ConfigSfaRebootMessage(LPCTSTR lpszStr);
VOID ConfigSfaRetryCommand(LPCTSTR lpszStr);
VOID ConfigSfaCounterReset(UINT count);
public:
static SRVARG m_InstallParam;
protected:
VOID ConfigInitialize();
};
서비스 본체를 구성하고 있는 main.cpp 를 살펴보기로 한다.
int main(int argc, char** argv)
{
#if 0
/////////////////////////////////////////////////////////////////////
// #define 로 쌓인 이 구문은 첨부된 클래스 2개의 간단한 사용법과
// 정상적인 지를 확인하기 위한 테스트 코드이다. 불필요하면 스킵해도 된다.
/////////////////////////////////////////////////////////////////////
CServiceUtility u;
u.ServiceUserBlankPassword(FALSE);
u.UserPrivileges("Administrtor", L"SeServiceLogonRight");
u.ProcessPrivileges(GetCurrentProcess(), SE_SHUTDOWN_NAME, TRUE);
PWTS_PROCESS_INFO sinfo = NULL;
DWORD count = 0;
u.WTSEnumProcesses(sinfo, &count);
u.WTSFree(sinfo);
PWTS_SESSION_INFO info = NULL;
count = 0;
u.WTSEnumSessions(info, &count);
u.WTSFree(info);
CServiceUtility::CWTSSession ws(WTS_CURRENT_SERVER_HANDLE, 0);
char buffer[512] = {0};
if(u.GetOSDisplayString(buffer))
printf("%s\n", buffer);
CServiceUtility su;
STARTUPINFO si = {0};
PROCESS_INFORMATION pi = {0};
u.CreateProcessToDesktop("C:\\Windows\\System32\\cmd.exe", NULL, si, pi, 0);
WaitForSingleObject(pi.hProcess, INFINITE);
#endif
// 서비스를 관리하는 매니저와 구현을 도와주는 유틸 클래스
CServiceManager manager;
CServiceUtility utility;
// 아래 항목은 설정하지 않으면, 파일정보를 읽어서 자동으로채운다.
manager.ConfigServiceName("CROWBACK");
manager.ConfigServiceDisp("CROWBACK SERVICE");
manager.ConfigServiceDesc("CROWBACK's test service, forever ting... ting...");
// 먼저 서비스로 구동중인지, 응용 어플리케이션 모드로 구동 중인지를 확인한다.
if(utility.IsServiceMode() == FALSE)
{
// 응용 어플리케이션 모드로 돌아갈 때는 인자를 하나 받는다. 인자는 아래와 같다.
// 인자가 없을 경우는 직접 인자를 입력 받는다. 지정된 값이 아니면 구동 상태로 돌입한다.
char ch = 0;
if(argc != 2)
{
printf("sample.exe [option: i, u, s, t, p, c]\n");
printf(" i - install\n");
printf(" u - uninstall\n");
printf(" s - start\n");
printf(" t - stop\n");
printf(" p - pause\n");
printf(" c - continue\n\n");
printf("input command: ");
ch = getch();
}
else
ch = argv[1][0];
// 인자에 따라 아래와 같은 동작을 수행한다.
switch(ch)
{
case 'i':
manager.Install();
return 0;
case 'u':
manager.Uninstall();
return 0;
case 's':
manager.Start();
return 0;
case 't':
manager.Stop();
return 0;
case 'p':
manager.Pause();
return 0;
case 'c':
manager.Continue();
return 0;
// 위의 값이 아니면 그냥 응용 어플리케이션 모드로 구동한다.
default:
return service_main(argc, argv);
}
}
// 여기로 왔다는 것은 현재 서비스 모드로 구동중인 상태인 것이다.
SERVICE_TABLE_ENTRY STE[] =
{
//SCM (Service Control Manager)에 등록하기 위하여 지정된 구조체 정보를 채운다.
{ manager.m_InstallParam.lpServiceName, (LPSERVICE_MAIN_FUNCTION)service_main },
{ NULL, NULL}
};
// 해당 채워진 구조체를 이용하여 서비스 메인 함수를 SCM이 호출할 수 있도록 등록작업을 마친다.
if(StartServiceCtrlDispatcher(STE) == FALSE)
return -1;
return 0;
}
여기서는 콘솔 응용프로그램의 main 함수에서 어떻게 서비스 메인함수를 등록하는지 간단한 절차를 처리해보았다.
하나의 페이지에 꽤 긴 내용이 들어가서 편집작업에 어려움이 있어 추가 설명과 나머지 내용을 다음장에서 정리할 것이다.