본문으로 바로가기

Day-14 Win32 API 이야가와 실습1

category Programming/TIPS 17기 2017. 8. 15. 14:11

 

 

핸들(Handle)

 

핸들을 알기 전에 운영체제와 리소스에 대해 알아둘 필요가 있다. 운영체제(Operating System)는 컴퓨터를 구성하는 여러 가지 장치를 관리해준다. 따라서 응용 프로그램 개발자는 컴퓨터라는 복잡한 시스템을 직접적으로 제어하지 않아도 된다. 리소스(Resource)는 운영체제에 의해서 관리되는 장치나 해당 장치를 사용하기 위해 필요한 정보들을 말한다. 또한 리스소는 대부분 메모리로 구성되어 있어 주소를 가지고 있다. 따라서 푸인터를 사용하여 주소에 접근 할 수 있다. 하지만 직접적으로 포인터를 사용해 주소에 접근하면 안정성에 문제가 생긴다. 즉, 누군가가 주소를 통해 운영체제의 중요 정보를 빼앗기거나 동작이 불가능한 상태가 될 수도 있다.

 

핸들은 윈도우 프로그램에서 대부분 사용된다.(단, 핸들은 윈도우에서 만들어진것이 아니라 UNIX부터 존재했다.) 운영체제는 어플리케이션의 공격을 막아야 한다. 응용 프로그램이 운영체제 리소스에 직접 접근할 수 있다는 것은 운영체제 리소스를 직접 파괴할 수 있다는 것이다. 즉, 리소스에 직접 접근하는 것을 운영체제가 막아야한다. 위에서 설명처럼 포인터 기술을 쓰면 좋겠지만 자신의 모든 상태를 외부로 알려줄 수 있기 때문에 주소를 어플리케이션에 알려주지 않으면서 주소를 사용한것과 같은 기능을 제공해야 한다.

따라서 운영체제는 자신을 지키기 위해서 주소값 대신 핸들을 사용한다.

 

핸들의 원리는 다음과 같다. 응용 프로그램이 운영체제 리소스를 알고 싶어서 핸들 테이블에 리소스의 주소를 기록하고 아무 상수값을 저장한다. 주소와 핸들값을 연결해주는것이 핸들 테이블이고 API에게 상수값을 알려준다. 윈도우에서 핸들은 대부분 unsigned int 형이다. 핸들을 의미하는 자료형은 모두 앞에 H로 시작하고 핸들 타입 변수라는 뜻이다.

 

응용 프로그램에서 리소스에 대한 핸들 값 얻기

 

1. 응용 프로그램이 API 함수에 원하는 리소스 사용 요청을 한다.

2. API 함수가 운영체제 리소스에 요청한 리소스의 메모리 주소를 확인한다.

3. 운영체제 리소스가 핸들 테이블에 리소스의 주소를 기록하고 함께 사용할 핸들 값을 무작위로 생성한다.

4. 주소를 API 함수에 알려주 않고 주소값에 대응하는 핸들 값을 API 함수에 알려준다.

5. API 함수가 응용 프로그램에 핸들 값을 전달한다.

 

핸들 값을 사용하여 운영체제 내부의 리소스를 사용하는 방법

 

1. 응용 프로그램이 API 함수에 핸들 값으로 리소스의 현재 값 확인을 요청한다.

2. API가 핸들 테이블의 핸듵 값을 확인한다.

3. 핸들 테이블의 핸들 값으로 리소스의 주소를 얻어서 해당 운영체제의 리소스로 이동한다.

4. 운영체제 리소스에서 해당 리소스의 주소를 사용하여 API 함수에 현재 값을 읽어서 전달한다.

5. API 함수가 리소스의 현재 값을 응용 프로그램에 전달한다.

 

HINSTANCE

 

HINSTANCE는 프로그램에서 많이 사용될수도 사용되지 않을수도 있다.

HINSTANCE는 H(Handle) + Instance로 구성도니 이름이다. Instance Handle을 저장한다. Instance Handle은 윈도우즈 운영체제에서 실행되는 프로그램을 구별하기 위한 ID값이다. Instance Handle은 정수 값이고 프로그램을 구별하기 위한 값이기 때문에 동일한 프로그램을 여러개 실한하면 동일한 Instance Handle값을 가진다.

 

INSTANCE의 상황별 뜻을 알아보자.

 

프로그래밍

 

INSTANCE : 객체를 메모리에 할당하고 객체를 생성하는 것이다.

 

운영체제(C, C++ 언어와 상관없다.)

 

INSTANCE : 핸들값이 굉장히 많은데 그 중 하나이고 프로그램을 구별하는 값이다.

INSTANCE ID는 프로그램마다 서로 다른 ID를 가지는데 같은 프로그램을 두개 실행시키면 같은 ID를 갖는다.

 

Process ID

 

프로그램 하나하나를 구별하기 위한 ID이다. 같은 프로그램을 두개 실행해도 각각 ID를 따로 갖는다.

윈도우즈 운영체제는 같은 프로그램이 두 개 실행되면 물리적으로 하나이지만 논리적으로 두 개이다.

같은 프로그램을 두 개 실행하면 운영체제가 어떤 메모리 공간에 A라는 메모리 패턴을 올린다. 하지만 프로그램이 가지고 있는 아이콘이나 비트맵 같은것은 바뀌지 않는다. A라는 곳에 바뀌지 않는 것을 다 구성한다. 실제 같은 프로그램을 실행하면 실제 메모리는 기존의 프로그램과 다른 곳만 사용하고 바뀌지 않는 요소는 참조해서 사용한다.

 

 

HINSTANCE 얻는 법

 

기존 C, C++ 언어 프로그램을 시작할 때는 main함수에서 시작했지만 API 프로그램을 시작할 때는 WinMain 함수에서 시작한다.

윈도우즈 운영체제는 파스칼 방식의 스택프레임을 사용한다. C언어의 스택프레임은 효율적이지만 안정적이지 않다. C언어의 스택프레임은 호출자가 매개변수를 만들어주고 호출하는 구조이다. 함수가 함수를 호출할 때 발생하는 메모리 구조이다. 매개변수를 만드는 것은 호출자가 만들어준다. 사용은 피호출자가 사용한다. 호출자가 매개변수를 만들어주고 피호출자에게 권한을 넘겨준다. 매개변수를 만들때 주소에 접근을 해야하는데 운영체제에 직접 접근을 해야한다. 스택 프레임 형태를 C로 접근하면 안정성이 떨어지기 때문에 피호출자가 모든 일을 맡아서하는 파스칼 방식의 스택 프레임을 생성했다. WinMain은 파스칼 방식의 스텍프레임을 사용한다는 뜻이다.

 

 

HINSTANCE hPrevInstance는 지금 사용하지 않아서 null을 적어준다.

예전에 같은 프로그램의 인스턴스가 달라서 이전에 실행된 같은 프로그램의 인스턴스를 적어주는것인데 지금은 같은 프로그램이 같은 인스턴스를 가지고 있어서 사용하지 않는다.

 

cmdline main 함수에 인자가 두 개 넘어올때가 있다. 실행파일 뒤에 적은 이름이 lpCmdLine에 들어온다. 아래 사진을 보자.

 

 

 

int nCmdShow : n은 Number의 접두어이다. CmdShow는 속성 실행에 기본창이라고 되어 있는데 최소화는 최소화 사이즈, 최대화는 최대화 사이즈이다.

 

 

 

 

Window Class에 대하여(중요!) (C++언어의 class 문법과 전혀 관련이 없다.)

 

Window Class는 출생신고에 해당하는것에 비유할 수 있다. window class를 사용하면 window의 통제를 받는다.

 

Window Class에 대해 자세히 알아보기 전에 Window System의 event와 message의 용어에 대하여 자세히 알아보자.

 

event : 행위가 발생했다. 발생하지 않았다. on/off 개념이다.

 

message : message event에 특정량의 추가적인 정보가 붙은것이다. event외의 4바이트 정보이다. WM_...(Window Message의 약자이다.) 어플리케이션의 변화 시스템의 변화는 운영체제가 window에 전달하는 메세지이다.

 

운영체제에 프로그램을 띄운다. 그리고 마우스로 클릭한다. 클릭한다는 사실은 운영체제가 알수있고 누구한테 클릭했는지 확인하고 window에 메세지를 보낸다. 이 메세지를 처리하는 것이 Window Procedure가 처리한다. window programe은 안에 들어있는 내용만 작업해주면 된다.

여기서 Window Procedure는 어떤 메세지가 Window에 발생한 경우 그 메세지를 어떻게 처리할 것인지를 정의한 함수이다.

 

Window Class는 WNDCLASS 구조체로 관리되며 이 구조체는 'WinUser.h' 헤더 파일에 정의되어 있다.

 

 

UINT style; 

 

Window의 그리기 특성과 관련된 정보를 설정하거나 몇 가지 사용자 환경을 추가하거나 제한하는 값을 저장합니다.

 

WNDPROC lpfnWndProc;

 

Window에서 전달된 메시지를 처리하는 함수이다. Window에 메세지가 전달되면 운영체제는 해당 Window의 'Window Class'에 저장된 'Window Procedure'를 호출해서 전달된 메세지를 프로그래머가 원하는 데로 처리한다. 단, 이 메세지 처리를 프로그래머가 원하는대로 작업하려면 'Window Procedure' 함수를 프로그래머가 직접 구현해야 한다. 여기서 모순(?) 같은 상황이 발생한다. Window Procedure 함수는 운영체제가 호출하도록 되어있다. 하지만 운영체제 개발자가 운영체제를 개발하는 시점에서 Window Procedure 함수가 존재하지 않기 때문에 Window Procedure 함수를 호출하는 코드를 생성할 수 없다. 따라서 이러한 모순(아직 만들어지지 않은 함수를 호출하도록 코드를 구성해야한다.)을 해결하기 위해 함수의 포인터를 사용한다.

 

typedef LRESULT (CALLBACK* WNDPROC) (HWND, UINT, WPARAM, LPARAM);

//  LRESULT CALLBACK MyProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

 

UINT : unsigned int형이다.

WPARAM은 16비트, LPARAM은 32비트이다. // 현재는 둘다 32비트를 사용한다.

WPARAM은 조합키이다. 마우스 왼쪽 클릭 + CTRL 등이다. LPARAM의 하위 16비트에 X좌표 LPARAM의 상위 16비트에 Y좌표이다.

 

int cbClsExtra;

 

동일한 'Window Class'를 사용하는 Window들이 공유할 수 있는 메모리의 크기를 설정한다.

 

int cbWndExtra;

 

HISTANCE hInstace;

 

Windows 운영체제가 실행된 프로그램을 구별하기 위한 교유값이다.

 

HICON hIcon;

 

Window의 좌측 상단에 표시되는 작은 이미지를 Window의 로고인데 이 로고 이미지로 사용할 아이콘의 핸들 값을 저장한다.

 

HCURSOR hCursor;

 

Window의 클라이언트 영역에 마우스가 위치했을 때 사용할 마우스 커서에 대한 핸들 값을 저장한다.

wc.hCursor = LoadIcon(NULL, IDI_APPLICATION);

NULL은 운영체제가 기본적으로 제공하는 아이콘을 사용하겠다는 것이다. 운영체제마다 ICON이 다르다.

 

HBRUSH hbrBackground;

 

Window 배경을 그릴 때 사용할 'Brush Object'에 대한 핸들 값을 저장한다.

Default는 흰색이다.

 

LPCWSTR lpszMenuName;

 

Window가 사용할 메뉴 이름을 저장한다.

 

LPCWSTR lpszClassName;

 

Window Class의 이름을 유니코드 문자열 형식으로 저장한다.

 

lpsz(long point string zero)

 

"일반 문자열을 다루는 포인터인데 끝에 널문자가 있다."라는 뜻이다. 요즘 lps만 쓰이는 경우도 있다. 그렇다면 문자열 끝에 널문자가 없으면 어떻게 문자열의 길이를 알까? 다른 변수 하나를 제공해서 문자열의 길이를 저장한다. lps에 대응하는 다른 변수가 있는지 확인하여 사용한다.

 

windows 운영체제, window는 창을 의미한다. 약어로 Win 운영체제를 의미하고, Wnd 창, 어플리케이션을 의미한다.

 

DefWindowProc();      //  기본적으로 처리해야하는 메세지(약 30가지)를 이 함수에 넣어놔서 특별히 처리하고 싶지 않을때 이 함수를

                                  //  호출하면 된다.  리하고 싶을 메세지는 if문을 사용해서 프로그래머의 마음대로 처리할 수 있다.

 

A가 B에 가려졌다는것은 운영체제만 알고 있다. 프로그램은 정작 가려진지 모른다. B를 빼서 겹쳐지는 부분이 사라지만 valid라고 하고 운영체제만 알고 있다. 운영체제가 A프로그램에 겹쳐졌다 메세지를 보내준다.. 이 메세지가 WM_PAINT라는 메세지이다. 이 메세지의 뜻은 내 윈도우의 특정 부분이 다시 그려야할 필요가 있다는 뜻이다. 지금은 윈도우즈가 WM_PAINT를 발생시키지 않는다. 성능이 좋아짐에 따라 기본적으로 모든 윈도우의 이미지를 백업하기 때문이다. 윈도우를 영역 밖으로 나갔다가 다시 가지고 와도 WM_PAINT가 발생한다.

 

윈도우를 가장 힘들게 하는 것

 

윈도우가 움직이면서 발생하는 WM_PAINT 메세지이다. 마우스를 빠르게 움직이는것 또한 윈도우를 힘들게 한다.

무엇인가를 화면으로 보여주는것은 우선순위가 제일 느리다. 화면에 무엇인가 보여주는것보다 처리하는것이 더 중요하다.

real time system은 자기가 정해 놓은 유효시간 내에 반응하는 시스템이다. WM_PAINT의 우선순위가 제일 낮다. WM_PAINT는 메세지 플레그라고 해서 따로 플레그가 있다. 1, 0으로 세팅된다. WM_PAINT가 발생하면 발생했다고 1로 적는다. 자기가 급한일을 다 하고 시간이 남으면 메세지 플래그를 보고 화면에 그려준 후 1을 0으로 바꾼다.

 

updatewindow 함수

 

강조로 무조건 그리는 함수이다. cpu를 90프로 이상 사용할 때 프로그램을 키면 보이지 않는다.

updatewindow 함수는 즉시 갱신해서 무조건 그리는 함수이다. windows를 실행할 때 updatewindow 함수를 반드시 사용한다.

 

 

 

 

 

실습을 해보자. visual studio 2017을 사용하겠다. 아래와 같이 따라 해보자

 

 

 

 

 

 

 

 

dll은 따로 라이브러리를 만들어 프로그램이 실행될 때 이 라이브러리를 교체할 수 있도록 할 수 있는 기술이다.

일반 라이브러리를 만들때는 동적 라이브러리 이다.

 

헤더 파일이 두 개 선언되있는것을 볼 수 있다. "stdafx.h", "stdafx.cpp"이 선언되어있다. std는 standard의 줄임말이다. afx는 MS사에서 afx는 헤더파일을 만드는 팀 이름이다.

 

windows 헤더 파일은 라인수가 기본적으로 많아서 컴파일할 때마다 시간이 오래 걸린다. 한 번 읽어들여서 메모리 구조를 만든 후 메모리 구조를 저장한다. 이 기술을 프리 컴파일 헤더라고 한다.

 

위와같이 프로젝트를 생성하면 소스코드에 코드가 작성되어 있다. 하지만 win32를 동작시키기 위한 최소 코드를 가지고 실습할것이다.

 

 

소스코드를 위와 같이 작성하고 디버깅 해보면 아래와 같이 실행된다.

 

 

 

 

 

 

소스코드를 분석하고 변경해보자.

 

 

 

위와 같이 작성하고 디버깅을 한 후 종료를 위해 X버튼을 누르면 아래와 같이 실행되는 것을 알 수 있다.

 

 

 

 

 

 

 

 

 

 

 

'Programming > TIPS 17기' 카테고리의 다른 글

Day-16 MFC이야기와 실습1  (0) 2017.08.20
Day-15 Win32 API 이야기와 실습2  (0) 2017.08.17
Day-13 C++ 이야기3  (0) 2017.08.14
Day-12 C++ 이야기2  (0) 2017.08.11
Day-11 함수 포인터, C++의 철학과 C++ 이야기1  (1) 2017.08.01