Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Aufruf neuer Typen in anderen Units (https://www.delphipraxis.net/166128-aufruf-neuer-typen-anderen-units.html)

Caesar2012 31. Jan 2012 14:14

Aufruf neuer Typen in anderen Units
 
Aufruf neuer Typen in anderen Units


Einleitung:
Ich bin mit der Weile ein fortgeschrittener Anfänger. Zwar kann ich neue Methode, Variablen und Formen miteinander Verknüpfen, aber wenn ich manchmal meine Fehler sehe, lache ich später selbst darüber. Nun geht es darum, eine neue Unit zu erstellen (schon geschehen) und einen neuen Typen zu kreieren. Ich kann darauf zugreifen, bekomme dennoch einen "SIGSEGV"-Error, einen Zugriffserror. Ich suche nach der Lösung seit 3 Tagen und arbeitete mich durch sämtliche Anleitungen, Themen, Foren und was es alles noch so gibt. Arbeiten tue ich mit Lazarus "0.9.30.2" und Win7 32bit.


Überblick:
Mein Projekt beinhaltet die Formen Form1 vom Typ TForm1 in mainunit.lfm bzw. mainunit.pas und Form2 vom Typ TForm2 in image.lfm bzw. image.pas. Außerdem beinhaltet mein Projekt die Unit kennzeichen.pas in denen TListe und TDatensatz deklariert sind.


Problembeschreibung:
In meiner mainunit.pas befindet sich
Delphi-Quellcode:
procedure TForm1.Search(SearchString: String);
, welche von einer anderen Prozedur in mainunit.pas aufgerufen wird. Am Schluss des interface-Teils deklariere ich:
Delphi-Quellcode:
var
     Form1: TForm1;
     isGo: Boolean;
     unsereListe: TListe;
Die Prozedur "Search" in mainunit.passieht im "implementation"-Teil wie folgt aus:
Delphi-Quellcode:
procedure TForm1.Search(SearchString: String);
begin
     if (Length(SearchString) <> 0) and (SearchString <> Last01.Caption) then
     begin
          Form1.Enabled := False;
          StartProgressBar(25);
          Wait(3);
          ChangeLast(SearchString);
          unsereListe := unsereListe.create;
          unsereListe.dateieinlesen('kfz.csv', SearchString);
          if unsereListe.NotA = false then
          begin
               Output2.Caption := unsereListe.liste[unsereListe.anzahl].kennzeichen;
               Output4.Caption := unsereListe.liste[unsereListe.anzahl].ort;
               Output6.Caption := unsereListe.liste[unsereListe.anzahl].bundesland;
               Output8.Caption := unsereListe.liste[unsereListe.anzahl].sonder;
          end;
          while ProgressBar.Position <> 0 do Wait(2);
          Form1.Enabled := True;
     end;
end;
Durch das Durchsteppen mit F7 erfuhr ich, dass er wie befohlen
Delphi-Quellcode:
constructor TListe.create;
begin
     anzahl := 0;
     NotA := false;
end;
ohne Probleme durchläuft und erst bei
Delphi-Quellcode:
procedure TListe.dateieinlesen (datname, suchKFZ: STRING);
begin
      try
           datname := extractfilepath(ParamStr(0)) + 'lib\kfz.csv';
           CompleteFile := TStringList.Create;
           CompleteFile.LoadFromFile(datname);
           ...
      finally
           ...
      end;
end;
kläglich scheitert. Nach dem "SIGSEGV"-Error zeigt er mit die Zeile
Delphi-Quellcode:
CompleteFile := TStringList.Create;
an. Das heißt, er kommt erst gar nicht dazu,
Delphi-Quellcode:
CompleteFile.LoadFromFile(datname);
durchzuführen.

Ich habe mein Projekt auf file-upload.net hochgeladen.

P.S.:

Während ich das Thema erstellt habe, fiel mir etwas auf. Ich verschob in kennzeichen.pas
Delphi-Quellcode:
CompleteFile := TStringList.Create;
in die
Delphi-Quellcode:
constructor TListe.create;
. Schon leif dies ohne Probleme. Aber es stockte nun bei
Delphi-Quellcode:
CompleteFile.LoadFromFile(datname);
.




Mit freundlichen Grüßen
Caesar2012, der Moderne

Sir Rufo 31. Jan 2012 14:30

AW: Aufruf neuer Typen in anderen Units
 
Versuch mal:
Delphi-Quellcode:
constructor TListe.create;
begin
  inherited; // <--- das ist wichtig!

     anzahl := 0;
     NotA := false;
end;
EDIT: Du kannst auch an deinen Beitrag Dateien anhängen, dann bleiben die auch am Beitrag und verschwinden nicht, wenn der Hoster sich verabschiedet

Caesar2012 31. Jan 2012 15:05

AW: Aufruf neuer Typen in anderen Units
 
Danke für die schnelle Antwort. Was macht dieses
Delphi-Quellcode:
inherited;
?
Ich könnte das Projekt nicht anhängen, da es 17 MB groß war. Und in den nächsten Monaten verabschiedet er sich nicht, der ist seit Jaaahren online und mein Favorit, da ohne Schnickschnack.

EDIT: Es funktioniert immer noch nicht. Nach dem Error meldet mir der Debugger beim Überfahren von
Delphi-Quellcode:
CompleteFile := TStringList.Create;
in TListe.dateieinlesen

"TStringList.Create = A syntax error in expression near `.Create` .

Sir Rufo 31. Jan 2012 15:13

AW: Aufruf neuer Typen in anderen Units
 
Zitat:

Zitat von Caesar2012 (Beitrag 1148538)
Danke für die schnelle Antwort. Was macht dieses
Delphi-Quellcode:
inherited;
?

inherited sorgt dafür, dass der Code aus der Vorgänger-Klasse für diese Methode (bzw. hier Konstruktor) auch aufgerufen wird.
Zitat:

Zitat von Caesar2012 (Beitrag 1148538)
Ich könnte das Projekt nicht anhängen, da es 17 MB groß war.

Du hast da eine Software mit 17MB gezippten QuellText ... Respekt ... das nenne ich fleißig ... ich kann es aber irgendwie nicht richtig glauben

Caesar2012 31. Jan 2012 15:51

AW: Aufruf neuer Typen in anderen Units
 
Liste der Anhänge anzeigen (Anzahl: 1)
Wie schon oben gesagt, funktioniert es immer noch nicht. Danke für deine Antwort. Jetzt habe ich nur das nötigste und ohne Bilder als Anhang mein Projekt hinzugefügt.

MFG Caesar2011, der Moderne

Falls ihr noch was benötigt, sagt es ruhig.

Sir Rufo 31. Jan 2012 17:05

AW: Aufruf neuer Typen in anderen Units
 
Sodele, ich habe dann mal die Unit entsprechend kommentiert und ausgebessert
Delphi-Quellcode:
unit kennzeichen;

// {$mode objfpc}{$H+}

interface

uses
  SysUtils,
  Classes;
// , Graphics, Controls, Forms, Dialogs, ExtCtrls;

type
  TDatensatz = class
    kennzeichen : STRING;
    // Öffentliche Variable, die das "kennzeichnen" ausgibt
    Ort : STRING;
    // Öffentliche Variable, die den "ort" ausgibt
    Bundesland : STRING;
    // Öffentliche Variable, die das "bundesland" ausgibt
    Sonder : STRING;
    // Öffentliche Variable, die das "bundesland" ausgibt
    constructor Create;
    // Konstruktor von TDatensatz
    destructor Destroy;
    // Destruktor von TDatensatz
    procedure Einlesen( DatenString : STRING );
    // Aufteilen von "datenstring" in "kennzeichen", "ort", "bundesland"
    function IstKennzeichen( KennzeichenString : STRING ) : BOOLEAN;
    // Boolean'sche Funktion, ob "kennzeichenstring" == "kennzeichen"
  end;

  TListe = class
  public
    Liste : ARRAY [1 .. 600] OF TDatensatz;
    // riesiges Array der kompletten *.csv des Types "TDatensatz"
    Anzahl : CARDINAL;
    // Nr. der aktuellen Zeile in der *.csv
    CompleteFile : TStringList;
    // Ganzes File als TStringList
    NotA : BOOLEAN;
    // Sagt aus, ob Input verfügbar
    constructor Create;
    // Konstruktor von TListe
    destructor Destroy; override;
    // Destruktor von TListe
    procedure DateiEinlesen( datname, suchKFZ : STRING );
    // Einlesen und Verarbeiten der *.csv (LoadFileFrom, Erstellen von "liste")
    function Lesen( i : CARDINAL; datnamen : TStringList ) : TDatensatz;
    // Gibt einen result des Typen TDatensatz aus

    function GibAnzahl : CARDINAL;
    // Gibt Variable "anzahl" aus

    // aber wozu? die Variable kann man doch direkt auslesen, wozu nochmals eine function dafür?

    function NAcheck( j : INTEGER; Stringlist : TStringList ) : BOOLEAN;
  end;

var
  unsereListe : TListe;

implementation

constructor TDatensatz.Create;
begin
  inherited; // <-- fehlte
  kennzeichen := '';
  Ort        := '';
  Bundesland := '';
  Sonder     := '';
end;

destructor TDatensatz.Destroy;
begin
  // überflüssig

  // kennzeichen := '';
  // Ort        := '';
  // Bundesland := '';
  // Sonder     := '';
  inherited; // <-- fehlte
end;

procedure TDatensatz.Einlesen( DatenString : STRING );
begin
  kennzeichen := Copy( DatenString, 1, pos( ';', DatenString ) - 1 );
  Delete( DatenString, 1, pos( ';', DatenString ) );
  Ort := Copy( DatenString, 1, pos( ';', DatenString ) - 1 );
  Delete( DatenString, 1, pos( ';', DatenString ) );
  Bundesland := DatenString;
end;

function TDatensatz.IstKennzeichen( KennzeichenString : STRING ) : BOOLEAN;
begin
  Result := false; // <-- verschoben, da sonst immer False als Result
  if KennzeichenString = kennzeichen
  then
    Result := true;
  // result  := false;
end;

constructor TListe.Create;
begin
  inherited;
  Anzahl      := 0;
  NotA        := false;
  CompleteFile := TStringList.Create; // das muss hier hin
end;

destructor TListe.Destroy;
var
  i : CARDINAL;
begin
  if Anzahl > 0
  then
    begin
      for i := 1 to Anzahl do
        Liste[i].Free; // nicht Destroy direkt aufrufen
      Anzahl := 0;
    end;
  NotA := false;
  CompleteFile.Free; // nicht Destroy direkt aufrufen
  inherited;        // <-- fehlte
end;

procedure TListe.DateiEinlesen( datname, suchKFZ : STRING );
begin
  // try

  datname := extractfilepath( ParamStr( 0 ) ) + 'lib\kfz.csv';

  // Nein, da gibt es schon eine Instanz

  // CompleteFile := TStringList.Create;

  CompleteFile.LoadFromFile( datname );
  if NAcheck( Anzahl, CompleteFile )
  then
    begin
      Lesen( Anzahl, CompleteFile );
      if not Liste[Anzahl].IstKennzeichen( suchKFZ )
      then
        begin
          Anzahl := GibAnzahl + 1;
          DateiEinlesen( datname, suchKFZ );
        end
      else
        begin
          if Lesen( Anzahl, CompleteFile ).Bundesland = ''
          then
            begin
              Liste[Anzahl].Sonder    := Liste[Anzahl].Bundesland;
              Liste[Anzahl].Bundesland := '';
              Liste[Anzahl].Ort       := '';
            end;
        end;
    end;

  // und diese Instanz bleibt

  // finally
  // CompleteFile.Free;
  // end;
end;

function TListe.Lesen( i : CARDINAL; datnamen : TStringList ) : TDatensatz;
begin
  Liste[i].Einlesen( datnamen.Strings[i] );
  Result := Liste[i];
end;

function TListe.GibAnzahl : CARDINAL;
begin
  Result := Anzahl;
end;

function TListe.NAcheck( j : INTEGER; Stringlist : TStringList ) : BOOLEAN;
begin
  Result := false; // <-- verschoben, da sonst immer False als Result
  if j <= Stringlist.Count
  then
    Result := true;
  NotA    := true;
  // Result  := false;
end;

end.
Allerdings ist der wirkliche Fehler gar nicht in der Unit zu finden ;)
sondern hier:
Delphi-Quellcode:
procedure TForm1.Search( SearchString : String );
begin
  if ( Length( SearchString ) <> 0 ) and ( SearchString <> Last01.Caption )
  then
    begin
      Form1.Enabled := False;
      StartProgressBar( 25 );
      Wait( 3 );
      ChangeLast( SearchString );

      // hier war der Haupt-Fehler

      unsereListe := TListe.Create; // statt unsereListe.create

      try

        unsereListe.dateieinlesen( 'kfz.csv', SearchString );
        if not unsereListe.NotA // statt unsereListe.NotA = False
        then
          begin
            Output2.Caption := unsereListe.liste[unsereListe.anzahl].kennzeichen;
            Output4.Caption := unsereListe.liste[unsereListe.anzahl].ort;
            Output6.Caption := unsereListe.liste[unsereListe.anzahl].bundesland;
            Output8.Caption := unsereListe.liste[unsereListe.anzahl].sonder;
          end;

      finally
        unsereListe.Free; // freigeben, sonst gibt das ein riesen Speicherleck
      end;

      // Geduldsprobe für den User, es passiert nichts, ausser dass der Balken sich bewegt

      while ProgressBar.Position <> 0 do
        Wait( 2 );

      Form1.Enabled := True;
    end;
end;
  • Warum ist unsereListe global definiert? so wie du diese benutzt könntest du die auch lokal in der Methode deklarieren. Oder in der Form-Klasse ... aber so passt das nicht so richtig zusammen
  • Wofür du aber extra die Ausführung abbremst um da in einer ProgressBar etwas anzuzeigen, ist mir etwas schleierhaft.

Caesar2012 31. Jan 2012 17:21

AW: Aufruf neuer Typen in anderen Units
 
Danke, dass du dir so viel Zeit gelassen hast. Ich werde es begutachten!!

Gibt es hier im Forum ein "User-Like"-Buuton? :-D

Bei den ganzen Fehlern wird mir schwindelig.

Den
Delphi-Quellcode:
destructor TDatensatz.destroy;
und alles andere mit
Delphi-Quellcode:
destroy;
habe ich ausder Schule, wo wie das Projetzt aus Zeitgründen niedergelegt haben.

Ach und zur Geduldsprobe: Weil ich's kann! ;-)


EDIT:
Jetzt bekomme ich zumindest kein Error. Nur leider kommt kein Output auf dem Feld. Ich schau mir mal den Debugger an.

Blup 1. Feb 2012 09:17

AW: Aufruf neuer Typen in anderen Units
 
Delphi-Quellcode:
TDatensatz = class
{...}
  destructor destroy; override;
{...}
end;
Der Destructor Destroy muss mit "override" überschrieben werden, damit dieser auch von Free benutzt wird.
In dieser Klasse ist der constructor überflüssig, da Stringvariablen einer Klasse automatisch leer, Interfaces und Pointer nil und numerische Variablen mit 0 initialisiert werden.
Diese Hinweise gelten z.T. auch für deine anderen Klassen.

Delphi-Quellcode:
Liste : ARRAY [1 .. 600] OF TDatensatz;
Sinnvoller ist hier ein dymamisches Array oder TObjectList.

Delphi-Quellcode:
// String-Parameter nach Möglichkeit als const übergeben, kann der Compiler besser optimieren.
function TDatensatz.istKennzeichen(const kennzeichenstring:STRING):BOOLEAN;
begin
// Vergleiche ergeben bereits einen Boolean-Wert
// if (True) then
//   Result := True
// else
//   Result := False;
// den man auch einer Variablen oder dem Rückgabewert direkt zuweisen kann.
  Result := (kennzeichenstring = kennzeichen);
end;
Das Laden und Speichern direkt im Datenobjekt zu implementieren ist keine gute Idee.
Hier bietet sich z.B. das Visitor-Pattern an.
http://de.wikipedia.org/wiki/Visitor

Caesar2012 1. Feb 2012 10:46

AW: Aufruf neuer Typen in anderen Units
 
Danke auch dir. Nur bei dem destroy von
Delphi-Quellcode:
destructor TListe.destroy;
hasbe ich Probleme.

Aufgerufen wird er mit
Delphi-Quellcode:
unsereListe.destroy;
in mainunit.pas, nachdem alles fertig ist.

Ich glaube es hat teilweise auch was mit dem anderen Destruktor
Delphi-Quellcode:
destructor TDatensatz.destroy;
zu tun. Sir Rufo half mir schon per PN, aber ich konnte keine wiederholte Zerstörung feststellen. Außerdem bin ich mir mit dem override sowie dem Unterschied von free/freeandnil/destoy nicht im Klaren. Wann benutze ich
Delphi-Quellcode:
override:
bei Destuktoren und wann nicht? Wann benutze ich
Delphi-Quellcode:
.Free
,
Delphi-Quellcode:
.FreeAndNil
und wann
Delphi-Quellcode:
.Destroy
?

Delphi-Quellcode:
destructor TListe.destroy;
var i:CARDINAL;
begin
     if anzahl > 0 then
     begin
          for i := 1 to anzahl do liste[i].free;
          anzahl := 0;
     end;
     NotA := false;
     CompleteFile.free;
     inherited;
end;
Delphi-Quellcode:
destructor TDatensatz.destroy;
begin
     kennzeichen := '';
     ort := '';
     bundesland := '';
     sonder := '';
     inherited;
end;

DeddyH 1. Feb 2012 10:50

AW: Aufruf neuer Typen in anderen Units
 
Der Destruktor Destroy sollte immer überschrieben werden. Ein direkter Aufruf ist zu vermeiden, stattdessen Free verwenden, welches vorher auf nil prüft. FreeAndNil verwendet man dann, wenn es möglich sein kann, dass nach dem Freigeben evtl. noch auf die (nun nicht mehr vorhandene) Instanz zugegriffen werden könnte. Somit kann man auf nil prüfen.


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