티스토리로 이전 했습니다. 사는 이야기




카테고리도 없고,

Syntax Highlight 도 적용이 안되고.

서식 기능도 없고.




도통 답이 없는 이쁘기만 한 이글루스에서 티스토리로 이동 했습니다.

C++ 의 새로운 표준과, MFC, Python, 그리고 프로그래머의 생산성을 향상시키는 여러 도구들에 대해서 알고싶다면


http://anster.tistory.com 로!!!














C++ : 가상 함수 테이블 C/C++ 마을


C++ 은 다형성, Polymophism 을 지원하기 위해서 virtual 키워드를 제공한다.

부모 클래스 내에서 virtual 로 선언된 함수는 자식 클래스에서 재정의 될 수 있다.

재정의 되지 않는다면, 호출시 부모 클래스의 함수가 호출되고, 재정의 된다면 호출 시 자식 클래스의 함수가 호출된다.

Base, Derived 두개의 클래스가 있다고 가정 해 보자. Base 클래스는 Derived 클래스의 부모 클래스다.  

각각 start 란 virtual 함수를 가지고 있다고 하자. virtual 함수는 재정의(Override) 될 것이다.


Base* pBase = new Derived();

pBase->start();


컴파일러는 pBase 가 Derived 타입인지, Base 타입인지 알지 못한다. 

즉, start 를 호출하는 런타임 시점까지는 Derived 클래스의 재정의된 start 가 호출될지 부모 클래스의 start 가 호출될지 모른다.

그렇다면, 프로그램은 어떻게 런타임에 자신의 클래스에 맞는 함수를 찾아가서 호출하는 것일까?

답은 가상 함수 테이블(vftbl) 에 있다. 


클래스 내에 virtual 함수가 존재하고, 이 virtual 함수가 상위 클래스의 함수를 override 하거나, 하위 클래스에 의해 override 된다면 컴파일러는 클래스 내에 이 함수에 대한 가상 함수 테이블을 생성한다.

아래의 그림을 보자. High, Middle, Low 클래스가 있고, 상속 관계이며 오버라이딩된 함수를 가지고 있다. (누르면 커짐)






아래는 소스코드.



01: // Sample.cpp : 콘솔 응용 프로그램에 대한 진입점을 정의합니다.
02: //
03:
04: #include "stdafx.h"
05:
06: #include <iostream>
07:
08: class High
09: {
10: int a;
11:
12: public:
13: virtual void func1(void)
14: {
15: std::cout << "High::func1" << std::endl;
16: }
17:
18: virtual void func2(void)
19: {
20: std::cout << "High::func2" << std::endl;
21: }
22: };
23:
24: class Middle : public High
25: {
26: int b;
27:
28: public:
29: virtual void func1(void)
30: {
31: std::cout << "Middle::func1" << std::endl;
32: }
33:
34: virtual void func3(void)
35: {
36: std::cout << "Middle::func3" << std::endl;
37: }
38: };
39:
40: class Low : public Middle
41: {
42: int c;
43:
44: public:
45: virtual void func1(void)
46: {
47: std::cout << "Low::func1" << std::endl;
48: }
49:
50: virtual void func3(void)
51: {
52: std::cout << "Low::func3" << std::endl;
53: }
54:
55: virtual void func4(void)
56: {
57: std::cout << "Low::func4" << std::endl;
58: }
59: };
60:
61: int _tmain(int argc, _TCHAR* argv[])
62: {
63:
64: High* cHigh = new High();
65: cHigh->func1();
66:
67: Middle* cMiddle = new Middle();
68: cMiddle->func3();
69:
70: Low* cLow = new Low();
71: cLow->func3();
72:
73: return 0;
74: }
75:


클래스 내에 가상 함수 테이블이 포함되면, 클래스의 첫 4바이트에 가상 함수 테이블 주소가 추가된다. 아래는 VC에서 확인한 스크린 샷.




위의 조사식에서 Low 클래스의 인스턴스인 cLow 의 메모리 주소는 0x00397bd8 임을 알 수 있다. 이 메모리 값을 확인 해 보면

아래와 같이 0x00447941 이다. 



이 값은 Low 클래스의 가상 함수 테이블의 주소 0x417944 와 같은 값이다. 아래의 스크린샷에서 확인할 수 있다.



Low 클래스의 vftable 의 주소는 0x00417944 다. 이러한 가상 함수 테이블은 인스턴스마다 존재하면 메모리가 낭비되므로 테이블을 클래스 정보로 두고 각각의 인스턴스는 포인터 값으로 이 가상 함수 테이블을 가리키고 있다.

위의 조사식 스크린샷을 다시 한번 보자. 

Low 클래스는 func1, func2, func3, func4 를 가지고 있다.

func1 은 High 클래스와 Middle 클래스의 func1 을 오버라이딩 한 것이고

func2 는 Low 클래스 내에는 없지만, 상속받은 High 클래스 내에 존재하므로 호출 할 수 있다.

func3 은 Middle 클래스의 func3 을 오버라이딩 한 것이다.

func4 는 가상함수로 선언되긴 했지만, 실제 오버라이딩 한 함수도 아니고, 오버라이딩 된 것도 아니므로 가상함수 테이블에 포함되지 않는다.

조사식에서는 cLow 의 가상 함수 테이블에 2개의 값, Low::func1 과 High::func2 밖에 안나왔지만, 사실 Middle 클래스의 func3 함수를 오버라이딩한 Low::func3 도 가상 함수 테이블에 존재해야 한다. 오버라이딩 했기 때문이다.

실제로 존재하는지 확인 해 보자.



위의 그림은 cLow 클래스의 첫 4바이트가 가리키는 0x00417944 를 확인 한 것이다.

보면 알겠지만, 가상함수 테이블에 3개의 함수가 존재한다. 

Low::func1, High::func2, Low::func3. 조사식과 비교해 보면 올바른 주소임을 확인 할 수 있다.







2011. 08. 18

By. Anster

MFC 메시지 맵 3 : 메시지맵을 사용해 윈도우 프로시저(WndProc) 자동화 MFC 마을


지난시간에는 WIn32 API 를 이용한 윈도우 응용프로그램을 클래스화 했다.

MFC 메시지 맵 2 : 클래스를 사용해 프로그램을 구조화




하지만, 이 프로그램의 문제점은 사용되는 윈도우 메시지가 추가 될 때마다 윈도우 프로시저(WndProc)에 case by case 로 코딩해 주어야 한다는 것.

이러한 문제점을 이번시간에는 MFC 에서 사용하는 메시지 맵 구조를 사용해 해결 해 보자.


우선, 기본적인 변수들 즉, HWND, MSG, WNDCLASSEX 를 포함하고 있고, InitInstance, Run, ExitInstance

등의 멤버 함수들을 가지고 있어 이 클래스만으로도 응용프로그램을 구성 할 수 있는 CObject 클래스를 가정하자.

윈도우 메시지에 대응되는 핸들러는 이 클래스를 상속받은 CView 클래스 내에서 구현한다.


( 누르면 크게보인다. )


CView 클래스는 static 멤버로 메시지맵, 즉 어떤 윈도우 메시지와, 어떤 핸들러가(WM_CREATE - OnCreate) 대응되는지에 대한 맵을 가지고 있다.


아래는 CObject.h

01: 
02: #include <windows.h>
03: #include <tchar.h>
04:
05: #ifndef SIMPLE_COBJECT_H
06: #define SIMPLE_COBJECT_H
07:
08: LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam);
09:
10: class CObject
11: {
12: protected:
13: static TCHAR szAppName[];
14: HWND m_hwnd;
15: MSG m_msg;
16: WNDCLASSEX m_wndclass;
17:
18: public:
19: void InitInstance(HINSTANCE hInstance, LPSTR szCmdLine, int iCmdShow);
20: void Run(void);
21: WPARAM ExitInstance(void);
22: };
23:
24:
25:
26: #endif

아래는 CObject.cpp


01: 
02: #include "CObject.h"
03: #include "CView.h"
04:
05: TCHAR CObject::szAppName[] = _T("Sample Program");
06:
07: extern CView app;
08:
09: static CViewFuncPointer fpCViewGlobal;
10:
11:
12: void CObject::InitInstance(HINSTANCE hInstance, LPSTR szCmdLine, int iCmdShow)
13: {
14: m_wndclass.cbSize = sizeof(m_wndclass);
15: m_wndclass.style = CS_HREDRAW | CS_VREDRAW;
16: m_wndclass.lpfnWndProc = WndProc;
17: m_wndclass.cbClsExtra = 0;
18: m_wndclass.cbWndExtra = 0;
19: m_wndclass.hInstance = hInstance;
20: m_wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
21: m_wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
22: m_wndclass.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
23: m_wndclass.lpszMenuName = NULL;
24: m_wndclass.lpszClassName = szAppName;
25: m_wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
26:
27: RegisterClassEx ( &m_wndclass );
28:
29: m_hwnd = CreateWindow ( szAppName, _T("Simple"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
30: CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL );
31:
32: ShowWindow(m_hwnd, iCmdShow);
33: UpdateWindow(m_hwnd);
34: }
35:
36: void CObject::Run(void)
37: {
38: while ( GetMessage(&m_msg, NULL, 0, 0))
39: {
40: TranslateMessage(&m_msg);
41: DispatchMessage(&m_msg);
42: }
43: }
44:
45: WPARAM CObject::ExitInstance(void)
46: {
47: return m_msg.wParam;
48: }
49:
50: int WINAPI WinMain( __in HINSTANCE hInstance, __in_opt HINSTANCE hPrevInstance, __in LPSTR lpCmdLine, __in int nShowCmd )
51: {
52: app.InitInstance(hInstance, lpCmdLine, nShowCmd);
53: app.Run();
54: return app.ExitInstance();
55: }
56:
57: LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
58: {
59: UINT uMessage = 0;
60:
61: while ( CView::messageMap[uMessage].iMsg != 0 )
62: {
63: if ( iMsg == CView::messageMap[uMessage].iMsg )
64: {
65: fpCViewGlobal = CView::messageMap[uMessage].fp;
66: (app.*fpCViewGlobal)();
67: return 0;
68: }
69:
70: uMessage++;
71: }
72:
73: return DefWindowProc(hwnd, iMsg, wParam, lParam);
74: }

CObject.cpp 내부에 WinMainWndProc 를 넣고, CView.cpp 에 전역으로 선언된 CView app; 인스턴스를 extern 키워드를
이용해서 얻었다. 주의해야할 부분은 WndProc 인데, CView 내부에 존재하는 메시지맵을 사용해 WndProc 를 자동화 함으로써

프로그램에 이벤트 핸들러가 추가되더라도 WndProc 를 건드릴 필요가 없어졌다.

아래는 CView.h 의 소스다.


01: 
02: #ifndef SIMPLE_CVIEW_H
03: #define SIMPLE_CVIEW_H
04:
05: #include <windows.h>
06: #include <tchar.h>
07:
08: class CView;
09:
10: typedef void (CView::*CViewFuncPointer)();
11: typedef struct tagMessageMap {
12: UINT iMsg;
13: CViewFuncPointer fp;
14: } MessageMap;
15:
16: class CView : public CObject
17: {
18:
19: public:
20: //Message Map
21: static MessageMap messageMap[];
22:
23: public:
24: //Event Handler
25: void OnCreate(void);
26: void OnDraw(void);
27: void OnDestroy(void);
28: void OnLButtonDown(void);
29: };
30:
31:
32: #endif







아래는 CView.cpp 의 소스


01: #include "CObject.h"
02: #include "CView.h"
03:
04: CView app;
05:
06: MessageMap CView::messageMap[] = {
07: {WM_CREATE, &CView::OnCreate},
08: {WM_PAINT, &CView::OnDraw},
09: {WM_DESTROY, &CView::OnDestroy},
10: {WM_LBUTTONDOWN, &CView::OnLButtonDown},
11: {0, NULL }
12: };
13:
14: void CView::OnCreate()
15: {
16:
17: }
18:
19: void CView::OnDraw()
20: {
21: PAINTSTRUCT ps;
22: RECT rect;
23: HDC hdc;
24:
25: hdc = BeginPaint(m_hwnd, &ps);
26: GetClientRect(m_hwnd, &rect);
27: DrawText(hdc, _T("Hello Windows!"), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER );
28: EndPaint(m_hwnd, &ps);
29: }
30:
31: void CView::OnDestroy()
32: {
33: PostQuitMessage(0);
34: }
35:
36: void CView::OnLButtonDown(void)
37: {
38: MessageBox(m_hwnd, _T("Button Down!"), _T("Hello"), MB_OK);
39: }

CObject.cpp 를 보면 알겠지만, 윈도우 프로시저와 메인 함수가 CObject 내부에 존재하므로 더이상 WndProc WinMain 신경쓸 필요가 없다.

사용자는 단지 CView.h 에 원하는 핸들러를 선언하고, CView.cpp 메시지맵에 메시지와 핸들러를 추가한 후 CView.cpp 에 구현하기만 하면 된다.


다음시간에는 매크로를 이용해서 메시지맵을 더욱 더 간단하게 표현해 보자.



참고 - MFC 구조와 원리 p.218 ~ 223

부스트 1.47 빌드 for x86 C/C++ 마을



Get Boost



1. 부스트 폴더(이하 %BOOST%) 로 이동  

2. Visual Studio 명령 프롬프트에서 bootstrap.bat 로 bjam 생성

3. bjam --toolset=msvc-10.0 --without-mpi --without-python --build-type=complete install -j 2


C:\Boost 를 확인하면 된다.

2011년 8월 3주 지난 이야기



광복절 및 UFG

MFC 메시지맵 구현 방식 이해




1 2 3 4 5 6 7 8 9 10 다음


시계

라운드 시계

위키피디아