很快,我的问题是,当有大量内存空闲时,为什么WinAPI RegisterClass
会失败ERROR_NOT_ENOUGH_MEMORY
,我该怎么做才能阻止它呢?
背景 我正在开发一个应用程序(WinSCP FTP / SFTP客户端),许多人使用它来自动执行文件传输。有些人每天都会从Windows Scheduler每分钟运行一次。
我收到很多报告,经过一定数量的运行后,应用程序停止工作。触发问题的运行次数似乎并不准确,但它在数万甚至数十万的范围内。此外,似乎只有在Windows Scheduler下运行时才会出现问题,而不是在常规Windows会话中运行时。虽然我无法100%确认这一点。
此外,所有报告似乎都适用于Windows 2008 R2 +一些适用于Windows 7.此外,这可能只是巧合。
我自己能够在Windows 7上重现该问题。一旦系统进入此状态,我的应用程序就不再在Scheduler的会话中启动。但它在正常的常规会话中开始很好。而且一些其他应用程序(不一定全部)甚至在Scheduler的会话中启动。同样在这种状态下我无法调试应用程序,因为它甚至在调试器(或Process Monitor等工具)运行时都没有加载。
该应用程序正在使用Embarcadero(前Borland)C ++ Builder VCL库。它在VCL初始化代码中崩溃(我的WinMain
甚至没有启动)并退出代码3.检查初始化代码正在做什么,我可能能够识别触发崩溃的代码(虽然它可以只是是许多可能的原因之一)。
罪魁祸首似乎是RegisterClass
WinAPI函数返回8
(ERROR_NOT_ENOUGH_MEMORY
)。发生这种情况时,VCL代码会抛出异常;并且因为还没有异常处理程序,它会崩溃应用程序。
我使用在VS 2012中开发的非常简单的C ++控制台应用程序验证了这一点(将问题与C ++ Builder和VCL隔离开来)。核心代码是:
SetLastError(ERROR_SUCCESS);
fout << L"Registering class" << std::endl;
WNDCLASS WndClass;
memset(&WndClass, 0, sizeof(WndClass));
WndClass.lpfnWndProc = &DefWindowProc;
WndClass.lpszClassName = L"TestClass";
WndClass.hInstance = GetModuleHandle(NULL);
ATOM Atom = RegisterClass(&WndClass);
DWORD Error = GetLastError();
// The Atom is NULL and Error is ERROR_NOT_ENOUGH_MEMORY here
(测试应用程序的完整代码在最后)
尽管有错误,但它似乎不是内存问题。我通过在RegisterClass
调用之前和之后分配10 MB内存来验证的内容(可以在最后的完整测试代码中看到)。
绝望,我甚至偷看了RegisterClass
的Wine实施。确实可能会失败ERROR_NOT_ENOUGH_MEMORY
,但只有当它无法为类注册分配内存时才会失败。什么是字节。它也使用HeapAlloc
分配内存。由于任何其他原因,任何其他错误代码都不会使RegisterClass
的葡萄酒失败。
对我来说,它首先看起来像Windows中的一个错误。我相信Windows应该在它退出时释放进程分配的所有资源。因此,无论应用程序实施得多么糟糕,以前的运行都不会对资源(例如内存)的后续运行产生任何影响。无论如何,我很乐意找到一个解决方法。
更多事实:除标准系统流程(总共约50个)外,测试系统不会运行任何特殊操作。在我的情况下,它是VMware虚拟机,虽然我的用户显然在真实的物理机器上看到了问题。该过程的先前实例已经消失,因此它们不会被正确终止,这将阻止系统释放资源。大约有500 MB的内存空闲(占总数的一半)。分配的句柄只有大约16000个。
测试VS应用程序的完整代码:
#include "stdafx.h"
#include "windows.h"
#include <fstream>
int _tmain(int argc, _TCHAR* argv[])
{
std::wofstream fout;
fout.open(L"log.txt",std::ios::app);
SetLastError(ERROR_SUCCESS);
fout << L"Allocating heap" << std::endl;
LPVOID Mem = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 10 * 1024 * 1024);
DWORD Error = GetLastError();
fout << L"HeapAlloc [" << std::hex << intptr_t(Mem) << std::dec
<< L"] Error [" << Error << "]" << std::endl;
// ===== Main testing code begins =====
SetLastError(ERROR_SUCCESS);
fout << L"Registering class" << std::endl;
WNDCLASS WndClass;
memset(&WndClass, 0, sizeof(WndClass));
WndClass.lpfnWndProc = &DefWindowProc;
WndClass.lpszClassName = L"TestClass";
WndClass.hInstance = GetModuleHandle(NULL);
ATOM Atom = RegisterClass(&WndClass);
Error = GetLastError();
fout << L"RegisterClass [" << std::hex << intptr_t(Atom) << std::dec
<< L"] Error [" << Error << "]" << std::endl;
// ===== Main testing code ends =====
SetLastError(ERROR_SUCCESS);
fout << L"Allocating heap" << std::endl;
Mem = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 10 * 1024 * 1024);
Error = GetLastError();
fout << L"HeapAlloc [" << std::hex << intptr_t(Mem) << std::dec
<< L"] Error [" << Error << "]" << std::endl;
fout << L"Done" << std::endl;
return 0;
}
输出是(当从Windows 7系统上的Windows Scheduler运行到我的应用程序的数万次运行时进入上述状态时):
Allocating heap
HeapAlloc [ec0020] Error [0]
Registering class
RegisterClass [0] Error [8]
Allocating heap
HeapAlloc [18d0020] Error [0]
Done
答案 0 :(得分:6)
ATOM
是16位类型,因此只有65536个可能的原子值。然而,像窗口类的全局原子具有更有限的范围 - 0xC000到0xFFFF,理论上只给出0x4000(16384)个最大注册类(在实践中可能更少)。检查从RegisterClass()
获得的原子值。如果他们在出错之前接近FFFF
,那可能是你的问题。
编辑:似乎其他人已经运行into the same issue并确定了罪魁祸首:
VCL中有一个严重的错误会消耗掉原子中的原子 私人原子表。 Windows中包含有限数量的私有原子 私有原子表(32767),这是由Windows类共享的, Windows消息,剪贴板格式等每次控件 模块已初始化,它会创建一个新的Windows消息:
ControlAtomString := Format('ControlOfs%.8X%.8X', [HInstance, GetCurrentThreadID]); ControlAtom := GlobalAddAtom(PChar(ControlAtomString)); RM_GetObjectInstance := RegisterWindowMessage(PChar(ControlAtomString));
问题乘以应用程序的DLL数量 包含控件模块。如果你有10个dll,和 一个应用程序,每次运行时,此代码将消耗11个原子。
当系统用完私有原子表中的原子时,否 窗口类可以注册。这意味着,没有窗口程序会 私有原子表已满后能够打开。
您也可以dump the atom table using WinDbg并自行检查此模式。