- android - RelativeLayout 背景可绘制重叠内容
- android - 如何链接 cpufeatures lib 以获取 native android 库?
- java - OnItemClickListener 不起作用,但 OnLongItemClickListener 在自定义 ListView 中起作用
- java - Android 文件转字符串
很快,我的问题是,当有大量可用内存时,为什么 WinAPI RegisterClass
会失败并返回 ERROR_NOT_ENOUGH_MEMORY
,我该怎么做才能防止它发生?
背景:我正在开发一个应用程序(WinSCP FTP/SFTP 客户端),许多人使用它来自动传输文件。有些人每天每分钟都在通过 Windows 调度程序运行它。
我收到很多报告,在运行一定次数后应用程序停止工作。触发问题的运行次数似乎并不准确,但在几万到几十万之间。此外,问题似乎仅在 Windows 调度程序下运行时出现,而不是在常规 Windows session 中运行时出现。虽然我不能 100% 证实这一点。
此外,所有报告似乎都针对 Windows 2008 R2 + 一些针对 Windows 7。同样,这可能只是巧合。
我自己能够在 Windows 7 上重现该问题。一旦系统进入此状态,我的应用程序就不再在调度程序的 session 中启动。但它在正常的常规 session 中开始就好了。还有一些其他应用程序(不一定是全部)甚至在 Scheduler 的 session 中启动。同样在这种状态下,我无法调试应用程序,因为它甚至在调试器(或 Process Monitor 等工具)运行时都不会加载。
该应用程序使用 Embarcadero(前 Borland)C++ Builder VCL 库。它在 VCL 初始化代码的某处崩溃(我的 WinMain
甚至没有启动)并以代码 3 退出。检查初始化代码在做什么,我可能能够识别触发崩溃的代码(尽管它可能只是许多可能原因之一)。
罪魁祸首似乎是返回 8
(ERROR_NOT_ENOUGH_MEMORY
) 的 RegisterClass
WinAPI 函数。发生这种情况时,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
分配内存。 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 Scheduler 运行时,我的应用程序运行了数万次进入上述状态):
Allocating heap
HeapAlloc [ec0020] Error [0]
Registering class
RegisterClass [0] Error [8]
Allocating heap
HeapAlloc [18d0020] Error [0]
Done
最佳答案
ATOM
是 16 位类型,因此只有 65536 个可能的原子值。但是,像窗口类那样的全局原子具有更有限的范围 - 从 0xC000 到 0xFFFF,理论上最多只能给你 0x4000 (16384) 个注册类(在实践中可能更少)。检查从 RegisterClass()
获得的原子值。如果他们在出错之前接近 FFFF
,那可能是你的问题。
编辑:似乎其他人已经运行了into the same issue并确定了罪魁祸首:
There is a serious bug in the VCL that will eat up atoms in the private atom table. Windows has a limited number of private atoms in the private atom table (32767), and this is shared by Windows classes, Windows Messages, Clipboard formats, etc. Every time the controls module is initialized, it creates a new Windows Message:
ControlAtomString := Format('ControlOfs%.8X%.8X',
[HInstance, GetCurrentThreadID]);
ControlAtom := GlobalAddAtom(PChar(ControlAtomString));
RM_GetObjectInstance := RegisterWindowMessage(PChar(ControlAtomString));The problem is multiplied by the number of DLL's that the application contains that includes the controls module. If you have 10 dll's, and one application, this code will consume 11 atoms every time it is ran.
When the system is ran out of atoms in the private atom table, no window class can be registered. This means, no windowed programs will be able to open after the private atom table is full.
您也可以dump the atom table using WinDbg并自己检查这种模式。
关于c++ - 为什么 RegisterClass 因 ERROR_NOT_ENOUGH_MEMORY 而失败?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18893366/
我们的应用程序在一个特定用户的计算机上失败了 ERROR_NOT_ENOUGH_MEMORY (“没有足够的存储空间来处理此命令”)。 该错误显然是在我们正在使用的 Delphi VCL 框架的某个深
我试图让我的软件通过内存映射与预先存在的第三方软件进行通信。我被告知要将结构写入由其他软件创建的内存映射文件。我设法打开了该文件,并且肯定已正确创建它,但是当我尝试映射该文件时出现错误 8 (ERRO
这是一个很奇怪的问题,但我相信,这是 SO 的主题。 简介: 我有一个用 C# 编写的服务,它调用我的 C++ 库。 C++ 库通过 WinExec 执行一些 3rdparty 软件。 第 3 方软件
很快,我的问题是,当有大量可用内存时,为什么 WinAPI RegisterClass 会失败并返回 ERROR_NOT_ENOUGH_MEMORY,我该怎么做才能防止它发生? 背景:我正在开发一个应
代码: #include #include int main() { system("mode 128"); int range = 0xCFE2; char* buf = new c
我正在使用 C++ 制作简单的 dll 数据包嗅探器,它将挂接到应用程序,并将接收到的数据包写入 INI 文件。不幸的是,在 20-30 分钟后,主要应用程序崩溃了。 当接收到数据包时,receive
最近,我尝试在我的电脑上在win10(64位)平台上编译ffmpeg3.4.2, 以前的一些步骤进展顺利,但是当我运行“make”时出现错误,它说: 'ERROR_NOT_ENOUGH_MEMORY'
我是一名优秀的程序员,十分优秀!