Search code examples
c++winapimemory-leakscomvb6

Enumerating printer folder via IShellFolder interface causes heap allocation leak


I need to provide a function for a VB6 app, which enumerates the printers of the current user. The built-in VB6 Printer object fails on terminal servers due to listing all printers on the network.

Compiled with VC2013 Update 5 on Win7 x64 SP1. Note: error checking omitted

#include <Windows.h>
#include <ShlObj.h>
#pragma comment(lib, "Shell32.lib")

int main(int argc, wchar_t* argv[])
{
    HRESULT hr = CoInitialize(0);   

    ULONG ulFetched = 0;
    LPITEMIDLIST pPidl = NULL;
    IShellFolder *pPrinterFolder = NULL;
    IEnumIDList *pEnum = NULL;  
    IShellFolder *pDesktopFolder = NULL;

    hr = SHGetDesktopFolder(&pDesktopFolder);   

    LPITEMIDLIST pPidlLocation = NULL;
    hr = SHGetSpecialFolderLocation(NULL, CSIDL_PRINTERS, &pPidlLocation);

    hr = pDesktopFolder->BindToObject(pPidlLocation, NULL, IID_IShellFolder, (void **)&pPrinterFolder);

    hr = pPrinterFolder->EnumObjects(NULL, SHCONTF_NONFOLDERS, &pEnum);

    while ((hr = pEnum->Next(1, &pPidl, &ulFetched)) == S_OK && ulFetched > 0)
    {
        // Do something with item
        CoTaskMemFree(pPidl);
    }

    CoTaskMemFree(pPidlLocation);

    pEnum->Release();
    pPrinterFolder->Release();
    pDesktopFolder->Release();

    // Heap allocation leak
    CoUninitialize();

    return 0;       
}

The problem is that the call to CoUnitialize() causes a heap allocation leak when being monitored with application verifier:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<avrf:logfile xmlns:avrf="Application Verifier">
    <avrf:logSession TimeStarted="2015-10-06 : 13:13:37" PID="1880" Version="2">
        <avrf:logEntry Time="2015-10-06 : 13:13:40" LayerName="Leak" StopCode="0x900" Severity="Error">
            <avrf:message>A heap allocation was leaked.</avrf:message>
            <avrf:parameter1>6ff7ff0 - Address of the leaked allocation. Run !heap -p -a &lt;address&gt; to get additional information about the allocation.</avrf:parameter1>
            <avrf:parameter2>49a5774 - Address to the allocation stack trace. Run dps &lt;address&gt; to view the allocation stack.</avrf:parameter2>
            <avrf:parameter3>5bd8fe8 - Address of the owner dll name. Run du &lt;address&gt; to read the dll name.</avrf:parameter3>
            <avrf:parameter4>11390000 - Base of the owner dll. Run .reload &lt;dll_name&gt; = &lt;address&gt; to reload the owner dll. Use &apos;lm&apos; to get more information about the loaded and unloaded modules.</avrf:parameter4>
            <avrf:stackTrace>
                <avrf:trace>vfbasics!+59d8a7b7 ( @ 0)</avrf:trace>
                <avrf:trace>vfbasics!+59d8b031 ( @ 0)</avrf:trace>
                <avrf:trace>vfbasics!+59d86ac4 ( @ 0)</avrf:trace>
                <avrf:trace>ntdll!RtlApplicationVerifierStop+1a6 ( @ 0)</avrf:trace>
                <avrf:trace>ntdll!RtlUlonglongByteSwap+222e ( @ 0)</avrf:trace>
                <avrf:trace>ntdll!LdrUnloadDll+4a ( @ 0)</avrf:trace>
                <avrf:trace>vfbasics!+59d87065 ( @ 0)</avrf:trace>
                <avrf:trace>KERNELBASE!FreeLibrary+15 ( @ 0)</avrf:trace>
                <avrf:trace>ole32!PropVariantCopy+746 ( @ 0)</avrf:trace>
                <avrf:trace>ole32!PropVariantCopy+81c ( @ 0)</avrf:trace>
                <avrf:trace>ole32!PropVariantCopy+830 ( @ 0)</avrf:trace>
                <avrf:trace>ole32!PropVariantCopy+7b7 ( @ 0)</avrf:trace>
                <avrf:trace>ole32!SetErrorInfo+75 ( @ 0)</avrf:trace>
                <avrf:trace>vfbasics!+59d8ee93 ( @ 0)</avrf:trace>
                <avrf:trace>userprinters!main+183 (c:\projects\userprinters\userprinters\userprinters.cpp @ 44)</avrf:trace>
                <avrf:trace>userprinters!__tmainCRTStartup+199 (f:\dd\vctools\crt\crtw32\dllstuff\crtexe.c @ 626)</avrf:trace>
                <avrf:trace>userprinters!mainCRTStartup+d (f:\dd\vctools\crt\crtw32\dllstuff\crtexe.c @ 466)</avrf:trace>
                <avrf:trace>kernel32!BaseThreadInitThunk+12 ( @ 0)</avrf:trace>
                <avrf:trace>ntdll!RtlInitializeExceptionChain+63 ( @ 0)</avrf:trace>
                <avrf:trace>ntdll!RtlInitializeExceptionChain+36 ( @ 0)</avrf:trace>
            </avrf:stackTrace>
        </avrf:logEntry>
    </avrf:logSession>
</avrf:logfile>

As a side note, it seems that CoInitialize() seems to be a requirement of recent Windows versions, as described here

Can anyone point me into the right direction as to what's causing this leak?


Solution

  • Yes, it seems there are leaks in modules prncache.dll and prhfldr.dll that handle the printers enumeration.

    First of all I've modified your code a bit adding a cycle to check memory usage in Task Manager:

    while (true)
    {
        HRESULT hr = CoInitialize(0);
    
        ULONG ulFetched = 0;
        LPITEMIDLIST pPidl = NULL;
        IShellFolder *pPrinterFolder = NULL;
        IEnumIDList *pEnum = NULL;
        IShellFolder *pDesktopFolder = NULL;
    
        hr = SHGetDesktopFolder(&pDesktopFolder);
    
        LPITEMIDLIST pPidlLocation = NULL;
        hr = SHGetSpecialFolderLocation(NULL, CSIDL_PRINTERS, &pPidlLocation);
    
        hr = pDesktopFolder->BindToObject(pPidlLocation, NULL, IID_IShellFolder, (void **)&pPrinterFolder);
    
        hr = pPrinterFolder->EnumObjects(NULL, SHCONTF_NONFOLDERS, &pEnum);
    
        while ((hr = pEnum->Next(1, &pPidl, &ulFetched)) == S_OK && ulFetched > 0)
        {
            // Do something with item
            CoTaskMemFree(pPidl);
        }
    
        CoTaskMemFree(pPidlLocation);
    
        pEnum->Release();
        pPrinterFolder->Release();
        pDesktopFolder->Release();
    
        // Heap allocation leak
        CoUninitialize();
    }
    

    And I saw that memory usage is growing.

    Then I took Deleaker, set breakpoint at the line with CoInitialize() and made several snapshots to compare them later.

    Here what I got:

    leaks