briast
briast

Reputation: 317

Send bytes using Indy 10

I'm trying to send bytes between two delphi 2010 applications using Indy 10, but without success. Received bytes are different from sent bytes. This is my example code:
Application 1, send button click:

var s:TIdBytes;
begin
    setlength(s,3);
    s[0]:=48;
    s[1]:=227;
    s[2]:=0;
    IdTCPClient.IOHandler.WriteDirect(s);  // or .Write(s);
end;

Application 2, idtcpserver execute event (first attempt):

procedure TForm1.IdTCPServerExecute(AContext: TIdContext);
var rec:TIdBytes;
     b:byte;
begin
    SetLength(rec,0);
    b:=AContext.Connection.IOHandler.ReadByte;
    while b<>0 do
    begin 
         setlength(rec, length(rec)+1);
         rec[length(rec)-1]:=b;
         b:=AContext.Connection.IOHandler.ReadByte;
    end;

    // Here I expect rec[0] = 48, rec[1]=227, rec[2]=0.
    // But I get: rec[0] = 48, rec[1]=63, rec[2]=0.   Rec[1] is incorrect
    // ... rest of code
end;

Application 2, idtcpserver execute event (second attempt):

procedure TForm1.IdTCPServerExecute(AContext: TIdContext);
var c:ansistring;
begin
    c:= AContext.Connection.IOHandler.ReadLn(Char(0));

    // Here I expect c[0] = 48, c[1]=227, c[2]=0.
    // But I get: c[0] = 48, c[1]=63, c[2]=0.   c[1] is incorrect
    // ... rest of code
end;

The most strange thing is those applications were developed with Delphi 5 some years ago and they worked good (with readln(char(0)). When I translate both applications to Delphi 2010 they stop working. I supoused It was due unicode string, but I haven't found a solution.

Upvotes: 1

Views: 759

Answers (1)

Remy Lebeau
Remy Lebeau

Reputation: 598124

First off, don't use TIdIOHandler.WriteDirect() directly, use only the TIdIOHandler.Write() overloads instead.

Second, there is no possible way that your 1st attempt code can produce the result you are claiming. TIdIOHandler.Write[Direct](TIdBytes) and TIdIOHandler.ReadByte() operate on raw bytes only, and bytes are transmitted as-is. So you are guaranteed to get exactly what you send, there is simply no possible way the byte values could change as you have suggested.

However, your 2nd attempt code certainly can produce the result you are claiming, because TIdIOHandler.ReadLn() will read in the bytes and convert them to a UnicodeString, which you are then assigning to an AnsiString, which means the received data is going through 2 lossy charset conversions:

  • first, the received bytes are decoded as-is into a UTF-16 UnicodeString using the TIdIOHandler.DefStringEncoding property as the decoding charset, which is set to IndyTextEncoding_ASCII by default (you can change that, for example to IndyTextEncoding_8bit). Byte 227 is outside of the ASCII range, so that byte gets decoded to Unicode character '?' (#63), which is an indication that data loss has occurred.

  • then, the UnicodeString is assigned to AnsiString, which converts the UTF-16 data to ANSI using the RTL's default charset (which is set to the user's OS locale by default, but can be changed using the System.SetMultiByteConversionCodePage() function). In this case, no more loss occurs, since the UnicodeString is holding characters in the US-ASCII range, which convert to ANSI as-is.


That being said, I would not suggest using TIdIOHandler.ReadByte() in a manual loop like you are doing. Although TIdIOHandler does not have a WaitFor() method for bytes, there is nonetheless a more efficient way to approach this, eg:

procedure TForm1.IdTCPServerExecute(AContext: TIdContext);
var
  rec: TIdBytes;
  LPos: Integer;
begin
  SetLength(rec, 0);

  // adapted from code in TIdIOHandler.WaitFor()...
  with AContext.Connection.IOHandler do
  begin
    LPos := 0;
    repeat
      LPos := InputBuffer.IndexOf(Byte(0), LPos);
      if LPos <> -1 then begin
        InputBuffer.ExtractToBytes(rec, LPos+1);
        { or, if you don't want the terminating $00 byte put in the TIdBytes:
        InputBuffer.ExtractToBytes(rec, LPos);
        InputBuffer.Remove(1);
        }
        Break;
      end;
      LPos := InputBuffer.Size;
      ReadFromSource(True, IdTimeoutDefault, True);
    until False;
  end;

  // ... rest of code
end;

Or:

procedure TForm1.IdTCPServerExecute(AContext: TIdContext);
var
  rec: string
begin
  rec := AContext.Connection.IOHandler.ReadLn(#0, IndyTextEncoding_8Bit);
  // ... rest of code
end;

The most strange thing is those applications were developed with Delphi 5 some years ago and they worked good (with readln(char(0)). When I translate both applications to Delphi 2010 they stop working. I supoused It was due unicode string, but I haven't found a solution.

Yes, the string type in Delphi 5 was AnsiString, but it was changed to UnicodeString in Delphi 2009.

But even so, in pre-Unicode versions of Delphi, TIdIOHandler.ReadLn() in Indy 10 will still read in raw bytes and convert them to Unicode using the TIdIOHanlder.DefStringEncoding. It will then convert that Unicode data to AnsiString before exiting, using the TIdIOHandler.DefAnsiEncoding property as the converting charset, which is set to IndyTextEncoding_OSDefault by default.

The morale of the story is - whenever you are reading in bytes and converting them to string characters, the bytes must be decoded using the correct charset, or else you will get data loss. This was true in Delphi 5 (but not as strictly enforced), it is true more so in Delphi 2009+.

Upvotes: 1

Related Questions