Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi IsObject / IsClass (https://www.delphipraxis.net/15729-isobject-isclass.html)

choose 3. Feb 2004 13:29


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:
if Assigned(AnObject) then
eingesetzt werden kann, um zu überprüfen, ob es sich um eine gültige Referenz handelt.
Der Compiler von Delphi initialisiert Objektreferenzen innerhalb von Methoden und Prozeduren/Funktionen nicht vor, so dass ein Test der Art
Delphi-Quellcode:
procedure MyProc;
var
  myObject: TObject;
begin
  if Assigned(myObject) then
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.

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:
if MagicCode and $2 = $2 then
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.
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:
var
  myObject: TObject;
begin
  myObject:= TObject.Create;
  myObject.Free;
  if IsObject(myObject) then
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.

Hat jemand eine Lösung?

Touchdown 3. Feb 2004 13:50

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:

negaH 3. Feb 2004 21:01

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:
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;
Gruß Hagen

choose 4. Feb 2004 08:26

Re: IsObject / IsClass
 
Hallo Hagen,
Zitat:

Zitat von negaH
Ich weiß nicht welchen Code du von IsObject() von mir analysiert hast.

ich berufe mich auf die Unit DEC_api vom 26.08.1999 (zugegeben, etwas alt?), in der folgende Abfrage zufinden ist:
Delphi-Quellcode:
if (PInteger(PChar(AObject) - SizeOf(Integer))^ and $00000002 = $00000002)
bevor der Vergleich auf die Klasse stattfindet.

Zitat:

Zitat von negaH
Dabei lädt sie die VMT der Objectinstance und überprüft ab da rekursiv die Klassentypen bis hinab zu TObject.

Diese Implemenentierung konnte ich weder in der DEC_api noch in der DECUtil vom 26.08.1999 mit
Delphi-Quellcode:
if TObject(AObject) is AClass
entdecken.
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:

Zitat von negaH
Man kann noch eine Verbesserung durchführen indem man TObject.FreeInstance() überschreibt oder eben hookt/patcht.

An diesen Ansatz hatte ich auch schon gedacht und entspricht wohl dem, was Touchdown meinte(?). Zumindest für den Fall der gerade freigegebenen Objekte könnte so in einer SingleThread-Anwendung oder bei einer Implementierung des MM, bei der jeder Thread unterschiedliche Bereiche von Speicher zugewiesen bekommt, gelöst werden.
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:
myRef:= anotherRef;
unterschieden werden kann (und sollte?).


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?

Touchdown 4. Feb 2004 10:24

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.

negaH 4. Feb 2004 14:51

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:

man könnte einen Speicherbereich prüfen ob es ein Objekt repräsentiert
Man kann es zwar prüfen aber das bringt eben nichts.
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

choose 4. Feb 2004 15:25

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:
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;
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.
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 von negaH
1.) sicherstellen das das freizugebene Object alle auf sich bezogenen Variablen auf NIL setzt.

Denkbar, aber nur mit einer Registrierung aller Referenzen. Es bleibt dabei fraglich, wie vorgegeben werden sollte, wenn Referenzen threadübergreifen genutzt werden: Wird ein Objekt innerhalb des einen Ausführungsstrangs freigegeben, müsste die Referenz des anderen Threads genilt werden. Ein Programmcode kann deshalb nicht länger von korrekten Werten innerhalb (lokaler) Variablen ausgehen, da sie "von außen" jeder Zeit verändert werden könnten, bzw es müssten viele Bereiche definiert werden, in denen ein solcher "Entzug" nicht zulässig ist. Dann sind Deadlocks vorprogrammiert.

Zitat:

Zitat von negaH
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.

Nicht immer sind Daten Top-Down organisiert, so dass es bei einem schlichten Ansatz, wie dem der Delphi-Referenzzählung, bei komplexeren zyklischen Strukturen zu Problemen kommt.

Zitat:

Zitat von negaH
Im dezeitigen Zustand des MM's und des Compiliers kann es keine Lösung geben.

Das denke ich leider auch.


Anbei der Code, den ich verwende
Delphi-Quellcode:
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;
Es bleibt nach meiner Ansicht die Frage nach einer eine möglichst sicheren Prüfung auf zufälligen Daten.

negaH 4. Feb 2004 18:43

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

choose 4. Feb 2004 22:09

Re: IsObject / IsClass
 
Hallo Hagen,
Zitat:

Zitat von negaH
deine Überprüfung auf zirkuläre Referenzen ist ebenfalls nicht 100%'tig sicher.

Ich bin mir nicht ganz sicher, welche Lösung Du hier ansprichst. Weder mein Code prüft soetwas ab noch sprach ich zirkuläre Hierarchien in der Vererbung an. Mein Beispiel aus der Funktion TakeIsClassForARide sollte lediglich unglückliche willkürliche Daten Repräsentieren. Den Fall, bei dem die eingangs gewählte zu überprüfende Referenz zufällig auf willkürliche Daten verweist (zB als "Rückstände" auf dem Stack). Einer der schlechtesten solche Fälle wären hierbei Daten sie -rein zufällig- so beschaffen sein könnten, dass Dein Code sie als zyklisch interpretiert und in einer Endlosschleife zu keinem Ergebnis kommt.
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 von negaH
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.

Und eben genau diesen Ansatz verfolgt mein obiger Code.

Zitat:

Zitat von negaH
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.

Ich habe zwar an Lösungen dieser Art gearbeitet, konnte letztlich aber doch jedes Problem mit OOP-Patterns oder Code-Generation lösen. Ich glaube deshalb ebenfalls, dass diese Fall vernachlässigt werden kann.

Zitat:

Zitat von negaH
3.) man kann über alle Typ Information der Anwendung und seiner geladenen Module iterieren.

Dieser Ansatz klingt sehr interessant: Es könnte mit TObject.ClassType geprüft werden, ob eine gültige Klassenreferenz in der RTTI abgelegt worden ist. Siehst Du hier in Kombination mit der Selbstreferenz noch ein Problem? Die Warhscheinlichkeit, dass bei zufälligem Speicher die Selbst-Referenz identisch ist (bei absolut zufälligen Daten) mit der Klasse selbst, liegt bei 1/2^32. Multipliziert mit der Wahrscheinlichkeit n/2^32, dass es sich bei der Wert um eine der n gültigen Klassen handelt, ergibt sich nach diesem Ansatz eine Wahrscheinlichkeit von n/2^64. Geht man von weiterhin weniger als 16000 Klassen aus, liegt die Wahrscheinlichkeit einer Fehlinterpretation zufälliger Daten folglich bei weniger als 1/2^50.
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?

negaH 5. Feb 2004 18:22

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

negaH 5. Feb 2004 18:56

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:
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;
Gruß Hagen

negaH 5. Feb 2004 19:26

Re: IsObject / IsClass
 
Hier der Code um über die TypInfo's zu iterieren

Delphi-Quellcode:
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.
Gruß Hagen

choose 5. Feb 2004 22:07

Re: IsObject / IsClass
 
Hallo Hagen,

Zitat:

Zitat von negaH
Du benutzt EBX ohne es vorher zu sichern, eg. PUSH/POP

Ja, das mache ich, weil es nicht notwendig ist:
Zitat:

You can rely on the fact that all Windows APIs save and restore the registers EBP,EBX,EDI and ESI. Therefore use these registers to keep handles and pointers which need to be used more than once through sequences of API calls.
Zitat:

Zitat von negaH
IsBadReadPtr() ist zwar eine Funktion die überprüfen soll ob ein Zeiger gültig ist, sie [..][würde bei einem] Zugriff auf KernelSpeicher^ aber denoch eine Zugriffsverletzung auslösen

Das war mir nicht bekannt. Danke für Deinen recht kurzen Ansatz des Try..Excepts-Blocks. Ich werde ihn mir morgen etwas genauer ansehen.

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?)!

jbg 5. Feb 2004 23:13

Re: IsObject / IsClass
 
Zitat:

Zitat von choose
Zitat:

Zitat von negaH
Du benutzt EBX ohne es vorher zu sichern, eg. PUSH/POP

Ja, das mache ich, weil es nicht notwendig ist:
Zitat:

You can rely on the fact that all Windows APIs save and restore the registers EBP,EBX,EDI and ESI. Therefore use these registers to keep handles and pointers which need to be used more than once through sequences of API calls.

Und das schließt aber nicht aus, dass der Delphi Compiler in EBX bestimmte Daten hält. Es muss ja auch einen Grund haben, warum der Compiler bei Benutzen von EBX dieses vorher sichert und danach wiederherstellt.
Aus der Delphi Hilfe:
Zitat:

In einer asm-Anweisung muß der Inhalt der Register EDI, ESI, ESP, EBP und EBX erhalten bleiben
Unter Windows mag das nicht gleich zu einem Fehler führen, aber unter Linux steht in EBX die GOT (Global Offset Table) und wenn du diese verlierst, bekommst du sie nicht wieder zurück. Und ohne GOT wird es ein Zuckerschlecken ala SEGV (=AccessViolation), wenn du auf globale Variablen zugreifen willst.

choose 5. Feb 2004 23:21

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 ;)

negaH 6. Feb 2004 01:27

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:

kannst Du eine Aussage über die Kompatibilität Deiner Lösung zu den verschiedene Delphi-Versionen treffen?
Ja kann ich. Vorweg muß ich sagen das mein Code eine Aufgabe erledigt die selbst die Borland Programmierer für unmöglich gehalten haben. Deren Aussagen in diversen Foren und Newsgroups mir gegenüber war immer "das ist nicht möglich". Logische Ableitung davon ist der Fakt das mein Code auf "undokumentierte" Informationen und Features WIE die TypInfos gespeichert werden angewiesen ist. Der obige Code ist von mir für Delphi 2 bis 6 entwickelt und getestet wurden. Er ist aber auch wie man sieht eine "Quick&Dirty" Lösung, d.h. ich habe sie nicht "schön" gemacht. Die einzigste Änderung die man für D7 machen muß ist in

Delphi-Quellcode:
  if (PDWord(P)^ = DWord(K)) and (PByte(K)^ > 0) and (PByte(K)^ < 18) then // Info.Kind in ValidRange.D6
Der Wert 18 könnte durch

Delphi-Quellcode:
 ... <= Integer(High(TTypeKind)) then
ersetzt werden. Dann wäre es automatisch durch neucompilieren für alle Delphi Versionen gültig.

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

choose 21. Nov 2005 09:00

Re: IsObject / IsClass
 
Hey,

habe einmal versucht, die Idee von Hagen umzusetzen, und nun eine angepasste Version von IsObject erstellt:
Delphi-Quellcode:
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;
@Hagen: Wenn Du damit einverstanden bist, würde ich IsObject sowie ein Extrakt für IsClass nach TypInfoEx übernehmen.

Phoenix 21. Nov 2005 11:55

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.

negaH 21. Nov 2005 17:10

Re: IsObject / IsClass
 
Zitat:

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.


1.) die Wahrscheinlichkeit ist bei nicht multithreaded Anweendung exakt 100%. Der Borland MM merkt sich speziell den zu letzt freigegebenen Speicherblock und überprüft bei einer erneuten Allozierung ob das Speicherbereich in den alten zuvor freigegebenen reinpasst. Er verwendet also als erste und schnellste Alternative immer den zuvor freigegebenen Speicher wieder. Im Falle das also der neue Speicher in der Größe <= dem zuvor freigegebenen ist kann man mit 100% Sicherheit sagen das dieser weiderverwendet wird.

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:

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.
Eines hat mit dem Anderen nichts zu tuen. Überschreibt man durch falsche Speicheroperationen Felder eines Objektes dann hat dieser Fehler reingarnichts mit einem Fehler bei der falschen Variablenbenutzung, ergo Freigabe von Objekten zu tuen. IsObject() soll primär eine Möglichkeit bieten eine Variable auf eine Objectinstance zu überprüfen. Analytisch gesehen gibts dafür mit IsObject() keine 100% sichere Methode, ABER! die Verwendung von IsObject() ist wesentlich sicherer als einfach nur Assigned() zu benutzen. Konzeptionell sauberer wäre es wenn man natürlich Assigned() benutzen kann, dazu muß aber eben zu 100% sichergestellt sein das man mit Assigend() auch wirklich sauber bereinigte Variablen überprüft. Und exakt dies ist eben nicht der Fall. IsObject() befreit also den Programmierer von keinerlei Verantwortung einen sauberen Source zu schreiben, es verringert nur die Abstütze falls man einen Source eines schlampigen Coders benutzen muss.

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

choose 21. Nov 2005 20:01

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 allen Exemplaren einer Klasse könnten mit einer Art von IsObject gelöst werden, auch wenn die vorliegende Implementierung zugegeben etwas inperformant sein würde...

Phoenix 22. Nov 2005 07:26

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 :)

negaH 22. Nov 2005 07:59

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:
 
if Variable is TMyClass then
aus meiner Sicht darf in einem solchen Konstrukt KEINE Exception ausgelösst werden. IsObject() ist also als "safer Replacement" vom is Operator gedacht.

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:
var
  Korrekt: Boolean;
begin
  try
    Korrekt := Variable is TMyClass;
  except
    Korrekt := False;
  end;
  if Korrekt then ;
end;
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.

Gruß Hagen


Alle Zeitangaben in WEZ +1. Es ist jetzt 20:06 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