훈, IT 공부/C,C++,MFC

[MFC] DC, CDC 관련하여 정리

IT훈이 2018. 5. 1.
반응형

 

 

 

 DC도 헷갈리고 CDC도 헷갈리고 HDC를 받아오기 CDC를 받아와서 뭘하는지 헷갈리는 부분이 많아서 정리한 글을 바탕으로 정리해보았습니다. 잘정리 되어있는 사이트 링크 걸어두었으니 부족한 부분들은 참조부탁드립니다.

 


 

 

 

DC(Device Context) 관련

기본적으로 Windows는 세가지 동적 연결 라이브러리로 구성된다. 

 

 - 메모리와 프로그램을 관리하는 Kernel ( 신의영역 ) 

 - 유저인터페이스와 Window를 관리하는 User ( 인간의 영역 ) 

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

 

CDC 를 알기전에 DC에 대해서 먼저 알아보겠다. 

GDI 는 Graphic Device Interface 이다. ( 그래픽 장치 접근혹은 접속 ) GDI 는 화면에 어떠한 접근하는 것을 담당한데 화면에 글을 쓰거나 그림을 넣거나 하는 행동들. DC 는 Device Context 이다 ( 장치 내용 혹은 관계 )  GDI를 추상화한 것이 DC 라고 한다. 추상화라는 말이 조금 이해가 힘들다면 그냥 GDI를 대변하는 녀석이 DC라고 생각하자.

 

📌CDC Class

DC 에 대한 기초 클래스로 화면이나 프린터 출력에 관계된 대부분의 맴버 함수를 포함한다. 출력할 수 있는 영역에 따라 파생클래스가 4개가 있다.

 

📌CWindowDC

캡션바, 메뉴바, 상태바 등 Non Client영역을 포함한 전체 윈도우를 표시하는 DC를 관리한다.

 

📌CClientDC

Window영역의 캡션바 메뉴바 상태바 등을 제외한 클라이언트 영역만을 관리하는 DC를 뜻한다.

WM_PAINT메시지가 발생하지 않았을 때 화면에 그림을 그리기 위해 사용한다. 내부적으로 GetDC(), ReleaseDC()함수를 사용해서 구현되어있다.

 

📌CPaintDC

WM_PAINT 메시지가 발생했을때 다시 그려져야 할 영역에 대한 DC를 관리한다. 

WM_PAINT 메시지 핸들러인 OnPaint()함수에서 사용한다. BeginPaint()함수와 EndPaint()함수로 이루어진다. MFC 뷰클래스를 WM_PAINT 메시지를 받으면 내부적으로 CPaintDC클래스를 생성하고, OnDraw()함수를 호출해준다. 그래서 OnDraw()함수를 WM_PAINT처럼 처리하는 것이다.

 

 

그림을 그릴때, DC를 도화지라 비유하면, 그림을 그릴때 필요한 연필 붓 등과 같은 도구가 필요한데, 이를 GDI Object라고 한다. GDI Object 를 사용시 사용되는 함수를 CDC 클래스의 맴버함수 SelectObject()로써 같이 사용을 한다.

 

GDI Object 사용하기 예시

1. CPaintDC dc(this);

2. CPen NewPen, *pOldPen;

3. NewPen.CreatePen(PS_SOLID, 1, RGB(0,0,255));

4. pOldPen = dc.SelectObject(&NewPen);

5. ...

6. dc.SelectObject(pOldPen);

7. NewPen.DelectObject();

 

1,2,3 행과 같이 PenObject를 설정하고 4행에서 PenObject를 사용한다는 SelectObject()함수를 호출한다.

7번행에서는 메모리낭비를 방지하기 위해서 Object를 삭제해준다.

 

GDI Object의 종류

CPen 예제와 같이 사용이 가능하다. 

 

 

Q&A) CPaintDC 와 CClientDC 는 유사한 것같은데 동일하게 사용하면 되는가?

 

 생성된 윈도우가 다른 윈도우에 가려져서 무효화영역(Invalid Region)이 발생하게 되면 WM_PAINT메세지가 호출되고 WM_PAINT메세지를 처리하는 메세지 핸들러에서 무효화 영역이 복구되어진다. 다이얼로그 기반대화상자에서 WM_PAINT메세지는 OnPaint() 라는 메세지 함수에서 처리하게 된다.

 

 WM_PAINT메세지는 플래그성 메세지이기 때문에 메세지가 발생하여 WM_PAINT 관련 플래그로 1로 설정되면 메세지큐가 비어있을 경우 WM_PAINT 메세지가 지속적으로 발생하게 된다. 비어있지 않다면 밀려있는 메세지를 처리하고나서 플래그메세지를 보고 처리하게된다. 즉 우선순위가 낮다.

void CDlgSizeDlg::OnPaint()
{
    if (IsIconic())
    {
        CPaintDC dc(this); // 그리기를 위한 디바이스 컨텍스트
 
        ..... 생략
    }
    else
    {
    //    CDialog::OnPaint();
        CClientDC dc(this);
        dc.Rectangle(10,10,200,200);
    }
}

 즉 CPaintDC는 단순히 DC의 역할만 수행하는 것이 아니라 WM_PAINT 메세지의 고유한 특성을 반영하는데 다른 부분은 CClientDC로 대체해도 어느정도 비슷하게 돌아가겠지만 메세지 테이블에서 플래그를 해제하는 기능은 대체되지 않기때문에 문제가 발생하게 된다. CClientDC는 WM_PAINT 메세지를 0으로 변경하지 못하기 때문에 메세지 핸들러함수 OnPaint()함수를 끊임없이 호출하게 되고, CPU의 점유율이 올라가게 됩니다.

 

 CPaintDC 의 소멸자에서는 WM_PAINT메세지를 0으로 변경해주어 CPU점유율을 증가시키는 것을 방지할 수있습니다. 또한 CDialog::OnPaint()는 CPaintDC의 객체 파괴자와 동일한 기능을 수행합니다. 그렇게 때문에 CPaintDC 객체를 생성하던지 혹은 CDialog::OnPaint()함수를 호출하던지 중복되지만 않으면 됩니다. 

 

애초에 5번행을 if문 밖으로 빼게되면 소멸자가 불리면서 자동으로 메세지 플래그를 0으로 변경해주게 됩니다.

 

 

http://www.tipssoft.com/bulletin/board.php?bo_table=FAQ&wr_id=636

▲ WM_PAINT 메세지에 관한 참조

 

http://www.tipssoft.com/bulletin/board.php?bo_table=FAQ&wr_id=700

▲ OnPaint에서 유의할점

 

 

제일 중요한 것은 DC를 받고나면 반드시 ReleaseDC() 로 DC를 해제 해주어야 한다. 메모리 누수방지를 위해서이다.

 

 

 

HDC ,CDC, GetDC()

 우리가 직접 접근할 수 없기 때문에 DC를 HDC 라는 핸들을 사용하고 BITMAP은 HBITMAP이라는 핸들을 사용한다. 화면에 접근을 하기위한 함수들은 HDC라는 핸들을 필요로 하기에 (어디에 그릴것인지 명시해야하기 때문에) HDC를 얻는 법을 알아야 한다. 

 

DC는 "하나의 윈도우"에 대한 정보를 담고있는 그래픽 정보 구조체이다. 항상 임의의 윈도우에 뭔가를 그리기 위해서는 'GetDC()'함수를 이용해서 '해당 윈도우'의 Device Context를 작성해야한다.  

 

WinApi에서는 Device를 '하나의 윈도우'를 의미한다는 점을 잊지말자.

 

이점을 잊어버리게 되면 Device Context를 변경하지 않거나 해제하지 않고 다른 윈도우에 이전 윈도우의 DC를 사용하는 오류를 저지를 수 있다. 

 

 DC에 관하여서 헷갈리면 모든 보이는 것들은 ( 버튼 ,이미지, 텍스트박스, 등등) 자신만의 DC를 가지고 있다고 생각하면 된다.

 

GetDC() 함수

HDC* GetDC(HWND hwnd);

 

DC를 작성하기 위해서는 GetDC(HWND hwnd) WinApi함수이다. 이 함수는 입력으로 들어오는 윈도우 핸들에 대해 적당한 DC를 만들어서( 각종 그리기 변수 및 함수들 ) 리턴해준다. 

 

HDC *hdc = GetDC(this->m_hWnd);

Rectangle(hdc,10,10,200,200);

ReleaseDC(this->m_hWnd,hdc);

 

WinApi에서는 이렇게 사용한다. MFC에서 사용하는 GetDC()의 경우에는 조금 다른부분이 있다.

 

CDC* GetDC();

 

원형은 이렇게 된다. 성공하면 CWnd 클라이언트 영역의 DC를 리턴한다. 실패하면 NULL을 리턴한다.

 

CDC *cdc = GetDC();

cdc->Rectangle(10,10,200,200);

ReleaseDC(cdc)

 

어떤가? 차이가 조금 보인다. MFC에서는 이렇게 사용한다는 점을 알수있다. CClientDC를 활용하는것도 가능하다.

 

CClientDC cdc(this);    // 내부생성자에서 GetDC();를 호출한다.

cdc.Rectangle(10,10,200,200);

// ReleaseDC()를 호출하지 않는 이유는 CClientDC내부에서 소멸시켜주기 때문이다.

 

그렇다면 어떻게 GetDC를 사용해야하는지 아래에서 살펴보겠다.

void CDlgSizeDlg::OnBnClickedOk()
{
 
    CDC *dc1 = CWnd::GetDC();
    
    CDC *dc2 = this->GetDC();
 
    CDC *dc3 = GetDC();
 
    CDC *dc4 = GetDC();
 
    ReleaseDC(dc1);
    ReleaseDC(dc2);
    ReleaseDC(dc3);
    ReleaseDC(dc4);
 
    OnOK();
}

 이렇게하면 어떻게 될까?? 개인적인생각으로 같은 DC를 받기때문에 같은 DC주소를 받을 줄 알았다. 그런데 그것이 아니였다.

 

DC받은후 변수

변수의 주소값들은 서로 다른 곳을 지명하고있다. 

 

 

http://www.tipssoft.com/bulletin/board.php?bo_table=story&wr_id=3797

▲ CDC관련 참조

 

http://andrew0409.tistory.com/44

▲ DC관련 참조.

 

 

반응형

댓글