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 <address> to get additional information about the allocation.</avrf:parameter1>
<avrf:parameter2>49a5774 - Address to the allocation stack trace. Run dps <address> to view the allocation stack.</avrf:parameter2>
<avrf:parameter3>5bd8fe8 - Address of the owner dll name. Run du <address> to read the dll name.</avrf:parameter3>
<avrf:parameter4>11390000 - Base of the owner dll. Run .reload <dll_name> = <address> to reload the owner dll. Use 'lm' 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?
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: