안녕하세요. 저번에 작성한 포스팅에서 좀 더 수정을 해보도록 하겠습니다.

일단 제가 작성한 코드를 먼저 보여드리겠습니다.

전에 작성한 소스코드에 몇가지 부분이 수정되거나 추가되었지요? 하나씩 살펴봅시다.

- wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); 

-> 기존코드

- wndclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); 

-> 수정코드

우리가 만들려는 abex crack me 2는 윈도우 창이 검은색입니다. 그러니까 배경색을 하얀색이 아닌 검은 색으로 바꾸었습니다. 결과를 봅시다.

검은색 윈도우 창이 생성되었습니다. 다음으로 넘어갑시다.

- wndclass.lpszClassName = L"HELLOWINDOWS"; -> 기존코드

- wndclass.lpszClassName = L"abexcm2"; -> 수정코드

윈도우의 클래스 이름을 수정하였습니다. 이렇게 수정한 이름은 CreateWindow함수의 첫 번째 인자로 들어가게 됩니다. 

제 소스에서는 윈도우 창을 하나만 띄우기 때문에 인자에 wndclass.lpszClassName를 직접 넣어줬지만 여러개의 윈도우 창을 띄울 경우에는 각 윈도우 창을 구분해야 하므로 가독성을 높이기 위해 lpszClassName에 저장된 문자열을 넣어주는 것이 좋습니다. 

사실 이부분은 안바꿔도 프로그램 실행에 지장이 없습니다. 다음으로 넘어갑시다.

- hWnd = CreateWindow(wndclass.lpszClassName, L"Hello Windows                            Application",

        WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,

        CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, wndclass.hInstance, NULL);

-> 기존코드

- hWnd = CreateWindow(wndclass.lpszClassName, L"abex 2nd crackme",

        WS_POPUPWINDOW, 820, 462,

        280, 155, NULL, NULL, wndclass.hInstance, NULL);

-> 수정 코드

먼저 두번째 인자의 타이틀 이름을 바꿔주었습니다. 결과를 봅시다.

잘 안보이시면 사진을 눌러 확대해서 보시기 바랍니다. 타이틀 이름이 바뀐 것을 알 수 있습니다.

다음은 세번째 인자입니다. 세번쨰 인자는 윈도우 스타일을 받는 인자였지요? 여기서 abex crack me2를 다시 봅시다.

우리가 생성한 창하고는 다르게 최소창, 닫기, 타이틀 등이 없는 윈도우 창입니다. 즉 이 윈도우 창을 만들기 위해서는 이 세번째 인자값을 다르게 주어야 합니다. 이러한 윈도우창은 팝업창과 비슷한 스타일입니다. 그러므로 세번째 인자에 팝업창 스타일을 전달해 봅시다. 팝업창을 의미하는 정의된 상수값은 WS_POPUPWINDOW입니다.

그리고 크기를 지정해줍시다. 이 WS_POPUPWINDOW창은 크기를 CW_USEDEFAULT로 주면 창이 보이지 않습니다. 그래서 사용자가 임의로 크기를 지정해주어야 합니다. abex crack me 2는 가로 약 280, 세로 약 155입니다. 결과를 한 번 봅시다.

완전 검은색인 팝업윈도우창이 생성되었습니다.

다음은 좌표입니다. 방금 생성된 창은 CW_USEDEFAULT로 인해 x,y 좌표가 0,0으로 된 상태입니다. abex crack me 2를 딱 실행하면 어떻게 됩니까? 화면의 가운데 쯤에 생성되지요? x와 y의 좌표도 abex crack me 2에 맞춰 줍시다. abex crack me2의 x좌표값은 약 820, y좌표 값은 약 462입니다. 결과를 한 번 봅시다.

중앙에 생성된 윈도우 창을 확인할 수 있습니다.

이제 창은 그럴듯하게 맞춰줬습니다. 이제 창에 글자를 적을 차례입니다.

창에 무언가를 그리는 것은 WndProc 함수에서 처리합니다.  WndProc함수를 살펴봅시다.

- HDC hdc;

HDC도 핸들을 의미합니다. 그러면? HWND와 HDC의 차이가 무엇일까요?

HDC는 디바이스 컨텍스트 핸들을 의미합니다. HWND는 윈도우 핸들을 의미하지요.

이 디바이스 컨텍스트(DC)라는 것은 그래픽 관련 옵션을 모아놓은 구조체를 뜻합니다.즉 무언가를 출력을 할 때 쓰이는데 이렇게 쓰일려면 당연히 출력대상을 알아야합니다. 그리고 그 출력대상을 저장하는 핸들이 바로 HDC입니다. 보통 출력을 한다는 것은 윈도우 창의 출력을 의미하므로 대부분 윈도우창에서 hDC를 얻어옵니다.

(ex : GetDC 함수, BeginPaint 함수 )

정리하면 출력대상을 저장하기 위한 자료형입니다.

- PAINTSTRUCT ps;

PAINTSTRUCT 형의 ps라는 변수를 선언합니다. PAINTSTRUCT 의 정의를 봅시다.

이름에서 알 수 있듯이 구조체를 뜻하는 군요. 이 구조체를 자세히 살펴봅시다.

이 PAINTSTRUCT은 생성된 응용프로그램의 화면에 관한 정보를 가져와 저장시킬 때 사용합니다. 

인자를 살펴봅시다. 

*HDC hdc;

정보를 가져올 응용프로그램의 출력대상을 저장합니다. 즉 응용프로그램 화면의 핸들이죠.

* BOOL fErase;

이 멤버는 배경을 다시 그려줘야 하는지에 대한 여부를 저장합니다. TRUE이면 그려줄 피필요가 없다는 것을 의미하고 FALSE라면 다시 그려줘야 한다는 뜻입니다.

나머지 인자들은 나중에 설명드리겠습니다.

정리하면 이 ps는 화면의 정보를 받을 때 쓰입니다.

- HFONT font;

HFONT는 폰트 핸들입니다. 만들거나 가져온 폰트의 핸들을 저장시키기 위해 사용합니다.

- HPEN pen;

HPEN은 펜 핸들입니다. 여기서 펜이란 선을 의미합니다. 만들거나 가져온 선의 핸들을 저장시키기 위해 사용합니다.

- LPCTSTR str = L"abex’  2nd crack me";

화면에 출력할 문자열을 따로 저장시켜 str로 선언해주었습니다. 물론 따로 선언안하고 함수 인자에 직접 적어주셔도 무방합니다. 다만 직접 적어주는 방식은 나중에 많은 문자열을 수정할 때 시간이 조금 걸립니다.

- hdc=BeginPaint(hWnd, &ps);

BeginPaint의 설명을 봅시다.

이 함수는 쉽게 설명하면 윈도우 로부터 DC를 얻어오는 함수입니다. 인자를 살펴봅시다.

* HWND hwnd

DC를 얻어올 윈도우의 핸들을 의미합니다.

* LPPAINTSTRUCT lpPaint

 LP는 LongPointer를 의미합니다. 즉 PAINTSTRUCT의 주소를 인자로 받습니다.

정리하면 이 함수는 윈도우 창으로부터 DC를 받아와 PAINTSTRUCT의 멤버에 저장시켜 HDC로 반환하는 함수입니다.

GetDC함수랑 비슷한 기능을 합니다. 하지만 제가 GetDC함수를 사용하지 않는 이유가 있습니다. 이것은 나중에 설명드리겠습니다.

 - font1 = CreateFont(18,0,0,0,700,0,0,0,HANGEUL_CHARSET,0,0,0,

DEFAULT_PITCH | FF_ROMAN,L"궁서");

폰트를 생성시켜 저장하는 코드입니다. CreateFont의 정의를 봅시다.

자 인자 설명 들어갑니다.

* int nHeight

글자의 높이를 받습니다.

* int nWidth

글자의 너비를 받습니다.

* int nEscapement

글자의 기울기를 받습니다.

* int nOrientation

글자 하나하나의 기울기를 받습니다. 사용할 필요가 없으니 0을 줍니다.

* int fnWeight

글자의 굵기를 받습니다. 400은 보통이며 700은 진하게를 의미합니다. 범위는 0에서1000 입니다.

* DWORD fdwItalic

받은 값이 TRUE이면 기울기(Italic)로 설정합니다.

* DWORD fdwUnderline

받은 값이 TRUE이면 밑줄로 설정합니다.

* DWORD fdwStrikeOut

받은 값이 TRUE이면 취소선으로 설정합니다.

* DWORD fdwCharSet

글자의 설정값을 받습니다. 한글을 출력하고 싶으시면 HANGEUL_CHARSET로 설정해주시면 됩니다. 물론 abex crack me2는 한글이 없기 때문에 다른 설정을 해주셔도 무방합니다.

* DWORD fdwOutPutPrecision

출력 정확도를 받습니다. 사용할 필요가 없으니 0으로 줍시다.

* DWORD fdwClipPrecision

클리핑 정확도를 받습니다. 사용할 필요가 없으니 0으로 줍시다.

* DWORD fdwQuality

출력의 품질을 받습니다. 사용할 필요가 없으니 0으로 줍시다.

* DWORD fdwPitchAndFamily

자간과 폰트를 받습니다. 여기서는 값을 DEFAULT_PITCH로 주어 자간을 기본값으로 설정했습니다. 폰트는 FF_ROMAN을 주었습니다. FF_ROMAN의 설명을 봅시다.

MS의 세리프를 폰트로 사용하겠다는 뜻입니다.

* LPCTSTR lpszFace

사용할 글꼴을 유니코드로 받습니다. 여기서는 궁서 글꼴을 사용하기로 했습니다.

인자가 정말 미친듯이 많습니다. 그만큼 글꼴 설정이 많다는 것을 알 수 있습니다.

- SelectObject(hdc,font);

만든 font를 등록합니다. SelectObject의 설명을 봅시다.

인자를 살펴봅시다.

* HDC hdc

등록할 DC를 인자로 받습니다. 

* HGDIOBJ hgdiobj

GDI 객체 핸들을 받습니다. 여기서는 만든 폰트의 핸들인 HFONT가 들어갑니다.

정리하면 그래픽 관련 핸들을 받아 DC에 등록합니다. 이렇게 등록된 FONT는 그 후 글자에 폰트가 자동으로 적용됩니다.

- pen = CreatePen(PS_SOLID,2,RGB(189,189,189));

선의 속성을 지정하는 핸들을 만들어 pen에 저장시킵니다. CreatePen을 살펴봅시다.

인자를 살펴봅시다.

* int fnPenStyle

펜 스타일을 설정합니다. 우리는 실선을 사용할 것이므로 PS_SOLID로 지정합시다.

* int nWidth

선의 굵기를 받습니다. 저는 2로 주었습니다.

*COLORREF crColor

선의 색깔을 받습니다. 자료형 COLORREF를 살펴봅시다.

타입이 DWORD입니다. 즉 4바이트를 받습니다. 

MSDN의 설명을 참고하면 상위바이트는 0으로 고정이며 그 다음 바이트는 파란색, 그 다음 바이트는 녹색, 최하위 바이트는 빨강색을 의미합니다.

머리가 아프니 RGB라는 함수를 사용합니다. RGB 정의를 봅시다.

따로 설명이 필요없겠지요? 단위가 1바이트이므로 2의 8승인 256개를 표현할 수 있습니다. 즉 0~ 255를 뜻하지요. 우리가 원하는 색은 회색이고 이 회색의 RGB값은 (189,189,189)입니다. 

- SelectObject(hdc,pen);

font에 이어 pen도 hdc에 등록시킵니다. 이후에 선을 그리면 pen의 선 속성이 자동으로 적용됩니다.

- SetTextColor(hdc,RGB(255,0,0,));

글자의 색깔을 지정하는 함수입니다. SetTextColor함수의 설명을 봅시다.

앞서 했던 인자들이니 설명은 넘기겠습니다.

글자의 색깔을 받아 hdc에 등록합니다. 여기서는 빨간색으로 등록하였습니다.

- SetBkColor(hdc,RGB(0,0,0,));

글짜의 음영 색깔을 지정합니다. 인자는 SetTextColor랑 똑같습니다. 이 함수를 쓰지 않으면 기본적으로 하얀색이 음영색으로 지정됩니다. 우리는 배경이 검은색이므로 검은색으로 맞춰주기 위해 음영색을 검은색으로 지정하였습니다.

- TextOut(hdc, 40,7,str,lstrlen(str));

DC에 글자를 출력하는 함수입니다. 정의를 한번 봅시다.

인자 설명 합니다.

* HDC hdc

출력할 DC의 핸들을 받습니다.

* int nXStart

DC내에서 출력할 글자의 x좌표를 의미합니다. 이 좌표는 글자가 시작하는 위치입니다.여기서는 40을 주었습니다.

*int nYStart

DC내에서 출력할 글자의 x좌표를 의미합니다. 여기서는 7를 주었습니다.

*LPCTSTR lpString

출력할 유니코드 문자열을 받습니다. 여기서는 아까 저장시킨 str을 주었습니다.

* int cchString

출력할 유니코드 문자열의 길이를 받습니다. 여기서는 lstrlen함수를 사용하여 길이를 주었습니다. 물론 직접 계산하여 숫자를 주어도 되지만 길이가 긴 문자열일 경우 계산이 귀찮아지므로 lstrlen함수를 사용했습니다.

그런데 왜 strlen함수가 아닌 lstrlen함수를 사용했을까요? 각각의 정의를 봅시다.

인자가 유니코드입니다.

인자가 멀티바이트코드 입니다.

아시겠지요?

- MoveToEx(hdc,0,30,NULL);

선을 그리기 위해 사용한 함수입니다. 보통 LineTo 함수와 같이 사용합니다.

정의를 봅시다.

* HDC hdc

출력할 DC를 받습니다.

* int x

선을 그릴 때 시작점의 x좌표를 뜻합니다. 여기서는 0으로 주었습니다.

* int y

선을 그릴 때 시작점의 y좌표를 뜻합니다. 여기서는 30으로 주었습니다.

* LPPOINT lpPoint

위 인자의 x,y 좌표를 변수에 저장시킬 때 그 변수의 주소를 인자로 받습니다. 사용하지 않을려면 NULL값을 줍니다.

즉 이 함수는 선을 그릴 때의 시작점을 지정할 때 사용합니다.

- LineTo(hdc,300,30);

대충 감이 오시지요? 이 함수는 선의 도착점을 지정합니다. 즉 MoveToEx의 시작점부터 LineTo의 끝점까지 선으로 연결한다는 뜻입니다.

인자는 MoveToEx의 인자에서 lpPoint만 없어졌습니다.

여기서 x좌표는 300, y좌표는 30을 주었습니다. 

- MoveToEx(hdc,185,30,NULL);

  LineTo(hdc,185,200);

또 하나의 선을 더 그리기 위해 함수를 호출하였습니다. 좌표값을 보면 세로로 직선을 그린다는 것을 알 수 있습니다.

- DeleteObject(font);

DC에 등록된 핸들을 지우기 위해 사용합니다. 보통 다른 font 핸들을 등록할때 지우거나 처리를 다 끝내고 메모리 효율을 위해 지웁니다. 인자는 SelectObject에서 hdc를 뺀 것입니다.

- DeleteObject(pen);

DC에 등록된 pen 핸들을 지웁니다.

- EndPaint(hWnd, &ps);

Paint의 끝을 알리는 함수입니다. 쉽게 얘기하면 DC를 반환하는 함수이죠. BeginPaint함수의 반대라고 생각하시면 됩니다. 인자는 BeginPaint함수랑 동일합니다. 보통 메모리 효율을 위해 사용합니다.

자 소스코드 설명이 끝났습니다. 이제 실행을 해봅시다.

조금씩 비슷해져가고 있습니다. 

네 이것으로 포스팅을 마쳤...으면 좋겠지만 추가로 설명하겠습니다.

WndPorc 함수의 코드를 다시 봅시다.

알다시피 이 함수는 메세지를 처리하는 함수입니다.

그런데? 윈도우 창이 생성되고 나면 이 메세지함수가 셀 수 없을 정도로 많이 실행됩니다. 즉 이 함수가 실행될 때 마다 우리가 작성한 코드에 의해 창에 글자와 선이 출력됩니다. 

아니 이미 한번 출력된 코드인데 계속 출력하라고 하면 메모리 효율이 극도로 떨어지겠죠? 그래서 우리는 이 코드가 매번 실행되는 것이 아닌 필요할 때만 실행되도록 할 필요가 있습니다. 그러므로 우리는 WndProc함수에서 WM_PAINT 메세지를 받았을 때 이 코드를 처리하도록 수정해줍시다.

물론 수정하기 전에 WM_PAINT 메세지를 설명해주어야겠지요?

기본적으로 윈도우 창이 생성될 때 WndProc에서는 몇 개의 메세지를 받습니다. 이러한 메세지는 큐에 저장되지 않고 바로 WndProc로 전달되기 때문에 GetMessage함수가 쓰이지 않습니다.

먼저 WM_CREATE 메세지가 전달됩니다. 이 메세지는 CreateWindow 함수가 전달한 것입니다. 즉 윈도우 창이 생성되기 직전에 받는 메세지이지요.

윈도우 창이 생성 된 다음 받는 메세지는 WM_MOVE, WM_SIZE 등의 메세지입니다. 당연히 이러한 메세지도 바로 WndProc로 전달됩니다. 대충 메세지를 다 받고 나면 마지막으로 받는 메세지가 WM_PAINT메세지 입니다.  우리는 이 WM_PAINT메세지를 받았을 때 코드가 처리되도록 수정하는 겁니다.  그렇다면 여기서 의문이 하나 생깁니다.

다른 메세지도 많은데 왜 WM_PAINT메세지에서 처리하나요?

예를 하나 보여드리겠습니다. 자 WM_MOVE 메세지를 받았을 때 출력을 하도록 수정해봅시다.

참고로 BeginPaint와 EndPaint함수는 WM_PAINT 메세지에서만 동작하는 함수입니다. 그 외의 메세지에서 DC를 할당받고 해제할려면 GetDC와 ReleaseDC함수를 써야합니다. 기능은 비슷하니 설명은 생략하겠습니다. 

WM_MOVE는 윈도우 창이 움직였을 때 발생하는 이벤트입니다. 자 이제 코드를 실행시켜 봅시다.

어라? 아무것도 안뜹니다. 분명 제가 GetMessage를 거치지 않고 WndProc함수에서 WM_MOVE를 무조건 받는다고 되어있는데 왜 출력이 되지 않을까요?

자 이번엔 윈도우 스타일을 바꿔봅시다. 아래와 같이 바꿔주세요.

WS_POPUPWINDOW속성을 WS_OVERLAPPEDWINDOW로 바꿨습니다. 실행을 해봅시다.

그냥 실행만 시켰을 뿐인데 출력이 되었습니다. 프로그램을 이동시키지 않았으니까 WM_MOVE 이벤트도 발생이 없었을 것입니다. 즉 GetMessage를 거치지 않고 WM_MOVE 메세지를 받아서 처리를 한 것입니다.

그러면 앞의 팝업 창은 WM_MOVE 메세지를 못받았을까요? 

아닙니다. 디버그를 해본 결과 WM_MOVE 메세지를 받았습니다.! 그런데 왜 팝업창에서는 출력이 되지 않았을까요?

그 이유는 팝업 윈도우 창의 특성 때문입니다. 팝업 윈도우 창은은 크기를 바꿀 수가 없고 이동시킬 수도 없습니다. 그렇기에 WM_MOVE나 WM_SIZE 메세지를 받아서 처리를 해도 실제로는 적용이 되지 않는 것입니다. 

그러면 방법이 없을까요? 아닙니다. 바로 WM_PAINT 메세지가 있습니다.! 메세지를 WM_PAINT로 수정해봅시다.

결과를 봅시다.

네 잘나오는군요.

참고로 WM_PAINT 메세지에서는 GetDC함수를 쓰지 않습니다. 왜 쓰지 않느냐고 하니 간단히 얘기해드리겠습니다. 

사실 이 WM_PAINT메세지는 무효화 영역이 생겼을 경우 발생하는 메세지 입니다. 즉

윈도우 생성 -> WM_PAINT(GetMessage를 거치지 않은) 메시지 처리

-> 이후에 오는 WM_PAINT는 무효화 영역이 생겼을 경우 GetMessage가 큐에서 뽑아서 DispatchMessage에 의해 WndProc로 전달합니다.

무효화 영역이라는 것은 간단하게 얘기하면 윈도우와 윈도우가 겹쳐진 영역 또는 최소화, 최대화로 인해 창이 사라진 영역을 의미합니다. 이러한 경우가 발생됐을 경우 WM_PAINT가 생성됩니다.

자 여기서 본론을 말하겠습니다. 이 WM_PAINT메세지는 GetMessage에서 뽑아올 때 지워지지가 않습니다. 보통 메세지를 가져와서 확인을 한 후 지워줘야 하는데 이 WM_PAINT메세지는 GetMessage가 지우질 않습니다. 즉 WM_PAINT메세지를 처리한 후에도 지워지지 않은 상태로 계속 큐에 남아있어 GetMessage가 또 WM_PAINT메세지를 받아 처리합니다. 즉 무한으로 WM_PAINT메세지를 처리하게 됩니다. 

끔찍하지요? 그러면 어떻게 해야할까요? 이를 방지하기 위해 BeginPaint함수를 쓰게 됩니다. 이 함수는 DC를 받아온 후 WM_PAINT 메세지를 큐에서 지웁니다.! 그래서 정상적으로 실행을 하게끔 합니다. GetDC함수는 DC만 얻어오고 이러한 작업을 하지 않습니다. 

그렇다면 BeginPaint함수만 사용하면 되느냐고 하니 이 함수는 WM_PAINT 메세지에서만 동작합니다. 즉 그 외의 메세지는 GetDC를 이용합니다. 

네 이것으로 포스팅을 진짜 마치겠습니다.


Posted by englishmath
,