본문 바로가기
Programming/Tips(C++,C#)

0122 - 서비스 프로그래밍 (1)

by 곰네Zip 2009. 1. 22.

*서비스프로그래밍
 -메인이있긴하나 메인이메인이아니다.
 -함수호출하지않고 콜백함수가있다.
 -제약이 많지만 권한이 시스템 권한이다. << 이것이 강점!
 -UI를 만들수가 없다.

*골치아픈 고려사항
 - 서비스프로세스 <-> OS는 통신에 문제가없다. 그런데..
 - 일반App. <- 서비스프로세스 (문제없음)
    일반App. -> 서비스프로세스 (고민해야함)

*프로그램작성할때 고려할사항
 - 서비스 install/uninstall/load/unload하는것을 다 만들어서 테스트해주어야한다.

*서비스 프로그램 소스 예제
///////////////////////////////////////////////////////
//header files start
#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
#include <TCHAR.h>

//header files end
///////////////////////////////////////////////////////
//define start
#define SERVICE_NAME _T("ServiceSample")
#define EXIT_EVENT  _T("SAMPLE_SERVICE_EXIT_EVENT")

//define end
///////////////////////////////////////////////////////
//Global variable declaration start
SERVICE_STATUS_HANDLE g_hServiceStatus = NULL;
DWORD     g_dwServiceState = 0;
HANDLE     g_hEventExit = NULL;

//Global variable declaration end
///////////////////////////////////////////////////////
//function prototype declaration start
void TestServiceMain(DWORD argc, LPTSTR* argv);
void SampleServiceHandler(DWORD dwOpcode);
void LogPrint(LPTSTR pszLog, WORD wEventType);
void SetSampleServiceStatus(DWORD dwState, DWORD dwError, DWORD dwExError,
 DWORD dwAccept = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE);
//마지막 dwAccept의 의미는 저러한 이벤트를 받을것이다.를 알림

//function prototype declaration end
///////////////////////////////////////////////////////
//Main function start
int main(int argc, char* argv[])
{
 SERVICE_TABLE_ENTRY DispatchTable[] = {
  {
   SERVICE_NAME,
   (LPSERVICE_MAIN_FUNCTION)TestServiceMain
  },
  {
   NULL,NULL
  }
 };

 //호출되는순간 서비스가 책임진다.얘는 더이상권한없음
 if( !::StartServiceCtrlDispatcher(DispatchTable)){
 }

 return 0;
}

//main function end
///////////////////////////////////////////////////////////
//user funcion define start

// TestServiceMain
void TestServiceMain(DWORD argc, LPTSTR* argv){
 //Steps
 //1. 서비스 핸들러 등록
 DWORD dwResult = 0;
 g_hServiceStatus = ::RegisterServiceCtrlHandler(SERVICE_NAME,
       (LPHANDLER_FUNCTION)SampleServiceHandler);

 if( g_hServiceStatus == NULL){ //error
  //event viewer에 에러 로그가 남도록 해주어야함!
  LogPrint(_T("Failed to register Service control handler"),
     EVENTLOG_ERROR_TYPE); //이벤트 뷰어에 남길모양
     //WINNT.H에있음, AUDIT prefix : 보안관련
  return;
 }
 //2.서비스가 시작되었음을 알리고 초기화 작업 시작
 SetSampleServiceStatus(SERVICE_START_PENDING, NO_ERROR, 0);
 //SERVICE_START_PENDING : 서비스 시작 중임.
 //종료이벤트 설정 - 이건 에러가 나면 안된다.
 g_hEventExit = ::CreateEvent(NULL,FALSE,FALSE,EXIT_EVENT);

 if(g_hEventExit == NULL){ //문제생김
  LogPrint( _T("Failed to create Exit event"),EVENTLOG_ERROR_TYPE);
  return;
 }
 //TODO: 초기화 코드를 넣으세요 ㅋㅋ

 //3.서비스가 정상적으로 실행되었음을 표시
 SetSampleServiceStatus(SERVICE_RUNNING, NO_ERROR, 0);
 LogPrint( _T("정상적으로 서비스가 등록됨."),EVENTLOG_INFORMATION_TYPE);

 //4.종료이벤트를 기다림
 ::WaitForSingleObject(g_hEventExit,INFINITE);

 //5. 서비스 프로세스를 종료함.
 ::CloseHandle(g_hEventExit);
 SetSampleServiceStatus(SERVICE_STOPPED,NO_ERROR,0);

}

// SampleServiceHandler
void SampleServiceHandler(DWORD dwOpcode){
 //서비스 매니저와 소통하는역할. 즉. 서비스매니저에의해 호출당함
 if(dwOpcode == g_dwServiceState){
  return;
 }

 switch(dwOpcode){
  case SERVICE_CONTROL_PAUSE:
   SetSampleServiceStatus(SERVICE_PAUSE_PENDING, NO_ERROR, 0);
   //TODO: 일시정지시 관련 코드를 작성
   SetSampleServiceStatus(SERVICE_PAUSED, NO_ERROR, 0);
   LogPrint( _T("서비스가 일시 정지됨."),EVENTLOG_INFORMATION_TYPE);
   break;

  case SERVICE_CONTROL_CONTINUE:
   SetSampleServiceStatus(SERVICE_CONTINUE_PENDING, NO_ERROR, 0);
   //TODO: 재시작시 관련 코드를 작성
   SetSampleServiceStatus(SERVICE_RUNNING, NO_ERROR, 0);
   LogPrint( _T("서비스가 정상적으로 재시작됨."),EVENTLOG_INFORMATION_TYPE);
   break;

  case SERVICE_CONTROL_STOP:
   SetSampleServiceStatus(SERVICE_STOP_PENDING, NO_ERROR, 0);
   //종료 과정이 시작됨
   ::SetEvent(g_hEventExit);
   break;

  case SERVICE_CONTROL_INTERROGATE:
   SetSampleServiceStatus(SERVICE_STOPPED, NO_ERROR, 0);
   break;

  default:
   SetSampleServiceStatus(g_dwServiceState, NO_ERROR, 0);
   break;
 }
}

// LogPrint
void LogPrint(LPTSTR pszLog, WORD wEventType){
 TCHAR* lpszStrings[2] = {0};
 TCHAR szMsg[256] = {0};
 HANDLE hEventSource = NULL;

 DWORD dwError = ::GetLastError();
 //이벤트 소스를 등록한다. 서비스를 이벤트뷰어에등록하는거임
 hEventSource = ::RegisterEventSource(NULL, SERVICE_NAME);
 wsprintf(szMsg,_T("%s [ERROR CODE: %d]"),SERVICE_NAME,dwError);

 lpszStrings[0] = szMsg;
 lpszStrings[1] = pszLog;

 if(hEventSource != NULL){
  ::ReportEvent(hEventSource,
      wEventType,
      0,
      0,
      NULL,
      2,
      0,
      (const char**)(LPTSTR*)lpszStrings,
      NULL);
  ::DeregisterEventSource(hEventSource);
 }
}

// SetSampleServiceStatus
void SetSampleServiceStatus(DWORD dwState, DWORD dwError,DWORD dwExError, DWORD dwAccept){
 SERVICE_STATUS ss = {0};
 ss.dwServiceType    = SERVICE_WIN32_OWN_PROCESS;
 ss.dwCurrentState    = dwState;
 ss.dwControlsAccepted   = dwAccept;
 ss.dwWin32ExitCode    = dwError;
 ss.dwServiceSpecificExitCode = 0;
 ss.dwCheckPoint     = 0;
 ss.dwWaitHint     = 2000; //ms단위

 if(dwError == ERROR_SERVICE_SPECIFIC_ERROR){
  ss.dwServiceSpecificExitCode = dwExError;
 }

 g_dwServiceState = dwState;
 ::SetServiceStatus(g_hServiceStatus, &ss);
}

//user function define end
//////////////////////////////////////////////////////////////

반응형

댓글