gpt4 book ai didi

c++ - 为什么 RegisterClass 因 ERROR_NOT_ENOUGH_MEMORY 而失败?

转载 作者:可可西里 更新时间:2023-11-01 16:39:33 26 4
gpt4 key购买 nike

很快,我的问题是,当有大量可用内存时,为什么 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

最佳答案

  1. 您可能会在用完 RAM 之前用完可用的虚拟地址空间(尤其是对于 32 位进程)。然而,这里的情况似乎并非如此。
  2. 该错误可能是指耗尽了实际 RAM 以外的其他资源,例如原子。由于 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/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com