지난 내용
[강좌로 보는 서비스 프로그래밍] 서비스란 무엇인가? [1/?]

[강좌로 보는 서비스 프로그래밍] 외적으로 보여지는 서비스 [2/?]
[강좌로 보는 서비스 프로그래밍] 설치와 제거 [3/?]
[강좌로 보는 서비스 프로그래밍] 가끔 쓸모있는 관련 함수들 [4/?]
[강좌로 보는 서비스 프로그래밍] 시작/중지/일시정지 [5/?]
[강좌로 보는 서비스 프로그래밍] 서비스의 본체 코딩 [6/?]

지난 장에서 콘솔 응용프로그램의 main() 함수에서 서비스 메인 함수를 호출하는 부분을 진행하다가 여기로 넘어왔다.
StartServiceCtrlDispatcher 함수는 그냥 서비스를 등록하기 위해서 호출하는 함수이려니.. (속편하게 ^^;;;)


다음으로 이전 장에서 서비스에 등록한 서비스 메인 함수에 대하여 알아보자.
// SCM에 등록된 서비스 메인함수 ( 콘솔 프로그램의 main()과 구조는 동일하다.
int
service_main(INT ARGC, LPSTR* ARGV)
{
    // 편하게 작업하려고 만들어놓은 클래스이니 선언해놓고, 자주 써먹자.
    CServiceManager manager;
    CServiceUtility utility;


    // 현재 구동 상태가 서비스 모드인지 확인한다.
    
if(utility.IsServiceMode() == TRUE)
    {
        // 서비스일 경우는 SCM 으로 부터 이벤트를 받을 수 있도록 이벤트 핸들러를 등록한다.
        // 이벤트 핸들러에 사용자 정의 파리미터를 전달하고 싶으면, 3번째 인자에 넣으면 된다.

     
   manager.SetServiceHandler(RegisterServiceCtrlHandlerEx(
           
CServiceManager::m_InstallParam.lpServiceName, service_handler, NULL));

        // 등록이 실패하면 어쩔 수 없이 에러 처리.
    
    if (manager.GetServiceHandler() == NULL)
            return 0;
    }
    else
    {
        // 응용 프로그램 모드로 구동하면 적당히 메시지 뿌려놓고 바로 코딩에 들어간다.
        // 이 경우는 그냥 콘솔 프로그램이랑 100% 동일하게 동작한다.
    
    printf("run debug mode\npress any key close...\n");
    }

    // 서비스가 시작되면 먼저 시작중임을 서비스 메니저에 알린다.
    // 서비스가 동작할 때는 SCM 에 나의 현재 상태가 어떤지를 알리는 작업은 매우 중요하므로
    // 상태가 변경될 때, 혹은 외부에서 변경되었을 때, 모든 상태에 대하여 꼭 처리를 해주어야 한다.
    // 그렇지 않으면, SCM 은 응답없음과 같은 처리 불가능한 상태로 인식하게 된다.
    
manager.SetServiceRunStatus(SERVICE_START_PENDING);

    // 현재 샘플 코드이므로 별로 처리할게 없다. 그냥 Sleep 로 대충 때운다.
    // 보통 서비스가 로딩될 때 초기화 하는 작업을 여기서 처리한다.
   
Sleep(1000);

    // 자 초기화가 모두 끝났다. 이제는 외부에서 이벤트도 받을 수 있고 서비스도 정상 동작중임을 알린다.
    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;
}
위의 함수는 서비스 모드일 경우는 이벤트 핸들러를 등록하는 과정이 추가될 뿐, 일반 콘솔 응응프로그램의 main() 함수와 전혀 다르지 않다.
주 내용은 그냥 무한 루프 돌면서 띵~ 띵~ 소리만 주구 장창 내는 아주 단순한 내용이다.

RegisterServiceCtrlHandlerEx 는 SCM으로 부터 이벤트를 받을 수 있도록 해주는 함수이다.
RegisterServiceCtrlHandler 함수라는 좀더 간단한 함수가 있지만, 이거는 서비스의 시작/중지/일시중지/재시작과 같은 아주 단순한 이벤트만
받을 수 있고, 시스템에서 발생하는 다양한 정보를 제공받지 못한다.

가능하면 시작할 때 RegisterServiceCtrlHandlerEx 함수를 이용하여, 서비스가 지원해주는 다양한 이벤트를 즐겨 보도록 하자.
유틸리티 클래스에 의하여
RegisterServiceCtrlHandlerEx 에 등록되는 과정이 조금 복잡해 보이지만, 해당 함수의 리턴값을 저장하여
다른 용도로 사용하기 위하여 꽁수를 부리다 보니, 저런 모양을 가지게 되었다. 별로 이뿌지가 않다 -_-;;;



자 이제 초간단 서비스의 최종 내용에 해당하는 이벤트 핸들러에 등록된 service_handler 함수에 대하여 알아보자.
DWORD WINAPI service_handler(DWORD fdwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext)
{
   // 전달되는 인자에 대하여 살펴보면 다음과 같다.
   // fdwControl  이벤트의 종류이다.
   // dwEventType 이벤트의 세부 종류이다.
   // lpEventData 이벤트에서 전달해주는 파라미터이다. 이벤트의 종류에 맞도록 타입 캐스팅하여 사용한다.
   // 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;
}

자, 드디어 가장 기초적이면서도 갖출건 다 갖춘 서비스 샘플을 구성하여 보았습니다.
별 쓸때 없는 클래스를 2개씩이나 만들어서 코드 모양이 조금 이해하기 어렵게 구성되었지만, 첨부된 플젝을 열어서 보시면
크게 어렵지 않을 것입니다.

어느정도 기초적인 서비스 프로그램을 진행해보신 분이라면 첨부된 2개의 클래스에 대하여 분석해 보는 것도 나쁘지 않겠지만
처음으로 서비스 프로그램을 작성하시는 분이라면, 괜한 시간 낭비일 수도 있으므로 메인 함수만 분석하시는게 작업 속도가 빠를 것입니다.

첨언으로, VC를 이용하여 작성하였는데 생성하시는 프로젝트 타입은 콘솔 응용프로그램 으로 만드시면 됩니다. (완전히 똑같습니다.)
상위버전 VC에 포팅을 용이하게 하느라고 VC++ 6.0으로 샘플을 작성하였으니, 컨버팅하는 부담은 없을 것입니다.

+ Recent posts