정리

[Error/Warning] error PRJ0002 : xxxxxxxx

저장소/VC++

VS2008에서 프로젝트 빌드를 수행할 때
 
"error PRJ0002 : xxxxxx"

라는 에러가 발생할 경우 우선 프로젝트 폴더 내에 rc파일이 존재하는지
정상적으로 작성되어있는지 확인할 필요가 있다.

사례를 검색해보면  VC6과 충돌이 발생해서 그러니 VC6을 지우고 VS2008을 다시 설치해라 윈도우를 다시 설치해라 어쩌라 그러던데 그걸 언제 하고있냐;;;

결과적으로 내 자리에선 rc파일이 없어서 발생했던 문제였는데
rc파일이 없을 경우 이런 문제도 발생하는구나 확인할 수 있었던 경험이랄까?

아무튼 이런 문제가 생기면 일단 rc파일부터 확인하고
rc.exe가 제대로 구동되는지도 확인해보자.

근데 rc파일 문제가 아니면 정말 시스템 다시 설정해야 하는건가? -_-;;

[Win32] 시스템의 Locale 관련 함수들

저장소/VC++

[GetSystemDefaultLocaleName]
시스템의 기본 로케일 이름 반환 함수
(The application should call this function in preference to GetSystemDefaultLCID if designed to run only on Windows Vista and later.)

int GetSystemDefaultLocaleName(
  __out  LPWSTR lpLocaleName,
  __in   int cchLocaleName
);


Parameters

lpLocaleName [out]

Pointer to a buffer in which this function retrieves the locale name.

cchLocaleName [in]

Size, in characters, of the output buffer indicated by lpLocaleName. The maximum possible character length of a locale name (including a terminating null) is the value of LOCALE_NAME_MAX_LENGTH. This is the recommended size.

Return Value

Returns a value greater than 0 that indicates the length of the locale name, including the terminating null character, if successful.

This function returns 0 if it does not succeed. To get extended error information, the application can call GetLastError, which can return one of the following error codes:

  • ERROR_INSUFFICIENT_BUFFER. A supplied buffer size was not large enough, or it was incorrectly set to NULL.

Remarks

This function can retrieve data from custom locales. Data is not guaranteed to be the same from computer to computer or between runs of an application. If your application must persist or transmit data, see Using Persistent Locale Data.

Examples

An example showing the use of this function can be found in NLS: Name-based APIs Sample.

Requirements

Minimum supported client Windows Vista
Minimum supported server Windows Server 2008
Header Winnls.h (include Windows.h)
Library Kernel32.lib
DLL Kernel32.dll

See Also

National Language Support
National Language Support Functions
Mapping Locale Data
DownlevelLCIDToLocaleName
GetLocaleInfoEx
GetUserDefaultLocaleName

[GetSystemDefaultLangID]
시스템의 기본 언어 식별자 반환 함수

[GetUserDefaultLangID]
현 사용자의 기본 언어 식별자 반환 함수

[GetSystemDefaultLCID]
시스템의 기본 로케일 식별자 반환 함수

[GetUserDefaultLCID]
현 사용자의 기본 로케일 식별자 반환 함수
[제어판]-[국가 및 언어 옵션]-[표준 및 형식] 항목에 선택된 국가 정보를 반환한다.

[ConvertDefaultLocale]
기본 로케일을 변경하는 함수

[SetThreadLocale]
이 함수를 호출한 쓰레드의 현재 로케일을 변경하는 함수

[Win32] IPC - #3 Pipe(파이프)

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

Win32 파이프는 두 프로세스간에 정보를 주고 받을 수 있는 통로(Information Conduit)를 말하는데 다양한 IPC 방법 중 하나이며 주로 연속적인 바이트 스트림을 교환할 때 많이 사용된다.
Win32 API는 이름없는 파이프와 이름있는 파이프 두 가지 종류의 파이프를 지원한다.

1. 이름없는 파이프(Anonymous Pipe) - 잘 안쓰이니 대충 넘겨도 됨.
 주로 부모 자식간의 단방향 통신에만 사용, 네트워크상의 다른 컴퓨터끼리는 사용할 수 없는 로컬 전용의 파이프이다. 이름있는 파이프에 비해 오버헤드가 적으니 기능상의 제약이 많다. 함수는 아래와 같다.

BOOL CreatePipe(PHANDLE hReadPipe, PHANDLE hWritePipe, LPSECURITY_ATTRIBUTES lpPipeAttributes, DWORD nSize);

이 함수는 파이프를 생성하고 파이프 양쪽 끝인 읽기 핸들과 쓰기 핸들을 첫 번째 인수(hReadPipe)와 두 번째 인수(hWritePipe)로 전달된 참조인수로 리턴한다. 두 개의 HANDLE형 변수를 선언하고 변수의 포인터를 이 함수로 전달하면 된다. 세 번째 인수는 파이프의 보안 속성을 지정하는데 이름없는 파이프는 주로 상속을 통해 자식 프로세스로 전달되므로 보안 속성의 bInheritHandle멤버를 TRUE로 설정해야 한다. 네 번째 인수 nSize는 파이프의 버퍼 크기이되 0으로 지정하면 디폴트 크기로 버퍼를 만든다.

부모 프로세스가 자식 프로세스에게 데이터를 보낼 때 취해야 하는 절차이다. 반대로 데이터를 받아야 할 때는 핸들만 바꿔주면 된다.

ㄱ. 일기 핸들 전달을 위해 표준 입력을 재지향하는데 핸들 전달 후 복구를 위해 표준 입력 핸들을 저장해 놓는다.
ㄴ. 상속 가능한 파이프를 생성한 후 표준 입력을 파이프의 읽기 핸들로 재지향한다. 이때 쓰기 핸들은 상속되어서는 안 되므로 상속되지 않는 복사본을 만들고 원래의 상속 가능한 쓰기 핸들은 파괴한다.
ㄷ. 차일드 프로세스를 실행한다. 부모 프로세스의 표준 입력 핸들이 차일드로 상속되며 이때 표준 입력이 파이프의 읽기 핸들로 재지향되어 있으므로 차일드는 결과적으로 파이프의 읽기 핸들을 상속받게 된다.
ㄹ. 부모는 읽기 핸들을 차일드에게 전달했으므로 자신이 가지고 있는 읽기 핸들은 닫는다. 그리고 보관해 놓았던 표준 입력 핸들을 원래대로 복구한다.
ㅁ. 쓰기 핸들의 복사본으로 데이터를 보내면 차일드는 표준 입력 핸들로부터 데이터를 읽을 수 있다.
ㅂ. 통신이 끝난 후 쓰기 핸들을 닫는다. 양쪽 끝의 핸들이 모두 파괴되면 파이프도 같이 파괴된다.


2. 이름있는 파이프(Named Pipe) - 확실히 알아둘 것




함수는 아래와 같다.
HANDLE CreateNamedPipe(LPCTSTR lpName, DWORD dwOpenMode, DWORD dwPipeMode, DWORD nMaxInstances, DWORD nOutBufferSize, DWORD nInBufferSize, DWORD nDefaultTimeOut, LPSECURITY_ATTRIBUTES lpSecurityAttributes);

[파라미터 설명]

lpName
만들고자 하는 파이프의 이름을 지정한다. 다른 파이프와 중복되지 않아야 한다.
형식은 아래와 같다.
"\\서버명\pipe\파이프 이름"
로컬 컴퓨터인 경우는 마침표(.)를 대신 찍으면 된다.
"\\.\pipe\파이프 이름"
CreateNamedPipe는 원격지 컴퓨터의 파이프를 생성하는 기능은 가지고 있지 않다. 그러나 CreateFile, CallNamedPipe 등 파이프를 여는 함수는 원격지의 파이프를 열 수 도 있으므로 이 때는 파이프 이름에 서버명을 같이 지정해야 한다.

파이프 이름은 최대 256문자까지 가능하며 대소문자는 구분하지 않는다.

dwOpenMode
동기화 입출력
비동기화 입출력

완전 쓰기 모드(Write through)는 바이트 유형의 원격지 컴퓨터끼리 통신할 때만 유효하다. FILE_FLAG_WRITE_THROUGH 플래그를 지정하므로써 파이프를 완전 쓰기 모드로 동작시킬 수 있다.
WriteFile 함수로 출력을 내보내면 원격지 컴퓨터의 버퍼에 데이터가 완전히 전달되기 전에는 리턴하지 않는다. 즉 데이터를 버퍼에 넣는 것 뿐만 아니라 이 데이터가 네트워크를 통해 반대편 컴퓨터의 버퍼로 완전히 전달될 때까지 대기한다.
이 모드는 쓰기 동작을 완벽하게 동기화해야 할 때 유용하다.
파이프를 생성할 때만 지정할 수 있어서 서버, 클라이언트가 각각 다른 모드로 지정하는 것도 가능하다.


dwPipeMode
파이프의 유형, 읽기 모드, 대기 모드를 지정한다.
파이프 유형은 파이프로 데이터가 출력되는 방법을 지정, 바이트 유형과 메시지 유형 두 가지가 있다.

바이트 유형

메시지 유형

읽기 모드

대기 모드

dwMaxInstances
같은 파이프 이름으로 생성할 수 있는 최대의 인스턴스 갯수를 지정한다. 즉 하나의 서버가 동시에 통신할 수 있는 클라이언트의 갯수이다.
1 ~ PIPE_UNLIMITED_INSTANCES(256)까지 지정할 수 있다.

nOutBufferSize, nInBufferSize
파이프의 입력용, 출력용 버퍼의 크기를 지정한다. 단, 이 크기는 어디까지나 권장치일 뿐이며 실제로 시스템은 효율을 높이기 위해 이 인수로 지정한 크기와는 다르게 버퍼를 할당하기도 한다.

nDefaultTimeOut
디폴트 제한 시간은 1/1000초 단위로 지정한다. 이 값은 WaitNamedPipe 함수가 여유 파이프를 대기할 때 제한 시간으로 사용된다. 모든 인스턴스가 동일한 제한 시간으로 파이프를 생성해야 한다.

lpSecurityAttributes
파이프도 커널 오브젝트이므로 보안 속성을 지정할 수 있다. NULL로 지정하면 디폴트 보안 속성이 적용된다. 대개의 경우 NULL로 지정하면 별 말썽이 없다.










[Win32] Service - #4 서비스 제어

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


UI 디자인은 아래와 같다.



#define SVCNAME                        _T("MemStat")
#define SERVICE_CONTROL_NEWFILE        128

BOOL CControlDlg::OnInitDialog()
{
    /*
         중략...
    */

    hScm = ::OpenSCManager(NULL, NULL, GENERIC_READ);

    if(NULL == hScm)
    {
        MessageBox(_T("SCM을 열 수 없습니다."), _T("알림"), MB_OK);
        EndDialog(0);
    }

    hSvc = ::OpenService(hScm, SVCNAME, SERVICE_ALL_ACCESS);

    if(NULL == hSvc)
    {
        MessageBox(_T("MemStatService 서비스가 설치되어 있지 않습니다."), _T("알림"), MB_OK);

        ::CloseServiceHandle(hScm);
        EndDialog(0);
    }
    else
    {
        ::CloseServiceHandle(hSvc);
    }

    QueryService();
}

void CControlDlg::QueryService()
{
    hSvc = ::OpenService(hScm, SVCNAME, SERVICE_INTERROGATE);

    if(NULL == hSvc)
    {
        return;
    }

    do
    {
        ::ControlService(hSvc, SERVICE_CONTROL_INTERROGATE, &ss);
    }
    while(    (SERVICE_STOPPED != ss.dwCurrentState) &&
            (SERVICE_RUNNING != ss.dwCurrentState) &&
            (SERVICE_PAUSED != ss.dwCurrentState));

    GetDlgItem(IDC_BTN_START)->EnableWindow(FALSE);
    GetDlgItem(IDC_BTN_STOP)->EnableWindow(FALSE);
    GetDlgItem(IDC_BTN_PAUSE)->EnableWindow(FALSE);
    GetDlgItem(IDC_BTN_CONTINUE)->EnableWindow(FALSE);
    GetDlgItem(IDC_BTN_NEW_LOG)->EnableWindow(FALSE);

    switch(ss.dwCurrentState)
    {
    case SERVICE_STOPPED:
        {
            GetDlgItem(IDC_BTN_START)->EnableWindow(TRUE);
            GetDlgItem(IDC_STATIC)->SetWindowText(_T("현재 상태 : 중지"));
        }
        break;
    case SERVICE_RUNNING:
        {
            GetDlgItem(IDC_BTN_STOP)->EnableWindow(TRUE);
            GetDlgItem(IDC_BTN_PAUSE)->EnableWindow(TRUE);
            GetDlgItem(IDC_BTN_NEW_LOG)->EnableWindow(TRUE);
            GetDlgItem(IDC_STATIC)->SetWindowText(_T("현재 상태 : 실행중"));
        }
        break;
    case SERVICE_PAUSED:
        {
            GetDlgItem(IDC_BTN_STOP)->EnableWindow(TRUE);
            GetDlgItem(IDC_BTN_CONTINUE)->EnableWindow(TRUE);
            GetDlgItem(IDC_STATIC)->SetWindowText(_T("현재 상태 : 일시중지"));
        }
        break;
    }

    ::CloseServiceHandle(hSvc);
}

void CControlDlg::MemControl(DWORD dwControl)
{
    hSvc = ::OpenService(hScm, SVCNAME, GENERIC_EXECUTE);

    ::ControlService(hSvc, dwControl, &ss);

    ::CloseServiceHandle(hSvc);
    QueryService();
}

void CControlDlg::OnBnClickedBtnStart()
{
    // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.

    hSvc = ::OpenService(hScm, SVCNAME, SERVICE_START | SERVICE_QUERY_STATUS);

    // 서비스를 시작시키고 완전히 시작할 때까지 대기한다.
    SetCursor(LoadCursor(NULL, IDC_WAIT));
    {
        if(TRUE == ::StartService(hSvc, 0, NULL))
        {
            ::QueryServiceStatus(hSvc, &ss);

            while(SERVICE_RUNNING != ss.dwCurrentState)
            {
                Sleep(ss.dwWaitHint);
                ::QueryServiceStatus(hSvc, &ss);
            }
        }
    }
    SetCursor(LoadCursor(NULL, IDC_ARROW));

    ::CloseServiceHandle(hSvc);
    QueryService();
}

void CControlDlg::OnBnClickedBtnStop()
{
    // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.

    MemControl(SERVICE_CONTROL_STOP);
}

void CControlDlg::OnBnClickedBtnPause()
{
    // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.

    MemControl(SERVICE_CONTROL_PAUSE);
}

void CControlDlg::OnBnClickedBtnContinue()
{
    // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.

    MemControl(SERVICE_CONTROL_CONTINUE);
}

void CControlDlg::OnBnClickedBtnNewLog()
{
    // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.

    MemControl(SERVICE_CONTROL_NEWFILE);
}




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

[Win32] 시스템의 Locale 관련 함수들  (0) 2009.07.07
[Win32] IPC - #3 Pipe(파이프)  (0) 2009.05.14
[Win32] Service - #3 서비스 설치  (0) 2009.05.13
[Win32] Service - #2 서비스 생성  (0) 2009.05.13
[Win32] Service - #1 정의  (0) 2009.05.13

[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