Entwickler-Ecke

Delphi Language (Object-Pascal) / CLX - ClassInterf. wird zu Pointer() as Interf. nach Memberaufruf


Symbroson - Mo 18.06.18 18:56
Titel: ClassInterf. wird zu Pointer() as Interf. nach Memberaufruf
Hallo EE und alles Gute nachträglich!, ich bins mal wieder. ;)

Wir sind (oder waren) in der Schule gerade bei 3D-Darstellug. Ich hab natürlich mein eigenes Ding gemacht und basierend auf meiner glm-Bibliothek alles zusammengebastelt.

Jetzt hatten wir die verschiedenen Darstellungsformen wie zB. Iso-, Di- oder Trimetrie. Diese Transformationen dürfen allerdings nicht das Objekt selbst ändern, sondern nur die Darstellung beeinflussen.

Also versuche ich mal im Code grob zu erklären, was ich gemacht habe:



Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
type
    PTriaCon = ^TTriaCon; //Container für Dreiecke
    TTriaCon = record
        verts: array[0..2of IVec4;
        color: integer;
        z: double;
    end;

    TTria = class        //Klasse für Dreiecke
        tria: PTriaCon;
        constructor Create(p1, p2, p3: IVec4; color: integer = 0);
        destructor Free;
        procedure transform(mat: IMat4x4);
    end;

    TRect = class        //Klasse für Rechtecke
        {$ifndef splitRects}
        trias: array[0..1of TTria;
        verts: array[0..3of IVec4;
        {$else}
        trias: array[0..3of TTria;
        verts: array[0..4of IVec4;
        {$endif}
        constructor Create(p1, p2, p3, p4: IVec4; color: integer = 0);
        destructor Free;
        procedure transform(mat: IMat4x4);
    end;

    TCube = class        //Klasse für Quader
        rects: array[0..5of TRect;
        {$ifndef splitRects}
        verts: array[0..7of IVec4;
        {$else}
        verts: array[0..13of IVec4;
        {$endif}
        constructor Create(p1, p2, p3, p4, p5, p6, p7, p8: IVec4);
        destructor Free;
        procedure transform(mat: IMat4x4);
    end;

var
    trias: array of PTriaCon;  //speichert alle Dreiecke
    lverts: TStringList;       //speichert alle Eckpunkte
    tverts: array of IVec4;    //speichert Eckpunkte für Transformationen zwischen
                               //die kein Objekt direkt beeinflussen sollen (Kameratransformationen)

Bei den Klassen, speziell den array of IVec4 nutze ich aus, dass diese nur Referenzen auf das Objekt speichern.
Der Grund, warum ich bei lverts eine TStringList verwendet habe ist, dass ich jeden Eckpunkt nur ein mal vertreten haben möchte und es by default keine Hashtables is Delphi7 gibt. Und ich hatte vorerst noch wenig lust mir selber ein Array zu basteln dass dann per Binärsuche und Insertsort Elemente findet und einfügt. Kommt vmtl noch.

So weit so gut. Jetzt zum Problem. Dieses tritt beim Rückkopieren der zwischengespeicherten Eckpunkte auf:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
procedure DrawTriangles(bmp: TBitmap; matProj: IMat4x4);
var i: integer;
    v0, v1, v2: IVec4; //eigentlich unnötig
begin
    //tverts hat die Länge lverts.count und ennthält initialisierte 'IVec4's
    for i := 0 to HIGH(tverts) do begin
        //speichert Eckpunkte in tverts
        tverts[i].assign(TVec4(lverts.Objects[i]));
        //transformiert Eckpunkte
        TVec4(lverts.Objects[i]).mul(matProj);
    end;
    
    //sortiert Dreiecke nach Z-Schwerpunkt
    SortTriangles(trias);

    //zeichnet alle Dreiecke
    for i := 0 to HIGH(trias) do begin
        
        v0 := trias[i].verts[0];
        v1 := trias[i].verts[1];
        v2 := trias[i].verts[2];
    
        bmp.Canvas.Brush.Color := trias[i].color;
        bmp.Canvas.Polygon([
            Point(calcx(v0.x), calcy(v0.y)),
            Point(calcx(v1.x), calcy(v1.y)),
            Point(calcx(v2.x), calcy(v2.y))
        ]);
    end;

    //kopiert zwischengespeicherte Eckpunkte zurück
    for i := 0 to HIGH(tverts) do begin 
        TVec4(lverts.Objects[i]).assign(tverts[i]);
    end;
end;


Jetzt passiert folgendes beim Rückkopieren: bei i = 8 (was dem 2. Eckpunkt des Würfels entspricht) wird aus dem IVec4 des Würfels ein Pointer. Alle anderen bleiben IVec4, nur der verliert anscheinend seine Gültigkeit.


Hier einige Screens:

Vor dem Rückkopieren:
Restore_1

Vor der fraglichen Zuweisung:
Restore_2

nach der Zuweisung:
Restore_3

Die TVec4.assign-Methode kopiert übrigens nur die Koordinaten ins andere Objekt, mehr nicht. Das kann also kaum die Ursache sein:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
function TVec4.assign(v: TVec4)          : TVec4; begin result := assign(v.x, v.y, v.z, v.w); end;
function TVec4.assign(v: IVec4)          : TVec4; begin result := assign(v.x, v.y, v.z, v.w); end;
function TVec4.assign(x, y, z, w: double): TVec4;
begin
    fx := x;
    fy := y;
    fz := z;
    fw := w;
    result := self;
end;


Kann sich das jemand erklären, warum ausgerechnet dieses Objekt verschwindet? Ich bin echt ratlos. Vielleicht hat es etwas mit der Verwendung von TStringList und der Konvertierung zu TObject zu tun, das erklärt aber nicht den Einzelfall.

Vielen dank im Vorraus :)


jaenicke - Mo 18.06.18 23:05

Benutzt du dort die Referenzzählung bei der Benutzung von IVec4? Dann ist es keine gute Idee parallel mit Objektreferenzen auf TVec4 zu arbeiten. Entweder nur Interfaces (würde ich empfehlen) oder nur Objektreferenzen, dann klappt es auch mit der Referenzzählung.

Konkret sehe ich auf Anhieb auch keine direkte Ursache, aber es muss denke ich mit der Referenzzählung zu tun haben.


Symbroson - Mo 18.06.18 23:14

Also wenn du meinst, dass ich den Referenzenzähler selber beeinflusse dann nein. Ich erstelle lediglich die Objekte und weise die dem Array zu, sodass alle Plätze im Array initialisiert wurden. Später nutze ich wie gesagt nur assign, dass nur die Koordinaten übernimmt.

Ich schau mal wie die Zähler sich verhalten…


Symbroson - Mo 18.06.18 23:36

Erwies sich als schwierig - ich kann das zu TVec4 gecastete Object nicht zu IVec4 casten. :nixweiss:

Ich schicke aber nochmal die Funktion rein, in der die Liste initialisiert wird - vielleicht bringt uns das weiter:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
constructor TTria.Create(p1, p2, p3: IVec4; color: integer = 0);
var len: integer;
begin 
    inherited Create;
    
    //erstellt neuen Dreieck-Container
    new(tria);
    tria.color := color;
    tria.verts[0] := p1;
    tria.verts[1] := p2;
    tria.verts[2] := p3;
    
{lverts wurde initialisiert mit:
    lverts := TStringList.Create;
    lverts.Duplicates := dupIgnore;
    lverts.Sorted := true;
}

    //speichert Eckpunkte ohne Dopplungen zwischen
    lverts.AddObject(Format('%p', [pointer(p1)]), TObject(p1));
    lverts.AddObject(Format('%p', [pointer(p2)]), TObject(p2));
    lverts.AddObject(Format('%p', [pointer(p3)]), TObject(p3));
    
    //initialisiert Zwischenspeicher
    len := LENGTH(tverts);
    setLength(tverts, lverts.Count);
    while len < lverts.count do begin
        tverts[len] := Vec4(0);
        inc(len);
    end;
    
    //speichert Dreieck
    len := length(trias);
    SetLength(trias, len + 1);
    trias[len] := tria;
end;


Symbroson - Mi 20.06.18 14:38

Ich hab jetzt lverts doch mal als array of IVec4 implementiert - und da scheint es keine Probleme zu geben.
Merkwürdig :gruebel:


jaenicke - Mi 20.06.18 21:55

Weil du dort nur mit den Interfaces arbeitest und nicht mit Objektreferenzen. Dadurch bleibt die Referenz in der Liste aktiv.

Referenzzählung ist ja im Grunde erst einmal sehr einfach. Wenn du einer Interfacereferenz eine Instanz zuweist, wird darin der Referenzzähler um 1 inkrementiert. Entfernst du die wieder, wird er wieder dekrementiert.

Nehmen wir mal diesen Quelltext, nur als Beispiel:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
var
  p1: IVec4;
begin
  p1 := TVec4.Create;
  lverts.AddObject(Format('%p', [pointer(p1)]), TObject(p1));
end;

Nach der Zuweisung an p1 ist der Referenzzähler auf 1. Dann packst du die Referenz als Objekt in die Liste. Am Ende der Methode geht p1 aus dem Scope und wird daher wieder abgeräumt. Der Referenzzähler geht auf 0 und somit wird das Objekt wieder freigegeben. Die Referenz in der Liste ist somit ungültig.

Machst du das gleiche mit deinem Array, geht der Referenzzähler stattdessen auf 2 hoch und beim Abräumen der Variable wieder auf 1 herunter. Das Objekt wird also nicht freigegeben.


Symbroson - Mi 20.06.18 22:08

Ok das macht Sinn. Seltsam bleibt nur, dass nur ein einziges Interface betroffen war.

Danke auf jeden Fall für deine Hilfe! :)


jaenicke - Do 21.06.18 09:59

user profile iconSymbroson hat folgendes geschrieben Zum zitierten Posting springen:
Seltsam bleibt nur, dass nur ein einziges Interface betroffen war.
Durch die Freigabe des Objekts wird noch kein Speicher überschrieben, er wird nur wieder als frei markiert. Deshalb merkt man das Problem oft erst später, wenn der Speicher an der Stelle, an der das Objekt lag, anderweitig verwendet wurde und daher dort keine gültigen Daten mehr liegen.

Anders sieht das aus, wenn du FastMM einbindest. Das überschreibt die Daten bei der Freigabe mit einem speziellen Muster, so dass es direkt beim Zugriff immer knallt und dies auch erkannt wird.