String, ANSIString, WideString, ... Was darf es es sein?

Für allgemeine Fragen zur Programmierung, welche nicht! direkt mit Lazarus zu tun haben.
Antworten
Ich934
Lazarusforum e. V.
Beiträge: 316
Registriert: So 5. Mai 2019, 16:52
OS, Lazarus, FPC: ArchLinux und Windows mit FPCUPdeluxe (L: 2.0.X, FPC 3.2.0)
CPU-Target: x86_64, i386
Wohnort: Bayreuth

String, ANSIString, WideString, ... Was darf es es sein?

Beitrag von Ich934 »

Guten Morgen,

Lazarus bzw. Pascal kennt ja jede Menge Stringvarianten (String, ANSISTring, WideString, UnicodeString, ... um nur mal ein paar zu nennen). Soweit ich weiß ist die LCL primär mit String geschrieben.

Meine Frage nun ist, was verwedet man am besten? Was setzt ihr ein? Vor allem mit ausländischen Zeichen bringt Unicode ja Vorteile mit sich. Sobald ich aber dann diese Zeichenkette an eine LCL-Komponente übergebe oder aber eine interne Funktion übergebe erhalte ich entweder eine Warnung oder ich muss konvertieren. Einen Verlust habe ich im schlimmsten Fall bei beiden Versionen.

Also was am besten tun?

cu tb
Tipp für PostgreSQL: www.pg-forum.de

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

Re: String, ANSIString, WideString, ... Was darf es es sein?

Beitrag von wp_xyz »

Ich nehme in 99% aller Fälle den Typ String. Das ist zwar ein 1-Byte-String - Delphi hat 2-byte-Strings, aber FPC ist inzwischen so "clever", dass die String-Konvertierung weitgehend transparent abläuft, selbst wenn derselbe Code für Lazarus und Delphi verwendet wird. "Weitgehend", denn wenn man explizit API-Funktionen aufruft, muss man WideString oder UnicodeString an die API-Funktion weiterreichen; und natürlich auch bei Dateien, wenn sie explizit zwei-Byte-Strings enthalten, oder auch ANSIStings mit speziellen Code-Pages.

Sonderzeichen sind kein Problem, denn Lazarus interpretiert den Typ String standardmäßig als UTF8-codiert, wobei maximal 4 Byte für ein "Zeichen" verwendet werden (oder auch nur eins bei den einfachen ASCII-Zeichen, oder zwei bei den deutschen Umlauten). Das hat zur Folge, dass es Probleme gibt, wenn man auf die einzelnen "Zeichen" des Strings zugreifen will, z.B. wenn die Bytes eines solchen Strings von vorne bis hinten in einer Schleife durchlaufen werden, da hier die Entsprechung "1 Byte = 1 Zeichen" aufgebrochen ist. Nimm stattdessen die Enumerator-Funktionen in Unit LazUnicode:

Code: Alles auswählen

uses
  LazUnicode;
var
  s: String;
  zeichen: String;    // WICHTIG: das muss bei UTF8 ein "String" sein, kein "char"
begin
  s := 'äöüÄÖÜß';
  for zeichen in s do
    if zeichen = 'ä' then ShowMessage(''ä' gefunden');
end;

Für Standard-Stringfunktionen nimmt man die Funktionen aus der Unit LazUTF8, z.B. UTF8Length (was bei dem String 'äöüÄÖÜß' im Beispiel den richtigen Wert 7 zurückliefert - die Standardfunktion Length(), die die Bytes zählt, aber 14), oder UTF8Pos, UTF8Lowercase, UTF8Copy, UTF8Insert etc.

In vielen Fällen, z.B. wenn ein Parser die Tags von HTML oder XML-Dateien durchsucht, reichen aber die Standardfunktionen, weil diese Tags nur aus ASCII-Zeichen (< #128) zusammengesetzt sind.

Man muss halt mitdenken...

Timm Thaler
Beiträge: 1224
Registriert: So 20. Mär 2016, 22:14
OS, Lazarus, FPC: Win7-64bit Laz1.9.0 FPC3.1.1 für Win, RPi, AVR embedded
CPU-Target: Raspberry Pi 3

Re: String, ANSIString, WideString, ... Was darf es es sein?

Beitrag von Timm Thaler »

Immer String.

Für Programme am PC, Raspberry, Windows, Linux:

Immer Compilerschalter {$H+}, das ist dann AnsiString, Länge unbegrenzt. Normalerweise sind das dann UTF8Strings. Man muss aber aufpassen:
- auch alle Units sollten dann Utf8 sein, bei älteren Units kann das anders sein
- auch Config-Dateien, eingelesene XML... sollten Utf8 sein
- für einige Funktionen muss man entprechende Utf-Libs nehmen, zum Beispiel XML => laz2_XML

Alles in Utf8 durchziehen, dann klappts auch mit Portieren von Win nach Linux.

Für Programme am AVR:

Compilerschalter {$H-}, das ist dann ShortString, Länge auf 255 Bytes begrenzt.

Und Achtung: "UnicodeString" ist NICHT Utf8String.

Warf
Beiträge: 1908
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: String, ANSIString, WideString, ... Was darf es es sein?

Beitrag von Warf »

Mal ne kleine erklärung. String ist ein Wrapper und kein eigener typ. Je nach Kompilereinstellungen ist das entweder ein AnsiString, ein UTF-8 String, ein Short String, oder ein ganz anderes encoding (Stichwort Codepage compilerswitch).

Grundsätzlich wenn In der datei der die Compilerswitches {$Mode ObjFPC}{$H+} stehen und sonst nix, benutzt du UTF-8 Strings. Wenn das H+ fehlt oder H- da steht sind es ShortStrings, und im {$Mode Delphi} ists das selbe wie {$H+} (also nicht wie in delphi).
UTF-8 ist der aktuelle standard für Internationalization, und soweit man keinen grund hat es nicht zu benutzen solltest du dabei bleiben.
Hier sind aber mal ein paar Gründe warum du vielleicht andere Strings benutzen möchtest.
AnsiStrings (ohne unicode codepage) sind simpler, wenn du also low level String parsing selbst machst, kann die beschränkung auf AnsiStrings dir viel Zeit und Mühe sparen. Dafür kannst du halt nur das Amerikanische Alphabet benutzen. Außerdem sind AnsiStrings deutlich schneller als UTF-8 in der berarbeitung. So ist die Länge eines AnsiStrings genau die länge an Byte, da jedes Zeichen ein Byte ist. Das N'te byte kannst du über Str[N] erreichen, und du bist vollkommen C kompatibel, d.h. dass Null nicht im String vorkommen kann (außer am Ende). Wenn du also weißt das du nur Ansi Strings benutzt (also keine seltsamen Zeichen drin hast) und dir performance sehr wichtig ist, oder du mit C Interfaces arbeitest, nimm AnsiStrings
ShortStrings sind Strings mit fixer Länge (maximal 255 zeichen, + 1 byte am anfang das die länge angibt). Diese haben den Vorteil das sie keinen dynamischen Speicher brauchen, können unter Umständen damit deutlich schneller sein als AnsiStrings sein. Außerdem, manche systeme wie Microcontroller haben keinen Dynamischen Speicher, ergo keine Ansi (oder UTF-8) Strings. Außerdem kannst du Shortstrings direkt in binäre dateien schreiben, was bei Ansistrings nur über umwege möglich ist. Du kannst also z.B. sowas machen:

Code: Alles auswählen

type TSomeData = record
  Name: String[32]; // ShortString mit max 32 zeichen
  ID: Integer;
  ...
end;
 
...
var fs: TFileStream;
  data: TSomeData;
fs:=TFileStream.Create(...);
fs.Write(data, SizeOf(Data));
fs.free;

Während mit Dynamischen strings würde das nur den Pointer reinschreiben, du musst also jedes feld des Records separat reinschreiben, somit wird für einen Record mit 10 einträgen aus einem 3 zeiler ein 12 zeiler.

Zu guter letzt können referenzgezählte Strukturen (zu denen Dynamische Strings zählen) nicht in variante Records verpackt werden. Wenn du also mit varianten records arbeitest, musst du shortstrings (oder PChar) benutzen.

WideStrings hingegen sind ein Fehler. Das Unicode Konsortium hat (ich glaub in den 90ern war das) noch gedacht das 16 bit chars für Unicode ausreichen sollte (Wahrscheinlich dachten die China würde nie anfangen Computer zu benutzen), und haben damals angefangen UTF-16 zu Standardisieren. Microsoft war eins der ersten Unternehmen die Unicode genutzt haben und haben direkt angefangen UTF-16 mit (ich glaub windows 2000/NT-Kernel) in Windows zu integrieren. Als das Unicode Konsortium gemerkt hat das UTF16 hinten und vorne nicht reicht, wars schon zu Spät, und daher hat jede Windows Kompatible plattform irgendwie UTF16 zu unterstützen (und dann nicht mal die aktuelle version, sondern so ein frühes teil was Microsoft damals schon integriert hat, was mit dem heutigen UTF16 in einigen teilen sogar inkompatibel ist). Also alles in allem ein fall von gute Idee, beschissene Umsetzung von sowohl Microsoft als auch den Unicode leuten.
WideStrings solltest du also nur nehmen wenn du mit Windows interagierst (oder mit Delphi, denn die sind so nah an Windows das die WideStrings zum standard gemacht haben).

Zu guter letzt gibt es noch PChar und PWideChar. Die representieren die Ungemanageten (also nicht referenzgezählten) varianten von String und WideString. Wenn du mit anderen Programmiersprachen, DLL's, etc. arbeitest, wissen diese natürlich nix von deiner Referenzzählung. Daher musst du denen Simple Char Pointer (PChar) übergeben, damit der FPC nicht anfängt bei der übergabe referenzzählung zu betreiben.

Timm Thaler
Beiträge: 1224
Registriert: So 20. Mär 2016, 22:14
OS, Lazarus, FPC: Win7-64bit Laz1.9.0 FPC3.1.1 für Win, RPi, AVR embedded
CPU-Target: Raspberry Pi 3

Re: String, ANSIString, WideString, ... Was darf es es sein?

Beitrag von Timm Thaler »

Warf hat geschrieben:WideStrings solltest du also nur nehmen wenn du mit Windows interagierst


Einspruch! WideStrings solltest Du gar nicht verwenden, auch unter Windows nicht. Ausnahme: Du musst unbedingt Dateien lesen die als WideString gespeichert wurden. Dann solltest Du dennoch alles in Utf8 = AnsiString halten und die Daten beim Einlesen konvertieren. Es gibt entsprechende Funktionen, die das anbieten.

Warf
Beiträge: 1908
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: String, ANSIString, WideString, ... Was darf es es sein?

Beitrag von Warf »

Timm Thaler hat geschrieben:Einspruch! WideStrings solltest Du gar nicht verwenden, auch unter Windows nicht. Ausnahme: Du musst unbedingt Dateien lesen die als WideString gespeichert wurden. Dann solltest Du dennoch alles in Utf8 = AnsiString halten und die Daten beim Einlesen konvertieren. Es gibt entsprechende Funktionen, die das anbieten.


Dann sag mir doch bitte wie du ohne widestrings die Prozessliste in Windows mit Prozessnamen die Unicode zeichen enthalten auslesen kannst. Dafür musst du Processwalking machen mit den Funktionen Process32First, Process32Next, etc. Davon gibt es zwei varianten, Ansi oder Widestring. UTF8 ist keine option.

Wenn du die WinAPI verwendest musst du mit widestrings arbeiten, ob dus willst oder nicht

Timm Thaler
Beiträge: 1224
Registriert: So 20. Mär 2016, 22:14
OS, Lazarus, FPC: Win7-64bit Laz1.9.0 FPC3.1.1 für Win, RPi, AVR embedded
CPU-Target: Raspberry Pi 3

Re: String, ANSIString, WideString, ... Was darf es es sein?

Beitrag von Timm Thaler »

Ja sag ich doch: Beim Einlesen konvertieren. Utf8 kann schließlich alle Zeichen darstellen, die Widestring kann.

Deswegen würde ich dennoch versuchen den Rest des Programms Utf8 zu halten.

Benutzeravatar
kupferstecher
Beiträge: 418
Registriert: Do 17. Nov 2016, 11:52

Re: String, ANSIString, WideString, ... Was darf es es sein?

Beitrag von kupferstecher »

Ich934 hat geschrieben:Sobald ich aber dann diese Zeichenkette an eine LCL-Komponente übergebe oder aber eine interne Funktion übergebe erhalte ich entweder eine Warnung oder ich muss konvertieren.

Das kann ich nicht nachvollziehen, habe weder mit deutschen Umlauten noch mit chinesischen Schriftzeichen Probleme und das im gleichen Programm.
Wie die Vorredner bereits gesagt haben, arbeitet die LCL mit UTF-8. Die FCL allerdings nicht (soviel ich weiß). Da also aufassen.

Warf
Beiträge: 1908
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: String, ANSIString, WideString, ... Was darf es es sein?

Beitrag von Warf »

Timm Thaler hat geschrieben:Ja sag ich doch: Beim Einlesen konvertieren. Utf8 kann schließlich alle Zeichen darstellen, die Widestring kann.

Deswegen würde ich dennoch versuchen den Rest des Programms Utf8 zu halten.

Ja aber das meinte ich doch, wenn du mit windows (aka der winapi) interagierst musst du widestrings benutzen. Es ging nur darum wann man nicht UTF-8 benutzen sollte, und die WinAPI ist eine der fälle in denen man ausnahmsweise widestring benutzen muss

Timm Thaler
Beiträge: 1224
Registriert: So 20. Mär 2016, 22:14
OS, Lazarus, FPC: Win7-64bit Laz1.9.0 FPC3.1.1 für Win, RPi, AVR embedded
CPU-Target: Raspberry Pi 3

Re: String, ANSIString, WideString, ... Was darf es es sein?

Beitrag von Timm Thaler »

An der RS232-Schnittstelle / Uart mittels serial.pp sollte man auch tunlichst Shortstrings benutzen. Vorteil: Es gehen auch Nullbytes ($00) und wenn man Ansistring mit Umlauten verschickt, dann werden die als zwei Zeichen gesendet. Was bei Austausch zwischen gleichen Programmen geht, denn die zwei Zeichen werden wieder richtig zusammengesetzt beim Empfang, aber mit anderen Porgrammen oder einem µC kommt dann Zeichensalat an.

Achso, und auf dem AVR verwende ich natürlich Shortstrings mit Längenbegrenzung, um Speicherplatz zu sparen:

Code: Alles auswählen

Stext_no   : string[4] = 'nein'; section '.progmem';

Ich934
Lazarusforum e. V.
Beiträge: 316
Registriert: So 5. Mai 2019, 16:52
OS, Lazarus, FPC: ArchLinux und Windows mit FPCUPdeluxe (L: 2.0.X, FPC 3.2.0)
CPU-Target: x86_64, i386
Wohnort: Bayreuth

Re: String, ANSIString, WideString, ... Was darf es es sein?

Beitrag von Ich934 »

Guten Morgen,

also wenn ich String verwende (bei den regulären Einstellungen) sollte ich eigentlich keine Probleme bekommen. WideString, etc. brauch ich nur in Sonderfällen. Das macht das Leben natürlich viel einfacher.

Vielen Dank für die ausführlichen Erklärungen!

Schöne Grüße
Tipp für PostgreSQL: www.pg-forum.de

Antworten