개발자 '쑥말고인절미'
[MFC] Win32 공부중 본문
MFC : 독자적인 개발 솔루션이 아니라 Win32 API를 C++문법을 사용해서 클래스화 해놓은 것
응용프로그램(Application Program) : OS에서 제공하는 프로그램과는 별도로 개발자들이 해당 OS에서 동작하는 프로그램을 개발한 것
Windows 데스크톱 응용 프로그램 : Windows OS에서 항상 볼 수 있는 그림판, 메모장, 탐색기 같은 프로그램들이다. 오랜기간동안 이런 프로그램들이 Win32 계열의 API 함수를 사용했기 때문에 Win32 API 응용 프로그램 또는 Win32 프로그램이라고 불렸는데 지금은 Windows 데스크톱 응용 프로그램이라고 명칭이 변경됐다.
Win32 응용 프로그래밍 = Win32 프로그래밍 = Windows 데스크톱 응용 프로그래밍
Windows 프로그래밍을 배운다 = Win32 API를 사용한 응용 프로그램을 만드는 방법에 대해 배운다.
API(Application Programming Interface) : 응용 프로그램 개발자들이 해당 OS에서 동작하는 프로그램을 쉽게 만들 수 있도록 OS가 제공하는 함수의 집합체
SDK(Software Development Kit) : 응용 프로그램을 개발하기 위해서 필요한 프로그램 집합체(개발환경)인 소프트웨어 개발 키트이다. Visual Studio도 SDK라고 부를 수 있지만 개발만 하는 프로그램이 아니라 개발 계획도 세우고 유지보수, 배포까지 가능하기 때문에 SDK보다 좀 더 넓은 의미의 통합 개발 환경을 제공한다.
리소스(Resource) : OS에 의해서 관리되는 장치나 해당 장치를 사용하기 위해 필요한 정보들. 리소스는 장치 그 자체를 의미하는 경우도 있지만 보통의 경우 장치를 사용할 수 있게 도와주는 정보 또는 해당 장치에 설정되어 있는 상태 값을 의미하는 경우가 많다. 그래서 응용 프로그램이 컴퓨터의 어떤 장치를 사용하고 싶다면 해당 장치와 관련된 OS 리소스에 접근하여 원하는 작업을 수행하게 된다. OS의 리소스는 대부분 메모리로 구성되어 있기 때문에 주소를 가지고 있어 응용 프로그램에서 포인터를 사용하여 이 주소에 접근하면 원하는 작업을 손쉽게 완료할 수 있다. 하지만 리소스의 주소를 응용 프로그램이 직접 사용하게 되면 OS가 자신의 내부에 있는 정보를 안전하게 관리할 수 없기 때문에 사용자 공간에서 실행되는 응용 프로그램은 포인터를 사용하여 OS의 수행부에 접근할 수 없다.
핸들(Handle) : OS가 자신의 리소스를 안전하게 관리하기 위해 주소를 사용하는 포인터 대신 사용하는 것이다. 핸들은 운영체제 내부에 있는 어떤 리소스의 주소를 정수로 치환한 값이다. 리소스의 주소와 이 핸들 값을 한 쌍으로 묶어서 관리하는데 이것을 '핸들 테이블'이라고 한다. 예들 들어 리소스 주소가 0X0E671027이라면 응용 프로그램에서는 이 주소를 알려주지 않고 이 주소와 연결된 핸들 값 100을 전달하여 OS가 자신의 내부에 있는 리소스를 안전하게 관리할 수 있게 만든다. Win32 프로그램에서 핸들 값을 저장할 때는 HANDLE이라는 자료형을 기본적으로 사용하며 이 값은 32비트 SO의 경우 unsigned int 자료형과 동일하다. API 함수를 사용할 때 자신이 가지고 있는 핸들 값을 다른 리소스에 사용하는 실수를 막기 위하여 리소스 별로 다른 자료형을 사용한다. 예를 들어 핸들을 의미하는 자료형은 모두 앞에 H로 시작하고 해당 자원을 의미하는 영문 명칭을 사용하기 때문에 마우스 커서의 핸들값은 HCURSOR라는 자료형을 사용하며 아이콘의 핸들값은 HICON을 사용한다.
HCURSOR h_my_cursor = LoadCursor(NULL, IDC_ARROW); //커서의 핸들 값 저장
HICON h_my_icon = LoadIcon(NULL, IDI_APPLICATION); //아이콘의 핸들 값 저장
HINSTANCE 자료형 : Instance Handle을 저장할 때 사용한다. Instance Handle은 윈도우즈 OS에서 실행되는 프로그램들을 구별하기 위한 ID값이다. 이 값은 정수이고 프로그램을 구별하기 위한 값이기 때문에 동일한 프로그램을 여러 개 실행한 경우에는 같은 값을 가지게 된다.
Process ID : 메모리에 실행 가능한 상태로 재배열된 코드를 프로세스라고 하는데 이 프로세스를 구별하기 위한 값을 의미한다.
Instance Handle / Process ID : 두 개념 모두 윈도우즈에서 실행되는 프로그램을 구별하기 위한 용도이지만 Instance Handle의 경우 동일한 프로그램이 여러 번 실행된 경우, 해당 프로그램들이 중복해서 가지게 되는 실행 명령 코드나 리소스(아이콘, 커서, 비트맵)를 공유하기 위해서 사용되는 개념이고, Process ID는 동일한 프로그램이든 아니든 모든 프로세스들을 개별적으로 구별하기 위한 값이다. 그래서 실제로 프로그램이 실행되었을 때 Instance Handle과 Process ID는 서로 다른 정수 값을 가진다.
동기화(Synchronization) : 두 개(A, B)의 값을 구해서 두 값을 더하는 작업을 해야 한다고 가정했을 때, 두 사람이 나눠서 진행하면 더 빨리 할 수 있기 때문에 두 명의 사람이 나눠서 계산하기로 했다. 근데 두 사람의 작업시간에 차이가 있을 때 A 값을 구한 사람이 다음 작업을 위해 B 값을 구하는 작업이 완료되기를 기다려주는 행위가 동기화이다. 즉, 두 사람이 다음 단계의 작업을 같이 하기 위해서 먼저 작업이 완료된 사람이 다른 사람의 작업이 완료되기를 기다리는 행위이다.
스레드(Thread) : 동기화 설명에서 말한 예제에서 작업을 하는 주체가 사람이었지만 프로그램에서는 실행의 주체가 스레드이기 때문에 스레드가 살마에 해당한다. 하나의 스레드를 작업하는 경우에는 동기화라는 개념이 필요가 없지만 두 개 이상의 스레드가 협력해서 작업을 하는 경우에는 상황에 따라 동기화가 필요하기 때문에 동기화를 처리하는 기술을 사용해야 한다.
Event 객체 : Windows OS에서 동기화 작업을 가장 간단하게 사용할 수 있는 기술이 Event 객체이다. 이벤트 객체는 같은 프로세스 내에 있는 스레드 간에도 사용이 가능하지만, 서로 다른 프로세스에 있는 스레드 간에서도 사용할 수 있기 때문에 매우 유용하다. 즉, 서로 다른 두 프로그램이 동기화 개념을 사용하기 위해 이벤트 객체를 사용할 수 있다. 이벤트 객체는 0(FALSE) 또는 1(TRUE)의 상태를 기억할 수 있는 커널 객체(Kernel Object, OS가 관리하는 객체)이며 아래와 같이 CreateEvent 함수를 호출하여 만들 수 있다.
HANDLE h_event_object = CreateEvent(NULL, TRUE, FALSE, NULL);
//CreateEvent함수의 원형
HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes,
BOOL bManualReset,
BOOL bInitialState,
LPCTSTR lpName);
- lpEventAttributes : SECURITY_ATTRIBUTES 구초제로 선언된 변수의 주소를 명시한다. 이 매개변수에 NULL을 명시하면 생성된 이벤트 객체의 핸들은 자식 프로세스에 상속되지 않는다. 이 매개변수에 NULL을 사용하여 보안 기술자에 대한 명시를 하지 않은 경우에는 새로 생성될 이벤트 객체에 일반적인 보안 기술자가 명시된 걸로 간주한다. 따라서 현재 로그인되어 있는 사용자의 로그인 정보에 있는 ACL(Access Control List)을 이용하거나 이벤트 객체를 생성한 프로세스의 ACL을 가져와 사용하게 된다.
- bManualReset : 이 매개변수에 TRUE를 적으면 발생된 이벤트 정보가 계속 유지되는 이벤트 객체를 생성한다. 즉, 이벤트 객체에 TRUE가 설정되면 다시 FALSE 값을 설정하기 전까지는 계속 TRUE 값으로 유지되는 이벤트 객체가 만들어진다는 뜻이다. 반대로 FALSE를 적으면 일정 조건이 만족되었을 때 자동으로 객체에 설정된 상태 값이 TRUE에서 FALSE로 변경되는 이벤트 객체를 생성한다.
- bInitialState : 이 매개변수에는 생성될 이벤트 객체의 초기값을 설정한다. TRUE 또는 FALSE 값을 설정할 수 있으며 이벤트 객체는 생성과 함께 이 상태 값을 가지게 된다.
- lpName : 이 매개변수에는 이벤트 객체에서 사용할 이름을 명시한다. 같은 프로세스 내에 있는 스레드 간에는 변수(메모리)를 같이 사용할 수 있기 때문에 이 매개변수에 특별한 이름을 적지 않고 NULL을 적어서 이름 없는 이벤트 객체를 생성해서 사용해도 된다. 하지만 서로 다른 프로세스에 있는 스레드 간에 이벤트 객체를 공유하고 싶다면 이 매개변수에 동일한 이름을 적어서 사용하면 된다. 즉, 아래와 같이 Tipsware라고 이름을 사용했는데 이미 다른 스레드에서 Tipsware라는 이름으로 이벤트 객체를 만든 상태라면 이 두 이벤트 객체는 공유된다는 뜻이다.
HANDLE h_event_object = CreateEvent(NULL, TRUE, FALSE, L("Tipsware");
그리고 이벤트 객체를 생성할 때 이름을 동일하게 사용해서 이벤트 객체가 공유된다면 먼저 만들어진 이벤트 객체의 정보가 우선시 되기 때문에 나중에 사용한 CreateEvent 함수의 bManualReset, bInitialState에 적은 매개변수 값은 무시된다. 그리고 이 매개변수에 명시한 이름이 이벤트 객체가 아닌 다른 커널 객체(Semaphore, Mutex, Waitable timer, Job 또는 File-mapping 객체)에 사용되고 있다면 객체의 공유가 불가능하기 때문에 이 함수는 실패하고 NULL 값이 반환될 것이다. 따라서 NULL 값이 반환되어 정확한 오류 체크를 위해 GetLastError 함수를 호출하면 이 상황에서는 ERROR_INVALID_HANDLE 값이 반환될 것이다. 결론적으로 커널 객체는 다른 형식의 커널 객체와 이름을 공유할 수 없기 때문에 동일한 형식의 커널 객체 간에서만 공유 여부가 결정된다.
- 함수의 반환값 : 생성된 이벤트 객체의 핸들이 반환된다. 만약, lpName에 사용한 동일한 이름으로 생성된 이벤트 객체가 이미 생성되어 있다면 해당 객체의 핸들이 반환될 것이다. 이때, GetLastError 함수를 호출해보면 ERROR_ALREADY_EXISTS가 반환될 것이다. 따라서 실수로 다른 프로세스와 이벤트 객체가 공유되는 것을 원치 않는다면 CreateEvent 함수가 성공하여 정상적인 이벤트 핸들 값을 반환하더라도 GetLastError 함수를 호출하여 ERROR_ALREADY_EXISTS값이 반환되는지를 체크하는 것도 좋은 방법이다. 그리고 이 함수가 실패한다면 NULL값이 반환된다. 만약, 이벤트 객체의 생성이 왜 실패했는지 알고싶다면 GetLastError 함수를 호출하여 좀 더 자세한 오류 상황을 확인할 수 있다. 그리고 이벤트 객체는 커널 객체이기 때문에 사용이 끝났다면 아래와 같이 객체의 핸들 값을 사용하여 CloseHandle 함수를 호출하여 반드시 해제해야 한다. 그리고 이벤트 객체가 다른 프로세스의 스레드와 공유되는 상황이라면 한 쪽에서 CloseHandle 함수를 호출한다고 해서 이벤트 객체가 제거되는 것이 아니고 둘 다 CloseHandle을 사용해야지만 이벤트 객체가 제거된다.
CloseHandle(h_event_object);
- 이벤트 객체에 상태 설정하기 : 이벤트 객체의 상태 값을 TRUE로 만들고 싶다면 SetEvent 함수를 호출, FALSE로 만들고 싶다면 ResetEvent 함수를 호출하면 된다.
//이름 없는 이벤트 객체를 생성한다. (수동해제, 초기값을 FALSE로 설정)
HANDLE h_event_object = CreateEvent(NULL, TRUE, FALSE, NULL);
//이 이벤트 객체의 상태값을 TRUE로 변경한다.
SetEvent(h_event_object);
//이벤트 객체의 사용을 중단한다
CloseHandle(h_event_object);
- 이벤트 객체의 상태 체크하기 : 이벤트 객체의 사용 목적이 두 개 이상의 스레드 간에 작업 시점을 동기화하기 위한 것이다. 따라서 한 쪽에서 SetEvent 또는 ResetEvent 함수를 사용하여 이벤트 객체의 상태 값을 변경하면 다른 쪽에서는 이벤트 객체의 상태 값이 변경되는지 체크하는 작업을 해야한다. 이 때 이벤트 객체의 상태 값이 변경되는지 체크하려면 WaitForSingleObject라는 함수를 사용하면 된다. 이 함수는 이벤트 객체의 현재 상태 값을 얻는 함수가 아니라 이벤트 객체의 상태 값이 변하기를 기다리는 함수이다. 즉, 아래와 같이 코드를 구성하면 5초간 h_event_object의 값이 변경되었는지를 체크하게 된다.
DWORD state = WaitForSingleObject(h_event_object, 5000); //5000 = 5초
위와 같이 코드를 사용했는데 이벤트 객체의 상태가 변경되면 5초까지 기다리지 않는다. 예를 들어, 함수가 호출된 지 1초 만에 이벤트 객체의 상태 값이 FALSE에서 TRUE로 변경되거나 TRUE에서 FALSE 값으로 변경되면 WaitForSingleObject 함수를 빠져나오고 WAIT_OBJECT_0 값이 state 변수에 저장된다. 반면에 지정된 시간까지 대기했는데도 이벤트 객체의 상태가 변경되지 않으면 5초가 되는 시점에 WaitForSingleObject 함수가 종료되며 WAIT_TIMEOUT 값이 state 변수에 저장된다. 따라서 아래와 같이 코드를 구성하여 작업을 많이 한다.
//이름 없는 이벤트 객체를 생성한다. ( 수동해제, 초기값을 FALSE로 설정)
HANDLE h_event_object = CreateEvent(NULL, TRUE, FALSE, L"Tipsware");
DWORD state = WaitForSingleObject(h_event_object, 5000); // 5000 = 5초
if(WAIT_OBJECT_0 == state){
//5초 이내에 상태가 변경된 경우
}else if(WAIT_TIMEOUT == state){
//5초 동안 상태가 변경되지 않아서 타임 아웃이 발생함
}
CloseHandle(h_event_object); //이벤트 객체의 사용을 중단한다.
만약 이벤트 객체의 상태가 변경될 때까지 시간제한 없이 기다리고 싶다면 아래와 같이 제한 시간을 적는 곳에 INFINITE 값을 사용하면 된다.
DWORD state = WaitForSingleObject(h_event_object, INFINITE);
이렇게 시간제한을 설정하거나 무제한으로 WaitForSingleObject 함수를 사용하면 다른 스레드가 SetEvent 또는 ResetEvent를 사용해서 이벤트 객체의 상태를 변경하는 것을 기다리게 하여 두 스레드의 작업 시점을 동기화할 수 있다.
Window Procedure : Windows OS에서 실행되는 프로그램은 Window 단위로 작업을 처리하는데 Window는 메시지를 주고받는 형태로 작업을 진행하기 때문에 Window Procedure를 정의해서 작업하게 된다. Window Procedure는 어떤 메시지가 Window에 발생한 경우 그 메시지를 어떻게 처리할 것인지를 정의한 함수라고 생각하면 된다.
Window Class : 서로 다른 프로그램에서 동일한 작업을 하는 Window가 있다면 그 Window를 위한 Window Procedure는 중복될 수 있고, 이 중복을 줄여야지 정말 많은 중복을 해결할 수 있다. 그래서 Window OS는 프로그램 단위가 아닌 Window 단위로 실행명령어 중복을 줄일 수 있는 방법(Window Procedure를 공유하는 개념)을 만들었는데 이것이 Window Class이다. Window Class는 WNDCLASS 구조체로 관리되며 이 구조체는 WinUser.h 헤더 파일에 아래와 같이 정의되어 있다.
typeof struct tagWNDCLASS {
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCWSTR lpszMenuName;
LPCWSTR lpszClassName;
} WNDCLASS;
'STUDY > C++ & MFC' 카테고리의 다른 글
| [MFC] Format()와 WriteString() (0) | 2022.04.13 |
|---|---|
| [MFC] CWinApp 클래스와 CWnd 클래스 (0) | 2022.04.13 |
| [22.04.11] noexcp과 Doc,View,MainFrm과 UpdateData() (0) | 2022.04.11 |
| [MFC] UpdateData() 함수란? (0) | 2022.04.11 |
| [에러] 비정적 멤버 참조는 특정 개체에 상대적이어야 합니다 (0) | 2022.04.11 |