2008년 12월 8일 월요일

MFC 스레드 클래스 사용 (작업자 스레드, 사용자 인터페이스 스레드) - 스크랩

MFC 스레드 클래스 사용
- 출처 : http://jbjempire.egloos.com/page/3

MFC 라이브러리에서는 CWinThread 클래스를 제공한다. MFC에서 유일한 스레드 클래스이며, MFC 어플리케이션의 모든 스레드는 CWinThread 로 대표된다.

MFC 스레드는 CWinThread 클래스의 용도를 크게 다음 2가지로 구분한다.
1. 작업자 스레드2. 사용자 인터페이스 스레드WIN32에서는 이 두 가지를 구분하지 않는다.

작업자 스레드
계산이나 입출력과 같이 사용자의 입력을 필요로 하지 않는 작업에 사용되는 스레드를 말한다.

사용자 인터페이스 스레드
사용자의 입력을 받거나, 사용자에 의해서 발생되는 이벤트를 처리할 목적의 스레드에 사용된다. 다시 말하면, 스레드 내부에 메시지 또는 이벤트를 처리할 수 있는 루틴을 포함하고 있다는 뜻이다. CWinThread 클래스는 메시지 펌프를 제공하여 사용자 입력 또는 이벤트를 처리할 수 있도록 하였다. 대표적으로 CWinApp 가 메시지 펌프를 사용한 사용자 인터페이스 스레드의 예이다.
사용자로부터의 입력 또는 이벤트에 따른 처리가 가능한 윈도우를 갖는 스레드를 말한다. 이러한 처리는 윈도우, 메시지펌프, 메시지큐, 메시지 프로시저(이벤트 핸들러)가 스레드 내에 존재해야 한다는 뜻이다.
AfxBeginThread, AfxEndThread

일반적으로는 객체를 만들기 위해 new를 사용하지만, CWinThread는 MFC 전역함수 AfxBeginThread를 호출하여 생성한다. 작업자 스레드와 사용자 인터페이스 스레드를 생성할 수 있도록 두 가지 형태의 AfxBeginThread() 를 제공한다.

MFC 작업자 스레드 생성
CWinThread* AfxBeginThread(ㅁㅁAFX_THREADPROC pfnThreadProc,                        // 실행시킬 함수포인터ㅁㅁLPVOID pParam,                                                     // 함수에 전달할 포인터ㅁㅁint nPriority = THREAD_PRIORITY_NORMAL,            // 스레드 우선순위등급ㅁㅁUINT nStackSize = 0,                                              // 초기 스택 크기ㅁㅁDWORd dwCreateFlags = 0,                                   // 생성옵션ㅁㅁLPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL // 보안속성)
AfxBeginThread() 는, 함수명(포인터)과 전달될 파라미터로 호출될 경우, 코드상에서 보면, 단지 함수가 스레드로 실행되는 것처럼 보인다.

AfxBeginThread() 는, 내부적으로는 CWinThread 클래스 객체를 생성하여 사용할 수도 있고, CWinThread 클래스에서 파생된 클래스를 직접 작성하여 사용할 수도 있다. 이때 스레드 함수는 해당 스레드 클래스의 정적 멤버함수로 작성하여 사용한다(물론 다른 방법도 있지만 권장하지 않는다)

사용자 인터페이스 스레드 생성

CWinThread* AfxBeginThread(
ㅁㅁCRuntimeClass* pThreadClass,ㅁㅁint nPriority = THREAD_PRIORITY_NORMAL,ㅁㅁUINT nStackSize = 0,ㅁㅁDWORd dwCreateFlags = 0,ㅁㅍLPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL  // 보안속성)
1. CWinThread 객체 생성 (CWinThread::CWinThread)
2. 스레드 루틴 실행 (CWinThread::CreateObject)3. 스레드 우선 등급 설정 (CWinThread::SetThreadPriority)4. 스레드 재개 (CWinThread::ResumeThread, 옵션에 따라)

CWinThread 클래스

CWinThread 클래스에서 ‘작업자 스레드’와 ‘사용자 인터페이스 스레드’를 구분하는 기준
은 스레드 함수 포인터의 유무에 의해서 결정된다. 스레드 함수 포인터를 생성자에서 초기화하지 않는 사용자 인터페이스 스레드는 Run() 함수를 사용한다.

작업자 스레드 용

CWinThread::CWinThread(AFX_THREADPROC pfnThreadProc, LPVOID pParam)
{ㅁㅁm_pfnThreadProc = pfnThreadProc;// 추가로 실행할 스레드 함수포인터}

사용자 인터페이스 스레드 용

CWinThread::CWinThread()
{ㅁㅁm_pThreadParams = NULL;// 스레드 함수포인터가 NULL}
객체가 생성된 이후 CWinThread::CreateThread() 를 호출한다. 이 멤버함수는 _beginthreadex() 를 호출하면서 _AfxThreadEntry() 전역함수를 스레드로 실행한다.

_beginthreadex(
ㅁㅁlpSecurityAttrs, ㅁㅁnStackSize, ㅁㅁ&_AfxThreadEntry, ㅁㅁ&startup,ㅁㅁdwCreateFlags | CREATE_SUSPENDED, ㅁㅁ(UINT*)&m_nThreadID);
MFC로 생성되는 모든 추가 스레드의 실행 루틴은 _AfxThreadEntry 이다. _AfxThreadEntry는 CWinThread 의 함수포인터 pThread->m_pfnThreadProc의 유무를 판단하여 작업자 스레드와 사용자 인터페이스 스레드를 구분하여 실행한다.

AfxThreadEntry() 에 의해 구분된
1. 작업자 스레드는 해당 ‘스레드 함수’ 를 호출한다.2. 사용자 인터페이스 스레드는 다음 순서로 호출된다.A. CWinThread::InitInstance()B. CWinThread::Run()C. CWinThread::ExitInstance()
Windows 운영체제는 스레드 단위로 메시지 큐를 제공한다는 것이다. 이를 이용한 것이 바로 사용자 인터페이스 스레드이다.


















 

 

WIN32 API

 

CreateThread

ExitThread

C 런타임 라이브러리

 

_beginthread/_beginthreadex

_endthread/_endthreadex

C++ 스레드 클래스를 제작하여 이용

Static CMyThread::ThreadFunc

MFC가 제공하는 CWinThread

 

AfxBeginThread

AfxEndThread


[MFC_Debug] Warning: no message line prompt for

Warning: no message line prompt for



 무심코 디버그 출력 창을 보니 위와 같은 경고 메시지가 자꾸 찍힌다.
컴파일 에러도, 컴파일 경고도 아니기에 무시하고 넘어갈려다가
괜히 나중에 일이 커질것 같아 알아보았다.

메뉴를 새로 추가하였을 때
해당 메뉴의 설명 즉,"Prompt" 값을 입력하지 않아서 발생하는 메시지 이다.




예).


메뉴에 새로운 메뉴를 추가하였다.




그러고 보니 해당 메뉴의 "Prompt" 값을 넣어주지 않았다.
값을 넣어주자.





프로그램에서 해당 메뉴에 마우스를 가져가면 프로그램 하단의 상태바에 출력이 된다.
이게 없어서 경고 메시지를 날려 주던 것이였다.


##

2008년 12월 7일 일요일

[MFC_Debug] error C2248: 'CObject::operator =' : private 멤버('CObject'클래스에서 선언)에 액세스할 수 없습니다.

error C2248:
'CObject::operator =' : private 멤버('CObject' 클래스에서 선언)에
액세스할 수 없습니다.



선언을 다음과 같이 하고

CReadProgressDlg m_pReadProgressDlg;


객체에 값을 아래와 같이 대입 하였다.

m_pReadProgressDlg = NULL;



음...
클래스 이름도 길고, 이름도 길어서 코딩하다가
포인터 객체임을 나타내는 '*'을 써주지 않았다.  아래와 같이 해주자

CReadProgressDlg *m_pReadProgressDlg;



포인터 객체가 아니라 일반 객체 이므로
' = NULL' 을 해주면 할당 연산자를 호출한 격이니...





2008년 12월 6일 토요일

윈도우간 통신 - SendMessage() 와 PostMessage(), PostThreadMessage()

윈도우간 통신
SendMessage() 와 PostMessage(), PostThreadMessage()




PostMessage()
1. 원형

BOOL PostMessage(
    HWND hWnd,     // 이 메시지를 받을 윈도우 핸들
    UINT Msg,      // 전달할 메시지
    WPARAM wParam, // 여분 데이터
    LPARAM lParam  // 여분 데이터
); 


2. 동작   - Msg 인수로 지정된 메시지를 hWnd 윈도우의 메시지 큐에 집어 넣는다.
   - 메시지를 받은 윈도우는 윈도우 프로시저에서 이 메시지를 처리한다.
   - 메시지를 메시지 큐에 넣기만 하고 바로 리턴 한다.
   - 큐에 붙인 메시지는 GetMessage() 에 의해 읽혀지고, DispatchMessage() 에 의해 윈도우 프로시저로 보내짐

3. 리턴값에 대하여
    - TRUE : 메시지를 메시지 큐에 붙이기 성공
    - FALSE : 메시지를 메시지 큐에 붙이기 실패
    ※ 가급적이면 리턴값을 점검해 보는 것이 좋다.
        메시지 큐는 크기가 한정되어 있기 때문에 고속으로 전송되는 모든 메시지를 다 수용하지 못할 수도 있기때문

4. 장점   - 메시지를 메시지 큐에 넣기만 하고 곧바로 리턴 하므로 보내는 쪽에서 "블록(Block)" 시키지 않는다.
      따라서 바로 다른 작업을 할 수 있다.

5. 단점
   - 메시지를 메시지 큐에 넣고 리턴한다. 이때 메시지 큐에 대기하고 있는 다른 메시지가 있으면 해당 메시지가
     바로 처리되지 않을 수도 있다.
   - 메시지가 언제 처리 될지도 예측할 수 없으므로 메시지의 wParam, lParam 에는 지역 포인터를 사용하지
     않아야 한다.

     (메시지를 붙일 시점에는 포인터가 존재했더라고 메시지가 처리될 시점에는 포인터가 무효해 질수도 있다.)
    
6. 활용   - 급하게 처리할 필요가 없는 처리의 메시지
   - 지금 하고 있는 작업을(메시지를 보내는 입장에서) 완전히 끝내야만 처리할 수 있는 메시지

7. 첫 번째 인수 hWnd 가 NULL 인 경우
   - 특정 윈도우에게 신호를 보내기 위한 것이 아니라 응용 프로그램 전반에 걸친 작업 지시를 보낼 때 사용
   - 대상 윈도우가 없기 때문에 이 메시지는 윈도우 프로시저가 처리할 수 없으며 윈도우 프로시저로 가기전인
     메시지 루프에서 직접 처리해 주어야 한다.

while (GetMessage(&Message, 000)) {
    if (Message.message == WM_SOME) {
        // 직접 처리
    } else {
        TranslateMessage(&Message);
        DispatchMessage(&Message);
    }
}
 

   - GetMessage로 메시지를 꺼낸 후 곧바로 메시지 ID를 비교해 보고 스레드를 위한 메시지 인지 검사해 본다
   - 만약 스레드를 위한 메시지라면 메시지 루프에서 직접 처리해야 하며 DispatchMessage 함수로 보내지말것
   - 대상 윈도우가 지정되지 않은 메시지이기 때문에 이 메시지를 받아줄 윈도우 프로시저가 없기 때문.
   - PostMessage 함수가 메시지를 붙여넣는 큐가 윈도우를 위한 큐가 아니라 스레드를 위한 큐이기 때문에 가능





PostThreadMessage()
1. 원형


BOOL PostThreadMessage(
     DWORD idThread,    // 스레드 ID
     UINT Msg,          // 전달할 메시지
     WPARAM wParam,
     LPARAM lParam
);
  


2. 사용 목적
   - 다른 스레드의 메시지 큐에 메시지를 붙일 때 사용
   - 스레드 외부에서 특정 스레드로 메시지를 전달할 때

3. 한계
   - 작업자 스레드에선 사용할 수 없고, 사용자 인터페이스 스레드 에서만 사용 가능




SendMessage()
1. 원형

BOOL SendMessage(
    HWND hWnd,     // 이 메시지를 받을 윈도우 핸들
    UINT Msg,      // 전달할 메시지
    WPARAM wParam, // 여분 데이터
    LPARAM lParam  // 여분 데이터
); 


            ※ SendMessage() 와 PostMessage() 함수의 원형은 동일

2. 동작   - 메시지를 메시지 큐에 넣는 것이 아니라 곧바로 윈도우 프로시저로 보내 즉각 처리하도록 한다.
   - 메시지가 완전히 처리되기 전에 리턴하지 않는다. (블록 시킴)

3. 장점
   - 보낸 메시지의 정확한 처리 보장

4. 단점
   - 보낸 메시지가 완전히 처리 될때까지 기다리므로, 메시지를 보낸쪽은 블록 상태에 머물게 된다.
     영영 블록되어 프로그램이 죽을 수도 있다.
   ※ 메인 스레드에서 다른 스레드로 메시지를 보낼때 SendMessage() 의 사용은 위험하다
5. 활용
   - 부모 윈도우와 차일드 컨트롤 간의 통신
   - 윈도우간 정확한 데이터 전송 및 처리를 위한 통신
   - 윈도우의 특정 메시지는 SendMessage() 만을 사용해야 하는 경우도 있다. (WM_COPYDATA)




cf)참고 - Windows API 정복 (김상형)





■  사용 예
1. SendMessage() 함수로 출력 시켰을때
2. PostMessage() 함수로 출력 시켰을때

※ SendMessage() 함수로 출력 시킨 결과가 올바른 값이다.
   하지만 PostMessage() 함수로 출력 시키면 제일 처음에 전송된 데이터가 처리 되기 전에 나중에
   나중에 전송된 데이터로 값을 찍어 버려서 올바른 값이 출력되지 않는다.



##

2008년 12월 3일 수요일

[MFC_Debug] error C2166: l-value가 const 개체를 지정합니다.


error C2166: l-value가 const 개체를 지정합니다.

 void ChRmtEvwrDoc::PrintLogRecordInfo(const CString &_strLogName) const{
    DWORD dwNumOfRecord = 0;

    m_pEvt->ReadNumOfRecord(_strLogName, dwNumOfRecord);
    if (1 >= dwNumOfRecord){
        m_pRECORDINFO = new RECORDINFO;

        if (NULL == m_pRECORDINFO)
            return;
    }

    m_pEvt->PrintRecordInfo(ReadRecordInfo, _strLogName);
}



무엇이 잘못 되었는지 보이는가?

PrintLogRecordInfo() 함수는 const 함수라고 지정해 주었는데
함수 안에서 m_pRECORDINFO 포인터에 동적 메모리 할당을 시도 하고 있다.
즉 멤버 변수 m_pRECORDINFO 값을 변경하려 하고 있으므로 컴파일러가 메시지를 날려 준다.

함수 헤더 옆에 const 를 제거해 주면 되긴 한다.
class 디자인 뿐만 아니라 함수 기능도 조금더 생각해 보고 const 키워드를 적용해 주자.