정리

[Win32] Service - #2 서비스 생성

저장소/VC++
[출처] 윈도우즈 API 정복, 김상형 지음

책에 나와있는 예제를 토대로 정리한다.
서비스는 매 10초마다 메모리의 현재 상태를 검사해서 로그 파일에 기록하는 아주 간단한 일만 한다. 우선 예제는 아래와 같다.

#include <windows.h>

#define SERVICE_CONTROL_NEWFILE        128

void MyServiceMain(DWORD argc, LPTSTR* argv);
void MyServiceHandler(DWORD opCode);

SERVICE_STATUS_HANDLE g_hSrv;
DWORD g_NowState;
BOOL g_bPause;
HANDLE g_ExitEvent;
TCHAR gbuf[65536] = "메모리 통계 파일\r\n";

int main()
{
    SERVICE_TABLE_ENTRY ste[] = {    {"MemStat", (LPSERVICE_MAIN_FUNCTION)MyServiceMain},
                                    {NULL, NULL}
                                };

    ::StartServiceCtrlDispatcher(ste);

    return 0;
}

// 서비스의 현재 상태를 변경하는 함수
void MySetStatus(DWORD dwState, DWORD dwAccept = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE)
{
    SERVICE_STATUS ss;
    ss.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
    ss.dwCurrentState = dwState;
    ss.dwControlsAccepted = dwAccept;
    ss.dwWin32ExitCode = 0;
    ss.dwServiceSpecificExitCode = 0;
    ss.dwCheckPoint = 0;
    ss.dwWaitHint = 0;

    // 현재 상태를 보관해 둔다.
    g_NowState = dwState;
    ::SetServiceStatus(g_hSrv, &ss);
}

void MyServiceMain(DWORD argc, LPTSTR* argv)
{
    HANDLE hFile;
    MEMORYSTATUS ms;
    DWORD dwWritten;
    TCHAR str[256];
    SYSTEMTIME st;

    // 서비스 핸들러를 등록한다.
    g_hSrv = ::RegisterServiceCtrlHandler("MemStat", (LPHANDLER_FUNCTION)MyServiceHandler);
    if(g_hSrv == 0)
    {
        return;
    }

    // 서비스가 시작중임을 알린다.
    MySetStatus(SERVICE_START_PENDING);

    // 전역 변수를 초기화한다.
    g_bPause = FALSE;

    // 이벤트를 생성한다.
    g_ExitEvent = ::CreateEvent(NULL, TRUE, FALSE, "MemStatExit");

    // 새로운 로그 파일을 작성한다.
    hFile = ::CreateFile("d:\\MemStat.txt", GENERIC_WRITE, 0, NULL,
                         CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

    ::CloseHandle(hFile);

    // 서비스가 시작되었음을 알린다.
    MySetStatus(SERVICE_RUNNING);

    // 10초에 한번씩 메모리 통계를 작성한다.
    for( ; ; )
    {
        if(g_bPause == FALSE)
        {
            // 현재 시간과 메모리 양을 조사하여 문자열로 조립한다.
            ::GetLocalTime(&st);
            ::GlobalMemoryStatus(&ms);
            wsprintf(str, "%d월 %d일 %02d시 %02d분 %02d초 =>"
                          "사용가능 물리 메모리 = %dMbytes(%d%%), 사용가능 가상메모리 = %dMbytes, "
                          "사용가능 페이징 파일 = %dMbytes\r\n",
                          st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond,
                          ms.dwAvailPhys/1048576, 100 - ms.dwMemoryLoad, ms.dwAvailVirtual / 1048576,
                          ms.dwAvailPageFile / 1048576);

            // 파일로 통계를 출력한다. 버퍼가 가득찬 경우 파일을 다시 만든다.
            if(lstrlen(gbuf) > 60000)
            {
                hFile = ::CreateFile("d:\\MemStat.txt", GENERIC_WRITE, 0, NULL,
                                     CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
                lstrcpy(gbuf, "메모리 통계 파일\r\n");
            }
            else
            {
                hFile = ::CreateFile("d:\\MemStat.txt", GENERIC_WRITE, 0, NULL,
                                     OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
            }

            lstrcat(gbuf, str);
            ::WriteFile(hFile, gbuf, lstrlen(gbuf), &dwWritten, NULL);
            ::CloseHandle(hFile);
        }

        if(WaitForSingleObject(g_ExitEvent, 10000) == WAIT_OBJECT_0)
        {
            break;
        }
    }

    MySetStatus(SERVICE_STOPPED);
}

// 핸드러 함수
void MyServiceHandler(DWORD fdwControl)
{
    HANDLE hFile;

    // 현재 상태와 같은 제어 코드일 경우는 처리할 필요 없다.
    if(fdwControl == g_NowState)
    {
        return;
    }

    switch(fdwControl)
    {
    case SERVICE_CONTROL_PAUSE:
        {
            MySetStatus(SERVICE_PAUSE_PENDING, 0);
            g_bPause = TRUE;
            MySetStatus(SERVICE_PAUSED);
        }
        break;
    case SERVICE_CONTROL_CONTINUE:
        {
            MySetStatus(SERVICE_CONTINUE_PENDING, 0);
            g_bPause = FALSE;
            MySetStatus(SERVICE_RUNNING);
        }
        break;
    case SERVICE_CONTROL_STOP:
        {
            MySetStatus(SERVICE_STOP_PENDING, 0);
            ::SetEvent(g_ExitEvent);
        }
        break;
    case SERVICE_CONTROL_NEWFILE:
        {
            hFile = ::CreateFile("d:\\MemStat.txt", GENERIC_WRITE, 0, NULL,
                                 CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
            lstrcpy(gbuf, "메모리 통계 파일\r\n");
            ::CloseHandle(hFile);
        }
        break;
    case SERVICE_CONTROL_INTERROGATE:
    default:
        MySetStatus(g_NowState);
        break;
    }
}

main 함수
여기서 해야할 가장 중요한 일은 디스패처 스레드를 실행하는 것이다. 디스패처(Service Control Dispatcher)는 서비스를 시작하고 핸들러에게 제어 코드를 전달하는 일을 하는 독립된 스레드이다. 요는 디스패처를 실행시켜 이 프로세스가 포함하고 있는 서비스를 시작할 준비를 하는 것, 이것 이외에는 하는 일이 거의 없다.

서비스 메인
실제 서비스 작업을 하는 본체라고 할 수 있다. 디스패처에 의해 호출되며 서비스 운영에 관한 여러 가지 일들을 한다. 핸들러를 등록하고 서비스를 기동하며 자신의 상태 변화를 SCM에게 알린다. 함수의 원형은 아래와 같다.

VOID WINAPI ServiceMain(DWORD dwArgc, LPTSTR* lpszArgv);

잘 쓰이지는 않지만 명령행 인수도 받을 수 있다. 원형만 지키면 함수명은 마음대로 바꿔도 상관없으며 디스패처를 실행할 때 함수명을 지정한다.

핸들러
서비스의 제어 신호를 처리하는 함수로서 서비스의 메시지 루프라고 보아도 좋다.
서비스 메인에서 디스패처에 등록된다. 서비스는 사용자로부터 직접 명령을 받지는 않지만 서비스 제어 프로그램이나 서비스 애플릿 등으로부터 명령을 받아 처리한다. SCM이 이 명령을 받아 이스패처로 전달하며 디스패처는 등록된 핸들러에게 제어 신호를 보낸다. 함수의 원형은 아래와 같다.

VOID WINAPI Handler(DWORD fdwControl);

인수로 서비스가 해야 할 작업 내용을 담고 있는 제어 신호값을 받아들인다. 함수 이름은 물론 마음대로 정할 수 있다.

'저장소 > VC++' 카테고리의 다른 글

[Win32] Service - #4 서비스 제어  (0) 2009.05.13
[Win32] Service - #3 서비스 설치  (0) 2009.05.13
[Win32] Service - #1 정의  (0) 2009.05.13
[Win32] IPC - #2 Message(메시지)  (0) 2009.05.12
[Win32] IPC - #1 정의  (0) 2009.05.12