안녕하십니까? 이번 포스팅에서는 지뢰찾기 구현을 위해 제가 만든 함수들을 살펴보겠습니다. 원래는 한꺼번에 설명하려고 했습니다만 워낙 소스코드가 길다보니 따로 빼서 설명하는 것이 좀더 나을 것 같아 먼저 설명드리겠습니다.

지뢰찾기에 쓰인 함수들은 다음과 같습니다.

void Createlandmine(void); - 지뢰배치함수

void Createlandmine2(void);  - 지뢰를 이용하여 기타 타일을 배치하는 함수

void CreateButton(void);  - 버튼을 생성하는 함수

void sp_check(void);  - 주변에 지뢰가 없는 타일(공백타일)을 체크하는 함수

void victoryCheck(void); - 승리조건에 도달했는가를 체크하는 함수

Createlandmine와 Createlandmine2함수는 앞 포스팅에서 설명드렸으니 넘어가겠습니다. 자 이제 차례대로 함수를 살펴 봅시다.

- void CreateButton(void)

함수명 그대로 버튼을 만드는데 쓰이는 함수입니다. 

- int i,j,k=0;

  int m = 0;

  int x=30,y=30;

for문에 쓰일 i,j 변수와 OpenClose_Tile, flagTile에 쓰일 인덱스변수 k를 선언하였습니다. OpenClose_Tile은 눌러진 타일인지, 안눌러진 타일인지의 여부를 저장하는 정수타입 전역 변수 배열 입니다. flagTile은 깃발이 있는지 없는지의 여부를 저장하는 정수타입 전역 변수 배열입니다.

m은 buttonHwnd의 인덱스 변수 겸 메뉴 이름입니다. buttonHwnd은 버튼들의 핸들을 저장시키는 핸들 타입 전역 변수 배열입니다.

x와 y는 버튼을 생성시킬 때의 좌표를 뜻합니다.

- for(i = 0; i <9 ;i++)

for(j = 0; j <9 ;j++)

초급지뢰찾기는 9*9 형태이므로 이 형태로 버튼을 만들어 출력하기 위해 2중 for문을 넣었습니다.

- buttonHwnd[m] = CreateWindow(L"button",NULL,WS_CHILD | WS_VISIBLE | BS_BITMAP,x,y,18,18,Global_hWnd, (HMENU) m,Global_hInstance,NULL);

CreateWindow함수로 버튼을 생성하여 buttonHwnd에 저장시킵니다. 이 때 버튼의 좌표는 아까 선언한 x와 y입니다. 크기는 18*18이이고 이름은 m입니다. 인자들을 보면 Global_hWnd와 Global_hInstance가 들어있는 것을 볼 수 있는데 Global_hWnd는 WndProc함수의 hWnd인자를 사용하기 위해 선언한 전역변수이고 Global_hInstance는 WinMain의 hInstance인자를 사용하기 위한 전역변수입니다.

그다음 눈에 띄는 인자는 바로 BS_BITMAP 입니다. MSDN의 설명을 봅시다.

버튼에 비트맵을 표시한다는 옵션이지요. 버튼에 원하는 색상을 주기 위해 이 옵션을 넣었습니다.

- SendMessage(buttonHwnd[m], BM_SETIMAGE,IMAGE_BITMAP,(LPARAM)LoadBitmap(Global_hInstance, MAKEINTRESOURCE(Tile)));

새로운 함수가 나왔습니다. 이 함수를 살펴봅시다.

말그대로 메세지를 보내는 함수입니다. 인자는 WndProc의 인자와 똑같습니다.

정리하면 이 SendMessage함수는 hWnd값의 윈도우 창에 메세지를 보내는 것입니다. 그러면 받은 hWnd값의 윈도우 창은 WndProc에서 받은 메세지를 처리하게 됩니다.

여기서 hWnd는 각 버튼의 핸들입니다. Msg로는 BM_SETIMAGE가 들어갔는데 이 메세지는 버튼의 이미지를 설정하는 메세지라고 보시면 됩니다. 세번째와 네번째 인자는 메세지의 추가정보 이지요. 먼저 wParam에는 IMAGE_BITMAP이 들어갔습니다. 앞서 메세지에서 BM_SETIMAGE인자를 넣었는데 그냥 이미지하면 커서인지, 아이콘인지, 비트맵인지 구별을 못하므로 비트맵 이미지를 버튼에 설정하기 위해 세번째 인자에 IMAGE_BITMAP가 들어갔습니다. 

마지막 인자에선 LoadBitmap함수가 쓰였습니다. 말 그대로 이함수는 비트맵을 불러오는 함수인데 LoadIcon과 맥락이 비슷하다고 보시면 됩니다.  인자로는 MAKEINTRESOURCE(Tile)이 쓰였습니다. 즉 비트맵리소스를 가져오신다고 보시면 됩니다. 이 리소스는 나중에 지뢰찾기 코드를 설명할 때 자세히 설명드리겠습니다.

마지막으로 LoadBitmap함수는 반환값이 HBITMAP이므로 (LPARAM)으로 강제형변환을 해주었습니다.

정리하면 이 sendMessage함수는 버튼의 BS_BITMAP 옵션을 사용할 때 호출되는 BM_SETIMAGE에 추가정보를 주기 위해 사용되었다고 보시면 됩니다. 이렇게 보낸 BM_SETIMAGE메세지에 의해 버튼은 비트맵 이미지를 받아 출력하게 됩니다.

다음으로 넘어갑시다.

- OpenClose_Tile[k] = 1;

  flagTile[k] = 1;

초기의 OpenClose_Tile, flagTile의 인덱스들의 값을 1로 설정합니다. 즉 OpenClose_Tile의 1은 버튼이 눌려지지 않은 상태임을 나타내고 flagTile의 1은 깃발이 꽃혀있지 않다는 것을 의미합니다.

- k = k+1;

  m += 1;

다음 인덱스를 지정하기 위해 변수들을 1씩 증가시킵니다.

- x += 18;

다음 버튼을 만들기 위해 x의 좌표를 버튼의 크기만큼 증가시킵니다. 이렇게하면 버튼이 오른쪽으로 하나씩 생성됩니다.

- x = 30;

  y += 18;

한줄의 버튼을 다 만들었다면 다음 줄의 버튼을 만들기 위해 x를 처음 버튼위치인 30으로 초기화 시키고, y를 버튼의 크기만큼 증가시킵니다.

최종적으로 이 CreateButton함수가 처리되고 나면 각각의 버튼 이름이 (0~80)인 버튼 81개가 생성됩니다.

다음 함수로 넘어갑시다.

- void sp_check(void)

이 함수는 모든 타일을 검사하여 타일이 9값이고 눌러진 상태일경우, 즉 주변에 지뢰가 없는 타일의 버튼이 벗겨진 경우 그 타일의 주변의 버튼을 없애는 기능을 합니다.

기존 지뢰찾기에서는 누르면 소리가 나면서 버튼이 크게 벗겨지지요? 그것을 구현한 겁니다.

- int i;

81개의 타일을 검사하기 위한 인덱스 변수를 선언합니다.

- for(i=0;i<81;i++)

81개의 타일을 검사하기 위해 선언하였습니다.

- if(buttonValue[i] == 9 && OpenClose_Tile[i] == 0)

buttonValue의 값이 9이고 OpenClose_Tile의 값이 0일 경우 if문을 실행합니다. 여기서 buttonValue는 Createlandmine2함수로 만들어진 arr[9][9]에 들어있는 값을 한 배열에 저장하기 위해 선언한 정수형 전역 변수 배열 입니다.

OpenClose_Tile의 값은 1과 0으로 나뉘어 지는데 1은 버튼이 안눌러진 상태이고 0은 버튼이 눌러진 상태를 의미합니다. 

즉 정리하면 현재 타일이 버튼이 벗겨진 상태의 9값 타일 인 경우를 의미합니다.

이 다음부터는 전 포스팅에서 얘기했던 Createlandmine2의 알고리즘을 이용하여 주변 타일을 삭제하고 OpenClose_Tile의 값을 0으로 지정해주는 코드입니다. 그러므로 일부분만 설명드리겠습니다.

- if(i <= 8)

switch(i)

case 0:

if(DestroyWindow(buttonHwnd[i+1]))

OpenClose_Tile[i+1] = 0;

if(DestroyWindow(buttonHwnd[i+9]))

OpenClose_Tile[i+9] = 0;

if(DestroyWindow(buttonHwnd[i+10]))

OpenClose_Tile[i+10] = 0;

break;

case 8:

.......

break;

default:

break;

i가 8보다 작거나 같을 경우입니다. 아까 CreateButton함수로 인해 버튼들의 핸들은 0~80까지의 인덱스들에 각각 저장되어있습니다. 즉 i가 8보다 작거나 같다는 것은 첫번째 줄을 의미합니다.

switch(i)는 i값에 따라 처리를 다르게 하기 위해 선언하였습니다. 아까의 Createlandmine2알고리즘으로 따지자면 j값을 의미합니다.

이 i값이 0이라면 i=0,j=0을 뜻하고 이 i값이 8이면 i=0,j=8을 뜻하고 그 외의 값(1~7)이라면 i=0, j= (1~7)까지를 나타내지요.

자 이제 처리부분을 한 번 봅시다.

DestroyWindow함수를 사용하여 핸들을 삭제합니다. 이 함수는 말그대로 인자에 해당하는 핸들을 가진 윈도우 창을 부수는 함수입니다. 즉 버튼을 삭제한다는 뜻입니다. 버튼이 삭제된다는 것은 벗겨진다는 것을 의미하지요. 그리고 버튼을 삭제한 이후 해당하는 OpenClose_Tile의 값을 0으로 줍니다. 벗겨졌으니까 OpenClose_Tile의 값을 바꿔주어야 겠지요.

여기서 궁금점이 하나 생길 수 있는데 그것은 바로 if문입니다. 이러한 if문을 준 이유는 버튼을 지우다보면 이미 버튼이 지워진 부분도 다시 버튼을 지우고 OpenClose_Tile의 값을 0으로 주기 때문에 이를 방지하기 위해 if문을 넣었습니다. if문이 실패하면 OpenClose_Tile에 값을 지정하지 않도록 하기 위해서입니다. 사실 if문이 있든 말든 동작에 지장은 없기 때문에 이 부분은 딱히 신경쓰지 않으셔도 됩니다. 편하실 대로 하십시요.

나머지 case와 default는 비슷하기 때문에 생략하였습니다.

- else if(i >= 72)

switch(i)

case 72:

......

break;

case 80:

......

break;

default:

......

break;

i가 72보다 크거나 같을 경우입니다. 즉 마지막 줄을 의미합니다. i값이 바꼈으므로 case의 값도 그에 맞게 바꿔주어야 합니다. 처리코드는 비슷하기에 생략하였습니다.

- else 

switch(i%9)

case 0:

......

break;

case 8:

......

break;

default:

......

break;

그 외의 줄을 처리하는 부분입니다. 즉 9<= i <= 71 인 부분이지요.

여기서 하나 유심히 보셔야 할부분은 switch(i%9)입니다. i값에 9를 나눈 나머지 값을 이용하여 case가 작동합니다. 이렇게 한 이유는 i값의 범위가 크다보니 일일이 case문을 작성해야하는데 너무 비효율적이라 생각하였기 때문입니다. i를 9로 나눈 나머지 값이 0이라면 첫번째 열을 의미하고, 나머지가 8이라면 마지막 열을 의미하고 그 외의 나머지 값은 중간열을 의미하므로 훨씬 간편하게 case문을 작성할 수 있습니다.

이제 마지막 함수를 살펴봅시다.

- void victoryCheck(void)

승리조건에 도달하였는지를 체크하는 함수입니다. 원리는 열려진 타일의 개수를 검사하여 열려진 타일이 10개(지뢰개수)일 경우 승리 메세지박스가 출력됩니다. 

- int i;

  int tilecount = 0;

  검사를 하기 위한 인덱스 변수 i와 타일의 개수를 저장시킬 변수 tilecount를 선언하였습니다.

- for(i=0;i<81;i++)

if(OpenClose_Tile[i] == 1)

tilecount = tilecount+1;

타일을 검사하여 열린 타일의 개수를 체크합니다.

- if(tilecount == 10)

MessageBox(Global_hWnd,L"지뢰를 다찾았습니다.  리!",

L"승리!",MB_ICONQUESTION | MB_OK);

for(i=0;i<81;i++)

if(buttonValue[i] == 0)

DestroyWindow(buttonHwnd[i]);

타일의 개수가 10개일 경우 승리 메세지박스를 띄우고 for문을 사용하여 남아있는 버튼을 전부 삭제합니다.

여기까지가 제가 작성한 함수들입니다.  다음포스팅에서는 리소스를 보여드리도록 하겠습니다.

Posted by englishmath
,

안녕하십니까? 이번 포스팅에서는 지뢰찾기의 알고리즘에 대해 간단히 설명하겠습니다. 먼저 소스코드를 봅시다.

보시다시피 Createlandmine2 함수가 제법 깁니다. 일단 차례대로 설명드리겠습니다.

- #include <stdlib.h>

srand함수를 사용하기 위해 선언하였습니다.

- #include <time.h>

time함수를 사용하기 위해 선언하였습니다.

- void Createlandmine();

지뢰를 랜덤으로 생성하는 함수입니다.

 - void Createlandmine2();

생성된 지뢰를 이용해 주변 타일들의 값을 생성하는 함수입니다.

 - void Print_arr(void);

만든 타일을 보여주는 함수입니다.

- int arr[9][9];

타일 값을 저장하기 위한 2차원 배열을 전역변수로 선언하였습니다.

이제 main함수를 살펴봅시다.

- srand((int)time(NULL));

srand함수가 나왔습니다. MSDN에서 이 함수를 살펴봅시다.

이 함수는 쉽게 얘기하면 난수 생성을 위한 seed값을 설정해주는 함수입니다.

난수란 특별한 규칙이 없는 임의의 수를 뜻하지요. 즉 랜덤한 값입니다. 

그렇다면 시드는 무엇을 의미할까요? 

기본적으로 컴퓨터에서 난수를 생성한다고 하면 컴퓨터는 내부의 난수표를 이용하여 난수를 생성합니다. 그런데 문제는 이 난수표 자체가 고정되어 있음으로 다음에 다시 난수표를 쓰면 항상 같은 순서로 숫자가 나오게 됩니다.

그래서 이를 방지하기 위해 보통 난수표를 여러개 만들어 놓고 매번 다른 난수표를 읽도록 하는데 이 난수표를 선택하는 기준을 시드라고 합니다. 그런데 또 여기서 문제가 발생하는 것이 이 시드값이 똑같으면 컴퓨터는 똑같은 난수표를 읽게 됩니다. 그러므로 우리는 이 시드값을 랜덤으로 주어 매번 다른 난수표를 읽도록 해야 합니다.

이 시드값을 랜덤으로 주는 방법은 여러가지가 있겠지만 대표적으로 시간을 이용하는 방법이 있습니다. 시간을 이용하여 시드값을 주기위해 여기서는 time함수를 사용하였습니다. time함수를 살펴봅시다.

MSDN의 설명에 의하면 이 time함수는 1970년 1월 1일 자정 이후 경과된 시간을 반환하는 함수라고 나와있습니다.  인자로 받는 timer는 반환값을 저장하는 포인터 변수입니다.  우리는 따로 저장할 필요가 없으므로 매개변수에 NULL값을 주었습니다.

이렇게 하면 시간은 고정될 수 없는 값이므로 시드는 항상 랜덤일 것이고, 시드가 항상 랜덤이므로 시드를 이용해 뽑아내는 난수도 랜덤이 될 것입니다. 추가로 time함수가 반환하는 값은 time_t인데 srand에서 받는 seed값은 int이므로 강제형변환을 이용하였습니다. 다음으로 넘어갑시다.

- printf("지뢰 생성, 지뢰 == 0\n\n");

  Createlandmine();

  Print_arr();

지뢰를 생성하는 구문입니다. 여기서 지뢰는 숫자 0을 의미합니다. 지뢰가 생성되고 나면 결과값을 보기 위해 Print_arr함수를 호출하였습니다. 참고로 함수에 인자값이 없는 이유는 지뢰가 담긴 배열이 전역변수로 선언되었기 때문에 인자로 받을 값이 없기 때문입니다.

- printf("지뢰에 맞게 숫자값 변경\n\n");

  Createlandmine2();

  Print_arr();

생성된 지뢰를 이용하여 주변 타일들의 숫자를 바꾸는 함수입니다. 자신 주변의 타일들중 지뢰 개수가 있는 만큼 자기 타일의 값을 지정해줍니다.

이제 함수들을 살펴봅시다.

- void Createlandmine(void)

  int i,j,count=0;

반복문에 쓰일 i,j변수와 지뢰 개수를 저장할 변수 count를 선언하였습니다.

- while(count != 10)

count가 10이 아니면 반복합니다. 즉 만들어진 지뢰가 10개가 아니면 반복합니다.

- count=0;

count를 0으로 만들어줍니다. 만들어진 지뢰가 10개가 되지 않을 경우 다시 만들기 위해 count를 초기화합니다.

- for(i=0;i<9;i++)

  for(j=0;j<9;j++)

2차원 배열을 조작하기 위해 2중 for문을 돌립니다.

- arr[i][j] = rand()%8;

arr[i][j]에 랜덤값을 집어넣습니다. 이 때 rand함수가 사용되었는데 이 함수를 살펴봅시다.

설명에 따르면 이 함수는 0에서 32767사이에서 랜덤한 정수를 반환한다고 나와 있습니다. 그런데 우리가 원하는 것은 랜덤으로 나오는 지뢰 즉 숫자 0만 필요합니다. 그렇다고 그냥 rand함수를 쓰자니 0이 나올 확률이 매우 적어집니다. 1/32768 확률이니까요. 

초급 지뢰찾기에서는 지뢰는 10개, 타일은 총 81개 입니다. 즉 확률은 약 1/8입니다. 그러므로 우리는 확률을 맞춰주기 위해 rand함수의 결과값에 %8를 붙여 0~7사이의 값만 나오게 하였습니다. 

- if(arr[i][j] == 0)

  count++;

랜덤으로 생성된 값이 0일 경우 count를 하나 증가시킵니다. 즉 지뢰가 생성되었을 경우 지뢰 개수를 의미하는 count를 증가시킨다는 뜻이지요.

이렇게 모든 for문을 다 실행하고 나면 arr에는 각 인덱스에 0~7값이 들어가게 됩니다. 그런데 지뢰를 의미하는 0 값이 10개를 넘거나, 10개가 안될 수 있으므로 while문을 이용하여 지뢰가 딱 10개만 생성되도록 해주었습니다.

자 다음은 이렇게 생성된 지뢰를 이용하여 주변 타일들의 값을 정하는 함수를 설명하겠습니다.

- void Createlandmine2(void)

  int i,j,count;

for문에 사용할 i,j변수와 주변 타일에 존재하는 지뢰 개수를 저장할 count를 선언하였습니다. 이 count의 값에 따라 타일의 숫자값이 바뀝니다.

- for(i=0;i<9;i++)

  for(j=0;j<9;j++)

arr을 조작하기 위해 2중 for문을 돌립니다.

- count = 0;

하나의 타일의 값 지정이 끝나면 당연히 초기화를 시켜주어야 합니다. 뒤쪽에서 처리를 해도 되지만 count를 선언할 때 초기화를 시켜주지 않았기 때문에 앞쪽에서 초기화를 하였습니다.

- if(arr[i][j] != 0)

타일의 값이 지뢰인지 아닌지를 검사합니다. 지뢰이면 if문을 건너뛰고 다음 타일을 검사합니다. 지뢰가 아니라면 값을 지정해주기 위해 if문을 실행합니다.

- if(i == 0)

i가 0일 경우입니다. 여기서 i는 행을 의미하지요.  즉 첫번째 행일 경우를 의미합니다. 첫번째 행일 경우 if문을 실행합니다.

- if(j == 0)

if(arr[i][j+1] == 0)

count++;

if(arr[i+1][j+1] == 0)

count++;

if(arr[i+1][j] == 0)

count++;

j가 0일 경우입니다. 여기서 j는 열을 의미하지요.  즉 첫번째 열일 경우를 의미합니다. 이 if문은  if(i == 0)안에 포함되어 있으므로 첫번째 행의 첫번 째 열 일 경우 if문을 실행하라는 뜻입니다.

그 다음 코드는 주변 타일이 지뢰인지 아닌지를 검사하는 코드입니다. 현재 i는 0이고 j도 0이므로 이 arr]i][j]는 첫번째 줄의 첫번째 열을 의미합니다. 이 타일은 검사할 수 있는 주변 타일이 우측타일, 우측하단타일, 하단타일 이 세개의 타일밖에 없으므로 이 세개의 타일만 검사하도록 하였습니다. 검사를 하여 이 타일들 중에 지뢰를 가진 타일이 나오면 count를 하나 증가시키도록 하였습니다.

- else if(j == 8)

if(arr[i+1][j] == 0)

count++;

if(arr[i+1][j-1] == 0)

count++;

if(arr[i][j-1] == 0)

count++;

j가 8일 경우의 타일 검사 처리부분입니다. i가 0이고 j는 8이므로 첫번째 줄의 마지막 열을 의미합니다. 여기서는 검사할 수 있는 주변 타일이 하단타일, 좌측하단타일, 좌측타일 밖에 없으므로 이 세개의 타일만 검사하여 지뢰일 경우 count를 증가시키도록 하였습니다.

- else

if(arr[i][j+1] == 0)

count++;

if(arr[i+1][j+1] == 0)

count++;

if(arr[i+1][j] == 0)

count++;

if(arr[i+1][j-1] == 0)

count++;

if(arr[i][j-1] == 0)

count++;

 j가 0도 아니고 8도 아닌 값일 때의 타일 검사 처리입니다. 즉 첫번 째 줄의 중간 열(1~7)을 의미합니다. 여기서 검사할 수 있는 주변 타일은 우측타일, 우측 하단 타일, 하단타일, 좌측 하단 타일, 좌측타일 입니다. 

- else if(i == 8)

if(j == 0)

if(arr[i-1][j] == 0)

count++;

if(arr[i-1][j+1] == 0)

count++;

if(arr[i][j+1] == 0)

count++;

이번에는 i가 8일 때의 처리입니다. 즉 마지막 줄을 의미하지요. 마지막 줄에서 j가 0일 때 if문을 실행하므로 마지막 줄의 첫번째 열이라고 보시면 됩니다. 여기서 검사할 수 있는 주변 타일은 상단타일, 상단우측타일, 우측타일 입니다.

- else if(j == 8)

if(arr[i-1][j] == 0)

count++;

if(arr[i-1][j-1] == 0)

count++;

if(arr[i][j-1] == 0)

count++;

이번에는 i가 8이고 j가 8일 때의 처리입니다. 즉 마지막 줄의 마지막 열을 의미하지요. 여기서 검사할 수 있는 주변 타일은 상단타일, 상단좌측타일, 좌측타일 입니다.

- else

if(arr[i][j+1] == 0)

count++;

if(arr[i-1][j+1] == 0)

count++;

if(arr[i-1][j] == 0)

count++;

if(arr[i-1][j-1] == 0)

count++;

if(arr[i][j-1] == 0)

count++;

이번에는 i가 8이고 j가 1~7일 때의 처리입니다. 즉 마지막 줄의 중간열(1~7)을 의미합니다. 여기서 검사할 수 있는 주변 타일은 우측타일, 상단우측타일, 상단타일, 상단좌측타일, 좌측타일 입니다.

else

if(j == 0)

if(arr[i-1][j] == 0)

count++;

if(arr[i-1][j+1] == 0)

count++;

if(arr[i][j+1] == 0)

count++;

if(arr[i+1][j+1] == 0)

count++;

if(arr[i+1][j] == 0)

count++;

이번에는 i가 1~7이고 j가 0일 때의 처리입니다. 즉 중간 줄(1~7)의 첫번째 열을 의미합니다. 여기서 검사할 수 있는 주변 타일은 상단타일, 상단우측타일, 우측타일, 하단우측타일, 하단타일입니다.

- else if(j == 8)

if(arr[i-1][j] == 0)

count++;

if(arr[i-1][j-1] == 0)

count++;

if(arr[i][j-1] == 0)

count++;

if(arr[i+1][j-1] == 0)

count++;

if(arr[i+1][j] == 0)

count++;

i가 1~7이고 j가 8일 때의 처리이므로 중간 줄의 마지막 열을 의미합니다. 여기서 검사할 수 있는 주변 타일은 상단타일, 상단좌측타일, 좌측타일, 하단좌측타일, 하단타일 입니다.

- else

if(arr[i-1][j-1] == 0)

count++;

if(arr[i-1][j] == 0)

count++;

if(arr[i-1][j+1] == 0)

count++;

if(arr[i][j+1] == 0)

count++;

if(arr[i][j-1] == 0)

count++;

if(arr[i+1][j-1] == 0)

count++;

if(arr[i+1][j] == 0)

count++;

if(arr[i+1][j+1] == 0)

count++;

i가 1~7이고 j가 1~7일 때의 처리입니다. 여기서는 주변 타일을 전부 검사할 수 있는 위치이지요. 그렇기에 자신의 타일을 둘러싸고있는 8개의 타일들을 검사합니다. 

코드가 조금 길지만 알고리즘 자체는 간단한 편입니다. 이제 count를 이용하여 타일의 값을 정해주는 부분을 살펴봅시다.

- switch(count)

case 1:

arr[i][j] = 1;

break;

case 2:

arr[i][j] = 2;

break;

case 3:

arr[i][j] = 3;

break;

case 4:

arr[i][j] = 4;

break;

case 5:

arr[i][j] = 5;

break;

case 6:

arr[i][j] = 6;

break;

case 7:

arr[i][j] = 7;

break;

case 8:

arr[i][j] = 8;

break;

default:

arr[i][j] = 9;

break;

count의 값, 즉 주변에 있는 지뢰 개수에 따라 타일의 값을 지정해주는 부분입니다. 지뢰가 하나 있을 경우 값을 1, 2개가 있으면 2,.... 이런식으로 8까지 값을 저장합니다.

여기서 하나 아셔야 할것은 default인데 이 부분은 count값이 1~8이 아니라는 것을 의미합니다. 즉 지뢰가 하나도 없다는 것을 의미하지요. 그러므로 case 0으로 처리할 수도 있습니다. 아무튼 지뢰가 하나도 없을 경우에는 값을 9로 설정합니다. 이 9라는 값은 빈타일을 의미합니다. 

위와 같은 타일을 뜻하는 것이지요. 이렇게 최종적으로 한 타일의 값을 지정해주고 나면 count값은 for문을 다시 시작할 때 초기화됩니다.

자 이제 남은 것은 타일을 출력하는 함수 Print_arr인데 이것은 간단하니 생략하겠습니다. 자 이제 결과를 한 번 봅시다.

위의 배열은 지뢰를 생성한 배열입니다. 지뢰를 의미하는 0이 무작위로 10개 생성된 것을 확인할 수 있습니다.

밑의 배열은 지뢰값을 이용하여 주변타일들의 값을 바꿔준 배열입니다. 제대로 배치가 되었는지 한번 확인해보셔도 좋습니다.

다시 실행하면 랜덤으로 바뀌는 것을 확인할 수 있습니다.

같은 원리로 중급, 고급 난이도의 지뢰도 구현할 수 있습니다.

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

Posted by englishmath
,

안녕하십니까. 이번에는 c언어로 지뢰찾기를 만드는 것을 포스팅하겠습니다. 물론 실력이 부족하여 100퍼 똑같이 만들어내기는 힘들어서 최대한 비슷하게 만들어보았습니다. 모델은 windows7 지뢰찾기 입니다.

일단 제가 만든 지뢰찾기는 기존의 지뢰찾기와는 많이 다릅니다. 기존의 지뢰찾기는 소리, 애니메이션 등의 이벤트가 추가되어있으며 난이도설정, 다시시작 등의 여러 기능이 있지만 제가 만든 지뢰찾기는 최소한의 기능만 넣었습니다. 이러한 점은 나중에 차차 기능을 추가하겠습니다.

먼저 완성된 지뢰찾기 화면을 보여드리겠습니다.


다음 포스팅에서 본격적으로 시작하겠습니다.

Posted by englishmath
,

안녕하십니까. 저번에 작성했던 소스를 추가작성하여 프로그램을 동작시켜 봅시다.

먼저 소스를 보겠습니다.

아는 분의 조언으로 글꼴을 바꿨습니다. 혹시 원하는 글꼴이 있으시다면 바꿔드리겠습니다.

프로그램 동작을 위해 소스코드를 수정한 것이므로 WinMain에서는 수정한 것이 없습니다. WndProc함수를 봅시다.

- TCHAR regcodeStr[50]

사용자가 입력한 값을 저장시키기 위한 배열을 선언하였습니다.

- int Message_result

메세지박스로 인해 호출된 메세지의 결과값을 저장하기 위해 선언하였습니다.

WM_COMMAND부분을 봅시다.

- case IDM_PROGRAMM_EXIT1:

PostQuitMessage(0);

break;

이 IDM_PROGRAMM_EXIT1은 리소스 메뉴를 의미합니다. 이 상수값은 리소스 헤더파일에 정의되어 있습니다. 

좀 더 자세히 알아보기 위해 ResEdit로 살펴봅시다.

IDM_PROGRAMM_EXIT1는 Programm의 Exit를 의미하는군요.

즉 case IDM_PROGRAMM_EXIT1: 는 Exit를 눌렀을 때의 동작을 뜻하는 것을 알 수 있습니다.

그다음 밑의 코드내용을 보면 PostQuitMessage함수를 호출한다고 되어있습니다. 즉 프로그램을 종료시키라는 뜻입니다. 간단하지요?

- case IDM__ER1:

MessageBox(hWnd,L"VB5-CrackMe 1.0 by hackingboy [DCD]",

L"VB5-Crackme 1.0",MB_OK);

break;

IDM__ER1을 살펴봅시다.

?er의 ID라는 것을 알 수 있습니다. 원본에선 ?er를 누르면 메세지박스가 띄워집니다. 그래서 여기에서도 똑같이 띄웠습니다. 한 가지 주의하실 점이 있다면 메세지박스의 첫번째 인자에 hWnd를 넣어주어야 합니다. 만약 이 값이 NULL로 지정되어있으면 메세지박스가 중복으로 생성되기 때문입니다.

- case REGISTRIEREN:

Registrieren 버튼을 눌렀을 때의 동작입니다.

- GetWindowText(regcode,regcodeStr,11);

GetWindowText함수로 regcode에 적힌 문자를 11개만큼 가져와서 regcodeStr에 저장시킵니다. 가져오는 문자열에는 널값이 포함되어 있으니 실제로 가져오는 글자 수는 10개입니다. 

- if(lstrcmp(regcodeStr,L"2G83G35Hs2") == 0)

GetWindowText함수로 인해 저장된 regcodeStr의 문자열과 2G83G35Hs2랑 같은지 안같은지를 비교합니다. 두 문자열이 같으면 lstrcmp는 0을 반환합니다.

- Message_result = MessageBox(hWnd,L"Danke, das Passwort ist richtig !",

L"Gluckwunsch !",MB_ICONWARNING | MB_OKCANCEL);

두 문자열이 같을 경우 성공메세지를 출력합니다. 여기서 MB_ICONWARNING 인자가 쓰였는데 이 인자는 느낌표 아이콘을 메세지에 넣겠다는 뜻입니다. 그리고 MB_OKCANCEL을 인자로 주어 확인, 취소 버튼을 메세지에 넣었습니다. 어떤 버튼을 누르냐에 따라 반환값이 달라지므로 Message_result에 반환값을 넣었습니다.

- if(Message_result != 1)

PostQuitMessage(0);

Message_result의 값이 1이 아니면 프로그램을 종료합니다. MessageBox에서 확인 버튼을 누르면 반환값은 1이 됩니다. 즉 위의 if문은 확인버튼을 누르지 않았을 시 프로그램을 종료하라는 코드입니다. 실제로 오리지널 03.exe에서는 성공문자열에서 취소버튼을 누르면 프로그램을 종료합니다.

- else

MessageBox(hWnd,L"Error ! Das Passwort ist falsch !",

L"PASSWORT FALSCH !",MB_ICONERROR | MB_OK);

입력한 값이 정답과 다를 경우 실패 문자열을 출력합니다. MB_ICONERROR 인자는 X표 아이콘을 의미합니다.

- case Exit:

PostQuitMessage(0);

break;

Exit버튼을 눌렀을 때의 동작입니다. 당연히 종료하도록 코드를 작성하였습니다.

생각보다 간단하지요? 프로그램을 동작시켜 봅시다.

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

 


Posted by englishmath
,

안녕하세요. 이번에는 우리가 코드엔진에서 풀어본 문제 중 하나인 bagic의 03문제인 프로그램을 만들어보겠습니다. 일단 동작을 봅시다.

실행시 메세지박스가 하나 띄워집니다. 확인과 취소 버튼이 있는데 확인 버튼을 누른시 아래와 같은 창이 뜹니다. 반대로 취소 버튼을 누르면 그냥 종료가 되버립니다.

동작을 확인했으니 일단 겉만 만들어봅시다. 제가 작성한 소스를 보여드리겠습니다.

저번 포스팅에서 왠만한 api함수들은 전부 설명드렸으니 새로 나온 함수 혹은 코드만 설명하겠습니다.

- #include "resource.h"

리소스가 선언되어 있는 헤더파일을 불러옵니다. 이것은 나중에 설명드리겠습니다.

- #define REGCODE 0

  #define REGISTRIEREN 1

  #define Exit 2

edit컨트롤과 버튼컨트롤을 제어하기 위해 정의된 상수입니다.

- HWND regcode;

edit컨트롤의 핸들을 저장시킬 변수를 전역변수로 선언하였습니다.

WinMain을 살펴봅시다.

- int Message_result;

MessageBox함수의 결과값을 저장시킬 변수입니다.

- HBRUSH bgBrush;

윈도우의 배경색을 저장할 용도로 HBRUSH 형의 변수를 선언하였습니다.

- bgBrush = CreateSolidBrush(RGB(238,238,238));

HBRUSH형의 변수에 CreateSolidBrush의 결과값을 집어넣습니다. CreateSolidBrush의 정의를 봅시다. MSDN을 참고합시다.

컬러를 인자로 받아 HBRUSH 형태로 반환합니다. 즉 이 함수는 단색 브러쉬를 만드는 함수입니다. 윈도우의 배경색을 주기 위해 사용하였습니다.

- wndclass.hbrBackground = bgBrush;

윈도우의 배경색을 아까 만든 bgBrush로 지정합니다. 여기서는 RGB(238,238,238)로 만들었으므로 회색 비슷한 색깔로 지정됩니다.

- wndclass.lpszMenuName = MAKEINTRESOURCE(IDR_MENU1);

윈도우의 메뉴를 IDR_MENU1로 지정합니다. 이 IDR_MENU1는 리소스를 의미합니다. 이 리소스 작성법은 나중에 설명드리겠습니다.

그리고 MAKEINTRESOURCE함수가 사용되었습니다. MSDN을 참고합시다.

정수형을 받아서 문자열로 반환합니다. 즉 이 함수는 리소스번호를 받아 리소스 문자열로 반환시킵니다. 우리가 만든 메뉴 리소스인 IDR_MENU1이 헤더파일에 상수로 정의되어 있으므로 이 IDR_MENU1 번호를 문자열로 변환시켜 lpszMenuName에 넣어주었습니다.

 - wndclass.lpszClassName = L"a";

클래스 이름을 지정합니다. 여기서는 그냥 a로 주었습니다.

- Message_result = MessageBox(NULL,L"Entferne diesen Nag, oder bekomme das richtige Passwort heraus !",L"Nag Meldung",MB_ICONQUESTION | MB_OKCANCEL);

MessageBox함수를 사용하여 메세지박스를 띄웁니다. 이 메세지박스는 프로그램을 실행할 때 제일 먼저 나오는 메세지박스입니다.

여기서 눈여겨보실 부분은 마지막 인자부분인데 ,MB_ICONQUESTION | MB_OKCANCEL입니다. MB_ICONQUESTION은 질문 아이콘을 메세지박스에 띄우는 인자입니다. 즉

빨간박스의 저 물음표 아이콘을 띄운다는 뜻입니다. 

MB_OKCANCEL은 OK버튼과 CANCEL버튼을 만든다는 뜻입니다. MessageBox함수는 각각의 버튼을 눌렀을 때 반환되는 결과값이 다릅니다.

- if(Message_result == 1)

Message_result는 MessageBox함수의 결과값이죠. 즉 어떤 버튼을 눌렀느냐에 따라 if문을 실행하냐 안하냐를 결정합니다. 결과값이 1이면 확인버튼을 눌렀다는 뜻이므로 조건문이 참이 되어 if문 안의 코드를 실행합니다. 만약 취소를 눌렀다면 if문을 실행하지 않고 else문을 실행합니다.

- hwnd = CreateWindow(L"a",L"DCD VB5-CrackMe 1.0",WS_SYSMENU | WS_VISIBLE,73,73,253,181,NULL,NULL,hInstance,NULL);

윈도우를 생성합니다. 여기서는 윈도우 스타일에 WS_SYSMENU 인자를 주었습니다. 이 인자는 닫기 창만 존재하는 윈도우를 의미합니다. 덧붙여서 이 스타일이 적용된 윈도우는 크기를 바꿀 수 없습니다.

- else

return 0

메세지박스의 결과값이 1이 아닐 경우 프로그램을 종료합니다.

나머지 소스코드는 앞시간에 전부 설명드렸으니 따로 설명드리진 않겠습니다.

자 이제는 리소스를 만들어봅시다. 리소스 파일부분에서 rc파일을 만듭니다.

이제 만들었으니 편집을 할 차례입니다. 저는 resedit를 사용하겠습니다.

아 그리고 앞 포스팅에서 깜박하고 빼먹은 부분이 있는데 resedit를 사용하시는 분들은 resedit의 경로를 따로 지정해주어야 합니다.

Options의 Preferences...를 누릅시다.

General의 경로를 추가해주는데 어떤 경로로 설정하냐면 Windows.h 파일이 들어있는 경로로 설정해주면 됩니다.

경로를 지정해주었으면 아까 만든 리소스 파일을 열어봅시다. 보통 리소스파일은 생성시 소스파일과 동일한 위치에 생성됩니다.

해당 rc파일을 성공적으로 여셨다면 메뉴 리소스를 추가합시다.

IDR_MENU1이라는 리소스가 생성되었습니다. 우측에 보시면 Type here이 보이시죠? 이 부분이 바로 윈도우의 메뉴부분입니다. 03.exe에 맞게 직접 메뉴를 지정해줍시다.

지정을 다하시고 나서 해당 메뉴를 누르면 밑에 하위항목이 보입니다. 03.exe에는 하위항목이 Exit밖에 없으므로 Exit를 적어줍시다.

메뉴가 다 작성되었습니다. 이제 메뉴의 설정을 변경해봅시다.

메뉴 ?er을 누르고 속성부분에서 Popup부분을 False로 설정해줍니다. 이 Popup 부분은 이 메뉴가 하위항목이 있냐 없냐를 설정해주는 부분입니다. Programm 메뉴는 하위항목 Exit가 있기 때문에 이 부분을 True로 설정해주어야 하지만 ?er은 하위항목을 표시하지 않기 때문에 이 Popup부분을 False로 고쳐주어야 합니다.

여기까지가 메뉴였습니다. 이번에는 아이콘을 추가합시다. 아이콘 추출법과 추가하는 방법은 앞시간에 올렸으므로 따로 설명드리지 않겠습니다.

다 만들었으면 저장해줍시다. 그리고 비주얼 스투디오로 돌아와 리소스의 헤더파일을 추가합시다.

resource.h 파일을 찾아 추가해주시면 됩니다. 이 헤더파일 안에는 우리가 만든 리소스들이 상수로 정의되어있습니다. 이렇게 추가된 resource.h 파일을 사용하기 위해서는 소스코드이 위에 include "resource.h" 를 추가해주어야 합니다. 

여기까지 다되셨으면 우리가 만든 리소스를 사용하기 위해 소스코드를 약간 수정해줍시다. 소스코드에서는 메뉴가 추가되어있지만 아이콘을 따로 추가가 되어있지 않습니다. 그러므로 아이콘을 새로 추가해 줍시다. 다음과 같이 수정합시다.

 - wndclass.hIcon = LoadIcon(NULL,IDI_APPLICATION); -> 기존코드

 - wndclass.hIcon = LoadIcon(hInstance,MAKEINTRESOURCE(IDI_ICON1));

-> 수정코드

LoadIcon의 인자를 변경해줍니다. 기존에 정의되어 있는 아이콘을 사용할 경우에는 첫 인자 값을 NULL로 주지만 그 외의 아이콘을 사용할 경우에는 첫 인자에 hInstance를 줍니다. 두번째 인자는 우리가 만든 아이콘을 문자열 형태로 넣어주시면 됩니다.

자 이제 코드 설명이 전부 끝났습니다. 한번 실행을 시켜 봅시다.

그럴듯하게 만들어졌습니다. 다음포스팅에서는 동작을 구현해봅시다.

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

Posted by englishmath
,

안녕하십니까. 드디어 길고 길던 abex crack me2 제작이 끝을 보이기 시작합니다. 자 한 번 해봅시다.

소스를 한 번 봅시다.

허허 양이 좀 많습니다. 코드를 살펴봅시다.

- #define name 10

  #define serial 20

  #define check 0

  #define about 1

  #define quit 2

자 우선 define을 이용해 상수를 정의해줍시다.

여기에 정의된 상수값은 각각 컨트롤의 식별자로 사용하기 위해 정의하였습니다. 

name,serial -> edit

check,about,quit -> button

즉 각각의 id를 의미하는데 쉽게 말하면 button을 눌렀을 때 어떤 button이 눌러졌는지 구분해야 하지 않겠습니까? 그걸 위해 버튼의 식별자를 정의하였습니다. 그리고 에디트의 식별자도 정의해놓았는데 이 식별자는 실제로는 쓸 데가 없습니다만 쓸일이 없다고 정의를 하지 않으니 알고리즘이 꼬여 버려서 결국 정의해주었습니다.

- LPTSTR Serial(LPTSTR NameStr);

NameStr이란 유니코드문자열을 받아 유니코드문자열로 반환하는 함수입니다.

즉 NameStr에 저장된 name(값)을 읽어서 그 값에 맞는 시리얼을 제작해 반환해주는 함수라고 보시면 됩니다. 이 함수는 뒤쪽에 구현되어 있습니다.

- HWND hName,hSerial;

이름과 시리얼을 읽어올 edit 들의 핸들을 저장시키기 위해 선언하였습니다. 이 핸들을 WinMain의 지역변수가 아닌 전역변수로 선언시킨 이유는 WndProc함수에서 읽어오기 위함입니다.

- CreateWindow(L"edit",NULL,WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL,

10,57,165,18,hWnd,NULL,hInstance,NULL);

- CreateWindow(L"edit",NULL,WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL,

10,113,165,18,hWnd,NULL,hInstance,NULL);

-> 기존코드들

- hName = CreateWindow(L"edit",NULL,WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL,10,57,165,18,hWnd,(HMENU) name,hInstance,NULL);

- hSerial = CreateWindow(L"edit",NULL,WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL,10,113,165,18,hWnd,(HMENU) serial,hInstance,NULL);

->수정코드들

edit를 생성하는 윈도우의 핸들을 저장시키기 위해 수정하였습니다. 그런데? 인자 하나가 다른 부분이 있지요? 인자 8번째의 값이 다릅니다. 기존코드에서는 NULL값을 주었습니다만 수정코드에서는 (HMENU) name과 (HMENU) serial을 주는 것을 알 수 있습니다. 이 8번째 인자는 해당 윈도우의 식별자를 받는 인자입니다. 우리가 앞에서 선언한 상수들을 사용하였지요. 즉 첫번째 edit의 식별자는 name, 두번째 edit의 식별자는 serial입니다. 물론 여기서는 edit의 식별자를 사용할 필요가 없지만 버튼들의 동작구현을 위해 선언하였습니다. 아 그리고 이 인자는 반환값을 HMENU로 받기 때문에 앞에 (HMENU)를 주어 HMENU로 넘겨주었습니다.

- CreateWindow(L"button",L"Check",WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,200,40,65,25,hWnd,NULL,hInstance,NULL);

  CreateWindow(L"button",L"About",WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,200,80,65,25,hWnd,NULL,hInstance,NULL);

  CreateWindow(L"button",L"Quit",WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,200,120,65,25,hWnd,NULL,hInstance,NULL);

-> 기존코드들

- CreateWindow(L"button",L"Check",WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,200,40,65,25,hWnd,(HMENU) check,hInstance,NULL);

  CreateWindow(L"button",L"About",WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,200,80,65,25,hWnd,(HMENU) about,hInstance,NULL);

  CreateWindow(L"button",L"Quit",WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,200,120,65,25,hWnd,(HMENU) quit,hInstance,NULL);

-> 수정코드들

역시 마찬가지로 각 버튼들에게 식별자를 주었습니다.

WinMain의 설명은 끝났군요. 이제 WndProc로 넘어갑시다.

- TCHAR SerialStr[50] = L"", NameStr[50] = L"",AnswerSerial[50] = L"";

TCHAR 형의 문자열 변수를 세개 선언하고 초기화 시켜주었습니다. 크기는 전부 50으로 주었습니다. 이 문자열 변수들은 사용자가 입력한 시리얼,이름 그리고 이름을 기반으로 생성된 올바른 시리얼 값을 유니코드로 저장시키기 위해 선언하였습니다.

- int result;

사용자가 입력한 시리얼과 올바른 시리얼 값을 비교한 값이 저장될 변수입니다.

- case WM_COMMAND:

메세지가 WM_COMMAND일 때의 처리를 하기 위해 만들었습니다. 이 WM_COMMAND메세지는 윈도우에서 각각의 메뉴(컨트롤 등)을 누를 때 발생합니다.

- switch(wParam)

위에서 WM_COMMAND메세지가 메뉴를 누를 때 발생한다고 하였지요? 이 WM_COMMAND메세지가 발생하였을 때 어떤 메뉴를 눌렀는가에 대한 정보가 추가적으로 WParam(세번째 인자)로 들어갑니다.  그러므로 각 메뉴에 대한 처리를 하기위해 switch함수를 사용하였습니다.

 - case check:

wParam의 값이 check 일때의 처리입니다. 이 check는 앞에 우리가 선언한 상수이자 check버튼의 식별자 값입니다. 즉 check버튼을 눌렀을 때의 처리를 위해 선언해주었습니다. 

- if(GetWindowText(hName,NameStr,5) < 4)

MessageBox(hWnd,L"Please enter at least 4 chars as name!",L"Error",MB_OK);

GetWindowText가 나왔군요. 정의를 한 번 봅시다.

해당 핸들의 내용을 읽어옵니다. 인자를 하나씩 살펴봅시다.

*hwnd

내용을 가져올 윈도우의 핸들을 의미합니다.

*lpString

내용을 가져와서 저장시킬 유니코드 문자열을 의미합니다.

*nMaxCount

윈도우에서 내용을 가져올 때 얼마만큼 가져올 것인지를 묻습니다.

즉 이함수는 edit에서 사용자가 입력한 값을 가져오기 위해 사용하였습니다. 

핸들값이 hName이므로 첫번째 edit 윈도우의 값 중 5글자를 읽어와 NameStr에 저장시키고 읽어온 문자의 개수를 반환합니다. 5글자를 읽어오라고 하였지만 한 글자는 null값을 읽어오므로 실제로 들고오는 값은 4글자입니다. 그리고 문자의 개수를 반환할 때에는 null값은 카운트 하지 않으므로 4라는 값을 반환합니다.

쉽게 얘기하면 읽어온 글자만큼 반환합니다. 참고로 읽어올 글자가 없을 경우 0을 반환합니다.

즉 GetWindowText함수를 사용하여 읽어온 입력된 이름 글자수가 4개 미만일 경우 messeagebox함수를 출력하여 이름을 4글자 이상 입력하라고 사용자에게 알립니다.

else문을 봅시다.

- else

이름 글자수가 4개 이상인 경우는 위의 if문의 조건이 거짓이 되어 else문을 실행하게 됩니다. 그 때의 처리구문입니다.

- GetWindowText(hSerial,SerialStr,100);

hSerial에서 100만큼의 글자를 읽어들여 SerialStr에 저장시킵니다. 왜 여기서는 100글자를 읽어오냐고 하니 시리얼 칸에 입력된 값을 전부 가져오기 위함입니다. 물론 시리얼 칸에 입력된 값이 100글자를 넘어가면 다 가져오진 못하지만 그래도 왠만한 시리얼 값을 전부 들고오기 위해 100을 넣어주었습니다.

추가로 위의 Name은 입력한 값을 전부 읽어오지 않고 4만큼만 읽어오게끔 코딩을 해주었는데 이유는 기존의 abex crack me2가 이름의 값을 4만큼 읽어들여 그 4글자로 시리얼을 만드므로 이에 맞춰주기 위해 이름을 4글자만 읽어들였습니다.

- strcpy(AnswerSerial,Serial(NameStr));

strcpy함수는 문자열을 복사하는 함수이죠. Serial함수를 호출한 후 이 반환값을 AnswerSerial에 복사시킨다는 뜻입니다. 여기서 이 Serial함수는 제가 임의로 만든 시리얼 생성 함수입니다. 

- result = lstrcmp(AnswerSerial,SerialStr);

시리얼키가 저장된 AnswerSerial과 사용자가 입력한 시리얼이 저장된 SerialStr을 비교하여 결과갑을 result에 저장됩니다. 일반 strcmp가 아닌 lstrcmp를 사용하였는데 이유는 유니코드 문자열을 비교하기 위해서입니다.

이 lstrcmp의 반환값은 두 문자열이 같을 시 0을 반환합니다. 

-  if(result == 0)

MessageBox(hWnd,L"Yep, this key is right!",L"Congratulations",MB_OK);

   else 

MessageBox(hWnd,L"Nope, this serial is wrong",L"Wrong serial!",MB_OK);

lstrcmp의 반환값을 받은 이 result의 값이 0일 경우 성공문자열을 출력하고 아닐 경우 실패 문자열을 출력합니다. result의 값이 0이라는 것은 사용자가 입력한 시리얼값과 알고리즘에 의해 생성된 정답 시리얼 값이 같다는 뜻이지요.

여기까지가 체크버튼을 눌렀을 때의 처리입니다. 이제 다른 버튼을 처리합시다.

- case about:

MessageBox(hWnd,L"abex' 2nd crackme\ncoded on 30th Nobember 2016",L"About",MB_OK);

about 버튼을 눌렀을 시의 처리입니다. 이 프로그램에 관한 메세지박스를 띄웁니다. abex crack me2의 about를 누르면 문자열 (엔터) 문자열 이런식으로 메세지박스가 띄워집니다. 그래서 우리도 문자열 중간에 \n을 줌으로써 개행이 되도록 하였습니다. 문자열 내용은 조금 바꿨습니다.

- case quit:

PostQuitMessage(0);

quit버튼을 눌렀을 때의 처리입니다. quit 버튼은 종료를 하는 버튼이므로 프로그램 종료를 위해 PostQuitMessage함수를 호출하였습니다.

일반적으로 이 PostQuitMessage함수는 WM_DESTROY에서 처리를 하는 것이 맞습니다. 하지만 이 WM_DESTROY메세지는 WM_CLOSE메세지가 DefWindowProc로 인해 처리가 되었을 때 발생하는 메세지입니다. WM_CLOSE.. 즉 윈도우 창이 닫혔을 때 발생하는 메세지인데 이 윈도우는 알다시피 팝업창이기 때문에 닫는 창이 없습니다. 즉 WM_CLOSE가 발생하지 않습니다. 그렇기에 WM_DESTROY에서 PostQuitMessage처리를 하지 않고 quit버튼을 눌렀을 때 닫히도록 코딩하였습니다.

자 이제 제가 만든 Serial 함수를 살펴봅시다. 그전에 잠깐 시리얼 알고리즘을 설명하겠습니다.

abex crack me2를 리버싱 해보신 분은 아시겠지만 시리얼 키를 만드는 알고리즘이 단순합니다. 4글자의 이름을 입력받은 후 각 글자를 하나씩 뽑아와서 그 글자의 16진수 값과 16진수 64를 더한 값을 시리얼로 사용합니다. 

ex) a(0x61) + 0x64 = 0xC5(시리얼)

Serial 함수를 살펴봅시다.

 - int i,j,Key;

for문에 쓰일 i,j 변수와 16진수 덧셈연산의 결과값을 저장할 Key값을 int로 선언합니다.

- int k = 0;

for문에 쓰일 k변수를 선언 후 0으로 초기화 시킵니다. 다만 위의 i,j하고는 조금 다른 의미로 쓰입니다. 

- TCHAR Key2[50] = L"", Key3[50] = L"";

Key2와 Key3 문자열 변수를 선언합니다. Key2는 한 글자의 시리얼값이 저장되고 Key3은 각 글자의 Key2값을 저장하기 위해 사용합니다.

- for(i=0; i<lstrlen(NameStr); i++)

이름을 시리얼로 바꾸기 위한 for문입니다. NameStr에 들어있는 글자수만큼 반복하도록 구현하였습니다.

- Key = NameStr[i] + 0x64;

사용자가 입력한 이름값 즉 NameStr에서 1글자를 뽑아와서 그 글자의 값과 16진수 64를 더해서 Key에 저장합니다. 다만 이렇게 연산을 할 경우에는 10진수로 연산이 됩니다. 

예를 들어 a를 가져왔을 때 a + 0x64를 연산하지요. 이럴 때 a의 10진수 값(아스키코드)인 97과 0x64의 10진수 값인 100을 더한다는 뜻입니다. 

즉 Key값에는 197이 들어가게 됩니다. 이 197의 16진수 값은 C5이므로 이 197을 16진수로 바꿀 필요가 있습니다.

- wsprintf(Key2, L"%X", Key);

앞에서 말한 정수값 Key를 16진수로 저장시키기 위한 코드입니다. wsprintf함수가 사용되었지요? 원래 sprintf를 써야 하는데 유니코드로 저장시켜야 하므로 wsprintf를 사용하였습니다. 

함수 이름에서 알 수 있듯이 이 sprintf함수는 출력시키는 함수입니다. 다만 일반 printf함수가 콘솔에 출력시키는 거라면 이 sprintf함수는 변수에 출력시킵니다.

즉 Key2란 변수에 %X, Key 형식으로 출력시킨다는 뜻입니다. %X는 16진수 출력을 의미하지요? 그리고 이에 대응하는 변수는 Key입니다. 

정리하면 Key값을 16진수로 변환시켜서 Key2에 출력합니다. 그러면 Key2에는 아까 16진수 연산의 결과값(정수) Key의 16진수 값이 문자열로 들어가집니다. 

- for(j=0; j<lstrlen(Key2); j++)

이 for문은 Key2에 들어있는 문자열의 길이만큼 반복합니다. Key2에 있는 16진수 값을 Key3에 저장시키기 위해서 사용하였습니다.

- Key3[k] = Key2[j];

Key2의 첫 글자부터 마지막 글자까지 Key3에 저장시키는 코드입니다. 이 때 Key3의 인덱스는 k변수를 사용하였는데 그 이유는 Key3의 인덱스는 for문이 시작되어도 초기화가 되면 안되기 때문입니다. 

- k++;

글자를 받았으니 k값을 하나 증가시킵니다.

- return Key3;

최종적으로 for문이 모두 처리되면 시리얼이 저장된 Key3을 리턴합니다. 이 리턴된 Key3은 lstrcpy함수에서 사용됩니다.

자 이제 진짜 다 끝났... 으면 좋겠지만 마지막 부분이 남았습니다. 바로 아이콘입니다.

현재 프로그램의 아이콘을 보면 

기본 아이콘으로 되어있지요? abex crack me2 아이콘을 볼까요?

아이콘이 참 예쁘지요? 우리도 이렇게 예쁜 아이콘을 프로그램에 적용시켜 봅시다.

먼저 리소스 해커로 abex crack me2의 아이콘 리소스를 가져옵시다.

저장시킵시다.

저장시키면 ico확장자를 가진 파일이 생성됩니다. 이 파일을 프로젝트 안에 넣어버립시다. 위치는 딱히 상관없지만 나중에 관리하기 용이하도록 해주기 위합니다.

생성이 된 것을 확인하였으면 리소스 파일을 생성합시다.

이 리소스라는 것은 메뉴,아이콘,커서 등의 데이터를 의미합니다. 우리는 아이콘 리소스를 사용해야 하므로 리소스 파일을 새로 생성시켜 주어야 합니다.

사실 제가 쓰고 있는 visual c++ 2010 express 프로그램은 리소스 편집기를 제공하지 않습니다. 그렇기에 저는 리소스 편집을 다른 방법으로 할 예정입니다. 다른 visual프로그램을 쓰고 계신분은 리소스 편집기를 제공하므로 그걸 쓰는 것이 좋습니다. 

파일을 생성시킬 때 이름을 Icon.rc로 바꿔줍시다. 메뉴에는 리소스가 없지만 이렇게 확장자를 바꿔주는 것만으로도 리소스 파일을 생성시킬 수 있습니다.

리소스 파일이 생성되었습니다. 제가 쓰는 프로그램은 편집 기능이 지원되지 않는군요.

자 그러면 저는 ResEdit를 쓰겠습니다. 이 ResEdit 프로그램은 리소스를 편집하고 저장할 수 있는 도구입니다. 다른 버전의 visual studio는 리소스 편집기를 사용할 수 있으니 여러분들은 편하신 대로 하시면 됩니다.

RESEdit를 열고 아까 생성한 리소스 파일을 열어봅시다.

리소스 파일을 여셨다면 리소스를 추가해 봅시다.

오른쪽마우스 -> Add Resource... -> Icon을 누릅시다.


아까 저장한 ico 파일을 불러오기 위해 위의 버튼을 체크합시다.

파일을 가져옵시다.

이 ico 파일의 경로를 절대로 지정할거냐 상대로 지정할거냐고 나옵니다. 우리는 상대로 지정해줍시다.

자 아이콘 리소스를 추가하였습니다. 저장시킨 다음 visual studio로 돌아갑시다.

우리가 만든 리소스를 사용하기 위해 리소스 파일을 추가합시다. 추가 - 기존 항목을 누릅시다.

리소스를 만들고 나면 그 리소스에 관한 내용을 담고 있는 헤더파일이 생성됩니다. 이 헤더파일은 rc파일과 동일한 경로에 생성됩니다.

헤더파일이 성공적으로 추가되었습니다. 이 헤더파일에는 앞서 만든 아이콘 리소스가 상수로 정의되어 있습니다. 헤더파일을 추가하였으면 소스코드 맨위에 헤더파일을 사용하기 위해 선언을 해 줍시다.

 - include "resource.h"

이 헤더파일을 선언하실 때 유의하실 부분이 있는데 경로를 잘 맞춰주어야 합니다.

소스코드를 수정합시다.

wndclass.hIcon = LoadIcon(NULL,IDI_APPLICATION); - > 기존코드

wndclass.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));

-> 수정코드

wndclass의 hIcon멤버를 위와 같이 수정하였습니다. 우리가 만든 리소스 아이콘을 사용하기 위함입니다. 세세한 설명은 다음 포스팅 때 설명드리겠습니다.

여기까지 다 되었으면 실행시켜봅시다.

작업표시줄을 보시면 아이콘이 등록된 것을 볼 수 있습니다. 파일을 한번 볼까요?

파일의 아이콘도 바뀌었습니다. 예~~

자 이제 진짜 완성되었습니다. 결과물 한번 봅시다.

네 이것으로 길고 긴 포스팅을 마치겠습니다. 감사합니다.

Posted by englishmath
,

 안녕하십니까. 저번 포스팅까지 작성했던 소스를 더 수정했습니다.

한번 보시지요.

바뀌거나 추가된 소스를 살펴 봅시다.

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

WS_POPUPWINDOW, 820, 462,

        280, 155, NULL, NULL, wndclass.hInstance, NULL); -> 기존코드

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

        WS_POPUPWINDOW | WS_VISIBLE, 820, 462,

        280, 155, NULL, NULL, wndclass.hInstance, NULL); -> 수정코드

윈도우를 생성할 때 스타일 옵션에 하나를 더 주었습니다. WS_VISIBLE라는 옵션입니다. 이 WS_VISIBLE은 ShowWindow 기능을 제공합니다. 즉 ShowWindow 함수가 없어도 윈도우 창이 보인다는 뜻이지요. 그래서 ShowWindow를 지우고 WS_VISIBLE을 넣어주었습니다.  사실 이  WS_VISIBLE은 나중에 알게 되어서 미리 처음에 작성할 때 적용하지 못했습니다.

- CreateWindow(L"edit",NULL,WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL,

10,57,165,18,hWnd,NULL,hInstance,NULL);

컨트롤을 만드는 코드입니다. 여기서 컨트롤이라 함은 사용자와의 인터페이스를 도와주는 윈도우 입니다. 즉 사용자로부터 입력을 받고 결과값을 보여주기 위해 사용합니다. 예로 버튼, 에디트 등이 있으며 이러한 컨트롤은 클래스가 이미 정의되어이 있습니다.

정의된 컨트롤을 쓰기 위해선 CreateWindow의 첫번째 인자인 클래스 이름을 바꿔주어야 합니다. 각각의 컨트롤은 정의된 클래스 이름이 있습니다.

button(버튼), static(텍스트), scrollbar(스크롤 바), edit(에디트) , listbox(리스트 박스), combobox(콤보 박스)

우리는 이중 사용자의 입력값을 받기 위해 edit라는 클래스를 사용하겠습니다. 원래 CreateWindow함수를 사용할려면 WinMain함수에서 보았듯이 윈도우 클래스를 선언하고.. 각각의 멤버를 정의하고... RegisterClass 함수를 이용해 윈도우클래스를 등록해야 합니다만 컨트롤은 미리 클래스가 등록되어 있기 때문에 그냥 갖다 쓰기만 하면 됩니다.

그다음 세번 째 인자인 윈도우 스타일을 봅시다. WS_CHILD가 적혀있습니다. 이 WS_CHILD 은 이 윈도우가 자식윈도우임을 나타냅니다. 즉 부모 윈도우가 꺼지면 이 자식 윈도우도 자동으로 꺼집니다. 만약 이 옵션이 없으면 이 윈도우는 독립된 윈도우로 인식하여 운영체제에 윈도우 창이 총 2개 띄워집니다. abex crack me2에서는 자식윈도우를 쓰고 있으니 우리도 자식 윈도우를 씁시다. WS_VISIBLE은 설명했으니 넘어갑시다. 

그 다음 눈에 들어오는 것이 ES_AUTOHSCROLL 옵션입니다. 이 옵션은 윈도우 클래스 edit의 전용옵션입니다. 어떤 옵션이냐면 말 그대로 자동으로 스크롤을 지원한다는 뜻입니다. 좀더 쉽게 얘기하면 이 edit라는 컨트롤이 사용자의 입력값을 받지 않습니까? 그런데 이 edit는 사용자가 입력칸에 값을 꽉 채웠을 때 더 못쓰게 할거냐 아니면 스크롤을 이용해서 계속 쓰게 할거냐를 결정합니다. 우리는 계속 쓰게 할 것이므로 ES_AUTOHSCROLL 옵션을 추가로 주었습니다.

8번째 인자에는 hWnd가 들어가 있습니다. 이 인자는 부모윈도우를 인자로 받습니다. 자식윈도우이므로 당연히 부모윈도우가 인자로 들어가야 합니다. 여기서는 팝업윈도우로 만들어진 윈도우가 부모윈도우이므로 이 부모윈도우의 핸들값인 hWnd를 넣어주었습니다.

이 edit창은 이름을 입력받습니다.

그다음은 달라진 것이 하나 있군요. 부모 윈도우는 wndclass.hInstance인데 자식 윈도우는 hInstance입니다. 어.. 별 차이는 없으니 넘어갑시다.

- CreateWindow(L"edit",NULL,WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL,

10,113,165,18,hWnd,NULL,hInstance,NULL);

두 번째 edit 창을 만듭니다. 이 edit창은 시리얼을 입력받습니다.

- CreateWindow(L"button",L"Check",WS_CHILD | WS_VISIBLE |   BS_PUSHBUTTON, 200,40,65,25,hWnd,NULL,hInstance,NULL);

이번에는 버튼을 만듭니다. 클래스 이름을 button으로 줌으로써 button을 생성합니다.물론 이 버튼도 전용옵션이 있습니다. BS_PUSHBUTTON입니다. 이 옵션은 이 버튼을 누르는 버튼으로 생성한다는 뜻입니다. 그 외에도 체크박스, 라디오 버튼 등의 옵션이 있는데 이것은 나중에 추가로 설명하겠습니다.

이 button의 보여지는 값은 Check입니다. 이 Check는 두번째 인자를 의미합니다. 그 외에는 앞에서 설명했으니 넘어갑시다.

- CreateWindow(L"button",L"About",WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 200,80,65,25,hWnd,NULL,hInstance,NULL);

About 푸쉬버튼을 만듭니다.

- CreateWindow(L"button",L"Quit",WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 200,120,65,25,hWnd,NULL,hInstance,NULL);

Quit 버튼을 만듭니다.

자 이제 WndProc함수를 살펴봅시다.

- HFONT font, font1;

font가 하나 더 필요하므로 font를 하나 더 선언하였습니다.

- font1 = CreateFont(12,0,0,0,400,0,0,0,HANGEUL_CHARSET,0,0,0,

DEFAULT_PITCH | FF_ROMAN,L"돋움");

폰트를 하나 더 생성합니다. 이 폰트는 앞의 font보다 크기가 작고, 진하게가 설정되지 않았으며(400), 글씨체가 돋움을 의미합니다.

- DeleteObject(font);

  SelectObject(hdc,font1);

처음에 font를 hdc에 등록한 후 제목을 출력하였으므로 이제 폰트를 바꾸기 위해 기존에 등록한 font를 지우고 새 폰트인 font1을 등록하였습니다. 

- TextOut(hdc, 10,40,L"Name:",lstrlen(L"Name:"));

  TextOut(hdc, 10,96,L"Serial:",lstrlen(L"Serial:"));

새로 등록한 폰트로 Name: 과 Serial: 을 출력시킵니다.

여기까지입니다. 생각보다 조금 짧네요. 결과를 한 번 봅시다.

오오오오 정말 그럴듯하게 만들어졌습니다. abex crack me2를 봅시다.

완전히 똑같지는 않지만 굉장히 비슷합니다. 

네 이것으로 포스팅을 마치겠습니다. 다음 포스팅에선 이제 버튼 동작을 시켜보겠습니다. 

점점 더 재밌어지는군요!. 아.. 아닌가요? 죄송합니다...

Posted by englishmath
,

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

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

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

- 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
,

안녕하세요. 이번 포스팅에선 abex crack me 2를 직접 제작해보겠습니다.

일단 동작을 한 번 봅시다.


이름과 시리얼을 받는 칸이 있고 버튼이 달려있습니다. 이번 프로그램은 메세지박스만으로는 구현이 힘들 것 같군요. 그래서 이번엔 윈도우 창을 만들어서 구현해야 할 것 같습니다. 소스가 생각보다 복잡해지니 몇번 나눠서 포스팅 하겠습니다.

일단 제가 짜놓은 소스코드를 한 번 봅시다.

네 제법 양이 되지요? 근데 이것은 말 그대로 틀만 짜놓은 소스입니다. 자 한줄씩 분석해봅시다.

- LRESULT WINAPI WndProc(HWND, UINT, WPARAM, LPARAM);

WndProc는 WindowProc의 약자입니다. MSDN의 설명을 봅시다.

반환값이 LRESULT라고 되어있습니다. LRESULT가 대체 무슨 자료형일까요?

LRESULT의 L은 long을 뜻합니다. 그런데 대문자 L이지요? 즉 LONG를 뜻합니다. 

그러면 대체 long랑 LONG랑 뭐가 다른 걸까요?

long은 정수형 4바이트죠? 그런데 말입니다. 이 자료형의 크기가 컴파일러마다 다르게 나옵니다. 어떤 컴파일러에서는 4바이트인데 어떤 컴파일러에서는 8바이트로 나오기도 합니다. 그래서 윈도우 API에서는 이 long이란 자료형의 크기를 4바이트로 정해놓은 자료형이 따로 있습니다. 그 자료형이 LONG입니다. 그리고 이 자료형은 windows.h파일에 정의되어 있습니다. 한번 보시죠.

정의를 보시면 long을 LONG으로 정의해 놓았습니다. 크기를 한번 살펴볼까요?

제 컴파일에서는 둘다 4바이트가 나왔지만 다른 컴파일러에선 long이 8바이트가 나올 수 있습니다. 

자 Long에 대한 설명이 끝났으니 이제 본론인 LRESULT를 알아봅시다. 정의를 한번 볼까요?

LONG_PTR을 LRESULT로 정의시켜 놓았군요. LONG은 알겠는데 그 뒤의 PTR이 뭔지 모르시겠죠? 그런데 여러분. 우리 이 PTR 어디서 많이 보시지 않았습니까?

바로 리버싱 할 때 입니다. 사진을 한 번 봅시다.

DWORD PTR이라고 되어있습니다. 아니 DWORD 그냥 쓰면 될걸 왜 굳이 PTR을 달아놓았을 까요?

일단 PTR을 설명하기 전에 32비트와 64비트 환경에 대해 간단히 설명하겠습니다. 

여러분 32비트 운영체제와 64비트 운영체제의 차이점이 무엇인지 아십니까? 여러가지가 있겠지만 대표적으론 32비트는 메모리가 32비트로 사용된다는 것이고 64비트는 메모리가 64비트로 사용된다는 것입니다. 자 여기서 하나 물어봅시다. 

32비트 프로그램이 64비트에서 동작될까요? 

네 물론 동작합니다. 어떻게 하냐구요? 직접 동작시켜보았으니까요. 자 그러면 하나 더 물어보겠습니다. 

왜 동작이 가능할까요?

생각해보십시요. 32비트 프로그램은 주소를 32비트 스택에 저장시킵니다. 즉 주소값은 32비트이지요. 그런데 64비트 환경에서는 스택 하나가 64비트입니다. 즉 주소값이 64비트여야 한다는 것이지요. 64비트 환경에서 32비트 프로그램을 실행시키면 32비트 주소가 64비트 주소로 인식하겠습니까? 당연히 문제가 생깁니다.

어? 그러면 2개의 스택을 써서 주소를 저장시키면 되지 않나요? 그러면 64비트에서도 동작이 정상적으로 될 거 같은데요?

물론 2개의 스택으로 주소를 저장시키면 64비트에서도 동작합니다. 그런데? 32비트프로그램을 32비트에서 동작할 때는 어떻게 됩니까? 물론 정상적으로 실행됩니다. 근데 스택 1개로 충분한 것을 2개로 잡아서 처리하니까 메모리 효율이 극도로 떨어집니다.

이를 위해 나온것이 PTR입니다. 즉 일반 32비트 자료형 뒤에 PTR을 붙여서 32비트에서는 주소를 32비트로 사용하고 64비트에서는 주소를 64비트로 사용하게 된 것이지요.

ex) INT_PTR, DWORD_PTR, LONG_PTR

즉 이 PTR은 32비트 환경과 64비트 환경 간의 호환성을 위해 사용됩니다.

사설이 길어졌군요. 본론만 말하면 LRESULT는 LONG_PTR 즉 LONG이라고 보시면 됩니다.

CALLBACK은 설명드렸지요? __stdcall 호출 방식입니다.

자 그럼 이 WndProc 함수는 정수 4바이트를 반환하는 함수입니다. 인자를 하나씩 살펴봅시다.

* HWND 

핸들입니다. 앞서 설명드렸으니 넘어갑시다. 이 인자는 윈도우의 핸들을 받습니다.

* UINT

이 자료형의 인자는 메세지를 양의 정수로 받습니다.

* WPARAM, LPARAM

새로운 자료형이 나왔습니다. 정의를 한 번 봅시다.

UINT와 LONG이군요. WPARAM은 양의 정수이고 LPARAM은 그냥 정수네요.

WPARAM가 자료형인 인자는 메세지의 추가정보를 받습니다.

LPARAM가 자료형인 인자도 메세지의 추가정보를 받습니다.

즉 정리하면 WndProc 함수는 윈도우 창에 들어온 메세지(마우스,키 값) 등을 처리하는 함수를 뜻합니다.

윈도우 창을 띄울려고 코딩할 때에는 이 함수를 무조건 선언해줘야 합니다. 선언이 안되있으면 윈도우창을 못띄웁니다. 당연한 겁니다.

기본적으로 이 함수가 없으면 우린 윈도우창을 닫을 수가 없습니다. 마우스 위치를 입력받은 메세지를 처리 못하니까 말이죠.

지금 코드에서는 이 WndProc 함수가 선언만 되어 있습니다. 즉 틀만 구현해 놓은 것이지요. 이렇게 틀만 구현해 놓은 이유는 바로 뒤에 실행될 WinMain함수에서 사용하기 때문입니다. 

이제 WinMain을 살펴봅시다.

- Hwnd hwnd;

hwnd라는 핸들자료형을 선언합니다.

- MSG msg

msg라는 MSG자료형을 선언합니다. MSG가 무슨 자료형일까요? 물론 여러분이 생각하시는대로 message의 약자입니다. 정의를 한번 볼까요?

정의를 살펴보니 구조체로 선언되어 있습니다. 구조체 안에 여러 자료형으로 선언된 변수가 들어있네요. 즉 MSG로 선언된 변수는 저러한 멤버들을 참조할 수 있게 됩니다. 한번 볼까요?

보시다시피 멤버 6개를 참조할 수 있습니다. 각 멤버들의 설명은 나중에 하겠습니다. 일단 이 MSG가 메세지 속성 값을 저장해놓은 구조체 라는 것만 아시면 됩니다. 다음을 봅시다.

- WNDCLASS wndclass

WNDCLASS 자료형인 wndclass를 선언합니다. WNDCLASS의 정의를 한번 봅시다.

WNDLASSW로 정의되어 있네요. 뒤쪽에 W가 하나 붙었지요? 이 W는 이 함수가 유니코드로 처리되는 함수라는 것을 의미합니다. WNDCLASSW의 정의를 한번 봅시다.

구조체가 나오는 군요. 앞의 MSG랑 비슷한 맥락입니다. 이 WNDLASS는 윈도우의 속성값을 저장해 등록해놓은 구조체입니다. 자 다음의 소스를 봅시다.

 - wndclass.style = CS_HREDRAW | CS_VREDRAW;

WNDLASS의 멤버 style로 접근하여 값을 줍니다. 이 멤버 style은 윈도우가 출력되는 형태를 저장시키는 변수입니다. 그런데 값이 CS_HREDRAW와 CS_VREDRAW이군요. 

style 타입이 정수이니까 이 CS_HREDRAW와 CS_VREDRAW은 define으로 정의된 상수라는 것을 알 수 있습니다. MSDN의 설명을 봅시다.

해석을 해드리겠습니다.

CS_HREDRAW은 윈도우의 width 즉 윈도우 창의 너비가 바꼈을 경우 윈도우 창을 다시 그립니다. 

CS_VREDRAW은 윈도의 height 즉 윈도우 창의 높이가 바꼈을 경우 윈도우 창을 다시 그립니다. 

즉 style의 값을 이렇게 주었다는 것은 윈도우의 너비, 높이가 바뀌면 다시 윈도우 창을 다시 그려라 라는 뜻입니다. 중간에 | 가 있죠? 이 | 은 or을 뜻합니다.

즉 윈도우 창의 높이 혹은 너비가 바뀌면 다시 그려라 라는 뜻으로 해석이 가능합니다.

다음으로 갑시다.

 - wndclass.lpfnWndProc = WndProc;

wndclass의 멤버변수 lpfnWndProc에 WndProc를 대입하지요? 이 WndProc는 우리가 앞에 선언한 함수를 의미합니다. 그런데 뭔가 이상하지 않습니까? WndProc함수는 분명 인자가 있었습니다. 그런데 여기에서는 인자 없이 함수만 쓰였습니다. 이게 가능할까요? 예제를 한번 봅시다.

분명 구조체 result의 자료형도 int이고 sum의 반환값도 int인데 함수만으로는 저장이 안됩니다. 인자까지 다 전달해야 비로소 완벽히 저장이 되는 것이지요. 그런데 어떻게 

wndclass.lpfnWndProc = WndProc 이러한 형태가 가능한 걸까요? 정답은 lpfnWndProc의 자료형에 있습니다.

lpfnWndProc의 자료형이 WNDPROC이죠? WNDPROC의 정의를 한번 봅시다.

이 WNDPROC이란 자료형이 포인터 변수로 되어있네요. 그것도 CALLBACK 포인터 변수입니다. CALLBACK이 함수호출규약인 __stdcall이죠? 즉 __stdcall 방식으로 호출하는 함수의 주소를 저장하는 변수가 이 WNDPROC입니다.

자료형이 포인터 변수이니까 주소로 받아야 합니다. 그래서! WndProc 원형만 적어 WndProc 의 주소를 lpfnWndProc에 저장하겠다는 뜻이지요. 물론 주소도 아무 주소도 받지 않고 CALLBACK호출 방식의 함수만 저장한다는 뜻입니다.

그렇기 때문에 아무리도 리턴값과 멤버의 자료형을 똑같이 맞춰줘도 사용자가 만든 sum이란 함수는 호출방식이 __stdcall이 아니라서 위와 같은 식이 성립하지 않습니다.

즉 lpfnWndProc 멤버는 메세지처리를 하는 __stdcall 방식의 함수 주소를 저장합니다.

- wndclass.cbClsExtra = 0

클래스의 여유메모리를 뜻합니다. 즉 클래스를 처리하는 도중 특수한 경우가 발생했을 경우 이 메모리를 갖다 쓴다는 뜻입니다. 아직 우리는 여유메모리를 사용할 필요가 없으므로 0으로 주었습니다.

- wndclass.cbWndExtra = 0;

윈도우의 여유메모리를 뜻합니다. 윈도우를 처리하는 도중 특수한 경우가 발생했을 때 이 메모리를 갖다 씁니다. 우리는 0으로 줍시다.

- wndclass.hInstance = hInstance;

생성하려는 윈도우의 핸들을 저장시킵니다. 여기서 hInstance값은 WinMain의 첫번째 인자값입니다.

- wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION)

자료형이 HICON이지요? HICON의 정의를 한번 봅시다.

DECLARE_HANDLE이라는 것이 나오지요? 쉽게 얘기하면 핸들을 새로 선언한다는 뜻입니다. 즉 HICON라는 핸들을 정의한다. 라는 뜻입니다. 그러면? HWND와 무슨 차이가 있을까요?

별 차이 없습니다. HWND은 핸들 윈도우, HICON은 핸들 아이콘입니다. 즉

HICON은 아이콘의 핸들을 받는다는 뜻입니다.

그렇다면 Icon멤버는 윈도우의 아이콘을 저장하는 멤버라는 것을 알 수 있습니다. 

자 이제 LoadIcon에 대해 알아봅시다. MSDN의 설명을 봅시다.

반환값이 HICON입니다. 즉 아이콘 핸들을 반환하다는 뜻입니다. 인자를 살펴봅시다.

 * HINSTANCE

아이콘의 핸들을 받습니다. 만약 받지 않을 시 NULL을 지정합니다. 저는 일단 NULL값을 주었습니다. 나중에 바꿀 것입니다.

* lpIconName

자료형이 LPCTSTR이므로 아이콘 이름을 유니코드 문자열로 받습니다. 여기서는 

IDI_APPLICATION을 넣었지요? 정의를 확인해봅시다.

응용프로그램의 기본 아이콘이라고 정의되어 있습니다.

자 다음으로 갑시다.

- wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);

HCURSOR 자료형은 HICON이랑 비슷합니다. 정의를 살펴봅시다.

HICON이랑 같다고 나옵니다. 즉 이름만 다르고 핸들을 받는 것은 똑같다는 것입니다.

그러면 HCURSOR은 어떤 핸들을 받냐고 하니 마우스 커서를 핸들로 받습니다.

LoadCursor함수를 살펴봅시다.

구조가 HICON이랑 거의 똑같습니다.  커서의 핸들과 커서이름을 인자로 받아 핸들을 반환합니다. 커서이름인자 부분에 IDC_ARROW라고 적혀있지요? IDC_ARROW의 정의를 봅시다.

표준 화살표라고 되어있네요. 다음으로 넘어갑시다.

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

hbrBackground의 자료형은 HBRUSH입니다. 정의를 살펴봅시다.

그냥 정의된 핸들이군요. 이 HBRUSH는 브러쉬 핸들을 뜻합니다. 즉 브러쉬핸들을 저장하겠다 라는 뜻이지요. 

자 값을 넣는 쪽을 보면 GetStockObject함수가 쓰였습니다. 한번 살펴봅시다.

반환값이 HGDIOBJ 자료형입니다. 이 HGDIOBJ는 Handle GDI 오브젝트를 뜻합니다.

여기서 GDI란 그래픽 디바이스 인터페이스의 약자로 그래픽을 도와주는 장치들을 모아놓은 집합을 의미합니다. 여기서 장치는 함수,자료형 등 일 것이고 오브젝트는 객체라는 뜻을 가지고 있으므로 HGDIOBJ란 그래픽 관련 함수와 자료형 등 을 모아놓은 객체라고 보시면 됩니다. 

그래서 이 객체에는 여러가지 자료형을 포함하고 있습니다.(HGDIOBJ, HPEN 등)

그런데 우리가 받아야 할 자료형은 hbrBackground의 자료형인 HBRUSH이죠? 그래서 HGDIOBJ에서 HBRUSH만 뽑아오도록 형변환을 시켜준 것입니다.

자 이제 하나 있는 인자를 살펴봅시다.

fnObject라고 되어있는데 이 자리에 제가 WHITE_BRUSH를 적었지요? 정의된 상수입니다. 목록을 한 번 봅시다.

하얀색 브러쉬를 뜻합니다. 즉 여기서는 하얀색 브러쉬를 인자로 받아 브러쉬 핸들을 넘겨준다는 것이지요. 여기서 brush는 그냥 배경화면이라고 보시면 됩니다.

- wndclass.lpszMenuName = NULL;

메뉴 이름을 저장하는 멤버입니다. 우리는 아직 메뉴가 필요없으니 NULL로 주었습니다.

- wndclass.lpszClassName = L"HELLOWINDOWS";

lpszClassName의 자료형은 LPCTSTR입니다. 즉 유니코드 상수 문자열을 받습니다.

이 멤버는 윈도우의 클래스 이름을 저장합니다. 여기서 윈도우 클래스 이름은 윈도우를 의미하는 변수라고 보시면 됩니다. 나중에 이 윈도우를 생성시킬 때 이 이름을 사용하니 잘 기억해 두셔야 합니다.

여기까지가 wndclass의 멤버에 값을 등록하는 거였습니다. 참고로 저 멤버중 하나라도 빠지면 정상 동작안합니다. 하나도 빠짐 없이 입력해 주세요.

- RegisterClass(&wndclass);

RegisterClass함수는 WNDCLASS로 선언된 윈도우 클래스를 등록하겠다는 것입니다. 이렇게 등록된 윈도우클래스는 나중에 윈도우 창을 생성시킬 때 사용합니다.

MSDN의 정의를 봅시다.

WNDCLASS로 선언한 윈도우 클래스의 주소를 인자로 받습니다. 반환형이 ATOM이네요. ATOM의 정의를 봅시다.

WORD이군요. 즉 윈도우 클래스의 주소를 2바이트로 넘긴다는 뜻입니다.

다음으로 넘어갑시다.

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

        WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,

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

드디어 대망의 CreateWindow함수가 나왔습니다. 여태까지 작성한 윈도우 클래스를 기반으로 윈도우 창을 만드는 것입니다. MSDN의 정의를 봅시다.

반환값은 핸들입니다. 인자들을 하나씩 살펴 봅시다.

* lpClassName

클래스 이름을 뜻합니다. 아까 우리가 만든 wndclass.lpszClassName의 값이 들어갑니다.

* lpWindowName

윈도우 이름을 인수로 받습니다. 여기서 받는 이름은 윈도우 창이 생성될 때의 제목이 됩니다.

* dwStyle

윈도우 창의 스타일을 인수로 받습니다. 여기서  쓰인 값은 

WS_OVERLAPPEDWINDOW값입니다. 정의를 한 번 봅시다.

이 윈도우창은 중복된 윈도우창이라는군요. 이것이 무슨 소리냐 하면 

WS_OVERLAPPEDWINDOW 값은 밑의 윈도우 창을 나타내는 6개의 속성을 다 포함한다는 뜻입니다. 즉 WS_OVERLAPPEDWINDOW를 써놓으면 갖출건 다 갖춘 윈도우 창 을 만들 수 있다는 것입니다. 이것은 나중에 자세히 말씀드리겠습니다.

* x

윈도우 창을 처음 생성할 때의 x좌표 값을 의미합니다. 여기서 CW_USEDEFAULT값을 설정해주면 윈도우 창이 생성될 때 OS에서 기본값 x를 설정해 줍니다.

* y

윈도우 창을 처음 생성할 때의 y좌표 값을 의미합니다. 여기서 CW_USEDEFAULT값을 설정해주면 윈도우 창이 생성될 때 OS에서 기본값 y를 설정해 줍니다.

* nWidth

윈도우 창을 처음 생성할 때 창의 너비를 뜻합니다. 여기서 CW_USEDEFAULT값을 설정해주면 윈도우 창이 생성될 때 OS에서 기본값 너비를 설정해줍니다.

* nHeight

윈도우 창을 처음 생성할 때 창의 높이를 뜻합니다. 여기서 CW_USEDEFAULT값을 설정해주면 윈도우 창이 생성될 때 OS에서 기본값 높이를 설정해줍니다.

* hWndParent

부모 윈도우 핸들을 의미합니다. 아직 우리는 부모 윈도우 창이 없으므로 일단 NULL값을 주었습니다. 이것은 나중에 설명드리겠습니다.

* hMenu

자료형이 HMENU입니다. 정의를 한 번 봅시다.

반환은 메뉴핸들로 반환하는 군요. 즉 메뉴 핸들을 인자로 받습니다. 아직 우리는 메뉴를 사용하지 않을 것이므로 NULL값을 주었습니다.

* hInstance

생성할 윈도우의 핸들을 의미합니다. 여기선 우리가 정의해놓은 윈도우클래스의 핸들을 가지고 옵니다.

* lpParm

이 인자의 자료형은 LPVOID입니다. 정의를 봅시다.

반환값이 없는 포인터 함수를 의미하는군요. 여기서 이 인자는 생성 윈도우 정보를 받습니다. 이것은 나중에 자세히 알려드리겠습니다. 일단 NULL로 주었습니다.

자 이것으로 CreateWindow 함수 설명을 다 끝냈습니다. 이 함수의 결과값은 핸들이므로 우리가 선언한 hwnd에 들어갈 것입니다. 다음으로 넘어갑시다.

 - ShowWindow(hWnd, nCmdShow);

생성한 윈도우를 화면에 나타내주는 함수입니다. 설명을 봅시다.

반환값은 BOOL 이므로 참, 거짓 둘중 하나입니다. 자 인자들을 하나씩 살펴봅시다.

* hwnd

생성한 윈도우 핸들입니다. CreateWindow의 핸들값이 들어있는 hwnd를 넣어주었습니다.

* nCmdShow

윈도우창을 화면에 띄울 때 어떤 방식으로 띄울지를 받습니다. 우리는 WinMain의 매개변수를 주었습니다. 보통 WinMain의 매개변수를 인자로 주면 기본 설정은 SW_SHOW가 됩니다. 정의를 한 번 봅시다.

정해진 크기와 위치에 윈도우 창을 활성화 시킵니다.

- while (GetMessage(&msg, NULL, 0, 0))

    {

        TranslateMessage(&msg);

        DispatchMessage(&msg);

    }

while은 반복이지요. 무엇을 반복한다는 것일까요? 조건문에 적힌 GetMessage를 살펴봅시다.

반환형은 참, 거짓입니다. 이 GetMessage함수는 말그대로 메세지를 받아옵니다. 인자들을 살펴봅시다.

* lpMsg

자료형이 LPMSG입니다. 정의를 한번 봅시다.

MSG구조체의 포인터를 의미하는 자료형이군요. 즉 MSG구조체의 주소값을 받습니다.

* hwnd

메세지가 발생한 윈도우의 핸들을 의미합니다. 아직 그런 윈도우가 없으므로 NULL을 줍시다.

* wMsgFilterMin

이 인자는 최소정수값을 의미합니다. 이 함수에서 메세지를 받을 때에는 정수형으로 받습니다. 그런데 메세지 번호가 한 두개 이겠습니까? 아니지요?  즉 필요한 메세지만 받기 위하여 최소 정수값을 인자로 받습니다.

* wMsgFilterMAX

위의 인자가 최소정수값을 받는다면 이 인자는 최대정수값을 받습니다.

추가로 이 두 인자가 둘다 0일 경우에는 메세지를 필터링하지 않고 모두 받습니다. 

자 이제 GetMessage 함수 설명이 끝났습니다. 그러면 이러한 GetMessage 함수를 왜 반복문에 돌리는 것일까요?

일반적으로 Windows GUI 응용프로그램은 실행된 후에 단지 윈도우를 출력할 뿐이며일반적으로 아무것도 하지 않습니다. 이런 프로그램은 사용자가 키보드 입력이나 마우스 버튼 클릭으로 이벤트가 발생되면 그때마다 대응되는 처리를 하는 방식이기 때문이지요.

그럼 그 이벤트를 어떻게 처리를 하느냐고 하니 컴퓨터의 장치 중 하나가 이를 감지하여 응용프로그램에 넘겨줍니다. 이 때 그 이벤트는 응용프로그램의 큐에 저장이 되는데 이 큐에 저장된 이벤트를 메세지라고 합니다. 참고로 큐는 스택의 반대개념이라고 보시면 됩니다. 이렇게 저장된 메세지는 GetMessage함수에서 받아서 처리를 하게 됩니다.

메세지는 끊임없이 받아야 하므로 반복문에 GetMessage함수를 넣어 처리를 하도록 하는 것입니다. 언제까지 처리하냐구요? 생성된 윈도우가 종료될 때까지 입니다.

정리하면 이 GetMessage함수는 메세지를 받아서 이 메세지가 WM_QUIT라면 FALSE 아니라면 TRUE를 반환합니다. 

자 이제 반복문 안에 있는 함수를 살펴봅시다.

 - TranslateMessage(&msg);

TranslateMessage함수의 정의를 봅시다.

인자는 MSG의 주소를 받습니다. 예도 GetMessage함수와 비슷합니다. 하지만 이 함수는 메세지를 인자로 받았을 때 이 메세지가 키보드 관련 메세지일 경우 이 키보드 관련 메세지에서 추가적인 정보를 뽑아내 반환합니다. 즉 a라는 키를 입력한 메세지를 인자로 받았을 경우 이 메세지에서 ctrl 등의 키가 눌러졌는지 안눌러졌는지에 대한 추가 정보를 뽑아내서 메세지에 덧붙입니다.

즉 키보드 관련 메세지를 받아서 처리할 경우에만 TRUE를 반환하고 그 외의 메세지를 받았을 경우에는 FALSE를 반환합니다.

- DispatchMessage(&msg);

정의를 한 번 봅시다.

이 함수도 메세지를 인자로 받아 LRESULT 형식으로 반환합니다. 즉 정수형으로 반환하는 것이지요.

이 함수는 간단하게 얘기하면 받은 메세지를 msg의 멤버에 저장시켜 LRESULT 값으로 윈도우 프로시저에 전달시킵니다. 여기서 윈도우 프로시저란 WndProc함수를 말합니다.

최종적으로 정리하면

while (GetMessage(&msg, NULL, 0, 0))

    {

        TranslateMessage(&msg);

        DispatchMessage(&msg);

    }

는 윈도우 창이 생성된 직후 큐에 저장된 메세지를 읽어들여 처리하는 부분입니다.

즉 GetMessage 함수로 메세지를 읽어오고 TranslateMessage 함수로 키보드 관련 메세지를 처리한 후 DispatchMessage 함수로 인해  WndProc함수에 전달됩니다.

이 함수가 없으면 윈도우 창이 생성된 후 추가적으로 오는 메세지를 받지 못해 바로 종료가 되버립니다. 궁금하시분 들은 이 while을 주석처리하고 실행해보십시요.

여기까지가 WinMain의 설명입니다. 너무 많지요? 근데 아직 덜 끝났습니다. 조금만 더 힘내봅시다. 이제 앞에서 선언된 WndProc함수를 구현해줘야 합니다. WndProc함수의 내부를 봅시다.

- switch (message)

    {

    case WM_DESTROY:

        PostQuitMessage(0);

    }

return DefWindowProc(hWnd, message, wParam, lParam);

switch문은 다들 아시죠? 정수를 받아서 해당되는 case문을 실행하는 명령어입니다.

여기서는 message를 받습니다. 이 message는 DispatchMessage 함수에서 받아온 메세지를 받는 매개변수입니다. (2번 째 인자) case문을 살펴봅시다.

* WM_DESTROY

윈도우 창이 파괴되었다는 것을 알리는 메세지입니다. DispatchMessage에서 받은 메세지가 WM_DESTROY라면 PostQuitMessage함수를 호출합니다. PostQuitMessage의 정의를 봅시다.

이 함수는 WM_QUIT라는 메세지를 큐에 저장시킵니다. 즉 종료하라는 메세지를 저장시키는 거지요. 이 함수 인자는 종료코드를 의미하는데 지금은 쓸 일이 없으므로 0을 주었습니다.

이 함수로 인해 큐에 WM_QUIT 메세지가 저장되고 나중에 GetMessage에서 WM_QUIT를 받으면 리턴값을 FALSE로 반환하고 이로 인해 반복문을 빠져나가서 return 0을 실행하고 윈도우 프로그램이 정상적으로 종료됩니다.

- return DefWindowProc(hWnd, message, wParam, lParam);

DefWindowProc함수의 정의를 봅시다.

WndProc함수랑 똑같은 인자와 반환값이다. 그러면 대체 DefWindowProc랑 WndProc의 다른 점이 뭘까요? 

DefWindowProc 의 Def는 Default입니다. 즉 기본을 의미하지요. 보통 WndProc에서 처리되지 않은 메세지를 자동으로 처리할 때 쓰입니다.

우리가 작성한 WndProc에서는 WM_DESTROY메세지만 처리가 되었지요? 그런데 그 외의 메세지도 있을 것이 아닙니까. 그러한 메세지를 자동으로 처리해 반환하도록 DefWindowProc함수를 사용하였습니다.

제가 작성한 소크코드 설명이 전부 끝났군요. 창 하나띄우는데 정말 많은 코드가 사용되었습니다. 이제 작성한 코드를 실행시켜 봅시다.

하얀색 배경의 창이 띄워졌습니다. 그런데 우리가 만들려는 abex crack me 2랑 너무 다르지요? 그래서 다음포스팅에서는 이 소스코드를 기반으로 abex crack me2 랑 비슷하게 만들어보겠습니다.

이상으로 포스팅을 마치겠습니다.







Posted by englishmath
,

안녕하세요. 저번에 공지한대로 이번 포스팅에서는 콘솔창을 띄우지 않고 프로그래밍을 해보겠습니다.

visual stdio를 열어 프로젝트를 만듭니다.

win32 응용 프로그램을 만들기 위해 Win32 프로젝트를 선택하여 프로젝트를 만듭니다.

만들 프로그램을 windows 응용 프로그램으로 선택하고 빈 프로젝트를 체크해 준 다음 마침을 누릅시다. 가끔 사람들이 빈 프로젝트가 뭐냐고 물으시는 분 들이 있는데 말 그대로 비어있는 프로젝트를 뜻합니다.

빈 프로젝트를 체크해제하고 만들 시 visual stdio에서 지정한 기본 main 함수가 미리 적혀있는 것을 볼 수 있습니다.

아래는 빈 프로젝트를 체크해제하고 만든 프로젝트입니다.

보시다시피 상당한 지식을 요구하는 코드가 자동으로 적혀있는 것을 볼 수 있습니다. 즉 전문가용 옵션입니다. 우리는 전문가가 아니니까 그냥 빈 프로젝트 옵션을 사용합시다.

프로젝트를 만드셨으면 소스를 만듭시다.

c언어로 작성해야하니 c++ 파일을 선택하시고 소스파일의 확장자를 c로 맞춥시다.

여기까지 하셨으면 이제 소스를 작성할 차례입니다. 미리 제가 작성한 소스를 한 번 봅시다.

네 소스코드는 콘솔프로그래밍과 별 차이가 없습니다. 하지만

main부분이 상당히 다르다는 것을 느낄 수 있습니다. 

- int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,int nCmdShow)

이번 포스팅의 핵심부분입니다. 하나씩 살펴봅시다.

*WINAPI WinMain

WINAPI란 windows api를 뜻합니다. 즉 windows에서 사용하는 api라는 뜻입니다.

여기서 api가 무엇인고하니 api는 Application Programming Interface의 약자를 뜻합니다.

해석하면 응용프로그램 프로그래밍 인터페이스를 뜻하는데 인터페이스가 두 장치 간의 상호작용을 위한 매개체를 뜻하므로 풀이하면

응용프로그램을 프로그래밍하기 위한 매개체를 뜻합니다. 쉽게 말하면 응용프로그램을 만들기 쉽게 해주는 규격 같은 것입니다.

즉 winapi는 윈도우에서 응용프로그램을 프로그래밍할 때 도움을 주는 인터페이스를 의미합니다.

정리하면 WINAPI WinMain이란 WINAPI 호출규약인 WinMain함수를 선언하였다. 라고 보시면 됩니다. 일반 콘솔 프로그래밍에서는 컴파일러가 main함수를 찾는 것이 비해 응용 프로그래밍에서는 컴파일러가 WinMain함수를 찾습니다. 그러므로 WinMain함수를 선언해 주었습니다.

이러한 WinMain은 인자를 다음과 같이 받습니다.

보시게 되면 제가 WINAPI를 넣었던 자리에 CALLBACK라고 적혀있는 것을 볼 수 있습니다. 사실 이자리는 함수호출규약을 정하는 자리입니다. 

함수호출규약이 무엇이냐면 함수를 호출할 때 스택을 정리하는 규약을 정해놓은 것입니다. 일반적으로 __cdecl와 __stdcall가 있는데 일반 콘솔 프로그래밍에서 함수를 호출 할 경우에는 __cdecl를 쓰지만 응용프로그래밍을 할 경우에는 __stdcall 방식을 씁니다. 좀더 자세히 알아보기 위해 실습을 해 봅시다.

WinDef.h를 작성하시고 문서를 열어봅시다. WinDef 헤더파일은 여러 자료형을 정의해 놓은 문서입니다.

문서를 찾아보면 위와 같이 정의되 있는 곳을 찾을 수 있습니다.

MSDN의 CALLBACK과 제가 작성한 WINAPI의 정의된 값을 보면 __stdcall라고 정의되어있습니다. 즉 함수호출규약을 뜻합니다.

즉 CALLBACK 자리에는 CALLBACK이 와도 되고 WINAPI도 와도 되고 __stdcall이 와도 된다는 것입니다. 

그러면 꼭 함수 호출 규약을 적어야 하나? 라는 의문이 들 수 있습니다. 실험 결과 규약을 적지 않아도 프로그램이 정상적으로 실행되는 것을 볼 수 있었습니다. 하지만 밑의 결과를 보면

WinMain의 호출규약은 __stdcall이어야 한다고 경고가 나옵니다.(에러 아닙니다)

그래서 저는 저 자리에 WINAPI를 적어주었습니다.

이제 인자들을 차례대로 살펴 봅시다.

hInstance

자료형이 HINSTANCE입니다. HINSTANCE는 핸들 인스턴스의 약자이며 쉽게 말하면 프로그램 자체의 주소라고 보시면 됩니다. HWND가 프로그램 내의 윈도창의 번호라고 한다면 HINSTANCE는 프로그램의 번호라고 보시면 됩니다.

hPrevInstance

자료형이 HINSTANCE입니다. 즉 프로그램 자체의 주소를 받는다는 것은 앞의 인자와 동일하나 이 인자는 이 프로그램의 주소가 아닌 앞에 실행된 프로그램의 주소를 받습니다. 그런데 이 인자는 16비트 환경일 때 사용한 인자이며 현재 32비트 환경에서는 사용되지 않습니다. 그래서 기본적으로 NULL값이 인자로 들어갑니다.

*lpCmdLine

자료형이 LPSTR인 이 인수는 이 프로그램의 이름 혹은 경로를 유니코드체계로 받습니다.

*nCmdShow

자료형이 INT인 이 인수는 윈도우가 표시되는 방법을 정수형으로 받습니다.

자 여기까지가 WinMain의 설명입니다. 기본적으로 WinMain를 사용할 시에는 저 4개의 인자를 적어주어야 합니다.

추가로 덧붙이자면 위의 인자이름들은 가독성을 위해 MSDN에서 정해놓은 인자이므로 굳이 인자이름을 똑같이 적을 필요는 없습니다.

자료형만 맞추고 인자 이름이 예약어가 아니라면 정상적으로 실행이 됩니다.

자 이제 소스설명이 끝났으니 한번 실행 해 봅시다.

콘솔창이 뜨지 않는 것으로 보아 정상적으로 실행됐음을 알 수 있습니다.

다음 포스팅은 이렇게 제작한 프로그램을 리버싱하는 것을 올리겠습니다. 참고로 리버싱 카테고리에 올릴 예정입니다.

이상으로 포스팅을 마치겠습니다.





Posted by englishmath
,