아래 발췌한 부분 중에 다음이 좀 잘못된 듯 하다.
1. 예제 소스에 InitApplication을 InitInstance로 수정해야 한다.
2. AfxHookWindowCreate함수의 기능을 잘못 설명한 듯 하다.
윈도우 프로시저를 후킹하고, _AFX_THREAD_STATE 전역변수에 해당 윈도우 객체 포인터 값를
입력함으로써 위 후킹과정이 이니셜라이즈되었음을 알려준다.
void AFXAPI AfxHookWindowCreate(CWnd* pWnd)
{
// MFC sets a trhead-global m_pWndInit, which points
// to the window being created. This is the only way
// CWnd::CreateEx and AfxHookWindowCreate can communicate
// with the hook function, SetWindowsHookEx does not let
// you pass an argument for the hook function.
//
_AFX_THREAD_STATE* pThreadState =
_afxThreadState.GetData();
// If this window is already hooked for creation, ignore.
// In theory, this should not happen.
if (pThreadState->m_pWndInit == pWnd)
return;
if (pThreadState->m_hHookOldCbtFilter == NULL) {
// Create the hook:
pThreadState->m_hHookOldCbtFilter = ::SetWindowsHookEx(WH_CBT,
_AfxCbtFilterHook, NULL, ::GetCurrentThreadId());
if (pThreadState->m_hHookOldCbtFilter == NULL)
AfxThrowMemoryException();
}
// Below are sanity checks
ASSERT(pThreadState->m_hHookOldCbtFilter != NULL);
ASSERT(pWnd != NULL);
ASSERT(pWnd->m_hWnd == NULL); // only do once
ASSERT(pThreadState->m_pWndInit == NULL); // hook not already in progress
pThreadState->m_pWndInit = pWnd; // set the m_pWndInit global !!
}
///////////////////////// 발 췌 내 용 ///////////////////////////////////
1. 예제 소스
// MainFrm.hpp
#include <afxwin.h>
class CMainFrame: public CFrameWnd
{
public:
virtual BOOL PreCreateWindow(CREATESTRUCT &cs);
};
// MainFrm.cpp
#include "MainFrm.hpp"
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT &cs) //CWnd의 PreCreateWindow의 가상함수를 재정의한 것이다.
{
if (!CFrameWnd::PreCreateWindow(cs)) //클래스 등록
return FALSE;
cs.style &= ~WS_THICKFRAME & ~WS_MAXIMIZEBOX; // 크기 조절 기능과 최대화 버튼 제거
cs.dwExStyle |= WS_EX_TOPMOST; // 윈도우가 비활성화 되어 있을 때도 가장 앞에 나오게
cs.x = 0; // 윈도우 위치 지정
cs.y = 0;
cs.cx = 640; // 윈도우 크기 지정
cs.cy = 480;
return TRUE;
}
// Simple.cpp
#include "Simple.cpp"
BOOL CSimpleApp::InitApplication()
{
CMainFrame *pFrame = new CMainFrame; // CFrameWnd 객체 할당
m_pMainWnd = pFrame;
pFrame->Create(NULL, "SImple"); // 프레임 윈도우 생성
pFrame->ShowWindow(SW_SHOW); // 화면에 윈도우 출력
pFrame->UpdateWindow(); // 윈도우 갱신
return TRUE;
}
CSimpleApp app;
2 Main window 생성 과정
2.1 프레임 윈도우 객체 생성
MFC 프로그램의 기본 흐름에서 MFC 프로그램은 일반적으로 프로그램 시작시 CWinApp::InitInstance를 재정의해서 그곳에서 프레임 창을 생성하는 코드를 만든다고 설명했었다.
샘플 코드에서도 InitInstance함수를 정의해서 윈도우를 생성시키고 있다.
먼저 프레임 창을 만들기 위해 CFrameWnd 객체가 생성되어야 한다. 예제에서는 CFrameWnd 객체를 상속받아 new호출을 통해 CMainFrame 객체를 선언했다.
BOOL CSimpleApp::InitApplication()
{
CMainFrame *pFrame = new CMainFrame; // CFrameWnd 객체 할당
m_pMainWnd = pFrame;
.....
return TRUE
}
CMainFrame이 상속받은 CFrameWnd는 CWnd의 파생 클래스로 프레임 창을 위한 기능을 제공하는 클래스이다.
프레임 창의 object를 관리할 때는 CWinThread::m_pMainWnd가 사용된다. CSimpleApp::InitInstance에서 CFrameWnd를 동적 메모리에 할당하고 그 포인터를 CWinThread::m_pMainWnd에 저장해야 한다.
CWinThread내의 멤버 변수인 m_pMainWnd는 다음과 같이 정의되어 있다.
class CWinThread : public CCmdTarget
{
public:
CWnd* m_pMainWnd;
.........
};
프레임 윈도우 객체를 생성한 후 왜 CWinThread::m_pMainWnd에 그 객체를 포인트 해줘야 하는가.
만일 이 멤버 변수에 프레임 윈도우의 object를 포인트하지 않고 계속 NULL값을 가진다면 CWinApp::Run에 의해 AfxPostQuitMessage가 호출되고 결과적으로 프로그램이 종료된다.
// Main running routine until application exits
int CWinApp::Run()
{
if (m_pMainWnd == NULL && AfxOleGetUserCtrl()) // 프레임 창의 object가 NULL인가 체크
{
// Not launched /Embedding or /Automation, but has no main window!
TRACE0("Warning: m_pMainWnd is NULL in CWinApp::Run - quitting application.\n");
AfxPostQuitMessage(0); // 프로그램 종료
}
return CWinThread::Run();
}
MFC내부에서도, 유저 레벨에서도 어플리케이션을 종료하고자 할 때 m_pMainWnd 변수의 포인터를 해제시켜 주면 된다.
프레임 창의 객체를 메모리에 할당했으면, 그 다음에 프레임 창을 만들고, 나타내고, 갱신할 때는 각각 CFrameWnd::Create, CFrameWnd::ShowWindow, CWnd::UpdateWindow 함수들을 사용한다.
2.2 프레임 윈도우 생성
2.2.1 CFrameWnd::Create & CWnd::CreateEx
프레임 윈도우를 생성하는 함수는 CFrameWnd::Create이다. CFrameWnd는 CWnd 의 하위 클래스로 내부 멤버인 HWND 를 생성해야 실제 윈도우로 존재 할 수 있다.
BOOL CSimpleApp::InitApplication()
{
CMainFrame *pFrame = new CMainFrame; // CFrameWnd 객체 할당
m_pMainWnd = pFrame;
pFrame->Create(NULL, "Simple");
.....
return TRUE;
}
<CFrameWnd::Create 함수에 대해 특별히 지정하지 않는다면 세번째 인자부터는 디폴트 값이 지정된다.>
CFrameWnd::Create의 첫째 파라미터에는 등록된 WNDCLASS의 이름을 지정한다. 이 파라미터에 NULL을 지정하는 것은 MFC 내부에 미리 정의된 디폴트 CFrameWnd 속성을 사용한다는 의미이다. 두번째 인자에는 창 제목을 지정해 주면 된다.
자, MFC 내부에 CFrameWnd::Create는 어떻게 구현되어 있을까
BOOL CFrameWnd::Create(LPCTSTR lpszClassName, // AfxRegisterWndClass에 등록한 윈도우 클래스 이름
LPCTSTR lpszWindowName, // 윈도우 제목
DWORD dwStyle, // 윈도우 스타일
const RECT& rect, // 화면에 출력되는 윈도우의 위치 및 크기
CWnd* pParentWnd, // 생성시키는 윈도우가 차일드라면 부모 윈도우 표기. 보통은 this를 지정, 프레임 창이 부모 창이 된다.
LPCTSTR lpszMenuName, // 메뉴가 있다면 메뉴 이름 지정
DWORD dwExStyle, // 윈도우 확장 스타일
CCreateContext* pContext) // CCreateContext 구조체 포인터
{
HMENU hMenu = NULL;
if (lpszMenuName != NULL)
{
// load in a menu that will get destroyed when window gets destroyed
HINSTANCE hInst = AfxFindResourceHandle(lpszMenuName, RT_MENU);
if ((hMenu = ::LoadMenu(hInst, lpszMenuName)) == NULL) <--- 메뉴 로딩
{
TRACE0("Warning: failed to load menu for CFrameWnd.\n");
PostNcDestroy(); // perhaps delete the C++ object
return FALSE;
}
}
m_strTitle = lpszWindowName; // save title for later
if (!CreateEx(dwExStyle, lpszClassName, lpszWindowName, dwStyle, <--- 실제 창을 생성하는 함수
rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
pParentWnd->GetSafeHwnd(), hMenu, (LPVOID)pContext))
{
TRACE0("Warning: failed to create CFrameWnd.\n");
if (hMenu != NULL)
DestroyMenu(hMenu);
return FALSE;
}
return TRUE;
}
CFrameWnd::Create의 인자에 메뉴 이름을 지정해 주었다면 메뉴를 로딩하고 그리고 그 다음 작업으로 CWnd::CreateEx를 호출하고 있다. (CreateEx함수는 CWnd 클래스 멤버 함수이다.)
실제로 윈도우를 생성하는 로직은 CWnd::CreateEx라는 것을 알 수 있다.
CWnd::CreateEx 함수 코드를 살펴보자.
BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName,
LPCTSTR lpszWindowName, DWORD dwStyle,
int x, int y, int nWidth, int nHeight,
HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam)
{
// allow modification of several common create parameters
CREATESTRUCT cs;
cs.dwExStyle = dwExStyle;
cs.lpszClass = lpszClassName;
cs.lpszName = lpszWindowName;
cs.style = dwStyle;
cs.x = x;
cs.y = y;
cs.cx = nWidth;
cs.cy = nHeight;
cs.hwndParent = hWndParent;
cs.hMenu = nIDorHMenu;
cs.hInstance = AfxGetInstanceHandle();
cs.lpCreateParams = lpParam;
if (!PreCreateWindow(cs))
{
PostNcDestroy();
return FALSE;
}
AfxHookWindowCreate(this); // _AFX_THREAD_STATE::m_pWndInit 에 생성된 윈도우 등록
HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass,
cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,
cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);
if (!AfxUnhookWindowCreate())
PostNcDestroy(); // cleanup if CreateWindowEx fails too soon
if (hWnd == NULL)
return FALSE;
ASSERT(hWnd == m_hWnd); // should have been set in send msg hook
return TRUE;
}
win32 API로 윈도우를 생성하는 과정과 별반 다르지 않다.
먼저 생성하려는 윈도우의 속성 정보를 CREATESTRUCTURE 구조체에 채워 넣고 CWnd::PreCreateWindow 함수를 거쳐 윈도우 생성 API인 ::CreateWindowEx를 호출하고 있다.
AfxHookWindowCreate는 윈도우 생성을 감시하고 있다가 윈도우가 생성이 되면 윈도우 핸들과 관련 객체를 테이블에 등록하는 기능을 하는 함수이다.
2.2.2 CFrameWnd::PreCreateWindow & CMainFrame::CreateWindow
CWnd::PreCreateWindow는 가상함수로서 유저가 재정의해서 생성하고자 하는 프레임 윈도우의 기본 속성을 지정할 수 있다. 예제에서는 PreCreateWindow를 재정의해서 thick frame과 타이틀바에서 최대화 버튼을 제거하고, 윈도우가 비활성화 되어 있을 때도 가장 앞에 나오게 하는 WS_EX_TOPMOST를 확장 스타일에 추가해 주었다.
이 함수의 리턴값은 TRUE와 FALSE가 있는데 TRUE를 리턴하면 ::CreateWindowEx가 호출되어 윈도우가 생성 되지만, FALSE를 리턴하면 창 생성이 중단되고 CWnd::PostNcDestroy가 호출되어 프로그램이 종료된다.
주의할 것은 CMainFrame::PreCreateWindow에서 CFrameWnd::PreCreateWindow를 호출하지 않으면 프레임 윈도우가 정상적으로 생성되지 못한다는 것이다. 이는 CFrameWnd::PreCreateWindow 내부에 윈도우 클래스를 디폴트 클래스 스타일로 정의하고 윈도우에 등록(register)하는 구현이 있기 때문이다.
그럼 내부로 들어가서 확인해 보자.
BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT& cs)
{
if (cs.lpszClass == NULL)
{
// 윈도우 클래스를 지정하지 않았다면 디폴트 스타일로 지정해서 등록한다.
VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));
cs.lpszClass = _afxWndFrameOrView; // COLOR_WINDOW background
}
if ((cs.style & FWS_ADDTOTITLE) && afxData.bWin4)
cs.style |= FWS_PREFIXTITLE;
if (afxData.bWin4)
cs.dwExStyle |= WS_EX_CLIENTEDGE;
return TRUE;
}
위에서 AfxDeferRegisterClass가 윈도우 클래스를 등록하는 함수를 가지고 있다. 실제로는 전처리기에 의해 다른 함수의 이름으로 명명되어 있다.
#define AfxDeferRegisterClass(fClass) AfxEndDeferRegisterClass(fClass)
2.2.3 AfxRegisterWndClass
CFrameWnd::PreCreateWindow에서 호출되는 함수는 실제로 AfxEndDeferRegisterClass이니 더 들어가보자.
BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister)
{
// mask off all classes that are already registered
AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
fToRegister &= ~pModuleState->m_fRegisteredClasses; // 모듈에 등록된 윈도우 클래스가 있는지 체크
if (fToRegister == 0)
return TRUE;
LONG fRegisteredClasses = 0;
// common initialization
WNDCLASS wndcls;
memset(&wndcls, 0, sizeof(WNDCLASS)); // start with NULL defaults
wndcls.lpfnWndProc = DefWindowProc;
wndcls.hInstance = AfxGetInstanceHandle();
wndcls.hCursor = afxData.hcurArrow;
....................
if (fToRegister & AFX_WNDFRAMEORVIEW_REG)
{
// SDI Frame or MDI Child windows or views - normal colors
wndcls.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
wndcls.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
if (_AfxRegisterWithIcon(&wndcls, _afxWndFrameOrView, AFX_IDI_STD_FRAME))
fRegisteredClasses |= AFX_WNDFRAMEORVIEW_REG;
}
.....................
// save new state of registered controls
pModuleState->m_fRegisteredClasses |= fRegisteredClasses;
.....................
// must have registered at least as mamy classes as requested
return (fToRegister & fRegisteredClasses) == fToRegister;
}
이 함수의 인자로 넘어오는 것은 등록하고자 하는 윈도우 스타일의 종류를 나타내는 플래그로 CFrameWnd::PreCreateWindow에서 이 함수를 호출할 때 넘겨주는 AFX_WNDFRAMEORVIEW_REG는 프레임과 뷰를 가지고 있는 표준적인 스타일로 윈도우 클래스를 등록한다라는 의미이다. 이 플래그에는 MDI 프레임 윈도우, control bar, three view, list view등의 콘트롤 기반의 윈도우 스타일등을 지정할 수 있다.
코드를 win32 API에서와 동일하게 윈도우 클래스 구조체(WNDCLASS)에 윈도우 속성 정보(윈도우 프로시저, 인스턴스 핸들, 커서등)를 지정하고 _AfxRegisterWithIcon 함수를 호출하고 있다. 아마도 이 함수 내부에서 윈도우 클래스를 등록하는 코드가 있다는 것을 추측할 수 있을 것이다.
실제로 찾아 들어가 보자.
AFX_STATIC BOOL AFXAPI _AfxRegisterWithIcon(WNDCLASS* pWndCls,
LPCTSTR lpszClassName, UINT nIDIcon)
{
pWndCls->lpszClassName = lpszClassName;
HINSTANCE hInst = AfxFindResourceHandle(
MAKEINTRESOURCE(nIDIcon), RT_GROUP_ICON);
if ((pWndCls->hIcon = ::LoadIcon(hInst, MAKEINTRESOURCE(nIDIcon))) == NULL)
{
// use default icon
pWndCls->hIcon = ::LoadIcon(NULL, IDI_APPLICATION);
}
return AfxRegisterClass(pWndCls); <--- 윈도우 클래스를 등록
}
아이콘을 로딩하고 AfxRegisterClass 함수를 호출해서 윈도우 클래스를 등록하고 있다.
만약 유저가 CFrameWnd::PreCreateWindow에서 등록하는 디폴트 클래스 스타일을 무시하고 자신만의 윈도우 스타일을 등록하고자 한다면 위의 AfxRegisterWndClass를 사용하면 된다.
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT &cs)
{
if (!CFrameWnd::PreCreateWindow(cs))
return FALSE;
cs.lpszClass = AfxRegisterWndClass(0,
AfxGetApp()->LoadStandardCursor(IDC_IBEAM),
(HBRUSH)::GetStoackObject(LTGRAY_BRUSH),
AfxGetApp()->LoadStandardIcon(IDI_ASTERISK));
return TRUE;
}
2.2.4 ::CreateWindowEx
자, 여기까지가 프레임 창을 생성하기 이전에 윈도우 클래스에 프레임 창의 속성을 지정하고 등록하는 과정이었다. 이렇게 CMainFrame::PreCreateWindow 함수를 거쳐서 윈도우 클래스의 등록을 마쳤다면 이제 윈도우를 생성하는 절차가 남았다.
BOOL CWnd::CreateEx(DWORD dwExStyle, ... )
{
CREATESTRUCT cs;
cs.dwExStyle = dwExStyle;
........
if (!PreCreateWindow(cs)) // 프레임 윈도우 속성 지정 및 윈도우 클래스 등록
{
PostNcDestroy();
return FALSE;
}
AfxHookWindowCreate(this);
HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass,
cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,
cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);
.........
return TRUE;
}
진짜 윈도우를 생성시키는 부분은 win32 API 함수인 ::CreateWindowEx1를 호출하는 부분이다. CREATESTRUCT 구조체의 정보를 바탕으로 메인 프레임 윈도우를 생성하고 있다.
2.3 윈도우 출력 및 갱신
윈도우를 화면에 띄우고 갱신할 때는 각각 CFrameWnd::ShowWindow, CFrameWnd::UpdateWindow 함수들을 사용한다.
BOOL CSimpleApp::InitApplication()
{
CMainFrame *pFrame = new CMainFrame; // CFrameWnd 객체 할당
m_pMainWnd = pFrame;
pFrame->Create(NULL, "SImple"); // 프레임 윈도우 생성
pFrame->ShowWindow(SW_SHOW); // 화면에 윈도우 출력
pFrame->UpdateWindow(); // 윈도우 갱신
return TRUE;
}
이들은 기본적으로 win32 API에서 제공하는 ShowWindow, UpdateWindow와 동일하다고 보면 된다.
사실 처음에 프레임 윈도우를 화면에 띄우는 작업은 ShowWindow 함수 하나만으로도 충분하다. 이 함수에서 WM_PAINT 메시지가 발생해서 화면에 윈도우를 나타내도록 하기 때문이다. 그런데 왜 굳이 화면에 띄우자마자 UpdateWindow 를 통해 화면 갱신이 필요하냐고 물으면 명쾌하게 설명할 수 있는 개발자가 많지 않다.
ShowWindow와 UpdateWindow의 차이점은 UpdateWindow는 윈도우로 하여금 WM_PAINT 메시지 처리를 메시지 루프인 CWinThread::Run에서 처리하도록 대기하지 않고 바로 윈도우 프로시저로 보내서 처리하도록 한다는 것이다. 옛날에 CPU의 처리속도가 그리 빠르지 않던 시절에 ShowWindow를 통해 윈도우를 나타내도록 하면 메시지 큐에 WM_PAINT 메시지를 넣고 그 이전 메시지를 처리할 때까지 기다리는 시간이 길었기 때문에 화면에 윈도우가 뜰 때까지 비교적 오랜 시간을 기다려야 했다. 따라서 프레임 윈도우를 생성해서 화면에 띄우는 순간에는 WM_PAINT 메시지를 바로 윈도우 프로시저에서 처리하도록 해서 윈도우를 빠르게 화면에 출력시키도록 했던 것이다.
그러나 CPU의 사양이 높아진 요즘은 WM_PAINT 메시지를 메시지 큐에 넣고 메시지 루프에서 처리하는 텀이 무시될 정도로 짧기 때문에 프레임 윈도우를 생성할 때 굳이 UpdateWindow 함수를 호출하지 않아도 큰 지장이 없다. 단지 프레임 창을 띄울 때 UpdateWindow를 쓰던 옛날의 코드를 습관적으로 가져다 쓰는 것이라고 보면 된다.
'MFC' 카테고리의 다른 글
클래스 멤버 함수를 윈도우 프로시져로 등록하기 [3/3] (0) | 2008.09.01 |
---|---|
클래스 맴버함수를 콜백함수로 사용하기 [2/3] (1) | 2008.09.01 |
클래스 멤버함수를 윈도우 프로시저로 사용하기 [1/3] (1) | 2008.09.01 |
MFC 팁들 (0) | 2008.09.01 |
Creating New Documents, Windows, and Views (0) | 2008.09.01 |
CWnd::Create 과정.... (1) | 2008.09.01 |
MFC HandlingWindowMessages (1) | 2008.09.01 |