TString mit Byte Array befüllen

Rund um die LCL und andere Komponenten
Antworten
Mathias
Beiträge: 6194
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

TString mit Byte Array befüllen

Beitrag von Mathias »

Da ich Daten von einem COM-Port einlese, kommen die Daten als Byte Array zurück, bei der die Länge bekannt ist.
Diese Array will ich hinten an einen TString hängen. In meinem Fall ist das TMemo.Lines .
Momentan mache ich dies über einen ZwischenString und Move.
Gibt es dafür etwas eleganteres ?

Code: Alles auswählen

procedure TSerial_Monitor_Form.Timer1Timer(Sender: TObject);
var
  buf: array[0..4095] of byte;
  bufCount,   StringCount: integer;
  s: string;
begin
  Timer1.Enabled := False;
  bufCount := SerReadTimeout(SerialHandle, buf, Length(buf), 10);
  if bufCount > 0 then begin
    SetLength(s, bufCount);
    Move(buf, s[1], bufCount);
 
    StringCount := Memo1.Lines.Count - 1;
    Memo1.Lines[StringCount] := Memo1.Lines[StringCount] + s;
  end;
  Timer1.Enabled := True;
end;


Das Ganze bezieht sich auf folgenden Thread: https://www.lazarusforum.de/viewtopic.php?f=18&t=12873
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Benutzeravatar
Winni
Beiträge: 1577
Registriert: Mo 2. Mär 2009, 16:45
OS, Lazarus, FPC: Laz2.2.2, fpc 3.2.2
CPU-Target: 64Bit
Wohnort: Fast Dänemark

Re: TString mit Byte Array befüllen

Beitrag von Winni »

Hi!

Ist doch alles wunderbar.
Move ist die schnellste Lösung, um die bytes in einen String zu überführen.
So werden auch eventuelle bytes von einem UTF8-Char richtig transportiert.

Das einzige, was fies sein könnte, ist, wenn Zeichen < #32 im byte-buffer vorhanden sind:
TAB, Zeilenschaltung, Backspace etc.
Das weiss ich nicht.
Falls ja müsste man den Buffer vorher filtern.

Winni

wp_xyz
Beiträge: 4889
Registriert: Fr 8. Apr 2011, 09:01

Re: TString mit Byte Array befüllen

Beitrag von wp_xyz »

Wenn du sicherstellen kannst, dass keine Null sich im Buffer befindet, aber als letztes übertragenes Zeichen gekommen ist (zur Not selbst in den Buffer schreiben), dann kannst du mit PChar und StrPas() arbeiten, was das ganze etwas lesbarer macht:

Code: Alles auswählen

procedure TForm1.Button1Click(Sender: TObject);
var
  buf: array[0..4095] of byte;
  bufCount: integer;
  i: Integer;
  s: String;
begin
  bufCount := Random(30);
  for i:=0 to bufCount-1 do
    buf[i] := ord('A') + i;
  buf[bufCount] := 0;
 
  s := StrPas(PChar(@buf[0]));
  Memo1.Lines.Add(s);
end
Zuletzt geändert von wp_xyz am Sa 25. Apr 2020, 00:41, insgesamt 1-mal geändert.

Mathias
Beiträge: 6194
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: TString mit Byte Array befüllen

Beitrag von Mathias »

#13#10 werden jetzt schon richtig intepretiert.
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Mathias
Beiträge: 6194
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: TString mit Byte Array befüllen

Beitrag von Mathias »

Code: Alles auswählen

Memo1.Lines.Add(s);

Genau dies darf ich nicht machen, ich will nicht nach jedem String automatisch eine neues Zeile.
Die Zeilenumbrüche werden von der Seriellen Schnittstelle eingelesen. Und dies wird TString richtig intepretiert.

Sowas kommt richtig in der Memo an.

Code: Alles auswählen

Memo1.Text := 'Zeile 1' + LineEnding + 'Zeile 2'


Move ist die schnellste Lösung, um die bytes in einen String zu überführen.

Am besten wäre doch, wen man die Char-Array erst gar nicht kopieren müsste. Sondern nur Zeiger kopieren.
In etwa so:

Code: Alles auswählen

    Memo1.Lines[StringCount] := Memo1.Lines[StringCount] + buf;

Oder machen StrPas() und PChar() dies ?
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

wp_xyz
Beiträge: 4889
Registriert: Fr 8. Apr 2011, 09:01

Re: TString mit Byte Array befüllen

Beitrag von wp_xyz »

Spätestens beim Anhängen an den letzten String des Memo wird kopiert (plus noch viel mehr). Und StrPas macht meiner Meinung nach auch eine Kopie, genauso wie Move. Möglicherweise kannst du den Buffer nicht als Array[0..4095] of Byte deklarieren, sondern als RawByteString mit SetLength(4096) und das SerReadTimeout rufst du mit SerReadTimeOut(..., buf[1], Length(buf),...) auf. Wenn du dann den buf-String per SetLength auf die von SerReadTimeOut zurückgegebene Größe zurechtstutzst, dann wird nichts kopiert und die empfangenen Daten stehen schon in einem String.

Aber ehrlich, warum diese Mikrooptimierung? In einem halben Jahr wirst du dich fragen, warum du nur so umständlich programmiert hast.

Mathias
Beiträge: 6194
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: TString mit Byte Array befüllen

Beitrag von Mathias »

Und StrPas macht meiner Meinung nach auch eine Kopie, genauso wie Move.

Was hindert mich an dieser direkten Lösung ?

Code: Alles auswählen

procedure TSerial_Monitor_Form.Timer1Timer(Sender: TObject);
var
  buf: array[0..4095] of byte;
  bufCount, SLCount: integer;
begin
  Timer1.Enabled := False;
  try
    bufCount := SerReadTimeout(SerialHandle, buf, Length(buf)-1, 10);
    if (bufCount > 0) or (bufCount < 4095) then begin
      buf[bufCount] := 0;
 
      SLCount := Memo1.Lines.Count - 1;
      Memo1.Lines[SLCount] := Memo1.Lines[SLCount] + PChar(@buf[0]); // Direkt
 
      Memo1.SelStart := -2;
    end;
  finally
    Timer1.Enabled := True;
  end;
end;


Was ist der Unterschied zwischen eine direkten Zuweisung und StrPas ?
s0 und s1 sehen im folgenden Testcode gleich aus.

Code: Alles auswählen

procedure TForm1.Button1Click(Sender: TObject);
const
  pc = 'abc'#0;
var
  s0, s1: string;
begin
  s0 := StrPas(PChar(pc));
  s1 := PChar(pc);
  Caption := s0 + '-' + Length(s0).ToString + '---' + s1 + '-' + Length(s1).ToString;
end
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Antworten