Jump to content

aehimself

Members
  • Content Count

    1030
  • Joined

  • Last visited

  • Days Won

    22

Posts posted by aehimself


  1. If any future visitor is wondering, a simple

     Picture.Assign(DataLink.Field);

    solved the problem, there was no need for a middle TJPEGImage. It's still a mystery for me though, why it is not working with a TJPEGImage and working without. Especially since they were all loaded correctly.

     

    Especially since the above works fine with a normal TImage, but not from inside a TDBImage descendant.


  2. 1 hour ago, Uwe Raabe said:

    You should be aware that assigning to TPicture.Graphic will create an internal copy of the source (see TPicture.SetGraphic), so you get a memory leak with the above code.

    I know. This is why I am attempting to get rid of it.

     

    1 hour ago, Uwe Raabe said:

    Not refreshing the display may have a different cause.

    Well, it is working if we are leaking TJPEGImages, which I find really strange. Also attempting to Self.Invalidate / Self.Repaint / Self.Refresh after loading the picture makes no difference.


  3. Good idea. Commented the .Free out:

          jpeg := TJPEGImage.Create;
          Try
           jpeg.Assign(DataLink.Field);
           Picture.Assign(jpeg);
          Finally
    //       jpeg.Free;
          End;

    Bad thing is, it still does not work (picture isn't shown) and it leaks a TBitmapImage, a TJPEGImage, a TBitmap, a TJPEGData and a TMemoryStream object upon destruction 😞

     


  4. Hello,

     

    I'm in the process of fixing some unusual amount of memory leaks and code errors in a legacy codebase. We have a custom TDBImage descendant, which was leaking TJPEGImages like there's no tomorrow.

     

    The code used to look like this:

    constructor TMyDBImage.Create(AOwner: TComponent);
    begin
     […]
      Picture.Graphic := TJPEGImage.Create;
     […]
    end;
    
    procedure TMyDBImage.DataChange(Sender: TObject);
    begin
      Picture.Graphic := TJPEGImage.Create;
      FPictureLoaded := False;
      if FAutoDisplay and
        (DataSource.DataSet.State <> dsInactive) then
        LoadPicture;
    end;
    
    procedure TMyDBImage.LoadPicture(ARefreshDataSet: Boolean = False);
    begin
     […]
    if not DataSource.DataSet.EOF and DataLink.Field.IsBlob then
        Picture.Graphic.Assign(DataLink.Field)
      else
        Picture.Graphic := TJPEGImage.Create;  // Empty picture
    end;

    I guess they wanted to clear the image, so I changed all those TJPEGImage.Create-s with a simple nil assignments (Picture.Graphic := nil). I also changed the loading mechanism:

    procedure TMyDBImage.LoadPicture(ARefreshDataSet: Boolean = False);
    Var
     jpeg: TJPEGImage;
    begin
      [...]
    
      if not DataSource.DataSet.EOF and DataLink.Field.IsBlob then
        Begin
          jpeg := TJPEGImage.Create;
          Try
           jpeg.Assign(DataLink.Field);
           Picture.Assign(jpeg);
    //       Picture.Bitmap.Assign(jpeg);  This does not work either
          Finally
           jpeg.Free;
          End;
        End
      else
        Picture.Graphic := nil;  // Empty picture
    end;

    The leaks are gone, but the picture is not shown in the component. I already confirmed that "jpeg" is filled correctly (stop with debugger, execute a .SaveToFile and check the result) but I can not really make the DBimage's Picture to load it. Based on my previous codes the Picture.Bitmap.Assign should be fine; I guess in this case it's the nil assignment of the .Graphic property which is not letting it to work?

     

    Where is my logic wrong?

     

    Thanks!


  5. Procedure TAEMultiSQLMainForm.ConnectionClick(inSender: TObject);
    Var
     ts: TTabSheet;
     sqf: TSQLConnectionFrame;
     s: String;
    Begin
     s := (inSender As TMenuItem).Caption;
    
     ts := TTabSheet.Create(Display);
     ts.Caption := s;
     ts.ImageIndex := 1;
     ts.PageControl := Display;
    
     sqf := TSQLConnectionFrame.Create(ts, Settings.Connections[s], Log);
     sqf.Parent := ts;
    
     RecolorSQLFrame(ts);
     Display.ActivePage := ts;
     ChangeFocus;
     sqf.ReconnectButton.Click;
    End;

    This is the code snipplet I use in a tabbed SQL browser to open a new connection; which is also a frame. I am creating a tabsheet and a frame without a name and never got an error concerning component names. That's the only reason I asked.


  6. On 5/5/2020 at 6:38 PM, Tntman said:

     

    
    for i := 0 to 10 do
      begin
        randNumb := Random(9900);
        messageList[randNumb] := TListBoxItem.Create(Self);

     

    Based on your luck, you can leak 10 TListBoxItem obects with one run. Don't do this. Just because it did not fail during debugging, it might fail in real life.

    Random(9900) will give you a number between 0 and 9899, leaving the last one (#9900) unused 100% of the times. Consider using Random(High(messageList) + 1) instead of a burned-in number; so if you decide to resize your array later on - you only have to do it at one place.

    On 5/5/2020 at 6:38 PM, Tntman said:

     

    
        TFrame1.Create(Self).Name := IntToStr(randNumb);
        with TFrame1(FindComponent(IntToStr(randNumb))) do

     

    If you really like With that much, you can just say

    With TFrame1.Create(Self) Do
     Begin
      Name := IntToStr(randNumb);
      [...]
     End;

    Burning CPU cycles for FindComponent is completely useless, if you just created that object. I'd use a local variable here, but that's only my taste.

     

    As for the rest of your fears - they are all valid. I guess @David Heffernan has an alert set up if you write SetLength(myArray, Length(myArray) + 1) anywhere, as he will appear and tell you that it is a REALLY bad practice. Indexing will also never automatically shrink, items will not be moved - you have to do it manually if you want to...

     

    ... oooor instead of an array you can say

    messageList: TObjectList<TListBoxItem>;
    [...]
    messageList := TObjectList<TListBoxItem>.Create(True);

    ...this way you will get an automatically expanding and shrinking list, which will automatically .Free your object if you delete it from the list.

    With this, the above With block could be replaced by...

    Var
     index: Integer;
    Begin
     index := messageList.Add(TListBoxItem.Create(Self));
     messageList[a].Name := xxx;
     [...]

    which looks cleaner imo.

     

    P.s.: Is there a purpose you are naming dynamically created objects? I never ever had to do it so far; thus can not imagine a reason why I'd need to do so.

    • Thanks 1

  7. 8 minutes ago, Tntman said:

    its more for myself to learn more

    In this case I keep my mouth shut - this is how we all experiment and learn at the end.  I still do dislike custom notifications, though 🙂

    10 minutes ago, Tntman said:

    Yes , in my app user can toggle on / off if he wants to get custom notifications.

    Custom, as in notifications at all; or custom-themed notifications? People using computers nowadays are lazy. We don't want to flip a switch if we expect the program to know to keep quiet. Furthermore, if I already set "Quiet hours in Cortana"....

    You can add a button like "Don't show notifications when [Insert currently active window title] is active". At least you have to click once, then never again.

     

    I'm cursed to write my own - even - experimenting apps with my own comfort in sight. If I cannot do it with a feature I want to see in a finished product, it's a no-go for me.

     

    P.s.: yes, I have hundreds of unfinished test projects 😞


  8. 9 hours ago, Sherlock said:

    I shut it down every day. Have done so since... well, always. You just can't get windows to behave as expected unless it's been freshly rebooted.

    So. Damn. True.

    I'll always smile on the EULA of Win2k, which clearly stated that it's not an error-prone operating system and therefore it's not recommended for appliances such as in airplanes or rocket guidance.

    But - as always - you'll always find gems. Once one of our customer called us and said that they "found" a running server under a desk in a closed-down section of the building. If my memory is correct, it had Windows 2003 installed and when we checked it it was up for 3,5 years straight. Easy without any real tasks, though.


  9. 12 hours ago, Tntman said:

    The way how i solved this is to make form without border with animations and display it in corner of the screen .. Add timer and on timer finish display closing animation, i have not found any better other method

    I am against custom notifications. I disliked MSN messenger, early Outlook (2003 era, maybe; can't recall) now Viber and all the others doing this as it completely breaks a computers look-and-feel. Especially since later OSes have their native notification system.

    I half-implemented this once, but soon I realized that it's not that easy. A notification only worth it if it's above everything else on your screen - except some situations: you'll have to check for full-screen apps (gaming, video); possibly add DnD intervals and maybe allow the user that if a specific application is running - don't show anything. What happens if your notification covers the native notification or vice versa...? Would be nice to have some syncing to make sure it never happens.

     

    That's a lot of extra work, and the OS already knows it all.

    • Like 1

  10. 5 hours ago, Magno said:

    Yep, but I think the helper with setting nil before is not so handy, but it is ok. This is the way, as I have spoken.

    I'm not saying there's something wrong with this type of helpers (I have close to the same at work) it's just an "inconvenient" - or should I say unusual - format to follow. And - as @Attila Kovacs mentioned and your example shows - you have to be careful; as there's no way (according to my knowledge) in Delphi to see if a TObject is initialized or not.

    • Thanks 1

  11. 13 minutes ago, RTollison said:

          _KeyValue := bintoAscii(dmspCreate.adsAcuLocks1.FieldbyName('KeyValue').AsBytes);
          if (_keyValue[1] = #0) then
            _keyValue := bintoStr(dmspCreate.adsAcuLocks1.FieldbyName('KeyValue').AsBytes);  

    The first and the third example starts with 0x30 so bintoStr will not be called, but the byte array seems to be binary; not ASCII. If you are sure that the field always contains binary data, drop BinToAscii and call BinToStr only.


  12. BinToHex should work, but you can give this a try:

    Function MyBinToHex(inBinary: TArray<Byte>): String;
    Var
     b: Byte;
    Begin
     Result := '0x';
     For b In inBinary Do
      Result := Result + IntToHex(Ord(b), 2);
    End;

    ..,in older Delphi's:

    Function MyBinToHex(inBinary: Array Of Byte): String;
    Var
     a: Integer;
    Begin
     Result := '0x';
     For:= Low(inBinary) To High(inBinary) Do
      Result := Result + IntToHex(Ord(b), 2);
    End;

     


  13. I am strictly talking about security. By not a valid license I don't mean an expired; a crafted one which is known to be not from the author.

    As for code execution, it's not that easy. Of course if you are corrupting with (or you did not initialize your buffer, and it contains) the exact binary representation of a call to DeleteFile - it will work.

     

    procedure TForm1.Button1Click(Sender: TObject);
    Type
     TProcedure = Procedure;
     PProcedure = ^TProcedure;
    Var
     p: PProcedure;
     buf: Pointer;
    begin
     GetMem(buf, 1024);
     Try
      p := buf;
      p^;
     Finally
      FreeMem(buf);
     End;
    end;

    Project Project1.exe raised exception class $C0000005 with message 'access violation at 0x00000000: read of address 0x00000000'. Execution denied.

     

    Same, if you try to execute a differently allocated memory area:

    procedure TForm1.Button2Click(Sender: TObject);
    Type
     TProcedure = Procedure;
     PProcedure = ^TProcedure;
    Var
     p: PProcedure;
     x: TObject;
    begin
     p := Addr(x);
     p^;
    end;

    Project Project1.exe raised exception class $C0000005 with message 'access violation at 0x02f788b0: write of address 0x060904ec'. No luck.

     

    Out-of-bounds?

    procedure TForm1.Button3Click(Sender: TObject);
    Type
     TProcedure = Procedure;
     PProcedure = ^TProcedure;
    Var
     p: PProcedure;
    begin
     p := Pointer($ABABABAB);
     p^;
    end;

    Project Project1.exe raised exception class $C0000005 with message 'access violation at 0x005fd31b: read of address 0xabababab'.

     

    The OS is attempting to take measures against this, and if it's possible (somewhat how iPhone / PS4 jailbreaks used to work until they patched their browsers) - you found an exploit.

     

    With not invasive memory corruption you'll turn some output Chinese, or crash the application at a point. Do it carelessly, and you can face charges.

    • Like 1

  14. 8 minutes ago, Attila Kovacs said:

    it was error prone and annoying initializing the querys to nil.

    And this is something I will never EVER argue about. For me it also makes the code harder to read, as I already got used to Create - Try - Finally - Free - End. I'll always be alarmed if this pattern is not followed. In my case unfortunately it's a bit different, as we have (I'd call them as) protocol descriptors in our custom dataset descendants, no direct SQL queries.

     

    But I hate setting the variables to nil before.

    • Haha 1

  15. 15 minutes ago, Attila Kovacs said:

    @aehimself If it's not marked as "var" the parameter is just a preinitialized local var which won't be freed, as this was a factory logic originally. Or am I missing something?

    If you mean you have to set your variable to nil before first calling this method there's no question about it. As for freeing, that should be handled in the same method, which calls this helper. Like...

    Var
     myquery: TFDQuery
    Begin
     myquery := nil;
     Try
      createMyQuery(myquery, 'SELECT * FROM USERS');
      [...]
     Finally
      myquery.Free;
     End;
    End;

    What I mean is, whether if var is declared in the definition of createMyQuery or not; it will not leak and will function correctly, as "myquery" will be passed as a reference (the object itself) and not a copy of it.

     

    Edit: I'm an idiot. Instead of asking I could have made a test case to confirm. Will come back soon with my results.

     

    Edit-edit: Disregard everything. Var is needed.

    Procedure TForm1.SLCreateAdd(inSL: TStringList);
    Begin
     If Not Assigned(inSL) Then inSL := TStringList.Create;
     inSL.Add('Hello');
     inSL.Add('World');
    End;
    
    procedure TForm1.Button1Click(Sender: TObject);
    Var
     sl: TStringList;
    begin
     sl := nil;
     Try
      SLCreateAdd(sl);
      ShowMessage(sl.Count.ToString);
     Finally
      sl.Free;
     End;
    end;

    Causes a nullreference exception and leaks a TStringList object.

    Procedure TForm1.SLCreateAdd(Var inSL: TStringList);
    Begin
     If Not Assigned(inSL) Then inSL := TStringList.Create;
     inSL.Add('Hello');
     inSL.Add('World');
    End;

    does not. I am obviously wrong with my memories and most probably used Var in my helper 🙂

     

     


  16. Just now, Attila Kovacs said:

    @aehimself "var" also enforces passing a variable to the method, in this case it would be bad if you could call it with nil.

    You are absolutely right in this, I'm always declaring local variables though (and as it's my helper I'll not call it with nil) it seems to be irrelevant in my case. My real surprise is/was the leaking part. If my memory doesn't cheat and class instances are indeed passed as references to methods, there should not be any leaks in the above code, with var or not.


  17. As network traffic is really easy to be misdirected, I am strongly against network-based authentication. As @Sherlock pointed out, they will simply fail to launch (or fall back to demo mode) in most of the real-world customer scenarios, where networks are controlled as they should be.

    Local license authentication is the way to go in my opinion, but there is no fool-proof way. Everything can be (and if it worth, will be) hacked no matter what. You only can make the job of the pirate harder with obfuscation, fake no-op assembly blocks, custom multi-level encryption, on-the-fly method assignments and so on. One thing for sure, delay checking the license and NEVER use something like If Not TMyLicense.IsValid Then Halt as on assembly level that's a modification of one JMP to bypass everything.

     

    I started to learn the proper use of pointers and if my license is not valid, I'm simply corrupting memory on purpose. It might (that's the beauty in it, it's not guaranteed) start to crash or malform data at the most random places / times. If you hide it well enough, even the hacker might think that it's a piece of junk and does not worth the effort...


  18. 4 hours ago, Dany Marmur said:

    As Gunter says, is a quite common practice.

    I am actively using this solution (usually with an atomic value) but it always felt hacky. Strange to see that it seems to be THE way.

    At least I did not write smelly code. At least in this part... 🙂


  19. On 4/23/2020 at 3:02 PM, Anders Melander said:

    Yes it is. The code you have posted will leak the TFDQuery instance and not return anything.

    I thought class instances are always passed as references (pointers to the actual instance)...? I also do have a similar method to create and initialize the dataset and I think I didn't use var there. I do have to doublecheck it; I don't have access to that piece of code right now.

×