쓰레드 동기화 오브젝트

 (Thread Synchronization Objects)

 쓰레드가 2개 이상 실행될 때 여러 가지 변수가 있습니다.

하나의 공유자원(예를 들어 동시에 접근하는 변수) 에 접근할 때, 파일 입출력 이나 디바이스I/O작업을 할 때 동기화 오브젝트가 필요합니다.

동기화 오브젝트 없이 쓰레드가 공유 자원을 사용할 때 공유자원이 원치 않은 값이 될수 있고, I/O작업 시 쓰레드가 I/O작업이 끝날 때 까지 무한정 블로킹(blocking : 특정 함수가 리턴 될 때 까지 기다림)현상이 발생할 수 있습니다.

동기화 오브젝트를 사용하여 다중 쓰레드에서 어떻게 안전 하게 공유자원에 접근하고 다른 쓰레드간의 실행 순서등을 조작하는지에 대해 알아 보겠습니다.

동기화 오브젝트는 유저 모드와 커널 모드로 분류할 수 있습니다.

  유저 모드

 유저 모드는 현재 프로세스/쓰레드 내의 상태를 말합니다.

유저모드에서는 커널 오브젝트(프로세스, 파일, 디바이스 등)으로 바로 접근을 할 수 없고, 커널 오브젝트로 접근 시 시스템에 의해 변환 작업이 이루어집니다.

이런 변환 작업은 시간을 많이 걸리는 작업이기 때문에 유저 모드가 커널 모드 보다 속도가 빠릅니다.

유저 모드 동기화 방법은 코드레벨에서 동기화 하는 방법을 이야기 합니다.

유저모드에서 동기화는 사용하기 쉽고 커널 모드 동기화 함수들에 비해 속도가 빠른 장점이 있지만 커널 오브젝트(파일 I/O, 프로세스, 쓰레드 등)의 동기화는 불가능하다는 단점이 있습니다.

 Interlocked

 Interlocked 함수들은 다중 쓰레드에서 공유변수들을 안전하게 1씩 증가/감소, 특정값을 증가 , 비트 연산을 할 수 있습니다.

Intlocked 함수는 사용하기 쉬우므로 길게 설명은 하지 않겠습니다.

아래 사이트를 참조하시길 바랍니다.

http://msdn2.microsoft.com/en-us/library/ms686360(VS.85).aspx

 

 크리티컬 섹션(CRITICAL SECTION)

 크리티컬 섹션은 특정 코드영역을 쓰레드가 동시에 실행되는 것을 막아 줍니다.

아래는 크리티컬 섹션 관련 함수들입니다.

 

DeleteCriticalSection

크리티컬 섹션 오브젝트를 삭제 합니다.

InitializeCriticalSection

크리티컬 섹션 오브젝트를 초기화 합니다.

InitializeCriticalSectionAndSpinCount

크리티컬 섹션 오브젝트를 초기화 하고 스핀 카운트를 설정합니다.

InitializeCriticalSectionEx

크리티컬 섹션을 초기화하고 스핀카운트 설정, 부가기능을 설정합니다.

LeaveCriticalSection

크리티컬 섹션 오브젝트 권한을 해제합니다..

SetCriticalSectionSpinCount

특정 크리티컬 섹션 오브젝트 스핀카운트를 설정합니다.

TryEnterCriticalSection

블로킹(Wait) 되지 않고 크리티컬 섹션 오브젝트의 권한을 요청합니다.


InitializeCriticalSectionAndSpinCount 함수는 스핀 카운트를 두어서 쓰레드가 크리티컬 섹션 오브젝트를 획득하지 못하면 Wait상태로 일정 시간(스핀카운트) 루프를 돌아 크리티컬 섹션오브젝트가 해제 되었는지 체크합니다.

해제되지 않았으면 Sleep하게 됩니다.

이 함수는 멀티 프로세서 환경에서만 유효하며, 스핀카운터는 4000을 추천(Windows Via C/C++)하지만 자신의 환경에서 값을 바꾸어 가며 테스트 해보길 권장합니다.

         

 #include <Windows.h>

CRITICAL_SECTION g_cs;

 //크리티컬 섹션 오브젝트를 초기화 합니다.

void InitCriticalSection()

{

        InitializeCriticalSection(&g_cs);

}

 

unsigned _stdcall CallThreadHandlerProc(void *pThreadHandler)

{

        while (bExit == FALSE)

        {

               if(TryEnterCriticalSection(&g_cs))

               {

                       //쓰레드 작업을 수행 합니다.

                       //수행 하고 LeaveCriticalSection 함수를 호출

                       //하여 크리티컬 섹션 오브젝트를 해제합니다.

 

                       LeaveCriticalSection(&g_cs);

               }

               else

               {

                        // 크리티컬 섹션 오브젝트 획득에 실패 시 수행할

                        // 작업을 선언합니다.

                        // SwitchToThread함수를 호출하여 다른 쓰레드로

                        // 스위칭 합니다.

                       SwitchToThread();                    

               }             

        }

        DWORD exitCode;

        GetExitCodeThread(InputThrd, &exitCode);

        _endthreadex(exitCode);

        return 0;

}


**Sleep() 함수와 SwitchToThread()함수는 디스패쳐가 다른 쓰레드로 스케쥴 하도록 합니다.

차이점은 Sleep()함수는 현재 쓰레드보다 우선순위가 같거나 높은 쓰레드가 없으면 쓰레드 전체를 리스케쥴링(rescheduling)합니다.

 

Slim Reader/Writer Locks

 Slim reader/writer (SRW) locks 하나의 프로세스내의 쓰레드들이 공유자원을 동기화 할 수 있습니다.

아주 작은 메모리를 차지하면서 속도도 빠릅니다.

Reader 쓰레드는 공유자원을 읽고 Writer 쓰레드는 공유자원에 쓰기 작업을 할수 있습니다.

다중 쓰레드가 공유자원을 읽고 쓰기를 , 크리티컬 섹션과 뮤텍스 같은 상호배제 오브젝트(exclusive locks)들은 reader 쓰레드는 계속 돌고 writer 쓰레드는 거의 돌지 못하면 병목현상(bottle neck) 발생 있습니다.

SRW locks 공유자원에 접근 할 수 있는 두 가지 모드를 제공합니다.

·Shared mode : 읽는 작업을 하는 쓰레드가 여러 개일 때 공유자원을 읽기 전용으로 접근할 수 있도록 해서 동시 다발적으로 작업을 할 수 있도록 합니다.  만약 읽는 작업이 쓰는 작업을 초과 할 경우, 성능과 처리량은 크리티컬 섹션과 동일하게 됩니다.
·Exclusive mode : 읽기/쓰기 쓰레드는 하나의 쓰레드만 접근 할 수 있습니다.  Exclusive mode로 락이 걸려지면 다른 쓰레드들은 공유자원에 접근할 수 없습니다.

하나의 SRW lock 두 가지 모드를 동시에 가질 수 있습니다. 읽는 쓰레드는 Shared mode 쓰는 쓰레드는 Exclusive mode 작업을 있습니다.  어떤 쓰레드가 소유권을 먼저 가질지는 알수 없습니다. SRW Locks 공정하거나 선입선출(First In First Out : FIFO)방식이 아닙니다.

SRW lock 포인터 크기를 가집니다. 장점은 속도가 빠르고 lock상태의 변환이 빠르다는 입니다.

단점은 아주 작은 상태 정보만 저장이 되어 재귀적으로 SRW locks 가질 없습니다. Shared Mode 쓰레드가 Shred Mode 변환 될수 없습니다.

SWR Locks Windows Server 2008,Vista 에서만 사용이 가능합니다.

아래는 SRW lock 함수들 입니다.

SRW lock function

Description

AcquireSRWLockExclusive

SRW lock  exclusive mode 얻습니다.

AcquireSRWLockShared

SRW lock shared mode 얻습니다.

InitializeSRWLock

SRW lock 초기화 합니다.

ReleaseSRWLockExclusive

exclusive mode SRW lock 해제 합니다.

ReleaseSRWLockShared

shared mode SRW lock 해제 합니다.

SleepConditionVariableSRW

SRW작업이 완료 될때까지 Sleep 합니다.

 커널 모드

유저 모드에서 커널 오브젝트에 접근을 할 때 시스템은 커널 모드로 변환을 합니다.

커널 오브젝트에는 File, Event, Mutex, Semaphore, Process, Waitable Timer, Job, Thread 가 있습니다.

커널 모드에서 동기화는 커널 오브젝트가 non-Signal인지 Signal 상태인지를 보고 쓰레드를 스케쥴링합니다.

커널 오브젝트가 signal상태이면 쓰레드가 돌아갈 준비가 된 상태이고, non-signal 상태이면 쓰레드는 기다림(Wait)상태 입니다.

 

이벤트(EVENT)
 가장 많이 보편화되고 많이 쓰는 다중 쓰레드 동기화 오브젝트가 이벤트가 아닌가 생각이 됩니다.

이벤트로 다중 쓰레드 동기화 하는 방법에는 두 가지 방법이 있습니다.

개발자가 직접 이벤트를 수동으로 signal/non-signal 상태로 변환 하는 것과 시스템이 자동으로 이벤트를 signal/non-signal상태로 변환 하는 방법입니다.

이벤트를 기다리는 방법은 WaitForSingleObjec/WaitForSingleObject 함수를 사용합니다. 이벤트가 non-signal상태가 될 때까지 쓰레드는 Wait상태로 됩니다.

이벤트 및 모든 커널 오브젝트는 사용이 끝나면 CloseHandle로 사용을 종료 해야 합니다.

 

Event function

Description





CreateEvent

이벤트 오브젝트를 생성하거나 오픈 합니다.





CreateEventEx

이벤트 오브젝트를 생성하거나 오픈 합니다.(접근 권한을 줄수 있습니다.)





OpenEvent

존재하는 이름이 있는 이벤트 오브젝트를 오픈 합니다.





PulseEvent

특정 이벤트 오브젝트를 signal 상태로 바꾸고 일정시간 non-signal상태로 바끕니다.





ResetEvent

이벤트 오브젝트를 non-signal 상태로 놓습니다.        





SetEvent

이벤트 오브젝트를 signal 상태로 놓습니다.          






 
간단 하게 SDI 프로그램에서 수동 모드(passive mode) 이벤트를 이용하여 사각형과 원을 그리는 멀트 쓰레드 프로그램을 보겠습니다.

 BOOL CEventSampleView::PreCreateWindow(CREATESTRUCT& cs)

{

        // TODO: CREATESTRUCT cs를 수정하여 여기에서

        //  Window 클래스 또는 스타일을 수정합니다.

        srand( (unsigned)time( NULL ) );

 

        //종료조건을 초기화합니다.

        m_bContinue = TRUE;

        //수동모드 이벤트 오브젝트를 생성합니다.

        m_DrawEvent = CreateEvent(0, TRUE, TRUE, _T("DrawEvent"));

 

        // 시그널 상태로 둡니다.

        SetEvent(m_DrawEvent);

       

        return CView::PreCreateWindow(cs);

}

 

/*OnDraw 메시지 핸들러에서 사각형을 그리는 쓰레드와

원을 그리는 쓰레드를 생성합니다.*/

void CEventSampleView::OnDraw(CDC* /*pDC*/)

{

        CEventSampleDoc* pDoc = GetDocument();

        ASSERT_VALID(pDoc);

        if (!pDoc)

               return;

        m_RectThrd = AfxBeginThread(RectThreadProc,reinterpret_cast<LPVOID>(this));

        m_CricleThrd = AfxBeginThread(CircleThreadProc, reinterpret_cast<LPVOID>(this));

        // TODO: 여기에 원시 데이터에 대한 그리기 코드를 추가합니다.

}

 

/*사각형을 그리는 쓰레드 프로시저*/

UINT CEventSampleView::RectThreadProc(__in LPVOID lpParameter)

{

        CEventSampleView* pView = reinterpret_cast<CEventSampleView*>(lpParameter);

        int x=0,y=0, cx=0, cy=0;

        cx = 100;

        cy = 100;

        while(pView->m_bContinue)

        {

               //시그널상태가 될 때까지 기다립니다.

               WaitForSingleObject(pView->m_DrawEvent, INFINITE);

               //이벤트 오브젝트를 받아오면 넌시그널 상태로 둡니다.

               ResetEvent(pView->m_DrawEvent);

              

               HDC hDC = ::GetDC(pView->GetSafeHwnd());

               x= rand()%300;

               y = rand()%300;

               ::Rectangle(hDC,x, y, x+cx, y+cy);

               ::ReleaseDC(pView->GetSafeHwnd(),hDC);

               // 작업이 끝나면 이벤트를 시그널상태로 두어 다음 쓰레드가 가질 수

               // 있도록 합니다.

               SetEvent(pView->m_DrawEvent);

        }

        DWORD dCode=0;

        GetExitCodeThread(pView->m_CricleThrd->m_hThread, &dCode);

        AfxEndThread(dCode, TRUE);

        return 0;

}

/*원을 그리는 쓰레드 프로시저*/

UINT CEventSampleView::CircleThreadProc(__in LPVOID lpParameter)

{

        CEventSampleView* pView = reinterpret_cast<CEventSampleView*>(lpParameter);

        int x=0,y=0, cx=0, cy=0;

        cx = 100;

        cy = 100;

        while(pView->m_bContinue)

        {

               WaitForSingleObject(pView->m_DrawEvent, INFINITE);

               ResetEvent(pView->m_DrawEvent);

               HDC hDC = ::GetDC(pView->GetSafeHwnd());

               x= rand()%300;

               y = rand()%300;

               ::Ellipse(hDC,x, y, x+cx, y+cy);

               ::ReleaseDC(pView->GetSafeHwnd(),hDC);

               SetEvent(pView->m_DrawEvent);

        }

        DWORD dCode=0;

        GetExitCodeThread(pView->m_CricleThrd->m_hThread, &dCode);

        AfxEndThread(dCode, TRUE);

        return 0;

}

//프로그램이 종료할 때 종료조건을 맞춰 주고 쓰레드 종료 메시지를 주어 정상적으로

//종료하도록 하고, 이벤트 핸들을 닫습니다.

void CEventSampleView::OnDestroy()

{

        CView::OnDestroy();

        m_bContinue = FALSE;

        DWORD dExitCode = 0;

        GetExitCodeThread(m_CricleThrd->m_hThread,&dExitCode);

        PostQuitMessage(dExitCode);

 

        GetExitCodeThread(m_RectThrd->m_hThread,&dExitCode);

        PostQuitMessage(dExitCode);

 

        CloseHandle(m_DrawEvent);

}


 
뮤텍스(MUTEX)

뮤텍스는 하나의 공유자원에 대한 상호 배타(mutual exclusive)적으로 동기화 하는 방법입니다.  뮤텍스는 서로 다른 프로세스의 쓰레드의 동기화를 할수 있습니다.  이 특성을 이용해서 보통 하나 이상의 프로그램을 실행하기 위해 뮤텍스를 이용합니다.(이걸 깨는 방법도 있죠.)

뮤텍스는 다음과 같은 규칙이 있습니다.

Ø  쓰레드의 ID 0(유효하지 않은 쓰레드 ID)이면 뮤텍스의 소유권은 어느 쓰레드에게도 없다는 의미 이고 뮤텍스 오브젝트는 시그널된 상태입니다.

Ø  쓰레드 ID 0이 아닌 값이면, 해당쓰레드(생성 시킨 쓰레드)가 소유권을 가지면 뮤텍스 오브젝트는 non-signal상태 입니다.

Ø  다른 커널 오브젝트와는 달리 뮤텍스는 소유권(thread ownership)이라는 개념이 있습니다. 

뮤텍스를 해제할 때(ReleaseMutex), 쓰레드 ID와 생성할 때 설정한 쓰레드의 ID가 맞지 않으면 해제에 실패하고 시스템은 해당 뮤텍스의 시그널 상태를 기다리는 다른 쓰레드를 스케쥴링 합니다.

뮤텍스의 소유권을 가진 쓰레드가 뮤텍스를 해제 하지 않고 종료 되면, 시스템은 해당 뮤텍스를 “abandoned”상태로 두고, 이 뮤텍스를 기다리는 쓰레드를 찾아 기다리고 있는 쓰레드에 뮤텍스의 소유권을 주고 해당 쓰레드를 스케쥴링 합니다.

 

Mutex function

Description

CreateMutex

뮤텍스 오브젝트를 생성하거나 오픈 합니다.

CreateMutexEx

뮤텍스 오브젝트를 생성하거나 오픈합니다. 접근 권한 속성을 있습니다.

OpenMutex

이름이 있는 뮤텍스 오브젝트를 오픈 합니다.

ReleaseMutex

뮤텍스 오브젝트를 해제합니다.

 http://msdn2.microsoft.com/en-us/library/ms686927(VS.85).aspx

 
세마포어(SEMAPHORE)

 세마포어는 공유 자원의 카운팅의 용도로 사용합니다.

세마포어는 사용 개수(usage count)이외에 signed 32비트 값 2개를 더 가지고 있습니다.

Ø  최대 리소스 카운트(maximum resource count) : 세마포어가 관리할 수 있는 최대 리소스의 개수.

Ø  현재 리소스 카운트(current resource count) : 현재 사용 가능한 리소스의 개수.

세마포어는 다음과 같은 규칙을 가지고 동작을 합니다.

Ø  현재 리소스 카운터가 0보다 크면(>0) 세마포어 오브젝트는 signal 상태입니다.

Ø  현재 리소스 카운터가 0이면, 세마포어 오브젝트는 non-signal상태입니다. 

Ø  시스템은 현재 리소스카운터를 값이 되지 않도록 합니다.

Ø  현재 리소스 카운터는 최대 리소스 카운터 보다 클 수 없습니다.

 

Semaphore function

Description

CreateSemaphore

세마포어를 생성/오픈 합니다.

CreateSemaphoreEx

세마포어를 생성/오픈 합니다. 접근 권한을 있습니다.

OpenSemaphore

이름이 있는 세마포어 오브젝트를 오픈합니다.

ReleaseSemaphore

사용 가능한 리소스 개수를 증가 시킵니다..

 세마포어 사용 예는 아래 사이트를 참조 하세요.

http://msdn2.microsoft.com/en-us/library/ms686946(VS.85).aspx


Waitable Timer

Waitable Timer 오브젝트는 특정 시간이 되면 오브젝트가 시드널 됩니다.

특정 시간 마다 어떤 동작을 해야 할 때 사용할 수 있습니다.

Waitable-timer function

Description

CancelWaitableTimer

Waitable Timer 오브젝트를 비활성화 시킵니다.

CreateWaitableTimer

Waitable Timer 생성하거나 오픈 합니다.

CreateWaitableTimerEx

Waitable Timer 생성/오픈 합니다.

OpenWaitableTimer

이름이 붙여진 Waitable Timer 오브젝트를 오픈 합니다.

SetWaitableTimer

Waitable Timer 오브젝트를 활성화 시키거나, Waitable Timer 오브젝트가 시그널 되었을 완료 통보 프로시져를 등록 있습니다..

TimerAPCProc

SetWaitableTimer 함수로 등로한 완료 통보 프로시져 선언.

 

Timer-queue Timer

 Timer queue Timer 오브젝트는 일정시간이 지나면 시그널 되는 동작은 Waitable Timer 와 같습니다.

Timer Queue Timer 오브젝트는 일정 시간이 지나면 시그널 되는 오브젝트이고, Timer Queue오브젝트가 Timer Queue Timer 오브젝트를 큐 형태로 관리하면서

해당 프로시저를 호출 합니다.

 

위의 그림은 Timer Queue오브젝트가 Timer Queue Timer 오브젝트를 관리하고,

Timer Queue Timer가 시그널 되면 해당 프로시저를 호출하는 모습입니다.


Timer-queue timer function

Description

ChangeTimerQueueTimer

Timer queue timer 오브젝트의 속성을 변경 합니다..

CreateTimerQueue

Timer Queue 오브젝트를 생성합니다.

CreateTimerQueueTimer

Timer Queue Timer 오브젝트를 생성합니다.

DeleteTimerQueue

타이머 오브젝트를 삭제 합니다.

DeleteTimerQueueEx

타이머 오브젝트를 삭제 합니다.

DeleteTimerQueueTimer

Timer Queue 있는 Timer Queue Timer 오브젝트를 삭제 합니다.

 

쓰레드간 통신

  마지막으로 쓰레드간 통신 하는 방법을 알아 보겠습니다.

윈도우 프로그래밍을 하면 윈도우에 SendMessage/PostMessage 함수로 메시지를 보내 듯이 쓰레드에 메시지를 보내고 받으면서 쓰레드간 통신을 할 수 있습니다.

쓰레드에 메시지를 보내는 함수는 PostThreadMessage 입니다.

BOOL PostThreadMessage(      
    DWORD idThread,  
/*해당 쓰레드 ID*/

    UINT Msg,       /*메시지 ID*/

    WPARAM wParam,    /*메시지를 받는 쓰레드로 넘겨주는 WPARAM 인자*/

    LPARAM lParam     /* 메시지를 받는 쓰레드로 넘겨주는 LPARAM 인자*/

);

 
PostThread
로 보낸 메시지는 쓰레드 프로시저에서 PeekMessage/GetMessage로 받을 수 있습니다.

http://msdn2.microsoft.com/en-us/library/ms644936(VS.85).aspx

BOOL GetMessage(      
    LPMSG lpMsg,          
//MSG 구조체의 포인터 타입

    HWND hWnd,             //윈도우 핸들

    UINT wMsgFilterMin,    //필터링할 최소 메시지 ID

    UINT wMsgFilterMax     //필터링할 최대 메시지 ID

);

 

http://msdn2.microsoft.com/en-us/library/ms644943.aspx

 

BOOL PeekMessage(      
    LPMSG lpMsg,          
//MSG 구조체의 포인터 타입

    HWND hWnd,             //윈도우 핸들

    UINT wMsgFilterMin,    //필터링할 최소 메시지 ID

    UINT wMsgFilterMax,    //필터링할 최대 메시지 ID

    UINT wRemoveMsg        //메시지 큐에 해당 메시지를 지울지 안지울지 설정

);

 
PeekMessage
GetMessage의 차이점은, GetMessage는 메시지가 메시지 큐에 들어 올 때가지 블록 되고 Peek메시지는 메시지가 없으면 FALSE를 리턴 합니다.

더 자세한 사항은 차고 사이트를 참고 하세요.

 아래 예제 코드는 쓰레드 프로시져에서 메시지를 확인하고, 다른 작업을 수행 하는 예제 코드입니다.

 unsigned _stdcall CallThreadHandlerProc(void *pThreadHandler)

{

        while (bExit == FALSE)

        {

               MSG msg;

             BOOL res = PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);

             if (res)

             {

                        //0x404메세지 가 들어오면 특정 작업 수행

                    if (msg.message = 0x404)

                    {                  

                           std::cout<<"Message Received"<<std::endl;

                    }

             }

 

               //다른 작업 수행

        }

        DWORD exitCode;

        GetExitCodeThread(InputThrd, &exitCode);

        _endthreadex(exitCode);

        return 0;

}

………………………………………….

//다른 쓰레드에서 쓰레드에 메시지를 보냅니다.

BOOL res = PostThreadMessage(ID1, 0x00404, 0,0 );

 
참고 사이트 및 서적

Windows Via C/C++

http://msdn2.microsoft.com/en-us/library/aa904937(VS.85).aspx

http://windows-programming.suite101.com/article.cfm/win32_message_processing_primer


*출처 :

http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=51&MAEULNo=20&no=8026&ref=8026

출처 : Tong - navy9370님의 MFC통

1. DC얻기
  CClientDC dc(this);

2. Client 영역 구하기

  GetClientRect(&rect);
  WM_SIZE 메시지발생후 cx,cy 사용

3. 문자열 사각형안에 그리기

  pDC->DrawText(문자열,사각형,Style);
  Style: DT_BOTTOM - 문자열을 사각형 맨아래줄에배열 반드시 DT_SINGLELINE과 함께사용
        DT_CENTER - 문자열을 가로중앙에 배치
        DT_VCENTER - 문자열을 세로중앙에 배치
        DT_LEFT,RIGHT - 문자열을 좌,우로 배치
        DT_SINGLELINE - 문자열을 한줄로만 쓴다

4. Brush 사용법
  CBrush brushname(RGB(red,green,blue)); //브러쉬 생성
  CBrush *oldBrush=pDC->SelectObject(&brushname); //이전Brush 저장, 새로운 Brush 선택
  pDC->SelectObject(oldBrush); //원래의 브러쉬로 반환

5. Pen사용법
  CPen pen(Pen Style,RGB(red,green,blue)); //브러쉬생성
//Style: PS_SOLID,PS_DASH,PS_DOT,PS_DASHDOT,PS_GEOMETRIC,PS_COSMETRIC - 펜종류
        PS_ENDCAP_ROUND,PS_ENDCAP_SQUARE - 펜끝을 둥글게,각지게 설정
  CPen *oldPen=pDC->SelectObject(&pen); //이전Pen저장, 새로운 Pen설정
  pDC->SelectObject(oldPen); //펜반환

6. 화면다시그리기
  View Class에서 - Invalidate(TRUE) : 화면을 지우고다시그린다
                    Invalidate(FALSE) : 화면을 덮어씌운다
  UpdateAllViews(NULL);  // Doc Class에서 View 의 OnDraw 호출
  RedrawWindow();

7. 메시지,함수 수동으로 넣기 (EX)버튼클릭함수넣기
  헤더파일의 AFX_MSG_MAP 부분에 함수를 정의
  EX) afx_msg void funcName();
  .cpp파일의 AFX_MSG 부분에 메시지를 추가한다
  EX) ON_BN_CLICKED(ID_NAME,funcName)...
  ID 등록:  View 메뉴의 Resource Symbol 에 들어가서 메뉴 ID 를 등록해준다..
  .cpp파일의 맨아래에서 함수를 정의한다
  EX) void CClass::funcName() { ... }

8. 마우스커서 바꾸기
  리소스탭에서 커서를 그리고 저장한뒤 ID값은 준다음
  SetCapture(); //커서의입력을 클라이언트영역을 벗어나더라도 받아낸다
  SetCursor(AfxGetApp()->LoadCursor(nIDResource));
  //APP클래스의 LoadCursor View의 SetCursor 사용
  ReleaseCapture(); //SetCursor()상태를 해제한다

9. 색상표 사용하기
  CColorDialog dlg;
  if(dlg.DoModal()==IDOK) //Dialog 를 띄운후 OK버튼을누르면 실행할부분
  MemberFunc: GetColor() //선택된 색상을 받아온다 return 형은 COLORREF 형

10. 팝업메뉴 만들기
  CMenu menu; //메뉴 객체생성
  CMenu *pmenu; //메뉴 포인터생성
  menu.LoadMenu(IDR_MAINFRAME); //메뉴를 불러온다
  pmenu=menu.GetSubMenu(3); //메뉴의 3번째 메뉴를 가져온다
  menu.CheckMenuItem(ID_MENU,m_kind==ID_MENU ? MF_CHECKED : MF_UNCHECKED);
  //메뉴 체크하기 (메뉴 ID, ID 체크조건)
  pmenu->TrackPopupMenu(TPM_LEFTALIGN,point.x,point.y,this)  //(TMP_Style,x좌표,y좌표,hWnd) 메뉴 띄우기

  *주의사항*
    [안내]태그제한으로등록되지않습니다-OnContextMenu(CWnd* pWnd, CPoint point)  //여기서 point 는 스크린 기준이고,
    OnRButtonDown(UINT nFlags, CPoint point)  //여기서 point 는 클라이언트 기준이다!


11. 클라이언트 포인터를 스크린 포인터로 변경
  ClientToScreen(&point);

12. 그림판기능

         if(m_flag==FALSE)  return;   //m_falg=그리기 기능 참,거짓설정  그리기 아니면 빠져나간다
        CClientDC dc(this);
        CPen myPen(PS_SOLID,m_width,m_color);
        CPen *pOldPen=dc.SelectObject(&myPen);
        switch(m_shape)
        {
        case ID_FREELINE: //자유선그리기
                dc.MoveTo(m_oldpt.x,m_oldpt.y); //지난포인터부터
                dc.LineTo(point.x,point.y); //새포인터까지 그린다
                break;

        case ID_RECT: //사각형그리기
                dc.SetROP2(R2_NOTXORPEN);
                dc.Rectangle(m_spt.x,m_spt.y,m_oldpt.x,m_oldpt.y);  //지워지는 효과
                dc.Rectangle(m_spt.x,m_spt.y,point.x,point.y); //그려지는 효과
                break;

        case ID_ELLIPSE: //원그리기
                dc.SetROP2(R2_NOTXORPEN);
                dc.Ellipse(m_spt.x,m_spt.y,m_oldpt.x,m_oldpt.y);  //지워지는 효과
                dc.Ellipse(m_spt.x,m_spt.y,point.x,point.y); //그려지는 효과
                break;

        case ID_LINE: //선그리기
                dc.SetROP2(R2_NOTXORPEN);
                dc.MoveTo(m_spt.x,m_spt.y); //시작점부터
                dc.LineTo(m_oldpt.x,m_oldpt.y); //지난점까지 그은선을 지운다
                dc.MoveTo(m_spt.x,m_spt.y); //시작점부터
                dc.LineTo(point.x,point.y); //새로운점까지 그린다
                break;
        }
        m_oldpt=point;  //바로이전값 보관
        dc.SelectObject(pOldPen); //펜 반환

13. MessageBox
  AfxMessageBox() -> 전역함수를 이용하영 메세지 박스를 출력한다.   //어디서든지 사용할수 잇다
  int CWnd::MessageBox("메세지","창제목","아이콘|버튼(상수값)");   //View클래스에서 사용한다
  아이콘 상수값  MB_IC[안내]태그제한으로등록되지않습니다-xxONERROR, MB_ICONWARNING, MB_ICONQUESTION,MB_ICONINFOMATION
                MB_SYSTEMMODAL //시스템모달 대화창 닫기전에 다른작업 못함
                MB_APPLMODAL //응용모달
  버튼 상수값    MB_OK, MB_OKCANCEL, MB_YESNO

14. OS 컨트롤
        ExitWindowEx(EWX_SHUTDOWN,NULL); //Shut Down
        ExitWindowsEx(EWX_FORCE,0); //강제종료
        ExitWindowsEx(EWX_LOGOFF,0); //로그오프
        ExitWindowsEx(EWX_POWEROFF,0); //Shut Down -> Turn Off
        ExitWindowsEx(EWX_REBOOT); //Shut Down -> Reboot

15. DialogBox 메시지 교환
        UpdateData(FALSE); // 컨트롤에 멤버변수의 내용을 표시해준다
        UpdateData(TRUE);  // 컨트롤 내용을 다이얼로그 클래스의 멤버변수로 저장

16. 자료변환

        atoi,itoa - int <=> ASCII(char) 변환
        str.Format(" %d %d",x,y); // int형을 문자열로 변환
        atol,ltoa - ASCII <=> long 변환
        atof - ACSII => float 변환
        fcvt,gcvt  - 실수를 text로 변환
        LPtoDP, DPtoLP - 장치좌표 <=> 논리좌표 변환

17. CEdit Class 사용하기
  CEdit e_str.SetSel(int StartChae, int EndChar); //처음문자부터 마지막까지 블록 지정
  CEdit e_str.GetSel(int SChar,int EChar); //블럭 지정한 처음문자와 마지막문자 받기
  CString str=m_str.Mid(SChar,EChar-SChar); //블럭지정한 부분을 가져온다

18. 컨트롤과 자료교환
  SetDlgItemText(컨트롤 ID,문자열) //컨트롤에 문자열을 넣는다
  GetDlgItemText(컨트롤 ID,문자열) //컨트롤의 내용을 문자열에 넣는다
  GetDlgItem(컨트롤 ID); //컨트롤의 주소를 가져온다

19. 상태바조작
  CMainFrame 생성자 위에
  static UINT indicators[] = //이안에 새로운 ID를 넣고 그 ID의 갱신핸들러를 만든다음 코딩
  pCmdUI->SetText("표시할내용");

20. 수동으로 Bitmap 컨트롤 사용하기

  CStatic bitmap; //bitmap 컨트롤변수
  bitmap.SetBitmap(CBitmap m_bitmap); //컨트롤에 비트맵지정
  GetDlgItem(IDC_BITMAP)->ShowWindow(SW_SHOW,HIDE);  // 그림을 보이거나 숨긴다.   

21. 응용프로그램 실행하기
  WinExec("프로그램경로",SW_SHOW,HIDE); //응용프로그램실행,경로는 \\로 구분한다

22. Bitmap 사용하기

  CBitmap bitmap.LoadBitmap(IDC_BITMAP); //비트맵객체에 비트맵지정
  CDC memDC; //그림그릴 메모리DC생성
  MemDC.CreateCompatibleDC(pDC); //화면 DC와 메모리 DC 호환 생성
  CBitmap *pOldBitmap=MemDC.SelectObject(&m_bitmap); //메모리에 그림을그린다.
  pDC->BitBlt(int x, int y,int Width, int Height, CDC* pSrcDC, int xSrc, int ySrc, DWORD dwRop);
//BitBlt(그림x좌표,그림y좌표,그림넓이,그림높이,그림그려진메모리DC,그림시작x좌표,그림시작y좌표,스타일);
  pDC->StretchBlt( int x, int y, int nWidth, int nHeight, CDC* pSrcDC, int xSrc, int ySrc, int nSrcWidth, int nSrcHeight, DWORD dwRop )
//StretchBlt(그림x좌표,그림y좌표,그림넓이,그림높이,그림그려진메모리DC,그림x좌표,그림y좌표,메모리그림넓이,메모리그림높이,스타일);
MemDC.SelectObject(pOldBitmap); // 메모리DC반환

23. Font 바꾸기

  CFontDialog dlg; //폰트다이얼로그 생성
  LOGFONT m_logFont; //폰트받을변수선언
  if(dlg.DoModal()==IDOK) //폰트다이얼로그표시
  {dlg.GetCurrentFont(&m_logFont)} //선택된 폰트받기

  OnDraw()
   CFont newFont,*pOldFont; //폰트 객체 만들기
   newFont.CreateFontIndirect(&m_logFont); //폰트 생성
   pOldFont=(CFont *)pDC->SelectObject(&newFont); //폰트 선택

   OnCreate()
   CClientDC dc(this); //DC 생성
   CFont *pFont=dc.GetCurrentFont();        //클라이언트 영역의 폰트를
   pFont->GetLogFont(&m_logFont); //로그폰트 멤버값으로 지정

24. Font 만들기
         LOGFONT logfont; //폰트를 만든다
        logfont.lfHeight=50;               //문자열 높이
        logfont.lfWidth=0;                 //너비
        logfont.lfEscapement=0;            //문자열기울기
        logfont.lfOrientation=0;             //문자개별각도
        logfont.lfWeight=FW_NORMAL;     //굵기
        logfont.lfItalic=TRUE;             //이탤릭
        logfont.lfUnderline=TRUE;  //밑줄
        logfont.lfStrikeOut=FALSE; //취소선
        logfont.lfCharSet=HANGUL_CHARSET; //필수
        logfont.lfOutPrecision=OUT_DEFAULT_PRECIS;              
        logfont.lfClipPrecision=CLIP_DEFAULT_PRECIS;      //가변폭폰트 고정폭폰트
        logfont.lfPitchAndFamily=DEFAULT_PITCH|FF_SWISS; //글꼴이름
        strcpy(logfont.lfFaceName,"궁서체");
        CClientDC dc(this);

        CFont newFont; //폰트객체생성
        newFont.CreateFontIndirect(&logfont); //폰트지정
        CFont *pOldFont=dc.SelectObject(&newFont); //폰트선택
        dc.TextOut(100,100,m_text);
        dc.SelectObject(pOldFont); //폰트반환


25. Font 만들기 2

  CFont newFont;
  newFont.CreateFont( int nHeight, int nWidth, int nEscapement, int nOrientation, int nWeight, BYTE bItalic, BYTE bUnderline, BYTE cStrikeOut, BYTE nCharSet, BYTE nOutPrecision, BYTE nClipPrecision, BYTE nQuality, BYTE nPitchAndFamily, LPCTSTR lpszFacename );
CFont *pOldFont=dc.SelectObject(&newFont);

26. ComboBox 사용하기

  CComboBox combo; //콤보박스 선언
  combo.Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );
  //Style - WS_CHILD|WS_VISIBLE
  int n=combo.GetCurSel(); //선택된 아이템의 index를 가져온다
  combo.AddString("문자열"); //문자열을 추가한다
  combo.GetLBText(n,str); //n번째 아이템을 str에 저장

27. Spin 사용하기
  Spin은 바로앞의 Tab Order에 따라 붙는다
  m_spinr.SetRange(1900,3000); //스핀 범위 지정
  m_spinr.SetPos(m_nYear); //스핀 위치 지정

28. CTime사용하기
  CTime time; //시간객체생성
  time=CTime::GetCurrentTime(); //현재시간을 저장
  time.GetYear(),time.GetMonth();,time.GetDay(),time.GetHour(),time.GetMinute(),time.GetSecond()

29. CListBox 메소드
  AddString("문자열");             //리스트에 문자열 추가
  DeleteString(index);             //리스트에서 항목 삭제
  GetCount()                     //전체 항목 갯수를 얻는다.
  GetSelcount()                   //선택된 항목 갯수 리턴
  GetSel()                       //선택된 것인지 아닌지를 리턴한다 -> 양수 = TRUE , 음수 => FALSE
  GetText(int index,문자열변수)     //index 번째 문자열을 문자열 변수에 넣는다
  FindStringExact(문자열)          //지정 문자열의 index 값 리턴 -> 없으면 리턴값 LB_ERR 반환
  FindString("a")                 //"a"로 시작하는 항목을 모두 찾는다.
  ResetCountent()                 //모든 내용을 지운다.

30. 파일입출력
프로젝트생성시 Step4 => Advanced => 저장파일확장자지정
.h 파일에       DECLARE_SERIAL(CSawon) //이 클래스를 저장,로드가능한 클래스로 쓰겟다는 선언
.cpp 파일에     IMPLEMENT_SERIAL(CSawon,CObject,1) //이거를 해야 저장이 가능하다
void CFileioDoc::Serialize(CArchive& ar)
        if (ar.IsStoring())  //저장하기
        {        ar<
        else    //열기
        {        ar>>m_shape; //불러올걸 쓴다. 읽을때도순서대로읽어야한다}

31. MicroSoft FlexGrid 사용하기!
        CMSFlexGrid m_Grid; //FlexGrid 컨트롤 변수
        CString strTitle[]={"고객코드","고객성명","고객포인트","신장","몸무게","고객등급","BMT지수","판정결과"};
        // Grid 의 제목에 넣을문자배열
        int Width[]={900,900,1100,800,800,900,1000,900};
        // Grid 의 열넓이 지정할 배열
        m_Grid.SetRows(m_cnt+2); //전체행수 지정
        m_Grid.SetCols(8); //전체열수 지정
        m_Grid.Clear(); //지우기
        m_Grid.SetFixedCols(0); //고정열은 없다.
        m_Grid.SetRow(0); // 행선택
        for(int i=0;i<=7;i++)
        {
                m_Grid.SetColWidth(i,Width[i]); //열 넓이 설정
                m_Grid.SetCol(i); //열 선택
                m_Grid.SetText(strTitle[i]); // 선택된행, 선택된열에 Text 를 넣는다
        }

32. 4대 Class간 참조
//각각 헤더파일 include
#include "MainFrm.h" //메인프레임 헤더파일
#include "ClassDoc.h"   //Doc클래스 헤더파일
#include "ClassView.h" //View를 include 할때는 반드시 Doc 헤더파일이 위에잇어야한다
#include "Class.h" //APP Class 의 헤더파일

void CClassView::OnMenuView() //뷰클래스
        CClassApp *pApp=(CClassApp *)AfxGetApp();   //View -> App
        CMainFrame *pMain=(CMainFrame *)AfxGetMainWnd();  //View -> MainFrm
        CClassDoc *pDoc=(CClassDoc *)pMain->GetActiveDocument(); //View -> MainFrm -> Doc
        CClassDoc *pDoc=(CClassDoc *)GetDocument();                     //View -> Doc

//MainFrame 클래스
        CClassView *pView=(CClassView *)GetActiveView();  //MainFrm -> View
        CClassDoc *pDoc=(CClassDoc *)GetActiveDocument();  //MainFrm -> Doc
        CClassApp *pApp=(CClassApp *)AfxGetApp(); //MainFrm -> App

//Doc 클래스
        CClassApp *pApp=(CClassApp *)AfxGetApp(); //Doc -> App
        CMainFrame *pMain=(CMainFrame *)AfxGetMainWnd(); //Doc -> MainFrm
        CClassView *pView=(CClassView *)pMain->GetActiveView(); // Doc -> MainFrm -> View
        CClassView *pView=(CClassView *)m_viewList.GetHead();      // Doc -> View

//App 클래스
        CMainFrame *pMain=(CMainFrame *)AfxGetMainWnd(); //App -> MainFrm
        CClassView *pView=(CClassView *)pMain->GetActiveView(); //App -> MainFrm -> View
        CClassDoc *pDoc=(CClassDoc *)pMain->GetActiveDocument(); //App -> MainFrm -> Doc

33. ToolBar 추가하기
  CMainFrame 으로 가서 멤버변수 추가
        CToolBar m_wndToolBar1;
  OnCreate 로 가서 다음 내용을 추가해준다 (위의 toolbar 부분을 복사하고 이름만 바꾸면 된다.3군데..)
  if (!m_wndToolBar1.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
                | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
                !m_wndToolBar1.LoadToolBar(IDR_TOOLBAR1))
        {
                TRACE0("Failed to create toolbar\n");
                return -1;      // fail to create
        }

  그 함수내에서 //TODO 아래에 내용추가..역시..복사해서 이름만 바꾸면 된다.
        m_wndToolBar1.EnableDocking(CBRS_ALIGN_TOP|CBRS_ALIGN_BOTTOM);
        //DockControlBar(&m_wndToolBar1);   <= 이부분 대신..
        이거를 넣는다..
        CRect toolRect; //툴바 영역을 얻을 사각형
        this->RecalcLayout(); //현상태의 Client 영역을 구해서 저장한다
        m_wndToolBar.GetWindowRect(&toolRect); //툴바영역을 저장한다
        toolRect.left+=1; //사각형의 왼쪽을 1Pixel 줄인다
        DockControlBar(&m_wndToolBar1,AFX_IDW_DOCKBAR_TOP,&toolRect); //ToolRect에 툴바를 붙인다
        return 0;

34. ToolBar에 ComboBox붙이기
        CComboBox m_combo; //객체생성
        ID 등록 => view 메뉴 => resource symbol => new => ID_COMBO
  oncreate 에 내용 추가 (콤보를 만들고 표시하는 내용)
        m_wndToolBar.SetButtonInfo(10,IDC_COMBO,TBBS_SEPARATOR,150); 
        //툴바의 10번째버튼을 편집한다
        CRect itemRect; //콤보를넣을 사각형을 만든다
        m_wndToolBar.GetItemRect(10,&itemRect); //툴바의 10번째 버튼을 사각형에 넣는다
        itemRect.left+=5; //앞여백
        itemRect.right+=5; //뒤여백
        itemRect.bottom+=100; //콤보가열릴 공간확보
       m_combo.Create(WS_CHILD|WS_VISIBLE|CBS_DROPDOWN,itemRect,&m_wndToolBar,IDC_COMBO);
        //콤보박스를 툴바에 붙여준다
        m_combo.AddString("이름"); //내용추가
        m_combo.SetCurSel(0); //셀 선택

35.  Toolbar에 수동으로넣은 ComboBox 사용하기
  afx_msg void [안내]태그제한으로등록되지않습니다-xxOnSelectCombo(); //원형
  ON_CBN_SELCHANGE(IDC_COMBO,[안내]태그제한으로등록되지않습니다-xxOnSelectCombo) //메세지맵에 추가
        CMainFrame *pMain=(CMainFrame *)GetParent(); //메인프레임 주소참조
        CComboBox *pCom=(CComboBox *)(pMain->m_wndToolBar.GetDlgItem(IDC_COMBO));
        //콤보박스의 주소를 가져온다, 접근할 때 메인프레임 -> 툴바 -> 콤보박스 의 순서로 가야한다
        int n=pCom->GetCurSel(); //현재선택된 셀의 인덱스를 가져온다
        if(n==CB_ERR) return; //선택된셀이 없으면 중지한다
        CString str;
        pMain->m_combo.GetLBText(n,str); //선택된셀의 Text를 가져온다

36. UPDATE_COMMAND 사용하기
        pCmdUI->Enable(TRUE); //버튼 활성화
        pCmdUI->SetText((bAdd)?"취소":"신규"); //버튼의 text 설정
        pCmdUI->SetCheck(TRUE); //버튼 체크

37. 프로그램정보저장
  CWinApp::GetProfileString(섹션명,항목명,기본값); // 함수를 사용한다. (문자열)
  CWinApp::GetProfileInt(섹션명,항목명,기본값);  //불러올때사용 (숫자) 
  CWinApp::WriteProfileString(섹션명,항목명,값); //저장할때 사용 (문자열)
  CWinApp::WriteProfileInt(섹션명,항목명,값); //저장할때 사용 (숫자)

  //불러올때 사용할함수
  void CMainFrame::ActivateFrame(int nCmdShow)  //프로그램 실행후 프레임생성될때 실행

  //저장할 때 WM_DESTROY 메시지 사용

38. 컨트롤바 표시하기
        CMainFrame *pMain=(CMainFrame *)GetParent(); //MainFrame 주소가져오기
        pMain->ShowControlBar(&pMain->m_wndToolBar,bTool1,FALSE); //툴바를 bTool2 에따라 보이고 감춘다

39. Window 창크기,위치정보 저장하기

MainFrame 의 WM_DESTROY 에
        WINDOWPLACEMENT w;
        this->GetWindowPlacement(&w); //윈도우의 정보를 저장한다.
        CString strRect;
        strRect.Format("%04d,%04d,%04d,%04d", //04d 는 4자리 확보하고 남은건 0으로 채워라
                w.rcNormalPosition.left,w.rcNormalPosition.top,
                w.rcNormalPosition.right,w.rcNormalPosition.bottom); //윈도우의 위치,크기 확보..
        
        BOOL bMax,bMin; //윈도우의 상태를 저장하기위한 변수
        //w.falg 는 이전상태의 정보를 가지고 잇다!!
        if(w.showCmd==SW_SHOWMINIMIZED)           //최소화 상태
        {
                bMin=TRUE;
                if(w.flags==0) //falg 값이 0 이면 이전 상태가 보통상태이다!!
                        bMax=FALSE;
                else    //이전상태가 최대화 상태
                        bMax=TRUE;
        }
        else                            
        {
                if(w.showCmd==SW_SHOWMAXIMIZED) //최대화상태
                {
                        bMax=TRUE;
                        bMin=FALSE;
                }
                else  //보통 상태
                {
                        bMax=FALSE;
                        bMin=FALSE;
                }
        }
        AfxGetApp()->WriteProfileString("WinStatus","Rect",strRect);
        AfxGetApp()->WriteProfileInt("WinStatus","Max",bMax);
        AfxGetApp()->WriteProfileInt("WinStatus","Min",bMin);

//읽어올차례..
ActivateFrame 함수로 가서
        WINDOWPLACEMENT w;  //윈도우의 상태를 저장하는 구조체..
        BOOL bMax,bMin;               //최대,최소상태를 저장할 변수
        CString strRect; //창크기를 받아올 변수
        strRect=AfxGetApp()->GetProfileString("WinStatus","Rect","0000,0000,0500,0700");
        bMin=AfxGetApp()->GetProfileInt("WinStatus","Min",FALSE);
        bMax=AfxGetApp()->GetProfileInt("WinStatus","Max",FALSE);
        int a=atoi(strRect.Left(4)); //문자열을 int 로 바꿔준다.
        int b=atoi(strRect.Mid(5,4));     //atoi 아스키 값을 int형으로 바꿔준다..
        int c=atoi(strRect.Mid(10,4));
        int d=atoi(strRect.Mid(15,4));
        w.rcNormalPosition=CRect(a,b,c,d);
        if(bMin)
        {
                w.showCmd=SW_SHOWMINIMIZED;
                if(bMax)
                {
                        w.flags=WPF_RESTORETOMAXIMIZED  ;
                }
                else
                {
                        w.flags=0;
                }
        }
        else
        {
                if(bMax)
                {
                        w.showCmd=SW_SHOWMAXIMIZED;
                }
                else
                {
                        w.showCmd=SW_SHOWNORMAL;
                }
        }
        this->SetWindowPlacement(&w); //설정된 값으로 윈도우를 그리게 한다..
        
        //CFrameWnd::ActivateFrame(nCmdShow); //이건 반드시 주석처리한다..

40. progress Bar 쓰기
        m_progress.SetRange(m_first,m_last); //Progress 범위설정하기
        m_progress.SetStep(m_step); //Progress Step설정하기
        //m_progress.StepIt(); //스텝만큼 움직이기
        //또는 다음을 사용한다
        for(int a=m_first;a<=m_last;a+=m_step) //a가 처음부터 끝까지
        {
                m_progress.SetPos(a); // 위치를 a에 맞춘다
                Sleep(50); //천천히 움직이게한다
        }

41. 파일대화상자 FileDialog 사용하기

void CConDlg1::OnFileopen()  //파일열기 버튼
{
        CFileDialog *fdlg; //파일대화상자 객체 생성 // 포인터로 만든다..
        static char BASED_CODE szFilter[] = "Animate Video Files (*.avi)|*.avi|All Files (*.*)|*.*||";
        //필터를 만들어 준다..이건 할줄 모름..
        fdlg =new CFileDialog(TRUE, ".avi", NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,szFilter);
        //대화상자 만들기..이렇게 해야댄다..
        if(fdlg->DoModal()==IDOK) //이제..대화상자를 띠우고..    
        {                               //OK 누르면 실행될 부분..
                m_filename=fdlg->GetPathName();        //대화상자에서 경로를 받아서 저장.
                UpdateData(FALSE);    
        }
}
선생님이 해준거 //파일 다이얼로그 만들기
CFileDialog fdlg(TRUE,"avi",".avi",OFN_OEVRWRITEPROMPT,"Vidoe Files(*.avi)|*.avi|All Files(*.*)|*.*||");

42. Animate Control 사용하기
        m_animate.Open(m_filename); //파일을 연다
        m_animate.Play(0,-1,1);  //(처음프레임,마지막프레임,반복횟수)
        m_animate.Stop(); //정지시키기
        m_ani.SetAutoStart(TRUE); //자동으로 시작한다

43. Control 의 Style 바꿔주기
        Control.ModyfyStyle(제거할스타일,추가할스타일); //스타일은 MSDN내용 참조

44. 시스템 날자바꾸기 버튼
//SetSystemTime(),GetSystemTime() //GMT 표준시를 가져온다.
//GetLocalTime(),SetLocalTime()  //현재 지역시간을 가져온다.
        SYSTEMTIME st;
        GetLocalTime(&st); //현재 시간, 날자를 넣는다.
        st.wYear=m_date2.GetYear();
        st.wMonth=m_date2.GetMonth();
        st.wDay=m_date2.GetDay();
        SetSystemTime(&st);

45. 시스템 시간 바꾸기 버튼
        UpdateData(TRUE);
        SYSTEMTIME st;
        GetLocalTime(&st);
        st.wHour=m_time.GetHour();
        st.wMinute=m_time.GetMinute();
        st.wSecond=m_time.GetSecond();
        SetLocalTime(&st);

46.시스템의 드라이브 문자 얻기
        char temp[50];
        GetLogicalDriveStrings(sizeof(temp),temp);
        CString str,str1;
        int n=0;
        while(*(temp+n)!=NULL)
        {
                str=temp+n;
                str1+= " "+str.Left(2);
                n+=4;
        }

47. 현재 작업경로 얻기
        char temp[MAX_PATH]; //MAX_PATH 는 경로길이의 최대를 define 해놓은것.
        GetCurrentDirectory(sizeof(temp),temp);  // 현작업하는 경로를 얻어온다.(경로 길이,문자형);

48. Tree Control 사용하기
        HTREEITEM hmov,hmus; //핸들을받을 변수 이게 잇어야 하위 디렉토리 생성가능
        hmov=m_tree.InsertItem("영화",TVI_ROOT,TVI_LAST); //,TVI_ROOT,TVI_LAST는 default
        hm1=m_tree.InsertItem("외화",hmov);  //hmov 아래 "외화"트리 생성
        CImageList m_image; //그림을 사용하기 위한 클래스다!! 알아두자..
        m_tree.SetImageList(&m_image,TVSIL_NORMAL); //Tree View Style Image List => TVSIL
        hmov=m_tree.InsertItem("영화",0,1,TVI_ROOT,TVI_LAST); //,TVI_ROOT,TVI_LAST는 default
        hmus=m_tree.InsertItem("가요",1,2); //("문자열",처음그림번호,선택시그림)
        hm1=m_tree.InsertItem("외화",2,3,hmov); //그림 번호는 default 로 0이 들어간다..

49. List Control 사용하기
        m_list.ModifyStyle(LVS_TYPEMASK, LVS_ICON); //리스트를 큰아이콘형태로 보인다
        m_list.ModifyStyle(LVS_TYPEMASK, LVS_SMALLICON);  //리스트를 작은아이콘형태로 보인다
        m_list.ModifyStyle(LVS_TYPEMASK, LVS_LIST); //리스트를 리스트형태로 보인다
        m_list.ModifyStyle(LVS_TYPEMASK, LVS_REPORT); //리스트를 자세히형태로 보인다

        CImageList m_treeimage; //이미지리스트
        CImageList m_small, m_large;
        m_large.Create(IDB_LARGE,32,0,RGB(255,255,255)); //이거는 클래스에서 추가해준거다
        m_small.Create(IDB_SMALL,16,0,RGB(255,255,255)); (bmp ID값,
        m_list.SetImageList(&m_large,LVSIL_NORMAL);
        m_list.SetImageList(&m_small,LVSIL_SMALL);
        CString name[]={"홍길동","진달래","한국남","개나리"};
        CString tel[]={"400-3759","304-7714","505-9058","700-9898"};
        CString born[]={"1980-1-1","1981-12-20","1980-05-15","1981-08-31"};
        CString sex[]={"남자","여자","남자","여자"};
  
        m_list.InsertColumn(0,"이름",LVCFMT_LEFT,70);
        m_list.InsertColumn(1,"전화번호",LVCFMT_LEFT,80);
        m_list.InsertColumn(2,"생일",LVCFMT_LEFT,90);
        m_list.InsertColumn(3,"성별",LVCFMT_LEFT,50);

        LVITEM it; //리스트 구조체
        char temp[100];
        for(int a=0;a<4;a++)
        {       
                int n=(sex[a]=="남자")?0:1;
                m_list.InsertItem(a,name[a],n); //insert item 은 행을 만들고..
                it.mask=LVIF_TEXT|LVIF_IMAGE; //마스크 설정
                it.iItem=a;

                it.iSubItem=1; //열 설정
                strcpy(temp,tel[a]); //이거 모하는거냐..
                it.pszText=temp;
                m_list.SetItem(&it);                      // setitem 열에 정보를 넣는다.

                it.iSubItem=2; //열 설정
                strcpy(temp,born[a]); //이거 모하는거냐..
                it.pszText=temp;
                m_list.SetItem(&it);                      // setitem 열에 정보를 넣는다.

                it.iSubItem=3; //열 설정
                strcpy(temp,sex[a]); //이거 모하는거냐..
                it.pszText=temp;

                m_list.SetItem(&it);                      // setitem 열에 정보를 넣는다.

50. Bitmap Button 사용하기
  CBitmapButton 을 사용한다! CButton 에서 상속 받는클래스임..
        m_button1.Create(NULL,
                WS_CHILD|WS_VISIBLE|BS_OWNERDRAW,CRect(310,20,370,50),
                this,IDC_MYBUTTON); //버튼만들기
        m_button1.LoadBitmaps(IDB_UP,IDB_DOWN,IDB_FOCUS,IDB_DISABLE); //버튼의 그림설정
        m_button1.SizeToContent(); //버튼을 그림 크기로 맞춰 준다!!
그냥 버튼을 비트맵버튼으로 바꾸기 -> 버튼을 만든다 속성에서 OWNERDRA 속성에 체크!!
        m_button2.LoadBitmaps(IDB_UP,IDB_DOWN,IDB_FOCUS,IDB_DISABLE); //버튼의 그림설정
        m_button2.SizeToContent(); //버튼을 그림 크기로 맞춰 준다!!

51. 중복없는 난수발생하기
        int su; //발생된 난수저장
        int a,b;
        BOOL bDasi; //숫자가중복될경우 다시하기위한 변수
        for(a=0;a<9;a++)  //난수 9개 발생
        {
                bDasi=TRUE;
                while(bDasi)
                {
                        bDasi=FALSE;
                        su=rand()%10; //난수발생
                        for(b=0;b
                        {
                                if(temp[b]==su)  //중복이면
                                {
                                        bDasi=TRUE; //중복이 잇으면 다시while 문을 실행한다
                                        break;
                                }//if
                        }//for
                }//while
                temp[a]=su; //중복이 아니면 대입한다

52. 메뉴 범위로 사용하기
  ON_COMMAND_RANGE(ID_LEVEL3,ID_LEVEL9,OnLevel); //범위메세지 발생
 //메뉴 ID의 값이 연속된 숫자일 경우 범위로 지정해서 사용할수잇다

53. 한,영 전환함수
void CCustView::SetHangul(BOOL bCheck) //T:한글 F:영문 이건 외우자..
{
        HIMC hm=ImmGetContext(this->GetSafeHwnd()); //뷰클래스의 윈도우 핸들포인터를 얻는다.
        if(bCheck)
        {
                ::ImmSetConversionStatus(hm,1,0); //1은 한글 0은 영문
        }
        else
        {
                ::ImmSetConversionStatus(hm,0,0); //영문으로 바꿔준다
        }
        ::ImmReleaseContext(this->GetSafeHwnd(),hm); //장치를 풀어준다
}

#include "imm.h" //헤더 반드시 추가하고
imm32.lib (라이브러리 파일)를 반드시 링크해주어야 한다!
**** 라이브러리 추가하기
프로젝트메뉴 -> 셋팅 -> 링크탭

54. DLL함수정의하기

임포트함수 :  extern "C"  __declspec(dllimport)   리턴형  함수명(매개변수,...) ;
  - 메인프로그램에서 DLL에 있는 함수를 호출할때 사용한다.
엑스포트함수 :  extern "C"  __declspec(dllexport)   리턴형  함수명(매개변수,...)
                      {
                             내용;
                      }

'1.소프트웨어 이야기 > 01.MFC(Visual Studio)' 카테고리의 다른 글

[펌] 다중 쓰레드 동기화  (0) 2009.02.27
[펌] DialogBar 구현  (0) 2008.08.07
클래스간 포인터 얻기  (0) 2007.12.06

[원문] http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=60&MAEULNo=23&no=234&ref=234


구현 내용은 다이얼로그바에서 버튼을 틀릭 하면 뷰가 변경되는 기능을 구현 하엿으며,

뷰 스위칭 하는 소스는 데브피아에서 참고 하여 작성 하엿습니다..


프로젝트는 CE MFC(exe)의 뷰의 Base Class를  CFormView기반으로 시작 하엿습니다..

나중에 뷰 스위칭을 위해서요..^^
 

1.  CDialogBar를 상속 받은 파생 클래스를 제작 합니다. 클래스명은 CDlgBar이고요..^^

---클래스 생성시 위져드에 보면 CDialogBar 가 Base class 에 포함 되어 있지 않기 때문에
    CDialog 를 상속 받아 클래스를 만든 다음 부모 클래스를 CDialogBar로 바꾸어 주어야 합니다..

    그리고 리소스에서 다이얼로그 의 스타일을 Child로 바꾸어 줍니다..


- 생성된 클래스에 Create 함수를 두개 구현 합니다..

    필요한거 하나만 구현 하여 사용 하셔도 됩니다..^^


2. 다이얼로그바 활성화 시키기

    -- 다이얼로그바 클래스를 만들고 MainFrame에서 바로 생성 하여 사용 할려면 버튼이 활성화 되지 않는     현상이 생기게 됩니다..
    해서 ON_UPDATE_COMMAND_UI 메시지 핸들러 함수를 설치하셔야 합니다..

    이녀석 또한 위져드에서 보이지가 않더군요...

    메시지 맵에 직접 추가해 주었습니다..

    메시지 핸들러 함수는

        afx_msg void OnUpDateCmdUi(CCmdUI *pCmdUI );

    BEGIN_MESSAGE_MAP에는

        ON_UPDATE_COMMAND_UI(IDC_DLGBAR_BTN1, OnUpDateCmdUi)

    핸들러 함수 구현은  

        void CDlgBar::OnUpDateCmdUi(CCmdUI *pCmdUI )

        {

            pCmdUI->Enable(TRUE);

        }

    이와 같이 등록 하여 줍니다..

     ON_UPDATE_COMMAND_UI매크로를 이용하여 COMMAND ID 와 메시지 핸들러 함수를 연결해 두면

    그 COMMAND ID를 갖는 사용자 인터페이스가 갱신되어야 할 필요가 있을때마다

    그 함수가 호출됩니다.

    - 이렇게 하여 다이얼로그바의 컨트롤을 (예제에서는 버튼) 을 활성화 하게 됩니다.


3.    다이얼로그바 생성

    CDlgBar를 MainFrame의 멤버 변수로  선언 하고 ,

    MainFrame의 OnCreate함수 에서 아래와 같이 다이얼로그바를 생성해 줍니다..

   



    이렇게 하면 다이얼 로그바가 생성 됩니다..
    다이얼로그바의 버튼 클릭시 하고 싶은 행동은 다이얼로그바의 클래스에서 해주시면 되겟죠..??^^

   

------------------    이렇게 해서 다이얼로그바는 생성을 마쳤습니다...  ----------------------


 그럼 다이얼 로그바의 버튼을 클릭 할때 버튼 마다 다른 뷰를 보여 주는 기능을 구현 해 보도록 하죠..

 

 데브피아에서 찿은 CE에서 뷰 스위칭 하는 소스를 가지고 사용 하엿습니다..^^;

 

1. 일단 메인뷰가 아닌 교체될 뷰를 생성 해야 겠죠??

    제가 만든 예제에서는 두개를 생성 햇습니다..
    - 먼저 리소스에서 다이얼로그를 하나 insert합니다.

    - 그다음 속성에서 style과  border를 각각 Child와 None으로 줍니다.

    - 그런 다음 Ctrl+W로 위져드를 열면 클래스를 생성할것이냐고 뭍습니다..그럼 OK하고 ,

    - 클래스 이름을 넣어 주시고 , Base Class 를 다이얼로그가 아닌 CFormView로 하여

        클래스를 생성 합니다.

    - 그럼 뷰 클래스 하나가 생성 되엇습니다..

   

    - 만든 뷰에서 어떤 작업을 하고 싶으면 만들어진 클래스에서 작업 하시면 됩니다..^^

   

2. 교체되어질 뷰가 만들어 졌으니 이제 교체를 해보겟습니다..
    - 먼저 뷰를 담을 배열을 하나 선언 합니다..

        CView* m_pViewArray[MAX_VIEW_CNT];

        CView * 타입의 변수 입니다.. MAX_VIEW_CNT는 define된 배열의 개수 이고요

        이녀석을 생성자에서 초기화 해줍니다..



         

    - 그담에 MainFrame에 멤버 함수를 추가 합니다.

        뷰를 인덱스로 관리 하여 해당 뷰의 인덱스를 인자로 받아 현재 보이고 잇는 뷰를 사라지게 하고,

        바꾸어질 뷰를 보여 주는 함수 입니다..

        이 함수를 다이얼로그바의 버튼에서 호출 하면 버튼마다 보여주고 싶은 뷰를 보이도록 할 수

        있습니다..

       

설명이 제대로 됐는지 모르겠습니다..
아까 오전에 올린거는 설명이 넘 허접 하게 올려서 추후에 다시 수정 하겠습니다..
일하는중에 올리다 보니 정신이 없네요..^^; 남에세 설명 하는것도 처음이고요.....^^;

허접한 팁이었습니다..

 

소스첨부 하였습니다....

 

-----      즐거운 하루들 보내세요..^^     -----

SDI 형태
1.     MainFrame 얻기
-          CMainFrame *pFrame = (CmainFrame *) AfxGetMainWnd();

2.     App 포인터 얻기
-          CTestApp *pApp = (CtestApp *) AfxGetApp();

3.     Document 포인터 얻기
-          CMainFrame *pFrame = (CMainFrame *)AfxGetMainWnd();
            CTestDoc *pDoc = (CTestDoc *)pFrame->GetActiveDocument();
-          CTestDoc *pDoc = ((CMainFrame *)AfxGetMainWnd())->GetActiveDocument();

4.     View 포인터 얻기

-          CMainFrame *pFrame = (CMainFrame *)AfxGetMainWnd();
           CTestView *pView = (CTestView *)pFrame->GetActiveView();
-          CTestView *pView = ((CMainFrame *)AfxGetMainWnd())->GetActiveView();

MDI 형태
1.     ChildFrame 포인터 얻기
-          CMainFrame *pFrame = (CMainFrame *)AfxGetMainWnd();
           CChildFrame *pChild = (CChildFrame *)pFrame->GetActiveFrame();
-          CChildFrame *pChild = ((CMainFrame *)AfxGetMainWnd())->GetActiveFrame();

2.     Document 포인터 얻기
-           CMainFrame *pFrame = (CMainFrame)AfxGetMainWnd();
            CChildFrame *pChild = (CChildFrame *)pFrame->GetActiveFrame();
            CMdiTestDoc *pDoc = (CMdiTestDoc *)pChild->GetActiveDocument();
-          CMdiTestDoc *pDoc = (((CMainFrame *)AfxGetMainWnd())->GetActiveFrame())->GetActiveDocument();

3.     View 포인터 얻기
-          CCainFrame *pFrame = (CMainFrame)AfxGetMainWnd();
            CChildFrame *pChild = (CChildFrame *)pFrame->GetActiveFrame();
            CMdiTestView *pView = (CMdiTestDoc *)pChild->GetActiveView();
-          CMdiTestView *pView = (((CMainFrame *)AfxGetMainWnd())->GetActiveFrame())->GetActiveView();

--------SDI----------------

1.애플리케이션 클래스의 포인터를 얻을 때

CWinApp* AfxGetApp()

함수를 사용합니다.

2.메인 프레임 클래스의 포인터를 얻을 때

CWnd* AfxGetMainWnd()

함수를 사용합니다.

 이들 두 함수는 MFC의 전역함수로써 프로그램을 작성하는 도중 어디에서나 사용할 수 있습니다. MFC에서는 Afx~로 시작하는 함수들은 모두 전역함수를 의미합니다.

 물론 타입 캐스팅을 해야 하구요. 사용법은 다음과 같습니다.

    CTestApp *pApp = (CTestApp *)AfxGetApp();
    CMainFrame *pFr = (CMainFrame *)AfxGetMainWnd();

이렇게 써준 다음에는 pApp와 pFr은 각각 애플리케이션 클래스와 메인 프레임 클래스의 인스턴스 포인터를 가리키게 됩니다.

 이 외에 뷰 클래스에서 그 뷰를 둘러싸고 있는 프레임 윈도우를 참조할 때는

    CFrameWnd* GetParentFrame() const

함수를 사용할 수 있습니다. 물론 뷰 클래스뿐만이 아니라 일반적인 윈도우를 둘러싸는 틀로써 프레임 윈도우가 사용될 수 있기 때문에 GetParentFrame() 함수는 CWnd() 클래스의 멤버함수로 되어있습니다.

 이 함수와 AfxGetMainWnd() 함수는 SDI에서는 같은 기능을 하지만, MDI에서는 메인 프레임 윈도우와 뷰를 둘러싸고 있는 프레임 윈도우가 다르기 때문에 그 각각을 구하는 역할을 합니다.

3.도큐먼트 클래스의 포인터를 얻을 때는 몇 가지 경우가 있습니다.

    (a) 뷰 클래스에서 도큐먼트 클래스에 접근할 때.

      이때는 말할 필요도 없이 GetDocument() 함수를 쓰면 됩니다. 뷰에 이미 정의되어 있는 함수죠. 하지만 사용자가 임의로 뷰를 추가한 경우에는 이 GetDocument() 함수가 포함되어 있지 않습니다. 이럴 경우 기존에 있는 뷰에서 GetDocument() 함수 부분을 복사해다가 넣으면 됩니다. 이 부분은 Debug 모드와 Release 모드 두 가지의 함수가 있으므로 모두 복사해 넣아야 합니다.

    (b) 임의의 클래스에서 도큐먼트 클래스에 접근할 때.

       CMainFrame *pFr = (CMainFrame *)AfxGetMainWnd();
       CTestDoc *pDoc = (CTestDoc *)pFr->GetActiveDocument();

    위와 같이 두 줄에 걸쳐 써도 되고 다음처럼 한 줄로 줄여써도 됩니다.

    CTestDoc *pDoc = (CTestDoc *) ((CMainFrame *)AfxGetMainWnd())->GetActiveDocument();

4. 뷰 클래스의 포인터를 얻을 경우.

    (a) 임의의 클래스에서 뷰 클래스에 접근할 때

     뷰 클래스의 포인터를 얻어야 하는 경우는 대부분 다이얼로그에서 뷰에 접근하거나, 스플릿을 사용한 경우 다른 뷰 클래스에서 접근하는 경우가 대부분일 겁니다. 뭐 어떻든 상관없이 다음과 같이 하면 어디서든 뷰에 접근할 수 있습니다.

    CTestView *pView = (CTestView *) ((CMainWnd *)AfxGetMainWnd())->GetActiveView();

     물론 위의 3번의 경우처럼 두 줄로 나누어 써도 상관이 없습니다.

    (b) 도큐먼트 클래스에서 뷰 클래스에 접근을 할 때

    도 큐먼트 클래스에서 뷰 클래스의 인스턴스 포인터를 얻으려면 GetFirstViewPosition() 함수와 GetNextView() 함수를 조합하여 사용해야 합니다. 이렇게 복잡해지는 이유는 도큐먼트 하나에 여러개의 뷰가 연결될 수 있기 때문입니다.

    도큐먼트에는 이에 연결된 뷰가 연결 리스트 형태로 관리되고 있기 때문에 몇 번째 뷰를 얻을 것인지 선택하고 나서 위의 함수를 조합하여 사용하면 됩니다.

    다음은 도큐먼트와 연결된 모든 뷰 클래스를 차례로 얻어 뷰 클래스의 멤버함수인 UpdateWindow() 함수를 호출하는 예제입니다.

      POSITION pos = GetFirstViewPosition();

      while(pos != NULL) {
           CView *pView = GetNextView(pos);
           pView->UpdateWindow();
      }

    물 론 이와 같은 효과를 내기 위해서 도큐먼트 클래스의 멤버함수인 UpdateAllViews(NULL) 함수를 호출해도 됩니다. 여기서 쓰이는 인자인 NULL 은 모든 뷰를 업데이트하는 것이고, NULL 대신, 신호를 보내는 뷰의 포인터를 넣어주면 신호를 보내는 뷰는 빼고 나머지 뷰만 업데이트를 합니다.

      도큐먼트에 뷰가 오직 하나만 연결되어 있는 경우에는 다음과 같이 간단하게 뷰 클래스의 인슽턴스 포인터를 얻어낼 수도 있습니다. m_viewList 는 CDocument 클래스의 멤버변수로서, 뷰를 관리하는 연결 리스트입니다. 이것을 이용하여 GetHead() 함수를 호출하면 리스트에 들어있는 첫 번째 뷰가 얻어집니다.

      void CTestDoc::OnRepaintViews()
      {
           CView *pView = m_viewList.GetHead();
           pView->UpdateWindows();
      }

 

    (c) 스플리트 윈도우에서의 각 뷰 클래스에 접근할 때

      동적 스플리트 윈도우라면 모든 페인에서 같은 뷰를 사용하므로 별 문제가 되지 않는데, 정적 스플리트 윈도우라면 각 페인마다 다른 뷰를 사용할 수 있으므로 각 페인별로 뷰의 인스턴스 포인터를 얻는 것이 문제가 되는 경우가 생길 수 있습니다.

     이렇때는 메인 프레임 클래스에서 정의한 CSplitterWnd 클래스의 변수인 m_wndSplitter 의 멤버함수 GetPane()을 사용하면 각 페인의 뷰에 접근할 수 있습니다.

      우선 메인 프레임에서 정의된 m_wndSplitter 변수를 public: 속성으로 바꾸고(외부에서 접근해야 하므로) 메인 클래스의 인스턴스 포인터를 얻은 다음, 다시 여기서 m_wndSplitter 변수에 접근하여 이 멤버변수의 멤버함수 GetPane()을 이용하면 됩니다. 다음은 GetPane()의 함수 원형입니다. 리턴값은 대부분의 경우 CView에서 파생된 클래스의 인스턴스 포인터가 됩니다.

    CWnd* GetPane( int row, int col );

    임의의 클래스에서 다음과 같이 사용하면 페인에 연결된 뷰의 포인터를 얻을 수 있습니다.

        CTestView *pView = (CTestView *)((CMainFrame *)AfxGetMainWnd())->m_wndSplitter.GetPane(0,1);

-------- MDI ---------------

1.애플리케이션 클래스의 포인터를 얻을 때

    CWinApp* AfxGetApp()

함수를 사용합니다. 이것은 SDI에서와 동일합니다. App 클래스는 MDI든 SDI든 프로그램 내에서는 하나뿐이니까요. 다음처럼 사용하면 pApp는 애플리케이션 클래스의 인스턴스 포인터를 가리키게 됩니다.

    CTestApp *pApp = (CTestApp *)AfxGetApp();

2. CMDIFrameWnd의 파생클래스인 메인 프레임 클래스의 포인터를 얻을 때

    CWnd* AfxGetMainWnd()

함수를 사용합니다. 사용법은 SDI에서와 동일합니다.

    CMainFrame *pFr = (CMainFrame *)AfxGetMainWnd();

이렇게 써준 다음에는 pFr은 메인 프레임 클래스의 인스턴스 포인터를 가리키게 됩니다.

3. CMDIChildWnd의 파생클래스인 자식 프레임 윈도우의 포인터를 얻을 때

    (a) 활성화된 자식 프레임 윈도우의 포인터를 얻을 때

      virtual CFrameWnd* GetActiveFrame( );

    함수를 사용합니다. MSDN에 실려있는 이 함수의 리턴값에 대한 설명을 보면, 애플리케이션이 SDI이거나 MDI 프레임 윈도우에 활성화된 도큐먼트가 없을 때, 리턴값은 묵시적인 this 포인터가 된다고 하네요.

    사용법은 다음과 같습니다.

    CMDIChildWnd* pChild = (CMDIChildWnd *)AfxGetMainWnd()->GetActiveFrame();

4. 뷰 / 도큐먼트 클래스의 포인터를 얻을 때

 SDI 에서 구한 것과 마찬가지 방법을 사용한다. SDI에서는 메인 프레임의 포인터를 얻어 GetActiveView() 또는 GetActiveDocument() 함수를 사용했지만, MDI에서는 차일드 프레임의 포인터를 얻어 거기서 GetActiveView() / GetActiveDocument() 함수를 사용하면 된다. 사용법은 거의 동일하다.

 MDI에서도 SDI와 마찬가지로 도큐먼트나 뷰가 여러 개가 연결될 수 있으므로 그점만 주의해 주면 된다.

 사용법은 다음과 같다.

    CMDIChildWnd* pChild = (CMDIChildWnd *)AfxGetMainWnd()->GetActiveFrame();

    // 뷰 클래스의 포인터 얻기
    CTestView *pView = (CTestView *)pChild->GetActiveView();  

    // 도큐먼트 클래스의 포인터 얻기
    CTestDoc *pDoc = (CTestDoc *)pChild->GetActiveDocument();


'1.소프트웨어 이야기 > 01.MFC(Visual Studio)' 카테고리의 다른 글

[펌] DialogBar 구현  (0) 2008.08.07
클래스간 포인터 얻기  (0) 2007.12.06
MFC Socket 사용시 유의점  (0) 2007.12.06
MFC Socket 사용시 유의점

socket.IOCtl( FIONREAD, &avail ); // avail에 읽을 수 있는 양이 들어간다.
socket.Receive( pBuf, avail ) // 하지만 avail 만큼 다 못 읽을 경우가 있다.

따라서

MFC CAsyncSocket 사용시 유의사항

1. 소켓 API를 이용하여 특정 크기의 데이터를 보내면 나눠지거나 합쳐지는 경우가 있는가?

2. 송신자가 100바이트를 전송하여 수신자의 OnReceive함수가 호출 되었다.
그러나 그 함수 에서 50 바이트만 Receive했을 경우 다시 OnReceive함수가 호출되는가?

3. 위와 같은 경우에서 50 바이트가 아니라 아예 Receive를 하지 않은 경우는 어떻게 되는가?

4. 특정 데이터 크기 단위로 받아서 처리하는 로직을 구성하려 한다.
송신자는 데이터 단위당 그 크기를 4바이트 정수로 데이터 앞에 붙여서 보낸다.
다음과 같이 구성하여 완전한 길이의 데이터가 들어왔을때만 메세지를 보내도록 하였다.
이것이 잘 동작하는가? 문제가 있다면 무엇인가?
5. 소켓버퍼에 데이터가 있더라도 인위적으로 OnReceive가 호출 되지 않도록 할 수 있는가?

6. 데이터 크기를 명시하여 잘 동작하는 패킷 전송 프레임웍을 구현해보라.

+ Recent posts