카테고리 보관물: c++

MVC(Model-View-Controller)

상황

상당수 컴퓨터 시스템의 용도는 데이터 저장소에서 데이터를 검색하여 사용자에게 보여주는 것입니다. 사용자가 데이터를 변경하면 변경된 데이터는 시스템의 데이터 저장소에 저장됩니다. 정보가 주로 데이터 저장소와 사용자 인터페이스 사이에 전달되므로 이 두 개체를 서로 연결하여 코딩의 양을 줄이고 응용 프로그램 성능을 향상시키려고 할 것입니다. 하지만 이 자연스러워 보이는 방법에도 몇 가지 중요한 문제점이 있습니다. 한 가지 문제점은 사용자 인터페이스가 데이터 저장소 시스템보다 훨씬 더 자주 변하기 쉽다는 것입니다. 또 다른 문제점은 데이터 저장소와 사용자 인터페이스를 연결할 때 비즈니스 응용 프로그램에는 데이터 전송을 훨씬 넘어서는 비즈니스 논리를 통합하기 쉽다는 것입니다.

문제점

사용자가 쉽게 개별 파트를 수정할 수 있도록 웹 응용 프로그램의 사용자 인터페이스 기능을 모듈화할 수 있는 방법은 무엇일까요?

강제 요인

이러한 상황에서 다음과 같은 강제 요인이 시스템에 작용하며 위의 문제를 해결할 때 이러한 강제 요인을 조정해야 합니다.

 

  • 사용자 인터페이스 논리는 특히 웹 기반 응용 프로그램에서 비즈니스 논리보다 더 자주 변하는 경향이 있습니다. 예를 들어, 새로운 사용자 인터페이스 페이지가 추가되거나 기존 페이지의 레이아웃이 바뀔 수 있습니다. 무엇보다 웹 기반 씬(thin) 클라이언트 응용 프로그램의 이점 중 하나는 응용 프로그램을 재배포하지 않고도 언제든지 사용자 인터페이스를 변경할 수 있다는 점입니다. 프레젠테이션 코드와 비즈니스 논리가 단일 개체에 연결된 경우, 사용자 인터페이스가 변경될 때마다 비즈니스 논리가 포함된 개체를 수정해야 합니다. 이 경우 오류가 발생하기 쉬우며 사소한 사용자 인터페이스 변경이 있은 후에도 모든 비즈니스 논리를 다시 테스트해야 합니다.
  • 일부 경우에는 응용 프로그램이 동일한 데이터를 다른 식으로 표시합니다. 예를 들어, 분석가는 데이터를 스프레드시트로 보기 원하지만 경영진은 이 데이터를 원 그래프로 보기를 원합니다. 일부 리치(rich) 클라이언트 인터페이스에서는 동일한 데이터를 여러 개의 뷰로 동시에 표시할 수 있습니다. 사용자가 하나의 뷰에서 데이터를 변경하면 시스템이 데이터의 모든 다른 뷰를 업데이트해야 합니다.
  • 보기에 좋고 효율적인 HTML 페이지를 디자인하기 위해서는 대개 복잡한 비즈니스 논리를 개발할 때와는 다른 기술이 필요합니다. 두 가지 기술을 모두 가지고 있는 사람은 흔하지 않습니다. 따라서 이 두 파트에 대한 개발 노력을 구분하는 것이 바람직합니다.
  • 사용자 인터페이스 작업은 대개 프레젠테이션과 업데이트의 두 부분으로 구성됩니다. 프레젠테이션 파트에서는 데이터 소스에서 데이터를 검색하고 서식을 지정한 후 표시합니다. 사용자가 데이터 기반의 작업을 수행하는 경우, 업데이트 파트에서는 데이터를 업데이트하기 위해 제어권을 비즈니스 논리로 넘겨줍니다.
  • 웹 응용 프로그램에서의 단일 페이지 요청은 사용자가 선택한 링크의 작업 처리와 대상 페이지의 렌더링을 결합합니다. 상당수의 경우 대상 페이지가 해당 작업에 직접 연결되지 않을 수 있습니다. 예를 들어, 항목의 목록을 표시하는 간단한 웹 응용 프로그램을 생각해 보겠습니다. 사용자가 목록에 항목을 추가하거나 목록에서 항목을 삭제한 후 주 목록 페이지로 돌아갑니다. 따라서 응용 프로그램이 동일한 HTTP 요청 내에서 상당히 다른 두 명령(추가 또는 삭제)을 실행한 후 동일한 페이지(목록)를 렌더링해야 합니다.
  • 사용자 인터페이스 코드는 비즈니스 논리보다도 더 장치의 영향을 받는 경향이 있습니다. PDA(Personal Digital Assistant) 또는 웹 가능 휴대폰을 지원하기 위해 브라우저 기반 응용 프로그램을 마이그레이션하려는 경우, 사용자 인터페이스 코드는 상당수 교체되어야 하는 반면 비즈니스 논리는 영향을 받지 않을 수 있습니다. 이 두 파트를 명확하게 구분하면 마이그레이션이 촉진되고 비즈니스 논리에 오류가 발생할 위험이 최소화됩니다.
  • 자동화된 사용자 인터페이스용 테스트를 만드는 것은 비즈니스 논리용 테스트를 만드는 것보다 어렵고 많은 시간이 소요됩니다. 따라서 사용자 인터페이스에 직접 연결된 코드의 양을 줄이는 것이 응용 프로그램의 테스트 성능을 향상시킬 수 있습니다.
  • 솔루션MVC(Model-View-Controller) 패턴은 도메인의 모델링과 프레젠테이션 그리고 사용자 입력 기반의 작업을 세 개의 별도 클래스로 구분해 줍니다[Burbeck92].
  • 모델. 모델에서는 응용 프로그램 도메인의 동작(behavior)과 데이터를 관리하고, 대개 뷰로부터의 상태 정보 요청에 응답하고, 대개 컨트롤러로부터의 상태 변경 명령에 응답합니다.
  • . 뷰에서는 정보 표시를 관리합니다.
  • 컨트롤러. 컨트롤러에서는 사용자의 마우스 및 키보드 입력을 해석하여 적절하게 변경하도록 모델과 뷰에서 알려줍니다.그림 1은 세 가지 개체의 구조적 관계를 나타냅니다.

    그림 1: MVC 클래스 구조

    중요한 점은 뷰와 컨트롤러는 모델에 의존하지만 모델은 뷰와 컨트롤러에 의존하지 않는다는 것입니다. 이것이 바로 구분의 가장 큰 이점입니다. 이 구분에서는 시각적 프레젠테이션의 영향을 받지 않으면서 모델을 만들고 테스트할 수 있습니다. 상당수의 리치(rich) 클라이언트 응용 프로그램에서는 뷰와 컨트롤러 사이의 구분이 그다지 중요하지 않으며 실제로 상당수 사용자 인터페이스 프레임워크에서는 두 역할을 하나의 개체로 구현합니다. 반대로 웹 응용 프로그램에서는 뷰(브라우저)와 컨트롤러(HTTP 요청을 처리하는 서버측 구성 요소) 사이의 구분이 명확하게 정의됩니다.

     

    MVC(Model-View-Controller) 는 사용자 인터페이스 논리와 비즈니스 논리를 구분하기 위한 기본적인 설계 패턴입니다. 불행히도 이 패턴의 인기로 여러 잘못된 설명이 나타났습니다. 특히 “컨트롤러”라는 말은 다양한 여러 상황마다 다른 의미를 가지게 되었습니다. 하지만 다행히 웹 응용 프로그램의 등장으로 뷰와 컨트롤러 사이의 구분이 명확해짐에 따라 어느 정도의 모호함이 해결되고 있습니다.

    변형

    Application Programming in Smalltalk-80: How to use Model-View-Controller (MVC)[Burbeck92]에서 Steve Burbeck는 MVC의 두 가지 변형인 수동 모델과 능동 모델에 대해 설명합니다.

     

    수동 모델은 하나의 컨트롤러가 모델을 독자적으로 처리할 때에 사용됩니다. 모델을 수정한 컨트롤러는 해당 모델이 변경되었으며 새로 고쳐야 함을 뷰에 알려줍니다(그림 2 참조). 이 시나리오에서는 모델이 뷰와 컨트롤러로부터 완전히 독립되어 있기 때문에 모델이 상태 변경 사항을 보고할 방법이 없습니다. HTTP 프로토콜이 바로 그 예입니다. 브라우저에는 서버로부터 비동기 업데이트를 받을 수 있는 간단한 방법이 없습니다. 브라우저는 뷰를 표시하고 사용자 입력에 응답하지만 서버의 데이터가 변경되는 것을 감지하지 못합니다. 사용자가 명시적으로 새로 고침을 요청해야만 서버에 변경 사항이 있는지 물어봅니다.

    그림 2: 수동 모델의 동작(behavior)

     

    능동 모델은 컨트롤러의 개입 없이 모델이 상태를 변경할 때에 사용됩니다. 이러한 경우는 다른 소스가 데이터를 변경 중일 때 이 변경 사항을 보기에 반영해야 하는 경우에 발생할 수 있습니다. 증권 시세 표시기를 생각해 보겠습니다. 주식 데이터가 변경되면 외부 소스에서 주식 데이터를 받아 뷰(예: 시세 표시기 및 경보 창)를 업데이트하고 싶습니다. 오직 모델만이 변경된 내부 상태를 감지할 수 있기 때문에 변경이 발생할 경우, 모델은 표시기를 새로 고치도록 뷰에게 통보해야 합니다.

     

    하지만 MVC 패턴을 사용하는 이유 중 하나는 모델이 뷰의 영향을 받지 않기 때문입니다. 모델이 뷰에 변경 사항을 통보해야 한다면 지금까지 피하려고 했던 의존성이 재현되는 것입니다. 다행히도 옵서버 패턴[Gamma95]은 의존성을 재현하지 않고도 상태 변경 사항을 다른 개체에 알려줄 수 있는 메커니즘을 제공합니다. 개별 뷰가 옵서버 인터페이스를 구현하고 모델에 등록합니다. 이 모델은 변경 사항을 구독하는 모든 옵서버의 목록을 추적합니다. 모델이 변경되면 이 모델은 등록된 모든 옵서버를 검색한 후 옵서버에게 변경 사항을 통보합니다. 이 방법을 흔히 “게시-구독”이라고 합니다. 모델에는 뷰에 관한 특정 정보가 전혀 필요 없습니다. 실제로 모델 변경 사항(예: 메뉴 옵션 설정 또는 해제)을 컨트롤러에 알려야 하는 시나리오에서는 모든 컨트롤러가 옵서버 인터페이스를 구현하고 모델 변경 사항을 구독해야 합니다. 많은 뷰가 있는 상황에서는 여러 개의 주제를 정의하고 각 주제로 특정 유형의 모델 변경 사항을 설명하는 것이 좋습니다. 각각의 뷰는 해당 뷰에 관련된 유형의 변경 사항만을 구독할 수 있습니다.

     

    그림 3은 옵서버를 사용하는 능동 MVC의 구조와 모델이 뷰를 직접 참조하지 못하도록 옵서버가 모델을 격리시키는 방법을 보여줍니다.

    그림 3: 능동 모델에서 옵서버를 사용하여 모델과 뷰를 분리

     

    그림 4는 모델이 변경될 때 옵서버가 뷰에 어떻게 변경 사항을 통보하는지 그 방법을 보여줍니다. 불행히도 모델과 뷰의 구분을 UML(Unified Modeling Language) 시퀀스 다이어그램에 적절하게 표시할 수 있는 방법이 없는데 그 이유는 이 다이어그램은 클래스와 인터페이스가 아니라 개체 인스턴스를 표시하기 때문입니다.

    그림 4: 능동 모델의 동작(behavior)

    예제

    ASP.NET에서 MVC(Model-View-Controller 구현)을 참조하십시오.

    테스트 고려 사항

    MVC(Model-View-Controller) 패턴을 사용하면 테스트 성능이 상당히 향상됩니다. 구성 요소가 상호 의존적인 경우 구성 요소(특히 사용자 인터페이스 구성 요소)를 테스트하는 것이 어려워집니다. 이런 구성 요소의 경우는 간단한 기능을 테스트하는 데도 복잡한 설정이 필요합니다. 설상가상으로 오류가 발생한 경우에는 문제를 특정한 구성 요소에 격리시키는 것이 어렵습니다. 바로 이러한 이유 때문에 Architecture를 구성하는 데 있어 구분이 중요해집니다. MVC에서는 데이터의 저장, 표시 및 업데이트 문제를 세 개의 구성 요소로 구분한 후 이 구성 요소를 개별적으로 테스팅할 수 있습니다.

    상호 의존성에 의한 문제는 제외하더라도 사용자 인터페이스 프레임워크의 테스트 자체가 어렵습니다. 사용자 인터페이스를 테스트하기 위해서는 지루한 수작업 테스트(오류가 발생하기 쉬운 테스트)나 사용자 동작을 시뮬레이션하는 테스트 스크립트가 필요합니다. 이 스크립트는 개발에 많은 시간이 소요되며 불안정합니다. MVC가 사용자 인터페이스 테스트의 필요성을 없애주지는 않지만 모델과 프레젠테이션 논리를 구분하기 때문에 프레젠테이션의 영향을 받지 않고 모델을 테스트할 수 있으므로 사용자 인터페이스 테스트 사례의 수가 줄어듭니다.

    결과

    MVC 패턴 주위에 프레젠테이션 레이어를 구성하면 다음과 같은 이점과 단점이 있습니다.

    이점

     

  • 여러 개의 뷰 지원. 뷰와 모델이 구분되어 뷰와 모델이 직접 의존하지 않으므로 사용자 인터페이스에서 동일한 데이터를 여러 개의 뷰로 동시에 표시할 수 있습니다. 예를 들어, 웹 응용 프로그램의 여러 페이지에서 동일한 모델 개체를 사용할 수 있습니다. 또 다른 예는 사용자가 페이지의 모양을 변경할 수 있는 웹 응용 프로그램입니다. 이 페이지는 공유된 모델로부터 동일한 데이터를 표시하지만 다른 방식으로 데이터를 표시합니다.
  • 변경 사항을 수용. 사용자 인터페이스 요구 사항은 비즈니스 규칙보다 더 빨리 변하는 경향이 있습니다. 사용자는 휴대폰 또는 PDA와 같은 신형 장치에 차별화된 색상, 글꼴, 화면 배치 및 지원 수준을 선호할 수 있습니다. 모델은 뷰에 의존하지 않기 때문에 새로운 유형의 뷰를 시스템에 추가하더라도 모델에는 영향을 미치지 않습니다. 따라서 변화의 범위가 뷰로 국한됩니다. 이 패턴은 페이지 컨트롤러 및 프런트 컨트롤러와 같이 이 패턴을 더욱 특수화하기 위한 기반이 됩니다.단점 
  • 복잡성. MVC 패턴에서는 새로운 수준의 간접 참조가 사용하므로 솔루션이 약간 더 복잡해집니다. 또한 사용자 인터페이스 코드의 이벤트 중심적 특성이 강해지므로 디버깅이 더 어려워질 수 있습니다.
  • 빈번한 업데이트의 손실. 모델과 뷰를 분리한다고 해서 모델 개발자가 뷰의 특성을 무시할 수 있다는 의미는 아닙니다. 예를 들어, 모델이 자주 변경되는 경우 뷰에 업데이트 요청이 쇄도할 수 있습니다. 그래픽 디스플레이와 같은 일부 뷰의 경우는 렌더링에 약간의 시간이 소요될 수도 있습니다. 이 결과 뷰가 업데이트 요청을 따라잡지 못할 수 있습니다. 따라서 모델을 코딩할 때도 뷰를 염두에 두는 것이 중요합니다. 예를 들어, 모델은 뷰에 전달되는 단일 통보에 여러 개의 업데이트를 포함할 수 있습니다.변형문서-보기 변형 패턴은 MVC(Model-View-Controller) 의 세 가지 역할을 모드 인식하지만 컨트롤러를 보기에 병합합니다. 문서는 MVC의 모델 역할에 해당합니다. 이 변형 패턴은 기존의 상당수 GUI 플랫폼에 나타납니다. 문서-보기의 가장 좋은 예는 Microsoft Visual C++ 환경의 MFC(Microsoft Foundation Class) 라이브러리입니다. 이 변형 패턴을 사용할 때는 뷰와 컨트롤러가 약간 더 긴밀하게 연결됩니다.

    관련 패턴

    자세한 내용은 다음과 같은 관련 패턴을 참조하십시오.

  • 옵서버. 이 패턴은 뷰와 해당 모델을 동기화시킬 필요성으로 인해 MVC와 관련되어 자주 언급됩니다.
  • 페이지 컨트롤러 및 프런트 컨트롤러는 MVC 패턴의 컨트롤러 부분에 필요한 구현 전략에 대해 설명합니다.참고 자료MVC(Model-View-Controller) 는 1970년대 후반 Trygve Reenskaug가 Smalltalk 플랫폼용으로 개발한 프레임워크로 출발했습니다[Fowler03]. 방금 읽은 이 버전에서는 다음과 같은 문서를 참조합니다.

    [Burbeck92] Burbeck, Steve. “Application Programming in Smalltalk-80: How to use Model-View-Controller (MVC).”University of Illinois in Urbana-Champaign (UIUC) Smalltalk Archive. 다음 웹 사이트에서 구할 수 있습니다: http://st-www.cs.uiuc.edu/users/smarch/st-docs/mvc.html (영문).

    [Fowler03] Fowler, Martin. Patterns of Enterprise Application Architecture. Addison-Wesley, 2003.

    [Gamma95] Gamma, Helm, Johnson 및 Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 1995

 

Drag and drop

http://blog.naver.com/sjhghj/140104051130

DragDemo 프로젝트를 만들고

일단 ListControl를 좌우로 2개 만듭니다..

그리고

public:

 // CListCtrl 클래스의 CreateDragIImage()메소드가 반환한 드래그 이미지의 포인터를 저장

 CImageList * m_pImgListDrag;

 // 왼쪽 리스트 컨트롤에서 드래그 하려고 선택한 항목의 인덱스
 int   m_nIndexLeftSel;

public:
 CListCtrl m_List_Left;  // 왼쪽 리스트 컨트롤러의 변수 

public:
 CListCtrl m_List_Right; // 오른쪽 리스트 컨트롤러의 변수
public:

//CDragDemoDlg
 afx_msg void OnLvnBegindragList1(NMHDR *pNMHDR, LRESULT *pResult);
public:
 afx_msg void OnMouseMove(UINT nFlags, CPoint point);
public:
 afx_msg void OnLButtonUp(UINT nFlags, CPoint point);


CDragDemoDlg::CDragDemoDlg(CWnd* pParent /*=NULL*/)
 : CDialog(CDragDemoDlg::IDD, pParent)
{
 m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);

 // 이 항목들을 다음과 같이 초기화 합니다.

 m_pImgListDrag = NULL;
 m_nIndexLeftSel = -1;

}

OnInitDialog() 함수에 다음과 같이 작성합니다..

 CBitmap Bitmap;
 Bitmap.LoadBitmapW(IDB_ImageList);

 static CImageList ImgList;
 ImgList.Create(32, 32, ILC_COLOR32 | ILC_MASK, 5, 0);
 ImgList.Add(&Bitmap, RGB(0,0,0));//   현재 Left와 Right 리스트 컨트롤 2개가 CImageList 클래스 객체를 공유하여 사용하고 있습니다.
 m_List_Left.SetImageList(&ImgList, LVSIL_NORMAL);
 m_List_Right.SetImageList(&ImgList, LVSIL_NORMAL);

 CString strItem =_T(“”);
 for(int i=0; i<5; ++i)
 {
  strItem.Format(_T(“%dth Item”), i);
  m_List_Left.InsertItem(i, strItem, i);

 }

 DWORD dwExStyle = m_List_Left.GetExtendedStyle();

// 둘다 LVS_EX_BORDERSELECT 확장 스타일을 하고 있습니다.
 m_List_Left.SetExtendedStyle(dwExStyle | LVS_EX_BORDERSELECT);
 dwExStyle = m_List_Right.GetExtendedStyle();
 m_List_Right.SetExtendedStyle(dwExStyle | LVS_EX_BORDERSELECT);

다음은…………

void CDragDemoDlg::OnLvnBegindragList1(NMHDR *pNMHDR, LRESULT *pResult)
{
 LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
 // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
 
 CPoint ptDrag, ptAction;

// 드래그가 시작된 항목의 인덱스가 들어가 있고, WM_LBUTTONUP 메세지가 발생할 때 사용된다
 m_nIndexLeftSel = pNMLV->iItem;

//  CreateDragImage()메소드를 호출하여 새로운 이미지 목록을 만듭니다.

//   이 함수는 내부적으로 new 연산으로 새로운 CImageList 클래스 객체를 생성하기에

// 드래그가 끝날 때 delete 연산을 통해서 객체를 소멸시켜야 합니다.
 m_pImgListDrag = m_List_Left.CreateDragImage(pNMLV->iItem, &ptDrag);

 m_pImgListDrag->SetBkColor(RGB(0,0,0));
 ptAction = pNMLV->ptAction;

 SetCapture();
 m_pImgListDrag->BeginDrag(0,ptAction – ptDrag);
 m_List_Left.ClientToScreen(&ptAction);
 m_pImgListDrag->DragEnter(NULL, ptAction);

 *pResult = 0;
}

다음은………….

void CDragDemoDlg::OnMouseMove(UINT nFlags, CPoint point)
{
 // TODO: 여기에 메시지 처리기 코드를 추가 및/또는 기본값을 호출합니다.
 if(m_pImgListDrag != NULL){
  //좌표를 반환한다
  ClientToScreen(&point);
  // 변환된 좌표가 속한 윈도우 객체의 주소를 구한다
  CWnd* pWnd = CWnd::WindowFromPoint(point);
  // 주소가 대화상자 자체나 자식 윈도우에 해당하는 주소이면 계속 드래그 상태를 유지하도록 한다
  if(pWnd != NULL)
  {
   if(this ==pWnd || IsChild(pWnd))
   {
    
    m_pImgListDrag->DragEnter(NULL, point);
    //DragMove :  인자로 전달받은 좌표에 드래그 이미지를 출력
    m_pImgListDrag->DragMove(point);
   }
   else
   {
    // DragEnter와 반대로 이미지를 지운다
    m_pImgListDrag->DragLeave(NULL);
   }
  }
  //지워진 이미지를 다시 화면에 나오게 하려면 DragEnter()함수를 다시 호출하면 된다

 }

 CDialog::OnMouseMove(nFlags, point);
}

다음은……

void CDragDemoDlg::OnLButtonUp(UINT nFlags, CPoint point)
{
 // TODO: 여기에 메시지 처리기 코드를 추가 및/또는 기본값을 호출합니다.
 CString strTmp = _T(“”);
 if(m_pImgListDrag != NULL)
 {
  ClientToScreen(&point);

  m_pImgListDrag->DragLeave(NULL);

// EndDrag() 함수가 호출되면 관련 객체는 드래그 앤 드랍 동작이 끝났다고 인식하게 됩니다.
  m_pImgListDrag->EndDrag();
  ReleaseCapture();

  //오른쪽 리스트에 아이템 추가

// 드롭 위치에 해당하는 윈도우 객체의 주소를 알아내고 있습니다.

// 만일 이 주소가 오른쪽 리스트 컨트롤이고 왼쪽 리스트 컨트롤에서 선택한 항목이 있다면 GetItem()함수를 통해서 오른쪽 컨트롤 리스트에 항목을 추가하게 됩니다.
  CWnd* pWnd = CWnd::WindowFromPoint(point);
  if(pWnd == &m_List_Right && m_nIndexLeftSel >= 0)
  {
   LVITEM lvItem;
   TCHAR szBuffer[256];
   ::ZeroMemory(&lvItem, sizeof(lvItem));
   ::ZeroMemory(szBuffer, sizeof(szBuffer));

   lvItem.mask = LVIF_TEXT | LVIF_IMAGE;
   lvItem.iItem = m_nIndexLeftSel;
   lvItem.pszText = szBuffer;
   lvItem.cchTextMax = 256;
   m_List_Left.GetItem(&lvItem);
   
   m_List_Right.InsertItem(0, lvItem.pszText,lvItem.iImage);

  }

// 드롭된 위치가 왼쪽 리스트 일 경우
  else
  {
   m_List_Left.ScreenToClient(&point);
   int nIndex = m_List_Left.HitTest(point);

   if(nIndex >=0)
   {
    strTmp.Format(_T(“Drop on %dth item”), nIndex);
    AfxMessageBox(strTmp);
   }
  }
  delete m_pImgListDrag;
  m_pImgListDrag = NULL;
  m_nIndexLeftSel = -1;

  
 }
 CDialog::OnLButtonUp(nFlags, point);
}

[출처] 드래그 앤 드랍(Drag and drop)|작성자 Parker Charles

TinyXML

http://hanburn.tistory.com/6
http://hanburn.tistory.com/7
http://hanburn.tistory.com/8

 

TinyXML은 간단하고 작은 C++ XML 파서 입니다.
사용하기가 편리하고 다른 프로그램으로 이식하기도 수월한 편입니다.

온라인 도움말 : http://www.grinninglizard.com/tinyxmldocs/index.html
소스를 받는곳 : http://sourceforge.net/projects/tinyxml/

설치하려면.. 일단 소스 받는곳에서 소스를 환경에 맞게 받는다.
윈도우 버전으로 받으면 VC++6.0 작업화일과 VS.NET 용 솔루션 파일이 포함되어 있다.

사용하는 방법은 2가지로 나눌수 있다.  ( 그리고 각각 STL을 사용하는 버전과 아닌 버전으로 되어 있다. )

1. 해당 소스를 컴파일 하여 생성되는 Tinyxml.lib와 헤더파일을 이용하는 방법
– 이방법은 제공되는 솔루션 파일( tinyxml.sln) 을 열어서 빌드만 하면 된다.
– 사용하는 프로젝트가 멀티 쓰레드이면 런타임 라이브러리를 멀티 쓰레드로 변경해야 한다.
(기본 설정은 싱글 쓰레드로 되어 있다.
– tinyXml.lib 는 스태틱 라이브러리로 컴파일시에 exe에 포함되므로 따로 배포를 않해도 된다.

2. 소스를 직접 포함시켜서 사용하는 방법.
– 필요한 파일을 프로젝트에 파일 추가로 등록한다.
(tinystr.h(cpp), tinyxml.h(cpp), tinyxmlerror.cpp, tinyxmlparse.cpp 총 6개다 )
– 컴파일을 하려고 하면 precompile header 관련하여 에러가 난다.
– 프로젝트 설정에서 cpp의 (3개 파일) precompile header 사용을 빼버린면 된다.

사용자 삽입 이미지

어떻게 보면 거의 같은 방법이다. ㅎㅎ
개인적으로 2번째 방법을 선호한다. 개발중에 cpp 소스를 가끔 보게 되므로..
그럼 다음 글에서는 간단한 사용법들을 알아보자.

 

XML에 대해서는 이미 알고 있다고 가정합니다.
(모르면 공부하고 다시 오세요 ^^)

일단 XML을 파일에서 읽을수도 있고 문자열로 읽을 수도 있다.
아래 예에서 사용할 xml 파일은 다음과 같다.

<?xml version=”1.0″ ?>
<MyApp>
<!– Settings for MyApp –>
<Messages>
<Welcome>Welcome to MyApp</Welcome>
<Farewell>Thank you for using MyApp</Farewell>
</Messages>
<Windows>
<Window name=”MainFrame” x=”5″ y=”15″ w=”400″ h=”250″ />
</Windows>
<Connection ip=”192.168.0.1″ timeout=”123.456000″ />
</MyApp>

먼저 파일에서 읽을때는,

TiXmlDocument document;
document.LoadFile(_File_Name_);

문자열로 읽을때는,

TiXmlDocument document;
document.Parse(szXML);

너무 쉽다. 그런 다음에는 원하는 값을 찾아오면 된다.
먼저 노드와 엘리먼트를 가지고 오는 방법을 알아보자.
( 노드는 자식을 가지고 있는 것이고 엘리먼트는 마지막에 있는 놈이다. )

TiXmlElement* pRoot = document.FirstChildElement(“MyApp”);
if( NULL == pRoot ) return FALSE;   // 해당 Element가 없으면 널이므로.. 체크해주는게 좋다.

그럼, <Welcome> 태그로 가서 데이터를 가지고 와보자.

pElement = pRoot->FirstChildElement(“Message”);
pElement = pElement->FirstChildElement(“Welcome”);
char* pAA = pElement->Value();                 // pAA 은  “Welcome” 이다.
pAA = pElement->GetText();                       // pAA 은  “Welcome to MyApp” 이다.

이번에는 다음에 windows 태그로 가서 name 속성을 읽어 와보자.

pElement = pRoot->FirstChildElement(“Windows”);
char* pAA = pElement->Attribute(“name”);   // pAA에 “MainFrame”이 들어온다.
int x;
pElement->Attribute(“x”, &x);                    // x 에 숫자 5가 들어온다.

그다음에는 루프를 돌면서 child 노드를 순환하는 방법이다.

pNode = pRoot->FirstChild(“sub_node”);
for( pNode ; pNode ; pNode = pNode->NextSibling())
{
pAA = pNode->Value();

pElement = pNode->FirstChildElement(“item”);
pAA = pElement->GetText();
}

앞에서는 xml 파일이나 xml문자열에서 파싱을 해서 원하는 정보를 뽑아오는 것을 해보았다.
그러면 이제는 반대로 설정이나 원하는 정보를 xml 형식으로 만들어 보자.
앞에서 사용한 xml을 만들어 보도록 하자

<?xml version=”1.0″ ?>
<MyApp>
<!– Settings for MyApp –>
<Messages>
<Welcome>Welcome to MyApp</Welcome>
<Farewell>Thank you for using MyApp</Farewell>
</Messages>
<Windows>
<Window name=”MainFrame” x=”5″ y=”15″ w=”400″ h=”250″ />
</Windows>
<Connection ip=”192.168.0.1″ timeout=”123.456000″ />
</MyApp>

일단 xml 의 형식선언부터 시작해 보자..

TiXmlDocument doc;
TiXmlElement* msg;
TiXmlDeclaration* decl = new TiXmlDeclaration( “1.0”, “”, “” );
doc.LinkEndChild( decl );

이렇게 하면 <?xml version=”1.0″ ?> 이 생성된다.
다음으로는, 서브 노드를 추가해보자.

TiXmlElement * root = new TiXmlElement( “MyApp” );
doc.LinkEndChild( root );

간단하다. 그리고 new로 생성한 TiXmlElement를 해제할 필요가 없다. 내부에서 자동으로 삭제를 해주기 때문이다. (편리하군~ ^^)
그런 다음 주석문장을 추가해 보자.

TiXmlComment * comment = new TiXmlComment();
comment->SetValue(” Settings for MyApp ” );
root->LinkEndChild( comment );

주석은 TiXmlComment 클래스를 사용하면 된다. 편리하군..
다음으로는  Message 서브노드와 하위 노드및 데이터를 추가해보자

TiXmlElement * msgs = new TiXmlElement( “Messages” );
root->LinkEndChild( msgs );

msg = new TiXmlElement( “Welcome” );
msg->LinkEndChild( new TiXmlText( “Welcome to MyApp” ));
msgs->LinkEndChild( msg );

msg = new TiXmlElement( “Farewell” );
msg->LinkEndChild( new TiXmlText( “Thank you for using MyApp” ));
msgs->LinkEndChild( msg );

역시 쉽니다. 다음에는 노드를 추가하고 Attribute를 설정해보자
레벨을 맞추기 위해서 root의 하위로 추가 한것을 주의 깊게 봐야 한다.

TiXmlElement * windows = new TiXmlElement( “Windows” );
root->LinkEndChild( windows );

TiXmlElement * window;
window = new TiXmlElement( “Window” );
windows->LinkEndChild( window );
window->SetAttribute(“name”, “MainFrame”);
window->SetAttribute(“x”, 5);
window->SetAttribute(“y”, 15);
window->SetAttribute(“w”, 400);
window->SetAttribute(“h”, 250);

다음은 마지막으로 Double 값 (소수점 값) 을 설정하는 예를 보자

TiXmlElement * cxn = new TiXmlElement( “Connection” );
root->LinkEndChild( cxn );
cxn->SetAttribute(“ip”, “192.168.0.1”);
cxn->SetDoubleAttribute(“timeout”, 123.456); // floating point attrib

이렇게 하면 우리가 원하는 xml이 doc에 만들어 졌다. 이것을 파일로 저장할 수도 있고 xml 문자열로 만들수도 있다. 방법은 아래를..

// 파일로 저장
doc.SaveFile(“text.xml”);

//문자열로..
TiXmlPrinter printer;
printer.SetStreamPrinting();
Doc.Accept( &printer );
char* pAA = printer.CStr();                    // char* 를 반환한다.
std::string str = printer.Str();                  // std::string으로 반환한다.

이로써 간단하게 TinyXML의 사용법을 마칠까 한다.

보색(Complementary color) 변환 (RGB/HSL 변환)

http://www.devpia.com/Maeul/Contents/Detail.aspx?BoardID=51&MAEULNO=20&no=7249&page=45

보색이란 색상휠에서 색조(hue) 값이 180도 반대방향에 위치한다고 합니다.
(어렴풋 예전 미술 시간에 배웠던 것도 같습니다.)
즉 RGB를 HSL값으로 변환한 후 HSL에서 색조(hue)를 180도 돌려준 후 이 HSL 값을 RGB 값으로 다시 변환하면 됩니다.

– 보색 구하는 함수

[code language=”cpp”]
//
// Calculate complementary color
//
// @param clr : [in] COLORREF, input RGB color from which
// complementary color is calculated
//
// @return RGB color value of complementary color of input RGB color

COLORREF CalcComplemental(COLORREF clr)
{
long hsl_h = 0, hsl_s = 0, hsl_l = 0;
RGB2HSL_(clr, hsl_h, hsl_s, hsl_l); // CONV RGB to HSL
hsl_h += 0x200; // HUE, ADD 180 deg (0x200 = 0x400 / 2)
hsl_h &= 0x7ff; // HUE, MOD 360 deg to HUE
return HSL2RGB_(hsl_h, hsl_s, hsl_l); // CONV HSL to RGB
}
[/code]

– RGB 에서 HSL 변환하는 함수

[code language=”cpp”]
//
// RGB to HSL conversion
//
// @param clr : [in] COLORREF, input RGB value to be converted
// @param hue : [out] double &, converted hue value (0 ~ 2*PI)
// @param saturation : [out] double &, converted saturation value (0%~ 100%)
// @param lightness : [out] double &, converted lightness value (0% ~ 100%)
void RGB2HSL( COLORREF clr,
double & hue,
double & saturation,
double & lightness)
{
long hsl_h = 0, hsl_s = 0, hsl_l = 0;
RGB2HSL_(clr, hsl_h, hsl_s, hsl_l);

// long to double
hue = 2.0 * 3.141592 * (double)hsl_h / 1024.0;
saturation = (double)hsl_s / 1024.0;
lightness = (double)hsl_l / 1024.0;
}
[/code]

– HSL 에서 RGB 변환하는 함수

[code language=”cpp”]
//
// HSL to RGB conversion
//
// @param hue : [in] double, input hue value (0 ~ 2*PI)
// @param saturation : [in] double, input saturation value (0% ~ 100%)
// @param lightnelss : [in] double, input lightness value (0% ~ 100%)
//
// @return converted RGB color value from the input HSL value
COLORREF HSL2RGB(double hue, double saturation, double lightness)
{
// double to long
const long hsl_h = (long)(hue / 2.0 / 3.141592 * 1024.0);
const long hsl_s = (long)(saturation * 1024.0);
const long hsl_l = (long)(lightness * 1024.0);

return HSL2RGB_(hsl_h, hsl_s, hsl_l);
}
[/code]

– 옵티마이징

[code language=”cpp”]
//
// Integer version with 2^10(1024) resolution
//
// @param hsl_h : [out] long &, hue, 0 ~ 1024
// @param hsl_s : [out] long &, saturation, 0 ~ 1024
// @param hsl_l : [out] long &, lightness, 0 ~ 1024
void RGB2HSL_(COLORREF clr, long & hsl_h, long & hsl_s, long & hsl_l)
{
const long var_r = GetRValue(clr) << 2;
const long var_g = GetGValue(clr) << 2;
const long var_b = GetBValue(clr) << 2;

const long var_min = min(var_r, min(var_g, var_b));
const long var_max = max(var_r, max(var_g, var_b));
const long del_max = var_max – var_min;

hsl_l = (var_max + var_min) >> 1;

if(del_max == 0)
{
hsl_h = hsl_s = 0;
}else
{
if(hsl_l < 512)
hsl_s = (del_max << 10) / (var_max + var_min);
else
hsl_s = (del_max << 10) / (2048 – var_max – var_min);
}

const long temp = (var_max << 9) / 3 / del_max + (del_max << 9) / del_max;

const long del_r = temp – ((var_r << 9) / 3) / del_max;
const long del_g = temp – ((var_g << 9) / 3) / del_max;
const long del_b = temp – ((var_b << 9) / 3) / del_max;

if(var_r == var_max)
hsl_h = del_b – del_g;
else if(var_g == var_max)
hsl_h = (1024 / 3) + del_r – del_b;
else if(var_b == var_max)
hsl_h = (2048 / 3) + del_g – del_r;

if(hsl_h < 0)
hsl_h += 1024;
else if(hsl_h > 1024)
hsl_h -= 1024;
}

//
// Integer version with 2^10(1024) resolution
//
// @param hsl_h : [in] long, hue, 0 ~ 1024
// @param hsl_s : [in] long, saturation, 0 ~ 1024
// @param hsl_l : [in] long, lightness, 0 ~ 1024
COLORREF HSL2RGB_(long hsl_h, long hsl_s, long hsl_l)
{
BYTE clrR = 0, clrG = 0, clrB = 0;
long var_1 = 0, var_2 = 0;

const long var_h = hsl_h;
const long var_s = hsl_s;
const long var_l = hsl_l;

if(hsl_s == 0)
clrR = clrG = clrB = (BYTE)(var_l >> 2);
else
{
if(var_l < 512)
var_2 = var_l + ((var_l * var_s) >> 10);
else
var_2 = var_l + var_s – ((var_l * var_s) >> 10);

var_1 = (var_l << 1) – var_2;

clrR = (BYTE)(HUE2RGB_(var_1, var_2, var_h + (1024 / 3)) >> 2);
clrG = (BYTE)(HUE2RGB_(var_1, var_2, var_h) >> 2);
clrB = (BYTE)(HUE2RGB_(var_1, var_2, var_h – (1024 / 3)) >> 2);
}

return RGB(clrR, clrG, clrB);
}

long HUE2RGB_(long var_1, long var_2, long var_h)
{
if(var_h < 0)
var_h += 1024;
else if(var_h > 1024)
var_h -= 1024;

const long temp1 = 3 * var_h;

if(temp1 < 512)
return (var_1 + (((3 * var_2 * var_h) >> 9) –
((3 * var_1 * var_h) >> 9)));
else if(var_h < 512)
return var_2;
else if(temp1 < 2048)
{
const long temp2 = 4096 – (temp1 << 1);
return (var_1 + ((var_2 * temp2 – var_1 * temp2) >> 10));
}

return var_1;
}
[/code]

Rendering in Linear Color Space

http://juhlnet.egloos.com/m/1960058

# sRGB 컬러 공간

a0008416_4add15a95c626
우리가 사용하는 대부분의 디지털 이미지 장비들(디카, 스캐너, 모니터, 프린터) 은 표준 non-linear 색 공간인 sRGB 를
사용하도록 정해져있다. 따라서 사진이나 그림파일들도 그에 맞추어진 형태로 저장된다.
보통 이미지를 합성(필터링, 블렌딩, 쉐이딩,..) 할때 우리는 색공간이 linear 하다고 생각하지만, 실제 저장된 파일은
sRGB 에 맞춰져 있기 때문에 연산 전에 먼저 linearize 해줘야 정확한 컬러 연산이 가능해진다고 할 수 있다. 모니터 역시
sRGB 에 맞춰져 있기 때문에 연산 후 sRGB 색공간으로 변환해줘야 한다.
변환 수식은 아래와 같다. (gamma 2.2 에 근접)

[latex]\begin{bmatrix}
R_\mathrm{linear}\\G_\mathrm{linear}\\B_\mathrm{linear}\end{bmatrix}=
\begin{bmatrix}
3.2406&-1.5372&-0.4986\\
-0.9689&1.8758&0.0415\\
0.0557&-0.2040&1.0570
\end{bmatrix}
\begin{bmatrix}
X \\
Y \\
Z \end{bmatrix}[/latex]
[latex]C_\mathrm{srgb}=\begin{cases}
12.92C_\mathrm{linear}, & C_\mathrm{linear} \le 0.0031308\\
(1+a)C_\mathrm{linear}^{1/2.4}-a, & C_\mathrm{linear} > 0.0031308
\end{cases}
[/latex]*where [latex]a = 0.055[/latex]

[latex]C_\mathrm{linear}=\begin{cases}\frac{C_\mathrm{srgb}}{12.92}, & C_\mathrm{srgb}\le0.04045\\
\left(\frac{C_\mathrm{srgb}+a}{1+a}\right)^{2.4}, & C_\mathrm{srgb}>0.04045
\end{cases}
[/latex]
[latex]\begin{bmatrix}

X\\Y\\Z\end{bmatrix}=
\begin{bmatrix}
0.4124&0.3576&0.1805\\
0.2126&0.7152&0.0722\\
0.0193&0.1192&0.9505
\end{bmatrix}
\begin{bmatrix}
R_\mathrm{linear}\\
G_\mathrm{linear}\\
B_\mathrm{linear}\end{bmatrix}
[/latex]

대부분의 이미지 프로세싱 유틸리티나 게임들은 이를 무시하고 구현하고 있다. 자신이 작업한 텍스쳐가 실제 엔진에서
(포스트 프로세싱 끄고) 왠지 모르게 칙칙하고 어둡게 느껴진다면?… 100% 임다 -_-;
# sRGB 읽기
텍셀을 fetch 할 때 sRGB -> linear 변환
DX10 카드에서는 텍스쳐를 GPU 쪽으로 로딩할때 linear space 로 자동 변환되게 할 수 있다.  (변환될 때 색 손실을 막기
위해 채널 당 8bit 이미지가 내부적으로 12bit 나 16bit 로 저장된다고 한다) – 로딩 타임에 변환되므로 텍스쳐 필터링도
linear space 에서 하게 된다.
DX9 카드에서도 지원하지만 로딩 타임이 아닌 쉐이더에서 텍셀을 fetch 할때 linear space 로 변환되게 된다. –
필터링은 non-linear space 에서 하게 되므로 미묘하게 색이 변하게 된다.
주의. 컬러 이미지 외에 선형적인 데이터(노멀맵, 스페큘러맵, 하이트맵,…) 들은 이미 linear space 에 있기 때문에
따로 처리할 필요는 없다. – 생각해보니 스페큘러맵이 linear data 라고는 할 수 없겠다..
# sRGB 쓰기 & 블렌딩 쓰기
프레임 버퍼에 write 할 때 linear -> sRGB 변환
블렌딩 모드라면 프레임 버퍼에서 src 를 가져올때 sRGB -> linear 변환한 결과를 블렌딩 후 다시 sRGB 로 변환
DX10 이상의 카드에서 하드웨어적으로 지원한다.
지원하지 않는다면 픽셀 쉐이더에서 직접 처리해 줄 수 있다. – 하지만 블렌딩 할때 정확한 연산이 이루어지지 않는다.
(먼저 sRGB 프레임 버퍼를 읽어와서 linearize 해야 하므로..)
주의. 중간 단계 맵 (굴절맵, 반사맵, 거울맵, …) 들은 다시 linear space 에서 사용될 것이므로 linearity 를 유지 한다.
# 적용
첫번째 스크린샷이 linear space 쉐이딩, 두번째가 일반적인 non-linear space 쉐이딩
첫번째 스크린샷 보다 두번째 스크린샷의 텍스쳐가 좀 더 칙칙하고 어둡게 나오고, 스펙큘러 하이라이트 부분은 더 강렬하게
나온다. 라이트 세기를 밝게할 수록 더 확연하게 차이가 나는 걸 볼 수 있었다.
1024
1024
원본 디퓨즈 텍스쳐
512

참고 자료:

D3D 디바이스 소실 처리

http://www.gpgstudy.com/forum/viewtopic.php?topic=1451

디바이스 소실의 두가지 경우

1. 디바이스 소실되었고 복구 불가능의 경우

: 이 경우에는 대책이 없다. 전부(IDirect3DDevice를 통해 생성된 모든 리소스들과 IDirect3DDeivce객체) 해제한 후 처음부터 다시 생성한다.

2. 디바이스 소실되었고 복구 가능의 경우

: 이 경우에는 디바이스에 올라간 리소스(리소스 생성시 D3DPOOL_DEFAULT 플래그를 이용하여 생성된 객체들)들만 새로 로딩하면 된다.

특이사항

디바이스가 소실되어도 IDirect3DDevice관련 함수들이 성공을 반환 할 경우가 있다.

도움말에 보면 “디바이스가 소실되어도 동작은 보증된다”고 되어있다. 물론 이때의 동작은 헛동작이다. 아무 효과가 없는것이다. 왜 이런식인가 생각해보니, 밑의 글을 읽어보면 알겠지만,, 언제 어떻게 디바이스 손실이 발생할지 모르니 프로그래머로써는 이런 상황을 대처 할 수 없는것이다. 디바이스와 관련된 모든 함수들에 대해서, 리턴값을 체크하지 않는한 불가능하다는 얘기이다. 그리고 손실된 디바이스 자원이 언제 넘어오게 될지도 모르는 상황에서 이런 처리는 너무 고난이도일껏 같다. 차라리 처리되는것 처럼 보이고, 실제로는 처리가 안되게 하는것이 훨씬 이득일껏 같다. 어디까지 제 추측입니다. 틀렸어도 양해를..

왜 이딴게 있는걸까?

예를들면, 2개의 응용프로그램 A와 B가 돌아가는 상황에서, A란 놈과 B란 놈 둘다 비디오 램을 많이 사용하는 프로그램이었다 치자. 이 상황에서 A란 프로그램이 돌아가고 있는 상황이어서 비디오 램의 여유분이 없었다 치자, 자.. 이제 문제의 B가 실행되어 졌을 경우,, 어떻게 해야할까? Win32는 멀티 태스킹 환경이다. 고로 여러 응용프로그램들이 같은 하드웨어 자원을 공유한다. 즉, DOS처럼 하나의 응용프로그램이 하드웨어 자원을 독점하면 안된다는 것이다. 고로 윈도우즈가 택한방식이 활성화 애플리케이션에게 자원을 넘겨주자는 방식이다. A가 활성화되면, A에게 자원을 주고, B가 활성화되면 B에게 자원을 준다. 그러므로 사용자는 두 응용프로그램을 동시에 사용할 수 있는것이다. 덕분에 프로그래머는 귀찮지만은… 어디까지 제 추측입니다. 틀렸어도 양해를.. 아니지 지적을..

디바이스 소실 처리

IDirect3DDevice::TestCooperativeLevel()을 호출하여, 디바이스를 복구 할 수 있는지 검사해본다. 반환값(HRESULT)이

D3DERR_DEVICELOST인 경우 디바이스가 소실되었고 아직 복구할 수 없는 상태이다. 복구할 수 있을때까지 대기한다.
D3DERR_DEVICENOTRESET인 경우 디바이스가 소실되지만 지금 복구할 수 있는 상태이다. 디바이스를 복구한다.
디바이스 복구

. D3DPOOL_DEFAULT로 잡은 리소스들을 전부 릴리즈한다. %%%(안그러면 다음에 호출될 IDirect3DDevice::Reset()이 실패할것이다.)
. IDirect3DDevice::Reset()을 호출한다. %%%(Reset()을 그냥 IDirect3DDevice의 복구명령어라 생각하면 이해가 편함)
. 디바이스를 다시 셋팅한다.(랜더스테이트, 뷰 행렬등..)
. D3DPOOL_DEFAULT로 잡은 리소스들을 다시 로드한다.
Adapter나 D3DDEVTYPE이 변경되어야 하는 경우에는 디바이스를 다시 생성해야 한다.

. 모든 리소스들을 릴리즈한다.
. 디바이스를 릴리즈한다.
. 디바이스를 새로 생성한다.
. 디바이스를 다시 셋팅한다.(랜더스테이트, 뷰 행렬등..)
. 모든 리소스들을 다시 로드한다.
디바이스가 소실되는경우

풀스크린 모드일때..

다른 애플리케이션이 디바이스를 점유하는 경우.
애플리케이션이 작업표시줄로 최소화 되는 경우.
다른 애플리케이션이 풀스크린 모드로 생성될경우.
전력관리 이벤트(WM_POWERBROADCAST)가 발생했을 경우.
윈도우 모드일때..

전력관리 이벤트가 발생했을 경우.
다른 애플리케이션이 풀스크린 모드로 생성될경우.. (이경우 제 카드에서는 문제가 없었음)
디스플레이 등록정보에서 색상모드를 변경하는 경우.
테스트 사항

카드 : Radeon 9000 128M, OS : Win 2000

A,B : D3D를 사용하는 애플리케이션

. A:win, B:win ==> A:full, B:win // (A를 풀스크린으로 전환) %%%-A:ok, B:ok
. A:full, B:win ===> A:win B:win // (A를 윈도우모드로 전환) %%%- A:ok, B:lost(고칠수 있음)
. A:full, B:win ===> A:full(minimize), B:win // (마우스로 바탕화면을 찍거나 Alt+Tab) %%%- A:lost(고칠수 없음), B:lost(고칠수 없음)
. A:full(lost), B:win ===> A:full(lost), B:full // (B를 풀스크린으로 전환) %%%- A:lost상태 유지, B:ok (A는 시작하기 전에 이미 lost상태였음)
. A:full(lost), B:full ===> A:full(lost), B:full(lost) // (바탕화면 찍음) %%%- A:(lost상태유지), B:lost(고칠수 없음) (A는 시작전에 이미 lost상태였음)
. A:full, B:win ===> A:종료, B:win(lost) // (A를 종료) %%%- A:종료됨, B:lost(고칠수 있음)
X. 기타 등등, 여러 상황에 따라 디바이스 로스트가 발생 된다. 이런 상황이 더 있으나 해결책을 어느정도 찾은것 같으므로 더 이상은 생략.

X. 그리고 다른 그래픽 카드의경우는 어떻게 처리될지 잘모르겠음.

결론

1. Present()함수의 리턴값은 항상 체크해야 한다. 위와 같이 언제 디바이스 손실이 발생 할지 모른다.

2. 임의로 체크해 보고 싶은 경우. TestCooperativeLevel()함수를 이용하자.

방법론

필수사항 : Present()리턴값은 항상 반드시 꼭 체크하자. 언제 어떻게 어떤 이유로 Present()가 실패할지 모른다. 이건 필수이다.

그리고 Present()가 실패하면 위와같은 방법으로 복구하면 될꺼 같다.

0. Present()함수 실패시만 복원한다.

단점으로는 에러를 알아내는 시점이 Present()가 호출될때(뷰포트를 하나 생성한 경우라면 한프레임에 한번씩)뿐 이라는 것이다. 이 단점은 랜더링 결과물에 접근하여, 그것이 애플리케이션 로직에 적용되는 경우라면 치명적일 수 있다고 예상된다. 그점을 제외하면 가장 무난할 껏 같다.

1. CD3DApplication(Direct3D Wizzard에서 제공)의 방식

이 클래스를 보면,, 꽤나 복잡한 방식(주로 윈도우 메세지를 핸들링한다.)으로 처리 하는데(즉, 디바이스 손실이 일어나기전에 미치 처리함)으로 해결하고 있다. 꼭 이렇게 까지 해야 하는지는 잘모르겠지만… 요는 여러가지 상황에 미리 대처한다는 방식이다. 위의 <테스트 사항>같은 짓을 많이 시도해보고, 어느때 디바이스가 손실되는지 정확하게 알아낸 후, 미리알 수 있는경우(풀스크린에서 Alt+tab에 의해 갑자기 비활성화 시킨다든지 등등) 할수 있는데까지 까지 하는것이다. 그런 눈물겨운 노력이 CD3DApplication에 보면 잘 나온다. 이런건 보고 베끼자…^^ 이경우 100% 문제를 해결했다고 볼 수는 없다고 생각되지만 0번의 경우보단 낫다. 이런 문제를 봐선 Direct3D의 설계의 결함같기도 하다. 결국 이런 문제들을 100% 해결 할 수는 없다고 생각된다.

결과 : 실제로 1번과 같이 대부분 예측되는 치명 적인 몇몇 가지들에 대해 미리 알아내서 처리해주면,, 거의 문제는 안생긴다. 대신 코딩이 지저분해지는건 어쩔수 없다.

stub code

void RenderAll()
{
// …
// 모든 그래픽들에 대한 랜더링을 한다.
// …

// 백버퍼 플리핑.
hr = m_pDevice->Present( NULL, NULL, NULL, NULL );
if ( FAILED(hr) )
{
if ( D3DERR_DEVICELOST == hr )
{
// 디바이스를 소실했고, 복구가 불가능 상태
}
else ( D3DERR_DEVICENOTRESET == hr )
{
// 디바이스를 소실했지만, 복구가 가능한경우.
}
}
}
설계

한가지 방법은 랜더러 클래스(또는 이런 이벤트를 처리할 객체)에 두가지상황(디바이스 손실시 복구가능/복구불가능)이 발생했을때, 로딩할 목록을 등록해 놓은 후, 이벤트가 발생하면 각각 해당 이벤트에 맞게끔 다시 로드하는 식으로, 구성하면 될 껏 같다.

//**** 디바이스 리소스 인터페이스..
// 디바이스 의존적인 리소스들의 추상클래스.
struct IDeviceRes
{
virtual void Invalidate() = 0;
virtual bool SetValidate() = 0;
};

//**** 텍스쳐 클래스
class CTextureRes : public IDeviceRes
{
public:
virtual void Invalidate() { 텍스쳐 해제; }
virtual bool SetValidate() { 텍스쳐 다시 로드; }

bool Load();
void Unload();

protected:
char m_szFileName[256];
LPDIRECT3DTEXTURE9 m_pTexture;
};

//**** 텍스쳐를 관리할 클래스(클라이언트에서 접근하여 사용)
class CDataContainer
{
public:
LoadTexture( const char* szFileName )
{
// 1. 텍스쳐 로딩
// 2. 만약 텍스쳐가 VRam에 잡혔다면..(D3DPOOL_DEFAULT)
// 2.1 랜더러의 의 VRam리스트에 등록(m_pRenderer->m_VRamRes)
// 3. 텍스쳐가 다른모드로 생성되었다면..(D3DPOOL_MANAGED or etc..)
// 3.1 랜더러의 의 Managed리스트에 등록(m_pRenderer->m_ManagedRes)
}

UnloadTexture( const char* szFileName )
{
// 랜더러의 디바이스 목록에서 지운다.
m_pRenderer->Unregist( szFileName );
}

protected:
vector m_TextureContainer;
CRenderer* m_pRenderer;
}

//**** 문제의 랜더러 클래스
class CRenderer
{
public:
// 디바이스가 해제 됬거나 해제 하라.
// – 이 함수는 외부에서 임의로 호출이 가능하다.
void InvalidateDevice( HRESULT hrDeviceState )
{
if ( FAILED( hrDeviceState )
{
if ( D3DERR_DEVICENOTRESET == hrDeviceState )
{
// 1. m_VRamRes 리소스만 해제한다.
// for_each(m_VRamRes의 시작에서 끝까지) { m_VRamRes[i]->Invalidate(); }
}
else if ( D3DERR_DEVICELOST == hrDeviceState )
{
// 1. m_VRamRes과 m_ManagedRes들에 대해서 해제한다.(전 리소스들에 대해 수행)
// for_each(m_VRamRes의 시작에서 끝까지) { m_VRamRes[i]->Invalidate(); }
// for_each(m_ManagedRes의 시작에서 끝가지) { m_ManagedRes[i]->Invalidate(); }

// 2. 디바이스를 릴리즈한다.
}
}
m_hrDeviceState = hrDeviceState; // 디바이스 상태값 업데이트
}

// 디바이스가 활성화하라.
// – 이 함수도 외부에서 임의로 호출이 가능하다.
bool SetValidateDevice()
{
if ( FAILED(m_hrDeviceState) )
{
if ( D3DERR_DEVICENOTRESET == m_hrDeviceState )
{
//**** 디바이스를 복구할수 있다.
// 1. IDrect3DDevice::Reset()한다.
// 2. 랜더 스테이트 / 뷰행렬 셋팅
// 3. m_VRamRes 리소스들을 다시 로딩한다.
// for_each(m_VRamRes의 시작에서 끝까지) { m_VRamRes[i]->SetValidate(); }
}
else if ( D3DERR_DEVICELOST == m_hrDeviceState )
{
//**** m_VRamRes 복구할수 없다.
// 1. 디바이스를 재생성한다.
// 2. 랜더 스테이트 / 뷰행렬 셋팅
// 3. m_VRamRes / m_ManagedRes 리소스를 로딩한다.
// for_each(m_VRamRes의 시작에서 끝까지) { m_VRamRes[i]->SetValidate(); }
// for_each(m_ManagedRes의 시작에서 끝가지) { m_ManagedRes[i]->SetValidate(); }
}

// 검증해본다.
m_hrDeviceState = m_pDevice->TestCooperativeLevel();
return SUCCEEDED(m_hrDeviceState);
}
return true;
}

// 랜더링한다.
void RenderAll()
{
//**** 디바이스가 유효한 상태가 아니라면 그냥 리턴.
if ( FAILED(m_hrDeviceState) ) return;

// …
//**** 모든 그래픽들에 대한 랜더링을 한다.
// …

//**** 백버퍼 플리핑.
m_hrDeviceState = m_pDevice->Present( NULL, NULL, NULL, NULL );
if ( FAILED(m_hrDeviceState) )
{
// 디바이스에 뭔가 이상이 있다면,,
InvalidateDevice( m_hrDeviceState );

// 외부에 알려준다. 아마 누군가에게 알려줄 필요가 있을껏이다.
// CApp나 혹은 다른 누군가에게..
}
}

protected:
//**** properties
// 디바이스 의존적인 메모리 관리
vector m_VRamRes; // POOL_DEFAULT
vector m_ManagedRes; // MANAGED or etc
HRESULT m_hrDeviceState; // 현재의 디바이스 상태값

LPDIRECT3DDEVICE9 m_pDevice;
};
추신

괜찮은 방법이나, 사용되어지는 방법이 있으면 올려주세요. 저도 구현은 안해본 사람입니다. 아마 틀린 부분도 있으리라 생각됩니다. 많은 지적을.. 써놓고 보니 장문이네요.

< 덧글 >

그리고 업데이트 내용입니다. gpgstudy.com의 손님께서 쓰신글의 요약입니다.

TestCooperativeLevel()로 D3DERR_DEVICENOTRESET임을 확인한후 Reset()을 했을때 실패할 수도 있다는군요.

나중에 TestCooperativeLevel()로 확인해 보니 D3DERR_DEVICELOST였다고 합니다. 결국 Reset()이 실패했을경우에도 다시 체크를 한후 소실상태라면 디바이스를 다시 생성해야 할껏같습니다.