회사에서 여러 서버군을 지원하면서 며칠전에 Windows 2008 Server x64 버전을 설치하였다.
새로산 머신을 깔끔하게 정리하고 OS 설치한 후의 느낌은 윈도우 비스타 아류작 정도 되는 느낌..

Hyper-V 문답 [kor] : http://www.microsoft.com/korea/windowsserver2008/virtualization/faq.mspx
Hyper-V 주요기능[kor] : http://www.microsoft.com/korea/windowsserver2008/virtualization/default.mspx

현재 화면을 캪처해본것...


앞으로의 테스트를 위해서 2008 서버를 계속 운영하기는 해야하는데.. 흠.. 서버 사양은 쓸만한데
테스트용으로만 사용하기는 낭비가 아닐까 싶었다. VMWare를 올려서 가상 머신을 돌리면 너무 무겁고
결국 2008 가상화 기능인 Hyper-V 를 사용해보기로 맘먹고 서버관리자->역활->역활추가->Hyper-V 를 일단 인스톨
아래 그림은 이미 설치된 후에 캡처한거라 설치됨으로 나와있다.


설치된 후 한번의 리부팅이 있은 후 드디어 가상머신(게스트 오퍼레이션이라고도 부른다)을 설치하기에 이른다.
일단 Windows 2008 Server x86 버전을 추가로 설치하기로 하고 기본적인 셑팅을 마쳤다.


해당 가상 머신을 선택하고, Start 를 누르는 순간 Hyper-V service is not running.. 라는 에러 메시지와 함께
멈추어 버린다. (물론 기본 3개의 서비스는 잘 돌구 있었다) 새로운 OS를 올려 좀더 여유있는 테스트 환경을 만들어 보려고 했던 노력이 한순간에 날아가 버린듯... 지금 까지 투자한 시간이 아까워서 에러 원인 추적에 들어갔다.

MSDN 및 Hyper-V 도움말등을 몽땅 뒤져보니, CPU가 가상화 기능을 지원해야 한단다.



일단 머신을 종료한 후 리부팅하면서 BIOS에 들어가 칩셑 옵션들을 살펴보기 시작했다. 가상화와 관련된 옵션을 살펴보니
옵션에 떡하니 Intel Vitualization [Enable/Disable] 옵션이 버티고 있었다. (프로세스는 Intel Xeon E5420 모델)
옵션을 Enable 한 후, 리부팅 하고... Start 버튼을 누르니 VMWare와 거의 흡사한... 윈도우가 생성되면서 OS 설치
화면으로 넘어간다. 기본적으로 설치 화면에서 OS 를 설치하는 동안 마우스의 사용이 금지되어 있다. (이건 쫌 불편함)

아래는 Hyper-V 로 띄운 가상머신의 화면이다.

일단 CD/DVD Rom 에 이미지(iso)를 맘대로 넣었다 뺄수 있는 기능이 맘에 들었다.
또한... 메모리를 공유 자원이 아니니 어쩔 수 없지만, CPU 사용이 가상화 되면서 터미널로 가상머신에 접속했을 때
동작속도는 VMWare 워크스테이션 버전과는 비교할 수 없을 정도로 안정되고 빠른 속도를 보여주었다.
마치 하나의 머신자원을 그대로 쓰는 느낌이 들정도... ^^;;;

테스트 서버의 설정을 마친 후, 개인적으로 사용하는 작업 머신을 2008로 업그레이드할 생각으로 프로세스 정보를 확인해 보았다.
어쩔 수 없는 이유로 Windows 2003 Server 를 사용하고 있는데, 걸리는게 많아서 불편한 점이 한두가지가 아니다 보니..
2008로 업그레이드 한후, 2003 서버, XP, Vista 를 설치해보구 싶음 마음에.. ㅎㅎ;;;

Interl Core2 Duo E6550 2.3GHz 프로세스를 사용하는데, 바이오스를 확인해 보니 역시나 가상화를 지원한다고 나와있다...

시간이 난다면 작업 머신을 밀어버리고
Windows 2008 Server x64 버전을 베이스로 깔고, Hyper-V 를 설치 한후
-> Windows 2008 Server x86
-> Windows 2003 Server x64, x86
-> Windows XP Profssional x86
-> Windows Vista Basic
이렇게 설치해서 작업 환경을 개선해볼까 한다.


서비스를 설치 하고, 제거 할 수 있으며 서비스를 시작하면 1초마다 윈도우즈 기본 에러메시지.. ~ 이 나옵니다.
서비스를 일시 중지 시키면 동작이 멈추고, 재시작하면 다시 띵~ 이러한 구조로 흘러갑니다.
간단하게 util.h util.cpp 를 만들어서 구조와 크게 상관없는 함수들을 모아놓았습니다.

#include "util.h"
#pragma comment(lib, "Advapi32.lib")

const char* S_NAME = "TEST";
const char* S_DISP   = "TEST Service";
const char* S_DESC  = "TEST Service Description"; 

// 서비스를 제어하기 위한 핸들을 저장하고 있어야한다.
SERVICE_STATUS_HANDLE srvhd = 0;

// 서비스의 상태를 저장하는 플래그
DWORD  srvst = SERVICE_STOPPED;

int main(int argc, char** argv)
{
    // 실행파일의 경로를 구한다.
    char S_BINARY[MAX_PATH] = {0};
    ::GetModuleFileName(NULL, S_BINARY, MAX_PATH);

    // 인자가 2개이고, 두번째 인자가 'i' 이면 인스톨
    if(argc == 2 && argv[1][0] == 'i')
    {
        ServiceInstall();
        return 0;
    }

    // 인자가 2개이고, 두번째 인자가 'u' 이면 언인스톨
    else if(argc == 2 && argv[1][0] == 'u')
    {
        ServiceUninstall();
        return 0;
    }

    SERVICE_TABLE_ENTRY STE[] =   
    {
        {(char*)S_NAME, (LPSERVICE_MAIN_FUNCTION)_tmain_service},
        {NULL,NULL}
    };
    // 서비스 메인 함수를 등록한다.
    if(StartServiceCtrlDispatcher(STE) == FALSE)
        return -1;

    return 0;
}

 VOID _tmain_service(INT ARGC, LPSTR* ARGV)
{
    srvhd = RegisterServiceCtrlHandlerEx(S_NAME, _tmain_service_handler, NULL);
    if (srvhd == NULL)
        return;       

    // 정상적으로 서비스가 시작되었다.
    // 서비스의 기본적인 작업이 끝나고 메인 루프로 들어가기 전에 꼭 상태를
    // 런닝 상태로 만들어주어야 한다
    // 이걸 처리해놓지 않으면, 서비스 관리자에서 정상적으로 시작된건을 인지하지 못한다.
    SET_SERVICE_STATE(srvhd, SERVICE_RUNNING);

    // 무한 루프를 돌면서 띵띵~ 소리를 낸다.
    while(GET_SERVICE_STATE() != SERVICE_STOPPED)
    {
        // 일시 중지 상태일 때는 그냥 별동작 없이 대기를 반복한다.
        if(GET_SERVICE_STATE() == SERVICE_PAUSED)
        {
            Sleep(1000);
            continue;
        }

        ::MessageBeep(0xFFFFFFFF);
        Sleep(1000);
    }
    return;
}

DWORD WINAPI _tmain_service_handler(DWORD fdwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext)
{
    // 서비스라 좋은점이 있네, 시스템에서 발생하는 잡다구리한 이벤트들을 별로 힘들이지 않고
    // 이곳에서 받아다가 처리할 수 있다.

    switch (fdwControl)
    {
    case SERVICE_CONTROL_PAUSE:
        SET_SERVICE_STATE(srvhd, SERVICE_PAUSE_PENDING,0);
        // 서비스를 일시 중지 시킨다.
        SET_SERVICE_STATE(srvhd, SERVICE_PAUSED);
        break;

    case SERVICE_CONTROL_CONTINUE:
        SET_SERVICE_STATE(srvhd, SERVICE_CONTINUE_PENDING,0);
        // 일시 중지 시킨 서비스를 재개한다.
        SET_SERVICE_STATE(srvhd, SERVICE_RUNNING);
        break;

    case SERVICE_CONTROL_STOP:
        SET_SERVICE_STATE(srvhd, SERVICE_STOP_PENDING, 0);
        // 서비스를 멈춘다 (, 종료와 같은 의미)
        SET_SERVICE_STATE(srvhd, SERVICE_STOPPED);
        break;     

    default:
        break;
    }

    return NO_ERROR;
}

// 아래 파일은 샘플 프로젝트입니다.

TestService.zip
0.01MB


다음에는 개인용 피씨나, 서버 피씨에 어떤 사용자가 접속하고, 종료했는지 터미널 서버로 들어왔다면
어떤 IP주소로 접속했는지 등등등.. '내컴퓨터 누가 건드렸어..' 서비스를 만들어 보도록 하겠습니다.

출처: https://crowback.tistory.com/184?category=86281 [까막's 블로그:티스토리]


const char* S_NAME = "TEST";
const char* S_DISP = "TEST Service";
const char* S_DESC = "TEST Service Description";

// 서비스를 제어하기 위한 핸들을 저장하고 있어야한다.
SERVICE_STATUS_HANDLE srvhd = 0;

VOID _tmain_service(INT ARGC, LPSTR* ARGV);
DWORD WINAPI _tmain_service_handler(DWORD fdwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext);
VOID SERVICE_STATE(DWORD dwState, DWORD dwAccept);

int main(int argc, char** argv)
{
    SERVICE_TABLE_ENTRY STE[] =
    {
        {(char*)S_NAME, (LPSERVICE_MAIN_FUNCTION)_tmain_service},
        {NULL,NULL}
    };
    
    // 서비스를 위해서 특별히 만들어진 구조의 함수 시작 부분을 시스템에 전달해야 한다.
    // 콘솔 프로그램과 다른 부분은 이렇게 등록시킨 함수가 콘솔의 main 처럼 동작한다는 점이다.
    // 일종의 콜백함수 포인터를 등록하면, 서비스 매니저가 이걸 호출해주는 방식이다.
    if(StartServiceCtrlDispatcher(STE) == FALSE)
        return -1;
   
    return 0;
}

VOID _tmain_service(INT ARGC, LPSTR* ARGV)
{
    // 서비스가 외부 제어 명령(시작, 중지, 다시 시작... etc) 을 받을 때, 그것을 받을 수 있도록 콜백형식의
    // 함수를 등록하는 것이다.

    srvhd = RegisterServiceCtrlHandlerEx(S_NAME, _tmain_service_handler, NULL);
    if (srvhd == NULL)
        return;

    // 무한 루프를 돌면서 띵띵~ 소리를 낸다.
    while(1)
    {
        ::MessageBeep(0xFFFFFFFF);
        Sleep(1000);
    }

    return;
}

// 서비스의 상태를 변경 시켜주는 함수.
VOID SERVICE_STATE(DWORD dwState, DWORD dwAccept)
{
    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;
        
    SetServiceStatus(srvhd, &ss);
}

DWORD WINAPI _tmain_service_handler(DWORD fdwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext)
{
    // 서비스라 좋은점이 있네, 시스템에서 발생하는 잡다구리한 이벤트들을 별로 힘들이지 않고
    // 이곳에서 받아다가 처리할 수 있다.


    switch (fdwControl)
    {
    case SERVICE_CONTROL_PAUSE:
        SERVICE_STATE(SERVICE_PAUSE_PENDING,0);
        // 서비스를 일시 중지 시킨다.
        SERVICE_STATE(SERVICE_PAUSED);
        break;
       
    case SERVICE_CONTROL_CONTINUE:
        SERVICE_STATE(SERVICE_CONTINUE_PENDING,0);
        // 일시 중지 시킨 서비스를 재개한다.
        SERVICE_STATE(SERVICE_RUNNING);
        break;
       
    case SERVICE_CONTROL_STOP:
        SERVICE_STATE(SERVICE_STOP_PENDING, 0);
        // 서비스를 멈춘다 (즉, 종료와 같은 의미)
        SERVICE_STATE(SERVICE_STOPPED);
        break;
       
    default:
        break;
    }
   
    return NO_ERROR;
}

간단하게 샘플을 완성해 보려고 했더니, 좀더 알고 있어야 하는 사항들이 자꾸 생기네요..

   _tmain_service_handler 에 추가된 내용

   각각의 서비스 상태 변화에 맞추어  SERVICE_STATE 라는 함수를 호출합니다.

   위 함수의 기능은 현재 이 서비스는 이러한 상태입니다.... 라고 서비스 관리자 (SCM)에
   전달해 주는 기능을 합니다. 이것을 빼놓고 동작만 구현하면 서비스 관리자에서 해당
   서비스의 상태를 정상적으로 보여 줄 수 없기 때문에 잘못된 동작을 일으킬 수 있습니다.

   서비스는 상태를 변화 시키기 이전에 "상태 변화를 준비중입니다". 라고 하는 PENDDING
   상태로 먼저 만들고, 필요한 작업을 한 후 마지막에 해당 상태로 변환 되었음을 알려주어야 합니다.


지금까지는 전체 소스 샘플없이, 부분 코드만으로 작업을 진행했습니다만, 다음 진행되는 부분 부터는
프로젝트를 만들어서 소스를 업데이트 시키고, 파일로 첨부하겠습니다.

const char* S_NAME = "TEST";
const char* S_DISP = "TEST Service";
const char* S_DESC = "TEST Service Description";

VOID _tmain_service(INT ARGC, LPSTR* ARGV);
DWORD WINAPI _tmain_service_handler(DWORD fdwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext);

int main(int argc, char** argv)
{
    SERVICE_TABLE_ENTRY STE[] =
    {
        {(char*)S_NAME, (LPSERVICE_MAIN_FUNCTION)_tmain_service},
        {NULL,NULL}
    };
    
    // 서비스를 위해서 특별히 만들어진 구조의 함수 시작 부분을 시스템에 전달해야 한다.
    // 콘솔 프로그램과 다른 부분은 이렇게 등록시킨 함수가 콘솔의 main 처럼 동작한다는 점이다.
    // 일종의 콜백함수 포인터를 등록하면, 서비스 매니저가 이걸 호출해주는 방식이다.
    if(StartServiceCtrlDispatcher(STE) == FALSE)
        return -1;
   
    return 0;
}

VOID _tmain_service(INT ARGC, LPSTR* ARGV)
{
    // 서비스가 외부 제어 명령(시작, 중지, 다시 시작... etc) 을 받을 때, 그것을 받을 수 있도록 콜백형식의
    // 함수를 등록하는 것이다.

    SERVICE_STATUS_HANDLE hd = RegisterServiceCtrlHandlerEx(S_NAME, _tmain_service_handler, NULL);
    if (hd == 0)
        return;

    // 요기가 콘솔의 main() 함수 역활을 하는 곳이다.
    // 먼가 기능을 넣으려면 이곳에다가 만들면 된다.  
  

    return;
}

DWORD WINAPI _tmain_service_handler(DWORD fdwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext)
{
    // 서비스라 좋은점이 있네, 시스템에서 발생하는 잡다구리한 이벤트들을 별로 힘들이지 않고
    // 이곳에서 받아다가 처리할 수 있다.


    return 0;
}

살펴보면, 간단한 Windows Console Application 에다가 기본 main() 함수를 잠깐 개조한것 정도로 밖에는 보이지 않는다.
이렇게 처리하면서 서비스용 메인함수서비스 제어용 제어함수 2개를 등록하면 기본 서비스 구조는 끝이다.

서비스의 장점은 일단 윈도우가 로그온/로그오프 상태에서도 정상적으로 동작한다는 것이고,
단점은, 일반적인 어플리케이션에서 사용하는 많은 기능이 제약된다는 것이다. 디버깅도 좀 취약하다.
           특히나, 서버계열에서는 간단한 EnumWindows 같은 함수도 원하는데로 동작하지 않는다. -_-;;;;
           즉, 특정 터미널 세션이나, 데스크톱 윈도우의 제어 기능(일반 어플들의 기능)같은 것들을 처리할라믄 꽤 고달프다는 말이다.
           메인 함수야 제대로 블록해놓고 구성해도 되지만 제어기능의 내부는 블록되면 다른 서비스에도 영향을 미친다.

자, 이렇게 기본 서비스의 구조를 살펴봤으니, 다음장에서는 간단하게 띵띵~ 소리를 내는 서비스를 만들어 보자.
서비스의 시작(START), 중지(STOP), 일시 정지(PAUSE), 이어가기(CONTINUE) 동작도 처리해보자.

간단하게 서비스를 설치하는 기능에 대하여 설명하여 보았습니다.
서비스를 설치할 때 CreateService에 들어가는 인자들을 살펴보면 더 많은 무언가가 있어보이지만..
그건 진행하면서 필요할 때 하나씩 추가하는 걸로 해보죠..

자, 이제 서비스를 설치해봤으니, 제거를 해봐야 겠죠?
설치는 간단하지만, 제거는 좀더 복잡한 과정을 거쳐야합니다.

일단, 주의 사항으로 서비스는 제거하기 전에 꼭 멈추고 제거시켜야 한다는 것을 명심하세요.
단계는 서비스 열고, 중지 시키고, 제거한다.


DWORD ServiceUninstall()
{
    // 에러 처리를 위한 간단한 매크로
    #define ERROR_RETURN { RET = GetLastError(); goto ERROR_LABEL; }
    
    SC_HANDLE hSrv;
    DWORD RET = ERROR_SUCCESS;
    DWORD dwType;
    int loop = 0;
    SERVICE_STATUS ss;

    // SCM 을 열어서 서비스에 작업을 진행할 수 있도록 핸들을 하나 달라고 하자.
    SC_HANDLE hScm = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
    
    // 열지 못하면 에러다. 머 어쩔 수 없다. 왜 그런지 에러 코드나 리턴한다.
    if (hScm == NULL)
        return GetLastError();
   
   
    // 서비스 관리자를 열었으니, 이제 서비스를 직접 열어보자.
    hSrv = OpenService(hScm, S_NAME, SERVICE_ALL_ACCESS);
    if (hSrv == NULL)
        ERROR_RETURN;
   
   
    // 관리자를 열어으면, 이젠 서비스의 상태를 읽어오자.
    // 서비스를 제거하려면 먼저, 서비스를 멈추어야 한다.

    loop = 0;
    do
    {
        ControlService(hSrv, SERVICE_CONTROL_INTERROGATE, &ss);
        Sleep(1);
    } while (++loop <10);
    dwType = ss.dwCurrentState;
   
   
    // 만약 서비스의 상태가 멈춤이 아니면, 서비스를 중지 시키자.
    if(dwType != SERVICE_STOPPED)
    {
        if(!ControlService(hSrv, SERVICE_CONTROL_STOP, &ss))
            ERROR_RETURN;
    }
   
   
    // 아주 무식한 방법으로, 서비스가 멈출 때 까지 기다린다.
    // 하지만 서비스를 멈춰야 하는데, 멈추지 않는다면?
    // 서비스를 제작한 사람을 쪼아야지.. 불쌍한 서비스 제거
    // 프로그램이 먹통되었다고 화내면 건강에 해롭다.
   
    // 만약 서비스를 멈추지 않고, 동작중인데 DeleteService를
    // 이용하여 제거하면, 컴터를 리부팅 하기 전까지는 다시
    // 설치가 불가능하다. (서비스 내부 디비엔트리에 먼가 찌꺼기가 남아서 안된다는데..흠...)

    do
    {
        Sleep(100);
       
        loop = 0;
        do
        {
            ControlService(hSrv, SERVICE_CONTROL_INTERROGATE, &ss);
            Sleep(1);
        } while (++loop <10);
        dwType = ss.dwCurrentState;
    } while(dwType != SERVICE_STOPPED);
   
   
    // 멈추어졌으면 제거한다.

    if(!DeleteService(hSrv))
        ERROR_RETURN;
   
ERROR_LABEL:
    if(hSrv)
        CloseServiceHandle(hSrv);
    if(hScm)
        CloseServiceHandle(hScm);
   
    return RET;
}

와우!!! 잘 살펴보면 알겠지만, 서비스를 제거할 때 필요한 것은 서비스를 제거할 수 있는 권한과, 서비스 이름만 알면된다.

이전 장에서 나온 인자에 대하여 간단하게 설명을 추가하도록 한다.
        (1)서비스 이름,
        (2)서비스 표시 이름,
        (3)서비스 액세스 권한,
        (4)서버스 타입,
        (5)서비스 시작 타입,
        (6)서비스 에러 제어,
        (7)서비스 파일 경로,
        (8)서비스의 세부 설명

대략 살펴보아야 할 인자에 대하여 번호를 붙혀두었다. 그림을 보면서 살펴보자.

서비스 관리자에 나오는 화면을 캡처하고, 인자 번호를 부여하여 보았다. 구지 추가 적인 설명을
달지 않더라도 어떤 의미인지 확실하게 이해할 수 있을거라 생각한다.
 
나머지 인자에 대한 설명을 달아보자.

(3)서비스 액세스 권한 - 말 그대로 서비스를 핸들링할 수 있는 세부 권한을 말한다.
  별일 없으면 그냥 SERVICE_ALL_ACCESS 때려 넣는다.

더보기
SERVICE_ALL_ACCESS (0xF01FF) Includes STANDARD_RIGHTS_REQUIRED in addition to all access rights in this table.
SERVICE_CHANGE_CONFIG (0x0002) Required to call the ChangeServiceConfig or ChangeServiceConfig2 function to change the service configuration. Because this grants the caller the right to change the executable file that the system runs, it should be granted only to administrators.
SERVICE_ENUMERATE_DEPENDENTS (0x0008) Required to call the EnumDependentServices function to enumerate all the services dependent on the service.
SERVICE_INTERROGATE (0x0080) Required to call the ControlService function to ask the service to report its status immediately.
SERVICE_PAUSE_CONTINUE (0x0040) Required to call the ControlService function to pause or continue the service.
SERVICE_QUERY_CONFIG (0x0001) Required to call the QueryServiceConfig and QueryServiceConfig2 functions to query the service configuration.
SERVICE_QUERY_STATUS (0x0004) Required to call the QueryServiceStatusEx function to ask the service control manager about the status of the service.
SERVICE_START (0x0010) Required to call the StartService function to start the service.
SERVICE_STOP (0x0020) Required to call the ControlService function to stop the service.
SERVICE_USER_DEFINED_CONTROL(0x0100) Required to call the ControlService function to specify a user-defined control code.

(4)서버스 타입 - 윈도우즈에서 말하는 서비스라는건 여러가지 의미로 사용되는데 여기서는 현재 설치하려는 무언가가
  무엇인지를 알려주어야 한다. 파일시스템 드라이버(SERVICE_FILE_SYSTEM_DRIVER) 일 수도 있고,
  디바이스 드라이버(SERVICE_KERNEL_DRIVER) 일 수 도 있으며 지금 만들려고 하는 일반 응용프로그램
  수준의 서비스 프로그램(SERVICE_WIN32_OWN_PROCESS) 일 수 도 있다.

(6)서비스 에러 제어 - 만약 시스템 초기에 서비스가 구동하다가 에러가 발생하면 어떻게 할것인가? 에러난 서비스가 파일    시스템  이라면? 키보드 드라이버라면? 내가 만든 간단한 응용 서비스라면? 과 같이.. 서비스의 특성과 중요도에 따라,
  재부팅을 할 수도, 그냥 무시( SERVICE_ERROR_IGNORE) 할 수도 있다.

더보기
SERVICE_ERROR_CRITICAL
0x00000003
The startup program logs the error in the event log, if possible. If the last-known-good configuration is being started, the startup operation fails. Otherwise, the system is restarted with the last-known good configuration.
SERVICE_ERROR_IGNORE
0x00000000
The startup program ignores the error and continues the startup operation.
SERVICE_ERROR_NORMAL
0x00000001
The startup program logs the error in the event log but continues the startup operation.
SERVICE_ERROR_SEVERE
0x00000002
The startup program logs the error in the event log. If the last-known-good configuration is being started, the startup operation continues. Otherwise, the system is restarted with the last-known-good configuration.

아무것도 없는, 서비스 인스톨 함수 딸랑 하나 있는 Windows Console Application 이다.
아래 샘플 소스에 하나씩 하나씩 추가하여 간단한 서비스를 만들어 보도록 하자.

TestService.zip
0.01MB

개념도 없이, 아무것도 없이 갑자기 웬? 서비스의 설치? 코딩 한줄도 못만들어 봤는데.. -_-???

작성한 서비스 프로그램을 돌려 보려면, 일단 서비스를 설치해서 구동해야 한다.
그냥 콘솔 상태에서 아무리 디버깅해봐야 실제로 서비스 레이어에서 구동할 때 정상적인지...
실제로 서비스로 돌아는 가는지 확인해 봐야 할것 아닌가?

일단 아주 심플한 서비스 설치용 함수이다.
인자 중에 NULL 라고 된것들은 첨에 알아봐야 머리만 아프고
별로 쓸일이 없는 경우이다. 예제 만들다 필요하면 설명이 추가될 수 도 있을것 같은데 잘 모르겠다.

해당 소스를 사용하려면
   헤더파일 : #include <windows.h>
   라이브러리 : Advapi32.lib
                   : 코드로 라이브러리를 추가하려면 #pragma comment(lib, "Advapi32.lib")
가 있어야 한다.

DWORD ServiceInstall()
{
    DWORD RET = ERROR_SUCCESS;
    SC_HANDLE hSrv;

    // SCM 을 열어서 서비스에 작업을 진행할 수 있도록 핸들을 하나 달라고 하자.
    // 지금 할 작업은 새로운 서비스를 만들어 등록하는 작업이다.
    SC_HANDLE hScm = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
        
    // 열지 못하면 에러다. 머 어쩔 수 없다. 왜 그런지 에러 코드나 리턴한다.
    if (hScm == NULL)
        return GetLastError();
           
    // SCM 을 서비스 생성 권한으로 열었으니, 서비스를 만들어야지..
    // 함수에 필요한 인자들을 채워서 넣어주면 된다. 
    // 첫번째 인자는 SCM 에서 넘겨준 핸들이고 나머지는 밑에다가 추가적으로 설명을 단다.

    hSrv = CreateService(
        hScm,
        (1)서비스 이름,
        (2)서비스 표시 이름,
        (3)서비스 액세스 권한,
        (4)서버스 타입,
        (5)서비스 시작 타입,
        (6)서비스 에러 제어,
        (7)서비스 파일 경로,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL);
    
    // 서비스 생성이 실패하면 별수 없다. 여러가지 이유가 있을 테니까..
    // 에러 코드나 리턴하자.

    if (hSrv == NULL)
    {
        RET = GetLastError();
        CloseServiceHandle(hScm);
        return RET;
    }
    else
    {
        // 와우 서비스 생성에 성공했네...
        // 서비스 관리자 (services.msc) 를 열어보면 해당 서비스마다, 친절하게 이건 무신 무신 서비스 입니다.
        // 라는 설명을 보았을 것이다. 그와 같은 설명을 달아주는 부분이다.
        // 구지 설명 달아줄 필요가 없으면 건너 띄어도 무방하다.
        lpDes.lpDescription=(8)서비스의 세부 설명;
        if(!ChangeServiceConfig2(hSrv, SERVICE_CONFIG_DESCRIPTION, &lpDes))
        {
            RET = GetLastError();
            CloseServiceHandle(hScm);
            CloseServiceHandle(hSrv);
            return RET;
        }
        
        CloseServiceHandle(hScm);     
        return CloseServiceHandle(hSrv) ? ERROR_SUCCESS : GetLastError();
    }
}
윈도우에서 프로그램을 개발하다 보면, 응용 어플리케이션으로 만들었을 경우..
이런 저런 한계에 때문에 고민해 보신 분들이 있을 겁니다.

터미널 서버에서 프로그램을 구동시켜 놓았는데, 누군가 세션을 로그 오프 시켜버리면 꺼진다든지
아니면, 서버가 부팅되었는데 아무도 로그온을 시켜 놓지 않은 상태에서 (즉, 부팅만 된 상태)
돌아가야 한다던지.. 기타 등등 서버 계통에서는 많은 부분을 서비스 프로그램으로 개발하게 된다.

또한, 비스타 계열에서는 UAC 때문에 의외로 까탈스런 부분들이 많은데, 시스템을 건드리는 대부분을
서비스에 몰아놓고, 응응프로그램에서는 서비스와 통신하여 불필요한 경고창이 뜨지 않도록 할 수도 있다.

대충 아주 기본적인걸 살펴보면, 설치->시작->중지->제거 형태로 이루어지고 실제적인 수행은 일반 어플과
크게 다르지 않다. 몇몇 제약사항도 있고, 장점도 존재하므로 이런 것들은 나중에 살펴보도록 한다.

위의 과정을 나열하기 전에 알아두어야할 개념으로 SCM (Service Control Manager) 이라는 용어에 익숙해지자.
윈도우즈 시스템에서 서비스를 제어하는 관리자이며 필요한 대부분의 API를 제공해주는 키워드이다.



다음 장 부터, 서비스의 설치, 시작/중지, 서비스의 제거 형태로 먼저 진행을 한 후에..
기본적인 서비스의 틀을 가진 템플릿 형태의 프로그램을 제작하는 단계로 진행해 볼까 한다.

지금껏 이벤트로그에 쓰는건 해봤어도, 이벤트 로그를 읽을일이 없어 그냥 무심히 지나쳐왔었는데요..
일이란걸 하다보니, 이벤트 로그를 읽어들이면 간단하게 끝날 수 있는데,
직접 코딩하려니 무자게 빡센 업무가 되는것들도 있더라구요.. 흠...

지금 하려는 작업은 시스템 이벤트중 보안(Security) 이벤트에 먼가가 써지면 그 이벤트를 바로 받아서
어떤게 써졌는지 읽어오는 작업이 되겠습니다.

// 이벤트 로그의 핸들과, 그것의 이벤트를 처리할 EVENT 핸들
HANDLE m_hEventLog;
HANDLE m_hEvent;

// 이벤트 로그를 오픈하고, 새로운 이벤트를 하나 만든다.
m_hEventLog = OpenEventLog(NULL, "Security);
m_hEvent = CreateEvent(NULL, FALSE, FALSE, "crowback_m_hEvent");

// 열린 이벤트로그에, 새로 만든 EVENT를 등록하여
// 이벤트 로그의 이벤트를 받아보자.
if(NotifyChangeEventLog(m_hEventLog, m_hEvent) == FALSE)
{
   if(m_hEvent)
        CloseHandle(m_hEvent);
   CloseEventLog(m_hEventLog);
}

비동기로 계속 이벤트를 모니터링 해야하므로 별도의 스레드가 하나 있어야겠죠?
프로그램 자체의 메인 루프에다가 처리해도 상관없을꺼구요..

// 스레드나 메인루프에서 이벤트를 대기하다가, 시그널이 날라오면
// 해당 이벤트 로그를 열어서 필요한 정보를 추출한다.
while(종료조건)
{
    if(WaitForSingleObject(pThis->EventLogGetEventHandle(), 100) == WAIT_OBJECT_0)
        EventLogCallback();
}

// 이벤트 로그를 열어서 최근 데이터를 읽어들인다.
void EventLogCallback()
{
    HANDLE evt = OpenEventLog(NULL, "Security");
    if (evt == NULL)
        return;

    BYTE bBuffer[4096] = {0};
    DWORD dwRead, dwNeeded;
    EVENTLOGRECORD *pevlr = (EVENTLOGRECORD *) &bBuffer;
    LPCTSTR lpSourceName;
    
    if(ReadEventLog(evt,   // Event log handle
        EVENTLOG_BACKWARDS_READ | // 최근 것을 읽어들인다.
        EVENTLOG_SEQUENTIAL_READ, // Sequential read
        0,       // Ignored for sequential read
        pevlr,      // Pointer to buffer
        4096,      // Size of buffer
        &dwRead,     // Number of bytes read
        &dwNeeded) == FALSE)   
    {
        CloseEventLog(evt);
        return;
    }
    CloseEventLog(evt);

    lpSourceName = (LPCTSTR) ((LPBYTE) pevlr + sizeof(EVENTLOGRECORD));

    // EVENTLOGRECORD 구조체에 필요한 정보가 모두 들어있다.
}


9월초 오산의 물향기 수목원에 다녀오면서 찍은 사진입니다.
지금까지 신혼초에 삿던 구형 디카에서 벗어나, 캐논 860IS로 촬영한것인데요..
카메라에 신경좀 써야겠다는 생각이 물씬 나는군요..
공부좀 하면서, 여유가 되면 DSLR을 하나 장만해, 사진쪽으로 취미를 가져볼까 생각중입니다.

   
입구에서 현이와 은이를 찍은 사진...
5000원 주고산 썬글라스가 생각보다 뽀내가 나네요.. ㅎㅎ;;;
 

은이의 해맑은 미소를 보면... 이런게 부모 마음이구나... 하는 생각도 들고,
너무 빨리 자라는게 아닌가, 하는 아쉬움도 드네요 ^^;;;

버뜨, 며칠전에 오랜만에 온가족이 모여서 점심때 떡뽁기를 해먹었습니다.
현이랑, 애 엄마는 무척 좋아하지만, 은이는 좀 매워서 물에다가 행궈서 먹는데...

장난기가 발동한 아빠 왈 : 아빠는 크니까 많이 먹어야지! 넌 쪼메나니까 쫌만 먹어라.
만만찬은 울 아들 : 아빤 다 컷으니까 쬐끔만 드세요. 저는 아직 많이 커야하니까 더 많이 먹을께요.
GG;;;


+ Recent posts