2

It seems that IDWriteTextLayout::GetMetrics increments COM reference counter inside IDWriteFactory by 2 but IDWriteTextLayout object doesn't decrement it at all upon destruction. As a result IDWriteFactory object isn't destroyed. Am I right that this is a bug?

Here is a code snippet:

#define NTDDI_VERSION NTDDI_VISTA
#define _WIN32_WINNT _WIN32_WINNT_VISTA

#include <windows.h>
#include <dwrite.h>

#include <conio.h>
#include <iostream>

#pragma comment(lib, "Dwrite.lib")

using namespace std;

template<class Interface>
inline void SafeRelease(Interface **ppInterfaceToRelease)
{
    if (*ppInterfaceToRelease != nullptr) {
        (*ppInterfaceToRelease)->Release();
        (*ppInterfaceToRelease) = nullptr;
    }
}

void test(unsigned int);

int wmain(void)
{
    try {
        test(1);
        test(2);
    } catch (HRESULT) {
        cout << "EXCEPTION" << "\n";
    }

    _getch();
}

void test(unsigned int testId)
{
    IDWriteFactory*    factory    = nullptr;
    IDWriteTextFormat* textFormat = nullptr;
    IDWriteTextLayout* textLayout = nullptr;;

    DWRITE_TEXT_METRICS textMetrics;

    auto checkIfError = [&textFormat, &textLayout, &factory](HRESULT hr) -> void {
        if (FAILED(hr)) {
            SafeRelease(&textFormat);
            SafeRelease(&textLayout);
            SafeRelease(&factory);
            throw hr;
        }
    };

    checkIfError(DWriteCreateFactory(
        DWRITE_FACTORY_TYPE_ISOLATED,
        __uuidof(IDWriteFactory),
        reinterpret_cast<IUnknown**>(&factory)
    ));

    checkIfError(factory->CreateTextFormat(
        L"Calibri",
        nullptr,
        DWRITE_FONT_WEIGHT_LIGHT,
        DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_NORMAL,
        16.f,
        L"en-US",
        &textFormat
    ));

    checkIfError(factory->CreateTextLayout(
        L"test",
        4,
        textFormat,
        0,
        0,
        &textLayout
    ));

    if (testId == 2) {
        checkIfError(textLayout->GetMetrics(&textMetrics));
    }

    textFormat->Release();
    textLayout->Release();

    long counter = factory->Release();
    cout << "TEST#: " << testId << ", Counter = " << counter << "\n";
}

Results of the execution:

TEST#: 1, Counter = 0
TEST#: 2, Counter = 2

Platform: Windows 7 x64 SP1 With Platform Update.
Compiler: Visual Studio 2015 Update 1.

geniuss99
  • 101
  • 5
  • That's pretty interesting if it's truly what it does. Do you actually see memory leaking? Refcount is not necessarily a solid evidence, what if you run it 60K times let's say with isolated factories, do you see constant memory usage increase? It's also interesting to try on newer system to see if it's different there. – bunglehead May 21 '16 at 09:41
  • @bunglehead Actually I see memory usage increase during tests but not constant. Here are the quick numbers: Iterations Test1 Test2 10K 792K 1300K 100K 784K <= 1316K I used **"Memory - Private Working Set"** in Task Manager to measure memory usage of a process. I can't explain the second row for Test2: somehow the memory usage doesn't increase after ~1316K. – geniuss99 May 22 '16 at 19:37
  • Another thing to check is to see if factory methods are still operational after last valid Release that returns 2 in your test. It's possible non-standard refcount is an implementation detail and not a real leak. For example it's possible that layout triggers system font fallback instance creation that sticks around after layout is released. – bunglehead May 23 '16 at 11:45
  • @bunglehead Yes, methods are still operational after last valid Release. It looks like factory works as normal. – geniuss99 May 25 '16 at 10:35
  • Did you get a chance to test this on Win8 and newer? – bunglehead Jun 04 '16 at 14:50

0 Answers0