안녕하세요 삽잡이입니다.
지난 시간에 이어서 ShowWindow와 UpdateWindow에 대해서 알아보려고 합니다.
위의 두 함수는 CreateWindow에서 반환된 윈도우 핸들을 사용하여
윈도우를 나타내는 기능을 담당하고 있습니다.
프로그램상 메모리가 생성되면 해당 프로세스를 관리할 수 있는 핸들을 OS로부터 받게됩니다.
즉, ShowWindow는 핸들을 줄테니 만들어 달라는 기능을 담당하고 있는 것이지요...
이때 WinMain 함수의 마지막 인수인 nCmdShow가 사용되는데,
사용자가 Input한 형태로 윈도우를 띄우라는 의미가 내포되어 있습니다.
UpdateWindw는 메시지를 처리하느라 화면에 안 뜰 수 있는 문제를 해결해주는 함수로,
'지금 나타내!'라는 뜻을 가지고 있는 함수입니다.
다음으로는 메시지를 읽고 Dispatch 하는 작업에 대해서 알아보도록 하겠습니다.
Dispatch는 약간 던진다는 의미를 가지고 있는데,
DispatchMessage는 메시지를 출력하라는 뜻을 가지고 있습니다.
지난 시간에 이어 지금까지 WinMain만 보고 있었는데, 드디어 WndProc이 호출되는 것입니다.
메시지 루프가 돌면서 GetMessage로 메시지를 읽어오고,
DispatchMessage로 메시지를 프로시저에 전달하게 됩니다.
정확히는 DispatchMessage가 전달하는 것이 아닌 OS가 호출하는 것이지만 말입니다...
WndProc 함수는 OS만이 호출할 수 있습니다.
Dispatch는 메시지를 처리한다는 뜻을 가지고 있는데,
이를 OS에게 메시지를 전달해주는 것이라고 생각하시면 됩니다.
왜 이런 과정을 거칠까요?
메시지는 크게 두가지 종류로 나뉠 수 있습니다.
1. 메시지 큐에 저장되는 메시지
2. 바로 프로시저에 전달되는 메시지
이와같이 말입니다...
GUI는 텍스트처럼 간단하지 않으니 OS가 많은 일들을 하게 됩니다.
사용자가 Input을 많이 때리게 되도 순차적으로 메시지를 처리할 수 있어야 됩니다.
즉, 메시지가 섞여버리면 안된다는 것이지요...
즉, 순차적으로 메시지를 처리하기 위해 메시지 큐에 저장이 되는데
GetMessage가 메시지를 읽어들여오는 것입니다.
읽어온 메시지를 OS가 처리할 수 있도록 Dispatch하는 것입니다.
화면이 가려졌다가 다시 나타나는 Active 관련 메시지던지,
프레임이 변경되거나 사라지거나 윈도우가 없어지거나 하는 메시지들은
시스템과 관련된 메시지라 OS가 다이렉트로 WndProc 함수를 호출하게 됩니다.
정리하자면, 메시지 큐는 사용자 Input을 순차적으로 처리하려고 있는 것인데,
비큐 메시지와 같이 시스템 관련 메시지는 바로 Proc으로 전달된다는 것입니다.
따라서 두가지의 메시지 종류는 이와 같이 다시 정의할 수 있습니다.
바로, '큐 메시지'와 '비큐 메시지'로 말입니다.
사용자의 메시지와 비큐 메시지를 처리하는 과정을 거치는데,
만약, 메시지를 하나 처리하고 있는데 다른 메시지가 들어온다면 어떻게 될까요?
정답부터 말씀드리자면,
OS가 메시지를 어떻게 보낼지까지 관리하기 때문에 알아서 처리해줍니다.
즉, DispatchMessage는 OS에 전달이 되고 OS가 Proc함수를 호출시켜준다는 것입니다.
그렇다면 WndProc함수는 Callback 함수라고도 할 수 있게 됩니다.
Callback 은 뭘까요?
서버가 클라이언트에게 요청하는 것을 Callback이라고 할 수 있습니다.
이게 뭔 x소리냐구요? A()라는 함수와 B()라는 함수가 있다고 해보겠습니다.
A()함수고 B()를 호출하게 된다면
A는 B를 요청한 놈이니 Client가 되고 B는 서비스를 제공하니 Server가 되는 것입니다.
즉, 서버가 클라이언트를 호출하게 된다면 Callback이라고 할 수 있는데요,
Call은 클아이언트가 서버의 기능을 요청하는 것(함수를 부르는 것)이고,
Callback은 서버코드에서 클라이언트 코드를 요청하는 것을 의미합니다.
이 모든 관점은 클라이언트입장에서 봤을때 성립이 됩니다.
Server는 기존에 만들어져있는 서비스 제공자이고
Client는 미래에 우리가 만드는 Client이기 때문에 Server의 기능은 우리가 고칠 수 없습니다.
이 말은 뭔 말이냐하면, 서버가 먼저 만들어져있고, 클아이언트가 나중에 만들어지기때문에...
서버 코드는 못바꾸고 기능만 사용할 수 있다는 것입니다.
예를 들어 서버에서 검색하는 기능이 있다고 가정해보도록 하겠습니다.
서버는 이미 만들어져 있는 검색 기능을 가지고 있으니
우리는 해당 서버 코드를 호출하여 그 기능만을 사용할 수 있게 됩니다.
그런데말입니다... 검색할때 검색 기능의 종류가 하나밖에 없을까요?
아니겠지요... 무수히 많은 정책들이 있지 않겠습니까...
이걸 서버가 다 정의해놓을까요? 불가능합니다....
그렇다면 무수히 많은 행동을 서버에서 바꿀 수 없으니 그 기능만큼은 클라이언트마다 마련해두는 것입니다.
즉, 서버에게 찾아달라고 요청할 수는 있어도
어떤 값을 찾으라고 하는 정책은 클라이언트가 알고 있다는 것입니다.
검색 기능을 호출했다고 해도 어떻게 찾을지에 대해서는 서버가 모르기 때문에
클라이언트에게 물어봐야되는 상황에 처하게 됩니다.
즉, 서버에서는 호출한 기능을 실행하다가 클라이언트에게 정책을 물어보고
다시 실행하게 되는 것입니다. 이를 Callback이라고 하는 것이고요...
확실하게 정리하자면, 클라이언트에 있는 정책... 이를 콜백 함수라고 부릅니다.
콜백에 대한 말이 너무 길었군요...그놈의 Callback...
본론으로 다시 돌아가서, API들은 OS가 제공하는 것입니다.
그런데 WndProc 함수는 OS가 호출하게 되는 것이구요...
예를 들어 키보드 버튼을 누르던지, 마우스를 움직이던지에 대한 행위는
클라이언트가 알고 있으니 OS가 이때에는 서버가 되는 것입니다.
지금까지는 항상 어플리케이션이 API를 호출하는 방식이었지만,
WndProc은 OS가 콜을 하게 되는 것이니 WndProc은 콜백함수가 되는 것이지요...
즉, 모든 프로시저는 Callback함수로 만들어진다는 것을 알게되었습니다.
여담으로 WinMain함수는 Callback 함수가 아닙니다....
따라서 WndProc 함수 앞에 CALLBACK이 붙지 않습니까?
이는 __stdcall, __cdesl 방식을 CALLBACK, WINAPI 로 감춘 것일 뿐인데,
이 둘을 우리는 함수 호출 규약이라고 합니다....
함수의 스택 메모리를 호출한 놈쪽에서 제거할지, 반환 되어 제거할지에 대해 나눠져 있습니다.
뭐... 지금은 그리 중요하지는 않습니다...
여기서 잠시 생각해볼 문제가 있습니다.
1 2 3 4 5 6 7 8 9 10 | while(GetMessage(&msg, NULL, 0, 0)) { DispatchMessage(); } .... WndProc() { } | cs |
이와 같은 코드에서 큐와 비큐 메시지를 호출하는 과정이라고 하겠습니다.
만약 Proc에서 메시지를 처리하고 있는데 Dispatch 시키면 어떻게 될까요?
정답을 말씀드리자면, 프로그램이 실행되고 있는 상태인 Process는
기본적으로 딱 하나의 명령만을 실행할 수 있습니다.
WndProc에서 명령을 실행하고 있다면 다른 명령을 실행하지 못하기 때문에 Dispatch가 되지 않습니다.
메시지 명령이던, WndProc 명령이던 모두 Process의 것이기 때문에
해당 Process에서는 단 하나의 명령만을 실행 시킬 수 있다는 것입니다.
실제 프로세스 내에서 명령을 실행시켜주는 놈을 스레드라고 부릅니다.
이 스레드는 어떤 명령을 실행할지에 대한 정보를 가지고 있습니다.
프로세스를 생성하는 단 하나의 스레드이기에 Primary (Main) 스레드라고도 부릅니다.
쓰레드가 하나면 명령은 단 하나만 실행시킬 수 있게 되는 것입니다.
만약 쓰레드가 두개라면 명령어를 실행할 수 있는 놈이 두명이기에 둘이서 일을 할 수 있게 되죠...
즉, 쓰레드는 실행경로라고도 부를 수 있게 됩니다. 프로세스 내의 실행 경로 말입니다...
따라서 쓰레드의 개수에 따라 실행경로의 갯수가 정해질 수 있게 됩니다.
함수는 실행 단위지만 실행되는 것이 아닙니다. 명령어를 나열해 둔 집합일 뿐이지요...
즉, 함수는 단위일 뿐 실행과 관련없다는 것입니다.
오직, 쓰레드만이 실행과 관련된 것입니다.
실행이라는 단어는 뭘까요 그렇다면?
실제 실행할 수 있는 것은 연산입니다. 이 연산을 할 수 있는 장치는 CPU의 ALU라는 친구밖에 없는데
실행이라는 말은 즉, ALU에 임무를 올리는 것입니다.
이렇게 일을 나를 수 있는 놈은 쓰레드밖에 없다는 것이구요...
함수()
{
1.
2.
3.
}
이와같은 함수가 있게 됬을때 쓰레드가 하나라면
함수()의 1번째 함수를 ALU로 올리고 그다음 2번... 3번...이렇게 왔다 갔다 하게 되는 것이지요...
그렇다면, ALU가 하나밖에 없다면 쓰레드가 아무리 많더라두
진짜 실행되는 놈은 딱 하나밖에 없겠지요...
그런데 왜 동시에 실행되는 것처럼 보일까요? 이는 시분할 스케쥴링이라 하여
조금씩 가지고 노는 것이죠....
아무튼... 원점으로 가서...
지금 본인은 현재에서 프로그램은 딱 하나의 명령만 실행시키는 수준을 가지고 있는데요...
WndProc 은 OS가 호출하는데 그렇다면 OS마음대로 실행하게 되는것인가요?
아닙니다... 실제적으로는 OS가 'WndProc아 너의 쓰레드다 이거 실행해...' 이러면서
자기 쓰레드가 명령을 실행하는 것입니다.
단지 OS가 WndProc의 쓰레드가 실행하도록 유도하는 것입니다...
어찌됬건... WndProc은 아무리 반복적으로 중첩되어도 여러번 실행되지 않습니다.
DispatchMessage는 처리하라고 메시지를 보내도 리턴이 되기 전까지 아무것도 하지 않습니다.
리턴이 된다면 다시 메시지를 하나 꺼내와서 읽으들이는 것이지요...
자... GUI계의 C언어인 Win32 API를 씹어먹는 그날까지...
열심히 공부하겠습니다... 이상 삽잡이였습니다!
'삽질의 현장 > - 윈도우 API' 카테고리의 다른 글
#005_WIndow_API_그리기 (I/O) (1) (0) | 2015.09.09 |
---|---|
#004_WIndow_API_WinMain, WndProc에 대해서 (3) (0) | 2015.09.08 |
#002_WIndow_API_WinMain, WndProc에 대해서 (1) (0) | 2015.09.08 |
#001_WIndow_API_Intro (0) | 2015.09.07 |
#Win API 기본 (0) | 2015.09.06 |