/* 
written by kaspy (kaspyx@gmail.com)
*/ 

 이번 블로그 내용은 Windows 운영체제 환경에서 실제적인 출력을 담당하고 있는 윈도우에 대해서 다루기로 하겠습니다.-*

기초적인 내용이라기보다는 기본적인 내용에 가깝고, 공부하면서 도움이 될만한 중요한것들만 정리해 보았습니다..



1. 윈도우는 'Windows 운영체제'(이하 Windows)가 관리하는 객체이다.

 화면에 뜨고 우리가 직접 보는 '윈도우'는 Windows 에서 관리하는 객체의 일종이다. 객체에 따라 대응되는 화면이 표시된다. 즉 평상시 사용자가 보고있는 윈도우는 Windows 내부에 있는 윈도우 객체가 출력된 형태이다. 대표적으로 윈도우를 띄우는 함수는 CreateWindow API이며 프로토 타입은 아래와 같다. Windows 에서는 기본값으로 픽셀 단위 좌표계를 사용하며, 화면의 좌측 상단 구석이 원점(0,0), 우측 방향이 X축 방향(양수+), 아래 방향이 y축 양수(+) 방향이다. (화면 해상도가 1024 x 768 일 경우 우측 하단 구석의 좌표는 1023, 767)


  1. HWND CreateWindow(              // 리턴값: 생성된 윈도우 핸들
  2.         LPCTSTR lpClassName,    // 윈도우 클래스명
  3.         LPCTSTR lpWindowName,   // 윈도우 이름
  4.         DWORD dwStyle,          // 윈도우 스타일
  5.         int x,                  // 출력할 윈도우의 좌측 상단 x좌표
  6.         int y,                  // 출력할 윈도우의 좌측 상단 y좌표
  7.         int nWidth,             // 수평방향 크기
  8.         int nHeight,            // 수직방향 크기
  9.         HWND hWndParent,        // 부모 윈도우 핸들
  10.         HMENU hMenu,            // 메뉴 핸들
  11.         HINSTANCE hInstance,    // 윈도우를 생성하는 프로그램의 인스턴스 핸들
  12.         LPVOID lpParam          // WM_CREATE 메시지에 넘길 전달 인자
  13. );

2. 윈도우 스타일로 윈도우의 구성요소및 종류를 지정한다.

 세번째 인자중에 'dwStyle' 이라는 멤버가 있는데 이값은 윈도우의 외관이나 종류 등을 지정하기 위한 32비트 정수값이다. 제목 표시줄의 유무나 크기 변경 가능 여부 등등을 지정해줄수 있다. 

 각각의 속성을 부여해주고 싶으면 OR 연산으로 조합해주면 된다. 예를들어 최대화 버튼을 추가하고 싶으면 WS_MAXIMIZEBOX를 OR연산 해주면된다. 아래는 구성요소에 따른 속성값 들이다.


- 윈도우 스타일 

 스타일

구성요소 

WS_CAPTION 

제목 표시줄 

WS_BORDER

가는 윈도우(크기변경 불가능) 프레임 

WS_DLGFRAME 

대화상자에서 사용하는 윈도우 프레임 

WS_THICKFRAME 

굵은(크기 변경 가능) 윈도우 프레임 

WS_HSCROLL 

수평 스크롤바 

WS_VSCROLL

수직 스크롤바 

WS_SYSMENU 

시스템 메뉴, WS_CAPTION과 함께 지정 

WS_MAXIMIZEBOX

최대화 버튼, WS_SYSMENU와 함께 지정 

WS_MINIMIZEBOX 

최소화 버튼, WS_SYSMENU와 함께 지정 


CreateWindow 함수를 호출 하기전에 

  1. ....
  2. DWROD style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU;
  3. ...
  4. CreateWindow("haha","hoho",style,...);
  5. ...

(대략 요로코롬 쓴다...)


그런데 구성요소 값들을 미리 OR로 묶어서 간편하게 만든 값들이 있는데 이값에 따라 윈도우의 종류가 결정된다.윈도우는 성질의차이에 따라 세가지로 구별된다.


(1) 오버랩(Overlapped) 윈도우

(2) 팝업(Popup) 윈도우

(3) 자식(Child) 윈도우


오버랩 윈도우란 애플리케이션의 메인 윈도우로 사용하는 형식이다. 그냥 일반적으로 보는 윈도우 화면이라고 보면된다. 또한 팝업 윈도는 트레이 아이콘에 출력되는 윈도우라고 보면되고 자식 윈도우는 메인 윈도우가 될수없는 윈도우 타입으로 엑셀 편집기 안에 떠있는 sheet 라는 화면을 예로 들면 될것이다.


- 윈도우 스타일값에 따른 윈도우 종류

 스타일

윈도우의 종류

 WS_OVERLAPPED

 오버랩 윈도우. 제목 표시줄과 프레임이 있다. 

 WS_POPUP

 팝업 윈도우

 WS_CHILD

 자식 윈도우. 메뉴 표시줄을 사용하지 못한다.

 WS_OVERLAPPEDWINDOW

 WS_OVERLAPPED에 WS_CAPTION, WS_SYSMENU, WS_THICKFRAME, WS_MAXIMIZEBOX, WS_MINIMIZEBOX를 조합한 윈도우이다. 

 WS_POPUPWINDOW

 WS_POPUP에 WS_BORDER와 WS_SYSMENU를 조합한 윈도우로, WS_CAPTION을 조합했을 경우에만 메뉴가 나온다. 

 WS_CHILDWINDOW

 WS_CHILD와 같다.


-그외의 윈도우 스타일

 스타일

의미

 WS_MAXIMIZE

 초기 상태에서 윈도우를 최대화 한다.
 WS_MINIMIZE

 초기 상태에서 윈도우를 최소화 한다. 

 WS_VISIBLE

 초기 상태에서 윈도우를 나타낸다. 

 WS_DISABLED

 초기 상태에서 윈도우를 사용할수 없다. 

 WS_TABSTOP

 대화상자에서 탭 키를 눌렀을때, 입력 포커스를 사용할수 있다. 

 WS_GROUP

 대화 상자에서 컨트롤을 그룹화할때 지정한다. 

 WS_CLIPCHILDREN

 클라이언트 영역 내의 자식 윈오두가 있는 부분은 그릴 대상에서 제외한다. 

 WS_CLIPSIBLINGS

 같은 부모를 가진 자식 윈도우끼리 겹쳐진 부분은 그릴 대상에서 제외한다


- 확장 윈도우 스타일

 WS_EX_CLIENTEDGE  클라이언트 영역을 두드러져 보이게한다. 

 WS_EX_WINDOWEDGE

 윈도우 프레임을 입체적으로 보이게한다.

 WS_EX_OVERLAPPEDWINDOW

 WS_EX_CLIENTEDGE와 WS_EX_WINDOWEDGE를 조합한것이다.

 WS_EX_CONTEXTHELP

 콘텍스트 도움말 출력용 버튼을 제목표시줄에 추가한다. 

 WS_EX_DLGMODALFRAME

 입체적으로 보이는 대화상자용 프레임을 출력한다. 

 WS_EX_TOOLWINDOW

 작은 제목 표시줄을 사용하는 플로팅 툴바용 윈도우


3. 부모 윈도우는 자식 윈도우의 디스플레이

 윈도우 스타일에 WS_CHILD를 지정하거나 CreateWindow의 인자인 hWndParent에 부모 윈도우의 핸들을 지정하면 자식 윈도우를 생성할수 있다.

자식 윈도우는 다른 윈도우와 달리 부모 윈도우의 클라이언트 영역의 바깥쪽에 출력하거나 이동할수 없는 특징이 있다. 부모 윈도우가 종료되면 자식 윈도우도 자동으로 종료되게 된다.


4. 윈도우 간 소유관계가 있다.

 부모와 자식 관계에서 파생된 개념인데, 자식 관계에 있는 윈도우는 부모 윈도우 밖으로 나올수 없다. 그러나 피소유 윈도우는 소유 윈도우 윈도우 바깥으로 나와서 출력될수 있다. 대표적인 예로 IE에서 Ctr + F를 눌렀을때 뜨는 대화상자라고 보면 되겠다. 소유 관계를 설정하고 싶다면, 피소유 윈도우를 생성할떄, CreateWindow AIP에서 WS_CHILD를 설정하지 않으면서 hWndParent에 소유 윈도우 핸들을 지정하면 된다.


(* 대략 아래와 같이 조금 다르다.)

  1. // Child Window
  2. hWndChild = CreateWindow("ClassName","Child",
  3.                         WS_CHILDWINDOW | WS_CAPTION | WS_VISIBLE,
  4.                         0,0,200,100,
  5.                         hWnd0, hInst, NULL);
  6.  
  7. // Owned Window
  8. hWndOwned = CreateWindow("ClassName","Owned",
  9.                         WS_POPUPWINDOW | WS_CAPTION | WS_VISIBLE,
  10.                         0,0,200,100,
  11.                         hWnd0, hInst, NULL);



* 참고자료 및 서적

API로 배우는 Windows 구조와 원리

C언어 윈도우즈 API 프로그래밍

 

* 링크 참조


윈도우(Windows) 애플리케이션(application)의 구조

윈도우 메시지(Message) 

저작자 표시
신고
Posted by 캐스피
/* 
written by kaspy (kaspyx@gmail.com)
*/ 


Windows 프로그래밍을 해본 경험이 있는 사람이라면 API 및 Win32 API를 어떤 형태로든 접해본적이 있을 것이다.

API란 Application Programming Interface라는 약어로 윈도 운영체제의 기능을 애플리케이션에서 이용하기 위한 인터페이스로, 그 실체는 C/C++ 언어로 개발한 수천개의 함수의 집합이다.

기초적인 내용이라기보다는 기본적인 내용에 가깝고, 도움이 될만한 중요한것들만 정리해 보았다.


1. Windows 애플리케이션은 이벤트 반응형이다

 Windows의 GUI 애플리케이션을 시작하면 윈도를 출력할 뿐이며, 일반적으로 아무것도 하지 않는다. 메뉴를 선택하거나 마우스 버튼을 클릭하거나 키를 누르는 등의 사용자 조작에 따라 처리해 준다. 이처럼 사용자 조작으로 이벤트가 발생하고, 그때마다 대응되는 이벤트에 처리를 하는 프로그램을 '이벤트 반응형'이라고 한다.

 이벤트 반응형 프로그램이 동작 하기 위해선 이벤트가 발생했음을 알려주는 구조가 필요하다.Windows 에서는 운영체제인 Windows 자신이 장치를 항상 감시하다가 필요에따라 애플리케이션에 통지한다.

 Windows 에서는 감지한 이벤트의 상당수는 특정 애플리케이션을 향해 통지된다. 자체 내부적으로 Windows 메시지 대기열 큐에서 발생한 메시지를 순차적으로 각각의 애플리케이션에 통지하고 각각의 애플리케이션은 메시지를 처리하는 방식을 사용하고있다. 이런 대기행렬을 시스템 큐라고한다.

2. 메시지를 사용해서 이벤트를 통지한다

 Windows 에서 각각의 애플리케이션 윈도우에 이벤트를 통지하려면 '윈도우 메시지(이하 메시지)' 라고 하는 데이터 구조체를 사용하며 그 실체는 아래와 같이 정의 되어있다.

  1. typedef struct tagMSG{
  2.         HWND hwnd;      // 윈도우 핸들
  3.         UINT message;   // 메시지 ID
  4.         WPARAM wParam;  // 메시지 전달 인자 1
  5.         LPARAM lParam;  // 메시지 전달 인자 2
  6.         DWORD time;     // 이벤트 발생 시각
  7.         POINT pt;       // 이벤트 발생시 커서위치
  8. } MSG, *PMSG;

 첫번째 hwnd 라는 멤버는 메지지의 행선지(받을 윈도우)를 식별하기 위한 정수값이다. 애플래케이션이 여러개 띄워져 있는경우 어떤 윈도우인지를 알기위한 식별자인 셈이다.

 두번째, message라는 멤버는 메시지의 종류를 나타내는 ID이다. 일반적인 메시지는 winuser.h 안에 'WM_' 으로 시작하는 심볼형태로 수백개가 정의되어있다. 발생한 이벤트의 종류를 알아내려면 이 값을 보면된다.

 세번째, 네번째 wParam, lParam은 message에 대한 추가적인 정보인데, 메시지의 종류에따라 의미가 다르다. 예를들어 마우스의 왼쪽 버튼이 눌렸을때 받는 메시지(WM_LBUTTONDOWN)에서는 마우스 위치와 Shift 키나 Alt 키 등의 상태가 담겨오고, 키보드가 눌렸을때의 메시지(WM_KEYDOWN)일때는 쿨린 키의 종류를 나타내는 값이 저장되어진다.

 그리고 나머지 2개 time과 pt는 각각 이벤트가 발생했을 때의 시각과 그 순간의 마우스 커서위치를 나타낸다.

3. 윈도우 프로시저를 통해 메시지를 처리한다

 메시지 큐에서 꺼내온 메시지는 그 행선지가 되는 윈도우에 전송되어 거기서 처리된다. 메시지의 행선지가 윈도우 이므로 행선지 윈도우 안에서 처리해야 하는 것이다. 실제 Windows에서는 윈도우에 관련된 특별 함수인 '윈도우 프로시저'에서 메시지를 처리한다

 윈도우 프로시저의 프로토타입은 아래와 같다.

  1. LRESULT CALLBACK WindowProc (
  2.         HWND hwnd,      // 윈도우 핸들
  3.         UINT uMsg,      // 메시지 ID
  4.         WPARAM wParam,  // 메시지 전달인자 1
  5.         LPARAM lParam   // 메시지 전달인자 2
  6. );

 여기서 윈도우 프로시저는 각 윈도우에 연관된 함수인데, 왜 윈도를 식별하는 핸들이 인자로 필요한 것일지 궁금해할지도 모르겠다. 이는 윈도우 프로시저에서 담당하는 윈도우가 반드시 하나라고만 단정할수 없기 때문이다. 예를들어, 애플리케이션에서 동일한 행동을 하는 윈도우 2개를 만들수도 있다. ex 한글 및 엑셀 같은 애플리케이션은 하나의 윈도 안에 자식 윈도가 여러개있다.

 이때문에 Windows에서는 각각의 윈도우와는 별도로 '윈도우 클래스'를 제공한다. 윈도우 클래스는 윈도우 프로시저를 포함해서 일부 공통된 성질을 그룹화 한것이다. 즉 윈도우 클래스는 직접 등록하는것이 일반적이고, 애플리케이션은 메지시 큐에서 메시지를 하나씩 꺼내서 윈도우 프로시저에 전송한다. 그리고 윈도우 프로시저에서는 이벤트에 대응하는 처리가 수행된다.

4. WinMain 함수에서 실행을 시작한다

 아래는 Windows 애플리케이션의 전체 소스코드로, 실행하면 회색 윈도 하나를 출력한다. 윈도 애플리케이션이라면 아래와 같은 구조를 따른다고 보면 되겠다.

  1. #include <Windows.h>
  2.  
  3. // 윈도우 클래스명
  4. #define MYWNDCLSNAME "MyWindowClass"
  5.  
  6. LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
  7.  
  8. int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow)
  9. {
  10.         WNDCLASS wndcls;
  11.         HWND hWnd;
  12.         MSG msg;
  13.  
  14.         // 윈도우 클래스 등록
  15.         ZeroMemory(&wndcls,sizeof(wndcls));
  16.         wndcls.lpfnWndProc = WndProc;
  17.         wndcls.hInstance = hInst;
  18.         wndcls.hIcon = LoadIcon(0,IDI_APPLICATION);
  19.         wndcls.hCursor = LoadCursor(0, IDC_ARROW);
  20.         wndcls.hbrBackground = (HBRUSH) COLOR_BACKGROUND;
  21.         wndcls.lpszClassName = MYWNDCLSNAME;
  22.         if (RegisterClass(&wndcls) == 0) return -1;
  23.  
  24.         // 메인 윈도우 생성
  25.         hWnd = CreateWindow(MYWNDCLSNAME, "My WIndow",
  26.                 WS_OVERLAPPEDWINDOW,
  27.                 CW_USEDEFAULT, CW_USEDEFAULT,
  28.                 CW_USEDEFAULT, CW_USEDEFAULT,
  29.                 0, 0, hInst, NULL);
  30.         if (hWnd == 0) return -2;
  31.  
  32.         // 윈도우 초기상태 지정
  33.         ShowWindow(hWnd, nCmdShow);
  34.         UpdateWindow(hWnd);
  35.  
  36.         // 메시지 루프
  37.         while(GetMessage(&msg, 0,0,0))
  38.         {
  39.                 DispatchMessage(&msg);
  40.         }
  41.  
  42.         // WM_QUIT 메시지의 wParam을 종료코드로 한다.
  43.         return msg.wParam;
  44. }
  45.  
  46. LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  47. {
  48.         switch(uMsg)
  49.         {
  50.         case WM_DESTROY:
  51.                 PostQuitMessage(0);
  52.                 return 0;
  53.         }
  54.  
  55.         // 직접 처리하지 않은 메시지는 Windows에게 맡긴다.
  56.         return DefWindowProc(hWnd, uMsg, wParam, lParam);
  57.  
  58. }

 예제의 1번 라인, 앞 부분에는 Wndows.h라고 하는 헤더파일을 인클루드(include)한다. 이헤더파일은 Platform SDK에 포함된 헤더파일로, Win32 API를 C언어 함수에서 호출할때 필요한 프로토타입 선언이 들어있다. 따라서 Win32 API를 사용한 프로그램을 작성할때는 반드시 필요하다.

8번 라인은 WinMain이라는 함수로 프로그램 실행시 최초 실행되는 함수이다. C언어를 배운사람이라면 main() 함수라고 생각하면 된다. WinMain 함수의 인자들을 설명하자면, "hInst" 라는 실행중인 애플리케이션을 식별하는 정수값(인스턴스 핸들)을 받는다. 자주 쓰이는 인자는 아니지만 애플리케이션 내부적으로 필요한 경우가 많으므로 전역변수로 저장해두는 경우가 많다.

 두번째 인자인 hPrevInst도 인스턴스 핸들이지만, Windows3.1과 16비트 Windows와 호환을 위해 남아있다. 보통 0으로 저장되어있고 사용할 일이 없을 것이다. 세번째 인자 lpCmdLine은 명령행 문자열의 포인터이다. main함수의 argv와는 달리 전달인자마다 분리될수 있는 형태가 아니기 떄문에 필요하다면 스스로 문자열을 해석해야한다. CLI 기반이라면 쓸수도 있겠지만, GUI기반 애플리케이션인만큼 이 인자도 자주 쓸일은없다.

 네번째 인자인 nCmdShow는 애플리케이션이 띄우는 메인 위도우의 최초 출력상태를 지정해준다. 이값에 따라 윈도우를 HIDE할수도 있다. 보통 디폴트값을 지정해준다.

5. 윈도우 클래스를 등록해 윈도우를 생성한다

 위에 윈도 예제 소스를 가지고 이어서 설명하도록 하겠다. 22번 라인 을 보면일단 RegisterClass API를 사용해서  윈도우 클래스를 등록한다는 볼수있을 것이다. 등록 하려는 윈도우 클래스 정보를 저장한 WNDCLASS 구조체를 준비해서 인자에 그 주소를 넘겨주면된다. WNDCLASS에 대한 구조체는 아래와 같다.

  1. typedef strcut _WNDCLASS{
  2.         UINT style;     // 윈도우 클래스 스타일
  3.         WNDPROC lpfnWndProc;    //윈도우 프로시저의 주소
  4.         int cbClsExtra;         //윈도우 클래스의 추가 데이터크기
  5.         int cbWndExtra;         //윈도우 추가 데이터 추기
  6.         HINSTANCE hInstance;    // 윈도우 프로시저를 소유한 프로그램의 인스턴스 핸들(대부분 자신)
  7.        
  8.         HICON hIcon;            // 애플리케이션의 아이콘
  9.         HCURSOR hCursor;        // 마우스 커서
  10.         HBRUSH hbrBackground;   // 윈도우 배경색
  11.         LPCTSTR lpszMenuName;   // 메뉴 리소스명
  12.         LPCTSTR lpszClassName;  // 이 윈도우의 클래스 등록명
  13. } WNDCLASS, *PWNDCLASS;

25번 라인은 메인 윈도우 생성이다. CreateWindow API를 사용하는데, 각 인자는 아래와 같다.

  1. HWND CreateWindow(              // 리턴값: 생성된 윈도우 핸들
  2.         LPCTSTR lpClassName,    // 윈도우 클래스명
  3.         LPCTSTR lpWindowName,   // 윈도우 이름
  4.         DWORD dwStyle,          // 윈도우 스타일
  5.         int x,                  // 출력할 윈도우의 좌측 상단 x좌표
  6.         int y,                  // 출력할 윈도우의 좌측 상단 y좌표
  7.         int nWidth,             // 수평방향 크기
  8.         int nHeight,            // 수직방향 크기
  9.         HWND hWndParent,        // 부모 윈도우 핸들
  10.         HMENU hMenu,            // 메뉴 핸들
  11.         HINSTANCE hInstance,    // 윈도우를 생성하는 프로그램의 인스턴스 핸들
  12.         LPVOID lpParam          // WM_CREATE 메시지에 넘길 전달 인자
  13. );

이함수는 윈도우 생성에 성공하면 윈도우 핸들을 반환해준다. 4~7 번째 인자에는 CW_UESDEFAULT 라는 상수값이 지정되어있는데, 이는 윈도우 출력 크기를 Windows에게 맡긴다라는 의미이다. 또한 부모 윈도우와 메뉴 핸들은 0을 넘기면된다.(부모 윈도가 없으므로, 메뉴도 마찬가지)

 33번,34번 라인은 윈도우를 출력해주는 함수로 nCmdShow 라는 값을 넘겨서 윈도우가 보일수 있게 출력해주고 UpdateWindow라는 함수가 윈도우의 변화가 있을경우에 실제 그려주는 API이다.

6. 메시지 루프로 메시지를 하나씩 꺼낸다

 37번 라인 이어서 윈도우를 출력했으면 중요한 메시지 루프를 볼 차례이다. while 문안에서 호출되는 GetMessage는 메시지 큐에서 메시지를 꺼내는 API이다. 첫번째 인자인 MSG 구조체는 수신한 메시지를 저장하는 주소이다. 두번쨰 이후의 인자에는 특정 종류의 메시지만 꺼낼때의 메시지 범위를 지정한다. 보통 0을 지정한다.

 GetMessage의 반환값 형태는 보통 BOOL이며, 꺼낸 메시지가 애플리케이션의 종류를 의미하는 WM_QUIT 인 경우에만 FALSE를 반환한다. 따라서 예제 소스는 WM_QUIT 메시지(종료버튼을 눌렀을때 발생)를 받을때까지 루프가 계손돈다. 39번 라인은 DispatchMessage라는 API 하나만 호출되는데 이는 윈도우 프로시저를 MSG 구조체의 hwnd 멤버를 참조해서 윈도우 프로시저를 호출하도록 해주는 API이다. 메시지 처리를 WndProc에서 하게끔 자동으로 지정해주는 함수라고 보면된다.

7. 메시지의 대부분은 DefWindowProc 함수에 처리를 맡긴다.

 이어서 46번 라인 윈도우 프로시저 함수를 보겠다. 설명했듯이 윈도우 프로시저는 메시지를 처리해주는 함수이다. 16번 라인에서 윈도우를 등록하기전에 프로시저 함수를 지정해준것을 확인할수있다. 단 메시지 처리 방식은 메시지 종류별로 case로 처리하면 되는것이고, 나머지 함수들에 대해서는 DefWindowProc에 이벤트를 넘겨서 처리해주면된다.

 예제는 WM_DESTROY 라는 메시지만 처리하고 나머지는 DdefWindowProc을 호출하여 처리를 맡긴다.


* 참고자료 및 서적

API로 배우는 Windows 구조와 원리

* 다음 링크 참조

http://kaspyx.tistory.com/entry/윈도우Window와-메시지Window-Message

신고
Posted by 캐스피


티스토리 툴바