Mar*_*ryl 9 c++ winapi vcl memory-management out-of-memory
不久,我的问题是,当有大量内存空闲时,为什么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返回8(ERROR_NOT_ENOUGH_MEMORY)的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
Run Code Online (Sandbox Code Playgroud)
(测试应用程序的完整代码在最后)
尽管有错误,但它似乎不是内存问题.通过在RegisterClass调用之前和之后分配10 MB内存来验证的内容(可以在最后的完整测试代码中看到).
绝望,我甚至偷看了Wine的实施RegisterClass.它确实可以失败ERROR_NOT_ENOUGH_MEMORY,但只有当它无法为类注册分配内存时.什么是字节.它也确实使用了内存HeapAlloc.RegisterClass对于任何其他原因,Wine不会因任何其他错误代码而失败.
对我来说,它首先看起来像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;
}
Run Code Online (Sandbox Code Playgroud)
输出是(当从Windows 7系统上的Windows Scheduler运行到我的应用程序的数万次运行时进入上述状态):
Allocating heap
HeapAlloc [ec0020] Error [0]
Registering class
RegisterClass [0] Error [8]
Allocating heap
HeapAlloc [18d0020] Error [0]
Done
Run Code Online (Sandbox Code Playgroud)
ATOM是16位类型,因此只有65536个可能的原子值.然而,像窗口类的全局原子具有更有限的范围 - 0xC000到0xFFFF,理论上只给出0x4000(16384)个最大注册类(实际上可能更少).检查你得到的原子值RegisterClass().如果他们FFFF在错误出现之前就已经接近了,那可能是你的问题.
编辑:似乎其他人遇到了同样的问题,并确定了罪魁祸首:
VCL中存在一个严重的错误,它会占用私有原子表中的原子.Windows在私有原子表(32767)中具有有限数量的私有原子,并且由Windows类,Windows消息,剪贴板格式等共享.每次初始化控件模块时,它都会创建一个新的Windows消息:
Run Code Online (Sandbox Code Playgroud)ControlAtomString := Format('ControlOfs%.8X%.8X', [HInstance, GetCurrentThreadID]); ControlAtom := GlobalAddAtom(PChar(ControlAtomString)); RM_GetObjectInstance := RegisterWindowMessage(PChar(ControlAtomString));问题乘以应用程序包含的包含控件模块的DLL的数量.如果你有10个dll和一个应用程序,这个代码每次运行时将消耗11个原子.
当系统用完私有原子表中的原子时,不能注册任何窗口类.这意味着,在私有原子表填满后,没有窗口程序可以打开.
您也可以使用WinDbg转储原子表并自行检查此模式.
| 归档时间: |
|
| 查看次数: |
5423 次 |
| 最近记录: |