Jump to content
ChrisChuah

Access Violation when i free idUDPClient

Recommended Posts

Hi

I am trying to create a thread that will send out UDP message and when it receive the UDP message from the server, it will send a Reply as event back to the caller.

I create the UDPClient in the Execute function and of course i would need to free it when the thread ends.

But why do i get access violation when i free it?

The call stack is

--IdSocketHandle.CloseSocket.

--IdSocketHandle.Destroy

-- System.TObject.free;

--IdUDPBase.CloseBinding

--IdUDPBase.Destroy

--IdUDPClient.Destroy

 -- System.TObject.Free

--FUDP.Free;

 

please help to guide me what is the best way to send out UDP message and wait for reply?

Should i use UDPServer rather than UDPClient?

 

=== Code ===

procedure ilUDPClientThd.Execute;
var
  l_port : UInt32;
  l_memStream : TMemoryStream;
  l_len : integer;
  l_buf : TIdBytes;
  l_ilUDPData : Tt_BootReqMsg;
  l_ip : string;
  l_ipBytes : array[0..15] of byte;
  FUDP: TIdUDPClient;
begin
  { Place thread code here }
  l_ip := getOwnIP;
  if l_ip = '' then begin
    DoOnLog('Cannot find own IP address');
    exit;
  end;
  FUDP := TIdUDPClient.Create(nil);
  FUDP.Host := FBroadcastIP;
  FUDP.Port := FBroadcastPort;
//  FUDP.Binding.Port := FBroadcastPort;
  try
    l_port := FBroadcastPort;
    l_memStream := TMemoryStream.create;

    l_buf := ToBytes('SOREQ', IndyTextEncoding_ASCII);
    l_memStream.Write(l_buf[0], 6);

    l_port := GStack.HostToNetwork(l_port);
    l_memStream.write(l_port, 4);

    getArrayBytes(l_ip, l_ipBytes);
    l_memStream.Write(l_ipBytes, SizeOf(l_ipBytes));

    l_len := l_memStream.size;
    setLength(l_buf, l_len);
    l_memStream.position := 0;
    l_memStream.Read(l_buf[0], l_len );
    l_memStream.Clear;
    try
      FUDP.ReceiveTimeout := 3000;
      FUDP.BroadcastEnabled := true;
//      FUDP.Active := true;
      FUDP.SendBuffer(l_buf);
//      FUDP.SendBuffer(l_buf[0], l_len);
      FMsg := 'Send To Address: ' + FUDP.Host + ' Port: ' + Inttostr(FUDP.Port) + ' using IP: ' + l_ip;
      Synchronize(WriteToLog);
//      l_len := FUDP.ReceiveBuffer(l_ilUDPData, sizeof(l_ilUDPData), FUDP.ReceiveTimeout);
      setLength(l_buf, sizeof(l_ilUDPData));
      l_len := FUDP.ReceiveBuffer(l_buf);
      if l_len <= 0 then begin
        FMsg:='No Data received in UDP';
        FRepliedClientIP := '';
        FRepliedClientPort := 0;
        Synchronize(ReplyToClient);
        Synchronize(WriteToLog);
      end
      else begin
        l_ilUDPData.Port := GStack.NetworkToHost(l_ilUDPData.Port);
        FRepliedClientIP := getStringFromBytes(l_ilUDPData.hid);
        FRepliedClientPort := l_ilUDPData.port;
        Synchronize(ReplyToClient);
      end;
    except
      on E:Exception do begin
        FMsg := 'Error in Recv Data in UDP: ' + e.message;
        FRepliedClientIP := '';
        FRepliedClientPort := 0;
        Synchronize(ReplyToClient);
        Synchronize(WriteToLog);
      end;
    end;
    l_memStream.Free;
    FUDP.Binding.Destroy;
  except
    on E:exception do begin
      FMsg := 'Exception in SoFind: ' + e.Message;
      FRepliedClientIP := '';
      FRepliedClientPort := 0;
      Synchronize(ReplyToClient);
      Synchronize(WriteToLog);
    end;
  end;
  FUDP.Disconnect;
  FUDP.Free; <<==== Access violation when i tried to free UDP Client 
end;

Share this post


Link to post
2 hours ago, ChrisChuah said:

FUDP.Binding.Destroy;

Why are you doing this? This is the root of your crash. You don't own the Binding object, so you have no business destroying it like this.  Let TIdUDPClient manage the Binding for you.  It will be destroyed automatically when the TIdUDPClient itself is destroyed.

 

The stack trace you have provided clearly shows TIdUDPClient's destructor is trying to access the Binding after you have manually destroyed it, hence the crash.

 

If you really want to destroy the Binding manually, you need to instead set the TIdUDPClient.Active property to False, which will internally close and destroy the Binding if it is currently active:

//FUDP.Binding.Destroy; 
FUDP.Active := False;
Quote

Should i use UDPServer rather than UDPClient?

No.  That would just complicate your code further, since TIdUDPServer is asynchronous, whereas TIdUDPClient is synchronous.

 

That being said, I see some other issues with your code...

Quote

FUDP := TIdUDPClient.Create(nil);

...
FUDP.Free;

You should wrap the TIdUDPClient in a try/finally block to ensure that it gets Free()'d in case an exception is raised:

FUDP := TIdUDPClient.Create(nil);
try
  ...
finally
  FUDP.Free;
end;

Same with the TMemoryStream.

Quote

l_buf := ToBytes('SOREQ', IndyTextEncoding_ASCII);
l_memStream.Write(l_buf[0], 6);

This is a buffer overflow, as l_buf has 5 bytes in it but you are asking Write() to read 6 bytes from it. l_buf is not null-terminated, so if you need it to be then you have to either add an explicit terminator to the string:

l_buf := ToBytes('SOREQ'#0, IndyTextEncoding_ASCII);
l_memStream.Write(l_buf[0], Length(l_buf));

Or, write the null-terminator separately:

const NullTerm: AnsiChar = #0;
...
l_buf := ToBytes('SOREQ', IndyTextEncoding_ASCII);
l_memStream.Write(l_buf[0], Length(l_buf));
l_memStream.Write(NullTerm, 1);

Alternatively, use Indy's own WriteStringToStream() function in the IdGlobal unit:

WriteStringToStream(l_memStream, 'SOREQ'#0, IndyTextEncoding_ASCII);
WriteStringToStream(l_memStream, 'SOREQ', IndyTextEncoding_ASCII);
WriteStringToStream(l_memStream, #0, IndyTextEncoding_ASCII);

In any case, you are converting the TMemoryStream data to a TIdBytes for transmission, so you might consider simply getting rid of the TMemoryStream altogether and use Indy's own TIdBytes-oriented functions instead (CopyTIdString(), CopyTIdUInt32(), CopyTIdByteArray(), etc), which are also in the IdGlobal unit:

SetLength(l_buf, 6 + SizeOf(l_port) + SizeOf(l_ipBytes));

CopyTIdString('SOREQ'#0, l_buf, 0, -1, IndyTextEncoding_ASCII);

l_port := GStack.HostToNetwork(l_port);
CopyTIdUInt32(l_port, l_buf, 6);

getArrayBytes(l_ip, l_ipBytes);
CopyTIdByteArray(l_ipBytes, 0, l_buf, 10, SizeOf(l_ipBytes));
Quote

l_len := FUDP.ReceiveBuffer(l_buf);
if l_len <= 0 then begin

You are not checking to make sure that l_len specifies that enough bytes are actually available to read from l_buf into l_ilUDPData. There is no guarantee that the received datagram is actually as large as your buffer.

Quote

FUDP.Disconnect;

You are not Connect()'ing the TIdUDPClient to a specific peer, so this line is useless and can be removed. Especially since the next statement is Free()'ing the TIdUDPClient anyway.

Edited by Remy Lebeau

Share this post


Link to post

Thanks Remy

The only problem that i have with strings in delphi is that I dont know if they are Windows widechar or UTF8 char or ansi value

 

Edit1.text := 'MET';

 

ShowMe(Edit1.text);

 

Function showMe(aValue : string);

var

  l_value : string;

begin

  l_value := aValue + 'A';

  UDPClient.Write(l_value);

end;

 

So when the edit1 value is pass into ShowMe function, what is the length of l_value? 

As my server only accepts ANSI type of characters, the data sent may be Windows WideChar.

Hence to be on the safe side, i would have to translate it to idBytes.

 

What is the general rule on how you would send data when the server only accepts ANSI characters.

 

Please advise

regards

chris

 

BTW, After 20 years of using Delphi, i still remembered you were helping me last time when i was using Delphi 5, 6 in one of the forum.

Now, its hard to find help for delphi.


 

 

Share this post


Link to post

Hi Remy

Would like to ask why the UDPClient did not receive any data.

Attached is the screen capture of the Wireshark.

The first pic shows the server received the message sent from the delphi client

The second pic shows the server sends out the SORE (response) message to the delphi client IP address.

However, the client reported that there is no data received

 

<27/5/2022 11:14:02 am> Send To Address: 192.168.84.255 Port: 55510 using IP: 192.168.84.129
<27/5/2022 11:14:05 am> IP:  Port: 0 replied
<27/5/2022 11:14:05 am> No Data received in UDP

 

Did i do something not right?

please advise

 

regards

chris

 

 183309202_ScreenShot2022-05-27at11_16_32AM.thumb.png.9233321f3c92cc86f555d794e4c622e5.png21642686_ScreenShot2022-05-27at11_16_55AM.thumb.png.8de6716efc5ad415666e4a9a05e97504.png1504326953_ScreenShot2022-05-27at11_18_33AM.png.f6734a8a20bfedfff7b1312bf86acc90.png

Share this post


Link to post

Hi

When i use the IdUDPClient inside the thread object, the idUDPClient does not get any messages back even though it can be seen from the Wireshark that the message was sent back.

However, if i place the IdUDPClient on a Form, i am able to get the message.

 

Something funny is that when i use the IdUDPClient on the Form to send out the message, 

the first response message header is SOREQ <== this header is request header

Checking on the Wireshark, the server returned SORE

I tried to initialise the l_buf to $0 before retrieving from idUDPClient.

However, its only the first message sent that will be always SOREQ and the rest will be SORE

And the IP address returned in the message should be 192.168.84.128 which indicate the server address itself.

seems like the first message is the same message that i sent out.

 

 

please advise

 

regards

chris

 

===== Code for IdUDPClient on Form =====

procedure TForm1.Button2Click(Sender: TObject);
var
  l_port : UInt32;
  l_memStream : TMemoryStream;
  l_len : integer;
  l_buf, l_buf1 : TIdBytes;
  l_ilUDPData : Tt_BootReqMsg;
  l_ip : string;
  l_ipBytes : array[0..15] of byte;
  FMsg : string;
  FRepliedClientIP : string;
  FRepliedClientPort : Uint32;
  l_index : integer;
begin
  l_ip := getOwnIP;
  if l_ip = '' then begin
    writeLog('Cannot find own IP address');
    exit;
  end;
  IdUDPClient1.Host := '192.168.84.255';
  IdUDPClient1.Port := 55510;
  IdUDPClient1.BoundPort  := 55510;
  try
//    FUDP.Active := true;
    l_memStream := TMemoryStream.create;

    l_buf := ToBytes('SOREQ'#0, IndyTextEncoding_ASCII);
    l_memStream.Write(l_buf[0], 6);

    l_port := 55510;
    l_port := GStack.HostToNetwork(l_port);
    l_memStream.write(l_port, 4);

    getArrayBytes(l_ip, l_ipBytes);
    l_memStream.Write(l_ipBytes, SizeOf(l_ipBytes));

    l_len := l_memStream.size;
    setLength(l_buf, l_len);
    l_memStream.position := 0;
    l_memStream.Read(l_buf[0], l_len );
    l_memStream.Clear;
    try
      IdUDPClient1.ReceiveTimeout := 3000;
      IdUDPClient1.BroadcastEnabled := true;
      IdUDPClient1.SendBuffer(l_buf);
      FMsg := 'Send To Address: ' + IdUDPClient1.Host + ' Port: ' + Inttostr(IdUDPClient1.Port) + ' using IP: ' + l_ip;
      WriteLog(FMsg);
      setLength(l_buf, sizeOf(l_ilUDPData));
      for l_index := 0 to length(l_buf) - 1 do. <== initialise the buffer but i still receive SOREQ only on 1st time.
        l_buf[l_index] := byte($0);

      l_len := IdUDPClient1.ReceiveBuffer(l_buf, 2000);
      if l_len <= 0 then begin
        FMsg:='No Data received in UDP';
        FRepliedClientIP := '';
        FRepliedClientPort := 0;
        WriteLog(FMsg);
      end
      else begin
        l_memStream.Clear;
        l_memStream.Position := 0;
        l_memStream.Write(l_buf[0], l_len);
        l_memStream.Position := 0;
        setlength(l_buf1, 6);
        l_memStream.Read(l_buf1[0], length(l_buf1));
        WriteLog('Op Code: ' + getStringFromBytes(l_buf1));
        l_memStream.Read(FRepliedClientPort, sizeof(FRepliedClientPort));
        WriteLog('Port value: ' + Inttostr(FRepliedClientPort));
        l_memStream.Read(l_ilUDPData.hid, sizeof(l_ilUDPData.hid));
        FRepliedClientIP := getStringFromBytes(l_ilUDPData.hid);
        FRepliedClientPort := GStack.NetworkToHost(FRepliedClientPort);
        WriteLog('Replied. IP: ' + FRepliedClientIP + ' Port: ' + Inttostr(FRepliedClientPort));
      end;
    except
      on E:Exception do begin
        FMsg := 'Error in Recv Data in UDP: ' + e.message;
        FRepliedClientIP := '';
        FRepliedClientPort := 0;
        WriteLog(FMsg);
      end;
    end;
    l_memStream.Free;
  except
    on E:exception do begin
      FMsg := 'Exception in SoFind: ' + e.Message;
      FRepliedClientIP := '';
      FRepliedClientPort := 0;
      WriteLog(FMsg);
    end;
  end;

end;
 

==== End =====

 

380883448_ScreenShot2022-05-27at2_10_12PM.png.dc0c76981140d9c153e1f801f4d6a775.png1796901076_ScreenShot2022-05-27at2_10_23PM.thumb.png.b1f074d6598bfb7c485260a2c9e6190b.png1689686418_ScreenShot2022-05-27at2_10_39PM.thumb.png.f912e46f3fbc1778b6e59875d53f76b9.png

Share this post


Link to post
16 hours ago, ChrisChuah said:

The only problem that i have with strings in delphi is that I dont know if they are Windows widechar or UTF8 char or ansi value

Very simple:

 

pre-D2009:

Char is AnsiChar

String is AnsiString, using ANSI encoded AnsiChars

UTF8String is just an alias for AnsiString, it is not guaranteed to be UTF-8 unless it comes from UTF8Encode()

 

post-D2009:

Char is WideChar

String is UnicodeString, using UTF-16 encoded WideChars

UTF8String is a native string type, using UTF-8 encoded AnsiChars

Quote

Edit1.text := 'MET';

ShowMe(Edit1.text);

...

So when the edit1 value is pass into ShowMe function, what is the length of l_value?

In you example, the length will be 4 Chars (after adding the 'A').  How many bytes those Chars will take up depends on the byte encoding you tell the UDPClient to use.  In ANSI/UTF-8, ASCII characters are 1 byte each.  In UTF-16, they are 2 bytes each.  Things get more complex when you start dealing with non-ASCII characters.

Quote

As my server only accepts ANSI type of characters, the data sent may be Windows WideChar.

First off, TIdUDPClient does have a Write() method, it has Send() and SendBuffer() methods instead.

In any case, internally TIdUDPClient will convert the String to bytes, and then transmit the bytes.  The default encoding used to produce those bytes is US-ASCII, but you can specify a different encoding in the optional AByteEncoding parameter of Send/Buffer(), or by setting the global GIdDefaultTextEncoding variable in the IdGlobal unit.

Quote

Hence to be on the safe side, i would have to translate it to idBytes.

Well, you have to do that anyway, since you are including binary data in your request, not just text.

Quote

What is the general rule on how you would send data when the server only accepts ANSI characters.

Explicitly tell TIdUDPClient (or any other Indy component) to transmit strings in the desired ANSI encoding.  Don't just rely on defaults.

15 hours ago, ChrisChuah said:

Would like to ask why the UDPClient did not receive any data.

It is because your server did not send the response to the correct port on the client.  The client sent out the request from port 59114, so that was the port it could receive the response on, but the server sent the response to port 55510 instead.  So, the server ignored the source port of the request and sent the response to its own port instead.  If the server requires the client to use the same port that the server is using, you will have to set the TIdUDPClient.BoundPort to that port before sending requests (which you seem to have already figured out).  Otherwise, fix the server to send the response to the request's actual source port.

Quote

When i use the IdUDPClient inside the thread object, the idUDPClient does not get any messages back even though it can be seen from the Wireshark that the message was sent back.

However, if i place the IdUDPClient on a Form, i am able to get the message.

Indy does not behave differently in a worker thread vs in the main UI thread.  It is not dependent on message handling.

Quote

Something funny is that when i use the IdUDPClient on the Form to send out the message, 

the first response message header is SOREQ <== this header is request header

...

seems like the first message is the same message that i sent out.

Your client is sending the request to a subnet broadcast IP (192.168.84.255) instead of to the server's actual IP (192.168.84.128) (why?).  And since you are not setting the TIdUDPClient.BoundIP to bind the client to a specific network adapter, that explains why your client is receiving a duplicate copy of the broadcasted request.  So, you will have to either:

- send to the server's IP directly.

- make your client ignore messages that have its own local IP as the source IP.

- set the TIdUDPClient.BoundIP to the client's own local IP, then it won't receive broadcasts from itself.

Edited by Remy Lebeau

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×