[목차]
- 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]; }
'DirectX11 > 프레임워크 제작' 카테고리의 다른 글
[DirectX11] DirectX11 프레임워크를 위한 기능 추가 - 2 (0) | 2025.05.25 |
---|---|
[DirectX11] InputAssembler 단계 준비와 도형 출력 (0) | 2025.05.21 |
[DirectX11] DirectX11 프레임워크를 위한 기능 추가 - 1 (0) | 2025.05.17 |
[DirectX11] SwapChain과 RenderTarget 생성 (0) | 2025.05.16 |
[DirectX11] Device와 Context 생성하기 (0) | 2025.05.14 |