【问题标题】:System Error. Code: 8. Not enough storage is available to process this command系统错误。代码:8. 没有足够的存储空间来处理这个命令
【发布时间】:2010-10-05 04:54:18
【问题描述】:

我们有一些 Win32 应用程序(在 Delphi 2006 中编码),有时用户会收到一条错误消息,提示 “系统错误。代码:8。没有足够的存储空间来处理此命令。” .

从堆栈跟踪来看,它似乎总是在 CreateWnd 调用期间

Main ($1edc):
004146cc +070 app.exe SysUtils               RaiseLastOSError
00414655 +005 app.exe SysUtils               RaiseLastOSError
004ce44c +130 app.exe Controls               TWinControl.CreateWnd
00535a72 +022 app.exe cxControls             TcxControl.CreateWnd
004ce82a +016 app.exe Controls               TWinControl.CreateHandle
00553d21 +005 app.exe cxContainer            TcxContainer.CreateHandle
00586ef1 +005 app.exe cxEdit                 TcxCustomEdit.CreateHandle
005c331d +005 app.exe cxDropDownEdit         TcxCustomDropDownEdit.CreateHandle
004ceaf0 +074 app.exe Controls               TWinControl.UpdateShowing
004ceb1e +0a2 app.exe Controls               TWinControl.UpdateShowing
004cebdc +03c app.exe Controls               TWinControl.UpdateControlState
004d118a +026 app.exe Controls               TWinControl.CMVisibleChanged
004cb713 +2bb app.exe Controls               TControl.WndProc
004cf569 +499 app.exe Controls               TWinControl.WndProc
004b727d +4c1 app.exe Forms                  TCustomForm.WndProc
004cb3a0 +024 app.exe Controls               TControl.Perform
004c9f6a +026 app.exe Controls               TControl.SetVisible
004b6c46 +03a app.exe Forms                  TCustomForm.SetVisible
004baf1b +007 app.exe Forms                  TCustomForm.Show
004bb151 +14d app.exe Forms                  TCustomForm.ShowModal
007869c7 +0d3 app.exe UfrmPrice      770 +19 TfrmPrice.EditPrice
0078655d +009 app.exe UfrmPrice      628  +0 TfrmPrice.actNewBidExecute
00431ce7 +00f app.exe Classes                TBasicAction.Execute
004c2cb5 +031 app.exe ActnList               TContainedAction.Execute
004c397c +050 app.exe ActnList               TCustomAction.Execute
00431bb3 +013 app.exe Classes                TBasicActionLink.Execute
004af384 +090 app.exe Menus                  TMenuItem.Click
004b059f +013 app.exe Menus                  TMenu.DispatchCommand
004b16fe +082 app.exe Menus                  TPopupList.WndProc
004b164d +01d app.exe Menus                  TPopupList.MainWndProc
004329a8 +014 app.exe Classes                StdWndProc
7e4196b2 +00a USER32.dll                     DispatchMessageA
004bea60 +0fc app.exe Forms                  TApplication.ProcessMessage
004bea9a +00a app.exe Forms                  TApplication.HandleMessage
004becba +096 app.exe Forms                  TApplication.Run
008482c5 +215 app.exe AppName        129 +42 initialization

我一直无法查明造成这种情况的原因,而且这种情况很少发生,我没有关注过,但我想找出导致它的原因并希望能纠正它...

编辑:完整的堆栈跟踪

编辑 2: 更多信息... 今天遇到这种情况的客户已经安装了我的应用程序大约 4 个月,并且它每天在他的 PC 上运行 8 小时。该问题仅在今天出现并且即使他杀死了我的应用程序并重新启动它,它仍然会再次出现。他系统上的其他应用程序都没有异常行为。重新启动后,问题完全消失。这是否指向史蒂夫提到的堆短缺?

编辑 3: 有趣的 msdn 博客文章 herehere 关于桌面堆的主题。虽然我不确定这是否是问题的原因,但看起来很可能。

【问题讨论】:

    标签: delphi winapi


    【解决方案1】:

    编译器中可能存在错误,可以肯定的是,是您的应用程序中的某些东西导致了问题。可能是您的应用程序正在泄漏窗口句柄或其他一些 GUI 对象,如钢笔/画笔?这可能是一个原因。

    【讨论】:

      【解决方案2】:

      如果您的程序使用大量 Windows 资源,则可能是资源堆短缺。

      有一个注册表项可以增加以增加 XP 的堆大小。对于 Vista,Microsoft 已经将默认值设置得更高。我建议将默认的 3072 更改为至少 8192。

      此信息记录在MS Knowledge Base 中(或搜索“内存不足”)。有关参数值的更多详细信息,请参阅文章 KB184802

      我建议您阅读知识库文章,但有关更改的基本信息是:

      1. 运行注册表编辑器 (REGEDT32.EXE)。

      2. 从 HKEY_LOCAL_MACHINE 子树中,转到以下键:

        \System\CurrentControlSet\Control\Session Manager\SubSystem
        
      3. 在屏幕右侧双击键:

        windows 
        
      4. 在弹出窗口中,您将看到一个很长的字段被选中。将光标移动到字符串开头附近以查找此内容(值可能会有所不同):

        SharedSection=1024,3072,512
        
      5. SharedSection 使用以下格式指定系统和桌面堆:SharedSection=xxxx,yyyy,zzz 其中xxxx 定义系统范围堆的最大大小(以千字节为单位),yyyy 定义每个桌面的大小堆,zzz 定义“非交互式”窗口站的桌面堆大小。

      6. 仅将 yyyy 值更改为 8192(或更大),然后按 OK。

      7. 退出注册表编辑器并重新启动 PC 以使更改生效。

      祝你好运。

      【讨论】:

      • 谢谢。我会和体验最多的客户一起尝试。我从来没有能够在本地复制...
      • 有什么方法可以测量这个堆是否已经耗尽?
      • Marius,您是如何获得要添加到错误报告中的值的?从其他答案:我是这么想的,所以我将它们添加到我的错误报告中... 用户句柄限制:10000 GDI 句柄限制:10000 正在使用的用户句柄:562 正在使用的 GDI 句柄:520 – Marius(2 月 3 日 21:09 )
      • @Marius,测量并不总是像检查手柄限制那么简单,但您可以在这里阅读:stackoverflow.com/questions/548971/…
      • @SteveBlack 我的共享部分显示 20480 在 yyyyy 我将其更改为什么值?
      【解决方案3】:

      您可以使用 Microsoft 的 Desktop Heap Monitor 查看堆统计信息(使用 % 等),可在以下位置获得:

      http://www.microsoft.com/downloads/details.aspx?familyid=5cfc9b74-97aa-4510-b4b9-b2dc98c8ed8b&displaylang=en

      【讨论】:

        【解决方案4】:

        我最近在使用一些 Twain 代码时注意到了这个错误(系统错误。代码:8。没有足够的存储空间......),它发生在我的计算机上而不是我同事的计算机上,我们机器之间唯一真正的区别是我将笔记本电脑屏幕用作第二个显示器,因此我的桌面总尺寸更大。

        我找到了史蒂夫·布莱克(Steve Black)指出的上述问题的文档,但我找到了一种不需要编辑注册表的方法(至少修复了我机器上的错误):

        旧代码使用的是

          DC := GetDC(Owner.VirtualWindow);
          // ...
          ReleaseDC(Owner.VirtualWindow, DC);

        我发现用这个替换它消除了我的错误

          DC := CreateCompatibleDC(Owner.VirtualWindow);
          // ...
          DeleteDC(DC);


        我不知道这是否与您的问题有关,但将来可能对其他人有所帮助。

        【讨论】:

          【解决方案5】:

          其实这是 ATOM 表的问题。 I reported this issue to Embarcadero (saved in Wayback Machine) 因为这让我很伤心。

          如果您监控全局原子表,您会看到 Delphi 应用程序正在泄漏原子,从而留下您的应用程序的 id 而不将其从内存中删除:

          您将看到大量以下项目:

          **Delphi000003B4*
          
          *Controlofs0040000000009C0**
          

          基本上,由于您无法注册超过 0xFFFF 的不同 Windows 消息 ID,因此系统将返回“系统错误。代码:8。没有足够的存储空间来处理此问题命令”。然后您将无法启动任何创建窗口的应用程序。

          Another issue (saved in Wayback Machine) 已在 Embarcadero QC Central 中报告。

          此问题在 Windows 7 / Windows Server 2008 下出现。在 Windows Server 2003 及之前运行它的事实是由于错误的实现,一旦 AT​​OM 的索引围绕最大 16384 单位,它会回收 ATOM。

          请随时使用我的Global Atom Monitor 来检查您的 Delphi 应用程序是否泄漏原子。

          要解决此问题,您需要来自 Embarcadero 的补丁,或从 www.idefixpack.de/blog/downloads 下载 ControlsAtomFix1.7z

          【讨论】:

          • 谢谢,谢谢,谢谢。我唯一的失望是我在今天之前没有看到这个 SO 答案。和 Christian 一样,我已经花费(并且浪费了)无数小时试图确定我们的 DataSnap 应用程序服务器为何会泄漏资源。我什至无法确定它泄漏了哪种资源。尽管我们的应用程序不会连续停止和启动,但它确实每天多次按需加载和卸载 DLL。 (它在不使用时被卸载,主要是为了让 DLL 在不停止服务的情况下轻松更新。)
          • 您在显示器上遇到的错误是什么?如果那是您的操作系统,我还没有在 Windows 10 上尝试过。
          • @JordiCorbilla 我真的可以使用 Global Atom Monitor 工具——但我没有 Delphi 编译器,也没有开源编译器。是否有机会将已编译的 exe 添加到 github 存储库中?
          • @Z80 我修复了这些链接并添加了 ControlsAtomFix1.7z 的下载链接,其中包含 Andreas Hausladen 为 Delphi 6 提供的修复,包括 XE。如果您仍然看到这个错误,那么您的 Delphi 应用程序正在使用许多与 Controls 中不同的注册 Windows 消息并且需要调整。请参阅devblogs.microsoft.com/oldnewthing/20150319-00/?p=44433 为什么。
          【解决方案6】:

          找了2年,感谢Jordi Corbilla answer终于找到了!

          简而言之:Delphi 源代码存在导致此问题的错误!

          让我们了解发生了什么:

          Windows 有一个称为“Atom 表”的内存区域,用于应用程序相互通信 (see more)。

          此外,Windows 有另一个“内存区域”,称为“窗口消息系统”,用于相同目的 (see more)。

          这两个内存区域各有“16k 个插槽”。在第一个中,可以使用以下 Windows API REMOVE 原子:

          GlobalDeleteAtom // for removing an atom added by "GlobalAddAtom"
          

          在第二个“区域”中,我们只是无法移除任何东西!

          RegisterWindowMessage 函数通常用于注册 用于在两个协作应用程序之间进行通信的消息。如果 两个不同的应用程序注册相同的消息字符串, 应用程序返回相同的消息值。 消息仍然存在 注册直到会话结束

          Delphi 编译的应用程序(至少由 D7 编译)将在“消息区”中添加一条记录,而在“Atom 表”中添加一些其他记录每次启动时。该应用程序会在应用程序关闭时尝试将其删除,但我发现许多(和许多)“原子泄漏”,即使在应用程序关闭后也是如此。

          此时您可以看到,如果您的服务器每天启动数千个应用程序,您可能应该很快达到 16k 限制,并且问题开始了!此时的解决方案?只需要一次重启。

          那么,我们能做些什么呢?好吧,我的朋友,很抱歉告诉你,但我们需要修复 Delphi 源代码并重新编译所有应用程序。

          首先,打开单元Controls.pas,替换下面一行:

          RM_GetObjectInstance := RegisterWindowMessage(PChar(ControlAtomString));
          

          为:

          RM_GetObjectInstance := RegisterWindowMessage('RM_GetObjectInstance');
          

          然后重新编译 Delphi 包和您的应用程序。

          即使在应用程序关闭后我也发现原子泄漏,我创建了一个垃圾收集任何遗留的原子的应用程序。它只是每小时运行以下代码:

          procedure GarbageCollectAtoms;
          var i, len : integer;
              cstrAtomName: array [0 .. 1024] of char;
              AtomName, Value, procName: string;
              ProcID,lastError : cardinal;
              countDelphiProcs, countActiveProcs, countRemovedProcs, countCantRemoveProcs, countUnknownProcs : integer;
          
              // gets program's name from process' handle
              function getProcessFileName(Handle: THandle): string;
              begin
                Result := '';
                { not used anymore
                try
                  SetLength(Result, MAX_PATH);
                  if GetModuleFileNameEx(Handle, 0, PChar(Result), MAX_PATH) > 0 then
                    SetLength(Result, StrLen(PChar(Result)))
                  else
                    Result := '';
                  except
                end;
                }
              end;
          
              // gets the last 8 digits from the given atomname and try to convert them to and integer
              function getProcessIdFromAtomName(name:string):cardinal;
              var l : integer;
              begin
                result := 0;
                l := Length(name);
                if (l > 8) then
                begin
                  try
                    result := StrToInt64('$' + copy(name,l-7,8));
                    except
                      // Ops! That should be an integer, but it's not!
                      // So this was no created by a 'delphi' application and we must return 0, indicating that we could not obtain the process id from atom name.
                      result := 0;
                  end;
                end;
              end;
          
              // checks if the given procID is running
              // results: -1: we could not get information about the process, so we can't determine if is active or not
              //           0: the process is not active
              //           1: the process is active
              function isProcessIdActive(id: cardinal; var processName: string):integer;
              var Handle_ID: THandle;
              begin
                result := -1;
                try
                  Handle_ID := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, false, id);
                  if (Handle_ID = 0) then
                  begin
                    result := 0;
                  end
                  else
                  begin
                    result := 1;
                    // get program's name
                    processName := getProcessFileName(Handle_ID);
                    CloseHandle(Handle_ID);
                  end;
                  except
                    result := -1;
                end;
              end;
          
              procedure Log(msg:string);
              begin
                // Memo1.Lines.Add(msg);
              end;
          
          
          begin
          
            // initialize the counters
            countDelphiProcs := 0;
            countActiveProcs := 0;
            countRemovedProcs := 0;
            countUnknownProcs := 0;
          
            // register some log
            Log('');
            Log('');
            Log('Searching Global Atom Table...');
          
            for i := $C000 to $FFFF do
            begin
              len := GlobalGetAtomName(i, cstrAtomName, 1024);
              if len > 0 then
              begin
                AtomName := StrPas(cstrAtomName);
                SetLength(AtomName, len);
                Value := AtomName;
                // if the atom was created by a 'delphi application', it should start with some of strings below
                if (pos('Delphi',Value) = 1) or
                   (pos('ControlOfs',Value) = 1) or
                   (pos('WndProcPtr',Value) = 1) or
                   (pos('DlgInstancePtr',Value) = 1) then 
                begin
                  // extract the process id that created the atom (the ProcID are the last 8 digits from atomname)
                  ProcID := getProcessIdFromAtomName(value);
                  if (ProcId > 0) then
                  begin
                    // that's a delphi process
                    inc(countDelphiProcs);
                    // register some log
                    Log('');
                    Log('AtomName: ' + value + ' - ProcID: ' + inttostr(ProcId) + ' - Atom Nº: ' + inttostr(i));
                    case (isProcessIdActive(ProcID, procName)) of
                      0: // process is not active
                      begin
                        // remove atom from atom table
                        SetLastError(ERROR_SUCCESS);
                        GlobalDeleteAtom(i);
                        lastError := GetLastError();
                        if lastError = ERROR_SUCCESS then
                        begin
                          // ok, the atom was removed with sucess
                          inc(countRemovedProcs);
                          // register some log
                          Log('- LEAK! Atom was removed from Global Atom Table because ProcID is not active anymore!');
                        end
                        else
                        begin
                          // ops, the atom could not be removed
                          inc(countCantRemoveProcs);
                          // register some log
                          Log('- Atom was not removed from Global Atom Table because function "GlobalDeleteAtom" has failed! Reason: ' + SysErrorMessage(lastError));
                        end;
                      end;
                      1: // process is active
                      begin
                        inc(countActiveProcs);
                        // register some log
                        Log('- Process is active! Program: ' + procName);
                      end;
                      -1: // could not get information about process
                      begin
                        inc(countUnknownProcs);
                        // register some log
                        Log('- Could not get information about the process and the Atom will not be removed!');
                      end;
                    end;
                  end;
                end;
              end;
            end;
            Log('');
            Log('Scan complete:');
            Log('- Delphi Processes: ' + IntTostr(countDelphiProcs) );
            Log('  - Active: ' + IntTostr(countActiveProcs) );
            Log('  - Removed: ' + IntTostr(countRemovedProcs) );
            Log('  - Not Removed: ' + IntTostr(countCantRemoveProcs) );
            Log('  - Unknown: ' + IntTostr(countUnknownProcs) );
          
            TotalAtomsRemovidos := TotalAtomsRemovidos + countRemovedProcs;
          
          end;
          

          (以上代码基于this code

          在那之后,我再也没有遇到过这个 f** 错误!

          后期更新:

          另外,这也是这个错误的来源:Application error: fault address 0x00012afb

          【讨论】:

          • 谢谢克里斯蒂安。我会把它放到我们的下一个版本中,看看它是否有帮助。感谢分享
          • 一个问题,以确保我理解您的解释:这种“内存泄漏”仅通过运行应用程序(任何应用程序)或仅 Delphi 应用程序出现?
          • 我确认直到 Delphi 7(相当老......)他们可能已经修复(或没有?)。除了 Delphi 构建的应用程序之外,从未见过其他应用程序会发生这种情况。
          • @Christian - 但我在 Delphi XE7 中有这个错误!
          • @Christian - procName 未初始化!
          【解决方案7】:

          对我来说,这只是一堆以幻灯片方式解压缩的 TJPEGImage,最终内存不足。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2010-10-07
            • 2018-08-30
            • 2010-10-28
            • 1970-01-01
            • 2012-05-14
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多