最小,完整和可验证的示例:
Visual Studio 2017专业版15.9.3
Windows 10“ 1803”(17134.441)x64
环境变量OANOCACHE
设置为1。
显示的数据/屏幕截图显示了32位Unicode版本。
更新:在另一台装有Windows 10“ 1803”的计算机上的行为完全相同(17134.407) 更新:在装有Windows 7的旧笔记本电脑上零泄漏 更新:在另一台W10为“ 1803”(17134.335)的计算机上完全相同的行为(泄漏)
#include <windows.h>
#include <cstdio>
int main() {
getchar();
CoInitializeEx( NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE );
printf( "Launching and terminating processes...\n" );
for ( size_t i = 0; i < 64; ++i ) {
SHELLEXECUTEINFO sei;
memset( &sei, 0, sizeof( sei ) );
sei.cbSize = sizeof( sei );
sei.lpFile = L"iexplore.exe";
sei.lpParameters = L"about:blank";
sei.fMask = SEE_MASK_FLAG_NO_UI | SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NOASYNC;
BOOL bSuccess = ShellExecuteEx( &sei );
if ( bSuccess == FALSE ) {
printf( "\nShellExecuteEx failed with Win32 code %d and hInstApp %d. Exiting...\n",
GetLastError(), (int)sei.hInstApp );
CoUninitialize();
return 0;
} // endif
printf( "%d", (int)GetProcessId( sei.hProcess ) );
Sleep( 1000 );
bSuccess = TerminateProcess( sei.hProcess, 0 );
if ( bSuccess == FALSE ) {
printf( "\nTerminateProcess failed with Win32 code %d. Exiting...\n",
GetLastError() );
CloseHandle( sei.hProcess );
CoUninitialize();
return 0;
} // endif
DWORD dwRetCode = WaitForSingleObject( sei.hProcess, 5000 );
if ( dwRetCode != WAIT_OBJECT_0 ) {
printf( "\nWaitForSingleObject failed with code %x. Exiting...\n",
dwRetCode );
CloseHandle( sei.hProcess );
CoUninitialize();
return 0;
} // endif
CloseHandle( sei.hProcess );
printf( "K " );
Sleep( 1000 );
} // end for
printf( "\nDone!" );
CoUninitialize();
getchar();
} // main
代码使用ShellExecuteEx循环启动带有about:blank
URL的Internet Explorer的64个实例。 SEE_MASK_NOCLOSEPROCESS
用于随后可以使用TerminateProcess API。
我注意到两种泄漏:
windows.storage.dll!CInvokeCreateProcessVerb::_BuildEnvironmentForNewProcess()
您可以在此处阅读有关手柄泄漏的一些信息:ShellExecute leaks handles
第二个:相同的pid,如Process Explorer中所示:
Process Explorer还显示了HKCR\.exe
,HKCR\exefile
和HKCR\exefile\shell\open
的64 * 3打开注册表句柄。
最后:Process Explorer的屏幕快照,显示了在执行使用1024循环计数器修改的MCVE的过程中的“专用字节”。运行时间约为36分钟,PV开始于1.1 Mo(在CoInitializeEx之前),结束于19 Mo(在CoUninitialize之后)。然后该值稳定在18.9
我在做什么错? 我在没有泄漏的地方看到泄漏吗?
答案 0 :(得分:2)
这是Windows错误,版本1803。用于复制的最少代码:
declare @StartYear int = '2014';
declare @EndYear int = '2018';
select count(i.InvoiceKey) as [Number of Payments made on Invoice]
-- this will never by null because of the WHERE clause
,year(p.TransPostDate) as PaymentYear
-- I assume multiple payments to one invoice, so don't want to sum here.
-- All values are the same, just take the MAX. MIN would work too.
,max(i.Amt) as [Invoice Amount]
,sum(p.Amt) as [Total Payment Made]
-- count of partial payments
,count(case when p.Amt < i.Amt then i.InvoiceKey end) [Number of partial payments made]
-- sum of partial payments
,sum(case when p.Amt < i.Amt then p.Amt end) [Sum of partial payments made]
from.[Invoices] i
inner join [Payments] p
on i.InvoiceKey = p.InvoiceKey
where i.ClientKey = '518'
and p.InvClientKey = '518'
and i.CloseDate is null
-- if this is not true, then by definition p.TransPostDate is not null so
-- you don't need to specify "p.TransPostDate IS NOT NULL".
-- There is no need to multiply by 100
and year(p.TransPostDate) between @StartYear and @EndYear
group by i.InvoiceKey
,year(p.TransPostDate)
order by year(p.TransPostDate) asc;
执行此代码后,可以查看notepad.exe进程和第一个线程的句柄-当然,该句柄一定不能存在(被关闭),不能被关闭键
if (0 <= CoInitialize(0))
{
SHELLEXECUTEINFO sei = {
sizeof(sei), 0, 0, 0, L"notepad.exe", 0, 0, SW_SHOW
};
ShellExecuteEx( &sei );
CoUninitialize();
}
此调用之后,进程中也存在私有内存泄漏。
当然,此错误会导致 explorer.exe 和使用\REGISTRY\MACHINE\SOFTWARE\Classes\.exe
\REGISTRY\MACHINE\SOFTWARE\Classes\exefile
对此错误进行了精确的研究-here
此处的潜在问题似乎在 windows.storage.dll 中。在 特别是
ShellExecute[Ex]
对象从不 销毁,因为关联的引用计数永远不会达到0。 这会泄漏所有与CInvokeCreateProcessVerb
,包括4个句柄和一些内存。引用计数从未达到0的原因似乎与之相关
CInvokeCreateProcessVerb
的参数更改 从Windows 10 1709到1803,由ShellDDEExec::InitializeByShellInternal
。
在这里更具体,我们有一个对象(CInvokeCreateProcessVerb::Launch()
)对其自身的循环引用。
方法CInvokeCreateProcessVerb
内部有更具体的错误,该错误是从自身调用
CInvokeCreateProcessVerb::Launch()
参数错误6。包含内部HRESULT ShellDDEExec::InitializeByShellInternal(
IAssociationElement*,
CreateProcessMethod,
PCWSTR,
STARTUPINFOEXW*,
IShellItem2*,
IUnknown*, // !!!
PCWSTR,
PCWSTR,
PCWSTR);
子对象的CInvokeCreateProcessVerb
类。在Windows 1709中,ShellDDEExec
将指向CInvokeCreateProcessVerb::Launch()
的指针传递给static_cast<IServiceProvider*>(pObj)
的第6个参数,其中ShellDDEExec::InitializeByShellInternal
指向pObj
类的实例。但在1803版中,此处传递了指向CBindAndInvokeStaticVerb
的指针-因此指向 self 的指针。 static_cast<IServiceProvider*>(this)
将此指针存储在self内并添加对其的引用。请注意,InitializeByShellInternal
是ShellDDEExec
的子对象。因此在不调用CInvokeCreateProcessVerb
的析构函数之前,不会调用ShellDDEExec
的析构函数。但是CInvokeCreateProcessVerb
的析构函数将在其引用计数达到0之前不被调用。但是直到CInvokeCreateProcessVerb
不会释放指向ShellDDEExec
的自身指针,而该指针将仅在其析构函数内.. >
这可能在伪代码中更明显
CInvokeCreateProcessVerb
class ShellDDEExec
{
CComPtr<IUnknown*> _pUnk;
HRESULT InitializeByShellInternal(..IUnknown* pUnk..)
{
_pUnk = pUnk;
}
};
class CInvokeCreateProcessVerb : CExecuteCommandBase, IServiceProvider /**/
{
IServiceProvider* _pVerb;//point to static_cast<IServiceProvider*>(CBindAndInvokeStaticVerb*)
ShellDDEExec _exec;
TRYRESULT CInvokeCreateProcessVerb::Launch()
{
// in 1709
// _exec.InitializeByShellInternal(_pVerb);
// in 1803
_exec.InitializeByShellInternal(..static_cast<IServiceProvider*>(this)..); // !! error !!
}
};
保持指向包含对象ShellDDEExec::_pUnk
的指针,此指针仅在CInvokeCreateProcessVerb
析构函数中调用的CComPtr
析构函数内释放。从ShellDDEExec
析构函数调用,在引用计数变为0时调用,但这从未发生,因为额外的引用保留CInvokeCreateProcessVerb
因此对象存储引用了指向self的指针。在ShellDDEExec::_pUnk
的引用计数之后永远不会达到0