게임학원, 게임프로그래머 취업 전문 교육기관 DirectX11/12 자체엔진 게임개발과정,서버프로그래밍,자료구조,알고리즘,유니티,언리얼 게임학원, 언리얼학원 비주얼 스튜디오 디버깅 비주얼라이저에 대하여
본문 바로가기

비주얼 스튜디오 디버깅 비주얼라이저에 대하여

이글을 제대로 이해하기 위해서는 선행적으로 c++기초문법, 조사식, stl, xml, 템플릿에 대한 지식이 있어야 합니다. 또한 이 글은 비주얼스튜디오 2017을 기준으로 합니다.

 

이글은 비주얼 스튜디오를 좀더 잘 이용할 수 있는 방법인 비주얼 스튜디오 비주얼라이저 .natvis 파일에 대해서 설명합니다.

일반적으로 프로그래밍 경험이 많거나 어떤 게임이건 만들 정도의 있는 실력이 되는 분은 https://docs.microsoft.com/ko-kr/visualstudio/debugger/create-custom-views-of-native-objects?view=vs-2015 의 문서를 보시는 더 도움이 될 수 있습니다.

 

 

비주얼 스튜디오 Natvis파일 사용법

학생들에게 항상 강조하는 것이 있습니다. 그것은 비주얼 스튜디오를 잘 다루는 사람이 되라는 것입니다. 디자이너가 포토샵이나 3d맥스 같은 툴을 잘 다루는 것이 일반적이고 기획자가 엑셀과 워드 등등 문서작성에 도움이 되는 프로그램을 잘 다루는 것이 도움이 되는 것 이상으로 프로그래머는 자신이 사용하는 IDE를 잘 다루어야 한다고 생각합니다.

그 중에서도 비주얼 스튜디오는 강력한 디버깅기능과 코딩을 위한 다양한 편의기능을 내장하고 있지만 프로그래머가 그것을 100%활용하기 위해서는 역시나 프로그래밍 언어를 공부하는 것처럼 비주얼 스튜디오를 공부하고 그것을 통해서 재미와 기쁨을 느껴야 합니다.

그럼 .natvis파일과 비주얼라이저가 어떤 일을 하는 기능인지 알아보도록 하겠습니다.

일단 저는 비주얼 스튜디오에서 stl을 사용하다 보면 몇가지 의문점이 들 때가 있습니다. std::vector 같은 클래스들은 제가 만드는 배열이나 기본적인 클래스와는 전혀 다르게 비주얼스튜디오 디버깅 환경에서 뷰를 보여줍니다. 분명 벡터도 근본적으로 내부에 정적배열이 아닌 동적배열을 통해서 구현이 될 것이고 그를 위해서 내부에는 포인터가 들어있어야 하지만 실제로는 정적배열처럼 디버깅환경에서 보이게 됩니다.

여기까지 보면 무슨말이지??? 라는 생각이 들수 있으니 다음과 같은 예를 들어 설명해 보겠습니다. 다음과 같은 코드가 있다고 했을 때. 각각의 디버깅시 정보를 확인해보겠습니다.

 

1.     벡터의 경우.

벡터의 경우 마치 배열처럼 내부에 있는 정보들을 제대로 보여주고 있습니다. SizeCapacity등의 중요 정보를 아주 적절하게 필요한 만큼만 보여줍니다.

 2.     정적 배열의 경우

 정적 배열의 경우 클래스는 아니기 때문에 Size 등의 맴버변수 등은 없지만 내부 원소들을 잘 볼 수가 있습니다. 어느정도 양호하다고 할 수 있습니다.

 3.     동적할당 배열의 경우

포인터로 인식되기 때문에 내부의 내용물은 배열이지만 그 내용의 첫번째를 제외하고는 볼 수가 없습니다. 물론 조사식을 이용해서 다음과 같이 볼 수 있지만 매번 이런식으로 설정해주는 것은 여간 귀찮은 일이 아닙니다.

그렇다면 벡터 내부나 아니면 내가 직접만든 자료구조의 내부에서도 포인터의 배열을 사용한다면 위의 포인터 배열과 다르지 않게 사용할 때 어떻게 하면 편하게 디버깅을 할 수 있을지 알아보도록 하겠습니다.

 

#pragma once

#include <assert.h>

 

template<typename T>

class HARR

{

private:

        unsigned int m_Size;

        T* Data;

 

public:

        T& operator[](unsigned int _Index)

        {

               if (m_Size <= _Index)

               {

                       assert(false);

               }

 

               return Data[_Index];

        }

 

        HARR& operator=(const HARR& _Other)

        {

               if (m_Size != _Other.m_Size)

               {

                       Clear();

                       m_Size = _Other.m_Size;

                       Data = new T[_Other.m_Size];

               }

 

               for (unsigned int i = 0; i < _Other.m_Size; i++)

               {

                       Data[i] = _Other.Data[i];

               }

 

               return *this;

        }

 

public:

        int Size() const

        {

               return m_Size;

        }

 

        void Size(int _Value)

        {

               Clear();

               m_Size = _Value;

               Data = new T[_Value]();

               return;

        }

 

        void Clear()

        {

               if (nullptr != Data)

               {

                       delete[] Data;

                       Data = nullptr;

                       m_Size = 0;

               }

        }

 

public:

        HARR() : m_Size(0), Data(nullptr)

        {

        }

 

        HARR(const HARR& _Other)

        {

               Size(_Other.m_Size);

               for (unsigned int i = 0; i < _Other.m_Size; i++)

               {

                       Data[i] = _Other.Data[i];

               }

        }

 

        HARR(int _Size) : m_Size(0), Data(nullptr)

        {

               Size(_Size);

        }

 

        ~HARR()

        {

               Clear();

        }

};

 

자다음과 같은 본인지 직접 만든 배열 클래스가 있고 그것을 다음과 같이 사용해보겠습니다.

 

디버깅시 이 클래스를 살펴보겠습니다.

내부를 확인해도 std::vector처럼 사이즈는 확인할 수 있지만 뭔가 다르고 당연히 가장 중요한 동적배열의 내용은 가장 첫번째 자료를 제외하고는 확인이 불가능 합니다. 하지만 이번 글에서 설명하려는 .natvis를 적용하고 이 배열을 살펴보면 다음과 같이 마지 std::vector와 같은 디버깅정보를 볼 수 있습니다.

이를 사용자 지정 디버깅 뷰라고 부르며 .natvis파일을 통해서 조금만 공부한다면 누구나 디버깅시 자신의 클래스를 자신이 원하는 방식으로 보는 것이 가능해집니다.

그럼 .natvis파일을 만드는 법부터 알아보겠습니다.

 

프로젝트 선택 => 마우스 오른쪽 버튼 => 추가 => 새항목 => Visual c++ => 유틸리티

항목의 디버거 시각화 파일.natvis을 만들어 보겠습니다. 이름은 어떤 식으로도 좋지만 저는 “NatvisFile.natvis”로 정하겠습니다.

파일을 열어보면 다음과 같은 xml형식의 문서를 볼 수가 있으며 이 안에 내용을 기재하여 자신만의 디버깅뷰를 설정할 수 있습니다.

일단 간단한 기본적인 기능에 대해서 알아보겠습니다.

다음과 같이 xml의 내용을 작성합니다. type이라는 요소의 Name이라는 속성에는 디버깅시 뷰를 정의하고 싶은 클래스의 이름을 넣습니다.

 

그리고 DisplayString의 내용은 가장 기본적인 디버깅시 표현할 문자열을 집어넣으면 됩니다. 이렇게 편집된 .natvis문서가 같은 프로젝트 내부에 있다면 비주얼스튜디오는 이를 유저가 정의한 디버깅시 뷰로 인식하고 그에 해당하는 Class의 디버깅시 정보를 기본 뷰방식이 아닌 .natvis 파일 내부의 방식으로 처리합니다.

 

위의 내용만 보더라도 기본적으로 xml의 내용이 사용자정의 클래스에 어떻게 적용되는지 확인하실 수 있을 겁니다.

.natvis파일을 적용하지 않았을 때는 다음과 같이 여러분들이 기본적으로 확인하는 클래스 뷰가 보이게 됩니다.

이걸로 가장 기본적인 방식으로 .natvis파일의 역할에 대해서 설명해봤습니다.

당연히 디버깅시 간단한 메시지를 전하기 위해서 이 파일을 만드는 것은 아닙니다. 이 방식은 정말 다양한 방식으로 응용이 가능하며 그에 대한 몇 가지를 설명해 드리겠습니다.

 

class HPOINT

{

public:

        int X;

        int Y;

 

public:

        int HX() {

               return (int)(X * 0.5F);

        }

        int HY() {

               return (int)(Y * 0.5F);

        }

 

public:

        HPOINT() : X(0), Y(0) {}

        HPOINT(int _X, int _Y) : X(_X), Y(_Y) {}

};

 

위와 같은 클래스가 있다고 하겠습니다. 이 클래스는 기본적으로 XY의 정보를 가지고 있지만 함수로 만들어 놓은 XY의 절반 연산을 하는 값들도 굉장히 중요한 연산일수가 있습니다. 하지만 확인을 하려면 일부러 값을 받거나 조사식을 이용해야 합니다.

하지만 다음과 같이 사용자 뷰를 만들어주면 언제든지 절반의 값도 확인이 가능하게 됩니다.

결과는 다음과 같습니다.

XY의 정보 그 이외에도 함수로만 만들어 놓은 HXHY의 정보도 쉽게 확인이 가능합니다. 이러한 기능을 이용하면 자신이 만든 클래스나 구조체들은 좀더 직관적이고 자신이 빠르게 정보를 확인할 수 있게 됩니다.

이에 대한 요소들을 설명하자면.

<Expend> ~ </Expend> => 정보를 확인할 때 디버깅시 기본정보에서 확장되는 정보창에 나오는 정보들의 내용을 기입한다.

<Item Name=”설명”>내용 </Item> Name에 확장정보의 이름 내용에는 확장정보에서 표현하고 싶은 내용을 넣으면 됩니다. 이때 내용은 일반적으로 단하나의 맴버변수나 한번의 연산으로 나온 1개의 결과값을 값을 표현할 수 있습니다.

그리고 이렇게 사용자 정의 뷰를 공부해본다면 다음과 이미 이름을 정한 확장정보의 클래스도 좀더 쉽게 정보를 확인할 수 있습니다.

class HGAMERECT

{

public:

        HPOINT Postion;

        HPOINT Scale;

 

private:

        HPOINT  LeftTop()

        {

               return HPOINT{ Postion.X - Scale.HX() , Postion.Y - Scale.HY() };

        }

 

        HPOINT  RightTop()

        {

               return HPOINT{ Postion.X + Scale.HX() , Postion.Y - Scale.HY() };

        }

 

        HPOINT  LeftBot()

        {

               return HPOINT{ Postion.X - Scale.HX() , Postion.Y + Scale.HY() };

        }

 

        HPOINT  RightBot()

        {

               return HPOINT{ Postion.X + Scale.HX() , Postion.Y + Scale.HY() };

        }

};

다음과 같은 클래스가 있다고 할 때 역시나 기본적으로 LeftTop이나 RightTop같은 함수를 통해서 반환되는 값들은 확인할 수가 없습니다. 또한 기본적인 DisplayString 항목에서 일반적인 맴버의 내용을 확인하는 것은 기존의 항목의 디버깅 정보를 확장해서 열어야 하는 번거로움이 있습니다. 하지만 사용자 디버깅 뷰를 정의하면 역시나 마찬가지로 직관적으로 디버깅 정보를 확인이 가능합니다.

아까 보다는 식이 좀더 복잡하지만 다음과 같은 사용자 정의 뷰를 정의할 수도 있습니다. Synthetic 아이템은 일반적인 Expend 항목에서의 아이템 뿐만이 아닌 DisplayString을 사용하여 복잡한 항목의 내용도 표현이 가능해집니다. 이를 통한 디버깅시 뷰의 결과는 다음과 같습니다.

일반적으로 내부의 맴버변수를 늘리거나 함수의 리턴값을 받아야만 확인할 수 있는 값들도 다음과 같이 표현이 가능해 집니다.

 

마지막으로 템플릿 클래스를 표현하기 위한 뷰 방식을 알아보도록 하겠습니다. 가장 위에서 제가 제작했던 HARR클래스는 템플릿 클래스이기 때문에 자료형에 따라서 해석되는 클래스 명이 변경되는 점이 일반적인 클래스와는 다릅니다.

그렇기 때문에 몇가지 특별한 기호를 사용해야 합니다.

일반적으로 템플릿은 클래스명<자료형>의 내용으로 이루어져 있지만 XML에서 <기호와 >기호는 특수한 기호로 인식되기 때문에 일반적으로 <>를 사용할 수는 없습니다. 하지만 다음과 같이 정의하여 템플릿 클래스의 이름도 사용자 정의 디버깅으로 인식하게 만들 수 있습니다.

 

 

 

&lt; 라는 요소는 <기호를 &gt; 라는 기호는 >를 의미하게 됩니다.

그렇게 되면 자신이 디버깅뷰를 재정의하려는 클래스의 이름은 HARR<*>이 되며 이때 *은 내부에 어떤 이름이 오든 재정의를 내리기 위해서 사용하게 됩니다.

그 이외에도 DisplayString에서 변수명을 사용할 때는 보시면 알겠지만 {}기호를 사용하여야 하는 것을 볼 수가 있습니다.

마지막으로 어떤 포인터형을 디버깅뷰에서 배열형식으로 확인하기 위해서는 ArrayItems라는 문구를 정의하여 포인터와 사이즈를 넣어주는 방식으로 포인터를 디버깅 뷰에서 마치 배열처럼 보게 만들 수 있습니다.

이와 같이 자신만의 디버깅 뷰를 이용해서 편리한 디버깅을 이용하실수 있기를 바랍니다.