[Win32] WM_COMMAND

CS/C/C++ 2009. 9. 1. 23:08
The WM_COMMAND message is sent when the user selects a command item from a menu, when a control sends a notification message to its parent window, or when an accelerator keystroke is translated.

WM_COMMAND가 받는 경우는
1. 사용자가 메뉴에서 command item을 선택했을 때 (메뉴)
2. 컨트롤이 부모 윈도우로 통지 메세지를 보낼 때 (컨트롤)
3. 엑셀레이터키가 해석될 때 이다. (Accelerator)

즉, 메뉴, 컨트롤, 엑셀러레이터 의 3가지의 경우이다.
일단 각각을 살펴보기 전에 MSDN에 있는 WM_COMMAND의 파라미터가 어떻게 해석되는가 하면

Message Source wParam(high word) wParam(low word) lParam
Menu 0 Menu identifier (IDM_*) 0
Accelerator 1 Accelerator identifier (IDM_*) 0
Control Control-defined
notification code
Control identifier Handle to the
control window
  
요렇다.


일단 윈도우 메세지가 처리되는 과정을 보기 위해 Win Main을 보면

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
	// ...
	MSG msg;
	// ...
	hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_TESTWINDOW));
	// ...
	while (GetMessage(&msg, NULL, 0, 0))	{
		if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}
	return (int) msg.wParam;
}

요렇게 되어있다.그럼 다시 GetMessage()는 어디서 msg를 가져오는 것일까...

MSDN에 보면This function retrieves a message from the calling thread's message queue and places it in the specified structure. 라고 되어있다.
즉,쓰레드의 메세지 큐에서 가져오는 것이다.

그런데, 하나의 UI 쓰레드(메세지큐를 가질 수 있는 쓰레드)는 여러 개의 윈도우 프로시저를 가질 수 있는데, 어떤 윈도우 프로시저에 이 메세지를 전달 해야 할까...

GetMessag()는에 MSG구조체를 채워 주는데, 이 구조체가
typedef struct {
    HWND hwnd;   
    UINT message;   
    WPARAM wParam;   
    LPARAM lParam;   
    DWORD time;   
POINT pt;
} MSG, *PMSG;
요렇게 생겨 먹었다.

MSDN에 보면 hwnd는 Handle to the window whose window procedure receives the message. hwnd is NULL when the message is a thread message. 라고 되어 있다.

즉, 이 메세지를 처리할 윈도우 프로시저를 가지고 있는 윈도우의 핸들을 GetMessage()에서 알게 되고, 핸들을 알게 된다면 그 핸들 클래스의 윈도우 프로시저를 알 수 있다. 따라서 DispatchMessage()에서 해당 윈도우를 처리할 등록된 윈도우 프로시저로 디스패치 해 줄수 있게 된다.



1. Control

일단 컨트롤의 경우는, (컨트롤 이라는 의미를 미리 등록된, 즉, RegisterClass에 등록된 클래스 라고 생각 했다) 자신을 생성한 윈도우(부모 윈도우)에 어떤 이벤트가 발생했음을
PostMessage(hParentWindow, WM_COMMAND, MAKEWORD(ID, NOTIFICATION_CODE), handle);
로 알리는 것일까?

그런데!!!
button클래스를 생각해 보면, 모든 버튼은 이 button 클래스로부터 만들어 지게 되는데, 그렇다면 윈도우즈에 있는 모든 button은 button클래스에 등록된 하나의 윈도우 프로시저에서 처리된다는 것일까?
클래스를 구조체를 보면,

typedef struct {
    UINT cbSize;
    UINT style;
    WNDPROC lpfnWndProc;
    int cbClsExtra;
    int cbWndExtra;
    HINSTANCE hInstance;
    HICON hIcon;
    HCURSOR hCursor;
    HBRUSH hbrBackground;
    LPCTSTR lpszMenuName;
    LPCTSTR lpszClassName;
    HICON hIconSm;
} WNDCLASSEX, *PWNDCLASSEX;

즉, 인스턴스와 프로시저가 같이 등록되게 되어있다. 하지만, 인스턴스마다 다른 윈도우 프로시저를 사용한다는 것은 말이 안된다. 같은 일을 하는 윈도우인데 프로시저가 다름 이상하니깐... 그렇다면?

spy로 button클래스로 생성한 버튼의 윈도우 프로시저와 인스턴스핸들 값을 보면... 어떤 인스턴스이던지 프로시저의 값은 같고, 인스턴스 핸들의 값은 항상 0 이다. ... 쏙았다.... 역시 spy가 짱... -_-;

인스턴스 핸들의 값이 0이라는 것은 어떤 전역적인 인스턴스, 혹은 인스턴스와 상관이 없다는 것일 것이고(이것이 운영체제에서 관리되는어떤 것이라고 생각한다.) 이것은 컨트롤이 받는 메시지 자체가 해당 인스턴스의 메세지 큐와는 관련이 없다는 것을 의미하는 것일것이다. 이렇게 커널에서 관리되는 메세지큐와 프로시저라면, 굳이 Notification을 보내기 위해 PostMessage()api를 사용할 필요는 없을 것이다. 해당 UI 쓰레드의 메세지큐에 집어 넣어 놓으면 펌핑 해 갈테니...(api로 집어 넣었을 수도 있지만, 어차피 운영체제에서 관리 된다면 api가 아닌 좀더 빠른 무언가로 큐에 집어 넣어 놓지 않을까...)

결론을 얘기하면.
1. 버튼이 눌린다.
2. 이 눌림 메세지 자체는 인스턴스와 상관없이 커널단의 메세지 큐에서 관리될 것이다.
3. 해당 메세지 큐에서 메세지가 등록된 윈도우에 해당하는 윈도우 프로시저에 의해 처리된다.
4. 그 컨트롤의 부모 윈도우(hwndParent)가 속한 UI 쓰레드의 메세지 큐에 WM_COMMAND 메세지를 넣어 놓는다.
5. 이제 hwndParent의 UI 쓰레드에서 메세지를 펌핑한다.
6. DispatchMessage()에서 hwndParent가 등록되어 있는 WNDPROC로 이 메세지를 Dispatch한다.
7. 해당 WNDPROC에서 WM_COMMAND를 받는다.

아마도. -_-;

그렇다면, 사용자가 컨트롤을 흉내내는 경우라면,
윈도우 클래스를 등록하고, 이 클래스로 윈도우를 만들고, 클래스에 등록된 윈도우 프로시저에서 2-4의 과정에 해당하는 과정을 해 주면 될 것이다.
만약 버튼의 클릭 되었음을 알린다면, 해당 윈도우 프로시저의 WM_LBUTTONUP 메세지에서
PostMessage(hParentWindow, WM_COMMAND, MAKEWORD(ID, NOTIFICATION_CODE), handle);
해 주면 될 것이다.



2. Menu, Accelerator

Menu는 WM_COMMAND가 전달 되는 것은, 마우스로 메뉴의 아이템을 클릭 했을 때 이고,
Aeccelerator는 WinMain에서 TranslateAccelerator(msg.hwnd, hAccelTable, &msg) 된 정보 이다.

일반적으로 ALT+F(Nmemonic)로 메뉴 자체가 선택되는 것은 이와는 다른 메커니즘인 듯 하다. 나중에 시간 나면 알아봐야지...


: