Jump to content

aehimself

Members
  • Content Count

    1030
  • Joined

  • Last visited

  • Days Won

    22

Posts posted by aehimself


  1. Huge +1 to @Der schöne Günther. At work we have 2-5 levels of inherited frames and meet this kind of issue every single day. Reviewing DFMs became a tradition, especially since Delphi tends to move components around with +-1 pixels upon saving - even if you didn't touch them.

     

    Edit: It was like this back in 10.0 Seattle as far as I can remember. It's not only affecting 10.4(.1).


  2. 4 minutes ago, Anders Melander said:

    Why so sad? Because of the leak?

    Leaks are easy to be fixed, no. I just personally dislike inline variable declarations.

     

    If the Delphi language is moving closer to C#, they could have implemented something useful like Linq or the "?" operator when assigning values to variables (String myVar = inCondition ? "" : otherClass.SringProperty; forgot the name, sorry) in my opinion; that's all.


  3. Just now, FPiette said:

    One more important thing: In the OnDataAvailable event, you should read ALL data arriving. So you should make a loop calling Receive until it return < 0. If the buffer used for receive is to small, read anyway in a secondary small buffer and throw data away, then signal the error to the user "buffer to small".

    Yep, that's exactly what I do now. I have an outer cycle:

            Repeat
             // Receive the fixed size buffer (set in constructor)
             read := Self.Receive(@_rcvbuf[0], Length(_rcvbuf));
             If read <= 0 Then Break;
             [...]
            Until False;

    Within this cycle, I have an internal cycle which is assembling the packets to be decrypted from _rcvbuf; based on the size indicator. If the packet did not fit in _rcvbuf, the next outer cycle will read out the next fragment.

     

    This method seems to be working, packets are flowing in and being decrypted correctly. I just have to wait a couple of days to see if the lock-up happens again or not.
     

    • Like 1

  4. 2 minutes ago, Kas Ob. said:

    You lost me

    Indeed I did! My brain simply didn't process that the event is not placing the data in the beginning of InBuf... guess I'd finally need some sleep?

     

    I went on with the more difficult version however. I'm reading a fixed size (let's say 200 bytes). If the packet buffer is empty, I read the size and set the packet buffer to that size. If not, I'm appending as many bytes as needed / possible from this 200. If all was processed, I read the next 200 until there's none left. If it works (and I do have my hopes high) I'll move some processing parts to submethods to make the main one easier to read.


  5. 29 minutes ago, Kas Ob. said:

    And forgot to mention in my sample above there is no mention to the situation were it might fail, it is with the buffer size, example client send two 4k packets and server triggered the DataAvailable event but read only 3k then the rest of the first and the second received, in this case server will read the 2k only and might not get another event to receive again,

    I will be honest, I did not understand how it can stop receiving, up until this point. It all makes sense now.

    I'm in the progress of re-writing it this way, we'll see if that will bring success.

     

    Edit: the lockup indeed happens when there is a significantly larger amount of data incoming (6-7 * 150 bytes instead of 80) so that also confirms this theory. I finished converting the read-out logic and deployed it on the test server. We will see in a couple of days if it worked 🙂


  6. Hello,

     

    Most probably this is NOT going to be an issue with ICS as I experienced the very same symptom with TServerSocket before I made the switch. I'm mainly looking for tips on where can I start debugging the issue as for the time being I'm completely out of ideas.

     

    I have an application which is connecting to a server on a single, TCP socket. On average, 80 bytes (binary) are sent from the clients to the server each minute, in one packet. The TCP channel is unidirectional, messages are only going from the client to the server. Everything is working perfectly, until a seemingly random time; when for a seemingly random client the data is not received anymore. The TCP connection is still established, the client is still sending the packet and WireShark confirms that it arrives to the server machine. It seems that the socket's receive event stops firing off. What is even more interesting, that it affects random clients (with different OSes, sometimes Windows 2000, sometimes 2012 R2, sometimes 2019), only causes one client to get stuck at a time, but multiple clients can get stuck during the process. The application can remain in this state for days without memory increase (so I'm not inflating the local buffer endlessly, without triggering the data processing), memory or handle leaks. If I restart the client or the server, forcing the client to reconnect, everything jumps back to normal.

     

    As for a little background, the very same logic was working perfectly, when the binary data was converted to, and sent as text. By switching to binary the sent data size was reduced from 200-500 bytes to 60-100. I don't know why but I suspect this change triggered the error I'm seeing now; and maybe because of the data size.

     

    https://docs.microsoft.com/en-us/troubleshoot/windows/win32/data-segment-tcp-winsock mentions that TCP is not really efficient with unidirectional, small data packets but it only will result delivery delay. For me it seems to be irrelevant.

     

    Sending code looks something like this (TBufferLength = Word):

    Function TCommunicationEngine.Send(Const inText: String): Boolean;
    Var
     buf, len: TBytes;
     sent: Integer;
    Begin
     Result := False;
     Try
      // Step 1 - String to TBytes
      buf := TEncoding.UTF8.GetBytes(inText);
    
      // Step 2 - Encryption of "buf"
      // ...
      
      // If the buffer exceeds the maximum length allowed, raise an error as it can not be sent!
      If Length(buf) > TBufferLength.MaxValue Then Raise ETCPPortError.Create('Buffer overflow, cannot send ' + Length(buf).ToString + ' bytes!');
    
      // Step 3 - Append the length of the buffer to the beginning of the buffer
      SetLength(len, SizeOf(TBufferLength));
      PBufferLength(@len[0])^ := Length(buf);
      SetLength(buf, Length(buf) + Length(len));
      Move(buf[0], buf[Length(len)], Length(buf) - Length(len));
      Move(len[0], buf[0], Length(len));
    
      // Step 4 - Send the completed buffer
      sent := _tcpport.Send(@buf[0], Length(buf));
    
      // Step 5 - Post-sending verifications
      If sent < 1 Then Raise ETCPPortError.Create('No data was sent!');
      Log(LOG_TCP, '> ' + BytesToString(buf) + ' (' + sent.ToString + ' bytes)');
      Result := True;
     Except
      On E:Exception Do HandleException(E, 'while sending data');
     End;
    End;

    Receiving block looks like this (TClientConnection is a descendant of TWSocketClient, _data is a strict private TBytes, _count is a strict private Integer):

    Procedure TClientConnection.ConnectionDataAvailable(inSender: TObject; inError: Word);
    Var
     buf: TBytes;
     need, len, read: Integer;
     debuglog: String;
    Begin
     // Note that due to how TCP works, if packets are arriving at high speed they might be appended to one single ReceiveText event.
     If BanList.IsBanned(Self.PeerAddr) Then Self.Close // If the IP where the data is coming from is banned, disconnect
       Else Begin
            len := Self.RcvdCount;
            If len = 0 Then Exit;
            Repeat
             debuglog := Self.PeerAddr + ' > Read cycle starts. received data size: ' + len.ToString + ', socket data size: ' + Length(_data).ToString + ', position: ' + _pos.ToString + '. ';
             If _pos = 0 Then Begin
                              // Position is 0 = there is no fragment. Read the data size first
                              If len < SizeOf(Word) Then Begin
                                                         BanList.Failed(Self.PeerAddr, 'Packet size is incorrect');
                                                         Self.Close; // Packet is corrupted, reset the connection
                                                         Exit;
                                                         End;
                              SetLength(buf, SizeOf(TBufferLength));
                              Self.Receive(@buf[0], Length(buf));
                              // buf now contains the data size. Resize socket's data length
                              SetLength(_data, PBufferLength(@buf[0])^);
                              // As the data size is read out, reduce the received length
                              len := len - Length(buf);
                              debuglog := debuglog + 'Prepared a ' + Length(_data).ToString + ' byte buffer. ';
                              End;
             need := Length(_data) - _pos;
             If need < 0 Then Begin
                              // this should never happen. I'll just keep it here for debugging purposes...
                              Log(LOG_STD, 'Possible memory corruption happened. Data size of ' + Self.PeerAddr + ' is ' + Length(_data).ToString + ', position is ' + _pos.ToString);
                              Self.Close;
                              Exit;
                              End
               Else
             If need > 0 Then Begin
                              If len < need Then SetLength(buf, len) // If we received less bytes than needed to fill the buffer, read everything
                                Else SetLength(buf, need); // If we received more bytes than needed to fill th buffer, only read what is needed
                              debuglog := debuglog + 'Reading out ' + Length(buf).ToString + ' bytes. ';
                              read := Self.Receive(@buf[0], Length(buf));
                              If read > 0 Then Begin
                                               debuglog := debuglog + read.ToString + ' bytes read. ';
                                               // Something was read from the buffer. Append it to the socket's data
                                               Move(buf[0], _data[_pos], read);
                                               // Increase data position
                                               Inc(_pos, read);
                                               // Reduce received length
                                               len := len - read;
                                               End
                                Else debuglog := debuglog + 'Nothing was read. ';
                              End;
             If _pos = Length(_data) Then Begin
                                          Log(LOG_TCP, debuglog.TrimRight);
                                          Log(LOG_TCP, Self.PeerAddr + ' > ' + BytesToString(_data) + ' (' + _pos.ToString + ' bytes)');
                                          // Buffer is full. Process the data.
                                          
                                          // Decrypt the buffer...
                                          // ...
    
                                          Try
                                           ProcessLine(Self.PeerAddr, timestamp, TEncoding.UTF8.GetString(_data));
                                          Except
                                           On E:Exception Do Begin
                                                             Log(LOG_STD, TranslateException(E, 'processing client data'));
                                                             BanList.Failed(Self.PeerAddr, 'data processing error: ' + E.Message);
                                                             Self.Close;
                                                             End;
                                          End;
                                          _pos := 0;
                                          SetLength(_data, 0);
                                          End
               Else
             If Not debuglog.IsEmpty Then Log(LOG_TCP, debuglog.TrimRight);
            Until len = 0;
            If (Length(_data) > 0) Or (_pos > 0) Then Log(LOG_TCP, 'Storing a fragment for ' + Self.PeerAddr + ': Data size: ' + Length(_data).ToString + ', position: ' + _pos.ToString);
            End;
    End;

    I know that there are a couple of premature exits before the actual data processing, but even when I added temporary logging before these, none of them was reached.

     

    I'll investigate on how I can, and will try to add TCP_NODELAY and SO_SNDBUF, but I doubt that they will make any difference. Until then, I'm really interested what are the aspects what I did not even think of until now.

     

    I'm using ICS v8.64, application is compiled using Delphi 10.4.1 as a 32-bit executable, and is executed as a Windows service on a Server 2012 R2 machine.

     

    Any help is greatly appreciated 🙂


  7. 12 hours ago, FPiette said:

    You should display the ErrCode argument in OnSessionConnected event handler.

    And I seriously missed that, I just don't know how. Using the SessionConnected handler WITH a small check to the error code works - as designed I suppose 🙂

    Let's hope that changing to a little bit more up-to-date component will solve the connectivity issues I had!

     

    10 hours ago, Angus Robertson said:

    You can not use state alone to know when a connections succeeds. 

    This was the part I was missing; coming from an old, outdated component I expected the stateflow to be different if a connection attempt fails or succeeds. I was just unsure if I did something wrong in other parts of my code, or not.

    I'm an ICS newbie. I'll learn 🙂


  8. Hello,

     

    I would like to have a TMemo in my application, where the user could manually toggle the WordWrap property. When changing this property though runtime, the change has no effect on the display whatsoever.

    I did my homework, and I see only one reference to FWordWrap in the TCustomMemo implementation, which is this block:

    procedure TCustomMemo.CreateParams(var Params: TCreateParams);
    const
      ScrollBar: array[System.UITypes.TScrollStyle] of DWORD = (0, WS_HSCROLL, WS_VSCROLL,
        WS_HSCROLL or WS_VSCROLL);
      WordWraps: array[Boolean] of DWORD = (0, ES_AUTOHSCROLL);
    begin
      inherited CreateParams(Params);
      with Params do
        Style := Style and not WordWraps[FWordWrap] or ES_MULTILINE or ScrollBar[FScrollBars];
    end;

    The setter looks like this:

    procedure TCustomMemo.SetWordWrap(Value: Boolean);
    begin
      if Value <> FWordWrap then
      begin
        FWordWrap := Value;
        RecreateWnd;
      end;
    end;

    Placing a breakpoint in CreateParams confirms that RecreateWnd triggers the call of CreateParams, where FWordWrap already has the most recently set value, however it does not change the behavior.

     

    Google only lead me to one question on the FPC forums, but with no viable solution. Is there a way to make this work without using a 3rd party component?


  9. On 9/22/2020 at 1:28 PM, Fr0sT.Brutal said:

    Offtop: I'm always wondering why people tend to write "APP" (in uppercase) when it's just a short form of "application". Nobody says "I wrote an APPLICATION".

    Because it's the mainstream. I bet my shoes on that you can track it back to Apple.


  10. Hello,

     

    I'm in the process to migrate an old code from TClientSocket to an ICS TWSocket. All seems to work fine, except the OnConnect & OnDisconnect handlers.

     

    Create a new VLC project, with a TWSocket and a memo on it and use the following code:

    Function SocketStateToString(Const inSocketState: TSocketState): String;
    Begin
     Case inSocketState Of
      wsInvalidState: Result := 'invalid';
      wsOpened: Result := 'opened';
      wsBound: Result := 'bound';
      wsConnecting: Result := 'connecting';
      wsSocksConnected: Result := 'socks connected';
      wsConnected: Result := 'connected';
      wsAccepting: Result := 'accepting';
      wsListening: Result := 'listening';
      wsClosed: Result := 'closed';
      wsDnsLookup: Result := 'DNS lookup';
      Else Result := 'unknown';
     End;
    End;
    
    procedure TForm3.FormCreate(Sender: TObject);
    begin
     WSocket1.Connect;
    end;
    
    procedure TForm3.WSocket1ChangeState(Sender: TObject; OldState,
      NewState: TSocketState);
    begin
     Memo1.Lines.Add('State change from ' + SocketStateToString(OldState) + ' to ' + SocketStateToString(NewState));
    end;
    
    procedure TForm3.WSocket1SessionClosed(Sender: TObject; ErrCode: Word);
    begin
     Memo1.Lines.Add('Session closed');
    end;
    
    procedure TForm3.WSocket1SessionConnected(Sender: TObject; ErrCode: Word);
    begin
     Memo1.Lines.Add('Session connected.');
    end;

    Set the WSocket's Addr to 127.0.0.1 and the port to 1024. Make sure no application listens.

     

    When I run the above code, I get the following result:

     

    State change from closed to opened
    State change from opened to connecting
    State change from connecting to connected
    Session connected.
    State change from connected to closed
    Session closed
     

    The state goes to connected for a brief moment, also session is connected... to nothing (?) before both goes back to closed.

     

    What is the normal place to put my code in, which is granted to run ONLY if the socket was really connected and disconnected?

     

    Edit: I'm using ICS vV8.64 on Delphi 10.4.1


  11. Hello,

     

    I have a pagecontrol descendant, where I overridden the DoDrag method to show the picture of the dragged tab:

    Procedure TPageControl.DoStartDrag(Var DragObject: TDragObject);
    Var
     tab: TRect;
     bmp, tabbmp: TBitMap;
    Begin
     inherited;
     If DragObject <> nil Then Exit;
    
     // Create a bitmap of the tab button under cursor
     tab := Self.TabRect(Self.ActivePage.TabIndex);
     bmp := TBitmap.Create;
     bmp.Canvas.Lock;
     tabbmp := TBitmap.Create;
     Try
      bmp.Height := Self.Height;
      bmp.Width := Self.Width;
      tabbmp.Height := tab.Height;
      tabbmp.Width := tab.Width;
      Self.PaintTo(bmp.Canvas.Handle, 0, 0);
      tabbmp.Canvas.CopyRect(tabbmp.Canvas.ClipRect, bmp.Canvas, tab);
      DragObject := TPageControlExtraDragObject.Create(tabbmp);
     Finally
      bmp.Canvas.Unlock;
      FreeAndNil(tabbmp);
      FreeAndNil(bmp);
     End;
    End;

    When the user clicks on one of the tabs I'm manually initiating the dragging process by Self.BeginDrag(False);. When I went into BeginDrag, I saw that if you do not specify a dragging threshold, it takes this value from Mouse.DragThreshold, which is 5 pixels. This - to me - means that the dragging is NOT initiated unless the button is still down, and the cursor went at least 5 pixels away from the initiating position.

    What happens now is that the DoStartDrag event fires immediately, the bitmap is taken and is drawn as a fly-out immediately. Even it I am just switching tabs, which is kind of annoying.

     

    So the question is... if my logic is right (and the DoStartDrag should be fired based on distance) why it is firing immediately? If not, is there a simple setting I forgot to add or I manually have to handle this by MouseMove?

     

    I'm using Delphi 10.4.1, but the "issue" was present in 10.4 and 10.3 as well.


  12. 3 hours ago, Edwin Yip said:

    I think you can also implement the 're-order handles' like that~

    Possible, however there are no requests like that for the time being.

     

    And this is why I hate to implement something new. The next hour when I published the resizable multi-line editor an other request came in, that "it would be nice" if the editor would resize with the form itself, instead of showing a scrollbar 😄


  13. Just a small update, I managed to implement the resizing logic and it was really, really easy. Most of my time went away by drawing the transparent, themed, system default resize gripper...

    ...which almost can not be seen on dark styles, so totally with it! 👍

     

    image.thumb.png.e1cf11f12c0ef3bd1afbd86f396eddc1.png

     

    image.thumb.png.1b948d5a569678917bf3e914e7e6721f.png

     

    Frames above and below are adjusting properly, not changing places or jumping around. Overflow is handled correctly by the alClient scrollbox, if the contents grow too large for the window.

    The only thing is that I did not use splitters, I wrote the resizing logic myself (which is awfully long, like 10 lines of code?)

     

    Procedure TMultiLineParamFrame.ResizeHandleImageMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
    Begin
     _resizing := True;
     SetCapture(Self.Handle);
    End;
    
    Procedure TMultiLineParamFrame.ResizeHandleImageMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
    var
     relative: TPoint;
    begin
     If Not _resizing Then Exit;
    
     relative := ScreenToClient(Mouse.CursorPos);
     If relative.Y > 47 Then Height := relative.Y; // Burned in magic number, because we all love them!
    End;
    
    Procedure TMultiLineParamFrame.ResizeHandleImageMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
    Begin
     ReleaseCapture;
     _resizing := False;
    End;

     

    Drawing the gripping handle:

    Constructor TMultiLineParamFrame.Create(AOwner: TComponent);
    Var
     bmp: TBitmap;
    Begin
     inherited;
     _resizing := False;
     bmp := TBitmap.Create;
     Try
      bmp.Height := 16;
      bmp.Width := 16;
      bmp.TransparentColor := clYellow;
      bmp.Canvas.Brush.Color := bmp.TransparentColor;
      bmp.Canvas.FillRect(Rect(0, 0, bmp.Width, bmp.Height));
    
      StyleServices.DrawElement(bmp.Canvas.Handle, StyleServices.GetElementDetails(tsGripper), Rect(0, 0, bmp.Width, bmp.Height));
      ResizeHandleImage.Picture.Assign(bmp);
     Finally
      FreeAndNil(bmp);
     End;
    End;

     

    • Like 2

  14. I just found this awesome thing of @RRUZ, called TSMBIOS. Unfortunately though it seems to be abandoned now, and the only result I get on an Ubuntu 18.04 server is a nullpointer exception; even if I execute it via sudo (or su).

    Does anyone know about an updated fork of this component or something similar; which can gather hardware information on Windows and Linux?


  15. 7 minutes ago, dummzeuch said:

    Got it to work. Now the GExperts PEInfo tool also shows all strings in all languages stored in the version info resource which includes the non-standard once like BuildDateTime:

    Nice job. I think soon I'll need something like this in one of my applications - at least I'll know where to look 🙂

×