man*_*ell 6 winapi memory-leaks visual-c++
最小、完整和可验证的示例:
Visual Studio 2017 Pro 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
Run Code Online (Sandbox Code Playgroud)
该代码使用ShellExecuteEx循环启动具有about:blankURL 的64 个 Internet Explorer 实例。该SEE_MASK_NOCLOSEPROCESS用于能够再使用了TerminateProcess API。
我注意到两种泄漏:
windows.storage.dll!CInvokeCreateProcessVerb::_BuildEnvironmentForNewProcess()您可以在此处阅读有关句柄泄漏的一些信息:ShellExecute 泄漏句柄
第二:相同的 pid,如 Process Explorer 中所示:

Process Explorer 还显示 64*3 打开的注册表句柄,用于HKCR\.exe、HKCR\exefile和HKCR\exefile\shell\open。
最后:Process Explorer 的屏幕截图,显示了在执行 MCVE 期间使用 1024 循环计数器修改的“私有字节”。运行时间约为 36 分钟,PV 从 1.1 Mo(CoInitializeEx 之前)开始,并在 19 Mo(CoUninitialize 之后)结束。该值随后稳定在 18.9

我究竟做错了什么?我是否看到没有泄漏的地方?
这是版本 1803 中的 Windows 错误。重现的最少代码:
if (0 <= CoInitialize(0))
{
SHELLEXECUTEINFO sei = {
sizeof(sei), 0, 0, 0, L"notepad.exe", 0, 0, SW_SHOW
};
ShellExecuteEx( &sei );
CoUninitialize();
}
Run Code Online (Sandbox Code Playgroud)
执行此代码后,可以查看 notepad.exe 进程和第一个线程的句柄 - 该句柄当然不能存在(被关闭),而不是关闭的键
\REGISTRY\MACHINE\SOFTWARE\Classes\.exe
\REGISTRY\MACHINE\SOFTWARE\Classes\exefile
Run Code Online (Sandbox Code Playgroud)
此调用后进程中还存在私有内存泄漏。
当然,这个错误会导致explorer.exe和任何使用它的进程永久资源泄漏ShellExecute[Ex]
这个错误的确切研究 -这里
这里的根本问题似乎是在windows.storage.dll中。特别是,该
CInvokeCreateProcessVerb对象永远不会被销毁,因为关联的引用计数永远不会达到 0。这会泄漏与 关联的所有对象CInvokeCreateProcessVerb,包括 4 个句柄和一些内存。
ShellDDEExec::InitializeByShellInternal引用计数从未达到 0 的原因似乎与由 执行的从 Windows 10 1709 到 1803 的 参数更改有关CInvokeCreateProcessVerb::Launch()。
更具体地说,我们有一个对象(CInvokeCreateProcessVerb)对其自身的循环引用。
CInvokeCreateProcessVerb::Launch()从自身调用的方法内部出现更具体的错误
HRESULT ShellDDEExec::InitializeByShellInternal(
IAssociationElement*,
CreateProcessMethod,
PCWSTR,
STARTUPINFOEXW*,
IShellItem2*,
IUnknown*, // !!!
PCWSTR,
PCWSTR,
PCWSTR);
Run Code Online (Sandbox Code Playgroud)
6 个参数错误。CInvokeCreateProcessVerb包含内部ShellDDEExec子对象的类。在 Windows 1709 中,CInvokeCreateProcessVerb::Launch()将指针传递到static_cast<IServiceProvider*>(pObj)第 6 个参数,其中ShellDDEExec::InitializeByShellInternal指向pObj类的实例CBindAndInvokeStaticVerb。但在 1803 版本中,这里将指针传递给static_cast<IServiceProvider*>(this)-so 指向self的指针。将InitializeByShellInternal这个指针存储在 self 中并添加对其的引用。请注意,这ShellDDEExec是的子对象CInvokeCreateProcessVerb。所以析构函数ShellDDEExec不会被调用,直到析构函数CInvokeCreateProcessVerb不被调用。但CInvokeCreateProcessVerb在引用计数达到 0 之前,不会调用 of 的析构函数。但是,直到ShellDDEExec不释放 self 指针时,才会发生这种情况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 !!
}
};
Run Code Online (Sandbox Code Playgroud)
ShellDDEExec::_pUnk持有指向包含对象的指针,CInvokeCreateProcessVerb该指针仅在析构函数内释放CComPtr,从ShellDDEExec析构函数调用。打电话自CInvokeCreateProcessVerb析构函数调用,当引用计数变为 0 时调用,但这永远不会发生,因为额外的引用保持ShellDDEExec::_pUnk
所以对象存储引用指向 self 的指针。在此之后引用计数CInvokeCreateProcessVerb永远不会达到 0