API

WM_PAINT의 비밀...? - 메시지 큐잉 관련 좋은 글....비큐 메시지/큐 메시지 정확한 구분

디버그정 2008. 8. 31. 13:56
WM_PAINT의 비밀...?

을 이해 하기 위해서는 몇가지 간략한(!?) 사전지식이 필요하다.


Windows와 메시지큐는 안다는 가정 하에

하나씩 적어보자...


참고용 메시지루프 되시겠다. 다들 기억이 안날테니 ^______^

BOOL bRet;

while( (bRet = GetMessage( &msg, hWnd, 0, 0 )) != 0) {
    if (bRet == -1) {
        // handle the error and possibly exit
    } else {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}



0 :

윈도우즈에서 모든 객체의 소유권은 쓰레드가 가지고 있다.

윈도우의 메세지 큐는 해당 윈도우를 가지고 있는 쓰레드가 가지고 있다.

각 쓰레드는 한 쓰레드만 실행하는 것 같은 환경에서 실행되도록 하기 위함이다.
 초기 쓰레드 생성시에는 UI에 필요한 리소스 생성작업을 하지 않는다.
하지만 쓰레드가 UI관련작업(메세지큐 체킹(GetMessage), 윈도우를 생성(CreateWindow))

을 할경우 THREADINFO라는 Undocumened 구조체를 할당한다.


만약 하나의 프로세스가 세개의 쓰레드를 생성하고 각 쓰레드가 CreateWindow를 할 경우

 쓰레드별로 메세지큐가 생성된다.



1 :
윈도우즈는 기본적으로 메세지를 끊임없이 주고 받는 운영체제이다. 각종 이벤트(키보드, 마우스 입출력, 윈도우 사태변경등등)에 메세지가 발생하며 전달되고 처리된다.

메세지는 시스템 메세지 큐와 쓰레드 메세지 큐를 거치는 queued message와 해당 윈도우즈의 WNDCLASS에 등록된 프로시저에 전달되는 non-queued message로 구분된다.

함수로는 같은 쓰레드의 윈도우에 대해 호출되는 SendMessage()가 non-queued message이며 PostXXXMessage()류와 다른 쓰레드의 윈도우에 사용되는 SendMessage()가 queued message이다.

queued message의 경우 키스트로크 (WM_KEYDOWN / WM_KEYUP등)과 키스트로크에 의한 문자(WM_CHAR), 마우스 이동(WM_MOUSEMOVE), 마우스 클릭(WM_LBUTTONDOWN), 타이머 메세지(WM_TIMER), 그리기 메세지 (WM_PAINT), 종료 메세지(WM_QUIT)가 있으며,

non-queued message는 그 외의 다른 모든 메시지가 되겠다.



2 :

PostMessage :

간단하다. 쓰레드의 큐에 메시지 넣고 바로 리턴한다.(비동기)

하지만 모든 메시지가 메시지큐에 차례로 들어가는 것은 아니다. 예를 들어 WM_PAINT의 경우 이미 메지시큐에 WM_PAINT가 있다면 뒤에 들어온 WM_PAINT는 무시된다.

단 무효화 영역은 결합된다. InvalidateRect가 이런 놈이었던 거시다!!


SendMessage :

동일 thread 사이의 콜과 다른 thread 사이의 콜이 다른 동작을 한다.

동일 thread안에서 SendMessage를 할 경우 메시지큐를 통하지 않고 바로 함수를 찾아서 실행해 버린다.


하지만 다른 thread..즉 thread A의 윈도우에서 thread B의 윈도우로 SendMessage를 날리면 좀 복잡해 진다.

간단하게 줄여서 말하면...thread B가 thread A에 SendMessage를 날리면

thread A의 GetMessage, PeekMessage, WateMessage 가 호출된 시점에서

수행되게 되며,(당연하다. 메시지큐를 통하기 때문이다)

thread A가 요청을 다 처리할 때 까지 thread B는 "BLOCK" 된다.(동기식)

BLOCK이 뭐냐..하면 thread A가 불의의 사고를 당해 영원히 처리를 안하면 thread B는

영원히 기다린다는 말이다. 둘다 죽는다는 야그.(하지만 이 사태를 막는 구조가 이미 준비되어 있다)

중요한것은 블럭킹이 걸려 있는 동안에도 시스템의 다른 쓰레드(thread C,D..)

에서 보내어진 메세지는 send message queue에 쌓인다.



3 : 문제는 여기다.

이 SendMessage를 이놈저놈 막 날려대면...요청을 받은 thread는 어떻게 동작할 것인가?

당연하게도 MS가 나름 정의한 처리 규칙이 있으시다. 뭐 복잡하지만 간단히 말하면

"처리순서에는 우선순위가 있다"  라는게 핵심 되시겠다.


우선순위는 아래와 같다.

 SendMessage > PostMessage > QUIT > INPUT > PAINT > TIMER

즉, SendMessage로 온 요청부터 "싸그리 다"(여러개가 있으면 한꺼번에 다

처리한다) 처리하고 일단 리턴하고 그 다음에 메시지루프를 돌아 getMessage, peekMessage를

다시 호출하고 SendMessage로 온 요청이 없으면 PostMessage로 온 요청을 쳐다보고...

 PostMessage도 없으면 QUIT보고...

한~~~참 있다가 WM_PAINT를 봐준다는 얘기 되시겠다.

        


자..3번을 보고 팍 떠오르는 생각이 없는가?

그렇다...(뭐가 -_-)

thread A가 thread B,C,D....등의 수많은 thread들의 요청을 처리한다고 해보자.

사방에서 thread A로 미친듯이 SendMessage가 들어오고(PostMessage도 들어오겠지)

thread A의 메시지큐는 하염없이 쌓여만 간다.(설명 2번에 의해)


Thread A가 요청을 처리하기 위해 자신의 메시지큐를 딱! 열어본 순간(getMessage or peekMessage) 설명 3번에 의해 사방에서 날라온 SendMessage를 모조리 처리하기 시작한다.

오랜 시간을 소비하여 다 처리했다고 치자. 그러면 일단 리턴하고 다시 메시지 루프로 돌아가서...

getMessage or peekMessage를 호출하고 다시 자신의 메시지큐를 열어보게 된다.

근데 그동안 SendMessage or PostMessage로 요청이 또 잔뜩 쌓여 있으면???

또 한~참 걸려 처리하고 리턴하고 getMessage -> 요청처리 -> 리턴 -> getMessage................


이게 왜 문제냐고?

우선순위가 낮은 WM_PAINT, TIMER등은 SendMessage로 온 요청들을 처리하다가 시간 다 쓰기 때문에 영원히 처리되지 못할 수 있다.

수학 시험 볼 때 문제들을 열라게 다 계산해서 풀어놓고 답 쓸 시간이 없어 망했다는 얘기와도

일맥 상통한다(과연?)

이러면 윈도우 화면 업데이트가 안될수도 있다는 얘기고...아마 (응답없음)이 뜨거나 하면서

사용자에게는 프로그램이 다운된 것처럼 보이게 될 수 있다. 

References

http://wiki.rabidus.net/ow.asp?WindowsMessageFlow#h22

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

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

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



P.S : 이걸 누가 읽을까 싶다. 사실 까먹을까봐 써 놓는 것 뿐...

P.S2 : 윈도우의 모든 비밀은 사실 MSDN에 대부분 있다.