DanilGholtsman
DanilGholtsman

Reputation: 2374

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?

Upvotes: 0

Views: 1428

Answers (2)

Remy Lebeau
Remy Lebeau

Reputation: 598134

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.

Upvotes: 3

Sir Rufo
Sir Rufo

Reputation: 19106

This is your Main Problem here

msRecInfo.Write( arrOf, SizeOf( arrOf ) );

You write the pointer-address of the array into the stream ... i dont't think thats your goal.

If you want to put the content of the array into the stream you should use

msRecInfo.Write( arrOf[ Low( arrOf ) ], SizeOf( Integer ) * Length( arrOf ) );

Why? You have to point to the first position of the data (first element of the array), and you have to calculate the length of the data.

On the receiving part it is just the same

msRecInfo.Read( arrOf[ Low( arrOf ) ], SizeOf( Integer ) * Length( arrOf ) );

PS: This may work well in this special case, but to be safe, you should send at first, the length of all data, so the receiver knows, when message is complete

Upvotes: 3

Related Questions