[목차]

 

- Microsoft::WRL::ComPtr<T>란

- 주요 동작 방식

- 프로젝트에 사용하기 위한 설정

- 주의점

 

 

[Microsoft::WRL::ComPtr<T>란]

 

Direct3D 관련 인터페이스는 IUnKnown 클래스를 상속받는 COM(Component Object Model) 객체입니다. 이들은 내부에서 자체적으로 스마트 포인터와 같이 RAII 패턴과 더불어 Refernce Counting, Release를 통해 수명을 관리하기 때문에 스마트 포인터(std::shared_ptr)로 받지 못하게 막아놨습니다. 

 

이 COM 객체들을 원시 포인터에 저장하여 활용할 수 있지만, 이럴 경우에는 프로세스 종료 시점에 명시적으로 Release를 호출해줘야 합니다. 그렇지 않으면 메모리 누수 위험이 존재할 수 있는데, 앞서 D3D11CreateDevice() 함수를 통해 반환되는 값을 [ID3D11Device* Device]에 저장했다고 가정해봅시다. 사용을 다 했으면 Release를 해줘야하지만, 하지 않았을 경우 아래와 같은 출력 창을 확인할 수 있습니다.

 

오류 내용은 프로그램이 종료되었지만 해제되지 않은 Direct3D 객체가 있다는 경고입니다. 참조 카운트는 0이지만 객체는 여전히 GPU 메모리에 남아있다는 뜻이 됩니다.

 

물론 이 모든 상황을 컨트롤 할 수 있다면 상관없지만, 여간 번거로운 작업이 아닐 수 없습니다. 이를 위해서 Direct3D의 COM 객체들을 받아볼 수 있고, 스마트 포인터처럼 사용이 끝나면 자동으로 메모리를 해제해주는 포인터가 있는데, 그것이 바로 Microsoft::WRL::ComPtr<T>입니다.

 

 

[주요 동작 방식]

 

스마트 포인터와 동작 방식이 동일합니다.

 

1. 선언

: 아래와 같이 선언해서 사용하면 됩니다.

Microsoft::WRL::ComPtr<ID3D11Device> Device;

 

2. 참조 카운트 관리

: 스마트 포인터와 같이 생성 시 AddRef(), 소멸 시 Release() 함수를 호출하여 자동으로 객체 생명 주기를 관리해줍니다.

 

3. 스마트 포인터처럼 사용 가능

: operator->, Get(), Reset() 등의 함수를 제공해주기 때문에 스마트 포인터처럼 사용이 가능합니다.

 

4. 안전한 대입과 복사

: Comptr 간 복사 시 내부적으로 참조를 관리하기 때문에 생성된 값을 저장하는 용도로(D3D11CreateDevice() 반환값) 사용해도 안전하게 이용할 수 있습니다. 또한 대입 연산자로 다른 Comptr 변수에 값을 대입해도 Reference Counting 관리로 인해 메모리 누수 문제에 자유로워질 수 있습니다.

 

5. QueryInterface

: As(&out) 함수로 인터페이스를 제공해주는데, 해당 프로젝트에서는 크게 쓸 일은 없을 것으로 생각됩니다.

 

 

[프로젝트에 사용하기 위한 설정]

 

모든 COM 객체에 대해 선언마다 Microsoft::WRL::ComPtr<>를 선언하기도, using namespace를 선언하기도 여간 번거로운 작업이 아닐 수 없습니다. 때문에 래핑 구조체를 하나 선언했습니다.

/////////////////////////////// BaseHeader.h
#include <wrl/client.h>

// Microsoft::WRL::ComPtr<T> 래핑 구조체
template<typename T>
struct COMPTR
{
public:
    // 생성자
    COMPTR() {}
    COMPTR(nullptr_t) {}

    // 연산자 오버로딩
    operator Microsoft::WRL::ComPtr<T>& () { return Ptr; } // Ptr 직접 접근용 변환 연산자
    operator T* () const { return Ptr.Get(); } // Ptr 직접 접근용 변환 연산자
    T* operator->() const { return Ptr.Get(); } // 스마트 포인터처럼 동작하도록 연산자 오버로드
    T** operator&() { return Ptr.GetAddressOf(); } // 스마트 포인터처럼 동작하도록 연산자 오버로드
    operator bool() const { return Ptr != nullptr; } // 조건문에서 사용(null인지 아닌지 판별)

    // Getter
    T* Get() const { return Ptr.Get(); } // 원시 포인터 반환
    T** GetAddressOf() { return Ptr.GetAddressOf(); } // 레퍼런스 반환(함수 전달용)

    void Reset() { Ptr.Reset(); } // 현재 보유중인 COM 객체 해제(Release())를 호출하고 nullptr로 설정
    T* Detach() { return Ptr.Detach(); } // 소유권 이전

    // Microsoft::WRL::ComPtr<U>* 받아서 As, 인터페이스 변환
    template<typename U>
    HRESULT As(Microsoft::WRL::ComPtr<U>* out) const
    {
        return Ptr.As(out);
    }

    // U** 받아서 바로 QueryInterface 실시하기
    template<typename U>
    HRESULT As(U** out) const
    {
        return Ptr.CopyTo(out);
    }

public:
    Microsoft::WRL::ComPtr<T> Ptr = nullptr;
};

 

이렇게 해주면 [ID3D11Device* Device]와 같은 값을 [Microsoft::WRL::ComPtr<ID3D11Device> Device]이 아닌 [COMPTR<ID3D11Device> Device]의 형태로 활용할 수 있습니다.

 

이러면 값을 전달할때, 참조 전달은 GetAddressOf()로, 포인터 전달은 Get()으로 해주면 됩니다.

///////// 변경 전
HRESULT Hr = D3D11CreateDevice
(
	Adapter,
	D3D_DRIVER_TYPE_UNKNOWN,
	nullptr,
	iFlag,
	nullptr,
	0,
	D3D11_SDK_VERSION,
	&Device,
	&Level,
	&Context
);

///////// 변경 후
HRESULT Hr = D3D11CreateDevice
(
	Adapter.Get(),
	D3D_DRIVER_TYPE_UNKNOWN,
	nullptr,
	iFlag,
	nullptr,
	0,
	D3D11_SDK_VERSION,
	Device.GetAddressOf(),
	&Level,
	Context.GetAddressOf()
);

 

 

[주의점]

 

한 가지 주의할 점이 있습니다 . 함수의 반환값이나 인자 전달 값에 대한 설정입니다.

 

1. 사용자 정의 함수에 대한 인자 전달 방식 변경

: 구조체의 형태이기 때문에 인자는 그대로 전달하지만, 함수는 래퍼런스로 받음(권장)

///////////////// 사용처
COMPTR<ID3D11Texture2D> SwapBackBufferTexture;
SwapChain.Get()->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(SwapBackBufferTexture.GetAddressOf()))
std::shared_ptr<Ext_DirectXTexture> BackBufferTexture = std::make_shared<Ext_DirectXTexture>();
BackBufferTexture->CreateRenderTargetView(SwapBackBufferTexture);

///////////////// 변경 전
void CreateRenderTargetView(ID3D11Texture2D* _Texture);

///////////////// 변경 후
void CreateRenderTargetView(COMPTR<ID3D11Texture2D>& _Texture);

 

2. 사용자 정의 함수 반환값 변경

: 레퍼런스를 반환하도록 함(필수)

///////////////// 사용처
COMPTR<ID3D11RenderTargetView> RTV = MainRenderTarget->GetTexture(0)->GetRTV();

///////////////// 변경 전
ID3D11RenderTargetView* GetRTV(size_t _Index = 0) { return RTVs[_Index]; }

///////////////// 변경 후
COMPTR<ID3D11RenderTargetView>& GetRTV(size_t _Index = 0) { return RTVs[_Index]; }

+ Recent posts