본문 바로가기

Win32 Api/강의

2010년 12월 1일 수요일

//windows.h 헤더 파일은 기본적인 데이터 타입, 함수 원형 등을 정의하며 그 외 필요한 

헤더 파일을 포함
#include <windows.h> 

// 함수 선언부
LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);

//HINSTANCE, lpszClass 를 요구하는 경우가 많으므로 따로 전역으로 정의.
HINSTANCE g_hInst;
LPCTSTR lpszClass=TEXT("First!");

//프로그램 시작점인 엔트리 포인트가 WinMain 함수이다.
//APIENTRY 지정자는 호출규약(__stdcall)에 관한 명시. 없다고 생각해도 무관.
(★실습해보자.) 다르게 쓸 수 있는 건?, 빼도 되나?
//__cdecl ☜ 으로 교체하니 안됨, APIPRIVATE, PASCAL은 가능 어차피 저 두개도 #define으로 __stdcall명시되어있음 되는게 당연함 ㅡㅡ;;;
//HWND와 Hinstance의 차이.
인스턴스 : 실행중인 프로그램 하나. 윈도우는 멀티태스킹 시스템이면서 하나의 프로그램 중복실행 가능. 이때 실행되는 각각의 프로그램을 프로그램인스턴스라고 함. 

두개의 메모장 프로그램 인스턴스.
인스턴스는 클래스의 상속받은 객체라고 보면 된다. 메모장 이라는 클래스가 실현된 객체라고 보면 됨.
//call STACK으로 HINSTANCE값 살펴봐라.
★ 디버깅 모드로 실행해보면 callstack(ctrl + alt + C)에 instance값 나옴

HINSTANCE 핸들은 보통 실행되고 있는 Win32 프로그램이 메모리 상에 올라가 있는 시작 주소 값을 갖고 잇습니다. 보통은 0x00400000 이런 식의 값을 가지고 있는데 저 값의 메모리 주소에 실행 모듈이 올라가 잇다는 것을 의미 하겠죠.(스크랩 툴을 WINHEX로 보여준다.) 보통 리소스들을 로드 하는 함수들에서 이 핸들 값을 많이 참조하게 되는데 이 이유는 메모리 상에 올라가 있는 실행 모듈 들 중 hInstance 가 가르키는 주소에 올라가 있는 실행 모듈에서 그 리소스를 읽어오라고 지정 해주는 것입니다. 그리고 그 변수를 글로벌로 잡는 경우가 있는데 그 이유는 그 변수를 여러 함수들에서 자주 사용하기 때문에 Win32의 구현 방식상 여러함수에서 그 값을 읽어 와야 하고 그 값은 WinMain() 에서 한번 들어오는 값이기 때문에 글로벌 변수에 넣어 두는것이죠... HINSTANCE는 프로그램의 인스턴스 식별자(핸들)을 의미합니다. 간단히 말씀 드리자면 실행파일 형태로 껍데기에 불과한 프로그램이 메모리에 실제로 구현된 실체를 뜻합니다. 따라서 만약에 프로그램을 여러 개 실행시켰을 때 이들의 각각을 프로그램 인스턴스라고 하고 실행되는 프로그램마다 고유한 값을 갖고 실행중인 프로그램들을 구분하기 위한 식별 값으로 인스턴스 핸들을 이용합니다.
 
HWND는 프로그램의 윈도우 식별자(핸들)를 의미하는데 해당 프로그램의 윈도우들을 구분하기 위한 식별 값을 말합니다. 여기서 윈도우와 프로그램과는 차이가 있습니다. 윈도우 프로그래밍에서 하나의 프로그램에는 많은 윈도우들을 가질 수 있기 때문입니다. 프로그램의 윈도우 핸들 중에서 대표적인 것이 부모 윈도우의 핸들입니다. 따라서 대부분의 윈도우 프로그래밍의 작업과정 중 대부분은 이들 HWND 값을 통해 얻은 윈도우 핸들을 이용한다고 이해하시면 빠를 것 같습니다. 창뿐만 아니라 여러가지 수많은 컨트롤 등도 모두 윈도우로 생성시에 핸들 값을 소유하고 있는 존재이기 때문입니다.
------------------------------------------------------------------------------------
HINSTANCE는 프로그램의 핸들이 아니라, 프로그램 코드를 담고 있는 모듈에 대한 핸들이다. 즉, 프로그램이 수행되려면 프로그램 코드를 담고 있는 파일을 메모리의 특정 영역에 올려서 명령을 하나씩 읽어가면서 수행할 수 있도록 준비해 놓아야 한다. 이렇게 메모리에 올려진 프로그램 코드 덩어리를 윈도우에서 관리하기 위해서 일종의 고유 식별 번호를 부여하는데, 이것이 인스턴스 핸들, HINSTANCE이다. 기본적으로 프로세스를 실행하는 실행파일의 코드를 메모리에 올려놓은 모듈이 하나 있어야 하므로, 실행 파일의 모듈에 대한 인스턴스 핸들을 OS가 어플리케이션 WindMain의 인자로 넘겨주는 것이다. 한 프로세스가 여러 개의 모듈을 로딩하여 프로그램을 실행하고 있다면 하나의 프로그램이 여러개의 인스턴스 핸들을 할당받아 쓰고 있을 수 있다.(물론 한 개의 모듈을 여러 프로세스가 공유하고 있을 수도 있다.) 대표적인 예가 바로 IE인데, 간단하게 DLL파일 한 개를 쓸 때마다 이 DLL 모듈에 대한 인스턴스 핸들이 한 개씩 생긴다고 보면 된다. 물론 DLL이 한번 로딩되면 다른 프로그램 사이에서 공유된다. 한 개의 프로그램에서 HINSTANCE가 한 개만 있는 것이 아니며, 또한 하나의 인스턴스 핸들이 한 개의 프로그램에만 종속되는 것이 아니므로 어떤 프로그램의 출력 대상을 지정하는 데에는 부적절하다는 점을 알 수 있다. 물론, 인스턴스 핸들은 애초부터 화면 출력을 고려햐여 만들어진 식별자는 아니며 단지 프로그램 코드 덩어리를 관리하기 위해 
만들어진 리소스이다. 윈도우라는 OS에서 화면 출력을 위해 관리하는 리소스가 바로 HWND이다. MSN 메신저 등을 보면 알겠지만 하나의 프로그램이 하나의 창을 사용한다는 보장은 없다. 오히려 99.99%의 프로그램은 한 개 이상의 윈도우로 구성되어 있다. 한 프로그램의 윈도우가 겉보기에는 단일한 대상 영역으로 보일지라도 실제로는 구성 요소별로 분리하여 별개의 윈도우로 만들어 각 윈도우는 자기 자신이 맡은 부분에 대한 화면 출력과 사용자 입력만을 담당한다. 자연히 하나의 프로그램에서 사용하는 HWND 타입의 개체 역시 1개 이상이 될 수 밖에 없으며, 이러한 상황에서 특정 위치에 특정한 동작을 수행하기 위해서는 HWND로 대상 영역을 구분할 수 밖에 없다. 
------------------------------------------------------------------------------------
구조체를 찾아가보면 HWND는 윈도우 자체에 대한 정보를 가지는 것이고, HINSTANCE는 현재 실행중인 인스턴스에 관한 정보를 가지고 있습니다. 스턴스는 운영체제 전체에서 유일한 번호를 가지므로(실행중인 창들이라고 보면 되죠) HWND를 가지고 인스턴스를 구할순 없지만 HINSTANCE를 가지고 있으면 HWND에 관한 내용들을 구할수 있습니다. 
//hInstance : 프로그램의 인스턴스(클래스가 메모리에 실제로 구현된 실체) 핸들
//hPrevInstance : 바로 앞에 실행된 프로그램의 인스턴스 핸들. 없을 경우는 NULL. Win32에서는 항상 NULL, 16비트 와의 호환성을 위해 존재. 안쓴다.
//lpszCmdParam : 명령행 인수.
(★실습해보자.)

C에서 main 이라는 함수를 정의하면 프로그램 진입점인데, 여기서 main 이란 함수를 선언하고 정의해서 사용하면 될까 안될까? 한번 해보슈.(★실습해보자.)

//WinMain이 호출되는 과정 설명
//파일의 구조, 파일 헤더, 컴파일러가.. 운영체제의 프로그램 로더는 PE의 헤더에 있는 IMAGE_OPTIONAL_HEADER 구조체의 AddressOfEntryPoint 필드를 참고해 그 주소의 코드(.text 섹션 내부에 위치)를 실행시킨다. AddressOfEntryPoint가 가리키는 지점에는 WinMainCRTStartup()함수가 있다. 이 함수는 WinMain()이 필요로 하는 실행환경을 만들고, 각 매개변수로 전달될 값들을 구해서 WinMain()함수를 호출한다. WinMain()함수는 WinMainCRTStartup()함수로 부터 넘겨받은 arguments를 이용해 프로그램 내부적인 동작을 실행한다. WinMain()함수의 동작이 종료되면 실행 포인터는 WinMainCRTStartup()함수로 return되고, 이 함수는 다시 WinMain()의 리턴값을 exit함수의 parameter로 넘겨준다. exit()함수는 여러가지 마무리 작업을 하고, Win32 ExitProcess() 함수를 호출하고 WinMain()의 리턴값을 운영체제로 넘기고 프로그램을 종료한다.
int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpszCmdParam,int nCmdShow)
{
HWND hWnd
MSG Message

__cdeclC언어 표준 호출 규약이다. 파라미터는 오른쪽에서 왼쪽으로 스택을 통해 전달되며, 호출한 곳에서 스택을 정리한다. 특징적인건 호출한 쪽에서 스택을 정리하기 때문에 가변 인자를 지원한다는 것이다. __fastcall말 그대로 빠른 호출이다. 파라미터 중 일부를 레지스터를 통해서 전달하는 함수다. x86 계열에서는 일반적으로 ecx, edx로 파라미터를 전달하고 나머지는 스택으로 전달한다. __cdecl과 같이 오른쪽에서 왼쪽으로 파라미터를 전달하며, 스택 정리는 호출을 당한 곳에서 수행한다. 따라서 가변 인자를 지원하지 못한다. __stdcall윈도우 API의 표준 호출 규약이다. 파라미터는 오른쪽에서 왼쪽으로 스택을 통해서 전달되며, 스택 정리는 호출 당한 곳에서 이루어진다.

//윈도우 클래스. F12 또는 점찍어 확인. 10개의 맴버.
//여기서 10개를 다 지정해주므로 하나식 살펴보자.


WNDCLASS WndClass

//cbClsExtra, cbWndExtra : 예약영역. 윈도우가 내부적으로 사용.  특수한 목적으로 사용. 0으로 사용.
//hbrBackground : 윈도우 배경색깔을 채색할 브러시 지정. 색을 바로 지정해도 되고 GetStockObject라는 함수로 윈도우즈 기본 재공 브러시 사용하거나 COLOR_WINDOW 같은 색을 바로 지정해줘도 상관없다. WHITE니까 RED해봐라. 되는가? 윈도우는 흰색검은색 회색만 지원함.

//hCursor, hIcon 기본적으로 제공하는 아이콘과 커서 사용.
★ 커서에 IDC_CROSS, 아이콘에 IDI_HAND 로 바꿔보자. ☞ 커서모양이 글씨입력 모양으로 바낀다 I 요렇게 생김
//lpfnWndProc : 윈도우 메시지 처리 함수지정. 메시지 발생시 마다 함수가 호출. 함수 이름을 정할 수 있지만 거의 WndProc로 사용한다. 암묵적으로.여러분들도 사용하시길.
//lpszClassName : 윈도우 클래스의 이름을 정의. 윈도우 클래스의 이름은 보통 실행 파일의 이름과 일치시켜 작성. 이 두개의 멤버가 중요함.
//lpszMenuName: 이 프로그램이 사용할 메뉴를 지정한다. 메뉴는 프로그램 코드에서 만드는 것이 아니라 리소스 에디터에 의해 별도로 만들어진 후 링크시에 같이 합쳐진다. 메뉴를 사용하지 않을 경우 이 멤버에 NULL을 대입해 주면 된다.

//style : 윈도우가 어떤 형태를 가질 것인가를 지정하는 멤버. 이 멤버가 가질 수 있는 값은 무지하게 많지만 가장 많이 사용하는 값이 CS_HREDRAW와 CS_VREDRAW이다. 이 두 값을 OR 연산자(|)로 연결하여 사용한다. 이 값들의 의미는 윈도우의 수직(또는 수평) 크기가 변할 경우 윈도우를 다시 그린다는 뜻이다. 이밖에도 많은 값이 올 수 있다
Style : 윈도우 스타일. 
lpfnWndProc : 윈도우 메시지 처리함수 지정. 메시지 발생시 마다 이 멤버가 지정하는 함수 호출되어 메시지 처리.
이름을 마음대로 정의할 수 있지만 대부분 WndProc 으로 정하는 편.

g_hInst=hInstance
WndClass.cbClsExtra=0;
WndClass.cbWndExtra=0;
WndClass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
WndClass.hCursor=LoadCursor(NULL,IDC_ARROW);
WndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION);
WndClass.hInstance=hInstance;
WndClass.lpfnWndProc=(WNDPROC)WndProc;
WndClass.lpszClassName=(LPCWSTR)lpszClass;
WndClass.lpszMenuName=NULL;
WndClass.style=CS_HREDRAW | CS_VREDRAW;
//윈도우 클래스 구조체의 번지를 전달. 앞으로 사용할 윈도우를 등록하는 과정 RegisterClass(&WndClass);
★ 클래스 네임과 윈도우 네임을 같이 주었는데 변경하여 차이를 보자. 스파이 이용.

//3번째 인자는 dwStyle. 만들고자 하는 윈도우의 형태. 일종의 비트 필드값이며 거의 수십개를 헤아리는 매크로 상수들이 정의되어 있고 이 상수들을 OR연산자로 연결하여 윈도우의 다양한 형태를 지정한다. 윈도우가 경계선을 가질 것인가, 타이틀 바를 가질 것인가 또는 스크롤 바의 유무 등등을 세세하게 지정해 줄 수 있다. 가능한 스타일값에 관한 자세한 내용은 레퍼런스를 참조하되 WS_OVERLAPPEDWINDOW를 사용하면 가장 무난한 윈도우 설정 상태가 된다. 즉 시스템 메뉴, 최대 최소 버튼, 타이틀 바, 경계선을 가진 윈도우를 만들어 준다.
//X, Y, nWidth, nHeight : 인수의 이름이 의미하듯이 윈도우의 크기와 위치를 지정하며 픽셀 단위를 사용한다. x, y좌표는 메인 윈도우의 경우는 전체 화면을 기준으로 하며 차일드 윈도우는 부모 윈도우의 좌상단을 기준으로 한다. 정수값을 바로 지정해도 되며 CW_USEDEFAULT를 사용하면 윈도우즈가 알아서 적당한 크기와 위치를 설정해 준다. 예제에서는 모두 CW_USEDEFAULT를 사용하였다.

//hWndParent : 부모 윈도우가 있을 경우 부모 윈도우의 핸들을 지정해 준다. MDI 프로그램이나 팝업 윈도우는 윈도우끼리 수직적인 상하관계를 가져 부자(parent-child) 관계가 성립되는데 이 관계를 지정해 주는 인수이다. 부모 윈도우가 없을 경우는 이 값을 NULL로 지정하면 된다.

//hmenu : 윈도우 클래스에서 지정한 메뉴를 그대로 사용하려면 NULL로 주면된다. 나중에 5장에서 배운다.

//lpvParam : CREATESTRUCT라는 구조체의 번지이며 특수한 목적에 사용된다. 보통은 NULL값을 사용한다.

hWnd = CreateWindow(lpszClass,lpszClass,WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,
NULL,(HMENU)NULL,hInstance,NULL);

if (strcmp(lpszCmdParam,"mimi")==0)
{
MessageBox(NULL, L"mimi", L" ", 0);
}

// hWnd 인수는 화면으로 출력하고자 하는 윈도우의 핸들이며 CreateWindow 함수가 리턴한 핸들을 그대로 넘겨주면 된다. nCmdShow는 윈도우를 화면에 출력하는 방법을 지정.
//nCmdShow 출력해보자. SW_HIDE도 써보자.
//디버그 뷰 이용
TCHAR DbgStr[256];
wsprintf(DbgStr, TEXT("%d"),nCmdShow);
MessageBox(NULL, DbgStr, TEXT(""),MB_OK);


ShowWindow(hWnd,nCmdShow);
//윈도우즈를 메시지 구동 시스템(Message Driven System)이라고 하며 이 점이 도스와 가장 뚜렷한 대비를 이루는 윈도우즈의 특징이다. 도스에서는 프로그래머에 의해 미리 입력된 일련의 명령들을 순서대로 실행하는 순차적 실행방법을 사용한다. 윈도우즈는 이와 다르게 프로그램의 실행 순서가 명확하게 정해져 있지 않으며 상황에 따라 실행 순서가 달라지는 데 여기서 말하는 상황이란 바로 어떤 메시지가 주어졌는가를 말한다. 메시지란 사용자나 시스템 내부적인 동작에 의해 발생된 일체의 변화에 대한 정보를 말한다. 예를 들어 사용자가 마우스의 버튼을 눌렀다거나 키보드를 눌렀다거나 윈도우가 최소화되었다거나 하는 변화에 대한 정보들이 메시지이다. 메시지가 발생하면 프로그램에서는 메시지가 어떤 정보를 담고 있는가를 분석하여 어떤 루틴을 호출할 것인가를 결정한다. 즉 순서를 따르지 않고 주어진 메시지에 대한 반응을 정의하는 방식으로 프로그램이 실행된다. 윈도우즈 프로그램에서 메시지를 처리하는 부분을 메시지 루프라고 하며 보통 WinMain 함수의 끝에 다음과 같은 형식으로 존재한다.

BOOL GetMessage( LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT 

wMsgFilterMax); 

이 함수는 시스템이 유지하는 메시지 큐에서 메시지를 읽어들인다. 읽어들인 메시지는 첫번째 인수가 지정하는 MSG 구조체에 저장된다. 이 함수는 읽어들인 메시지가 프로그램을 종료하라는 WM_QUIT일 경우 False를 리턴하며 그 외의 메시지이면 True를 리턴한다. 따라서 WM_QUIT 메시지가 읽혀질 때까지, 즉 프로그램이 종료될 때까지 전체 while 루프가 계속 실행된다. 나머지 세 개의 인수는 읽어들일 메시지의 범위를 지정하는데 잘 사용되지 않으므로 일단 무시하기로 한다.

BOOL TranslateMessage( CONST MSG *lpMsg); 
키보드 입력 메시지를 가공하여 프로그램에서 쉽게 쓸 수 있도록 해 준다. 윈도우즈는 키보드의 어떤 키가 눌러졌다거나 떨어졌을 때 키보드 메시지를 발생시키는데 이 함수는 키보드의 눌림(WM_KEYDOWN)과 떨어짐(WM_KEYUP)이 연속적으로 발생할 때 문자가 입력되었다는 메시지(WM_CHAR)를 만드는 역할을 한다. 예를 들어 A키를 누른 후 다시 A키를 떼면 A문자가 입력되었다는 메시지를 만들어 낸다.
// 해보자.
case WM_CHAR:
MessageBox(NULL, L"이미 있음", L"", MB_OK);
PostQuitMessage(0);
return 0;

SPY로 확인도 해보자.

LONG DispatchMessage( CONST MSG *lpmsg); 
시스템 메시지 큐에서 꺼낸 메시지를 프로그램의 메시지 처리 함수(WndProc)로 전달한다. 이 함수에 의해 메시지가 프로그램으로 전달되며 프로그램에서는 전달된 메시지를 점검하여 다음 동작을 결정하게 된다.
while(GetMessage(&Message,0,0,0)) {
TranslateMessage(&Message);
DispatchMessage(&Message);
}
//메시지 루프가 종료되면 프로그램은 마지막으로 Message.wParam을 리턴하고 종료한다. 이 값은 WM_QUIT 메시지로부터 전달된 탈출 코드(exit code)이다. 도스에서 사용하는 탈출 코드와 동일한 의미를 가지며 사용되는 경우가 거의 없다.
return (int)Message.wParam;
}

//WndProc은 WinMain에서 호출하는 것이 아니라 윈도우즈에 의해 호출된다. WinMain내의 메시지 루프는 메시지를 메시지 처리 함수로 보내주기만 할 뿐이며 WndProc은 메시지가 입력되면 윈도우즈에 의해 호출되어 메시지를 처리한다. 이렇게 운영체제에 의해 호출되는 응용 프로그램내의 함수를 콜백(CallBack) 함수라고 한다. callback함수 프로그램에서 호출한 함수가 실행중에 실행하도록 먼저 지정해둔 함수호출한 함수의 인수로서 콜백함수에 참조 정보를 넘겨둠으로 초출한 함수에서 임의의 콜백 함수를 실행시켜 콜백 함수에 실행 제어를 옮길 수 있다. 전화를 상대에게 한번 걸어서 전화번호만 알려주고 전화를 끊은 후, 상대방이 다시 전화를 걸어주는 것과 비슷함으로 콜백이라는 이름이 붙었다. 콜백 함수는 부른 곡에서 어떤 이벤트가 발생한 경우 처리를 지정할 목적으로 쓰는 경우가 많다. 콜백 함수를 쓰면 이벤트의 발생을 감지하기 위한 처리와 그 이벤트를 일어났을 때, 실행할 각각의 처리를 나누어서 코딩할 수 있다. C/C++에서는 함수 포인터를 불러 함수에 인수를 넘겨주는 것으로 콜백 함수가 움직인다. C/C++이 아닌 다른 언어에서도 서브 루틴의 참조를 다른 서브 루틴에게 넘겨서 실행할 수 있게 되어진 언어에서는 동일하게 콜백 함수를 쓸 수 있다. 
//LRESULT : F12 따라가보자.
//long 형.그럼 반환값을 다르게 줘보자.
0 또는 1로.

LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM 

wParam,LPARAM lParam)
{
switch(iMessage) {
case WM_DESTROY:
//PostQuitMessage 함수는 여러 가지 면에서 일반적인 메시지 함수와는 다르게 동작합니다. 왜냐하면 프로세스를 종료하는 특수한 명령이기 때문입니다. PostMessage(WM_QUIT)에 비해 대여섯가지 차이점이 있는데 가장 이해하기 쉬운 예를 들자면요.
PostQuitMessage(0);
명령1;
명령2;
 
이런 상황일 때 프로그램을 종료하고 난 후라도 명령1과 명령2까지 다 수행을 해야 정상적인 종료가 됩니다. WM_QUIT는 메시지 루프를 종료시키므로 PostMessage로 보내서는 이런 효과를 낼 수가 없습니다. 그래서 별도의 함수를 제공하는데 이 함수는 실제로 프로그램을 종료하는 것이 아니라 다음 아이들 타임에 프로그램을 종료하도록 특별한 플래그를 세트하는 역할만 합니다. 다소 복잡한 내용이라 저도 바로 기억을 떠 올리기 어려운데요, 좀 더 자세한 내용에 대해 알고 싶으시면 Jeff의 책을 참고하세요. 그리고 스파이는 훅을 사용하는 것이 맞습니다. MSDN에 소스도 공개되어 있으니 어렵지 않게 찾을 수 있을 겁니다. 소스내용을 보시면 보기보다 특별한 내용이 없어 쉽게 분석 됩니다.

PostQuitMessage(0);
return 0;
}
return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}


'Win32 Api > 강의' 카테고리의 다른 글

CAM을 활용한 영상처리 프로그램  (0) 2011.03.03
비트맵뷰어  (0) 2010.12.28
2010년 11월 22일 월요일 (첫 강의)  (0) 2010.11.22