Search code examples
delphiserializationtcpmemorystream

IndyTCP delphi array of objects exchanging


I'm sorry I know that I ask too many questions, but tomorrow (actually today cause in my coutry it's 2:00 am right now) I need to show to my teacher what I started to make e.t.c. So as I asked in previous questions I need to send from server to client some data. But it nothing appers in server's memo field after thatt memo1.Lines.Add(IntToStr(arrOf[1]));

I was trying to send it like that on client

procedure TForm1.btnTestClick(Sender: TObject);
var
  msRecInfo: TMemoryStream;
  arrOf: array of integer; i:integer;
begin
  setLength(arrOf, 11);
  for i := 0 to 10 do
    arrOf[i]:=random(100);

  msRecInfo:= TMemoryStream.Create;

  try
    msRecInfo.Write(arrOf, SizeOf(arrOf));
    idTCPClient1.IOHandler.Write(msRecInfo);
  finally
     msRecInfo.Free;
  end;

end;

on server

procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
var
   msRecInfo: TMemoryStream;
  arrOf: array of Integer; i:integer;
begin
  msRecInfo:= TMemoryStream.Create;
  try
    AContext.Connection.IOHandler.ReadStream(msRecInfo, -1, False);
    SetLength(arrOf,11);
    msRecInfo.Position := 0;
    msRecInfo.Read(arrOf, SizeOf(arrof));
  finally
    memo1.Lines.Add(IntToStr(arrOf[1]));
    msRecInfo.Free;
  end;    
end;

Please could you help me to solve this problem and to find some examples of how to send arrays of different types/classes?


Solution

  • As Rufo already explained, you are not writing the array into, and reading the array out of, the TMemoryStream correctly.

    Worse, you are not sending the TMemoryStream over the socket correctly, either. The default parameters of TIdIOHandler.Write(TStream) and TIdIOHandler.ReadStream() are not compatible with each other. By default, Write(TStream) does not send the TStream.Size value. However, the default parameters of ReadStream() (which are the same values that you are passing in explicitally) tell it to read the first few bytes and interpret them as the Size, which would be very wrong in this example.

    Try this instead:

    procedure TForm1.btnTestClick(Sender: TObject);
    var
      msRecInfo: TMemoryStream;
      arrOf: Array of Integer;
      i: Integer;
    begin
      SetLength(arrOf, 11);
      for i := Low(arrOf) to High(arrOf) do
        arrOf[i] := random(100);
    
      msRecInfo := TMemoryStream.Create;
      try
        msRecInfo.WriteBuffer(arrOf[0], Length(arrOf) * SizeOf(Integer));
        IdTCPClient1.IOHandler.Write(msRecInfo, 0, True);
      finally
         msRecInfo.Free;
      end;
    end;
    
    procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
    var
      msRecInfo: TMemoryStream;
      arrOf: Array of Integer;
      i: Integer;
    begin
      msRecInfo := TMemoryStream.Create;
      try
        AContext.Connection.IOHandler.ReadStream(msRecInfo, -1, False);
        SetLength(arrOf, msRecInfo.Size div SizeOf(Integer));
        if Lenth(arrOf) > 0 then
        begin
          msRecInfo.Position := 0;
          msRecInfo.ReadBuffer(arrOf[0], Length(arrOf) * SizeOf(Integer));
        end;
      finally
        msRecInfo.Free;
      end;    
      ...
    end;
    

    Alternatively, get rid of the TMemoryStream and send the individual Integer values by themselves:

    procedure TForm1.btnTestClick(Sender: TObject);
    var
      arrOf: Array of Integer;
      i: Integer;
    begin
      SetLength(arrOf, 11);
      for i := Low(arrOf) to High(arrOf) do
        arrOf[i] := random(100);
    
      IdTCPClient1.IOHandler.Write(Length(arrOf));
      for I := Low(arrOf) to High(arrOf) do
        IdTCPClient1.IOHandler.Write(arrOf[i]);
    end;
    
    procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
    var
      arrOf: Array of Integer;
      i: Integer;
    begin
      i := AContext.Connection.IOHandler.ReadLongInt;
      SetLength(arrOf, i);
      for i := Low(arrOf) to High(arrOf) do
        arrOf[i] := AContext.Connection.IOHandler.ReadLongInt;
      ...
    end;
    

    Now, with that said, accessing the TMemo directly in the OnExecute event handler is not thread-safe. TIdTCPServer is a multi-threaded component. The OnExecute event is triggered in the context of a worker thread, not the main thread. UI components, like TMemo, cannot be safely accessed from outside of the main thread. You can use Indy's TIdNotify or TIdSync class to synchronize with the main thread, eg:

    type
      TMemoSync = class(TIdSync)
      protected
        FLine: String;
        procedure DoSynchronize; override;
      end;
    
    procedure TMemoSync.DoSynchronize;
    begin
      Form1.Memo1.Lines.Add(FLine);
    end;
    
    procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
    var
      ...
    begin
      ...
      with TMemoSync.Create do try
        FLine := IntToStr(arrOf[1]);
        Synchronize;
      finally
        Free;
      end;
      ...
    end;
    

    If you do not synchronize with the main thread, bad things can happen.