-1

I have a WMI-related code, that gets an event, once new app is started. I've skipped initialization part, here is the code. Note, that everything is working, all HRESULTs are S_OK.

IEnumWbemClassObject* pEnumerator = NULL;

pSvc->ExecNotificationQuery( // IWbemServices *pSvc is initialized
    bstr_t("WQL"),
    bstr_t("SELECT * FROM __InstanceCreationEvent WITHIN 1 "
        "WHERE TargetInstance ISA 'Win32_Process'"),
    WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
    NULL, &pEnumerator);

while (pEnumerator) {
    _variant_t v1, v2;
    pclsObj->Get(_bstr_t(L"TargetInstance"), 0, &v1, 0, 0);
    IUnknown* str = v1;
    str->QueryInterface(IID_IWbemClassObject, reinterpret_cast< void** >(&pclsObj));
    pclsObj->Get(bstr_t(L"Handle"), 0, &v2, 0, 0);
    LONG pid{ 0 };
    hr = VarI4FromStr(v2.bstrVal, LOCALE_NOUSEROVERRIDE, 409, &pid);
    Internal::Inject(pid); // It's my code, not relevant here

    str->Release();
    pclsObj->Release();
    v1.Clear();
    v2.Clear();
}

This code was taken from MSDN and slightly modified. However, it leaks memory, and I have no idea why. Looking via MSVC memory profiler gives us this picture: screen1

or this: screen2

From my point of view - I've cleared\released everything, however, allocations, like on screenshots happens once new event arrive and they are staying forever.

I've found this question, it appears to be the same, but no answer received.

Visual Studio 2015 Update 3, up-to-date Windows 10 x64 Professional.

Starl1ght
  • 4,422
  • 1
  • 21
  • 49

1 Answers1

1

When you call str->QueryInterface(), you are overriding the pclsObj pointer without calling pclsObj->Release() beforehand. You are calling Release() on the pclsObj object that QueryInterface() returns, and leaking the original pclsObj object.

You should stop managing interface reference counts manually and use the _com_ptr_t wrapper instead.

Where is the original pclsObj coming from? It looks like you are missing a call to pEnumerator->Next().

Try something more like this instead (error handling omitted for brevity):

_com_ptr_t<IEnumWbemClassObject> pEnumerator;

pSvc->ExecNotificationQuery( // IWbemServices *pSvc is initialized
    bstr_t("WQL"),
    bstr_t("SELECT * FROM __InstanceCreationEvent WITHIN 1 "
        "WHERE TargetInstance ISA 'Win32_Process'"),
    WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
    NULL, &pEnumerator);

if (pEnumerator)
{
    while (true)
    {
        _com_ptr_t<IWbemClassObject> pclsEvent, pclsObj;
        _variant_t v1, v2;
        ULONG ulReturned = 0;

        pEnumerator->Next(WBEM_INFINITE, 1, &pclsEvent, &ulReturned);
        pclsEvent->Get(_bstr_t(L"TargetInstance"), 0, &v1, 0, 0);

        _com_ptr_t<IUnknown> str = v1;
        str->QueryInterface(IID_IWbemClassObject, reinterpret_cast<void**>(&pclsObj));
        pclsObj->Get(bstr_t(L"Handle"), 0, &v2, 0, 0);

        LONG pid{ 0 };
        hr = VarI4FromStr(v2.bstrVal, LOCALE_NOUSEROVERRIDE, 409, &pid);
        Internal::Inject(pid); // It's my code, not relevant here
    }
}

Alternatively:

_com_ptr_t<IEnumWbemClassObject> pEnumerator;

pSvc->ExecNotificationQuery( // IWbemServices *pSvc is initialized
    bstr_t("WQL"),
    bstr_t("SELECT * FROM __InstanceCreationEvent WITHIN 1 "
        "WHERE TargetInstance ISA 'Win32_Process'"),
    WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
    NULL, &pEnumerator);

if (pEnumerator)
{
    while (true)
    {
        _com_ptr_t<IWbemClassObject> pclsObj;
        _variant_t v1, v2;
        ULONG ulReturned = 0;

        pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &ulReturned);
        pclsObj->Get(_bstr_t(L"TargetInstance"), 0, &v1, 0, 0);

        // _com_ptr_t::operator&() calls Release() on the current object
        // if not NULL before then returning the address of the the
        // interface pointer...

        _com_ptr_t<IUnknown> str = v1;
        str->QueryInterface(IID_IWbemClassObject, reinterpret_cast<void**>(&pclsObj));
        pclsObj->Get(bstr_t(L"Handle"), 0, &v2, 0, 0);

        LONG pid{ 0 };
        hr = VarI4FromStr(v2.bstrVal, LOCALE_NOUSEROVERRIDE, 409, &pid);
        Internal::Inject(pid); // It's my code, not relevant here
    }
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • 1
    There's also the handy [IID_PPV_ARGS](https://msdn.microsoft.com/en-us/library/windows/desktop/ee330727.aspx) macro, that allows you to write: `str->QueryInterface( IID_PPV_ARGS( &pclsObj ) );`. It's not only shorter, it's also safer, since interface ID and pointer type are guaranteed to match. – IInspectable Oct 29 '16 at 12:28
  • Can you explain a bit VarI4FromStr ? I tried the same thing but I get some in-existent pid.. – Mecanik Dec 11 '19 at 16:15
  • @NorbertBoros Did you read the documentation yet? [VarI4FromStr function](https://msdn.microsoft.com/en-us/windows/desktop/ms221194) and [Win32_Process class](https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/win32-process). In the `__InstanceCreationEvent` events being queried, the `TargetInstance` is a `Win32_Process` object, and its `Handle` property is a `string` (which is a [`BSTR`](https://learn.microsoft.com/en-us/previous-versions/windows/desktop/automat/bstr) in COM). `VarI4FromStr()` simply converts a string to an integer. – Remy Lebeau Dec 11 '19 at 18:33
  • Yep I did after I commented... my bad :) Thanks anyway – Mecanik Dec 11 '19 at 18:34