< DC를 얻는 방법 >

 

화면으로 출력을 하기 위해서는 반드시 DC가 있어야 하며 DC를 얻는 방법에는 두 가지가 있다.

 

방법 1.

 

GetDC 함수를 사용하여 DC를 얻고 사용 후 ReleaseDC로 해제하는 방법.

 

1
2
HDC GetDC(HWND hWnd);
int ReleaseDC(HWND hWnd, HDC hDC);
cs

 

DC는 주로 하나의 윈도우와 연관되는 출력 정보를 가진다.

GetDC는 hWnd가 가리키는 윈도우에 적당한 DC를 만들어 그 핸들을 리턴한다.

GetDC에 의해 얻어진 핸들은 사용 후에 반드시 ReleaseDC 함수로 해제해야 한다.

 

방법 2.

 

우선 해당 방법은 WM_PAINT 메시지 루틴에서만 사용할 수 있다.

WM_PAINT 메시지 처리 루틴에서는 DC 핸들을 GetDC로 얻지 않고 BeginPaint 함수로 얻으며 핸들을 해제할 때는 EndPaint 함수를

사용한다. GetDC는 DC 핸들을 얻는 일반적인 방법이며 BeginePaint는 WM_PAINT 메시지 내에서 그림 그리기 준비를 하는 좀 더

전문적인 함수이지만 그 외의 메시지에서는 절대로 사용할 수 없다.

 

1
2
HDC BeginPaint(HWND hWnd, LPPAINTSTRUCT lpPaint);
BOOL EndPaint(HWND hWnd, CONST PAINTSTRUCT* lpPaint);
cs

 

BeginPaint 함수는 윈도우 핸들 외에도 페인트 정보 구조체를 인수로 요구하며 이 구조체에 그림 그리기에 필요한 여러 가지 정보를

리턴한다. (페인트 정보 구조체는 주소값을 넘겨야 한다.)

 

1
2
3
4
5
6
7
8
typedef struct tagPAINTSTRUCT {
    HDC         hdc;
    BOOL        fErase;
    RECT        rcPaint;
    BOOL        fRestore;
    BOOL        fIncUpdate;
    BYTE        rgbReserved[32];
} PAINTSTRUCT;
cs

 

위의 3개 멤버(hdc, fErase, rcPaint)는 사용자가 사용하는 멤버이며 아래의 3개 멤버(fRestore, fIncUpdate, rgbReserved)는 윈도우즈가

내부적으로 사용하는 멤버이므로 사용자가 건드려서는 안 된다.

또한, PAINTSTRUCT 구조체에는 그리기 속도를 비약적으로 향상시킬 수 있는 정보들이 들어 있는데 이 정보를 활용하는 방법에 대해서는

다음에 설명한다.

김상형 저자님께서 작성하신 윈도우즈 API 정복 3장 출력에 대한 내용입니다.

 

< DC (Device Context) >

 

윈도우즈는 세 가지 동적 연결 라이브러리(DLL)로 구성되어 있다.

 - KERNEL : 메모리를 관리하고 프로그램을 실행시키는 DLL.

 - USER : 유저 인터페이스와 윈도우를 관리하는 DLL.

 - GDI : 화면 처리와 그래픽을 담당하는 DLL.

 

이 세 개의 DLL 중 화면에 원하는 정보를 출력하려면 GDI(Graphic Device Interface) 모듈을 통해야 하며, GDI 모듈은 출력에

필요한 모든 정보를 가지는 데이터 구조체인 DC(Device Context)를 관리한다.

 

DC의 필요성은 다음과 같다.

 - 화면에 그릴 정보를 함수 인자로 넘길 때 DC의 핸들만 넘겨주면 간편하게 사용할 수 있게 된다.

 - DC에는 생성된 윈도우를 기준으로 하는 원점 좌표를 가지고 있기 때문에 윈도우의 위치에 신경쓰지 않아도 된다.

 - DC는 출력이 가능한 영역에만 출력을 내 보내기 때문에 윈도우가 겹치는 문제에 대해 신경쓰지 않아도 된다.

 

이와 같이 DC가 그리기에 필요한 여러 가지 정보와 안전 장치 역할을 하기 때문에 모든 그리기 함수는 DC의 핸들을

첫 번째 인수로 전달받는다.

 

< WM_PAINT 메시지 >

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <Windows.h>
 
LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);
DWORD WINAPI ThreadFunc(LPVOID);
HINSTANCE g_hInst;
HWND hWndMain;
LPSTR lpszClass="TextOut";
 
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam,int nCmdShow)
{
    HWND hWnd;
    MSG Message;
    WNDCLASS WndClass;
    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=lpszClass;
    WndClass.lpszMenuName=NULL;
    WndClass.style=CS_HREDRAW | CS_VREDRAW;
    RegisterClass(&WndClass);
 
    hWnd=CreateWindow(lpszClass,lpszClass,WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,CW_USEDEFAULT,
        CW_USEDEFAULT,CW_USEDEFAULT,NULL,(HMENU)NULL,hInstance,NULL);
    ShowWindow(hWnd,nCmdShow);
 
    while(GetMessage(&Message,0,0,0)) {
        TranslateMessage(&Message);
        DispatchMessage(&Message);
    }
    return Message.wParam;
}
 
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
    HDC hdc;
 
    switch(iMessage) {
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    case WM_RBUTTONDOWN:
        hdc = GetDC(hWnd);
        TextOut(hdc, 100100, TEXT("Beautiful Korea"), 15);
        ReleaseDC(hWnd, hdc);
        return 0;
    }
    return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}
cs

 

오른쪽 버튼 클릭시 화면에 문자열을 출력하는 프로그램이다.

그러나 이 프로그램은 문자열을 다른 윈도우로 가리거나 문자열이 모니터 화면 밖으로 나가도록 윈도우를 움직이면, 가려진 문자열

부분은 화면상에서 완전히 지워지고 다시 그려지지 않는 문제점을 가지고 있다.

이유는 운영체제가 개별 윈도우의 화면을 보관 및 복구해 주지 않기 때문이며, 지워진 화면을 복구하는 것은 프로그램 자신이 직접

해야한다.

 

그래서 위와 같은 문제를 해결하기 위해서는 마우스 오른쪽 버튼이 클릭 될 때 출력하는 것이 아니라 화면이 지워질 때마다 문자열을

출력해야 한다. 그러므로 화면이 지워질 때 호출되는 WM_PAINT 메시지를 사용하면 된다.

운영체제는 개별 윈도우의 화면을 보관해 주지는 않지만 윈도우의 일부가 지워졌다는 정보는 개별 윈도우에게 알려주며 그 방법은

WM_PAINT 메시지를 보내주는 것이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
 
    switch(iMessage) {
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        TextOut(hdc, 100100, TEXT("Beautiful Korea"), 15);
        EndPaint(hWnd, &ps);
        return 0;
    }
    return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}
cs

 

위와 같이 코드를 수정하면 출력한 문자열은 지워져도 항상 다시 복구된다.

 

 

 

수동 리셋 이벤트

 

앞서 살펴봤던 자동 리셋 이벤트는 대기 상태를 풀 때 자동으로 대기함수에서 이벤트를 비신호 상태로 만들어주었다.

하나의 스레드만 사용할 경우에는 자동 리셋 이벤트를 사용하는 것이 훨씬 편리하며 논리적으로도 문제가 없다.

 

그러나 하나의 이벤트를 여러 개의 스레드가 기다리고 있는 경우에는 이야기가 달라진다.
예를 들어 계산 스레드가 계산을 완료하고 나서 진행될 작업이 3개가 있고 이 3개 스레드의 대기함수는 계산이 끝나길 기다리고 있다.

그리고 계산이 끝나게 되면 3개 스레드는 일제히 작업을 해야 하는데 자동 리셋 이벤트는 이것이 불가능하다.


이유는 계산 스레드에서 계산이 끝나고 SetEvent를 통해 Event를 대기 상태로 만들고 나면 제일 먼저 스위칭되는 스레드의 대기 함수가

동작하고 이 대기 함수는 Event를 다시 비신호 상태로 만들기 때문에 나머지 2개 스레드는 계산 스레드에서 계산이 끝났는지에 대한 여부를

알 수 없게 된다.


즉, 자동 리셋 이벤트는 단 하나의 스레드만 신호를 받아 처리할 수 있는 구조이다.

 

이에 반해 수동 리셋 이벤트는 대기 함수가 리턴될 때 신호상태를 그대로 유지하며 ResetEvent 함수를 통해 비신호 상태로 변경시킬 수 있다.

 

하기 예제 코드를 참고해보자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
HANDLE hEvent;
 
DWORD WINAPI ThreadSend(LPVOID prc)
{
    WaitForSingleObject(hEvent, INFINITE);
    HDC hdc = GetDC(hWndMain);
    TextOut(hdc, 210100"전송완료"8);
    ReleaseDC(hWndMain, hdc);
 
    return 0;
}
 
DWORD WINAPI ThreadSave(LPVOID prc)
{
    WaitForSingleObject(hEvent, INFINITE);
    HDC hdc = GetDC(hWndMain);
    TextOut(hdc, 110100"저장완료"8);
    ReleaseDC(hWndMain, hdc);
 
    return 0;
}
 
DWORD WINAPI ThreadPrint(LPVOID prc)
{
    WaitForSingleObject(hEvent, INFINITE);
    HDC hdc = GetDC(hWndMain);
    TextOut(hdc, 10100"인쇄완료"8);
    ReleaseDC(hWndMain, hdc);
 
    return 0;
}
 
DWORD WINAPI ThreadCalc(LPVOID prc)
{
    HDC hdc = GetDC(hWndMain);
    for (int i=0; i<10++i)
    {
        TextOut(hdc, 1050"계산중"6);
        GdiFlush();
        Sleep(300);
        TextOut(hdc, 1050"기다려",6);
        GdiFlush();
        Sleep(300);
    }
    TextOut(hdc, 1050"계산완료"8);
    ReleaseDC(hWndMain, hdc);
    SetEvent(hEvent); //이벤트 신호 상태 변경.
 
    return 0;
}
 
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    DWORD ThreadID;
    TCHAR *Mes = "마우스 왼쪽 버튼을 누르면 계산을 시작합니다.";
 
    switch(iMessage) 
    {
    case WM_CREATE:
        hWndMain = hWnd;
        hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); //비신호상태 수동 리셋 이벤트 생성.
        return TRUE;
    case WM_LBUTTONDOWN:
        InvalidateRect(hWnd, NULL, TRUE);
        ResetEvent(hEvent); //이벤트 비신호 상태 변경.
        CloseHandle(CreateThread(NULL,0,ThreadCalc,NULL,0,&ThreadID));
        CloseHandle(CreateThread(NULL,0,ThreadPrint,NULL,0,&ThreadID));
        CloseHandle(CreateThread(NULL,0,ThreadSave,NULL,0,&ThreadID));
        CloseHandle(CreateThread(NULL,0,ThreadSend,NULL,0,&ThreadID));
        return 0;
    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        TextOut(hdc, 1010, Mes, lstrlen(Mes));
        EndPaint(hWnd, &ps);
        return 0;
    case WM_DESTROY:
        CloseHandle(hEvent);
        PostQuitMessage(0);
        return 0;
    }
    return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}
cs

 

하나의 계산 스레드가 동작이 완료되면 인쇄, 저장, 전송 세 개의 작업 스레드가 실행되는 구조이다.
이벤트 객체를 생성할 때 두 번째 인수를 TRUE로 설정하여 수동 리셋 이벤트가 되도록 한다.

계산 스레드가 동작이 완료될 때까지는 Event 객체가 비신호 상태이므로 인쇄, 저장, 전송 스레드의 대기 함수는 대기 상태로 있고

계산 스레드에서 SetEvent를 거치는 순간부터 일제히 인쇄, 저장, 전송 기능이 실행되게 된다.

 

 

 

Event

 

이벤트란 어떤 사건이 일어났음을 알리는 동기화 객체이다.
크리티컬 섹션, 뮤텍스, 세마포어는 주로 공유 자원을 보호하기 위해 사용되고 이벤트는 스레드 간의 작업 순서나 시기를 조정하고

신호를 보내기 위해 사용된다.

특정한 조건이 만족될 때까지 대기해야 하는 스레드가 있을 경우 이 스레드의 실행을 이벤트로 제어할 수 있다.

 

이벤트를 기다리는 스레드는 이벤트가 신호상태가 될 때까지 대기하며 신호상태가 되면 대기를 풀고 작업을 시작한다.

이때 대기 함수가 대기를 풀면서 이벤트 객체를 어떻게 처리하는가에 따라 두 가지 종류로 구분된다.


 - 자동 리셋 이벤트 : 대기 상태가 종료되면 자동으로 비신호상태가 된다.
 - 수동 리셋 이벤트 : 스레드가 비신호상태로 만들 때까지 신호를 유지한다.

 

하기 두 함수는 이벤트를 만들거나 오픈하는 함수이다.

 

<CreateEvent>

1
2
3
4
5
6
7
8
HANDLE
WINAPI
CreateEvent(
    __in_opt LPSECURITY_ATTRIBUTES lpEventAttributes,
    __in     BOOL bManualReset,
    __in     BOOL bInitialState,
    __in_opt LPCTSTR lpName
    );
cs

 

<OpenEvent>

1
2
3
4
5
6
7
HANDLE
WINAPI
OpenEvent(
    __in DWORD dwDesiredAccess,
    __in BOOL bInheritHandle,
    __in LPCTSTR lpName
    );
cs

 

bManualReset 설정 값이 TRUE이면 수동 리셋 이벤트이고 FALSE이면 자동 리셋 이벤트가 된다.
bInitialState 설정 값이 TRUE이면 이벤트를 생성함과 동시에 신호상태로 만들어 이벤트를 기다리는 스레드가 곧바로 실행된다.
이벤트도 이름을 가지므로 프로세스간의 동기화에 사용될 수 있다.
이벤트가 동기화 객체와 또 다른 점은 대기 함수를 사용하지 않고도 임의적으로 신호상태와 비신호상태를 설정할 수 있다는 점이다.

이때는 다음 함수를 사용한다.

 

1
2
3
BOOL WINAPI SetEvent(__in HANDLE hEvent);
 
BOOL WINAPI ResetEvent(__in HANDLE hEvent);
cs

 

SetEvent는 이벤트를 신호상태로 만들고 ResetEvent는 이벤트를 비신호상태로 만든다.

 

 

Auto Reset Event (자동 리셋 이벤트)

 

멀티 스레드가 진가를 발휘할 때는 백그라운드 작업을 할 때이다.

예를 들어 백그라운드에서는 시간이 걸리는 계산, 인쇄, 다운로드, 자료 정렬 등을 할 때 스레드에게 분담할 수 있다.

 

우선 수만건의 DB 내용을 읽는 과정을 메인 스레드에서 진행하면 어떻게 되는지 확인해보자.

하기 예는 메인 스레드에서 DB 내용을 읽는 과정을 처리하는 코드이다.

 

1
2
3
4
5
6
7
8
9
void DBRead(void)
{
    int i;
    for (i=0; i<1000000++i)
    {
        Sleep(30); //시간이 오래 걸리는 작업임을 나타내기 위한 Sleep.
        buf[i] = i;
    }
}
cs

 

Sleep 함수가 시간이 오래 걸리는 과정이라 생각하고 보면, 메인 스레드는 DBRead 함수에서 DB 내용을 모두 읽을 때까지

기다려야만 한다. 즉, DB Read 과정 동안 메인 스레드는 아무 처리를 하지 못하게 되므로 이런 함수는 하기와 같이 별도의

스레드로 분리해야 한다.

 

1
2
3
4
5
6
7
8
9
DWORD WINAPI ThreadFunc(LPVOID prc)
{
    int i;
    for (i=0; i<1000000++i)
    {
        Sleep(30); //시간이 오래 걸리는 작업임을 나타내기 위한 Sleep.
        buf[i] = i;
    }
}
cs

 

이제 이 스레드는 새로 스택을 생성하고 자신만의 실행 흐름을 가지며 메인 스레드와는 따로 스케줄링된다.

만약 이 계산 작업이 메인 스레드와는 아무런 상관이 없는 단순 백그라운드 작업이라면 아무 문제가 없지만 메인 스레드에

작업 결과를 전달해주어야 한다면 이는 또 다른 문제의 발생 원인이다. 그렇게 되면 결국 메인 스레드는 Read 동작이 완료될

때까지 기다린 후 DB 내용을 화면에 표시해주어야 한다. 결국 메인 스레드에서 함수를 호출한 것과 다를게 하나도 없게 되버린다.


그러나 생각을 바꾸면 모든 데이터를 읽을 때까지 주 스레드가 기다릴 필요없이 당장 출력할 정도만 되면 일단 화면에 표시하고

남은 작업은 백그라운드 스레드가 하도록 맡겨 놓으면 이런 문제를 해결할 수 있다.

 

이런 처리를 하려면 작업 스레드가 어디까지 계산을 했는지 주 스레드에게 알릴 필요가 있으며 이런 통신에 이벤트가 사용된다.

다음 예제는 30개의 DB 내용을 Read한 후에 화면에 출력을 하는 코드이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
HANDLE hEvent;
int buf[100];
 
DWORD WINAPI ThreadFunc(LPVOID prc)
{
    int i;
    for (i=0; i<1000000++i)
    {
        Sleep(30);
        buf[i] = i;
        if (i == 30)
            SetEvent(hEvent); //hEvent를 신호상태로 변경.
    }
 
    return 0;
}
 
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
    int i;
    TCHAR str[128];
    HDC hdc;
    PAINTSTRUCT ps;
    DWORD ThreadID;
    HANDLE hThread;
    TCHAR *Mes = "마우스 왼쪽 버튼을 눌러 주십시오.";
 
    switch(iMessage) 
    {
    case WM_CREATE:
        hWndMain = hWnd;
        hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); //비신호상태 자동 리셋 이벤트 생성.
        return TRUE;
    case WM_LBUTTONDOWN:
        InvalidateRect(hWnd, NULL, TRUE);
        UpdateWindow(hWnd);
        hThread = CreateThread(NULL0, ThreadFunc, NULL0&ThreadID);
        CloseHandle(hThread);
        WaitForSingleObject(hEvent, INFINITE); //hEvent가 신호상태가 될 때까지 대기.
                                               //ThreadFunc에서 hEvent를 신호상태로 변경시키면 하기 코드 실행.
        hdc = GetDC(hWnd);
        for (i=0; i<=30++i)
        {
            wsprintf(str, "%d Line is %d", i, buf[i]);
            TextOut(hdc, 10, i*20, str, lstrlen(str));
        }
        ReleaseDC(hWnd, hdc);
        return 0;
    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        TextOut(hdc, 100100, Mes, strlen(Mes));
        EndPaint(hWnd, &ps);
        return 0;
    case WM_DESTROY:
        CloseHandle(hEvent);
        PostQuitMessage(0);
        return 0;
    }
    return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}
cs

 

WM_CREATE에서 최초 비신호상태인 자동 리셋 이벤트를 만들었고 사용자가 마우스 왼쪽 버튼을 클릭하면 별도의 스레드에서 DB를

읽기 시작한다. 그리고 DB 항목 중 30개를 읽으면 SetEvent를 통해서 hEvent를 신호 상태로 변경하여 메인 스레드에서 DB 내용을

출력하도록 한다.

WaitingForSingleObject는 hEvent가 신호 상태가 될 때까지 대기하다가 신호 상태가 되면 하기 코드를 실행시켜준다.

 

별도의 스레드에서는 SetEvent 후 자신이 맡은 작업을 백그라운드에서 혼자 진행하면 되고 메인 스레드는 자신의 다른 작업을 계속

이어서 하면 된다.

 

그리고 대기 함수인 WaitingForSingleObject 함수는 일단 대기가 종료되면 다음 사용을 위해 hEvent를 다시 비신호 상태로 변경시킨다.

 

 

동기화 객체.

 

동기화 객체란 말 그대로 동기화에 사용되는 객체이다. 프로세스, 스레드처럼 커널 객체이며 프로세스 한정적인 핸들을 가진다.

앞에서 살펴본 크리티컬 섹션은 구조체일 뿐이지 커널 객체는 아니다.

 

동기화 객체는 크리티컬 섹션보다 느리기는 하지만 훨씬 더 복잡한 동기화에 사용될 수 있다.

동기화 객체는 일정 시점에서 다음 두 가지 상태 중 한 상태를 가진다.

 

 - 신호 상태 : 스레드의 실행을 허가하는 상태. 신호상태의 동기화 객체를 가진 스레드는 계속 실행할 수 있다.

                  신호등의 파란불에 비유할 수 있다.

 

 - 비신호 상태 : 스레드의 실행을 허가하지 않는 상태. 신호상태가 될 때까지 스레드는 블록된다.

                     신호등의 빨간불에 비유할 수 있다.

 

동기화 객체는 대기 함수와 함께 사용되는데 대기 함수(Wait Function)는 일정한 조건에 따라 스레드의 실행을 블록하거나

실행을 허가하는 함수이다. 여기서 일정한 조건이란 주로 동기화 객체의 신호 여부가 된다.

 

 <대표적인 대기 함수>

1
DWORD WINAPI WaitForSingleObject(__in HANDLE hHandle, __in DWORD dwMilliseconds);
cs

  

 이 함수는 hHandle이 지정하는 하나의 동기화 객체가 신호상태가 되기를 기다린다.

 dwMilliseconds 인수는 타임 아웃 시간을 1/1000초 단위로 지정하는데 이 시간이 경과하면 설사 동기화 객체가

 비신호상태이더라도 즉시 리턴함으로써 무한 대기를 방지한다. 대신 dwMilliseconds를 INFINITE로 지정하면

 신호상태가 될 때까지 무한정 대기한다.

 

 WaitForSingleObject 함수는 동기화 객체가 신호상태가 되거나 타임 아웃 시간이 경과할 때까지 스레드의 실행을

 블록하는 역할을 한다고 할 수 있다.

 이 함수의 리턴값을 검사해 보면 어떤 이유로 대기상태를 종료했는지를 알 수 있다.

 

 - WAIT_OBJECT_0 : hHandle 객체가 신호상태가 되었다.

 - WAIT_TIMEOUT : 타임 아웃 시간이 경과하였다.

 - WAIT_ABANDONED : 포기된 뮤텍스.

 

 WaitForSingleObject 함수는 리턴하기 전에 hHandle 동기화 객체의 상태를 변경한다. (신호상태 → 비신호상태)

 그래서 일단 한 스레드가 동기화 객체를 소유하면 동기화 객체는 비신호상태가 되므로 다른 스레드가 이 객체를

 중복하여 소유하지 못한다.

 

1) 뮤텍스.

 

뮤텍스는 크리티컬 섹션과 여러 가지 면에서 비슷하며 크리티컬 섹션이 쓰이는 곳에 대신 사용될 수 있다.

프로세스간에도 사용할 수 있다는 장점이 있으나 그만큼 속도는 느리다. 그러나 같은 프로세스내의 스레드끼리만

동기화한다면 속도 차이는 크리티컬 섹션과 거의 없다.

 

Mutex는 Mutual Exclusion의 준말인데 두 스레드가 동시에 소유할 수 없다는 뜻이며 오직 한 스레드에 의해서만

소유될 수 있으며 어떤 스레드에게 소유되면 뮤텍스는 비신호상태가 된다.

반대로 어떠한 스레드에서도 소유하지 않고 있으면 신호상태인 것이다.

 

1
2
HANDLE WINAPI CreateMutex(__in_opt LPSECURITY_ATTRIBUTES lpMutexAttributes, __in BOOL bInitialOwner,
    __in_opt LPCSTR lpName);
cs

 

우선 CreateMutex함수를 통해서 Mutext를 생성 한 후 그 핸들을 리턴받는다.

 

 - 첫 번째 인수는 보안속성을 지정하는데 대개 NULL로 준다.

 

 - 두 번째 인수는 뮤텍스를 생성함과 동시에 소유할 것인지를 지정하는데 이 값이 TRUE이면 이 함수를 호출한 스레드가 먼저

   뮤텍스를 소유한 상태에서 동작한다. FALSE인 경우는 최초로 대기 함수를 호출하는 함수가 이 뮤텍스를 소유하는데 스레드에서

   뮤텍스를 생성할 때는 통상 FALSE로 준다.

 

 - 세 번째 인수는 뮤텍스의 이름을 지정하는 문자열이다. 프로세스 간에 뮤텍스가 공유될 때 이 이름을 이용한다.

   커널 객체들은 이런 식으로 문자열로 된 이름을 가짐으로써 공유가 가능하며 다른 프로세스에서 뮤텍스의 이름만 알면 OpenMutex

   함수로 뮤텍스의 핸들을 얻을 수 있다. 또는 같은 이름으로 CreateMutex를 한 번 더 호출해도 된다.

 

1
HANDLE WINAPI OpenMutex(__in DWORD dwDesiredAccess, __in BOOL bInheritHandle, __in LPCSTR lpName);
cs

 

CreateMutex, OpenMutex 함수가 지정하는 뮤텍스의 이름은 시스템 전역적으로 유일하게 하나의 뮤텍스를 가리킨다.

(즉, 1 PC에 동일한 이름의 서로 다른 뮤텍스는 존재할 수 없다는 것임.)

만약 한 프로세스 내에서만 사용한다면 뮤텍스의 이름을 줄 필요없이 세 번째 인자에 NULL을 주면된다.

 

뮤텍스를 파괴할 때는 모든 커널 객체와 마찬가지로 CloseHandle 함수를 사용한다.

CloseHandle은 뮤텍스 핸들을 닫으며 만약 이 핸들이 대상 뮤텍스를 가리키는 마지막 핸들이라면 뮤텍스 객체도 파괴된다.

뮤텍스는 스스로 카운트를 관리하며 모든 핸들이 닫힐 때 객체도 소멸된다. (스마트 포인터랑 같은 개념이라고 생각하면 됨.)

 

또한 비신호 상태의 뮤텍스를 다시 신호 상태로 변경할 때는 하기 함수를 사용한다.

1
BOOL WINAPI ReleaseMutex(__in HANDLE hMutex);
cs

 

<뮤텍스 사용 예제>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
int X;
HANDLE hMutex;
 
DWORD WINAPI ThreadFunc1(LPVOID temp)
{
    HDC hdc;
    hdc = GetDC(hWndMain);
    for (int i=0; i<100++i)
    {
        WaitForSingleObject(hMutex, INFINITE); //Mutex가 신호 상태가 될 때가지 무한정 대기.
                                               //Mutex가 신호 상태가 되었다면 대기함수를 지나고
                                               //Mutex는 비신호 상태로 변경된다.
        X = 100;
        Sleep(1);
        TextOut(hdc, X, 100"고양이"6);
        ReleaseMutex(hMutex);                  //대기 함수가 변경한 뮤텍스의 비신호 상태를
                                               //다른 Thread의 대기 함수 또한 실행될 수 있도록
                                               //신호상태로 변경한다.
    }
 
    ReleaseDC(hWndMain, hdc);
    return 0;
}
 
DWORD WINAPI ThreadFunc2(LPVOID temp)
{
    HDC hdc;
    hdc = GetDC(hWndMain);
    for (int i=0; i<100++i)
    {
        WaitForSingleObject(hMutex, INFINITE); //Mutex가 신호 상태가 될 때가지 무한정 대기.
        X = 200;
        Sleep(1);
        TextOut(hdc, X, 200"강아지"6);
        ReleaseMutex(hMutex);                  //신호 상태로 변경.
    }
 
    ReleaseDC(hWndMain, hdc);
    return 0;
}
 
 
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
    HANDLE hThread;
    DWORD ThreadID;
 
    switch(iMessage) 
    {
    case WM_CREATE:
        hMutex = CreateMutex(NULL, FALSE, NULL); //Mutex 생성.
        hWndMain = hWnd;
        return 0;
    case WM_LBUTTONDOWN:
        hThread = CreateThread(NULL0, ThreadFunc1, NULL0&ThreadID);
        CloseHandle(hThread);
        hThread = CreateThread(NULL0, ThreadFunc2, NULL0&ThreadID);
        CloseHandle(hThread);
        return 0;
    case WM_DESTROY:
        CloseHandle(hMutex); //Mutex 파괴.
        PostQuitMessage(0);
        return 0;
    }
    return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}
cs

 

 

멀티 스레드의 문제점

 

멀티 스레드의 가장 큰 문제점은 공유 자원을 보호하기 어렵다는 점이다. 공유 자원은 주로 메모리 영역의 전역 변수인

경우가 대부분이다. 동일한 프로세스에 속한 스레드는 같은 주소 공간에서 실행되며 전역변수를 공유하므로 문제의

소지가 많다. 두 스레드가 같은 변수에 값을 대입하는 경우, 앞쪽 스레드가 대입해 놓은 값은 뒤쪽 스레드가 대입한 값에

의해 지워진다.

이와 같이 스레드가 공유 자원을 서로 사용하려는 상태를 경쟁 상태(race condition)이라 한다.

 

또한 경쟁 상태를 해소하기 위해서는 실행 순서를 통제해야 하는데 그러다 보면 최악의 경우 스레드끼리 서로를 기다리는

교착상태(deadlock)가 발생하기도 한다.

 

이러한 여러 가지 문제를 해결하기 위해 스레드간의 실행 순서를 제어할 수 있는 기술들을 동기화(Synchronization)이라 한다.

 

우선 예제를 보자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
int X;
 
DWORD WINAPI ThreadFunc1(LPVOID temp)
{
    HDC hdc;
    hdc = GetDC(hWndMain);
    for (int i=0; i<100++i)
    {
        X = 100;
        Sleep(1);
        TextOut(hdc, X, 100"고양이"6);
    }
 
    ReleaseDC(hWndMain, hdc);
    return 0;
}
 
DWORD WINAPI ThreadFunc2(LPVOID temp)
{
    HDC hdc;
    hdc = GetDC(hWndMain);
    for (int i=0; i<100++i)
    {
        X = 200;
        Sleep(1);
        TextOut(hdc, X200"강아지"6);
    }
 
    ReleaseDC(hWndMain, hdc);
    return 0;
}
 
 
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
    HANDLE hThread;
    DWORD ThreadID;
 
    switch(iMessage) 
    {
    case WM_CREATE:
        hWndMain = hWnd;
        return 0;
    case WM_LBUTTONDOWN:
        hThread = CreateThread(NULL0, ThreadFunc1, NULL0&ThreadID);
        CloseHandle(hThread);
        hThread = CreateThread(NULL0, ThreadFunc2, NULL0&ThreadID);
        CloseHandle(hThread);
        return 0;
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}
cs

 

위의 소스는 X라는 값을 ThreadFunc1과 ThreadFunc2에서 동시에 접근하고 있다.

그러므로 Thread1에서는 X값이 100이어야 하고 Thread2에서는 X 값이 200이어야 하는데 결과를 보면 전혀 그렇지 않다.

두 Thread 모두 X 값이 100일 때도 있고 200일 때도 있게 된다.

 

이유를 설명하기 위해 예를 들자면...

10번 line 소스에서 스위칭이 발생하면 ThreadFunc1이 동작하는 중에 ThreadFunc2가 동작하게 된다.

그럼 X값은 100이었지만 ThreadFunc2를 거치면서 X값은 200으로 바뀌게 된다.

그리고 다시 스위칭이 발생하면 ThreadFunc1의 동작이 이어서 진행이 되는데, 원래 Thraed1의 X는 100이어야하는데

Thread2를 거치면서 200이라는 값으로 바뀌었기 때문에 원하지 않은 200이라는 값을 가지고 Thread1에서 처리하게 되는

것이다.

 

반대의 경우도 마찬가지이기 때문에 실행 결과의 Text가 찍히는 좌표는 사용자가 원하던 좌표가 아니게 되는 것이다.

 

해결 방법

 

1) 크리티컬 섹션

 

 가장 이해하기 쉽고 속도도 빠른 방법이지만 동일한 프로세스 내에서만 사용해야 하는 제약이 있다.

 다른 스레드에의해 방해받지 말아야 할 작업을 할 때 이 영역을 크리티컬 섹션으로 둘러싸 놓으면 전역 자원의 독점권이

 주어진다.

 

 <크리티컬 섹션 주요 함수>

1
2
3
4
5
6
7
VOID WINAPI InitializeCriticalSection(__out LPCRITICAL_SECTION lpCriticalSection);
VOID WINAPI DeleteCriticalSection(__inout LPCRITICAL_SECTION lpCriticalSection);
 
 
VOID WINAPI EnterCriticalSection(__inout LPCRITICAL_SECTION lpCriticalSection);
VOID WINAPI LeaveCriticalSection(__inout LPCRITICAL_SECTION lpCriticalSection);
 
cs

 

 모든 인자는 CRITICAL_SECTION 형의 포인터를 인수로 요구하므로 CRITICAL_SECTION 형 변수의 주소를 전달하면 된다.

 이 변수는 복수 개의 스레드가 참조해야 하므로 반드시 전역변수로 선언해야 한다.

 

 전역 변수 선언 후 InitializeCriticalSection 함수로 초기화하면 이후부터 크리티컬 섹션을 사용할 수 있다.

 다 사용한 후에는 DeleteCriticalSection으로 파괴해야 하며 EnterCriticalSection 함수와 LeaveCriticalSection 함수를 가지고

 보호될 코드를 감싼다. 아래 예제를 보면 사용법에 대해 쉽게 알 수 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
int X;
CRITICAL_SECTION crit;
 
DWORD WINAPI ThreadFunc1(LPVOID temp)
{
    HDC hdc;
    hdc = GetDC(hWndMain);
    for (int i=0; i<100++i)
    {
        EnterCriticalSection(&crit); //크리티컬 섹션 보호 시작
        X = 100;
        Sleep(1);
        TextOut(hdc, X, 100"고양이"6);
        LeaveCriticalSection(&crit); //크리티컬 섹션 보호 종료.
    }
 
    ReleaseDC(hWndMain, hdc);
    return 0;
}
 
DWORD WINAPI ThreadFunc2(LPVOID temp)
{
    HDC hdc;
    hdc = GetDC(hWndMain);
    for (int i=0; i<100++i)
    {
        EnterCriticalSection(&crit); //크리티컬 섹션 보호 시작
        X = 200;
        Sleep(1);
        TextOut(hdc, X, 200"강아지"6);
        LeaveCriticalSection(&crit); //크리티컬 섹션 보호 종료.
    }
 
    ReleaseDC(hWndMain, hdc);
    return 0;
}
 
 
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
    HANDLE hThread;
    DWORD ThreadID;
 
    switch(iMessage) 
    {
    case WM_CREATE:
        InitializeCriticalSection(&crit); //크리티컬 섹션 초기화.
        hWndMain = hWnd;
        return 0;
    case WM_LBUTTONDOWN:
        hThread = CreateThread(NULL0, ThreadFunc1, NULL0&ThreadID);
        CloseHandle(hThread);
        hThread = CreateThread(NULL0, ThreadFunc2, NULL0&ThreadID);
        CloseHandle(hThread);
        return 0;
    case WM_DESTROY:
        DeleteCriticalSection(&crit); //크리티컬 섹션 파괴.
        PostQuitMessage(0);
        return 0;
    }
    return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}
cs

 

주의할 점은 LeaveCriticalSection을 거쳐야지만 다른 Thread에서 다시 EnterCriticalSection을 들어갈 수 있다.

그렇기 때문에 예외 상황을 항상 고려해야하며 구조적으로 예외처리 구문제 포함시키는 것이 좋다.

 

1
2
3
4
5
6
7
__try{
    EnterCriticalSection(&crit);
    ...
}
__finally{
    LeaveCriticalSection(&crit);
};
cs

스레드 컨텍스트

 

스레드는 1개의 CPU에서 동시에 실행되는 것처럼 흉내내는 방법이므로 스레드를 어떤 순서로 얼마만큼의 간격으로 실행할 것

인가를 결정해야 하는데 이를 스케줄리이라고 한다.

운영체제는 CPU의 실행시간을 아주 잘게 쪼개어 스레드를 조금씩 순서대로 실행함으로써 동시에 실행되는 것처럼 보이게

만든다. 대략 0.02초 정도로 시간을 분할하며 이때 분할된 시간 조각 하나를 퀀텀(Quantum)이라고 한다.

또한 굉장히 빠른 속도로 스레드를 번갈아 가며 실행하는 방식을 라운드 로빈(Round Robin) 방식이라고 한다.

 

스케줄링 대상은 프로세스가 아니라 스레드이다. 즉, 워드 한번, 탐색기 한번, 특정 프로그램 한번 이렇게 스케줄링되는 것이

아니라 각 프로세스에 속한 개별 스레드들이 스케줄링되는 것이다. 또한 스케줄러는 스레드가 어떤 프로세스 소속인지에 대해서

아예 관심을 두지 않는다.

 

Thread A에서 Thread B로 작업이 스위칭된다고 생각해보자.

CPU는 다음에 다시 Thread A를 실행시킬 때 스위칭 시 중지되었던 구간이 어디인지 저장해 두어야 한다.

그렇지 않으면 다음 Thread A 실행 시 어디까지 작업을 하다 말았는지 알 수 없을 것이다.

이 때, 스레드 실행 상태에 대한 정보를 스레드 컨텍스트(Thread Context)라고 하는데 스레드가 하던 작업의 상태에 관한 정보는

단순한 레지스터값과 몇 가지 값의 집합이다. (일종의 구조체)

 

스레드 간에 작업을 전환하는 작업을 컨텍스트 스위칭이라고 하는데 매번 정보를 저장하고 읽어오는 과정이 필요하다.

그래서 실행시간 저하가 굉장히 심할 것 같지만 무시해도 될 정도로 비중이 작다. CPU는 생각보다 훨씬 더 빠르기 때문이다.

스레드 컨텍스트의 정보를 얻어와서 변경하는 함수가 제공된다. 그러나 컨텍스트 정보는 단순한 레지스터 값의 집할일 뿐이고

실행 번지를 알 수 있다 하더라도 기계어 수준에서 코드를 해석해야 하므로 사용자가 알기에는 굉장히 어렵다.

그래서 이 함수는 별도로 정리하지 않았으며 이 함수로 스레드를 임의 조작하는 것은 바람직하지 않다.

(책에는 함수에 대한 설명이 간략히 되어있음.)

 

우선순위

 

한 시스템에서 실행되는 스레드의 개수는 적어도 몇십 개이며 프로그램 몇 개만 실행되어도 백 개가 넘는 스레드가 동작한다.

이 많은 스레드를 순서대로 공평하게 실행한다면 시스템은 너무 느려질 것이다.

위에서 설명했던 것처럼 1 퀀텀이 0.02초라 치면 50개의 스레드가 실행되어도 1 스레드들을 1초마다 한 번씩 제어권을 가지게

될 것이다.

 

스레드 중에는 바쁘게 처리되는 스레드도 있고 아무것도 하지 않고 대기만 하는 스레들도 있기 때문에 시스템을 효율적으로

운영하기 위해서는 스레드간의 우선 순위를 정해 긴급하고 사용자에 더 가까운 스레드에게 좀 더 많은 시간을 주는 것이 합리적이다.

 

스레드를 생성할 때 디폴트는 보통 우선 순위 레벨은 THERAD_PRIORITY_NORMAL이며 CreateThread에는 스레드의 우선 순위 레벨을

지정하는 인수가 따로 없으므로 일단 스레드를 생성한 후 변경해야 한다.

우선 순위는 우선 순위 클래스와 우선 순위 레벨을 조합하여 정하는데 총 47가지가 되지만 거의 7~11 사이의 기본 우선 순위에서

동작한다. 결정된 순위는 스케줄러가 참조하는데 스케줄러는 무조건 순위가 높은 스레드를 우선 실행한다.

그래서 발생할 수 있는 문제점은 우선 순위가 높은 스레드 하나가 작업 중이라면 그 아래의 모든 스레드는 아무도 CPU 시간을 받을 수

없으며 이 상태를 기아(Starvation) 상태라고 한다. 그러나 실제로는 이런 상황은 발생하지 않는데 우선 순위가 높을수록 작업 시간은

짧아서 모든 스레드에게 시간이 골고루 돌아간다.

 

또한 스케줄러는 우선 순위보다도 스레드의 상태를 보고 스케줄 대상을 결정하는데 Suspend된 스레드, Sleep 중인 스레드, GetMessage

가 메시지를 꺼내지 못해 처리할 메시지가 없는 스레드 등에게는 CPU가 시간을 주지 않는다.

 

그러므로 사용자가 스레드 우선 순위를 설정할 일은 거의 없어 보인다. (내 생각)

 

 

UI 스레드

 

스레드는 보통 백그라운드에서 작업을 하며 사용자 눈에는 보이지 않는다. 이처럼 내부적인 계산만 하고 조용히 사라지는

스레드를 작업 스레드(Word Thread)라고 하는데 대부분의 스레드는 작업 스레드이다.

 

UI 스레드는 윈도우를 만들고 메시지 큐와 메시지 루프를 가진다. 메시지 큐는 스레드별로 생성되는데 UI 스레드는

주 스레드와는 다른 메시지 큐를 가지는 스레드이다. 윈도우를 가지고 메시지를 처리할 수 있다는 말은 곧 사용자와

상호 작용을 할 수 있다는 뜻이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#include <Windows.h>
 
LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);
HINSTANCE g_hInst;
HWND hWndMain;
LPCTSTR lpszClass="ThreadStopResume";
DWORD WINAPI ThreadFunc(LPVOID temp);
LRESULT CALLBACK DeCompProc(HWND hWnd, UINT iMessage,WPARAM wParam, LPARAM lParam);
 
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam,int nCmdShow)
{
    HWND hWnd;
    MSG Message;
    WNDCLASS WndClass;
    g_hInst=hInstance;
 
    WndClass.cbClsExtra=0;
    WndClass.cbWndExtra=0;
    WndClass.hbrBackground=(HBRUSH)(COLOR_WINDOW + 1);
    WndClass.hCursor=LoadCursor(NULL,IDC_ARROW);
    WndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION);
    WndClass.hInstance=hInstance;
    WndClass.lpfnWndProc=(WNDPROC)WndProc;
    WndClass.lpszClassName=lpszClass;
    WndClass.lpszMenuName=NULL;
    WndClass.style=CS_HREDRAW | CS_VREDRAW;
    RegisterClass(&WndClass);
 
    WndClass.cbClsExtra = 0;
    WndClass.cbWndExtra = 0;
    WndClass.hbrBackground = (HBRUSH)GetStockObject(LTGRAY_BRUSH);
    WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
    WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    WndClass.hInstance = g_hInst;
    WndClass.lpfnWndProc = DeCompProc;
    WndClass.lpszClassName = "DecompWnd";
    WndClass.lpszMenuName = NULL;
    WndClass.style = CS_HREDRAW | CS_VREDRAW;
    RegisterClass(&WndClass);
 
    hWnd=CreateWindow(lpszClass,lpszClass,WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,CW_USEDEFAULT,
        CW_USEDEFAULT,CW_USEDEFAULT,NULL,(HMENU)NULL,hInstance,NULL);
    ShowWindow(hWnd,nCmdShow);
    hWndMain = hWnd;
 
    while(GetMessage(&Message,0,0,0)) {
        TranslateMessage(&Message);
        DispatchMessage(&Message);
    }
    return Message.wParam;
}
 
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    TCHAR* Mes = "왼쪽 버튼을 누르면 압축 해제 스레드를 실행합니다.";
    HANDLE hThread;
    DWORD ThreadID;
 
    switch(iMessage) 
    {
    case WM_LBUTTONDOWN:
        hThread = CreateThread(NULL0, ThreadFunc, NULL0&ThreadID);
        CloseHandle(hThread);
        return 0;
    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        TextOut(hdc, 1010, Mes, lstrlen(Mes));
        EndPaint(hWnd, &ps);
        return 0;    
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}
 
DWORD WINAPI ThreadFunc(LPVOID temp)
{
    HWND hWnd;
    MSG Message;
 
    hWnd = CreateWindow("DecompWnd""압축 해제 중", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
        400150, hWndMain, (HMENU)NULL, g_hInst, NULL);
    ShowWindow(hWnd, SW_SHOW);
 
    while(GetMessage(&Message, NULL00)){
        TranslateMessage(&Message);
        DispatchMessage(&Message);
    }
 
    return Message.wParam;
}
 
LRESULT CALLBACK DeCompProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
    TCHAR Cap[256];
    int Value;
 
    switch(iMessage)
    {
    case WM_CREATE:
        CreateWindow("button""시작", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 50809025, hWnd,(HMENU)0, g_hInst, NULL);
        CreateWindow("button""닫기", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 250809025, hWnd,(HMENU)1, g_hInst, NULL);
        Value = 0;
        SetProp(hWnd, "VALUE", (HANDLE)Value);
        SendMessage(hWnd, WM_COMMAND, MAKEWPARAM(0, BN_CLICKED), (LPARAM)0);
        return 0;
    case WM_TIMER:
        Value = (int)GetProp(hWnd, "VALUE");
 
        //실제 압축 코드가 있다면 Timer가 아닌 압축을 진행하는 코드를 넣으면된다.
 
        Value++;
        wsprintf(Cap, "압축 해제 중 : %d", Value);
        SetWindowText(hWnd, Cap);
        SetProp(hWnd, "VALUE",(HANDLE)Value);
        if (Value == 100) {
            SetWindowText(hWnd, "압축 해제 완료");
            KillTimer(hWnd, 1);
            EnableWindow(GetDlgItem(hWnd,0), FALSE);
        }
        return 0;
    case WM_COMMAND:
        switch (LOWORD(wParam)) {
        case 0:
            GetDlgItemText(hWnd, 0, Cap, 256);
            if (lstrcmp(Cap, "시작"== 0) {
                SetDlgItemText(hWnd,0,"중지");
                SetTimer(hWnd,1,200,NULL);
            }else{
                SetDlgItemText(hWnd,0,"시작");
                KillTimer(hWnd,1);
            }
            break;
        case 1:
            DestroyWindow(hWnd);
            break;
        }
        return 0;
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
 
    return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}
cs

 

ThreadFunc에서는 압축 해제 경과를 표시하는 윈도우를 만들고 메시지 루프를 돌리는데 WinMain과 흡사하다.

또한, 윈도우 클래스는 스레드 별로 중복 등록할 필요가 없으므로 WinMain에서 딱 한 번만 등록한다.

 

UI 스레드(Threadfunc)는 메시지 큐를 따로 가지는데 운영체제는 윈도우를 생성하는 스레드에 대해 별도의 메시지 큐를

생성한다. DeCompProc는 전달되는 메시지를 처리하며 압축 해제 작업을 시뮬레이션한다.

 

닫기 버튼을 누르면 DestroyWindow 함수를 호출하여 WM_DESTROY Message를 전달받고 PostQuitMessage를 호출하여

스레드의 메시지 루프를 종료시켜 스레드 자체를 종료한다.

 

 

'Programming > Thread' 카테고리의 다른 글

[API] Thread 6 (동기화 - 크리티컬 섹션)  (0) 2017.08.30
[API] Thread 5 (스케줄링)  (0) 2017.08.30
[API] Thread 3 (Thread 관리)  (0) 2017.08.29
[API] Thread 2 (MultiThread)  (0) 2017.08.29
[API] Thread 1 (단일 Thread)  (0) 2017.08.24

+ Recent posts