We’re not going to get very far with Windows game programming without using COM. For those lucky enough to not have used it, COM is a way of passing classes around irrespective of what language they are written in and so seems really compilicated when you look at it. As with most things this isn’t really true, it’s actually very simple as long as you’re just consuming objects and there are a plethora of smart pointers out there in order to help you use them because it’s the future now and if you’re not using a smart pointer for everything then you’ve gone wrong.

Looking at the DX12 samples (https://github.com/microsoft/DirectX-Graphics-Samples/tree/master/Samples/Desktop/D3D12HelloWorld/src/HelloWindow) you can see that it uses the one from WRL. You can tell this easily if you see this using statement littering up the code.

using Microsoft::WRL::ComPtr;

Then later on you’ll see things like

ComPtr<IDXGIFactory4> factory;
ThrowIfFailed(CreateDXGIFactory2(dxgiFactoryFlags, IID_PPV_ARGS(&factory)));

This is all well and good, but there’s a new hotness in the C++ windows library world, and it’s called winrt.

I quite like the library because of one major feature: It’s implemented as a set of header files and so is smaller and faster. You will need to be using C++17 or 20 though, but if you’re writing new code and not using 17 then you’ve got bigger problems to worry about.

Converting to winrt is simple, here’s a fully working code example in a windows console app.

#include <iostream>
#include <windows.h>
#include <dxgi1_6.h>
#include <winrt/base.h>
#pragma comment(lib, "dxgi")

int main()
{
    winrt::com_ptr<IDXGIFactory4> factory;
    winrt::hresult hr = CreateDXGIFactory2(0, IID_PPV_ARGS(&factory));
	if(FAILED(hr))
	{
        std::cout << "Failed to create factory\n";
        return -1;
	}
}

You can see that it’s almost the same. I’ve stopped using the “oh god never use this in production code please” ThrowIfFailed macro and replaced it with the good old fashioned FAILED() macro that’s been around since the start of COM. We also need to include the headers and library that CreateDXGIFactory2 exists in, but all we really care about is including winrt/base.h.

And that’s it. The COM object will be released when it goes out of scope, and when all the references to it are released it will delete all actual object.

There is one last thing though. You will need to know how to get the pointers to COM objects to be passed into other functions. COM loves pointers to pointers that it can then write to.

winrt::com_ptr<IDXGIFactory4>* f1 = &factory;
IDXGIFactory4*  f2 = factory.get();
IDXGIFactory4** f3 = factory.put();

As you can see the 1st example is obvious, and in the example above it was enough for the IID_PPV_ARGS macro to decode happily because winrt understands it. You can also use get() to return a pointer and put() to return the pointer to the pointer version because you’re putting the pointer into a function or something? Actually you can just use the first example there as com_ptr has the following:

template <typename T>
void** IID_PPV_ARGS_Helper(winrt::com_ptr<T>* ptr) noexcept
{
    return winrt::put_abi(*ptr);
}

Which just means that it calls one of the versions of put inside that macro call. Very nice and very wrappped.

So when will you need to call put yourself? When you need to supply that pointer to a pointer to a function in order for it to be filled in and created, as shown by this fragment of DX12 code.

winrt::com_ptr<ID3D12CommandQueue> command_queue;

/// REMOVED UNIMPORTANT CODE

winrt::com_ptr<IDXGISwapChain1> swap_chain1;
hresult hr = factory->CreateSwapChainForHwnd(command_queue.get(), handle, &swap_chain_desc, nullptr, nullptr, swap_chain1.put());

Here you can see that CreateSwapChainForHwnd needs a ID3D12CommandQueue* parameter and a IDXGISwapChain1** parameter, which are handled by the get() and put() functions.

A slightly less pretty situation is when you need to pass as a pointer to a pointer to IUnknown, but this can be achieved with a reinterpret_cast.

winrt::com_ptr<IDWriteFactory> dwrite_factory;
hr = ::DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(dwrite_factory), reinterpret_cast<IUnknown**>(dwrite_factory.put()));

Conclusion

The com_ptr class is a nice lightweights COM wrapper that doesn’t need any extra libraries linked in and can be included through a single header file. It’s nice. I like it. Also if you’re writing a modern windows app you probably need some windows runtime functionality anyway somewhere and winrt just gets better as a wrapper for all that. You don’t even need to use ^ as a special magic pointer type. But that’s a whole different post.