Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
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
Archives
Today
Total
관리 메뉴

codingfarm

3. DC(Device Context) 본문

Windows/윈도우즈 API

3. DC(Device Context)

scarecrow1992 2020. 11. 19. 00:20

1. DC(Device Context)의 필요성

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

  1. KERNEL : 메모리를 관리하고 프로그램을 실행한다.
  2. USER : 유저 인터페이스와 윈도우를 관리한다.
  3. GDI : 화면 처리와 그래픽을 담당한다.(Graphic Device Interface)

윈도우즈 API 함수의 대부분은 이 세가지 DLL에 의해 제공된다.

출력을 위해선 이 중 GDI에 관심을 가져야 한다.

결론부터 말하면 DC는 GDI를 활용해 화면을 그림에 있어서 부수적인 부분을 최소화 하여 개발자들이 당장 필요로 하는 정보에만 집중하게끔 해주는 역할을 담당한다.

이 DC의 역할을 되새기며 화면 출력에 DC가 필요한 이유를 위한 예를 상황과 함께 들어본다.

 

상황1

화면에 선을 긋는 함수 LineTo를 만드려 한다.

화면에 선을 긋기 위해 필요한 정보는 아래와 같다.

  • 시작점과 끝점의 좌표
  • 선의 색상
  • 굵기
  • 모양
  • 선을 그리는 모드
  • 좌표값을 해석하는 방법
  • 어느 윈도우 창에 그릴것인가?

이외에도 추가 정보가 더있어야 완벽한 선을 그을 수 있다.

이런 정보를 모두 함수 인수로 넘기는것은 매우 불편하다

LineTo(StartX, StartY, EndX, EndY, Color, Width, Shape, ROP, mode, ......)

그러므로 선에대한 정보를 한곳에 모아 두고 그 값을 사용하는 방법이 훨씬 편하고 편리할것이다.

LineTo(hDC, X, Y)

한눈에 봐도 함수가 간편해젔다.

 

상황2

(50, 50)좌표에서 (150,50) 좌표로 선을 긋는다 하자.

그럴때 위의 숫자만 읽어서는 각 좌표의 기준점을 화면으로 할지 윈도우 작업영역으로 할지 모호성이 생긴다.

보편적으로 기대하는 결과는 후자이지만, 윈도우즈는 전자에 대한 정보도 DC에 가지고있다.

그래서 DC는 자신이 참조해야할 원점에 대한 정보를 가진다.

 

상황3

윈도우즈는 멀티 태스킹 시스템이므로 그리기 함수에 의해 실제 출력되는 결과는 주변 환경에 따라 달라진다.

가령 2개의 윈도우가 겹처진다면, 아래쪽에 있는 윈도우는 자신의 그림을 위에 있는 윈도우의 아래에 놓아야한다.

 

이런 복잡한 작업을 담당하는것이 DC이다.

여기서 DC는 윈도우 끼리의 출력 결과가 서로 방해하지 않도록 완충역할을 한다.

 

즉, DC는 부수적인 부분을 최소화 하여 개발자들이 당장 필요로 하는 정보에만 집중하게끔 해주는 역할을 담당한다.

 


2. 문자열 출력

DC를 사용하여 문자열을 출력하는 예제를 만들어 본다.

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
#include <windows.h>
 
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_hInst;
LPCTSTR lpszClass = TEXT("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(BLACK_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, 000)) {
        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_LBUTTONDOWN:
        hdc = GetDC(hWnd);
        TextOut(hdc, 100, 100, TEXT("Beautiful Korea"), 15);
        ReleaseDC(hWnd, hdc);
        return 0;
    }
    return(DefWindowProc(hWnd, iMessage, wParam, lParam));
}
cs

주황색이 이번에 주목해야할 코드들이다.

실행하면 까만화면이 나오고, 좌클릭하면 정해진 좌표에 문자열이 출력된다.

49 : GetDC함수로 DC를 얻는다.

50 : TextOut 함수로 (100, 100) 좌표에 15자 길이의 문자열을 출력하고, Release 함수로 DC를 해제하였다.

DC를 얻는방법과 Text함수에 대해서는 나중에 알아본다.

 


3. WM_PAINT 메시지

위 예제의 윈도우창은 완벽하지 않다.

가령 창의 크기를 조절하거나, 최소화 시킨후 다시 창을 틀어보면 문자열이 사라진것을 볼 수 있다.

왜냐하면 OS가 개별 윈도우의 화면을 보관 및 복구해 주지 않기 때문이다.

도스는 한번에 하나의 프로그램만 실행하므로 사용자가 일부러 지우지 않는한 절대 안지워지는 반면

멀티 태스킹 시스템인 윈도우즈에서는 언제까지고 그 자리에 문자열이 있다는 보장이 없다.

하지만 우리는 윈도우즈 사용도중 창의 크기를 조종한다고 해서 내용이 사라지는 것을 본적이 없는데

이는 다음에 설명할 방법으로 끊임없이 지워진 부분을 복구하기 때문이다.

이를 복구하는 방법은 OS가 아닌 개발자의 몫이다.

 

그렇다면 화면이 지워지면 다시 복구되도록 코드를 수정해보자.

어느 시점에 문자열을 다시 출력해야할까?

OS가 문자열의 일부가 지워졌다는 사실을 프로그램으로 즉각 알리면 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
55
56
#include <windows.h>
 
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_hInst;
LPCTSTR lpszClass = TEXT("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(BLACK_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, 000)) {
        TranslateMessage(&Message);
        DispatchMessage(&Message);
    }
    return Message.wParam;
}
 
 
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, 100, 100, TEXT("Beautiful Korea"), 15);
        EndPaint(hWnd, &ps);
        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
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
#include <windows.h>
 
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_hInst;
LPCTSTR lpszClass = TEXT("TextOut");
 
int cnt = 0;
 
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, 000)) {
        TranslateMessage(&Message);
        DispatchMessage(&Message);
    }
    return Message.wParam;
}
 
 
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);
        TCHAR str[128];
        wsprintf(str, TEXT("%d"), cnt);
        TextOut(hdc, 11, str, lstrlen(str));
        EndPaint(hWnd, &ps);
        cnt++;
        return 0;
    }
    return(DefWindowProc(hWnd, iMessage, wParam, lParam));
}
cs

WM_PAINT영역에 진입할때마다 cnt가 1씩 증가하고 문자열이 새로 출력된다.

창을 최소화 시켰다가 다시 키울때마다 1씩 증가함을 볼 수 있으며

화면을 모니터 밖으로 뺄때마다 매우 빠른속도로 증가함을 육안으로 볼 수 있다.

 

 


4. DC를 얻는법

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

 

첫번째 방법

앞의 예제처럼 GetDC함수로 얻고 ReleaseDC로 제거한다.

HDC GetDC(HWND hWnd);
int ReleaseDC(HWND hWnd, HDC hDC);

DC는 주로 윈도우와 연관되는 출력정보를 가지므로, 인수로 어떤 윈도우에 대한 DC가 필요한가를 밝혀야 한다.

 

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

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

즉, 할당 후 해제 원칙이 반드시 준수되어야 한다.

GetDC로 DC를 구해 사용하는 코드는 일반적으로 아래와 같다.

HDC hdc;
hdc = GetDC(hWnd);
각종 출력문에서 hdc를 사용한다.
ReleaseDC(hWnd, hdc);

 

 

두번째 방법

WM_PAINT 메시지 루틴에서만 사용할 수 있다.

WM_PAINT 메시지 처리 루틴에서는 DC 핸들을 GetDC가 아닌 BeginPaint 함수로 얻는다.

핸들을 해제할때는 ReleaseDC가 아닌 EndPaint 함수를 사용한다.

HDC BeginPaint(HWND hwnd, LPPAINTSTRUCT lpPaint);
BOOL EndPaint(HWND hwnd, CONST PAINTSTRUCT *lpPaint);

BeginPaint 함수는 윈도우 핸들 외에 페인트 정보 구조체를 요구하며, 지정된 window에 대한 화면DC의 핸들을 리턴한다.

typedef struct tagPAINTSTRUCT {
  HDC  hdc;
  BOOL fErase;
  RECT rcPaint;
  BOOL fRestore;
  BOOL fIncUpdate;
  BYTE rgbReserved[32];
} PAINTSTRUCT, *PPAINTSTRUCT, *NPPAINTSTRUCT, *LPPAINTSTRUCT;

1~3번째 멤버는 사용자가 사용하고, 4~6번째 멤버는 윈도우즈가 내부적으로 사용하므로 건드려서는 안된다.

아래와 같은 방법으로 BeginPaint와 EndPaint를 사용하면된다.

HDC hdc;
PAINTSTRUCT ps;
case WM_PAINT:
    hdc = BeginPaint(hWnd, &ps);
    각종 출력문에서 hdc를 사용한다.
    EndPaint(hWnd, &ps);

이에 대한 이야기는 추후 자세히 배울것이다.

  1. DC를 얻어 사용하는 방법
  2. GetDC는 ReleaseDC와 짝이다.
  3. BeginPaint는 EndPaint와 짝이다.(지금은 WndProc내의 WM_PAINT에서만 쓸것)

이렇게만 알아두자.

 

 

요약

  • 윈도우 프로그램을 구성하는 세가지 DLL
    1. KERNEL : 메모리를 관리하고 프로그램을 실행한다.
    2. USER : 유저 인터페이스와 윈도우를 관리한다.
    3. GDI : 화면 처리와 그래픽을 담당한다.(Graphic Device Interface)
  • DC(Device Context)
    • 화면 출력에 필요한 모든 정보를 가지는 구조체 (폰트, 색상, 굵기, 출력 방법 등)
    • GDI에 의해 관리됨
  • DC의 필요성 : GDI를 활용해 화면을 그림에 있어서 부수적인 부분을 최소화 하여 개발자들이 당장 필요로 하는 정보에만 집중하게끔 해줌
  • WM_PAINT 메시지 : 화면을 새로 그려야할 경우 시스템이 윈도우프로그램에 발생시키는 메시지
  • DC를 얻는법
    1. GetDC 함수로 얻고, ReleaseDC 함수로 해제한다.
    2. WM_PAINT 메시지 루틴 내에서 BeginPaint로 얻고, EndPaint로 해제한다.

 

 

 

 

Comments