Конференция "KOL" » Утечка GDI-ресурсов [Delphi, Windows]
 
  • G-Host © (05.01.12 17:36) [0]
    Есть форма, на ней - TKOLListView
    В момент создания формы в function TControl.CreateWindow: Boolean; вызывается
    fHandle := CreateWindowEx( Params.ExStyle, Params.WinClassName,..., который создает окно класса obj_SysListView32. При этом создаются 1 BRUSH и 3 FONT GDI-хэндла, которые даже в destructor TControl.Destroy; при вызове DestroyWindow( I ); не возвращаются.
    Соответственно, при постоянном создании-удалении окна у приложения растут GDI Handles.
    Вопрос - так и должно быть или я что-то упускаю?
  • Vladimir Kladov © (06.01.12 19:23) [1]
    Наверное, не хватает TKOLApplet?
    Demo смотрели с модальными формами? Оно gdi-ресурсы не теряет?
  • G-Host © (07.01.12 17:58) [2]
    Наличие TKOLApplet значения не имеет.
    DemoModalForm ресурсы не потребляет, но в нем и TKOLListView нет.
    А проблема как раз в ListView.

    Собственно, даже такой кусок кода создает утечку GDI-ресурсов и памяти:

    lv : TKOLListView;

    procedure TForm1.Button1Click(Sender: PObj);
    begin
     lv := NewListView(Form, lvsList, [], nil, nil, nil);
     lv.Show;
    end;

    procedure TForm1.Button2Click(Sender: PObj);
    begin
     lv.Free;
    end;

    Каждое нажатие Button1 + Button2 приводит к захвату дополнительных 1 BRUSH и 2 FONT. Потребление памяти также каждый раз увеличивается на 2 Кбайта.
  • G-Host © (08.01.12 00:56) [3]
    Обновление:

    Если взять стандартный пример DemoModalForm и добавить:
    1. в unitb.pas строку

       lv : TKOLListView;

    2. в UnitB_1.inc в конец
     Result.lv := NewListView(Result.Form, lvsList, [], nil, nil, nil);

    то ресурсы опять-таки теряются при каждом показе модальной формы.

    Иными словами, ресурсы теряются при каждом создании экземпляра TKOLListView.
  • Dufa © (08.01.12 17:21) [4]
    А где утечки смотрите? Memproof показал что все в норме
  • G-Host © (09.01.12 01:43) [5]
    А memproof разве контролирует GDI-ресурсы?

    GDI-ресурсы проверяю утилитой GDIView
    http://www.nirsoft.net/utils/gdi_handles.html

    Память - диспетчером задач.
  • Dufa © (09.01.12 17:26) [6]
    Количество брашей и фонтов он как раз показывает. Позже проверю GDIView'ом тогда
  • Dufa © (10.01.12 11:09) [7]
    Проверил через гдивьев, что-то не то он показывает явно. Попробуй код

    var
     //lv : TKOLListView;
     b: DWORD;

    procedure TForm1.Button1Click(Sender: PObj);
    var
     t: TLogBrush;
    begin
     //lv := NewListView(Form, lvsList, [], nil, nil, nil);
     //lv.Show;
     t.lbStyle := BS_SOLID;
     t.lbColor := DIB_RGB_COLORS;
     b := CreateBrushIndirect(t);
     form.Caption := Int2Str(b);
    end;

    procedure TForm1.Button2Click(Sender: PObj);
    begin
     //lv.Free;
     form.Caption := Int2Str(integer(DeleteObject(b)));
    end;

  • G-Host © (12.01.12 21:26) [8]
    GDIView показывает ровно то же самое, что и ProcesExplorer Русиновича и даже (!!!) стандартный Диспетчер задач в Windows (если включить в нем колонку "объекты GDI"). Последним двум я верю даже больше, чем себе :)

    А именно:
    1. Сразу после запуска за приложением числятся 14 GDI-ресурсов.
    2. После создания кисти число GDI-ресурсов становится равным 15.
    3. После удаления кисти число GDI-ресурсов остается равным 15. Это - да, непонятно.
    4. Однако при повторном создании кисти число ресурсов опять не изменяется и остается равным 15.
    5. к пункту 3.

    При создании-удалении KOLListView ситуация иная:
    1. Сразу после запуска за приложением числятся 14 GDI-ресурсов.
    2. После создания KOLListView число GDI-ресурсов становится равным 19.
    3. После удаления KOLListView число GDI-ресурсов не изменяется (!!!).
    4. При повторном создании KOLListView число GDI-ресурсов увеличивается на 3 (!!!).
    5. к пункту 3.

    Если с BRUSH-ами все не так страшно, поскольку общее число ресурсов не растет, то с ListView все достаточно плохо, поскольку каждый объект (пусть даже потом и удаленный) увеличивает число захваченных ресурсов на 3. 3333 созданных за время работы программы списков - и финиш.
  • G-Host © (12.01.12 21:54) [9]
    Забавно, что ни в VCL, ни в LCL (Lazarus) подобных фокусов с ListView не происходит - там GDI-ресурсы исправно возвращаются обратно.
  • thaddy © (15.01.12 00:22) [10]
    I just tried something.
    It seems you do not understand MemProof!
    You have to compile first with stackframes ON and full debug info.
    Otherwise MemProof will give false, incorrect results.
    If you test with these options, NO brushes and pens are leaked at all.

    This is just illusion.
    RTFM from MemProof BTW. <smile>

    To be friendly: you are not the first one who forgot this.
  • thaddy © (15.01.12 00:38) [11]
    To be fair:

    You can only test kol for memory leaks with stackframes on and {$D+}.
    You can remove that for "release".
    The reason is that kol is too efficient :-)
    If Memproof says it doesn't leak with these options, it will not leak without these options either!

    Plz let me know the results.
    I know that the code that you wrote here Does Not Leak!
  • G-Host © (15.01.12 22:04) [12]
    2 thaddy:

    Can you send me a working EXE (creating and destroying KOLListView example)?
  • G-Host © (15.01.12 22:09) [13]
    And again:

    I'm NOT using MemProof. I'm using GDIView, ProcessExplorer and standard Windows Task Manager.
    The all show the same - GDI resources leaking.

    If you have a correctly working EXE (with create/destroy KOLListView), please send me.
  • Thaddy © (16.01.12 00:43) [14]
    Send me some code in private. (you have mail)
  • Thaddy © (02.02.12 23:34) [15]
    I guess this is closed?
  • RusSun © (03.02.12 03:15) [16]
    hard to tell with such activity in this forum)
  • Thaddy © (06.02.12 16:54) [17]
    I spend some time to check the claim.
    My conclusion was right. I you debug the leaks with a good program (like AQ** or its ancestor memproof) and you follow the instructions, there are no leaks in GDI.
    There is also a directive to debug the GDI resources in KOL.
    That also doesn't give anything unusual.
    Debugging can ONLY work if you follow the rules, otherwise you get false positives for leaks.
    If G-host is still not convinced, email me with code.
  • thaddy © (06.02.12 17:02) [18]
    @RusSun: It is a bit quiet here, you are right. Doesn't mean we don't read.
  • G-Host © (10.02.12 02:02) [19]
    2 thaddy
    Sorry, I think you are wrong.
    Because this code just crash or hangs up  the application:

    procedure TForm1.Button1Click(Sender: PObj);
    var
     i : Integer;
    begin
     for I := 1 to 4000 do begin
       lv := NewListView(Form, lvsList, [], nil, nil, nil);
       lv.Show;
       lv.Free;
     end;
    end;



    GDI resources goes to 10000 and than application hangs. Why it's happens, if releasing of GDI is OK?
  • G-Host © (10.02.12 02:03) [20]
    2 everyone
    If you wish, you can check it by yourself downloading EXE & project here:
    http://dl.dropbox.com/u/5169980/test_kol.zip
  • RusSun © (11.02.12 20:37) [21]
    @thaddy: Reading without action for me like no reaction)
  • Thaddy © (13.02.12 16:54) [22]
    Action taken right now. I'll have a look...
    Was easier by email...
  • Thaddy © (13.02.12 17:23) [23]
    After one minute:

    procedure TForm1.Button1Click(Sender: PObj);
    var
     i : Integer;
    begin
    //  //start monkey (strictly speaking amoebe code)
    //  for I := 1 to 4000 do
    //  begin
    //    lv := NewListView(Form, lvsList, [], nil, nil, nil);
    //    lv.Show;
    //    lv.Free;
    //  end;
     for I := 1 to 4000 do
     begin
       // start homo erectus code (strictly speaking: monkey code <smile>)
       lv := NewListView(Form, lvsList, [], nil, nil, nil);
       try  //protect your object!
         lv.Show;
       finally
         lv.Free;
       end;
     end;

    end;


    No leaks....

    Slightly improved.....

     form.BeginUpdate; // Leave the rest of your apps alone, get a rest.
     try  // but make sure you get the screen back when we are finished
       for I := 1 to 4000 do
       begin
       // start homo sapiens code (strictly speaking: homo erectus code <smile>)
         lv := NewListView(Form, lvsList, [], nil, nil, nil);
         try //protect your object
           lv.Show;
         finally
           lv.Free;
         end;
       finally
         Form.EndUpdate;
       end;
    end;


    No leaks at all....

    And finally:

    procedure TForm1.Button2Click(Sender: PObj);
    begin
    // double free may look like a leak but isn't
     if Assigned(lv) then lv.Free;
    end;



    Case closed.
  • thaddy © (13.02.12 17:29) [24]
    BTW the trick is of course:

    SHOWMODAL!
  • thaddy © (13.02.12 17:41) [25]
    The lesson is: your program doesn't have full control over OS resources when your application has no time to respond to messages. Although Autofree does a good job, it still needs to be notified. AND you are creating the listview in a procedure again and again. in that case NO framework can guarantee that the original object is already freed. By using a try finally block you can prevent this.
    Second mistake: you use show, where you really want to use either showmodal or use the virtual listview option (Vladimir has an excellent example on kolmck,net)
    Third mistake: too many screen updates... Lock the form, with begin update ensures that no paint messages are send until you are finished with your work (be sure to unlock it!)
    Fourth mistake:
    The way you have written the code can cause the conflict that there is no listview at all, so you have to check if it is not already destroyed.

    If you test your code with my small improvements, you will find that it does not leak at all. But good programming is completely different. I can point you to at least a dozen other basic mistakes in the rest of the your program.
  • thaddy © (13.02.12 17:54) [26]
    Easy to make mistakes:

    form.BeginUpdate; // Leave the rest of your apps alone, get a rest.
    try  // but make sure you get the screen back when we are finished
      for I := 1 to 4000 do
      begin
      // start homo sapiens code (strictly speaking: homo erectus code <smile>)
        lv := NewListView(Form, lvsList, [], nil, nil, nil);
        try //protect your object
          lv.Show;
        finally
          lv.Free;
        end;
      end;
    finally
       Form.EndUpdate;
    end;
    end;

  • Thaddy © (13.02.12 20:34) [27]
    It doesn't leak, but it does not show either <smile>
    Programming in human memory isn't without flaws...
    But then again: the code is nonsense.
    Why would you do that?
    In the VCL application you solved things more normal. But with a totally different algorithm. Your KOL code has to many mistakes anyway.

    If you really want to create a new listview object every 4000 times --- do the screen lock in the loop too..... It still doesn't leak and this time everything is tested with memproof (I can prove it!)
  • G-Host © (14.02.12 13:08) [28]
    2 thaddy:
    Please send me a compiled EXE and project source of example, which create and destroy 4000 ListViews
  • G-Host © (22.02.12 01:00) [29]
    В общем, ответа от thaddy нет, а проблема остается - создать (и уничтожить) 4000 ListView в программе на KOL не представляется возможным.
    Причем не важно, сразу создавать или размазывать код по времени.
  • Dufa © (22.02.12 19:06) [30]
    Страдаете откровенной ерундой.

    вот код, утечек нет.

    procedure TForm1.Button1Click(Sender: PObj);
    var
     i : Integer;
    begin
     for I := 1 to 4000 do begin
       lv := NewListView(Form, lvsList, [], nil, nil, nil);
       lv.SetPosition(0, 0).SetSize(20, 20);
       Form.Invalidate;
       lv.Free;
     end;
    end;

    а так же читаем комменты в коле

       procedure Show;
       {* |<#appbutton>
          |<#form>
          Makes control visible and activates it. }

    где тут написано что можно юзать метод с листвью?

  • Vladimir Kladov © (22.02.12 23:12) [31]
    Попробуйте UNICODE_CTRLS. В новых Delphi начиная с 2009, как я понимаю, иначе и не получается, там этот символ просто должен быть. (Хорошо бы еще понять, как сделать, чтобы утечки не было без него - для старых Delphi. Возможно, надо отлавливать какое-нибудь событие с W на конце имени, хотя контрол и не уникодовский. Первый раз подобное было замечено с treeview).

    2Dufa: Однако, можно. Не помню как раньше (и было ли иначе), но сейчас это то же самое что Visible := true;
  • Vladimir Kladov © (23.02.12 16:46) [32]
    Все, разобрался. Действительно, UNICODE не при чем. Действительно, имеется утечка - именно для ListView. Надо было отнести код, удаляющий привязку контрола к окну, в WM_NCDESTROY. Есть некоторые сомнения, что абсолютно все будет работать корректно:
    - как минимум, пришлось вызвать обработчик WM_NCDESTROY по умолчанию
    - так же, в коде есть комментарий по поводу исправления для корректного уничтожения progress bar'а (версия 2.41-2.42). Я проверил - вроде бы все нормально после переноса, но может, я что-то не учел.
    - дополнительно, есть вероятность, что требуется проверка того, что сообщение WM_DESTROY / WM_NCDESTROY пришло от своего собственного окна. Заключение в кавычки {IFnDEF SMALLER_CODE}, видимо, достаточно - в общем случае проверка останется.
    - изменения могут привести к тому, что несколько больше обработчиков событий и оконных сообщений может срабатывать в момент разрушения контрола или формы. Но в принципе, особых проблем не наблюдается - на тестах.

    Сейчас я приготовлю обновление. Если есть пожелания о внесении в версию каких-либо изменений, пишите сейчас.
  • rdnks (23.02.12 17:43) [33]
    хотелось бы для мост копабилити в TKOLStrList между анси и юникодом наблюдать property values


    // kol.pas
    //[...]

    interface

    //[...]

    type
     PWStrList = ^TWstrList;
     {* }

    //[...]

       procedure OptimizeForRead;
    // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> Начало блока
     protected
       procedure SetValue(const AName, Value: KOLWideString);
       function GetValue(const AName: KOLWideString): KOLWideString;
     public
       function IndexOfName(AName: KOLWideString): Integer;
       property Values[const AName: KOLWideString]: KOLWideString read GetValue write SetValue;
    // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Конец блока
     end;

    //[...]

    implementation

    //[...]

    procedure TWStrList.OptimizeForRead;
    begin
       {$IFDEF TLIST_FAST}
       if  fList <> nil then
           fList.OptimizeForRead;
       {$ENDIF}
    end;

    // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> Начало блока
    function TWStrList.IndexOfName(AName: KOLWideString): Integer;
    var i: Integer;
       L: Integer;
       fCount: integer;
    begin
       Result:=-1;
       L := Length( AName );
       if L > 0 then
       begin
         AName := WLowerCase( AName ) + fNameDelim;
         Inc( L );
         fCount := GetCount - 1;
         for i := 0 to fCount do
         begin
           if _WStrLComp( PWideChar( WLowerCase( ItemPtrs[ i ] ) ), PWideChar( AName ), L ) = 0 then
           begin
             Result:=i; exit; {>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>}
           end;
         end;
       end;
    end;

    procedure TWStrList.SetValue(const AName, Value: KOLWideString);
    var
     I: Integer;
    begin
     I := IndexOfName(AName);
     if i=-1
     then Add( AName + fNameDelim + Value )
     else Items[i] := AName + fNameDelim + Value;
    end;

    function TWStrList.GetValue(const AName: KOLWideString): KOLWideString;
    var
     i: Integer;
    begin
     I := IndexOfName(AName);
     if I >= 0
     then Result := Copy(Items[i], Length(AName) + 2, Length(Items[i])-Length(AName)-1)
     else Result := '';
    end;
    // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Конец блока

  • Vladimir Kladov © (23.02.12 19:32) [34]
    так, еще?
 
Конференция "KOL" » Утечка GDI-ресурсов [Delphi, Windows]
Есть новые Нет новых   [134427   +35][b:0.001][p:0.006]