정리

[Win32] Service - #3 서비스 설치

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

서비스를 설치한다는 것은 시스템 레지스트리에 서비스에 대한 정보를 써넣는 작업이다. 그러나 설사 레지스트리의 구조를 완벽하게 안다고 하더라도 직접 레지스트리에 값을 써넣는 것은 호환성에 불리하다. (어렵고 귀찮기도 하지..-_-;) 운영체제가 업그레이드되면 레지스트리 구조가 어떻게 바뀔지 알 수 없기 때문이다. 게다가 서비스 설치는 원격지에서도 할 수 있어야 하므로 레지스트리를 직접 건드리기 힘든 경우도 있다.

뭐.. 여튼 서비스 설치는 어렵다는 게 결론이고, 그래서 설치 프로그램이 필요하다는 말임.
플랫폼 SDK와 함께 배포되는 SC.EXE라는 서비스 설치 유틸리티가 있긴 하지만 사용법이 복잡해서 End User에게 설치용으로 배포하는 것은 무리라고 한다.
그래서 직접 만들란다. 귀찮게스리... -_-+

대략 설치/제거 상태를 확인할 수 있게끔 Install/Unintall 함수를 구현하면 될터이고, 책에 있는 샘플은 아래와 같다. 상세 설명은 코드를 본 후에!!!


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

[Win32] IPC - #3 Pipe(파이프)  (0) 2009.05.14
[Win32] Service - #4 서비스 제어  (0) 2009.05.13
[Win32] Service - #2 서비스 생성  (0) 2009.05.13
[Win32] Service - #1 정의  (0) 2009.05.13
[Win32] IPC - #2 Message(메시지)  (0) 2009.05.12

[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

[Win32] Service - #1 정의

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


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

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

[Win32] IPC - #2 Message(메시지)

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

메시지는 정보를 교환할 수 있는 가장 간단한 방법이며 또한 가장 빠른 방법이기도 하다.
두 프로세스가 서로 윈도우 핸들만 알고 있다면 약속된 메시지의 wParam, lParam을 통해 정보를 교환할 수 있다.
메시지는 메모리를 거치지 않고 운영체제에 의해 직접 전달되므로 주소 공간이 격리되어 있더라도 잘 전달된다.

[ICPMessage1]
#define WM_IPC WM_USER + 1
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
   HDC hdc;
   PAINTSTRUCT ps;
   TCHAR* pszMsg = _T("마우스 왼쪽 버튼을 누르면 메시지 전송됨.");
   HWND hWnd2;

   switch(iMessage)
   {
   case WM_LBUTTONDOWN:
      hWnd2 = ::FindWindow(NULL, _T("IPCMessage2"));
      if(NULL != hWnd2)
         ::SendMessage(hWnd2, WM_IPC, (WPARAM)1234, (LPARAM)0);
      return 0;
   case WM_PAINT:
      hdc = ::BeginPaint(hWnd, &ps);
      ::TextOut(hdc, 10, 50, pszMsg, _tcslen(pszMsg));
      ::EndPaint(hWnd, &ps);
      return 0;
   case WM_DESTORY:
      ::PostQuitMessage(0);
      return 0;
   }

   return (DefWindowProc(hWnd, iMessage, wParam, lParam));
}

[ICPMessage2]
#define WM_IPC WM_USER + 1
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
   HDC hdc;
   PAINTSTRUCT ps;
   TCHAR* pszMsg = _T("메시지를 전달받는 녀석.");
   TCHAR szStr[128] = {0,};

   switch(iMessage)
   {
   case WM_IPC:
      hdc = ::GetDC(hWnd);
      _stprintf(szStr, _T("%d를 받았습니다."), wParam);
      ::TextOut(hdc, 10, 100, szStr, _tcslen(szStr));
      ::ReleaseDC(hWnd, hdc);
      return 0;
   case WM_PAINT:
      hdc = ::BeginPaint(hWnd, &ps);
      ::TextOut(hdc, 10, 50, pszMsg, _tcslen(pszMsg));
      ::EndPaint(hWnd, &ps);
      return 0;
   case WM_DESTORY:
      ::PostQuitMessage(0);
      return 0;
   }

   return (DefWindowProc(hWnd, iMessage, wParam, lParam));
}

비교적 크기가 작은 정보를 전달할 때만 사용할 수 있다.
정보라기보다는 단순한 어떤 사실을 통보하기 위한 목적으로도 사용할 수 있다.
그러나 문자열이나 구조체같은 큰 데이터는 이 메시지로 전달할 수 없다. 메시지와 함께 전달되는 wParam, lParam은 둘 다 더해봐야 불과 8바이트에 불과하기 때문이다.


Win32 환경은 프로세스간 메모리 공간이 격리됨으로 해서 훨씬 더 안정적이기는 하지만 간단한 문자열을 교환하는데도 아주 복잡한 방법이 동원되어야 하는 불편함이 있지만 이러한 불편함을 해소하기 위해 IPC를 위한 전용 메시지가 추가되었고, 이 메시지가 바로 WM_COPYDATA 메시지이다.
wParam에 정보를 보내는 윈도우의 핸들을 넣고 lParam에는 다음 구조체의 포인터를 넣는다.

typedef struct tagCOPYDATASTRUCT{   // cds
DWORD dwData;
DWORD cbData;
PVOID lpData;
} COPYDATASTRUCT;

dwData는 교환하고자 하는 정수값이며 lpData는 교환하고자 하는 문자열(또는 이진 데이터)이며 cbData는 lpData의 크기이다. 정수 하나와 포인터 하나를 보낼 수 있다. 이 구조체에 값을 채운 후 WM_COPYDATA 메시지를 보내면 운영체제가 구조체에 보관된 값을 해당 윈도우로 전달한다.

[CopyData1]
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
   HDC hdc;
   PAINTSTRUCT ps;
   COPYDATASTRUCT cds;
   TCHAR* pszStr = _T("WM_COPYDATA 메시지를 위한 Tesst String");
   TCHAR* pszMsg = _T("마우스 왼쪽 버튼을 누르면 메시지 전송됨.");
   HWND hWnd2;

   switch(iMessage)
   {
   case WM_LBUTTONDOWN:
      cds.dwData = 0;
      cds.cbData = _tcslen(pszStr);
      cds.lpData = pszStr;
      hWnd2 = ::FindWindow(NULL, _T("CopyData2"));
      if(NULL != hWnd2)
         ::SendMessage(hWnd2, WM_COPYDATA, (WPARAM)hWnd, (LPARAM)&cds);
      return 0;
   case WM_PAINT:
      hdc = ::BeginPaint(hWnd, &ps);
      ::TextOut(hdc, 10, 50, pszMsg, _tcslen(pszMsg));
      ::EndPaint(hWnd, &ps);
      return 0;
   case WM_DESTORY:
      ::PostQuitMessage(0);
      return 0;
   }

   return (DefWindowProc(hWnd, iMessage, wParam, lParam));
}

[CopyData2]
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
   HDC hdc;
   PAINTSTRUCT ps;
   COPYDATASTRUCT* pcds;
   TCHAR* pszMsg = _T("CopyData1 프로그램으로부터 메시지를 전달받는다.");

   switch(iMessage)
   {
   case WM_COPYDATA:
      pcds = (PCOPYDATASTRUCT)lParam;
      hdc = ::GetDC(hWnd);
      ::TextOut(hdc, 10, 100, (LPCTSTR)pcds->lpData, pcds->cbData);
      ::ReleaseDC(hWnd, hdc);
      return 0;
   case WM_PAINT:
      hdc = ::BeginPaint(hWnd, &ps);
      ::TextOut(hdc, 10, 50, pszMsg, _tcslen(pszMsg));
      ::EndPaint(hWnd, &ps);
      return 0;
   case WM_DESTROY:
      ::PostQuitMessage(0);
      return 0;
   }
}

COPYDATASTRUCT 구조체의lpData벰버는 포인터이므로 구조체나 배열도 보낼 수 있다. 이 포인턴를 전달받는 프로세스가 참조할 수 있게 변환하는 것은 운영체제가 대신 하는 데 내부적으로 복잡한 처리를 하고 있음은 쉽게 상상이 갈 것이다. 전다랗ㄹ 데이터의 크기만큼 파일 맵핑을 생성하고 통신 대상 프로세스들의 주소 공간에 동시에 맵해 놓고 번지를 lParam으로 전달한다. 이 메시지는 무척 편맇게 사용할 수 있는 반면 그다지 효율이 좋지는 못하다.

WM_COPYDATA 메시지를 사용할 때는 다음 사항에 주의해야 한다.
1. lpData에는 받는 쪽에서 읽을 수 있는 값만 전달해야 한다. 단순한 문자열이라면 간단하겠지만 복잡한 이진 포맷의 데이터라면 통신하는 양쪽이 포맷을 알고 있어야만 한다.
2. 받은 쪽에서 데이터를 다 사용하기 전에 전달된 값이 바뀌지 않도록 해야 한다. SendMessage는 메시지가 완전히 처리되기 전에 리턴하지 않으므로 자신이 직접 변경할 수는 없지만 멀티 스레드를 쓸 경우 다른 스레드에서 값을 변경할 여지를 차단해야 한다.
3. 받는 데이터는 일기 전용이므로 변경해서는 안 된다. 만약 꼭 변경하고자 한다면 로컬 메모리에 복사한 후 사본을 변경해야 한다.
4. WM_COPYDATA 메시지는 SendMessage로 보내야 하며 PostMessage로 부쳐서는 안 된다. 임시적인 처리를 통해 데이터를 전달하는 것이므로 메시지를 큐에 붙이는 것은 의미가 없다. (SendMessage로만 보낼 수 있다. PostMessage로 큐에 붙이면 이 메시지는 무시된다.)

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

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

[Win32] IPC - #1 정의

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

두 프로그램이 상호 통신하며 정보를 교환하는 방법을 IPC(InterProcess Comunication)라고 한다. 문자 그대로 프로세스간의 통신 방법이다.

IPC 방법을 선택하기 위해 고려해야 할 요소는 다음과 같은 여러 가지가 있다.

1. 네트워크 지원이 필요한가? 곧 컴퓨터의 경계를 넘어서는 통신인가?
2. 통신 대상이 다른 운영체제에서 실행되는 프로그램인가? 곧 플랫폼 경계를 넘어야 하는가?
3. 통신 대상이 고정되어있는가 아니면 불특정 다수와 통신해야 하는가?
4. 통신 속도가 중요한가 아니면 느려고 상관없는가?
5. 일회적인 교환인가 지속적인 교환인가?
6. 양방향 통신인가 아니면 단방향 통신인가?
7. 통신의 신뢰성 및 보안성이 요구되는가?

이런 요소들을 모두 고려하여 적절한 방법을 선택하기 위해서는 각 방법의 원리와 장단점을 잘 이해하고 있어야 한다.
Win32 환경에서 사용가능한 IPC 방법에는 다음과 같은 것들이 있다.

메시지
사용자 정의 메시지로 정보 교환. 길이에 제약이 있다.
클립보드
중앙 저장소를 통한 대량의 정보 교환. 일반적인 용도로는 쓸 수 없다.
DDE
DDE 메시지를 사용한 지속적인 정보 교환.
파일 맵핑
파일 맵핑을 이용한 메모리 공유. 번거롭다.
메일슬롯
메일슬롯 사용
파이프
파이프 사용
윈속
윈속 사용
RPC
원격 함수 호출 사용
COM
컴포넌트 오브젝트 모델

IPC는 프로세스간에 정보를 교환하기만 하면 되는 것이므로 이 목적을 이룰 수만 있다면 무엇이든지 IPC라고 할 수 있다. 예를 들어 IPC.txt파일에 교환할 정보를 쓰고 읽는 다던가 약속된 레지스트리, 또는 INI 파일을 사용하는 방법도 IPC라고 할 수 있다.

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

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