Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi RTTI Attribute verändern (https://www.delphipraxis.net/180262-rtti-attribute-veraendern.html)

Neutral General 5. Mai 2014 16:41

Delphi-Version: XE4

RTTI Attribute verändern
 
Hallo,

Ist es möglich zur Laufzeit die Werte von RTTI-Attributen einer Klasse/Property/Methode zu ändern?
Auslesen ist ja kein Problem. Aber kann man da auch zur Laufzeit was ändern?

himitsu 5. Mai 2014 17:25

AW: RTTI Attribute verändern
 
Eigentlich nein, denn die Typinfos sind ja fest einkompilert.

Aber man könnte den Schreibschutz von dem Speicherbreich entfernen und dann darin rumschreiben.
Windows verwaltet diesen Speicher ja als CopyOnWrite (standardmäßig aus der Datei gemappt, aber bei Veränderung wird im Hintergrund die Speicherverwaltung umgestellt)

Es ist aber nur möglich bestehende Attribute umzuschreiben, zu ersetzen oder zu entfernen.
Hinzufügen ist nicht so leicht, da es sich bei diesem AttrData um ein statisches Array handelt.
Delphi-Quellcode:
GetTypeData(TypeInfo(TDeinTyp)).AttrData // alte RTTI
TRttiType(...).TypeData.AttrData // Neue



Oder du baust dir eine Attribute-Klasse, welche sich die Daten nicht nur über die Parameter besorgt.
In den Parametern stehet dann z.B. nur die Position, wo das Attribut die Daten herholen soll und an der Stelle kannst du diese vermutlich veliebig verändern. (ich weiß jetzt allerdings nicht wann die Attribute-Instanzen genau erstellt werden und wie lange deren Lebensdauer ist)

Neutral General 6. Mai 2014 09:17

AW: RTTI Attribute verändern
 
Hallo,

Danke schon mal für die Antwort. Hinzufügen muss ich nicht, ich will nur ein vorhandenes Attribut ändern oder ersetzen (wenn Ändern nicht geht).

Ich will das Attribut einer Property ändern:

Delphi-Quellcode:
type
  TTestAttribut = class(TCustomAttribute)
  private
    FValue: Boolean;
  public
    constructor Create(AValue: Boolean);
    property Value: Boolean read FValue write FValue;
  end;

  TTest = class
    FString: String;
  public
    [TTestAttribut(false)] property Test: String read FString write FString;
  end;
 
// ....

var rtti: TRttiContext;
    typ: TRttiType;
    Prop: TRttiProperty;
    attrData: PAttrData;
    oldProt: Cardinal;
begin
  rtti := TRttiContext.Create;
  typ := rtti.GetType(ClassType);
  Prop := typ.GetProperty(APropName);

  attrData := @prop.PropertyType.Handle^.TypeData^.AttrData;
  VirtualProtect(attrData,4096,PAGE_READWRITE,oldProt);
  inc(attrData,SizeOf(Word)); // TAttrData.Len überspringen
  PAttrEntry(attrData)^...   // Dabei kommt nix sinnvolles raus.
end;
Das Problem ist dass in attrData, bzw. PAttrEntry(attrData)^ nichts sinnvolles drin steht.
Ich weiß aber auch nicht wo ich den Fehler mache :/

Stevie 6. Mai 2014 09:24

AW: RTTI Attribute verändern
 
Du kannst auch die Property beim Attribut schreibbar machen und es nach dem erstem Auslesen ändern.
Dazu musst du aber dafür sorgen, dass dein TRttiContext so lange lebt, wie du diesen geänderten Wert behalten willst. Somit bleiben auch die ganzen RTTI Objekte inkl der Attribut Instanzen am Leben.

himitsu 6. Mai 2014 10:39

AW: RTTI Attribute verändern
 
Jede RTTI-Instanz besitzt ihre eigenen Listen.

Sobald das erste Mal in einem Context das Attribut ausgelesen wird, wird im jeweiligem GetAttributes eine eigene Instanz des Attributes erstellt.
Ja, man kann der einen Instanz einen neuen Wert zuweisen, aber Dieser gilt dann auch nur dort und andere/fremde Instanzen haben dennoch den Urspungswert.
Also eigentlich sind die Setter sinnlos. (entweder direkt die RTTI umschreiben, aber bestehende Instanzen bleiben unberührt, oder den Wert dynamisch holen)

Nach dem "nochmal" sieht man das Erzeugen der zweiten TTest1Attribute-Instanz.

Delphi-Quellcode:
type
  TTest1Attribute = class(TCustomAttribute)
  private
    FValue: Boolean;
    function GetValue:       Boolean;
    procedure SetValue(AValue: Boolean);
  public
    constructor Create(AValue: Boolean);
    destructor Destroy; override;
    property Value: Boolean read GetValue write SetValue;
  end;

  TTest2Attribute = class(TCustomAttribute)
  private
    FName: string;
    function GetValue: Boolean;
  public
    constructor Create(AName: string; AValue: Boolean=False);
    property Value: Boolean read GetValue;
  private
    class var FValues: array[0..10] of Boolean; //TDictionary<string,Boolean>;
  public
    class procedure SetValue(AName: string; AValue: Boolean); // der Name wird erstmal nur einfach auf den Index umgecastet (Tipp: das TDictionary und ein Class-Constructor)
  end;

  TTest = class
  private
    FString: string;
  public
    [TTest1(False)] property Test1: string read FString write FString;
    [TTest2('2')]  property Test2: string read FString write FString;
  end;

constructor TTest1Attribute.Create(AValue: Boolean);
begin
  inherited Create;
  FValue := AValue;
  ShowMessage(ClassName + ': Create');
end;

destructor TTest1Attribute.Destroy;
begin
  ShowMessage(ClassName + ': Destroy');
  inherited;
end;

function TTest1Attribute.GetValue: Boolean;
begin
  ShowMessage(ClassName + ': GetValue');
  Result := FValue;
end;

procedure TTest1Attribute.SetValue(AValue: Boolean);
begin
  ShowMessage(ClassName + ': SetValue');
  FValue := AValue;
end;

constructor TTest2Attribute.Create(AName: string; AValue: Boolean);
begin
  inherited Create;
  FName := AName;
  SetValue(AName, AValue);
end;

function TTest2Attribute.GetValue: Boolean;
begin
  Result := FValues[StrToInt(FName)];
end;

class procedure TTest2Attribute.SetValue(AName: string; AValue: Boolean);
begin
  FValues[StrToInt(AName)] := AValue;
end;
Delphi-Quellcode:
var
  rtti:   TRttiContext;
  typ:    TRttiType;
  prop1:  TRttiInstanceProperty;
  prop2:  TRttiInstanceProperty;
  attr:   ^TAttrData;
  protOld: Cardinal;
  attrObj: TCustomAttribute;
begin
  ShowMessage(ClassName + ': Begin');
  rtti := TRttiContext.Create;
  typ  := rtti.GetType({ClassType}TTest);
  prop1 := typ.GetProperty({APropName}'Test1') as TRttiInstanceProperty;
  prop2 := typ.GetProperty({APropName}'Test2') as TRttiInstanceProperty;

  //for attrObj in prop1.GetAttributes do
  //  if attrObj is TTest1Attribute then
  //    ShowMessage(attrObj.ClassName + ': ' + BoolToStr(TTest1Attribute(attrObj).Value, True)) // billig gecastet ... den Value-Property hätte man ja auch via RTTI suchen und auslesen können
  //  else if attrObj is TTest2Attribute then
  //    ShowMessage(attrObj.ClassName + ': ' + BoolToStr(TTest2Attribute(attrObj).Value, True))
  //  else
  //    ShowMessage(attrObj.ClassName);
  attrObj := prop1.GetAttributes[0]; ShowMessage(attrObj.ClassName + ': ' + BoolToStr((attrObj as TTest1Attribute).Value, True));
  attrObj := prop2.GetAttributes[0]; ShowMessage(attrObj.ClassName + ': ' + BoolToStr((attrObj as TTest2Attribute).Value, True));

  attr := @GetTypeData(prop1.Handle).AttrData; // @prop.TypeData^.AttrData; // TypeData ist Privat
  VirtualProtect(attr, 4096, PAGE_READWRITE, protOld);
  Inc(attr, SizeOf(Word)); // TAttrData.Len überspringen
  if PAttrEntry(attr).AttrCtor = nil then ; // k.A.
  VirtualProtect(attr, 4096, protOld, protOld);

  (prop1.GetAttributes[0] as TTest1Attribute).Value := True;

  TTest2Attribute.SetValue('2', True);

  attrObj := prop1.GetAttributes[0]; ShowMessage(attrObj.ClassName + ': ' + BoolToStr((attrObj as TTest1Attribute).Value, True));
  attrObj := prop2.GetAttributes[0]; ShowMessage(attrObj.ClassName + ': ' + BoolToStr((attrObj as TTest2Attribute).Value, True));

  // nochmal
  rtti := TRttiContext.Create;
  typ  := rtti.GetType({ClassType}TTest);
  prop1 := typ.GetProperty({APropName}'Test1') as TRttiInstanceProperty;
  prop2 := typ.GetProperty({APropName}'Test2') as TRttiInstanceProperty;

  attrObj := prop1.GetAttributes[0]; ShowMessage(attrObj.ClassName + ': ' + BoolToStr((attrObj as TTest1Attribute).Value, True));
  attrObj := prop2.GetAttributes[0]; ShowMessage(attrObj.ClassName + ': ' + BoolToStr((attrObj as TTest2Attribute).Value, True));

  ShowMessage(ClassName + ': End');

Stevie 6. Mai 2014 11:30

AW: RTTI Attribute verändern
 
Zitat:

Zitat von himitsu (Beitrag 1258106)
Sobald das erste Mal in einem Context das Attribut ausgelesen wird, wird im jeweiligem GetAttributes eine eigene Instanz des Attributes erstellt.
Ja, man kann der einen Instanz einen neuen Wert zuweisen, aber Dieser gilt dann auch nur dort und andere/fremde Instanzen haben dennoch den Urspungswert.
Also eigentlich sind die Setter sinnlos. (entweder direkt die RTTI umschreiben, aber bestehende Instanzen bleiben unberührt, oder den Wert dynamisch holen)

Nach dem "nochmal" sieht man das Erzeugen der zweiten TTest1Attribute-Instanz.

Und genau deshalb schrieb ich:
Zitat:

Zitat von Stevie (Beitrag 1258086)
Dazu musst du aber dafür sorgen, dass dein TRttiContext so lange lebt, wie du diesen geänderten Wert behalten willst. Somit bleiben auch die ganzen RTTI Objekte inkl der Attribut Instanzen am Leben.


himitsu 6. Mai 2014 11:35

AW: RTTI Attribute verändern
 
Zitat:

Zitat von Stevie (Beitrag 1258113)
Und genau deshalb schrieb ich:

Was nur geht, wenn die lesende Stelle den selben Context verwendet, wie die Schreibende.

Stevie 6. Mai 2014 12:34

AW: RTTI Attribute verändern
 
Zitat:

Zitat von himitsu (Beitrag 1258115)
Zitat:

Zitat von Stevie (Beitrag 1258113)
Und genau deshalb schrieb ich:

Was nur geht, wenn die lesende Stelle den selben Context verwendet, wie die Schreibende.

Falsch, es gibt intern nur eine Instanz der TRttiPool Klasse, welche durch den TRttiContext record gewrappt wird.

himitsu 6. Mai 2014 12:49

AW: RTTI Attribute verändern
 
Sicher?

Siehe mein TestCode. (getestet in XE)
Da wird zwei mal "gleichzeitig" eine Instanz des TTest1Attribute erzeugt, nach dem "nochmal", obwohl es davon nur ein Attribut.

Es ändert sich auch nichts, selbst wenn ich nach dem "nochmal" eigene Variablen verwende und die ersten Instanzen nicht überschreibe.
TTest1Attribute ist bei der letzen Abfrage auch wieder False.

Stevie 6. Mai 2014 12:59

AW: RTTI Attribute verändern
 
Zitat:

Zitat von himitsu (Beitrag 1258131)
Sicher?

Siehe mein TestCode. (getestet in XE)
Da wird zwei mal "gleichzeitig" eine Instanz des TTest1Attribute erzeugt, nach dem "nochmal", obwohl es davon nur ein Attribut.

Ja, 100% sicher, ich kenn nämlich den Rtti.pas Source inzwischen fast im Schlaf.

Garantiert wird bei dem Source, den du gepostet hast nicht mehrfach eine Attribut Instanz erzeugt. Außer du hast lokal bei dir noch vor dem "nochmal" einen
Delphi-Quellcode:
rtti.Free
Aufruf.
Wenn du mir nicht glaubst, dann debug doch durch das
Delphi-Quellcode:
TRttiContext.Create
.

Oder du liest den Artikel von Barry dazu.

himitsu 6. Mai 2014 13:50

AW: RTTI Attribute verändern
 
Ja, ich war mir auch sicher, daß die RTTI (an vielen Stellen) ein Singleton verwenden und die Daten daher nur einmal vorkommen.
Darum hatte ich auch schon Probleme, wenn man die neue RTTI in Threads verwendet.


Aber, wie du am code siehst, hab ich das Create/Destroy des Attributes quasi geloggt und genau da erkennt man, daß es zwei Mal erstellt wird und nach dem "End" auch zwei Mal freigegeben.
Außerdem ist beim letzen Auslesen der zugewiesene Wert weg (ist ja auch die zeite instanz).

Stevie 6. Mai 2014 15:49

AW: RTTI Attribute verändern
 
Zitat:

Zitat von himitsu (Beitrag 1258144)
Aber, wie du am code siehst, hab ich das Create/Destroy des Attributes quasi geloggt und genau da erkennt man, daß es zwei Mal erstellt wird und nach dem "End" auch zwei Mal freigegeben.
Außerdem ist beim letzen Auslesen der zugewiesene Wert weg (ist ja auch die zeite instanz).

Bei mir ebend nich (XE). Da wird das Create nur einmal aufgerufen.

himitsu 6. Mai 2014 16:30

AW: RTTI Attribute verändern
 
Aber XE(1) sollte doch XE(1) sein? :gruebel:

himitsu 6. Mai 2014 17:19

AW: RTTI Attribute verändern
 
Liste der Anhänge anzeigen (Anzahl: 1)
Sehr eigenartig.

Ich hab das jetzt nochmal in einer einzelnen Anwendung getestet und hier im XE3 gibt es nur eine Instanz. :gruebel:

Stevie 6. Mai 2014 22:52

AW: RTTI Attribute verändern
 
Zitat:

Zitat von himitsu (Beitrag 1258190)
Sehr eigenartig.

Ich hab das jetzt nochmal in einer einzelnen Anwendung getestet und hier im XE3 gibt es nur eine Instanz. :gruebel:

Und genau diese Anwendung soll unter deinem XE eine andere Ausgabe liefern? Bei mir nicht.
Dann kann das nur an irgendeiner Compileroption liegen, durch die bei der erneuten Zuweisung des Contexts zuerst die Variable gecleared (und somit die TRttiPool Instanz freigegeben) und dann erst das Create aufgerufen wird.

Wenn du das herausfinden willst debug halt mal mit debug dcus in Zeile 151.

himitsu 6. Mai 2014 22:59

AW: RTTI Attribute verändern
 
Zitat:

Zitat von Stevie (Beitrag 1258212)
durch die bei der erneuten Zuweisung des Contexts zuerst die Variable gecleared (und somit die TRttiPool Instanz freigegeben) und dann erst das Create aufgerufen wird.

Im Prinip wäre es ja so eigentlich doch auch richtig?
Das Context-Interface in der Variable sollte ja erst freigegeben werden, bevor das Andere dort rein kommt. :gruebel:

Nja, massig Debuginfos, Eurekalog usw.. Da könnte es sich eventuell doch etwas am Verhalten ändern.

Stevie 6. Mai 2014 23:07

AW: RTTI Attribute verändern
 
Zitat:

Zitat von himitsu (Beitrag 1258213)
Zitat:

Zitat von Stevie (Beitrag 1258212)
durch die bei der erneuten Zuweisung des Contexts zuerst die Variable gecleared (und somit die TRttiPool Instanz freigegeben) und dann erst das Create aufgerufen wird.

Im Prinip wäre es ja so eigentlich doch auch richtig?
Das Context-Interface in der Variable sollte ja erst freigegeben werden, bevor das Andere dort rein kommt. :gruebel:

Nja, massig Debuginfos, Eurekalog usw.. Da könnte es sich eventuell doch etwas am Verhalten ändern.

Es wird halt erst Create aufgerufen und dann CopyRecord:

Code:
Unit16.pas.151: Rtti := TRttiContext.Create;
004EA99C 8D8514FFFFFF    lea eax,[ebp-$000000ec]
004EA9A2 E8A197FDFF      call TRttiContext.Create
004EA9A7 8D9514FFFFFF    lea edx,[ebp-$000000ec]
004EA9AD 8D45F8           lea eax,[ebp-$08]
004EA9B0 8B0D90754B00     mov ecx,[$004b7590]
004EA9B6 E8B5D5F1FF      call @CopyRecord


Alle Zeitangaben in WEZ +1. Es ist jetzt 03:22 Uhr.

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