블로그 스킨 변경
사회&문화 2007. 9. 7. 22:11기본 틀을 3단에서 2단으로 변경하여 본문의 공간을 좀더 확보하구요
옆에 있는 광고는 삭제하고
위/아래 있던 광고는 큰거 한개로 변경하였습니다.
예전보다 좀 심플하게 바꾼다고 했는데.. 어떤지요? ㅋㅋ
앞으로도 많이 놀러 오세요~
'분류 전체보기'에 해당되는 글 171건
블로그 스킨 변경사회&문화 2007. 9. 7. 22:11336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
요즘 글을 잘 못 쓰고 있는 상황에서~ 블로그를 조금 개편하였습니다.
기본 틀을 3단에서 2단으로 변경하여 본문의 공간을 좀더 확보하구요 옆에 있는 광고는 삭제하고 위/아래 있던 광고는 큰거 한개로 변경하였습니다. 예전보다 좀 심플하게 바꾼다고 했는데.. 어떤지요? ㅋㅋ 앞으로도 많이 놀러 오세요~ [강좌] WTL로 프로그래밍하기#6 – 컨트롤 사용하기개발 2007. 8. 30. 15:19336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
저자 : hanburn 날짜 : 2007.08.29 환경 : WTL7.5, VS-2003 지난시간까지 정리를 하면(오랜만에 강좌를 쓰니까 기억이 않나서~ ㅋ) 위저드를 통해서 생성되는 코드의 뼈대를 살펴보고, 메시지맵에 대해서 알아 보았습니다. 그럼 다음단계로는 윈도우의 각종 컨트롤들을 사용하는 방법입니다. 컨트롤의 사용법이라고 해서 윈도우의 공용컨트롤들의 각각의 사용법을 알아보는 것이 아니라 MFC와 비교해서 컨트롤들의 일반적인 사용법을 애기하는 것입니다. 그럼 또 편한 말로 계속하겠습니다. WTL에서 제공하는 컨트롤의 종류는 아래와 같습니다. User controls: CStatic, CButton, CListBox, CComboBox, CEdit, CScrollBar, CDragListBox Common controls: CImageList, CListViewCtrl (CListCtrl in MFC), CTreeViewCtrl (CTreeCtrl in MFC), CHeaderCtrl, CToolBarCtrl, CStatusBarCtrl, CTabCtrl, CToolTipCtrl, CTrackBarCtrl (CSliderCtrl in MFC), CUpDownCtrl (CSpinButtonCtrl in MFC), CProgressBarCtrl, CHotKeyCtrl, CAnimateCtrl, CRichEditCtrl, CReBarCtrl, CComboBoxEx, CDateTimePickerCtrl, CMonthCalendarCtrl, CIPAddressCtrl Common control wrappers not in MFC: CPagerCtrl, CFlatScrollBar, CLinkCtrl (clickable hyperlink, available in XP and later) 이중에서 User / Common 그룹의 컨트롤들은 MFC에서 일상적으로 사용하던 것들이고 추가로 CPagerCtrl, CFlatScrollBar, CLinkCtrl등의 유용한 클래스들도 제공이 되고 있다. 윈도우 공용컨트롤들이야 윈도우에서 제공되는 것들을 MFC에서 랩핑한 방식 비슷하게 WTL도 랩핑하여 제공하고 있고 함수이름도 똑같이 제공한다. 다만, 파라미터의 순서가 조금 다른것도 있다. 윈도우 공용 컨트롤들을 사용하는 것은 리소스편집기에서 Dialog에 추가하거나 직접 코드에서 생성하여 사용하는 방법이 있는데, 이 방법에 대해서 자세하게 알아보자. 먼저 리소스에서 추가한 컨트롤을 제어 하려면 아래처럼 하면된다. HWND hWndListBox = GetDlgItem(IDC_LIST1); CListBox list1(hWndListBox); CListBox list2; list2.Attach(hWndListBox); CListBox list3 = GetDlgItem(IDC_LIST1); GetDlgItem으로 HWND를 얻어와서 생성자에 넣어 주거나 Attach 함수를 이용해서 hwnd를 붙여주면 된다. 또는 간편하게 GetDlgItem의 반환값을 직접 대입하는 식으로 써주면 된다. 그런뒤에는 CListBox의 멤버함수를 통해서 원하는 작업을 하면 된다. 여기서 한가지 알고 넘어갈 것은 list1,list2,list3 같은 래퍼클래스의(여기서는 CListBox) 인스턴스가 소멸된다고 해서 실제적인 CListBox가 파괴되는 것은 아니라는 점이다. 따라서 attach한 핸들을 detach할 필요가 없다. 코드에서 직접생성하는 경우는 더 쉽다. 아래는 CButton을 직접 생성하는 경우인데 m_btnTitle을 가지고 해당 버튼을 제어하면 된다. m_btnTitle.Create(m_hWnd, NULL, NULL, WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 0, IDC_NES_WIDGET_BTN_TITLE); 컨트롤을 생성해서 제어를 하였으면 이제는 컨트롤에서 발생하는 이벤트를 처리해야 되는데, 이벤트 처리는 지난시간에 살펴본 메시지 핸들러를 통해서 제어하게 된다. 차일드 컨트롤에서 발생되는 이벤트는 부모위도로 WM_COMMAND, WM_NOTIFY 등의 메시지로 전달이 되므로 이 메시지를 잡아서 처리하면 된다. 간단하게 CListCtrl에서 발생하는 몇가지 이벤트처리 예를 보자 BEGIN_MSG_MAP_EX(CMainDlg) NOTIFY_HANDLER_EX(IDC_LIST, NM_DBLCLK, OnListDblClicked) NOTIFY_HANDLER_EX(IDC_LIST, LVN_ITEMCHANGED, OnListItemchanged) END_MSG_MAP() LRESULT OnListItemchanged(NMHDR* phdr); LRESULT OnListDblClicked(LPNMHDR pnmh); 위에서는 NM 계열의 일반적인 통지메시지와 ListCtrl의 통지메시지인 LVN_ITEMCHANGED를 처리하는 핸들러 함수의 모습이다. (음.. 너무 쉽군 ^^) 다음으로는 컨트롤 관련해서 DDX라는 방법을 MFC에서 제공하기때문에 WTL에서도 DDX를 유사하게 제공하고 있다. 필자는 DDX/DDV 방법을 별로 사용하지 않지만 그래도 간단하게 알아보고 넘어가도록 하자. DDX를 사용하기 위해서는 WTL의 설계 철학대로 CWinDataExchange 템플릿 클래스에서 상속을 받아야 한다. 그리고 메시지맵 비슷한 DDX맵을 추가해주어야 한다. DDX맵은 CWinDataExchange의 DoDataExchange 라는 함수를 재정의(override)하는 역할을 한다. 쉽게말하면 상속을 받았으면 꼭 추가를 해주어야 하는 매크로이다.(매크로대신 직접 코드를 써도 되긴 하지만.. ) 메시지맵에 익숙한 우리에게 DDX맵도 익숙할 수밖에 없다. DDX맵을 직접 보고 이야기 하도록 하자. BEGIN_DDX_MAP(CMainDlg) DDX_CONTROL(IDC_EDIT, m_wndEdit) DDX_TEXT(IDC_EDIT, m_sEditContents) DDX_FLOAT(IDC_EDIT, m_fEditFloat) DDX_FLOAT(IDC_EDIT, m_dEditDouble) DDX_CHECK(IDC_SHOW_MSG, m_bShowMsg) DDX_CONTROL(IDC_TREE, m_wndTree) END_DDX_MAP() 시작과 끝은 DDX_MAP을 나타내고 중간에 있는 여러가지 DDX_로 시작하는 매크로들에 대해서 알아보자. 첫번째인 DDX_CONTROL은 컨트롤아이디에 해당하는 컨트롤과 변수를 연결시켜주는 역할을 한다. 두번째인 DDX_TEXT는 컨트롤과 변수를 연결시켜주는데, 여기서 변수는 CString, BSTR, CComBSTR 등이나 정적으로 할당된 char 배열도 가능하다. DDX_INT는 컨트롤과 정수 변수를 연결시켜주고, DDX_FLOAT는 컨트롤과 실수를 연결시켜준다. DDX_CHECK는 체크박스컨트롤과 int나 bool형 변수와 연결이 된고, DDX_RADIO는 라이오버튼과 int형 변수와 연결을 시켜준다. 이렇게 DDX_ 관련 매크로를 사용하여 컨트롤과 변수를 연결시킨 다음에는, 필요한 시점에 DoDataExchange() 함수를 호출해주면 MFC에서 UpdateData를 호출하는 것처럼 호출시 파라미터를 TRUE로 하면 컨트롤의 데이터가 변수로, 파라미터를 FALSE로 하면 변수의 값이 컨트롤로 전달이 된다. 그리고 여러가지 DDX_ 매크로 중에서 특정 컨트롤의 변수만 update하고 싶을때는 DoDataExchange의 두번째 인자로 컨트롤 아이디를 넣어주면 된다. DoDataExchange(false, IDC_EDIT); 한가지 주의할 점은 DDX_ 매크로중에 잘못되 부분이 있다면 잘못된 부분의 아래에 있는 매크로는 작동이 안 되는 점이다. 여기까지 왔으면 다이얼로그 기반의 윈도우를 만들고 거기에 각종 컨트롤을 생성하고, 컨트롤을 제어하고, 컨트롤에서 발생하는 이벤트를 처리할수 있게 되었다. 이정도만 하면 왠만한 프로그램은 그냥 만들 수가 있을 것이다. ^^ 프로그램을 하다보면 스킨등 입히거나 컨트롤의 기능을 확장하기위해서 subclassing을 해야 뭔가 그럴듯해 보이는 프로그램이 나오게 됩니다. 그래서 다음시간에는 Subclassing에 대해서 알아보도록 하겠습니다. [CxxTest] 사용하려면... 이렇게..개발 2007. 8. 30. 15:09336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
작성일 : 2007.05.09 예전에 작석한 들인데 참 못섰다. 글제주가 이렇게 없다니~ ^^ 원본코드 : 여기서는 원래의 의미와 조금 다른뜻으로 사용하였다. (적당한 표현이 없어서.. ㅎㅎ) 우리가 원하는 동작을 하는 코드이다. 일반적으로 어플, dll 모듈에 해당한다. 테스트코드 : 우리가 만든 코드를 테스트 해주는 코드이다. CxxTest 가 반자동으로 생성해 준다. 반자동이라는 얘기는 기초코드를 규치게 맞게 작성해야 한다는 말이다. 그리고 우리가 테스트할 class 를 위한 테스트코드 헤더파일을 작성해야 한다. 예를들어 CMyCalc 라는 목적클래스가 있으면 CMyCalcTest.h 라는 파일을 생성한다. (여기서 파일이름의 끝이 xxxtest.h로 끝나야만 된다. 이것은 파이선스크립트에서 파일이름을 가지고 테스트코드(C++)를 생성하기 때문다. ) 그리고 CxxTest::TestSuite를 상속받는 클래스를 만들어 준다. 그리고 test로 시작하는 테스트함수를 작성해준다. 글로 길게쓰니까 무슨 말인지 잘 모를 것 같아서 간단한 샘플코드를 보자.
// RawPlayerTest.h class RawPlayerTestSuit : public CxxTest::TestSuite CRawPicture rawPicture; // 규칙 : 시작하기 전에 초기설정하는 함수(이름이 정확해야한다.) // 규칙 : 객체에 대한 종료작업하는 부분 TS_ASSERT_EQUALS( Pixcells == rawPicture.GetPixcells(), TRUE ); void test_111() 위의 샘플을 보면 대충 알수 있다 .
* 테스트함수는 test로 시작해야 한다. (파이선스크립트때문에)
VS2005 에서는 테스트프로젝트가 생겼다던데.. 그것도 한번 봐야 겠다. [CxxTest] 소개 - 단위(유닛)테스트를 위한 C++용 라이브러리개발 2007. 8. 30. 14:50336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
저자 : hanburn 날짜 : 2007.08.30 환경 : VS-2003, CxxTest, Python 단위테스트를 클래스를 작성하고 클래스단위로 테스트 모듈을 만들어 두면, 추후에 수정을 가해가 할 때 조금 편리하게 작업을 할 수 있게 된다. 자바쪽에서는 JUnit이라는 라이브러리를 이용하고 C++ 쪽에서는 이것을 포팅한 CppUnit 이라는 것이 있다. 그런데 CppUnit 이라는 것이 사용하기가 좀 불편한 감이 있다. 그래서 조금 가볍고 사용이 쉬운 CxxTest라는 것을 소개한다. 소스받는곳 : http://cxxtest.sourceforge.net 기본사용법 : http://cxxtest.sourceforge.net/guide.html CxxTest를 사용하기 위해서는 Python을 설치하는게 좋다. 왜냐하면, python 스크립트 중에서 cxxtestgen.py라는 스크립트가 포함되어 있는데, 이 스크립트가 testcode를 만들어 주기 때문이다. 즉, CxxTest와 궁합이 잘 맞는다고 할 수 있다. 컴퓨터에 Python이 설치가 않되었다면 먼저 설치부터 하도록 하자. 파이선받기 : http://www.python.org 파이선은 받아서 설치만 하면 되니까 별다른 설정없이 그냥 설치하면 된다. 필자는 C:\밑에 그냥 설치를 하였다. 설치된 폴더에 보면 cxxtestgen.py라는 파이선 스크립트 파일이 보일 것이다. 내용이 꽤 긴 스크립트 파일이다. 이것이 용도는 파일을 열어보면 알겠지만, 테스트용 소스파일을 생성해주는 역할을 하는데, 옵션이 꽤 된다. 그래서 좀금더 편리하게 하기 위해서 다음과 같은 파이서 스크립트를 더 생성한다. (출처는 인터넷에서 구했다. ) import os def toStr(aList): return ' '.join(aList) def main(): testFiles = [] for eachFile in os.listdir("."): if os.path.isfile(eachFile): lastestPeriod = eachFile.rfind(".") fileName = eachFile[:lastestPeriod] extension = eachFile[lastestPeriod+1:] if fileName.endswith("Test"): print fileName, extension testFiles.append(eachFile)
files = toStr(testFiles) if files == '': print "No Test Files... Make XxxText.xxx..." else : #cmd= "python cxxtestgen.py --runner=ParenPrinter --gui=Win32Gui -o runner.cpp "+toStr(testFiles) cmd= "C:\Python25\python.exe C:\Python25\cxxtestgen.py --runner=ParenPrinter --gui=Win32Gui -o runner.cpp "+toStr(testFiles) print cmd os.system(cmd) if __name__=="__main__": main() 대충보면 폴더에서 파일을 찾아서 확장자 전에 Test 라는 이름이 들어가는 파일을 찾아서 cxxtestget.py 스크립트 파일에 몇가지 옵션과 함께 넘겨서 테스트 소스를 생성해주는 역할이다. 위의 내용을 MakeCxxTest.py 라는 이름으로 파이선이 설치된 폴더(C:\Python25)에 생성을 해 놓는다. 다음으로는 우리가 사용할 VS2003에서 방금 만든 MakeCxxTest.py를 호출(실행)할수 있도록 간단하게 환경설정을 해보도록 하자. 메뉴에서 도구 -> 외부도구를 누르면 뜨는 창에 아래처럼 추가를 한다.
제목을 써주고, 명령과 인수를 각자 환경에 맞추어서 설정해주면 된다. 그리고 오류내용을 보기 쉽게 “출력창사용”에 체크를 해주면 된다. 요렇게 간단하게 설정하면 CxxTest를 사용할 준비가 된된 것이. ^^ 위에보면 Run Cxx Test Runner 라는 명령도 추가를 해주었는데, 별로 필요는 없다. 왜냐하면, 나중에 테스트 프로젝트를 실행 프로젝트로 하고 실행을 하는 것과 같기 때문이다. 개발시에 그냥 실행프로젝트를 설정해놓고 개발하는 것이 더 편리한 것 같더라. VS-2003의 개발환경에 익숙하고 단축키에도 익숙하므로.. 단축키 얘기가 나왔으니, 위의 명령을 단축키로 할당해 놓는 것도 편리하다. UTF간의 변환하는 소스개발 2007. 8. 29. 14:05336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
Unicode Consortium이 제공하는 블로그를 등록시켜 볼까?사회&문화 2007. 8. 20. 11:37336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
이곳을 각종 포털에 등록을 해보려고 하였습니다. 작업관리자가 응답없음을 판별하는 방법개발 2007. 8. 17. 14:53336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
예전에 어디에서가 비슷한 내용을 본적이 있다. 그때 본 내용은 WM_GETICON 이라는 메시지를 보내서 바로 응답이 오면 프로그램이 작동중이고, 응답이 않오면 프로그램이 멈춘상태(응답없음)라는 것이다. 당연히 메시지를 보냈는데, 응답이 않오면 작업관리에서 보이는 것처럼 응답없음 상태로 보는 것이다. 그런에 이와 유사한 기능을 하는 api가 있었다. 바로 IsHungAppWindow 라는 함수이다. 그래서 테트스겸 사용을 해볼려고 하니까 아래처럼 링크에러가 발생한다.
허허 참 이상하다.. 그래서 검색을 조금 해보니까.. user32.dll을 직접 로딩해서 사용해라는 것이다. 허허.. msdn 이거 않되겠구만~ 엉터리 정보를 가지고 있다니.. 그래서 할 수 없이 아래처럼 직접 로딩해서 사용하면 된다. typedef BOOL (WINAPI *PROC_IsHangAppWindow)(HWND);
PROC_IsHangAppWindow IsHangAppWindow; HMODULE hUser32 = GetModuleHandle("user32"); IsHangAppWindow = (PROC_IsHangAppWindow) GetProcAddress(hUser32, "IsHungAppWindow"); BOOL b = IsHangAppWindow(hWnd); 확인결과 잘 작동한다. 리턴값은 응답없음이면 TRUE 를 리턴한다. [강좌] WTL로 프로그래밍하기#5 – Message Map(2)개발 2007. 8. 17. 11:59336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
WTL로 프로그래밍하기#5 – Message Map(2) 저자 : hanburn 날짜 : 2007.08.18 환경 : WTL7.5, VS-2003 지난시간에 WM_XXX 메시지 핸들러를 알아 보았는데, 이번에는 나머지에 대해서 알아보자. 일단 WM_COMMAND 메시지에 해당하는 핸들러로 COMMAND_HANDLER 가 있다. BEGIN_MSG_MAP(CListDlg) COMMAND_HANDLER(IDCANCEL, BN_CLICKED, OnBnClickedCancel) COMMAND_HANDLER(IDOK, BN_CLICKED, OnBnClickedOk) END_MSG_MAP() COMMAND_HANDLER는 3개의 인자를 받는데, 각각 id, code, func 이다. Id는 어떤 컨트롤(혹은 매뉴 id)에서 발생된 것인지를 나타내고, code는 명령의 세부사항이다. 이 code는 각각 컨트롤 마다 다르기 때문에 이것또한 (MFC에 비해서)번거로운 일이다. 일일이 msdn을 참고해야 하기 때문이다. Win32로 코딩을 했던 사람들은 익숙하겠지만, MFC 개발자에게는 낯설 수가 있다. COMMAND_HANDLER의 정의를 한번 보도록 하자. #define COMMAND_HANDLER(id, code, func) \ if(uMsg == WM_COMMAND && id == LOWORD(wParam) && code == HIWORD(wParam)) \ { \ bHandled = TRUE; \ lResult = func(HIWORD(wParam), LOWORD(wParam), (HWND)lParam, bHandled); \ if(bHandled) \ return TRUE; \ } 별로 특별한 것인 없다. ㅋㅋ 유사한 것으로는 COMMAND_ID_HANDLER와 COMMAND_CODE_HANDLER가 있는데, 차이점은 이름에서 알 수 있듯이 전자는 ID에 매칭이 되고, 후자는 CODE에 매칭이 되는 핸들러 이다. 각각의 매크로 정의를 보면 쉽게 알 수 있다. #define COMMAND_ID_HANDLER(id, func) \ if(uMsg == WM_COMMAND && id == LOWORD(wParam)) \ { \ bHandled = TRUE; \ lResult = func(HIWORD(wParam), LOWORD(wParam), (HWND)lParam, bHandled); \ if(bHandled) \ return TRUE; \ } #define COMMAND_CODE_HANDLER(code, func) \ if(uMsg == WM_COMMAND && code == HIWORD(wParam)) \ { \ bHandled = TRUE; \ lResult = func(HIWORD(wParam), LOWORD(wParam), (HWND)lParam, bHandled); \ if(bHandled) \ return TRUE; \ } 쉽게 설명하면 COMMAND_ID_HANDLER 는 코드는 무시하고 ID가 매칭되면 해당 핸들러를 호출해주는 것이고 COMMAND_CODE_HANDLER는 ID는 무시하고 코드가 매칭되면 해당 핸들러를 호출해주는 것이다. 요 3개를 섞어서 사용하다 보면 이상한 경우가 생길수가 있다. 아래의 예를 보면.. BEGIN_MSG_MAP(CListDlg) COMMAND_HANDLER(IDCANCEL, BN_CLICKED, OnBnClickedCancel) COMMAND_ID_HANDLER(IDOK, OnCommandOK) COMMAND_CODE_HANDLER(BN_CLICKED, OnCommandBnClicked) ENG_MSG_MAP 위의 설명에 앞서서 메시지 맵에 들어있는 매크로의 순서대로 위에서부터 검사를 한다는 것을 알아야 한다. 위에서 cancel 버튼을 클릭한다고 하면 위에서부터 검사해서 첫번째에 걸리므로 OnBnClickedCancel이 호출되고, OK 버튼을 클릭했을때는 두번째에 걸리므로 OnCommandOK가 호출이 되는 것이다. 다른 버튼이 있다고 할 때, 그 버튼을 클릭하면 세 번째에 걸리므로 OnCommandClicked가 호출이 되는 것이다. COMMAND 메크로 중에서 범위를 지정하는 것은 2개밖에 없다. COMMAND_RANGE_HANDLER와 COMMAND_RANGE_CODE_HANDLER 이렇게 두개가 있는데, 전자는 ID 범위를 받고, 후자는 코드 범위를 받는다. 그럼 이제 NOTIFY에 대해서 알아보도록 하자. NOTIFY 핸들러는 WM_NOTIFY메시지를 처리하는데 WM_COMMAND에서 NOTIFY로만 변경된거 외에는 메시지 핸들러는 똑같다. 똑같이 NOTIFY_HANDLER, NOTIFY_ID_HANDLER, NOTIFY_CODE_HANDLER, NOTIFY_RANGE_HANDLER, NOTIFY_RANGE_CODE_HANLDER 가 존재한다. 다른점은 핸들러 함수의 파라미터가 다르다. BEGIN_MSG_MAP(CListDlg) NOTIFY_HANDLER(IDC_FILE_LIST, NM_CLICK, OnNMClickFileList) END_MSG_MAP() LRESULT OnNMClickFileList(int /*idCtrl*/, LPNMHDR pNMHDR, BOOL& /*bHandled*/);
BEGIN_MSG_MAP(CListDlg) REFLECT_NOTIFICATIONS() CHAIN_MSG_MAP(CBaseClass) END_MSG_MAP() 위와 나온 매크로도 자주 쓰이는 것들이니까, 알아 두어야 한다. 먼저 CHAIN_MSG_MAP은 말그대로 메시지 맵에 체인을 걸어주는 것이다. 무슨말인고 하면, CListDlg가 다른 클래스로부터 상속된 클래스라고 하면 베이스 클래스로 메시지 체인을 걸어주게 되는 것이다. CHAIN_MSG_MAP() 매크로가 맨 위에 있다면 메시지들은 일단 베이스 클래스에서 해당 메시지의 핸들러가 있는지 확인을 하고 없으면, CListDlg에서 처리를 하게되고 반대로 CHAIN_MSG_MAP 매크로가 메시지 맵의 마지막에 있으면, CListDlg에서 처리하지 않은 메시지 들에 대해서 베이스 클래스로 전달을 하는 역할을 하게 된다. 이 매크로는 상속관계에서 많이 쓰이고, Mix-in Template 클래스를 사용할 때도 사용해야 한다. 다음으로 REFLECT_NOTIFICATIONS() 매크로는 이름에서도 알수 있듯이 NOTIFY를 reflet 해주는 역할이다. 차일드 컨트롤 윈도우에서 발생하는 notify 메시지에 대해서 차일드 윈도으로 다시 보내주는 역할을 하는 것이다. MFC에서는 =NM_ 계열의 핸들러라가 있는 것을 알것이다. 이것이 바로 notify 메시지를 차이드가 자체적으로 처리하는대, WTL에서는 부모에서 위의 메크로를 써주어야 차일드에서 notify를 처리할 수 있는 것이다. [강좌] WTL로 프로그래밍하기#4 – Message Map(1)개발 2007. 8. 17. 11:53336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
WTL로 프로그래밍하기#4 – Message Map(1) 저자 : hanburn 날짜 : 2007.08.17 환경 : WTL7.5, VS-2003 WTL에서 메시지 처리는 MFC와 유사하게 메시지 매크로를 이용하는데, 조금 다른 점이 라면 메시지 매크로가 헤더파일에 있다는 점입니다. 위치만 변경된 것이므로 뭐 크게 다를건 없구요.. 메시지 맵은 BEGIN_MSG_MAP(클래스이름) / END_MSG_MAP() 매크로를 사용하여 MFC와 유사하게 처리합니다. WTL에서는 메시지 종류에 따라서 크게 3가지로 분류하여 처리를 합니다. WM_COMMAND WM_NOTIFY WM_계열의 일반 메시지 그럼 하나하나씩 알아보도록 하겠습니다. 먼저 일반 메시지 처리부터 살펴보도록 하죠.. WM_ 계열의 일반 메시지는 MESSAGE_HANDLER() 매크로를 이용하여 처리를 합니다. 함수 원형은 다음과 같다. BEGIN_MSG_MAP(CMyDlg) MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog) END_MSG_MAP() LRESULT OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled); 일반적인 메시지 핸들러 함수는 위에처럼 4개의 인자를 가지고 있는데, 첫번째 uMsg는 처리하는 메시지에 해당하구요, wParam, lParam은 각 메시지의 전달되는 값 그대로 이고, 마지막으로 bHandled는 해당 핸들러에서 메시지를 처리했는지 않했는지를 표시해준다. 예를 들어서 해당 메시지를 처리하고나서 ATL의 기본 WindowProc() 함수에서 해당 메시지를 처리하게 하려면 bHandled 값을 FALSE로 설정하면 된다. 이게 무슨 말인가?? 쉽게 설명하면 다음의 예를 보자 // MFC 버전 void CMyDialog::OnMouseMove(UINT nFlags, CPoint point) { ... // 어떤처리 __super::OnMouseMove(nFlags, point); } // WTL 버전 LRESULT OnMouseMove (UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { ... // 어떤처리 bHandled = FALSE; } bHandle를 FALSE로 해주면 WM_MOUSEMOVE 메시지를 CMyDialog에서 처리를 않했다고 표시하는 것이다. 그래서 WM_MOUSEMOVE 메시지가 WindowProc으로 전달이 되는 것이다. MFC 에서는 WM_MOUSEMOVE 이벤트를 처리하고 베이스 클래스의 OnMouseMove를 호출해주는 것과 비요해서, WTL에서는 베이스 클래스의 가상함수를 호출하는 역활을 bHandled 변수가 담당하게 되는 것이다. 그럼 좀더 자세하게 MESSAGE_HANDLER의 정의부를 살펴보자. #define MESSAGE_HANDLER(msg, func) \ if(uMsg == msg) \ { \ bHandled = TRUE; \ lResult = func(uMsg, wParam, lParam, bHandled); \ if(bHandled) \ return TRUE; \ } 역시나 정의된 것을 직접 보는 게 제일 편하다. ㅋㅋ 해당 함수를 호출하기 전에 bHandled를 true로 설정하고 함수를 호출하고 나서 bHandled를 체크하여 return TRUE를 해주고 있구나. 그렇다면 WTL에서는 LRESULT 값이 무시가 되는 건가? 그렇다 현재 7.5 버전에서는 무시를 하고 있다. (END_MSG_MAP 정의 부분을 보면 알 수 있다.) 그리고 정의부를 본 김에 BEGIN_MSG_MAP과 함께 전체를 풀어보면 아래와 같다. // BEGIN_MSG_MAP() 부분 public: \ BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lResult, DWORD dwMsgMapID = 0) \ { \ BOOL bHandled = TRUE; \ (hWnd); \ (uMsg); \ (wParam); \ (lParam); \ (lResult); \ (bHandled); \ switch(dwMsgMapID) \ { \ case 0: // MESSAGE_HANDLER()부분 if(uMsg == msg) \ { \ bHandled = TRUE; \ lResult = func(uMsg, wParam, lParam, bHandled); \ if(bHandled) \ return TRUE; \ } // END_MSG_MAP 부분 break; \ default: \ ATLTRACE(ATL::atlTraceWindowing, 0, _T("Invalid message map ID (%i)\n"), dwMsgMapID); \ ATLASSERT(FALSE); \ break; \ } \ return FALSE; \ } 이렇게 메시지 메크로를 풀어보니까 그냥 메시지에 따라서 if문으로 구성되어 있는 간단한 처리방식인데, 사용하기 편하게 매크로로 정의를 한 것 뿐이다. 요건 MFC도 마찬가지다. ^^ WTL에서는 MFC와 다르게 메시지를 unpack을 않해주고 있기 때문에, 직접 WPARAM, LPARAM에서 원하는 정보를 추출하여 사용하여야 한다. 이것이 조금 불편한 점이다. <atlcrack.h> 헤더파일을 추가해주면 MFC와 비슷하게 전달되는 파라미터를 각각의 메시지에 맞게 변환해준 값을 전달해 주는데, 이것도 조금 불편하다. 그 이유를 설명하면, 다음의 unpack을 사용하는 WTL의 메시지 매크로 모습을 보자. BEGIN_MSG_MAP_EX(CMiniNewsWnd) MSG_WM_CREATE(OnCreate) MSG_WM_LBUTTONUP(OnLButtonUp) MSG_WM_MOUSEMOVE(OnMouseMove) END_MSG_MAP() LRESULT OnCreate(LPCREATESTRUCT lpcs); void OnLButtonUp(UINT nID, CPoint pt); void OnMouseMove(UINT nFlag, CPoint pt); 이것만 보면 ‘어랏! MFC와 똑같네~’ 라고 할 수 있는데, 맞다 똑같다. 우리가 쓰던인자 그대로 뽑아서 넘겨준다. 그런데 왜 불편하다는 거야? 라고 물어보면 대답은 다음과 같다. MFC에서는 해당 메시지마다 위자드가 자동으로 핸들러를 추가해주지만, WTL에서는 위자드를 통해서 핸들러를 추가하게 되면 기본적인 MESSAGE_HANDLER 형식으로 추가를 해주기 때문이다. MSG_WM_ 형식은 수동으로 추가를 해야 하는데, 누가 각각의 메시지의 파라미터를 다 외우고 다니나? 그래서 추가를 하려면, 해당 MSG_WM_XXX의 정의를 보고 추가해주어야 한다. 이때 또 난감한 것이 있으니 다음과 같은 경우이다. #define MSG_WM_SETCURSOR(func) \ if (uMsg == WM_SETCURSOR) \ { \ SetMsgHandled(TRUE); \ lResult = (LRESULT)func((HWND)wParam, (UINT)LOWORD(lParam), (UINT)HIWORD(lParam)); \ if(IsMsgHandled()) \ return TRUE; \ } WM_SETCURSOR 핸들러를 추가하기 위해서 MSG_WM_SETCURSOR의 정의를 찾아본다. 하지만, 두번째, 세번째 인자는 lParam의 low/hi 워드를 표기 사는데 이것의 의미를 알기 위해서는 msdn을 참고해야 한다. 즉, 각각 메시지 마다 파라미터의 의미를 대 외우지 않으면 msdn을 찾아야 하는 것은 필수이기 때문이다. 따라서 MESSAGE_HANDLER 나 MSG_WM_XXX 중에서 맘에 드는 것을 선택적으로 사용하기 바란다. 참고로 필자는 두 개를 섞어서 사용한다. 그리고 MESSAGE_RANGE_HANDLER로 있다. 메시지의 범위를 처리하는 것인데, 사용자 정의 메시메시지 처리할 때 유용하다. 뭐 이건 MFC를 사용해 본 사람은 다 알고 있으리라 생각하고 넘어간다. 오늘은 여기서 짜르고 다음 시간에 나머지 부분에 대해서 알아보도록 하겠습니다.
[강좌] WTL로 프로그래밍하기#3 – Mix-in class개발 2007. 8. 14. 10:00336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
WTL로 프로그래밍하기#3 – Mix-in class 저자 : hanburn 날짜 : 2007.08.14 환경 : WTL7.5, VS-2003 지난 시간에 위자드를 통해서 생성되는 코드들을 살펴 보았습니다. MainDlg의 코드를 살펴보다가 이번시간으로 내용을 넘겼는데, 그 이유는 먼저 살펴 볼 내용이 있어서 입니다. 그럼 시작도록 하겠습니다. 템플릿에 익숙하지 않은 분들이 계시다면 부지런히 공부를 하시기 바랍니다. ㅎㅎ 이번에는 template의 한가지 사용예제를 먼저 살펴보자. C++ 표준에서는 클래스의 상속리스트로 해당 클래스를 사용 할 수가 있다. 이게 무슨 말인가? 쉽게 아래처럼 사용이 가능하다는 것이다. template <class T> class B1 { public: void SayHi() { T* pT = static_cast<T*>(this); pT->PrintClassName(); } void PrintClassName() { cout<< "This is B1" << endl; } }; class D1 : public B1<D1> { }; class D2 : public B1<D2> { public: void PrintClassName() { cout<< "This is D2" << endl; } }; Class D1 : public B1<D1> 이렇게 템플릿클래스에 자신을 매개변수로 해서 상속을 받을 수가 있는 것이다. 이런 방법은 ATL, WTL에서 자주 사용되는 방법이므로 유의깊게 보기 바란다. 물론 이미 알고 있는사람들은 그냥 대충 넘어가거나 잘못된 내용이 있으면 태클을 걸어주길 바랍니다.^^ D1의 SayHi()를 호출하면 D1은 PrintClassName을 오버라이드하지 않았기 때문에 베이스인 B1의 PrintClassName이 호출이 되고, D2의 SayHi()를 호출하면 D2는 PrintClassName이 오버라이드 되어서 D2의 PrintClassName이 호출되는 것이다. 이건 바로 C++의 다형성을 구현하는 가상함수의 개념이 아닌가? 그런데 한가지 다른 점이 있다. 각각 실행될 때 어떻게 되는지 자세히 살펴보자. 먼저 D1의 경우에는 템플릿이 아래처럼 컴파일 시간에 확장될 것이다. void B1<D1>::SayHi() { D1* pT = static_cast<D1*>(this);
pT->PrintClassName(); } 그리고 D2의 경우에는 템플릿이 아래처럼 컴파일 시간에 확장될 것이다. void B1<D2>::SayHi() { D2* pT = static_cast<D2*>(this);
pT->PrintClassName(); } 중요한 점은 컴파일 시간에 어떤 함수가 불려질지 결정이 된다는 점이다. 잉? 가상함수는 실행시간에 다형성이 결정되는데, 컴파일 시점에 결정된다면 오히려 안좋은 것이 아닌가? 라고 의문을 가질 수 있는데, 반대로 컴파일 시점에 호출되는 함수가 결정된다는 것은 다음과 같은 이점이 있다. 1. 객체의 포인터를 필요로 하지 않는다. 2. Vtbls 가 없어서 메모리가 절약되고 수행시간이 더 빠르다. 3. Vtbl이 초기화되지 않아서 발생할 수 있는 문제점들이 없다. (널참조 같은) 4. 컴파일 시간에 문제점들이 해결될 수 있다. 위의 예제는 가상함수를 사용하지 않고 tempalate을 이용하여 다형성을 구현한것이다. 참 흥미로운 부분이다. 아래의 예제를 하나 더보도록 하자 template<class T> class CSingleton { public: static T* getInstance(VOID) { if( TRUE == ::IsBadReadPtr(m_pInstance, sizeof(T)) ) m_pInstance = new T(); return m_pInstance; } static VOID releaseInstance(VOID) { if( FALSE == ::IsBadReadPtr(m_pInstance, sizeof(T)) ) delete m_pInstance; m_pInstance = NULL; } private: static T* m_pInstance; }; // Init static member template<class T> T* CSingleton<T>::m_pInstance = NULL; 위의 클래스는 디자인패턴에서 Singleton을 구현한 template 클래스이다. 사용은 아래처럼 하면 간단하게 해당 클래스가 Singleton클래스로 동작을 하게 된다. CMyClass : public Singleton<CMyClass> { Public: … } 이런 CSingeton 같은 template 클래스를 Mix-in class 라고 한다. Mix-in class는 해당 기능을 가지고 있고, 다른 클래스에 템플릿 상속을 통해서 쉽게 기능을 추가 할 수 있는 클래스 이다. WTL에서는 이런 방식으로 템플릿 클래스의 상속을 통해서 기능을 붙이는 방식으로 설계가 되어 있다. 오 놀라워라~ 자~ 그럼 다시 우리의 MainDlg.h를 살펴보면, 여러 가지 기능들을 상속으로 추가하는 모습들을 볼 수 있는데, 하나하나씩 살펴보도록 하자. class CMainDlg : public CDialogImpl<CMainDlg>, public CUpdateUI<CMainDlg>, public CMessageFilter, public CIdleHandler { 먼저 CDialogImpl 클래스다. 이것은 CDialog를 실제적으로 구현하고 있는 클래스로 아래와 같은 상속구조를 가지고 있다. CMainDlg : CDialogImpl : CDialgImplBaseT : CWindowImplRoot : TBase, CMessageMap Public을 빼고 그냥 베이스가 되는 클래스를 오른쪽으로 표시한 것이다. Dialog도 윈도우니까 기본적인 윈도우의 구현인 CWindowImpl에서 상속된 것들을 쭉 타고 내려온다. WTL은 인터페이스와 구현을 CWindow와 CWindowImlp 이라는 2개의 클래스로 구분을 하였다는 것 정도만 알고 넘어가도록 하자. 하부구조를 다 설명하려면 WTL Architecture라는 강좌를 통해서 하도록 하겠다. 그 다음으로는 CUpdateUI 라는 클래스를 살펴보자. 이 클래스는 메뉴 등의 UI의 상태를 관리해주는 역할을 하는 것으로, 내부적으로 UI요소들을 map으로 관리하면서, BEGIN_UPDATE_UI_MAP 매크로를 통해서 해당 map을 구성하게 된다. 그리고 UIAddMenuBar(), UIAddToolBar(), UIAddStatusBar() 등의 함수를 통해서 map을 설정하게 되고, UIEnable(), UISetCheck() 등의 함수를 통해서 UI를 관리하게된다. 이것은 나중에 SDI 형태의 프로그램을 볼 때 더 자세하게 다루도록 하겠다. 그 다음으로는 CMessageFilter 클래스이다. 이것은 클래스 이름에서 알수 있듯이 메시지를 필터링 할수 있는 인터페이스 클래스이다. class CMessageFilter { public: virtual BOOL PreTranslateMessage(MSG* pMsg) = 0; }; 이 클래스를 상속 받으면 반드시 PreTranslateMessage를 재정의 해주어야 한다. MFC의 Pretranslate 함수와 같은 역할을 한다. 메시지가 전달되기 전에 먼저 처리해주는 역확을 한다. 다음은 CIdleHandler 인데, 이것도 이름에서 바로 알 수 있듯이 OnIdle 핸들러를 제공하는 것이다. class CIdleHandler { public: virtual BOOL OnIdle() = 0; }; 그런데 단순히 CMessageFilter 와 CIdlHandler를 상속만 받으면 어떻게 PretranslateMessage 와 OnIdle이 호출될까 라고 궁금하지 안은가? 그 원리는 OnInitialDialog 에 있는 코드에 있다. // register object for message filtering and idle updates CMessageLoop* pLoop = _Module.GetMessageLoop(); ATLASSERT(pLoop != NULL); pLoop->AddMessageFilter(this); pLoop->AddIdleHandler(this); 위의 코드가 메시지 루프에 각각을 추가해주기 때문에 메시지를 전달하는 과정에서 수를 부리는 것이다. 이것을 보면 바로 Pretranslate 에는 OnInitialDialog 다음에 오는 메시지만 Filtering을 할 수 있게 된다. 즉 WM_INITDIALOG 같은 메시지는 안잡히게 되는 것이다. |