Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Typeinfo als Parameter (https://www.delphipraxis.net/177428-typeinfo-als-parameter.html)

hanspeter 6. Nov 2013 09:36

Delphi-Version: XE2

Typeinfo als Parameter
 
Hallo,
ich bin dabei ein altes Projekt von D5 auf XE2 zu portieren.

In diesem Projekt wird sehr viel mit records gearbeitet.
Diese enthalten Shortstrings (z.B. String[40]).
Solche Records werden dann in Memory und/oder Filestreams abgelegt.

Um von den Ansistrings wegzukommen probiere ich mehrere Möglichkeiten.

Was funktioniert ist einmal ein neuer Datentyp z.B. TFixedstring.
Delphi-Quellcode:
type
  TFixString = record
    Value: array[0..41] of Char;
    class operator implicit(const aValue: string): TFixString;
    class operator implicit(const aValue: TFixString): string;
  end;
Das bedeutet aber, dass für jede vorhandene Stringlänge ein eigener Datentyp
declariert werden muss.

String[22] -> Fixstring22
String[40] -> Fixstring40
String[128] -> Fixstring 128 ...

Die 2. Möglichkeit ist die Ergänzung eines Record mit einer Methode write/readstream.
Hier kann Variable und Dateninhalt über Rtti ausgelesen werden.
Beispiel:

Delphi-Quellcode:
function TNodeData.ToString: String;
var
    AContext : TRttiContext;
    AField   : TRttiField;
    ARecord  : TRttiRecordType;
    AFldName : String;
    AValue   : TValue;
begin
    Result := '';
    AContext := TRttiContext.Create;
    try
        ARecord := AContext.GetType(TypeInfo(TNodeData)).AsRecord;
        for AField in ARecord.GetFields do begin
            AFldName := AField.Name;
            AValue := AField.GetValue(@Self);
            Result := Result + AFldName + '|"' +
                      EscapeQuotes(AValue.ToString) + '";';
        end;
    finally
        AContext.Free;
    end;
end;
Beide Varianten bedingen, dass jeder record angepasst werden muss.

Was ich bauen möchte ist eigentlich eine Klasse z.B. TDatastream.
Eine Methode TDatastream.Write(const rec) sollte den Record in einem Stream schreiben
Jetzt suche ich eine Lösung um die Rtti Informationen von rec als typloser Parameter auszulesen.
Hat wer da eine Idee?

Gruß
Peter

himitsu 6. Nov 2013 10:41

AW: Typeinfo als Parameter
 
Zitat:

FixStringXX
So weit war ich auch schon, aber leider sind da auch die Generics zu nix zu gebrauchen.
Und aus irgendwelchen Gründen wehren sich Alle gegen die Einführung von Makros, bzw. daß man Konstanten als "Parameter" in den Generics verwenden kann.
Delphi-Quellcode:
TFixString<Len> = record
  Value: array[0..Len-1] of Char;
  class operator implicit(const aValue: string): TFixString;
  class operator implicit(const aValue: TFixString): string;
end;


Zitat:

Jetzt suche ich eine Lösung um die Rtti Informationen von rec als typloser Parameter auszulesen.
Delphi-Quellcode:
P{Pointer} := TypeInfo(TNodeData);
kannst du problemlos als Parameter an deine Funktion übergen, oder hab ich dein Problem falsch verstanden?

Du könntest auch TReader und TWriter verwenden, so wie es z.B. die VCL mit den binären DFMs macht.
Dort wurd zu jedem "Wert" auch der Typ mit gespeichert, was das Auslesen von "unbekannten" Datenstrukturen ermöglicht.
Wenn man da Feldname+Wert abspeichet, dann kann man beim Auslesen die Daten sogar in einen anderen Record/Datenobjekt übertragen, da man den Feld-/Propertynamen kennt.
- so kann man Speicher sparen, da man "unnötige" Daten weglässt (in der DFM stehen ja auch nur "abweichende" Property drin)
- wenn man das ausgelesene Feld über seinen Namen sucht, passiert nichts, wenn man an der Struktur des Records was verändert (neue Felder irgendwo einfügen oder Alte entfernen)

uligerhardt 6. Nov 2013 12:39

AW: Typeinfo als Parameter
 
Zitat:

Zitat von hanspeter (Beitrag 1234670)
In diesem Projekt wird sehr viel mit records gearbeitet.
Diese enthalten Shortstrings (z.B. String[40]).
Solche Records werden dann in Memory und/oder Filestreams abgelegt.

Um von den Ansistrings wegzukommen probiere ich mehrere Möglichkeiten.

Das Problem haben wir auch. Finde ich schwach vom Emba, dass sie keinen UnicodeString[N] eingebaut haben. Das wäre für den Compilerbauer IMNSHO um einiges leichter zu implementieren gewesen und mit besserem Ergebnis als eine drangefrickelte Lösung von dir oder mir.

Zitat:

Zitat von hanspeter (Beitrag 1234670)
Das bedeutet aber, dass für jede vorhandene Stringlänge ein eigener Datentyp declariert werden muss.

String[22] -> Fixstring22
String[40] -> Fixstring40
String[128] -> Fixstring 128 ...

Macht doch nix - dass lässt sich doch mit Suchen und Ersetzen gut lösen.

Zitat:

Zitat von himitsu (Beitrag 1234676)
Zitat:

FixStringXX
So weit war ich auch schon, aber leider sind da auch die Generics zu nix zu gebrauchen.
Und aus irgendwelchen Gründen wehren sich Alle gegen die Einführung von Makros, bzw. daß man Konstanten als "Parameter" in den Generics verwenden kann.
Delphi-Quellcode:
TFixString<Len> = record
  Value: array[0..Len-1] of Char;
  class operator implicit(const aValue: string): TFixString;
  class operator implicit(const aValue: TFixString): string;
end;

Da vermisse ich mal wieder C++ und Templates.:twisted: Was in Delphi halbwegs als Ersatz geht, ist statt direkt der Größe einen Typ passender Größe zu übergeben:
Delphi-Quellcode:
TFixString<TMitPassenderLaenge: record> = record
  Value: array[0..SizeOf(TMitPassenderLaenge)-1] of Char; // Achtung: SizeOf liefert gelegentlich 0
  // oder
  Value: array[0..1] of TMitPassenderLaenge; // danach fleißig casten. :-(
end;
und
Delphi-Quellcode:
Str40 = string[40];
FixString40 = TFixString<Str40>;
oder
Delphi-Quellcode:
Arr40 = Array[0..39] of AnsiChar;
FixString40 = TFixString<Arr40>;
Nicht elegant, aber besser als nix.

himitsu 6. Nov 2013 13:28

AW: Typeinfo als Parameter
 
Sowas ginge auch, aber diese "brutalen" Castst machen keinen Spaß, vorallem Dank der massiven Bugs beim SizeOf. (dann lieber copy&paste)
Delphi-Quellcode:
TFixString<Arr> = record
  Value: Arr;
  class operator implicit(const aValue: string): TFixString;
  class operator implicit(const aValue: TFixString): string;
end;

//Arr40 = array[0..39] of WideChar;
//FixString40 = TFixString<Arr40>;
  FixString40 = TFixString<array[0..39] of WideChar>;

uligerhardt 6. Nov 2013 13:38

AW: Typeinfo als Parameter
 
Zitat:

Zitat von himitsu (Beitrag 1234700)
Sowas ginge auch, aber diese "brutalen" Castst machen keinen Spaß, vorallem Dank der massiven Bugs beim SizeOf. (dann lieber copy&paste)

Mit Delphi macht DRY manchmal keinen Spaß. :cry:

Furtbichler 6. Nov 2013 14:07

AW: Typeinfo als Parameter
 
Zitat:

Zitat von hanspeter (Beitrag 1234670)
Um von den Ansistrings wegzukommen probiere ich mehrere Möglichkeiten.

Wenn Du die alten Daten verarbeiten musst, spricht doch nichts dagegen, die einzulesenden Strings explizit als AnsiString zu deklarieren. So steht es nun einmal in der Datei drin (wenn es denn eine Datei ist).

Vielleicht legst Du pro Record eine String-Property an, die diese AnsiStrings konvertiert, eventuell auch als lazy load (wenn Performance eine Rolle spielt).

Ich habe das allerdings mal so gelöst, das meine 'Records' nun Klassen sind. Jede Klasse hat eine Load und Store Methode, die die einzelnen Daten/Felder bytegenau ausliest bzw. abspeichert, und zwar genauso, wie das der Record gemacht hat.

Aus
Delphi-Quellcode:
Bockread(myFile, @myRecord, SizeOf(myRecord))
wird dann
Delphi-Quellcode:
MyClass.Load(myFileStream);
. Das ist zwar ein wenig Arbeit, aber nur 1x und straight forward. Für die einzelnen Load/Store-Methoden schreibe ich Unit-Tests, um sicherzugehen, das die Daten genauso abgelegt/gelesen werden, wie der Record.

Ich mach mir da keine großen Gedanken um Generics oder tolle allgemeingültige Lösungen. So ein
Delphi-Quellcode:
TFixString<irgendwas>
ist doch schwer zu lesen und bringt eigentlich auch nicht viel.

hanspeter 6. Nov 2013 18:32

AW: Typeinfo als Parameter
 
Erst mal danke für die Tips.

Ich werde wohl Record gegen Class auswechseln müssen.
Dadurch das records nicht erben können, gibt es keinen Vorfahr end eine typlose Übergabe funktioniert nicht richtig.
Da die Record sequentiell in einem Memorystream gespeichert und ausgelesen werden, habe ich jetzt eine andere Lösung gefunden, die funktioniert.
Ich habe mir eine Datenstreamklasse gebaut. Diese hat gleichnamige Methoden wie die TMemorystreamklasse. Es werden allerdings Klassreferenzern in einer TObjectliste verwaltet.
Das funktioniert erst mal.
Die Umsetzung von Ansi auf Unicode kann man in Delphi ja nicht gerade als gelungen bezeichnen.
Abgesehen von unterschiedlichen Datentypen bei string und string[] , herrscht auch in der stringbibliothek ein ziemliches Wirrwar.
Genauso gut könnte man Integer als 4 byt und Array of integer als jeweils 2 Byte implementieren.
Ich hoffe ja das der Mobile Hype bei Embarcadero bald vorbei ist und es bei Delphi selbst wieder mal ein paar Fortschritte gibt.


Gruß Peter


Alle Zeitangaben in WEZ +1. Es ist jetzt 07:56 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz