Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Delphi Bei Erzeugung einer eigenen Klasse knallts (https://www.delphipraxis.net/173878-bei-erzeugung-einer-eigenen-klasse-knallts.html)

hadschi92 21. Mär 2013 20:52

Bei Erzeugung einer eigenen Klasse knallts
 
Hallo Ihr,

ich bin gerade dabei eine Unit mit mehreren Klassen zu programmieren. Diese Unit verwendet die NewAC Komponenten um Aufnahmen tätigen zu können. Ich habe verschiedene Klassen programmiert, eine Klasse TTrack, in der die Informationen von einem Track liegen. Dann die Klasse TTrackList, die mehrere Tracks enthält und zusätzliche Funktionen bietet. Und die Hauptklasse AudioControl in der alle unteren Klassen geladen und verwendet werden. Aber seht selbst (Achtung viel Quelltext):

Delphi-Quellcode:
unit AudioControl;

interface

uses
  ACS_Streams, ACS_DXAudio, NewACIndicators, SysUtils, Dialogs, Windows, Classes;
  //ACS_Classes, ACS_LAME, ACS_Wave

type
  TAudioControlState = (Initialised, Recording, Paused, Stopped);

type
  TAudioSaveFormat = class
  private
    FFormat: String;
    FBitrate: Integer;
  public
     procedure SetToMP3(Bitrate: Integer);
     procedure SetToWave();
     function GetFormat(): String;
     function GetBitrate(): Integer;
  end;

type
  TTrack = class
  strict private
    FAlbum: String;
    FNumber: Integer;
    FTitle: String;
    FInterpret: String;
    FComposer: String;
    FGenre: String;
    FCategory: String;
    FDescription: String;

    FStartTime: Cardinal;
    FLengthInMilliseconds: Cardinal;
    FStreamPath: String;
    FStream: TStream;

    class var FTrackCount: Integer;
  private
    constructor Create(Directory: String);
    property Album: String read FAlbum write FAlbum;
    property Number: Integer read FNumber write FNumber;
    property Title: String read FTitle write FTitle;
    property Interpret: String read FInterpret write FInterpret;
    property Composer: String read FComposer write FComposer;
    property Genre: String read FGenre write FGenre;
    property Category: String read FCategory write FCategory;
    property Description: String read FDescription write FDescription;

    property StartTime: Cardinal read FStartTime;
    property LengthInMilliseconds: Cardinal read FLengthInMilliseconds;
    property Stream: TStream read FStream write FStream;
    property StreamPath: String read FStreamPath;

    class property TrackCount: Integer read FTrackCount write FTrackCount;
  end;

type
  TTrackList = class
  private
    FTrackList: Array of TTrack;
    FTemporaryDirectory: String;

    FNumberOfChannels: Cardinal;
    FBitsPerSample: Cardinal;
    FSampleRate: Cardinal;

    constructor Create(TemporaryDirectory: String);
    procedure AddTrack();
  public
    property NumberOfChannels: Cardinal read FNumberOfChannels;
    property BitsPerSample: Cardinal read FBitsPerSample;
    property SampleRate: Cardinal read FSampleRate;

    procedure MoveTrack(StartTrackNumber, EndTrackNumber: Integer);
    procedure DeleteTrack(TrackNumber: Integer);

    function GetAlbum(TrackNumber: Integer): String;
    function GetNumber(TrackNumber: Integer): Integer;
    function GetTitle(TrackNumber: Integer): String;
    function GetInterpret(TrackNumber: Integer): String;
    function GetComposer(TrackNumber: Integer): String;
    function GetGenre(TrackNumber: Integer): String;
    function GetCategory(TrackNumber: Integer): String;
    function GetDescription(TrackNumber: Integer): String;

    procedure SetAlbum(TrackNumber: Integer; Album: String);
    procedure SetNumber(TrackNumber: Integer; Number: Integer);
    procedure SetTitle(TrackNumber: Integer; Title: String);
    procedure SetInterpret(TrackNumber: Integer; Interpret: String);
    procedure SetComposer(TrackNumber: Integer; Composer: String);
    procedure SetGenre(TrackNumber: Integer; Genre: String);
    procedure SetCategory(TrackNumber: Integer; Category: String);
    procedure SetDescription(TrackNumber: Integer; Description: String);

    function GetStartTime(TrackNumber: Integer): Cardinal;
    function GetLengthInMilliseconds(TrackNumber: Integer): Cardinal;
    function GetStream(TrackNumber: Integer): TStream;
    function GetStreamPath(TrackNumber: Integer): String;

    function GetLastTrackStream(): TStream;
    function GetLengthOfAllTracksInMilliseconds(): Cardinal;
  end;

type
  TAudioControl = class
  private
    FAudioSource: TDXAudioIn;
    FAudioGain: TGainIndicator;
    FAudioStream: TStreamOut;

    FTrackList: TTrackList;
    FState: TAudioControlState;

    procedure RunStream();
    procedure RunStreamInNewTrack();
    procedure PauseStream();
    procedure StopStream();
  public
    constructor Create(DeviceNumber, Channels, BitsPerSample, SampleRate: Integer);
    procedure StartRecording();
    procedure StartRecordingWithNewTrack();
    procedure PauseRecording();
    procedure StopRecording();

    property TrackList: TTrackList read FTrackList;
//    function TrackList: TTrackList;
   // procedure StartSavingThread(FileName: String; AudioSaveFormat: TAudioSaveFormat);
  end;

implementation

//------------------------------ TAudioControl -------------------------------//

constructor TAudioControl.Create(DeviceNumber, Channels, BitsPerSample, SampleRate: Integer);
begin
  FAudioSource := TDXAudioIn.Create(nil);
  FAudioSource.DeviceNumber := DeviceNumber;
  FAudioSource.InChannels := Channels;
  FAudioSource.InBitsPerSample := BitsPerSample;
  FAudioSource.InSampleRate := SampleRate;

  FAudioGain := TGainIndicator.Create(nil);
  FAudioGain.Input := FAudioSource;

  FAudioStream := TStreamOut.Create(nil);
  FAudioStream.Input := FAudioGain;

  FTrackList.Create('C:\');

  FState := Initialised;
end;

procedure TAudioControl.StartRecording();
begin
  if FState = Initialised then begin
    RunStreamInNewTrack;
  end else if FState = Recording then begin
    //
  end else if FState = Paused then begin
    RunStream;
  end else if FState = Stopped then begin
    RunStreamInNewTrack;
  end else begin
    // Fehlerrückgabe
  end;
end;

procedure TAudioControl.StartRecordingWithNewTrack();
begin
  if FState = Initialised then begin
    RunStreamInNewTrack;
  end else if FState = Recording then begin
    StopStream;
    RunStreamInNewTrack;
  end else if FState = Paused then begin
    StopStream;
    RunStreamInNewTrack;
  end else if FState = Stopped then begin
    RunStreamInNewTrack;
  end else begin
    // Fehlerrückgabe
  end;
end;

procedure TAudioControl.PauseRecording();
begin
  if FState = Initialised then begin
    //
  end else if FState = Recording then begin
    PauseStream;
  end else if FState = Paused then begin
    //
  end else if FState = Stopped then begin
    //
  end else begin
    // Fehlerrückgabe
  end;
end;

procedure TAudioControl.StopRecording();
begin
  if FState = Initialised then begin
    //
  end else if FState = Recording then begin
    StopStream;
  end else if FState = Paused then begin
    StopStream;
  end else if FState = Stopped then begin
    //
  end else begin
    // Fehlerrückgabe
  end;
end;

procedure TAudioControl.RunStream();
begin
  FAudioStream.Run;
  FState := Recording;
end;

procedure TAudioControl.RunStreamInNewTrack();
begin
  FTrackList.AddTrack();
  FAudioStream.Stream := FTrackList.GetLastTrackStream;
  FAudioStream.Run;
  FState := Recording;
end;

procedure TAudioControl.PauseStream();
begin
  FAudioStream.Pause;
  FState := Paused;
end;

procedure TAudioControl.StopStream();
begin
  FAudioStream.Stop(false);
  FState := Paused;
end;

//function TAudioControl.TrackList;
//begin
//  Result := FTrackList;
//end;

//---------------------------------- TTrack ----------------------------------//

constructor TTrack.Create(Directory: String);
begin
  FAlbum := '';
  FNumber := TrackCount;
  FTitle := '';
  FInterpret := '';
  FComposer := '';
  FGenre := '';
  FCategory := '';
  FDescription := '';

 // FStartTime := Now;
  FLengthInMilliseconds := 0;
  FStreamPath := Directory;
  FStream := TFileStream.Create(FStreampath + IntToStr(FNumber) + '.dat', fmCreate);
end;

//------------------------------ TTrackList ----------------------------------//

constructor TTrackList.Create(TemporaryDirectory: String);
begin
  //SetLength(FTrackList, 0);
  //FTemporaryDirectory := TemporaryDirectory;

  //FNumberOfChannels := 2;
  //FBitsPerSample := 16;
  //FSampleRate := 44000;
end;

procedure TTrackList.AddTrack();
begin
  setLength(FTrackList, Length(FTrackList) + 1);
  FTrackList[High(FTrackList)].Create(FTemporaryDirectory);
end;

procedure TTrackList.MoveTrack(StartTrackNumber, EndTrackNumber: Integer);
var
  TrackToMove: TTrack;
  i: Integer;
begin
  TrackToMove := FTrackList[StartTrackNumber];
  if StartTrackNumber < EndTrackNumber then begin
    for i := StartTrackNumber to EndTrackNumber - 1 do begin
      FTrackList[i] := FTrackList[i + 1];
    end;
  end else if StartTrackNumber > EndTrackNumber then begin
    for i := StartTrackNumber downto EndTrackNumber + 1 do begin
      FTrackList[i] := FTrackList[i - 1];
    end;
  end;
  FTrackList[EndTrackNumber] := TrackToMove;
end;

procedure TTrackList.DeleteTrack(TrackNumber: Integer);
var
  i: Integer;
begin
  for i := TrackNumber to High(FTrackList) - 1 do begin
    FTrackList[i] := FTrackList[i + 1];
  end;
  setLength(FTrackList, Length(FTrackList) - 1);
end;

function TTrackList.GetAlbum(TrackNumber: Integer): String;
begin
  Result := FTrackList[TrackNumber].Album;
end;

function TTrackList.GetNumber(TrackNumber: Integer): Integer;
begin
  Result := FTrackList[TrackNumber].Number;
end;

function TTrackList.GetTitle(TrackNumber: Integer): String;
begin
  Result := FTrackList[TrackNumber].Title;
end;

function TTrackList.GetInterpret(TrackNumber: Integer): String;
begin
  Result := FTrackList[TrackNumber].Interpret;
end;

function TTrackList.GetComposer(TrackNumber: Integer): String;
begin
  Result := FTrackList[TrackNumber].Composer;
end;

function TTrackList.GetGenre(TrackNumber: Integer): String;
begin
  Result := FTrackList[TrackNumber].Genre;
end;

function TTrackList.GetCategory(TrackNumber: Integer): String;
begin
  Result := FTrackList[TrackNumber].Category;
end;

function TTrackList.GetDescription(TrackNumber: Integer): String;
begin
  Result := FTrackList[TrackNumber].Description;
end;

procedure TTrackList.SetAlbum(TrackNumber: Integer; Album: String);
begin
  FTrackList[TrackNumber].Album := Album;
end;

procedure TTrackList.SetNumber(TrackNumber: Integer; Number: Integer);
begin
  FTrackList[TrackNumber].Number := Number;
end;

procedure TTrackList.SetTitle(TrackNumber: Integer; Title: String);
begin
  FTrackList[TrackNumber].Title := Title;
end;

procedure TTrackList.SetInterpret(TrackNumber: Integer; Interpret: String);
begin
  FTrackList[TrackNumber].Interpret := Interpret;
end;

procedure TTrackList.SetComposer(TrackNumber: Integer; Composer: String);
begin
  FTrackList[TrackNumber].Composer := Composer;
end;

procedure TTrackList.SetGenre(TrackNumber: Integer; Genre: String);
begin
  FTrackList[TrackNumber].Genre := Genre;
end;

procedure TTrackList.SetCategory(TrackNumber: Integer; Category: String);
begin
  FTrackList[TrackNumber].Category := Category;
end;

procedure TTrackList.SetDescription(TrackNumber: Integer; Description: String);
begin
  FTrackList[TrackNumber].Description := Description;
end;


function TTrackList.GetStartTime(TrackNumber: Integer): Cardinal;
begin
  Result := FTrackList[TrackNumber].StartTime;
end;

function TTrackList.GetLengthInMilliseconds(TrackNumber: Integer): Cardinal;
begin
  Result := FTrackList[TrackNumber].LengthInMilliseconds;
end;

function TTrackList.GetStreamPath(TrackNumber: Integer): String;
begin
  Result := FTrackList[TrackNumber].StreamPath;
end;

function TTrackList.GetStream(TrackNumber: Integer): TStream;
begin
  Result := FTrackList[TrackNumber].Stream;
end;

function TTrackList.GetLastTrackStream(): TStream;
begin
  Result := FTrackList[High(FTrackList)].Stream;
end;

function TTrackList.GetLengthOfAllTracksInMilliseconds(): Cardinal;
var
  i: Integer;
  LengthOfAllTracksInMilliseconds: Cardinal;
begin
  LengthOfAllTracksInMilliseconds := 0;
  for i := 0 to High(FTrackList) do begin
    LengthOfAllTracksInMilliseconds :=
      LengthOfAllTracksInMilliseconds + FTrackList[i].LengthInMilliseconds;
  end;
  Result := LengthOfAllTracksInMilliseconds;
end;

//------------------------------ TAudioSaveFormat ----------------------------//

procedure TAudioSaveFormat.SetToMP3(Bitrate: Integer);
begin
  FFormat := 'mp3';
  FBitrate := Bitrate;
end;

procedure TAudioSaveFormat.SetToWave();
begin
  FFormat := 'wav';
  FBitrate := 0;
end;

function TAudioSaveFormat.GetFormat(): String;
begin
  Result := FFormat;
end;

function TAudioSaveFormat.GetBitrate(): Integer;
begin
  Result := FBitrate;
end;

end.
Ich kann ein AudioControl-Objekt per Knopfdruck erzeugen:
Delphi-Quellcode:
procedure TMainform.StartPauseButtonClick(Sender: TObject);
begin
  AudioControl := TAudioControl.Create(1, 2, 16, 44000);
end;
Doch folgende Fehlermeldung wird dann sofort geworfen:
Code:
First chance exception at $00407B6F. Exception class $C0000005 with message 'access violation at 0x00407b6f: read of address 0x00000000'. Process Eventrecorder.exe (7752)
Wenn ich auf Break drücke bleibt der Debugger in der Unit: "System" bei folgender Funktion stehen:
Delphi-Quellcode:
function _AfterConstruction(const Instance: TObject): TObject;
begin
  try
    Instance.AfterConstruction;
    Result := Instance;
  except
    _BeforeDestruction(Instance, 1);
    raise;
  end;
end;
Weiß jemand, wieso dieser Fehler auftritt? Ich tippe auf eine falsche Initialisierung, da ich die Funktion "Create" von der Klasse TTrackList überschreibe. Und genau bei der Ausführung von TTrackList.Create tritt der Fehler auf.

sirius 21. Mär 2013 20:56

AW: Bei Erzeugung einer eigenen Klasse knallts
 
Delphi-Quellcode:
 FTrackList.Create('C:\');
ist falsch

mkinzler 21. Mär 2013 20:59

AW: Bei Erzeugung einer eigenen Klasse knallts
 
Delphi-Quellcode:
FTrackList := TTracklist.Create('C:\');
Dein Auruf wendet den Konstruktor auf ein nicht existententes Objekt an.

Btw. Ich würde am Anfang noch ein inherited einfügen, auch wenn es in diesem Fall nicht unbedingt notwendig ist.

hadschi92 21. Mär 2013 21:19

AW: Bei Erzeugung einer eigenen Klasse knallts
 
Oh man, da sieht man den Wald vor lauter Bäumen nicht.

Danke für die Lösung:
Delphi-Quellcode:
FTrackList := TTrackList.Create('C:\');

Blup 22. Mär 2013 09:24

AW: Bei Erzeugung einer eigenen Klasse knallts
 
Verbesserungsvorschläge:
- Ich würde den Constructor zumindest public machen, schließlich ist der Standard-Constructor "create" auch public.
- Zur Verwaltung der einzelnen Tracks bietet sich die TObjectList an.
- Die Tracklist braucht auf jeden Fall einen Destructor, damit die einzelnen Tracks freigegeben werden, bzw. die TObjectList.
- Die einzelnen Property der Tracks in der Tracklist noch einmal zu veröffentlichen ist unnötig umständlich. Besser nur den Track veröffentlichen und beim Track die entsprechenden Property public.
Delphi-Quellcode:
type
  TTrack = class
  strict private
    FAlbum: String;
    FNumber: Integer;
    FTitle: String;
    FInterpret: String;
    FComposer: String;
    FGenre: String;
    FCategory: String;
    FDescription: String;

    FStartTime: Cardinal;
    FLengthInMilliseconds: Cardinal;
    FStreamPath: String;
    FStream: TStream;

    class var FTrackCount: Integer;
  private
    class property TrackCount: Integer read FTrackCount write FTrackCount;
  protected
    procedure SetStream(AStream; TStream);
  public
    constructor Create(Directory: String);

    property Album: String read FAlbum write FAlbum;
    property Number: Integer read FNumber write FNumber;
    property Title: String read FTitle write FTitle;
    property Interpret: String read FInterpret write FInterpret;
    property Composer: String read FComposer write FComposer;
    property Genre: String read FGenre write FGenre;
    property Category: String read FCategory write FCategory;
    property Description: String read FDescription write FDescription;

    property StartTime: Cardinal read FStartTime;
    property LengthInMilliseconds: Cardinal read FLengthInMilliseconds;
    property Stream: TStream read FStream;
    property StreamPath: String read FStreamPath;
  end;

  TTrackList = class
  private
    FTrackList: TObjectList;
    FTemporaryDirectory: String;

    FNumberOfChannels: Cardinal;
    FBitsPerSample: Cardinal;
    FSampleRate: Cardinal;

    procedure AddTrack();
    function GetTrack(TrackNumber: Integer): Track;
  public
    constructor Create(TemporaryDirectory: String);
    destructor Destroy; override; // <- FTrackList freigeben

    property NumberOfChannels: Cardinal read FNumberOfChannels;
    property BitsPerSample: Cardinal read FBitsPerSample;
    property SampleRate: Cardinal read FSampleRate;

    procedure MoveTrack(StartTrackNumber, EndTrackNumber: Integer);
    procedure DeleteTrack(TrackNumber: Integer);

    function GetLastTrackStream(): TStream;
    function GetLengthOfAllTracksInMilliseconds(): Cardinal;

    property Track[TrackNumber: Integer]: TTrack read GetTrack; default;
  end;


// Zugriff auf einzelne Property des Track
  TrackList[TrackNumber].Album

hadschi92 25. Mär 2013 14:53

AW: Bei Erzeugung einer eigenen Klasse knallts
 
Blup, deine Anregungen sind gut. Ein paar Fragen habe ich noch:

Wieso muss der Konstruktor nicht mit mit override überschrieben werden? Gibt es keinen Standardkonstruktor? Dass ein Override beim Destruktor dazu muss ist mir inzwischen klar geworden, ich habe mich immer gewundert, warum mein Destruktor nie aufgerufen wird...
Delphi-Quellcode:
type
  TTrack = class
  strict private
    FAlbum: String;

  public
    constructor Create(Directory: String);

    property Album: String read FAlbum write FAlbum;
Hier kann ich in der Funktion Create jetzt entweder auf FAlbum oder Album zugreifen und lande beides mal auf der gleichen Variable. Was ist sauberer? Innerhalb einer Klassenmethode immer auf die Private Variablen zugreifen und nur wenn man von einer anderen Klasse kommt auf die Public Variablen zugreifen? Oder immer auf die Public Variablen zugreifen?

Medium 25. Mär 2013 14:56

AW: Bei Erzeugung einer eigenen Klasse knallts
 
Das kommt darauf an. Wenn der Setter/Getter einer Property mehr macht als nur lesen/schreiben des Feldes, kann es mal nötig sein diese zusätzlichen Dinge auch in der Klasse selbst mit auszulösen, an anderen Stellen in der Klasse wäre dies aber ggf. auch völlig verkehrt. Da eine generelle Aussage zu machen traue ich mich nicht.

In deinem konkreten Fall (also reines Weiterreichen der Zugriffe an das private Feld) würde ich tendenziell eher die Property nehmen, damit wenn später doch mal Getter/Setter dazu kommen, diese auch benutzt werden. Die Spezialfälle in denen man sie gezielt umgehen muss dürften weniger wahrscheinlich sein, so das dann hoffentlich weniger zu ändern ist im restlichen Code. Geschwindigkeitseinbußen gibt es dabei nicht, da Delphi bei dieser Art der Property (so weit ich weiss) im Kompilat das gleiche produziert wie bei einem direkten Zugriff auf das private Feld.


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