簡単に言うと、私の質問は、大量の空きメモリがある場合に WinAPIRegisterClass
が失敗するERROR_NOT_ENOUGH_MEMORY
のはなぜですか? また、それを防ぐにはどうすればよいですか?
背景: 多くの人がファイル転送の自動化に使用するアプリケーション (WinSCP FTP/SFTP クライアント) を開発しています。Windows スケジューラから毎日、毎分実行している人もいます。
一定回数実行するとアプリケーションが動作しなくなるという報告が多数寄せられています。問題を引き起こす実行回数は正確ではないようですが、数万から数十万の範囲です。また、問題は通常の Windows セッションで実行した場合ではなく、Windows スケジューラで実行した場合にのみ発生するようです。これを100%確認することはできませんが。
また、すべての報告は Windows 2008 R2 に関するもので、一部は Windows 7 に関するものであるようです。
私自身、Windows 7 で問題を再現することができました。システムがこの状態になると、アプリケーションはスケジューラのセッションで起動しなくなります。しかし、通常の通常のセッションでは問題なく起動します。また、他の一部のアプリケーション (すべてである必要はありません) は、スケジューラのセッションでも開始されます。また、この状態ではアプリケーションをデバッグできません。デバッガー (または Process Monitor などのツール) が実行されているときにアプリケーションが読み込まれないためです。
アプリケーションは Embarcadero (旧 Borland) C++ Builder VCL ライブラリを使用しています。VCL 初期化コードのどこかでクラッシュし (myWinMain
は開始されていません)、コード 3 で終了します。初期化コードが何を行っているかを調べたところ、おそらくクラッシュを引き起こしたコードを特定できました (ただし、考えられる多くのコードの 1 つにすぎない可能性があります)。原因)。
原因は、( )RegisterClass
を返す WinAPI 関数のようです。これが発生すると、VCL コードは例外をスローします。例外ハンドラーがまだ配置されていないため、アプリがクラッシュします。8
ERROR_NOT_ENOUGH_MEMORY
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
(テストアプリケーションの完全なコードは最後にあります)
エラーにもかかわらず、メモリの問題ではないようです。呼び出しの前後に 10 MB のメモリを割り当てて確認したことRegisterClass
(最後の完全なテスト コードで確認できます)。
必死になって、私は の 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