Jump to content

aehimself

Members
  • Content Count

    1090
  • Joined

  • Last visited

  • Days Won

    23

Posts posted by aehimself


  1. 8 hours ago, kaarigar said:

    Thanks - that is what I ended up doing. I should perhaps set up tje All Configuration as yiu suggest. But then the question what gets precedence - All Configuration or individual specific platform configuration.

    Individual settings will overwrite the one set on the parent. What I really don’t like from this perspective is that there is no “force inheritance”. You change a setting topmost, but if the same setting has a different value somewhere, it’s not going to be changed.

    • Like 2

  2. 13 minutes ago, Dalija Prasnikar said:

    Memory manager works fine in Sydney and detects memory leaks.

    Yes, but it's not pinpointing the line in the source code where the leaked object was created like as DeLeaker / FastMM full does. My main application which is about 1M LOC, you tell me a class name and I can tell where and why it was created. In older, legacy applications this can be a nightmare, especially if - like at work - everyone uses a TStringList instance for text manipulation, data storage or even as a parameter for a function call.

     

    I guess this is what @David Schwartz meant. In these occasions, having the complete callstack of a leaked object is a life saver.


  3. To be honest I don't see the reason why interfaces are required here. If the objects are freed up multiple times that clearly indicates a coding issue; using interfaces for lifecycle management in this case seems a bit like duct tape on a totaled car.

     

    I am also storing objects in .Data properties, using the OnDelete handler to manually free these up and set the .Data to nil. Never had a single issue with multiple release attempts or leaks (except if VCL styles are active but that's a different topic).

     

    Out of curiosity you can add a FreeNotification handler and put a breakpoint in that. That way you could examine what is freeing your objects up when you don't want them to, yet.

    • Thanks 1

  4. @clarke Since the D7 era I was using Delphi's own TClientSocket - TServerSocket. Had some quirks but it worked perfectly.

    Due to a bug in my receiver code (which originally I thought is in the component) I migrated to ICS about a year ago. A bit more complicated, but works perfectly as well.

     

    Depending on your needs you can check these out. ICS works fine on D11 (I'm unsure if TClientSocket - TServerSocket is still shipped, though), both has decent documentation.


  5. I ended up changing the quote detection as it misbehaved in several occasions. At the moment it looks like this:

     

    '''', '"':
    Begin
      // GetMatchingBracketEx does not support the same opening and closing characters. Therefore quotes
      // need special handling.
      // This quick and easy method only detects quotes in the same line.
    
      index := bufcoord.Char;
    
      If bufcoord.Char > 1 Then
      Begin
        // Check the current token BEFORE the current quote. If it's a String or a delimited identifier,
        // the currently selected is the closer. Go backwards to find the opener.
    
        Dec(bufcoord.Char);
        If Not SynEdit1.GetHighlighterAttriAtRowCol(bufcoord, token, attr) Or
          ((attr <> SynHighlight.StringAttribute) And
           (attr <> SynHighlight.DelimitedIdentifierAttri)) Or
          Not token.StartsWith(c) Then
          a := 0
        Else
        Begin
          a := Length(token);
          Dec(bufcoord.Char, a - 2);
        End;
      End
      Else
        a := 0;
    
      If (a = 0) And (bufcoord.Char < Length(SynEdit1.Lines[bufcoord.Line - 1])) Then
      Begin
        // Character before the current quote was not the correct token or opener quote was not found.
        // Attempt to do the same check with the character after, this time looking for a closer...
    
        bufcoord.Char := index + 1;
        If Not SynEdit1.GetHighlighterAttriAtRowCol(bufcoord, token, attr) Or
          ((attr <> SynHighlight.StringAttribute) And
           (attr <> SynHighlight.DelimitedIdentifierAttri)) Or
          Not Token.EndsWith(c) Then
          Exit
        Else
        Begin
          a := Length(token);
          Inc(bufcoord.Char, a - 2);
        End;
      End;
    
      tp := SynEdit1.RowColumnToPixels(SynEdit1.BufferToDisplayPos(bufcoord));
    End;

    This way I can correctly determine if the token is before or after the current character, which direction I should look for the closer. It works, with only 1 limitation: the token returned is only the part of the token, which is the current line - therefore highlighting quotes only work in one line as well.

    Is there a way to make GetHighlighterAttriAtRowCol return the full token (including line breaks) or an other method I can use?


  6. 2 hours ago, limelect said:

    I did not check for FMX

    In VCL Easy use OgFirst in free "ONGUARD"

    in DPR

    begin
      if  TogFirst.IsFirstInstance  then
      begin
    xxxxxxxxxx

    xxxxxxxxxx

    xxxxxxxxxxx

      end
      else TogFirst.ActivateFirstInstance;
     

    I can not understand why you have to have (free or paid) components for everything; unless true multiplatform is supported.

    You can simply use WinApi:

    handle := CreateMutex(nil, True, 'MyApplicationUniqueMutex');
    Try
      If (handle = 0) Or (GetLastError = ERROR_ALREADY_EXISTS) Then
      Begin
        ShowMessage('Only one instance can be running at a time!');
        handle := FindWindow('TMyApplicationMainForm', nil);
        If IsWindow(handle) Then
        Begin
          ShowWindow(handle, SW_RESTORE And SW_SHOW);
          SetForegroundWindow(handle);
          SetFocus(handle);
        End;
        handle := 0;
        Exit;
      End;
      
      // Stuff from DPR... like Application.Run etc
      
    Finally
     If handle <> 0 Then
       CloseHandle(handle);
    End; 

    End result is the same and you have smaller resource footprint.
     


  7. Unfortunately GetMatchingBracketEx does not support quotes - to be precise opener and closer pairs which are the same character. I wrote a quick handler for quotes, all is working like a charm:

    // This whole method was copied from https://stackoverflow.com/questions/18487553/synedit-onpainttransientdemo and modified
    // to draw rectangles instead of coloring. A few bugs were fixed and it was greatly simplified, too. Oh and while I figured
    // out what it is doind exactly, I placed some comments for easier understanding :)
    Procedure TEditorFrame.SynEdit1PaintTransient(Sender: TObject; Canvas: TCanvas; TransientType: TTransientType);
    
      Procedure PaintText(Const inPoint: TPoint; Const inText: String);
      Begin
        Case TransientType Of
          ttAfter:
          Begin
            // Clear brush and pen has font color: rectangle will be drawn
            SynEdit1.Canvas.Brush.Style := bsClear;
            SynEdit1.Canvas.Pen.Color := SynEdit1.Font.Color;
          End;
          ttBefore:
          Begin
            // Solid brush and pen has background color: Rectangle will be cleared
            SynEdit1.Canvas.Brush.Style := bsSolid;
            SynEdit1.Canvas.Pen.Color := SynEdit1.Color;
          End;
        End;
    
        SynEdit1.Canvas.Rectangle(inPoint.X, inPoint.Y, inPoint.X + SynEdit1.Canvas.TextWidth(inText), inPoint.Y + SynEdit1.Canvas.TextHeight(inText));
        SynEdit1.Canvas.TextOut(inPoint.X, inPoint.Y, inText);
      End;
    
    Const
      OCSYMBOLS = '()[]{}<>''"';
    
    Var
      bufcoord: TBufferCoord;
      tp: TPoint;
      index, a: Integer;
      c: Char;
    Begin
      c := #0;
      bufcoord := SynEdit1.CaretXY;
    
      If (bufcoord.Char = 0) Or (bufcoord.Line = 0) Then
        Exit;
    
      If bufcoord.Char > 1 Then
      Begin
        // Take the character BEHIND the cursor and check if it's a character we need to highlight
        c := SynEdit1.Lines[bufcoord.Line - 1][bufcoord.Char - 1];
        index := OCSYMBOLS.IndexOf(c);
      End
      Else
        index := -1;
    
      If index = -1 Then
        If bufcoord.Char > Length(SynEdit1.Lines[bufcoord.Line - 1]) Then
          Exit
        Else
        Begin
          // The character behind the cursor wasn't something we need to highlight.
          // Take the character AFTER the cursor and check the same
    
          c := SynEdit1.Lines[bufcoord.Line - 1][bufcoord.Char];
          index := OCSYMBOLS.IndexOf(c);
    
          If index = -1 Then
            Exit;
        End
      Else
        bufcoord.Char := bufcoord.Char - 1;
    
      // Originally this was a FOR cycle but it's a waste to go through all if only one iteration actually performs something.
      // During the search we already got the character which requires highlighting. Simply use that to save some cycles
      SynEdit1.Canvas.Font.Assign(SynEdit1.Font);
    
      tp := SynEdit1.RowColumnToPixels(SynEdit1.BufferToDisplayPos(bufcoord));
      PaintText(tp, c);
    
      Case c Of
        '''', '"':
        Begin
          // GetMatchingBracketEx does not support the same opening and closing characters. Therefore quotes
          // need special handling.
          // This quick and easy method only detects quotes in the same line.
    
          index := bufcoord.Char;
          a := index - 1;
    
          // Try to find the previous quote of the same type
          While a > 0 Do
          Begin
            If SynEdit1.Lines[bufcoord.Line - 1][a] = c Then
              Break;
            Dec(a);
          End;
    
          If a = 0 Then
          Begin
            // No previous quote was found. Reset the position and try to find the next one
            a := index + 1;
    
            While a < Length(SynEdit1.Lines[bufcoord.Line - 1]) Do
            Begin
              If SynEdit1.Lines[bufcoord.Line - 1][a] = c Then
                Break;
              Inc(a);
            End;
    
            If a > Length(SynEdit1.Lines[bufcoord.Line - 1]) Then
              Exit;
          End;
    
          bufcoord.Char := a;
          tp := SynEdit1.RowColumnToPixels(SynEdit1.BufferToDisplayPos(bufcoord));
          End;
        Else
        Begin
          bufcoord := SynEdit1.GetMatchingBracketEx(bufcoord, OCSYMBOLS);
    
          If (bufcoord.Char = 0) Or (bufcoord.Line = 0) Then
            Exit;
    
          tp := SynEdit1.RowColumnToPixels(SynEdit1.BufferToDisplayPos(bufcoord));
    
          If tp.X <= SynEdit1.GutterWidth Then
            Exit;
    
          // We need to paint the opposite symbol now: if first one was an opener then a closer and vice versa.
          // Instead of an If statement we can simply use a XOr 1, as it will turn 1 -> 2 and 2 -> 1, 3 -> 4 and
          // 4 -> 3... giving the exact opposite symbol in the pair that we need
          c := OCSYMBOLS.Chars[index XOr 1];
        End;
      End;
    
      PaintText(tp, c);
    End;

    Feel free to use it if you like; any comment or improvement is welcome 🙂


  8. @pyscripter The patch worked, it highlight correctly now! I saw that the request to be able to provide matching characters were also implemented so I changed the array to a string - this way no sorting is required and I can simply pass it to GetMatchingBracketEx.

     

    I'll take a look into the efficiency you mentioned.

     

    Thank you!


  9. Hello,

     

    I found and modified a code to highlight matching brackets in SynEdit in a Delphi IDE style (no color highlights, but a square around the characters. All is working fine except this one scenario:

     

    Enter this pattern and place the cursor in the middle of the inner brackets:

    image.png.b67f4f4d96ef16e495f361814f47518d.png

     

    Now, press backspace:

    image.png.e28cbfb9d6929cdca86130cdafc9212d.png

     

    And now, enter an opening bracket again:

    image.png.4cad3d4bffb0c5c23fc00699268ff1c3.png

     

    The rectangle around the first bracket doesn't get cleared and I can not figure out why. What is even more strange, if you put all in one line all works just fine:

    image.png.939a4e657616f212c961c1213af9bbb2.png

     

    This image was taken after deleting and reentering the inner starting bracket.

     

    The code I have at the moment is the following:

    // This whole method was copied from https://stackoverflow.com/questions/18487553/synedit-onpainttransientdemo and modified
    // to draw rectangles instead of coloring.
    Procedure TEditorFrame.SynEdit1PaintTransient(Sender: TObject; Canvas: TCanvas; TransientType: TTransientType);
    
      Procedure PaintText(Const inPoint: TPoint; Const inText: String);
      Begin
        Case TransientType Of
          ttAfter:
          Begin
            // Clear brush and pen has font color: rectangle will be drawn
            SynEdit1.Canvas.Brush.Style := bsClear;
            SynEdit1.Canvas.Pen.Color := SynEdit1.Font.Color;
          End;
    
          ttBefore:
          Begin
            // Solid brush and pen has background color: Rectangle will be cleared
            SynEdit1.Canvas.Brush.Style := bsSolid;
            SynEdit1.Canvas.Pen.Color := SynEdit1.Color;
          End;
        End;
    
        SynEdit1.Canvas.Rectangle(inPoint.X, inPoint.Y, inPoint.X + SynEdit1.Canvas.TextWidth(inText), inPoint.Y + SynEdit1.Canvas.TextHeight(inText));
        SynEdit1.Canvas.TextOut(inPoint.X, inPoint.Y, inText);
      End;
    
    Const
      OCSYMBOLS: Array[0..7] Of Char = ('(', ')', '{', '}', '[', ']', '<', '>');
    
    Var
      bufcoord: TBufferCoord;
      tp: TPoint;
      a: Integer;
      c: Char;
    Begin
      bufcoord := SynEdit1.CaretXY;
      a := SynEdit1.RowColToCharIndex(bufcoord);
    
      If (a > 0) And (a <= SynEdit1.Text.Length) Then
      Begin
        // First, take the character BEHIND the cursor and check if it's an opener or a closer
    
        c := SynEdit1.Text[a];
        If Not TArray.BinarySearch<Char>(OCSYMBOLS, c, a) Then
          c := #0;
      End
      Else
        c := #0;
    
      If c = #0 Then
      Begin
        // Variable a might have been overwritten by TArray.BinarySearch so let's initialize it again
        a := SynEdit1.RowColToCharIndex(bufcoord);
    
        If (a < SynEdit1.Text.Length) Then
        Begin
          // If the character behind the cursor wasn't an opener or a closer,
          // take the character AFTER the cursor and check the same
    
          c := SynEdit1.Text[a + 1];
          If Not TArray.BinarySearch<Char>(OCSYMBOLS, c, a) Then
            c := #0;
        End;
    
        If c = #0 Then
          Exit;
      End
      Else
        bufcoord.Char := bufcoord.Char - 1;
    
      // Originally this was a FOR cycle but it's a waste to go through all if only one iteration actually performs something.
      // During the binary search we already got the index and if it's an opener or a closer. Simply use those previously saved
      // values
      SynEdit1.Canvas.Font.Assign(SynEdit1.Font);
    
      tp := SynEdit1.RowColumnToPixels(SynEdit1.BufferToDisplayPos(bufcoord));
      PaintText(tp, c);
    
      bufcoord := SynEdit1.GetMatchingBracketEx(bufcoord);
    
      If (bufcoord.Char = 0) Or (bufcoord.Line = 0) Then
        Exit;
    
      tp := SynEdit1.RowColumnToPixels(SynEdit1.BufferToDisplayPos(bufcoord));
    
      If tp.X <= SynEdit1.GutterWidth Then
        Exit;
    
      // We need to paint the opposite symbol now: if first one was an opener then a closer and vice versa.
      // Instead of an If statement we can simply use a XOr 1, as it will turn 1 -> 2 and 2 -> 1, 3 -> 4 and
      // 4 -> 3... giving the exact opposite symbol in the pair that we need
      PaintText(tp, OCSYMBOLS[a XOr 1]);
    End;

    I'm using TurboPack SynEdit on D10.4.2. If the code doesn't compile just use SynEdit1.Gutter.Width instead, I think nothing else had to be changed.

     

    Can someone please check and give some hints on why things go south? 🙂

    Thanks!


  10. On 8/26/2020 at 3:29 PM, Anders Melander said:

    Yeah, that one is really annoying. I used to have a class helper that added TZipFile.Delete and Remove methods but one of the Delphi versions after XE2 broke that one as the required TZipFile internal data structures are no longer accessible to class helpers.

    Kind of necroing a thread, I'm aware but I was just informed:

     

    https://docwiki.embarcadero.com/RADStudio/Alexandria/en/What's_New#RTL:_TZipFile

     

    Quote

    RTL: TZipFile
    We have focused on ZIP files (that is, the TZipFile class of the RTL) quality, improvements, and optimizations. We added support for Zip64 and a method to remove a file in TZipFile. Also, TZipHeader has a GetFIleName method, TZipFile.IsValid() accepts a stream parameter and System.Zip works with files larger in size than 4 GB.

     

    Still doesn't support LZMA and still fails on slightly corrupted archives 7Zip extracts happily, though.


  11. Just now, Attila Kovacs said:

    So they were leaks from the perspective of the initial test. This means that your repro's were also wrong, didn't they?

    Well, yes and no. My repro was correct - in a way as it should have been done. In real world it could be like

    Repeat
     CreateInnerMostFrameWithImageList;
    Until False;

    and just wait until I run out of GDI objects.

     

    There were no leaks, just too many created at once due to a design issue.


  12. There was a small trick I was unaware of. The innermost frame (which contained the ImageList) was not created once, but at least 10-20 times. Then, based on some criteria only one of them was shown (it's a really bad practice in my opinion, but it is how it is).

    Since the ImageList contains 10-11 images, basically what I wanted to say is... 1 main frame easily created 100-200 GDI objects (or more) just for this one ImageList. To add salt to the wound, some of these main frames are not actually closed when they should be to improve loading time upon the next time they should be shown.

     

    Moving the ImageList to a separate location kept the amount of GDI objects below 2500 at all times and the test finished without any issues. At the end of the day, @Fr0sT.Brutal was right - there was no leak, there were just way too many created which would have been freed up normally once the application (frame) closes.

     

    Legacy code is the best. You can learn so much just from the design issues your predecessors made 🙂 Actually, without irony this time. I had no idea that "GDI object" as a thing exists and can make your program to crash!


  13. 7 minutes ago, Lars Fosdal said:

    Not that it has any bearing on this problem, but I keep my shared image lists in a data module

    Yep, in my own projects me too. Unfortunately this application was written some 20+ years ago and contains several thousand frames and hundreds of custom components… it will take a while to check and correct all these small design mistakes :)


  14. 49 minutes ago, Fr0sT.Brutal said:

    Something could leak without a detectable leakage.

    for i in 1..MaxInt do TButton.Create(MainForm)

    these buttons will be freed on form close but they obviously leak

    That is true, and definitely could apply here (no memory / GDI leak upon application closure) but we are talking about auto-created components here. The imagelist is on the innermost frame, and that frame is embedded design time on the parent frame.

    I’ll check the whole chain tomorrow though - if just one is created runtime it can cause this.

     

    Thanks!


  15. 1 minute ago, Attila Kovacs said:

    You see what is leaking, you could start placing breakpoints to the constructor/destructor of the imagelists first and count them, and check who the caller is.

    The issue is, about 600 frames are opened and closed before the issue detectibly appears. My sanity will give up first than to count each and every TImageList and track their lifetime.

    Also, any Delphi object leak would be reported by DeLeaker upon application closure. I spent more than a year to get that list clean, so no more unfreed imagelists / TBitmaps 🙂


  16. Just now, Attila Kovacs said:

    Is that on the screenshot?

    It's showing a bunch of HBITMAP leaks in the comctl32.dll.

    TImageList is a wrapper for some windows imagelist so I'm suspecting that it's not freed correctly or created twice under the same reference etc...

    I'd look for that.

    Yes, it is. My first idea was that the skinning of TcxPageControl caused this (we recently applied some DevExpress skins based on user input) as it was the most recent change with comctrls involvement.

    My issue is that the ImageList is simply dropped on the innermost frame design-time. It should be created and freed up automatically. If there would be a bug in the Delphi wrapper, it would be visible in my test case, too.

     

    The real code contains a cximageList (DevExpress version) but symptoms are exactly the same if I replace it with a simply TImageList. This is why I didn't mention in until now. I doubt it will make any difference, though.


  17. 1 minute ago, Uwe Raabe said:

    Yes, is was MS itself making that error.

     

    BTW, Windows 10 requires a reboot for the changes to get applied - and there is a similar entry under the WOW6432Node (both should match).

     

    Anyway, there were several things done to fix a GDI leak and significantly reduce the overall GDI handle usage in Delphi 11.

    I already introduced my boss to D11 when it was released and I personally was the one who said I don't see a reason why we would need to upgrade.

    Good thing is that I asked him to doublecheck our licensing and we should still be eligible. Will be a funny talk, though 🙂

     

    My personal OCD still hasn't calmed down yet. How come a completely unused TImageList can cause a GDI object leak?

    Or I'm just looking at the completely wrong direction. That is unfortunately a completely valid option, too.


  18. 2 hours ago, Attila Kovacs said:

    I don't think that the ImageList should be on the frame.

    There is a combobox, which is normally pulling those images from the ImageList. I have a hunch that if I get rid of the imagelist and push those bitmaps in resources the issue is solved... it just bugs me that I don't know what is causing the mayhem now.

    2 hours ago, Attila Kovacs said:

    Anyway, is your test-project also creates the sub-frames from the constructor?

    Of course. I tried to replicate everything as close to the real thing as possible. Btw, creating the frames from constructor means inherited... by embedded I meant to add the innermost frame to an outer one from the designer.

    2 hours ago, Attila Kovacs said:

    You should check where and what is calling GetDC/ReleaseDC in the VCL and if they are called in pair.

    I suspect the VCL is working correctly, this is why creating and destroying my test frame 100k + times makes no difference. I'm suspecting something funky going on here... like the Z-order TTreeNode leak with styles active or some 3rd party component interference.

    My knowledge is severely limited in this area though, that's why I need starting points.

    I'll do a quick search to see if GetDC is called in our custom components / frame code somewhere, but I doubt. The complexity the ancient code is written is pretty basic... data storage is a TStringList 78% of the times 🙂


  19. 2 minutes ago, Uwe Raabe said:

    What is your registry value for HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\GDIProcessHandleQuota?

    Could it be that it is 10000 decimal instead of 10000 hex?

    It is the installation default, which is 10000 decimal. It seems irrelevant though, as if even an abandoned ImageList is on the innermost frame (all code commented out, not used at all, no images in it) will cause the application to have 7500+ GDI objects in about 20 minutes. If I completely remove the ImageList, GDI object count stays top 2500.


  20. Hello,

     

    Our legacy application mainly consist of frames which are created and displayed runtime when needed. Several parts of these frames are the same, so these functionalities were created on a smaller frame which was then embedded to the main frame. So basically we have a form with a panel, we create a frame in that panel and the constructor creates an other frame, which is on the frame we are creating. The subfame has an imagelist on it.

    Recently our automatic test started to fail after creating and destroying ~600 frames because of "invalid parameter" when the DFM streaming was creating the bitmap to add it to the imagelist.

    When executing the same test from the Delphi 10.4.2 IDE, actually 2 error messages appear, one after the other, but at the same place: parameter incorrect and out of system resources.

     

    I managed to find out that the errors start appearing when the GDI object count reaches 9999 and a new frame is about to be created.

     

    If I clear all bitmaps from the imagelist I get an "Invalid imagelist" error when GDI object count reaches the limit.

    If I remove the ImageList from the frame, the test finishes successfully, without GDI object count raising above 2500.

     

    No matter how hard I try I can not reproduce the issue in a fresh appication even if I model the imagelist-on-a-frame-on-a-frame-in-a-panel-on-a-form layout. GDI object count simply stays at 42.

     

    Now, this is the first time I heard that there is such a thing as GDI objects and that there is a limit on it, so my experience is converging towards zero.


    We have DeLeaker purchased and is showing no memory leaks. There are tons and tons of GDI objects during runtime, all from the same source:

     

    image.thumb.png.1d7fa571ae174a4146b6b75ca28ecad8.png

     

    Do you guys have any tips on how to find out what is causing this leak?

×