为什么注册类失败并ERROR_NOT_ENOUGH_MEMORY
Why RegisterClass fails with ERROR_NOT_ENOUGH_MEMORY?
简而言之,我的问题是,当有大量可用内存时,为什么 WinAPI RegisterClass
会因ERROR_NOT_ENOUGH_MEMORY
而失败,我该怎么做才能防止它?
背景:我正在开发一个应用程序(WinSCP FTP/SFTP 客户端),许多人用它来自动化文件传输。有些人每分钟,每天从Windows调度程序运行它。
我收到很多报告,说在运行一定次数后,应用程序停止工作。触发问题的运行次数似乎并不准确,但它在数万到几十万的范围内。此外,似乎仅在Windows计划程序下运行时才会出现问题,而不是在常规Windows会话中运行时。虽然我不能 100% 确认这一点。
此外,所有报告似乎都是针对Windows 2008 R2 +一些针对Windows 7的报告。同样,这可能只是一个巧合。
我自己能够在Windows 7上重现该问题。一旦系统进入此状态,我的应用程序将不再在调度程序的会话中启动。但它在正常的常规会话中开始得很好。其他一些应用程序(不一定是全部)甚至在调度程序的会话中启动。同样在这种状态下,我无法调试应用程序,因为它甚至在调试器(或进程监视器等工具)运行时也不会加载。
该应用程序正在使用Embarcadero(前Borland)C++Builder VCL库。它在 VCL 初始化代码中的某个地方崩溃(我的WinMain
甚至没有启动)并以代码 3 退出。检查初始化代码在做什么,我可能能够识别触发崩溃的代码(尽管它可能只是许多可能的原因之一)。
罪魁祸首似乎是返回8
(ERROR_NOT_ENOUGH_MEMORY
)的WinAPI函数RegisterClass
。发生这种情况时,VCL 代码会引发异常;由于还没有异常处理程序,它会使应用程序崩溃。
我已经使用VS 2012中开发的非常简单的C++控制台应用程序(将问题与C++生成器和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 内存来验证的内容(可以在最后的完整测试代码中看到)。
绝望之下,我甚至偷看了Wine的实现RegisterClass
。它确实会失败ERROR_NOT_ENOUGH_MEMORY
,但只有当它无法为类注册分配内存时。什么是几个字节。它也确实使用HeapAlloc
分配内存。Wine 不会因为任何其他原因以及任何其他错误代码而使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调度程序运行时,通过数万次运行我的应用程序进入上述状态):
Allocating heap
HeapAlloc [ec0020] Error [0]
Registering class
RegisterClass [0] Error [8]
Allocating heap
HeapAlloc [18d0020] Error [0]
Done
- 在 RAM 用完之前,您可能会用完可用的虚拟地址空间(尤其是对于 32 位进程)。然而,这里的情况似乎并非如此。
- 该错误可能是指用完实际 RAM 以外的其他资源,例如原子。由于
ATOM
是 16 位类型,因此只有 65536 个可能的原子值。然而,像窗口类这样的全局原子的范围更加有限 - 0xC000到0xFFFF,理论上最多只有0x4000(16384)个注册类(在实践中可能更少)。
检查您从RegisterClass()
获得的原子值。如果他们在出错之前接近FFFF
,那可能是您的问题。
编辑:似乎其他人遇到了同样的问题并确定了罪魁祸首:
VCL中有一个严重的错误,它会吞噬 专用原子表。Windows 在 私有原子表 (32767),这由 Windows 类共享, 视窗消息、剪贴板格式等每次控制 模块初始化后,它会创建一个新的 Windows 消息:
ControlAtomString := Format('ControlOfs%.8X%.8X', [HInstance, GetCurrentThreadID]); ControlAtom := GlobalAddAtom(PChar(ControlAtomString)); RM_GetObjectInstance := RegisterWindowMessage(PChar(ControlAtomString));
问题乘以应用程序的DLL数量 包含包括控件模块。如果您有 10 个 dll,并且 一个应用程序,此代码每次运行时将消耗 11 个原子。
当系统用完专用原子表中的原子时,否 可以注册窗口类。这意味着,没有窗口程序会 能够在专用原子表已满后打开。
还可以使用 WinDbg 转储原子表,并自行检查此模式。
- OpenMP卸载说'fatal error: could not find accel/nvptx-none/mkoffload'
- 使用JsonCpp将数据返回到带有pybind11的python会在python调用中产生Symbol not foun
- OpenCV Android C++ imwrite not found
- 应用程序崩溃并显示"symbol _ZdlPvm, version Qt_5 not defined in file libQt5Core.so.5 with link time reference"
- 使用单词"not"作为C ++类的名称会导致VS2019错误
- 错误"Could not find Boost"(缺少:上下文标头)
- 如何修复"error: ‘_1’ was not declared in this scope"?
- 套接字连接"Operation not permitted"错误,甚至使用升压/平发器根.cpp
- Is !NaN not a NaN?
- 为什么我会" void value not ignored as it ought to be"?
- 解决"ld: library not found for -ltensorflow_framework.2.3.0"
- 加载与引用 .NET DLL 位于同一文件夹中的引用的 .NET DLL 时"Not found"异常
- Directx 11 - CompileFromFile() is not compiling
- 方法错误"not all control paths return a value"和方法不返回值
- Centos7 g++ "to_string is not in a member of std"
- 将系数存储在头文件的数组中("does not name a type"错误)
- QGraphicsItems not showing up QGraphicsScene
- 在调试模式下引发C++ "deque iterator not dereferencable"异常
- InitializeCriticalSectionEx Not Located In KERNEL32.Dll
- VS2015的多处理器编译在运行cl时会产生"not enough quota is available to process this command".exe