C++:Утечки GDI в коде. Трабл проги в XP, а в Виста(7?) и ОС?

Обсуждения по программированию для ОС Windows безотносительно используемого языка программирования. Windows NT, Win32, Windows API, ядро и драйверы.
Admiralisimys
Постоялец
Постоялец
 
Сообщения: 318
Зарегистрирован: 01.06.2009 (Пн) 10:26

C++:Утечки GDI в коде. Трабл проги в XP, а в Виста(7?) и ОС?

Сообщение Admiralisimys » 04.11.2010 (Чт) 15:18

Здаствуйте.
Поскольку отельного раздела С++ нет спрошу здесь.
Есть следующий код (иллюстрирующий пример)
Код: Выделить всё
#include <windows.h>

HWND hMainWindow = HWND_DESKTOP;

#define OBJECT_COUNT 10

class Object
{
protected:
   int x, y; bool bAllowUp; RECT rectBar, inRect;
public:
   virtual void UpDate() = 0;
   virtual void Render() = 0;
};

class HorObject : public Object
{
public:
   HorObject(int _X, int _Y)
   {
      bAllowUp = true;
      x = _X;
      inRect.top = rectBar.top = y = _Y;

      inRect.bottom = rectBar.bottom = y + 40;
   }
   void UpDate()
   {
      if (x < 10)
         bAllowUp = true;
      else if (x > 750)
         bAllowUp = false;

      if (bAllowUp)
      {
         inRect.left = x;
         x += 10;
         inRect.right = x;
      }
      else
      {
         inRect.right = x + 50;
         inRect.left = x + 30;
         x -= 10;
      }
      
      rectBar.left = x;
      rectBar.right = x + 40;
   };
   void Render()
   {
      InvalidateRect(hMainWindow, &inRect, TRUE);
      HDC hDc = GetDC(hMainWindow);
      FillRect(hDc, &rectBar, CreateSolidBrush(RGB(0, 0, 255)));
      ReleaseDC(hMainWindow, hDc);
      hDc = NULL;
   };
};

class VertObject : public Object
{
public:
   VertObject(int _X, int _Y)
   {
      bAllowUp = true;
      inRect.left = rectBar.left = x = _X;
      y = _Y;

      inRect.right = rectBar.right = x + 40;
   }
   void UpDate()
   {
      if (y < 10)
         bAllowUp = true;
      else if (y > 530)
         bAllowUp = false;

      if (bAllowUp)
      {
         inRect.top = y;
         y += 10;
         inRect.bottom = y;
      }
      else
      {
         inRect.bottom = y + 50;
         inRect.top = y + 30;
         y -= 10;
      }

      rectBar.top = y;
      rectBar.bottom = y + 40;
   };
   void Render()
   {
      InvalidateRect(hMainWindow, &inRect, TRUE);
      HDC hDc = GetDC(hMainWindow);
      FillRect(hDc, &rectBar, CreateSolidBrush(RGB(255, 0, 0)));
      ReleaseDC(hMainWindow, hDc);
      hDc = NULL;
   };
};

class BoxManager
{
private:
   Object *mObjects[OBJECT_COUNT];
public:
   BoxManager()
   {
   for(int i = 0; i < OBJECT_COUNT/2; i++)
      if(mObjects[i])
         mObjects[i] = new HorObject(i*100, (4 - i)*100 + 65);
   for(int i = OBJECT_COUNT/2; i < OBJECT_COUNT; i++)
      if(mObjects[i])
         mObjects[i] = new VertObject((10 - i)*100 + 65, i*50 + 75);
   }
   
   void UpDate()
   {
   for(int i = 0; i < OBJECT_COUNT; i++)
      if(mObjects[i])
         mObjects[i] -> UpDate();
   }

   void Render()
   {
   for(int i = 0; i < OBJECT_COUNT; i++)
      if(mObjects[i])
         mObjects[i] -> Render();
   }

   ~BoxManager()
   {
   for(int i = 0; i < OBJECT_COUNT; i++)
   if(mObjects[i])
   {
      delete mObjects[i];
      mObjects[i] = NULL;
   }
   }
};

BoxManager *manager = NULL;

LRESULT CALLBACK WindowFunc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
   switch (message)
   {
      case WM_TIMER:
         manager -> UpDate();
         manager -> Render();
      break;
      case WM_CLOSE:
         DestroyWindow(hWnd);
      break;
      case WM_DESTROY:
         PostQuitMessage (0);
      break;
      default:
         return DefWindowProc (hWnd, message, wParam, lParam);
   }
   return 0;
}

int WINAPI wWinMain (HINSTANCE hThisInst, HINSTANCE hPrevInst, wchar_t *lpszArgs, int nWinMode)
{
   wchar_t ClassName[16] = L"AppClass", WindowName[16] = L"Boxes";
   WNDCLASS wcl;
   wcl.cbClsExtra = 0;
   wcl.cbWndExtra = 0;
   wcl.lpszMenuName = NULL;
   wcl.style = CS_HREDRAW | CS_VREDRAW;
   wcl.lpfnWndProc = WindowFunc;
   wcl.hInstance = hThisInst;
   wcl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
   wcl.hCursor = LoadCursor (NULL, IDC_ARROW);
   wcl.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
   wcl.lpszClassName = ClassName;

   if (!RegisterClass (&wcl))
      return -1;

   hMainWindow = CreateWindow (
            ClassName, WindowName, WS_SYSMENU | WS_MINIMIZEBOX,
            CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, HWND_DESKTOP,
            NULL, hThisInst, NULL);

   if (!hMainWindow)
      return -1;

   manager = new BoxManager();

   ShowWindow (hMainWindow, nWinMode);
   UpdateWindow (hMainWindow);

   SetTimer(hMainWindow, 1, 25, (TIMERPROC) NULL);
   MSG msg;
   while(GetMessage(&msg, NULL, 0, 0) > 0)
   {
      TranslateMessage (&msg);
      DispatchMessage (&msg);
   }
   KillTimer(hMainWindow, 1);

   delete manager;
   manager = NULL;
   return (int)msg.wParam;
}

Во-первых, не ясно, почему утечка наблюдается вовсе? Вроде всё как книга пишет: вызвал GetDC() вызови ReleaseDC(). В коде даже более, добавил зануление - hDc = NULL.
Но нет, программа набирает лимит 9999 объектов GDI, и далее, как следствие, по ней двигаются белые объекты. Это в ХР. В Висте же... в общем, кому нужна цветомузыка, это самое оно. При достижении указанного лимита экран монитор начинает мерцать самыми разными цветами. Если предварительно не запущен диспетчер задач, с мониторингом этого процесса (в частности там и наблюдал количество GDI объектов), вернуть управления системой будет крайне затруднительно :(
На Севен не проверял, но вероятнее всего там будет таже "картина" что и в Висте.

GDIPlus вариант
Код: Выделить всё
замена строки кода FillRect(hDc, &rectBar, CreateSolidBrush(RGB(0, 0, 255)));
на такие три строки
Graphics g(hDc);
g.SetPageUnit(UnitPixel);
g.FillRectangle(new SolidBrush(Color(255, 0, 0, 255)), Rect (rectBar.left, rectBar.top, rectBar.right - rectBar.left, rectBar.bottom - rectBar.top));

отрабатывает нормально (не зависимо от ОС), без утечек и с корректным отображением.

Где проблема?

Спасибо за уделённое внимание.

Alec
Бывалый
Бывалый
 
Сообщения: 275
Зарегистрирован: 31.08.2008 (Вс) 0:15
Откуда: Ростов-на-Дону

Re: C++:Утечки GDI в коде. Трабл проги в XP, а в Виста(7?) и

Сообщение Alec » 04.11.2010 (Чт) 16:19

Admiralisimys писал(а):Вроде всё как книга пишет: вызвал GetDC() вызови ReleaseDC()

А книга разве не пишет, что создал объект CreateSolidBrush(), удали его DeleteObject()?
Иногда лучше вовремя остановиться...
И начать заново!

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16473
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: C++:Утечки GDI в коде. Трабл проги в XP, а в Виста(7?) и

Сообщение Хакер » 04.11.2010 (Чт) 16:20

Честно говоря, крайне странное сочетание: делать InvalidateRect, а потом рисовать. InvalidateRect — это вежливый способ вызвать WindowProc::WM_PAINT для нужной области, чтобы перерисовать оную. WindowProc::WM_PAINT же перерисовав, должен как бы в ответ вызвать ValidateRect. Последняя вызывается неявно.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

Admiralisimys
Постоялец
Постоялец
 
Сообщения: 318
Зарегистрирован: 01.06.2009 (Пн) 10:26

Re: C++:Утечки GDI в коде. Трабл проги в XP, а в Виста(7?) и

Сообщение Admiralisimys » 04.11.2010 (Чт) 16:52

Alec пишет ;)
По крайне мере в памяти отложилось, что для чего-то там вызов не обязателен. Напомнил, это для GetStockObject.
Для CreateSolidBrush не выделял переменных, в основном их использовал лишь для wcl.hbrBackground. Стало быть, и там утечки допустил.
Спасибо Alec за подсказку. Исправил (соответственно в обоих местах с CreateSolidBrush)
Код: Выделить всё
HBRUSH hBrush = CreateSolidBrush(RGB(0, 0, 255));
FillRect(hDc, &rectBar, hBrush);
DeleteObject(hBrush);
По началу 8, потом 11 GDI объектов для данного примера, и величина не растёт :)

Виста в этой ситуации не приятно удивила, своей обработкой прог с утечкой в GDI ресурсах. И обновления, защищающего от программ с GDI утечками, вроде нет.

Хакер даже не знаю, как тут выйти из ситуации. Поначалу экран вовсе чистился, а очистка небольших ректов это уже прогресс. В первом случаи блики сильно заметны. InvalidRect нужно для очистки формы от действий, произошедших в предыдущем "кадре".
А то что InvalidRect вызывает событие WM_PAINT в курсе, даже думал что некая бесконечная рекурсия приводит к данным утечкам.
По началу хотел воспользоваться данным функционалом, поместив в WM_PAINT manager -> Render(). Соответственно это избавит от GetDC так как в WM_PAINT можно будет взять hDc через BeginPaint который предать в переписанный под новые условия метод Render(), способный принимать параметры. А в конце события WM_PAINT вызвать EndPaint.
Да вот из-за утечки там выход на лимит был ещё быстрее. Возможно опробую и данный вариант.
Спасибо за рассуждения.

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16473
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Re: C++:Утечки GDI в коде. Трабл проги в XP, а в Виста(7?) и

Сообщение Хакер » 04.11.2010 (Чт) 18:11

Не Invalid, а Invalidate. Она не «вызывает событие», она добавляет рект к «невалидному региону». Наличие ненулевого «невалидного региона» пораждает регулярную отправку WM_PAINT (пока регион не станет пустым).

Поизучай принцип работы GDI и с GDI.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

BV
Thinker
Thinker
Аватара пользователя
 
Сообщения: 3987
Зарегистрирован: 12.09.2004 (Вс) 0:55
Откуда: Молдавия, г. Кишинёв

Re: C++:Утечки GDI в коде. Трабл проги в XP, а в Виста(7?) и

Сообщение BV » 04.11.2010 (Чт) 18:49

Хакер, добавь раздел C++ на форум, сколько можно обсуждать его в трепе..
const char *out = "|*0>78-,+<|"; size_t cc = char_traits<char>::length(out);
for (size_t i=0;i<cc;i++){cout<<static_cast<char>((out[i]^89));}cout<<endl;

Admiralisimys
Постоялец
Постоялец
 
Сообщения: 318
Зарегистрирован: 01.06.2009 (Пн) 10:26

Re: C++:Утечки GDI в коде. Трабл проги в XP, а в Виста(7?) и

Сообщение Admiralisimys » 04.11.2010 (Чт) 19:10

Хакер опечатка ;) Естественно речь шла про InvalidateRect(). За дополнительное пояснение спасибо.
Этим и занимаюсь. :)

Наверное, более правильно будет так (выборочно, изменения в коде)
Код: Выделить всё
   void UpDate()
   {
   //...
      InvalidateRect(hMainWindow, &inRect, TRUE);
   }
   void Render(HDC *hDc)
   {
      HBRUSH hBrush = CreateSolidBrush(RGB(0, 0, 255));
      FillRect(*hDc, &rectBar, hBrush);
      DeleteObject(hBrush);
   }
//…
LRESULT CALLBACK WindowFunc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
//...
      case WM_TIMER:
         manager -> UpDate();
      break;
      case WM_PAINT:
      {
         PAINTSTRUCT ps;
         HDC hDc = BeginPaint(hWnd, &ps);
         manager -> Render(&hDc);
         EndPaint(hWnd, &ps);
      }
      break;

Теперь в переменной inRect типа RECT нужно учитывать не только невалидную область с предыдущего "кадра", но и область на которой нужно нарисовать в текущем "кадре".

BV поначалу и вовсе думал разместить здесь viewtopic.php?f=49&t=28564 ;)
Мол "вот как с GDI не сложилось, а GDIPlus какие промахи прощает" :(
Но, во-первых - некропостиг, во-вторых конструктив, как здесь, мог и не получить, а даже на оборот...

P.S.
По второй части обсуждение, всё же печально что испаганили подсистему GDI.
Применил твик
Код: Выделить всё
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows]
"GDIProcessHandleQuota"=dword:00000200
опираясь на
[q]In 64-bit Windows operating systems, the Graphical Device Interface (GDI) handle limit is not increased over that of 32-bit Windows operating systems. The maximum number of GDI handles that one process can handle is 65,535. You can restrict the number of GDI handles that each process may handle by setting the following registry value:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\ CurrentVersion\Windows
Value name: GDIProcessHandleQuota
Value type: REG_DWORD
Value data: 10000 (default decimal value)
You can set this registry value from a minimum value of 256 (decimal) to a maximum value of 65536 (decimal).
http://support.microsoft.com/kb/932406[/q]
установил лимит в 512 GDI объектов. Да это только усугубило, приложение быстрее заполняет GDI кеш системы (предполагалось что данным твиком будет установлен лимит на приложение, и проблемы только останутся у приложений с утечками).


Вернуться в Windows-программирование

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 8

    TopList