Autor Beitrag
Ares
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 128



BeitragVerfasst: Di 26.01.10 13:08 
Hallo!

Bei der Verwendung von Interfaces stoße ich bei einem Programm auf merkwürdige Fehler, die ich ohne das Interface nicht habe. Woran könnte das liegen?

An verschiedenen Stellen im Programm arbeite ich mit Ordnerstrukturen/Dateisystmen. Mal lese ich die Struktur direkt aus dem System, mal kommt diese aus anderen Quellen wie z.B. einer Datenbank oder einer XML-Datei. Um die Methoden für den Zugriff auf die Struktur möglichst allgemein zu halten will ich hier bei ein Interface verwenden, dass die Methoden definiert:

ausblenden volle Höhe 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:
46:
47:
48:
49:
50:
51:
52:
  
type
   // Interface, dass allgemeine Funktionen zum Arbeiten mit Ordnerstrukturen definiert
   IFileStructure = interface(IInterface)
    function FileExists(aFileName: string): boolean;
    function DirectoryExists(aPath: string): boolean;

    function GetFileList(...): TStrings; 
    function GetDirList(...) : TStrings;
  end;

  // Klasse die eine Ordner- und Dateistruktur aus einer XML-Datei ausliest 
  // und darstellt.
  TXMLFileStructure = class(TInterfacedObject, IFileStructure)
  // TXMLFileStructure = class(TObject)
  private
    FRoot : TXMLFolderNode;   // Wurzelknoten der internen Baumstruktur
    ...

    // Einen Pfad in seine Bestandteile zerlegen
    procedure ExplodePath(aPath: string): TStringArray;
  public
    constructor Create(XMLFilename: String);

    function FileExists(aFileName: string): boolean;
    function DirectoryExists(aPath: string): boolean;

    function GetFileList(...): TStrings; 
    function GetDirList(...) : TStrings;
  end;

  // Klasse die IFileStructure Objete verwendet um damit zu arbeiten
  TFileSearcher = class(TObject)
  public
    procedure SearchFile(Filename: String; FileStructure: IFileStructure);
  end;

  ...

  procedure TFileSearcher.SearchFile(Filename: String; FileStructure: IFileStructure);
  var
    dirList: TStrings;
    i: integer;
  begin
    dirList := FileStructure.GetDirList(...);

    for i:=0 to dirList.Count do begin
      if (FileStructure.FileExists(dirList[i]+Filename) = true) then
        ...
      end;
    end;
  end;


TXMLFileStructure speichert die gelesene Ordnerstruktur interne in einer Baumstruktur, die auf Zeigern und Listen basiert. Verwende ich TXMLFileStructure als "normale" Klasse (also TXMLFileStructure = class(TObject)) arbeitet diese ohne Probleme. Die Ordnerstruktur kann aus den passenden XML-Dateien ohne Fehler ausgelesen werden. Natürlich kann ich dann TXMLFileStructure nicht für TFileSearcher verwenden, aber das erstellen von TXMLFileStructure etc. gelingt fehlerfrei.

Verwende ich TXMLFileStructure jedoch als "interfaced" Klasse (also TXMLFileStructure = class(TInterfacedObject, IFileStructure)) kommt es bei der Verwendung zu Speicherzugriffsfehler. Ich ändere absolut nichts am Code von TXMLFileStructure (bis auf die Deklaration als Interfaced-Oject), trotzdem verhält sich die Klasse aufeinmal anders. Wie kann das sein?

Ich habe das Ganze bis zur folgenden Fehlerstelle zurückverfolgt:
TXMLFileStructure.ExplodePath wird intern verwendet um einen Pfad in seine Bestandteile zerlegt. Aus "C:\Test\Unterordner" wird dabei z.B. ["Test", "Unterordner"]. Intern wird hier der Ergebnis-Array für jedes Pfadstück verlängert, also "setLength(result, length(result)+1);" aufgerufen. Das funktioniert etliche Male prima, bis bei irgendeinem dieser Aufrufe aufeinmal als Nebeneffekt die interne Variable FRoot überschrieben wird und danach nicht mehr zu gebrauchen ist. Beim nächsten Zugriff auf FRoot kommt es dann zu einem Fehler.

Über die Implementierung von ExplodePath lässt sich sicherlich streiten. Aber ich kann das ganze auch Ändern und z.B. statt einem StringArray eine StringList zurück geben. Oder das Array nicht in jedem Schritt um 1 vergrößern sondern direkt die richtige Größe setzen. Wie die Methode arbeitet ist ganz egal. Irgendwann kommt es dem Effekt, dass FRoot überschrieben wird.

Das ist mir vollkommen unerklärlich. Natürlich kann wildes Schreiben in irgendwelche Arrays zu Problemen im Speicher führen, aber dazu kann "setLength(result, length(result)+1);" wohl kaum gezählt werden. Das sollte doch eigentlich "sicher" sein. Zudem tritt das Problem wirklich nur dann auf, wenn TXMLFileStructure als Interface-Objekt deklariert ist und es sonst absolut keinen Unterschied gibt.

Auch wenn ich ExplodePath gar nicht verwende kommt es irgendwo anders zu einem Fehler. Es scheint also irgendein tieferliegendes Problem zu geben.

Gibt es hierfür eine Erklärung? Muss bei der Arbeit mit Interfaces irgendwas besonders beachtet werden?

Besten Dank
Ares


Moderiert von user profile iconNarses: Topic aus Sonstiges (Delphi) verschoben am Di 26.01.2010 um 13:37
aksdb
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 29
Erhaltene Danke: 1

Windows 7, ArchLinux
D7 Prof, Kylix 3, Lazarus
BeitragVerfasst: Di 26.01.10 13:46 
TInterfacedObject ist referenzgezählt (bei Zugriff auf die Interfaces). Mehr dazu in der Hilfe oder über Google ;-)
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19339
Erhaltene Danke: 1752

W11 x64 (Chrome, Edge)
Delphi 12 Pro, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Di 26.01.10 13:53 
Wichtig ist, dass du ggf. auf das entsprechende Interface castest, wenn du es übergibst. Ich hab mir das jetzt bei dir nicht genauer angeschaut, aber da kann was schief gehen, obwohl es sich problemlos kompilieren lässt. Wenn ich mich recht entsinne, wird nämlich sonst das geerbte Interface zur Übergabe benutzt.

(Das musste ich vor einiger Zeit feststellen, hab über 2 Stunden oder so gebraucht bis ich da beim Debuggen drauf gekommen bin.)

Ich muss mir das nachher bei Gelegenheit mal nochmal anschauen, ich mach schon seit einiger Zeit nicht mehr so viel mit Delphi.
Ares Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 128



BeitragVerfasst: Di 26.01.10 19:10 
user profile iconaksdb hat folgendes geschrieben Zum zitierten Posting springen:
TInterfacedObject ist referenzgezählt (bei Zugriff auf die Interfaces). Mehr dazu in der Hilfe oder über Google ;-)


Die Referenzzählung ist mir schon bekannt. Soweit ich das Prinzip verstanden habe spielt diese aber nur beim Einsatz von Interfaces in verteilten Anwendungen eine Rolle also z.B. bei der Nutzung mit COM. Mir ist aber nicht klar, in wie weit das einen Einfluss auf mein Problem hat, da ich es sich hier nicht um eine verteilte Anwendung handelt. Kannst du mir vielleicht noch einen Wink geben in welcher Richtung ich suchen muss?
aksdb
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 29
Erhaltene Danke: 1

Windows 7, ArchLinux
D7 Prof, Kylix 3, Lazarus
BeitragVerfasst: Di 26.01.10 22:56 
COM ist schon das richtige Schlagwort. Sobald du (auch in deiner Anwendung) ein Interface abfragst, wird dort IUnknown.AddRef aufgerufen. Sobald eine Interface Variable verfällt (also du deren Scope verlässt), wird IUnknown.Release aufgerufen. TInterfacedObject hat IUnknown.Release allerdings so implementiert, dass sobald der RefCount = 0 wird, das Objekt zerstört wird.
Ergo: sobald du per Interface auf dein Objekt zugreifst, und dieser Zugriff wieder verfällt, wird dein Objekt aufgelöst.
Lösungsmöglichkeit: schreib dir dein eigenes Basisobjekt welches IUnknown implementiert, und bei dem AddRef sowie Release einfach nichts tun.