![]() |
IsObject / IsClass
Oft wird Delphi-Neulingen geraten, bei der Arbeit mit Objekten die Referenzen nach der Freigabe eines Objekts (zB mit FreeAndNil) wieder auf nil zu setzen, damit ein Vergleich der Form
Delphi-Quellcode:
eingesetzt werden kann, um zu überprüfen, ob es sich um eine gültige Referenz handelt.
if Assigned(AnObject) then
Der Compiler von Delphi initialisiert Objektreferenzen innerhalb von Methoden und Prozeduren/Funktionen nicht vor, so dass ein Test der Art
Delphi-Quellcode:
zwar überprüft, ob die Referenz (hier: myObject) einen Wert hält, der von nil verschieden ist, kann aber im Fall einer "durch Zufall" von nil verschiedenen "Vorbelegung" nicht entscheiden, ob der Wert nun tatsächlich auf ein Objekt verweist.
procedure MyProc;
var myObject: TObject; begin if Assigned(myObject) then Innerhalb des DEC und der JCL konnte ich nun zwei verschiedene Implementierungen einer Funktion IsObject (bei der JCL auch eine IsClass für Klassenreferenzen) finden, die zwei unterschiedliche Ansätze verfolgen. Die Lösung von Hagen prüft, sofern ich sie nicht missdeute, ob vor dem Speicher, in dem das vermeintliche Objekt liegt, einen MagicCode enthält (genauer ein Bit), der vom Borland-Speichermanager dort hinterlegt worden sein sollte. Die Lösung innerhalb der JCL geht davon aus, dass ein Objekt zu einer Klasse gehört und überprüft innerhalb der VMT der Klasse, ob die dort erwartete Referenz auf die Klasse selbst vorhanden ist. Während Hagens Ansatz schneller arbeiten sollte kann bei zufälligen Werten (durch eine zufällig vorbelegte lokale Referenz) durch seine Maskierung der Form
Delphi-Quellcode:
nur zu 50% gesagt werden, ob es sich um ein Objekt handelt. Aus informationstheoretischer Sicht ist dies keine Information, also nutzlos für diesen Fall, obgleich die Routine für andere Szenarien korrekt arbeiten sollte. Darüber hinaus könnte dieser Ansatz nicht mehr funktionieren, wenn man die Speichervergabe von Objekten über eine eigene Implementierung (NodeManager) lösen würde, die ohne den MagicCode auskommt.
if MagicCode and $2 = $2 then
Der Ansatz der JCL prüft zwar ein wesentlich unwahrscheinlicheres Kriterium, hat aber gegenüber der Lösung aus dem DEC den Nachteil, das er mit diversen Indirektionen arbeitet, also im Speicher liest, obgleich es bei der Interpretation von nicht-initialisierten Referenzen sein kann, dass keine Leserechte zu den entsprechenden Speicherbereichen existieren. Hagens Lösung hingegen "schluckt" solche Fehler (durch einen try-except-Block) und gibt für diesen Fall den erwarteten Wert False zurück. In der Vergangenheit habe ich eine zur JCL ähnliche Lösung entwickelt, die mithilfe der funktion IsBadReadPtr überprüft, ob die Bereiche gelesen werden können und außerdem beim Sonderfall nil abkürzt, so dass sie als Alternative für Assigned eingesetzt werden könnte. Leider ist diese Implementierung etwas langsamer als der Ansatz der JCL und bei weitem inperformanter als die Lösung aus dem DEC. Darüber hinaus vermag auch dieser Ansatz (genauso wie die beiden anderen) nicht das volgende Szenarium korrekt zu erfassen
Delphi-Quellcode:
weil der Speicher hinter dem durch den Destructor freigegeben Objekt nicht gelöscht wird und somit weiterhin auf eine gültige Klasse verweist. Auch der MagicCode des Borland-Speichermanagers zeigt weiterhin das geforderte Kriterium, so dass ich bisher keine geeignete Lösung des Problems zur identifizierung einer gültigen Objektreferenz ausmachen konnte.
var
myObject: TObject; begin myObject:= TObject.Create; myObject.Free; if IsObject(myObject) then Hat jemand eine Lösung? |
Re: IsObject / IsClass
Das wird nicht leicht, in deinem unteren Beispiel ändert sich tatsächlich für den Pointer ja nichts, er zeigt immer noch auf den gleichen Speicherbereich. Der Haken an der Sache, dieser Speicher ist für die Anwendung nicht mehr freigegeben.
Genau da würde ich nach einer Lösung suchen, aber einfach ist es sicher nicht. Da muss man wohl mit Speicherverwaltung des OS rumschlagen :witch: |
Re: IsObject / IsClass
Ich weiß nicht welchen Code du von IsObject() von mir analysiert hast. Meine isObject() Funktion fragt nicht ab ob der Speicherzeiger ein Magic enthält. Dies darf er auch garnicht, da es ja nicht gesagt ist das der Borland MM benutzt wird. IsObject() überprüft so wie die Borland Funktion "is" ob es sich um ein gültiges Object abgeleitet von TObject handelt. Dabei lädt sie die VMT der Objectinstance und überprüft ab da rekursiv die Klassentypen bis hinab zu TObject. Somit ist IsObject fast identisch wie IsClass(). Allerdings, habe ich eine andere Assembler Routinie als Ersatz für "is" benutzt, die eben SICHER gegen nil Pointer in den VMT's ist. Dies ist beim Borland Original "is" Operator NICHT der Fall. Zusätzlich zu dem habe ich noch einen try except Block eingebaut um eventuelle AV's abzufangen.
Aber grundsätzlich gibts für deine Problem keine saubere Lösung, das stimmt. Man kann noch eine Verbesserung durchführen indem man TObject.FreeInstance() überschreibt oder eben hookt/patcht. Die neue .FreeInstance Klassenmethode gibt so wie die originale den Speicher frei, überschreibt aber vorher mit FillChar(Self^, Self.InstanceSize) den Speicher des Objectes mit Nullen. Da Self^^ ein Zeiger auf die Klasse des Objectes ist, heist dies das nun der IS Operator auf einen NIL Zeiger zur VMT des Objectes zugreift. In diesem Moment wird also nochmal ca. 50% die Wahrscheinlichkeit erhöht falsche Objecte zu finden. Allerdings, dies hilft dir nur im Beispiel deines obigen Codes. Nach dem Object.Free ist nämlich Object^ = Object.PointerToClass == NIL. Würdest du aber sofort nach dem .Free wiederum ein Object erzeugen, mit gleicher Größe wie das vorherige, so benutzt der Speichermanager exakt den Speicher des vorher freigegeben Objectes erneut. Somit befindet sich an der Speicheradresse von Object nun wieder ein gültiges Object, es IST aber NICHT das gleiche Object !! also:
Delphi-Quellcode:
Gruß Hagen
var
A,B: TMyObject; begin A := TMyObject.Create(1); A.Free; if not IsObject(A) then "Alles Ok !" B := TMyObject.Create(2); if A = B then "sehr wahrscheinlicher Fall das A = B ist da MM Speicher wiederverwendet" if IsObject(A) then "Integer(A) = Integer(B), aber NICHT das gleiche Object" end; |
Re: IsObject / IsClass
Hallo Hagen,
Zitat:
Delphi-Quellcode:
bevor der Vergleich auf die Klasse stattfindet.
if (PInteger(PChar(AObject) - SizeOf(Integer))^ and $00000002 = $00000002)
Zitat:
Delphi-Quellcode:
entdecken.
if TObject(AObject) is AClass
Die von Dir beschriebene Variante mit der zusätzlichen Prüfung auf nil erfüllt mit der Möglichkeit auf die Prüfung einer konkreten Schnittstelle (Typen) noch eine zusätzliche Funktion, jedoch arbeitet sie bei zufällig (von nil verschiedenen) Werten auf zusammenhangslosen Daten fehlerhaft, so dass Du willkürliche Informationen gegen eine übergebene Klassenreferenz testest darüber hinaus nicht ausschließen kannst, in Endlosschleifen "hängenzubleiben", sofern ich das richtig verstanden habe. Zitat:
Das Problem sah ich bei dieser Lösung, genau wie Du, bei der erneuten Vergabe (zB durch weiteren Thread), die auch nicht unmittelbar später erfolgen muss, sondern bei Strukturen homogener Klassen auch nach mehrfacher Freigabe und Neuvergabe von Speicher auftreten kann. Es ist die Frage, ob dies nicht vielleicht sogar gewollt ist, wenn die Routine IsObject lediglich prüfen soll, ob hinter einer Referenz tatsächlich ein Objekt steht. Es liegt in der Natur der Referenzen, dass sie nicht surjektiv sind, so dass der von Dir beschriebene Fall nicht von einer Zuweisung der Form
Delphi-Quellcode:
unterschieden werden kann (und sollte?).
myRef:= anotherRef;
Es bleibt folglich das Problem der fälschlichen Verarbeitung von zuälligen Daten (auch mit der von mir vorgeschlagenen Prüfung auf die Selbstreferenz einer Klasse nach einer Prüfung auf die Lesbarkeit des Speichers ist dies nur zu einer endlichen Wahrscheinlichkeit möglich) und der zusätzliche Zeitaufwand bei der Freigabe von Objekten (abgesehen davon, dass nicht gerantiert ist, dass die Implementierung von TObject.FreeInstance aufgerufen wird (NodeManager)). Weiß jemand weiter? |
Re: IsObject / IsClass
Ich glaube euer Ansatz ist teilweise falsch, man könnte einen Speicherbereich prüfen ob es ein Objekt repräsentiert, das Ergebnis ist jedoch vollkommen uninteressant, wenn dieser Speicherbereich nicht für die Anwendung freigegeben ist. Bin nun wirklich kein Experte bzgl. Thread-Programmierung, denke aber, dass alle Threads einer Anwendung den gesamten Speicherbereich der Anwendung nutzen können. Falls dem nicht so ist, wird es noch ein wenig komplizierter.
|
Re: IsObject / IsClass
@Choose:
hier ist mein Code aus dem DEC -> Unit DECutil.pas
Delphi-Quellcode:
function IsClass(AObject: Pointer; AClass: TClass): Boolean; assembler; register;
asm // safer replacement for Borland's "is" operator @@1: TEST EAX,EAX JE @@3 MOV EAX,[EAX] TEST EAX,EAX JE @@3 CMP EAX,EDX JE @@2 MOV EAX,[EAX].vmtParent JMP @@1 @@2: MOV EAX,1 @@3: end; function IsObject(AObject: Pointer; AClass: TClass): Boolean; // Relacement of "is" Operator for safer access/check iff AObject is AClass begin Result := False; if AObject <> nil then try Result := IsClass(AObject, AClass); except end; end; Zitat:
Ein Object A wird an Addresse $1234567 erzeugt und wieder freigeben. Nun wird Object B mit gleichem Speicherverbrauch erzeugt. Der Speichermanager alloziert den Speicher auf grundf seiner internen Logik wiederum an Addresse $12345678. Somit würde ein Zugriff über eine Variable die ehemals ein Object A enthielte nun auf Object B zugreifen. Object A und B müssen aber nicht vom geleichen Typ sein oder könnten ganz andere Daten enthalten. Mit Multithreading o.ä. hat das erstmal überhaupt nichts zu tun, es geht einfach um die Aussage das es KEINE sauberen Weg gibt eine Variable die ein Object enthält das freigeben wurde zu überprüfen ob das Object noch gültig ist. Nur zwei Lösung gibt es für das Problem: 1.) sicherstellen das das freizugebene Object alle auf sich bezogenen Variablen auf NIL setzt. TComponent.FreeNotification() benutzt so eine Technik indem jedes TComponent eine TList von verlinkten Komponenten verwaltet. Speichert eine solche verlinkte Komponente eine Referenz auf Component A so kann es mit .FreeNotification() sich selbst registrieren. Beim Freigeben der Komponente ruft diese die Methode .Notification() für jeder der mit .FreeNotofiocation() registrierten Komponenten auf. Diese Technik könnte man auch für einfache Variablen benutzen. Allerdings hat diese Technik einen Hacken. Man muß nämlich jeweils BEIDE Komponenten gegenseitig registrieren. D.h. A -> B und B -> A, damit es eben nicht passieren kann das bei freigeben von A ein ungültiges B benachrichtigt wird. 2.) Einfach sicherstellen das solange irgendeine gültige Referenz existiert auch das Object nicht freigegeben wird. So arbeiten Interfaces. Ich halte diesen Weg für den saubersten da er vom Denkmodell Top-Down orientiert ist. Im dezeitigen Zustand des MM's und des Compiliers kann es keine Lösung geben. Man müsste schon den kompletten Compiler und MM umschreiben. In einem solchen Szenario wäre es vorstellbar das der Compiler über Magic einen virtuellen Pointer in eine Pointer Tabelle mappt. Diese Tabelle wäre Bestandteil des Speichermanagers und mappt die virtuellen Pointer zur korrekten Speicheradresse. D.h. jedesmal wenn der Compiler ein Object derefernzieren will muß der Compiler diese Magic benutzen. Nun ist es möglich die Zeigertabelle zum mappen so aufzubauen das ein sequentieller Gültigkeitszähler integriert wird. Gruß Hagen |
Re: IsObject / IsClass
Hallo Hagen,
zunächst einmal möchte ich die Problematik mit der Endlosschleife (zugegen, recht unwahrscheinlich) bei Deiner Implementierung auf ungüültigen Daten genauer demonstrieren:
Delphi-Quellcode:
um Dir weiterhin beizupflichten: Es können auch Objekte anderer Klassen an derselben Adresse abgelegt werden. Weil die Identität eines Objekts nach dem Ansatz in Delphi aber genau durch diesen Ort definiert ist und Referenzen weiterhin nicht eineindeitig (sondern nur injektiv) einem Objekt zugeordnet sind, ist es fraglich, ob die Funktion IsObject nicht vielleicht per definition korrekt arbeitet, wenn sie für diesen Fall erneut True zurückgibt.
procedure TakeIsClassForARide;
var myCircularData : record vmtParent : Pointer; // -36 vmtSafeCallException : Integer; // -32 vmtAfterConstruction : Integer; // -28 vmtBeforeDestruction : Integer; // -24 vmtDispatch : Integer; // -20 vmtDefaultHandler : Integer; // -16 vmtNewInstance : Integer; // -12 vmtFreeInstance : Integer; // -8 vmtDestroy : Integer; // -4 ClassOffsetZero : Pointer; end; begin myCircularData.vmtParent:= @myCircularData.ClassOffsetZero; myCircularData.ClassOffsetZero:= @myCircularData.ClassOffsetZero; IsClass(@myCircularData, TObject); end; Die Idee mit den Threads wollte ich nur anführen, um zu zeigen dass auch bei Konstrukten der Art
Delphi-Quellcode:
var
myRef: TObject; begin myRef:= TObject.Create; myRef.Free; // thread may create another object o right now IsObject(myObject) // <- result is undefined (myObject may be object o) Zitat:
Zitat:
Zitat:
Anbei der Code, den ich verwende
Delphi-Quellcode:
Es bleibt nach meiner Ansicht die Frage nach einer eine möglichst sicheren Prüfung auf zufälligen Daten.
function IsClass(const AClass: TClass): Boolean; assembler;
asm // EAX = AClass OR EAX, EAX // AClass = nil JE @OUT MOV EBX, EAX // store class reference PUSH 4 // valid pointer -> class self reference readable ADD EAX, vmtSelfPtr PUSH EAX CALL IsBadReadPtr OR EAX, EAX JNE @FALSE MOV EAX, EBX SUB EAX, EAX.vmtSelfPtr // test class against self reference JNE @FALSE INC EAX // Result:= True RET @FALSE: XOR EAX, EAX // Result:= False @OUT: end; function IsObject(const AnObject: TObject): Boolean; assembler; asm // EAX = AnObject OR EAX, EAX // AnObject = nil JE @OUT MOV EBX, EAX // store object reference PUSH 4 // valid Pointer -> class readable PUSH EAX CALL IsBadReadPtr OR EAX, EAX JNE @FALSE MOV EAX, [EBX] // class reference JMP IsClass // Result:= IsClass(EAX) @FALSE: XOR EAX, EAX // result:= False @OUT: end; |
Re: IsObject / IsClass
Hi Choose,
deine Überprüfung auf zirkuläre Referenzen ist ebenfalls nicht 100%'tig sicher. Angenommen die Klasse ist die 10'te Klasse in einer Hirarchie. Dann würde deine Absicherung im Source nur mit 1/(10*10)'tel besser sein als garkeine Überprüfung auf zirkuläre Referenzen. Denn es kann an jeder Stelle der VTM.Parent Hierarchie die zirkulare Referenz zu jeder der anderen Klassen in der Hirarchie auftreten. Somit müsste deine IsClass() Überprüfung auf zirkuläre Referenzen viel weiter ausgebaut werden. Es gibt noch drei zusätzliche Möglichkeiten IsClass() mehr abzusichern: 1.) in der RTTI/VMT ist selber ein Feld das als vmtSelfPtr bezeichnet wird. Eine gültige Klasse muß in diesem Feld auf sich selber zeigen. 2.) eine gültige Klasse sollte immer im Codesegment gespeichert werden. Bis auf einem Ausnahme, meine eigene Testimplementierung, kenne ich keinen Delphi Source der dynamisch Klassen zur Laufzeit konstruiert. D.h. man kann mit 99.99% Wahrscheinlichkeit sagen das der Zeiger auf die Klasse im Codesegement liegen muß. 3.) man kann über alle Typ Information der Anwendung und seiner geladenen Module iterieren. Die Klassen Strukturen sind nur ein Teil dieser Typinformation die der Compiler hardcoded erzeugt. Man kann nun diese Iteration über die Typinfo's als Referenzquelle für gültige Zeiger von allen im Projekt verwendeten Klassen benutzen. Damit ließe sich auch das Problem eventueller zirkulärer Referenzen beseitigen. Man iteriert über die Typinformation bis man die Klasse zum Object gefunden hat. Nun geht man iterativ die Klassenhierarchie top-down. Da fällt mir ein, zirkuläre Referenzen IN der Klassenhierarchie können nur auftreten wenn man das Codesegment modifiziert, also patcht. Sie sind also enorm unwahrscheinlich und würden NICHT eine Object-Instance ansich betreffen sondern die komplette Klasse. D.h. ALLE Objectinstancen der gleichen Klasse würden den selben Fehler in ihrer Klassenhierarchie enthalten. Ziemlich unwahscheinlich also da der Compilier die Klassenhierarchien hardcoded im Codesegment eincompiliert. Auf Grund der Delphi Sprachdefinitionen ist es aber nicht möglich zirkuläre Klassenhierarchien zu konstruieren. Es müsste also schon im Codesegment hineingepatcht wurden sein. Gruß Hagen |
Re: IsObject / IsClass
Hallo Hagen,
Zitat:
Auch mein Vorschlag könnte zufällige Daten missinterpretieren (also True zurückgeben obgleich hinter einem Datenbereich kein Objekt existiert), aber auch im worst-case nicht zu einer Endlosschleife führen. Hast Du zu solchen Daten eine bessere Idee? Darüber hinaus sprach ich zirkuläre Referenzen im Zusammenhang mit Interfaces an, konkret: die von Delphi implementierten Referenzzählung. Auch hier ging es weder um die Vererbungshierarchie (auf die bei Interfaces wohl auch eher selten geprüft wird, obgleich dies möglich ist) noch auf eventuelle Zyklen in ihr sondern um das Problem von "verhakten Clustern" von Objekten, die gegenseitig ihre Referenzen halten und in anderen Umegbungen nur mit einem Garbage Collector aufgelöst werden konnten. Zitat:
Zitat:
Zitat:
Die anderen Fälle sollten bei den bisherigen Betrachtungen als ausgeschlossen gelten. Für diesen Ansatz müsste jedoch sichergestellt werden, dass die TypeInfo/TypeData jeder Klasse zugänglich ist. Bisher habe ich Registraturen (zB für ein dynamisches Factory-Pattern) ähnlich der Funktion RegisterClass händisch implementiert. Kannst Du einmal zeigen, wie man über alle RTTI aller Datentypen iterieren kann? |
Re: IsObject / IsClass
Hi Choose,
ich hatte hier im Forum schon mal einen solchen Code gepostet. Dieser Source konnte über alle deklarierten TypInfos der geladenenen Module iterieren. D.h. alle TypInfos die auch mit der Funktion TypInfo(XYZ) abgefragt werden können konnten mit der Funktion EnumTypeInfo() durchiteriert werden. Nun, ich habe danch im Forum gesucht und konnte es nicht mehr finden, vielleicht findest du es ja. Gruß Hagen |
Re: IsObject / IsClass
Hi Choose,
deine Assembler Funktionen sind buggy, leider :) 1.) Du benutzt EBX ohne es vorher zu sichern, eg. PUSH/POP 2.) IsBadReadPtr() ist zwar eine Funktion die überprüfen soll ob ein Zeiger gültig ist, sie funktioniert nur leider nicht so wie erwartet. D.h. IsBadReadPtr(KernelSpeicher) würde FALSE ergeben, ein Zugriff auf KernelSpeicher^ aber denoch eine Zugriffsverletzung auslösen
Delphi-Quellcode:
Gruß Hagen
function IsObject(AObject: Pointer): Boolean;
asm OR EAX,EAX // AObject == nil ?? JNZ @@1 RET @@1: XOR EDX,EDX // install Exception Frame, SEH PUSH OFFSET @@3 PUSH DWord Ptr FS:[EDX] MOV FS:[EDX],ESP MOV EAX,[EAX] // EAX := AObject^.ClassType OR EAX,EAX // ClassType == nil ?? JZ @@2 CMP EAX,[EAX].vmtSelfPtr // EAX = ClassType.vmtSelfPtr SETZ AL @@2: POP DWord Ptr FS:[EDX] POP EDX RET // Exception Handler, wird aufgerufen wenn zwischen @@1 und @@2 eine AV auftritt, // zum Debugger muß auf @@3 ein Breakpoint gesetzt werden, // Dieser SEH ist NICHT sichtbar für Delphi's Debugger !! @@3: MOV EAX,[ESP + 00Ch] // context MOV DWord Ptr [EAX + 0B0h],0 // context.eax = 0 MOV DWord Ptr [EAX + 0B8h],OFFSET @@2 // context.eip = @@2 SUB EAX,EAX // 0 = ExceptionContinueExecution end; |
Re: IsObject / IsClass
Hier der Code um über die TypInfo's zu iterieren
Delphi-Quellcode:
Gruß Hagen
unit Unit1;
interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls, Buttons, StdCtrls, TypInfo; type TForm1 = class(TForm) ListBox1: TListBox; Button1: TButton; procedure Button1Click(Sender: TObject); private public function DoRTTI(Info: PTypeInfo): Boolean; end; var Form1: TForm1; implementation {$R *.DFM} type TEnumTypeInfoCallback = function(UserData: Pointer; Info: PTypeInfo): Boolean; register; function GetBaseOfCode(Module: hModule; var CodeStart, CodeEnd: PChar): Boolean; asm // get Codesegment pointers, check if module is a valid PE PUSH EDI PUSH ESI AND EAX,not 3 JZ @@2 CMP Word Ptr [EAX],'ZM'; JNE @@1 MOV ESI,[EAX + 03Ch] CMP Word Ptr [ESI + EAX],'EP' JNE @@1 MOV EDI,[EAX + ESI + 014h + 008h] ADD EAX,[EAX + ESI + 014h + 018h] ADD EDI,EAX MOV [EDX],EAX MOV [ECX],EDI XOR EAX,EAX @@1: SETE AL @@2: POP ESI POP EDI end; function EnumTypeInfo(Module: hModule; Callback: TEnumTypeInfoCallback; UserData: Pointer): PTypeInfo; // copyright (c) 1998 Hagen Reddmann var P,E,K,N: PChar; L: Integer; begin Result := nil; if Assigned(Callback) then try if GetBaseOfCode(Module, P, E) then while P < E do begin DWord(P) := DWord(P) and not 3; K := P + 4; if (PDWord(P)^ = DWord(K)) and (PByte(K)^ > 0) and (PByte(K)^ < 18) then // Info.Kind in ValidRange.D6 begin L := PByte(K + 1)^; // length Info.Name N := K + 2; // @Info.Name[1] if (L > 0) and (N^ in ['_', 'a'..'z', 'A'..'Z']) then // valid ident ?? begin repeat Inc(N); Dec(L); until (L = 0) or not (N^ in ['_', 'a'..'z', 'A'..'Z', '0'..'9']); if L = 0 then // length and ident valid if Callback(UserData, Pointer(K)) then // tell it and if needed abort iteration begin Result := Pointer(K); Exit; end else K := N; end; end; P := K; end; except end; end; function TForm1.DoRTTI(Info: PTypeInfo): Boolean; var P: PTypeData; begin Result := False; // if Info.Kind = tkClass then begin // P := GetTypeData(Info); // if P.ClassType.InheritsFrom(TCustomForm) then ListBox1.Items.Add(Info.Name{ + ', ' + P.UnitName}); end; end; procedure TForm1.Button1Click(Sender: TObject); begin ListBox1.Items.Clear; EnumTypeInfo(MainInstance, @TForm1.DoRTTI, Self); end; end. |
Re: IsObject / IsClass
Hallo Hagen,
Zitat:
Zitat:
Zitat:
Ebenso werde ich mir morgen den Code zum Iterieren der RTTI ansehen können, sieht schon einmal vielversprechend aus (kannst Du eine Aussage über die Kompatibilität Deiner Lösung zu den verschiedene Delphi-Versionen treffen?)! |
Re: IsObject / IsClass
Zitat:
Aus der Delphi Hilfe: Zitat:
|
Re: IsObject / IsClass
Hallo jpg,
Du hast Recht. Leider habe ich die Routinen bisher in zu isolierten Umgebungen getestet, als dass ein durch die Nachlässigkeit bedingter Fehler aufgetreten sein könnte... Ich werde die Implementierung wohl ohnehin zugunsten einer Prüfung gegen alle registrierten Klassenreferenzen, die mit Hagens Routine ermittelt werden können, aufgeben. Bei dieser Implementierung werde ich dann auf die GP-Register achten, versprochen ;) |
Re: IsObject / IsClass
EBX, Kylix und seine GOT (Global Object Table, übrigens) ist schon eines der schlimmsten Probleme, aber normalerweise wird ein Überschreiben von EBX ohne Sicherung schon in Windows Programmen für massiven Ärger sorgen. Mit Windows API hat das wenig zu tun, es liegt am Compiler der davon ausgeht das Unterproceduren EBX nicht verändern. Also nutzt er diese Festlegung auch intensiv.
Zitat:
Delphi-Quellcode:
Der Wert 18 könnte durch
if (PDWord(P)^ = DWord(K)) and (PByte(K)^ > 0) and (PByte(K)^ < 18) then // Info.Kind in ValidRange.D6
Delphi-Quellcode:
ersetzt werden. Dann wäre es automatisch durch neucompilieren für alle Delphi Versionen gültig.
... <= Integer(High(TTypeKind)) then
Es gibt Tricks wie man manuell und absichtlich per Assembler Datenstrukturen im Code ablegen kann die dann obigen EnumTypeInfo() Funktion ins stolpern bringen. Dazu muß man aber auch wirklich absichtlich exakt solche Strukturen anlegen. Bisher habe ich kein einzigstes Projekt gehabt bei dem dies der Fall war. Natürlich kann man in der Callback oder Enum Funktion zusäzliche Überprüfungen einbauen, die dann abhängig von der gefundenen TypInfo deren Struktur auf logische Plausibilitäten abchecken. Wichtigstes Hilfsmittel für dich ist die Unit TypInfo.pas :) Gruß Hagen |
Re: IsObject / IsClass
Hey,
habe einmal versucht, die Idee von Hagen umzusetzen, und nun eine angepasste Version von IsObject erstellt:
Delphi-Quellcode:
@Hagen: Wenn Du damit einverstanden bist, würde ich IsObject sowie ein Extrakt für IsClass nach
function IsObject(AObject: Pointer): Boolean; assembler;
asm OR EAX,EAX // AObject == nil ?? JNZ @@Try RET @@Try: XOR EDX,EDX // install Exception Frame, SEH PUSH OFFSET @@Except PUSH DWord Ptr FS:[EDX] MOV FS:[EDX],ESP // actual tests *************** @@Step1_ClassTypeIsNil: // test whether classtype is nil MOV EAX,[EAX] // EAX := AObject^.ClassType OR EAX,EAX JZ @@False @@Step2_SelfReference: // object's self reference should point to object again CMP EAX,[EAX].vmtSelfPtr // EAX = ClassType.vmtSelfPtr JNE @@False @@Step3_TypeInfosKindIsClass: MOV ECX,EAX // ECX := ClassType // object's typ info has to be a valid class MOV EAX,[EAX].vmtTypeInfo // EAX := TypeInfo(AnObject.ClassInfo) CMP [EAX].TTypeInfo.Kind, tkClass // AnObject.ClassInfo)^.Kind = tkClass JNE @@False @@Step4_ValidTypeInfo: // valid type info has self reference at -0x04 CMP EAX,[EAX-4] // (TypInfo-4)^ = TypInfo JNE @@False @@Step5_TypeDataPointsBackToClass: // type data of class' type info points to class again PUSH EDX // copied from GetTypeData (EAX==PTypeInfo) -> (EAX==PTypeData) XOR EDX,EDX MOV DL,[EAX].TTypeInfo.Name.Byte[0] LEA EAX,[EAX].TTypeInfo.Name[EDX+1] POP EDX CMP ECX,[EAX].TTypeData.ClassType // TypeData(AnObject)^.ClassType = AnObject.ClassType JNE @@False // **************************** @@True: MOV AL, 1 JMP @@ReturnWithoutException @@FALSE: SUB EAX, EAX @@ReturnWithoutException: POP DWord Ptr FS:[EDX] // uninstall Exception Frame POP EDX RET @@Except: MOV EAX,[ESP + 00Ch] // context MOV DWord Ptr [EAX + 0B0h],0 // context.eax = 0 MOV DWord Ptr [EAX + 0B8h],OFFSET @@ReturnWithoutException // context.eip = @@2 SUB EAX,EAX // 0 = ExceptionContinueExecution end; ![]() |
Re: IsObject / IsClass
Boah. Manchmal glaub ich echt, Ihr habt kein RL mehr. :shock:
Nee, jetzt aber mal im Ernst: Respekt! Da steckt ne ungeheure Menge Gehirnschmalz drin, da wär ich froh wenn ich auch irgendwann mal so weit komme. Aber ihr habt mir da glaub ich auch ein paar Jährchen voraus :) Um nochmal von der Praxis etwas wegzukommen nochmal zur Theorie: 1.) Wenn ein Objekt TypOfA zerstört wird und unmittelbar danach ein Objekt TypeOfB mit der gleichen Größe angelegt wird besteht wie Hagen sagte eine nicht unerhebliche Wahrscheinlichkeit, dass eine alte Referenz auf das erste Objekt danach eine gültige Referenz auf das zweite Objekt ist. Eine Abfrage ob das Objekt jedoch vom Typ TypOfA ist, würde fehlschlagen. ( if ref is TypeOfA ) Somit kann ich schonmal abfangen das mir ein falsches Objekt untergejubelt wird. 2.) Wird das Objekt einfach nur zerstört kann ich mit dem entsprechenden Code auch abprüfen, ob das Objekt hinter der Referenz noch gültig ist oder nicht. Dies stellt auch kein Problem dar, im schlimmsten fall eben über Try-except und eien Zugriff auf das Objekt. 3.) Wird ein Objekt vom TypOfA erzeugt, zerstört und neu angelegt liegt ein anderes Objekt vom gleichen Typ an der gleichen Speicherstelle. Das wollt ihr so wie ich das mitbekommen habe am liebsten abfragen. Hier stellt sich die Frage, warum? Es reicht doch, wenn die Datenfelder des Objektes verändert werden. Allein schon durch eine Änderung einer Variablen kann ein Objekt 'falsche' oder unerwartete Werte annehmen. Da muss ich nicht das Objekt erst zerstören, neu anlegen und wieder befüllen um Schindluder damit zu betreiben. Auf der ganz anderen Seite noch folgende Fragestellung: Ich als Entwickler sollte wissen wann und wo ich ein Objekt zerstöre und wann und wo ich es benutze. Ich müsste mich doch gar nicht mit solchen Problemen herumschlagen, ausser es geht um die Bugsuche. Wer sollte mir denn ein falsches Objekt unterjubeln wollen? Es ist doch mein Code. |
Re: IsObject / IsClass
Zitat:
2.) korrekt, aber exakt das ist aus Sicht einer wiederholten Freigabe und Neuallokation von Speicher eher weniger der Fall. 3.) Korrekt. Aber wenn man in Variable A ein Objekt allozierte und es freigibt und in B danach ebenfalls ein neues Objekt der gleichen Klasse so würde man mit A.Free; eben das neue Objekt zerstören. Exakt dies führt zu KEINEM sofort sichtbaren Fehler sondern zu serh unangenehmen Seiten-Effekt-Fehlern. Solche Fehler sind es die den Proghrammierer dann Wochenlang auf Fehlersuche festhängen lassen. Also sehr unangenehm. Zitat:
IsObject() ist also keine Lösung für ein Problem, sondern nur ein probates Mittel bei zb. der Entwicklung in Teams um eventuelle Programmierfehler frühzeitiger erkennen zu können. Gruß Hagen |
Re: IsObject / IsClass
...darüber hinaus könnte IsObject auch zu analytischen Zwecken eingesetzt werden. So könnte ein bekannter Speicherbereich nach Objekten "durchsucht" werden, um Heuristiken über den Gebrauch von Klassen zu erstellen oder spezielle, sonst nicht weiter zugängliche, Exemplare gesucht werden. Auch Anfragen nach
![]() |
Re: IsObject / IsClass
Ah. Das ist also weniger eine Sicherheitsfrage (im Sinne von Erkennung manipulierter Daten) als eine Frage von sauberer Programmierung und Fehlererkennung. Dann iss mir alles klar :)
|
Re: IsObject / IsClass
Naja, primär hatte ich IsObject() entwickelt weil es sicherer als der Operator is ist. Wird is auf ein falsches Objekt angewendet dann ist garantiert das es AVs hagelt. IsObject() macht nun eigentlich das was ich vom is Operator erwartet hätte, es erzeugt keine AVs sondern kehrt in diesem Falle mit FALSE zurück.
Man muß sich mal die Verwendung vom is Operator vergegenwärtigen:
Delphi-Quellcode:
aus meiner Sicht darf in einem solchen Konstrukt KEINE Exception ausgelösst werden. IsObject() ist also als "safer Replacement" vom is Operator gedacht.if Variable is TMyClass then Ein Programmierer der also sowas wie oben abfragen möchte interessiert sich primär nur für EINE Sache, nämlich "ist in Variable ein Objekt der Klasse TMyClass ?" und nicht "ist in Variable ein Objekt von TMyClass ? oder wenn es garkein gültiges Objekt ist dann erzeuge mit Pi*Daumen Wahrscheinlichkeit eine Exception". Das eine ist eine EINDEUTIGE Frage im Source, das andere ist eine mehrdeutige ANTWORT mit dem Seiteneffekt einer dritten ungewollten Programmverzweigung per Exception. IsObject() korregiert nun dieses unsaubere Verhalten. Klar, man kann sich darüber streiten was nun die Ursachen sind, diese sind mir aber im wahrsten Sinne Wurst, mich interessiert nur die Zielsetzung. Ergo: muss ich immer davon ausgehen das ich aufbauend auf schlechteren Source meine Ziele erreichen muß, ist einfach mal aus praktischen Erwägungen heraus so notwendig. Denn wie sähe die korrekte Alternative denn aus ?
Delphi-Quellcode:
also ziemlich unübersichtlich und aufwändig, denn daswäre bei jeder is Abfrage notwendig. Im Grunde macht IsObject() nur sowas wie oben, halt mit einigen kleineren zusätzlichen Verbeserungen.
var
Korrekt: Boolean; begin try Korrekt := Variable is TMyClass; except Korrekt := False; end; if Korrekt then ; end; Gruß Hagen |
Alle Zeitangaben in WEZ +1. Es ist jetzt 18:59 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 by Thomas Breitkreuz