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 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 ge
nilt 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 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 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.