AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Projekte [Komponente] TFiFoBuffer
Thema durchsuchen
Ansicht
Themen-Optionen

[Komponente] TFiFoBuffer

Ein Thema von littleDave · begonnen am 19. Mär 2008 · letzter Beitrag vom 19. Mär 2008
Antwort Antwort
Benutzerbild von littleDave
littleDave
Registriert seit: 27. Apr 2006
Hallo erstmal,

Einführung
ich hab vor ca. nem Jahr mal das Problem, dass ich einen Datenstrom in einem Puffer zwischenspeichern musste, damit der Puffer von einem anderen Stream ausgelesen und verarbeitet werden konnte. Da ich damals zu faul war, mir was zusammenzusuchen, hab ich eben schnell eine kleine Komponente geschrieben, die das erledigt. Da ich damals mehrere Threads hatte, ist die Komponente fast Thread-Safe. Das "Fast" ist nur ein kleines Fast: wenn man dem Puffer eine neue Puffer-Größe zuweisen will, wird eine Routine aufgerufen, die nicht Thread-Safe ist. Da man aber den Puffer nur einmal mit einer fixen Größe erstellen sollte, hab ich die Routine absichtlich Thread-UNsafe gelassen.

eine Anwendungsmöglichkeit
Streams: Wenn man z.B. eine menge einzelner Bytes in eine Datei schreiben will, ist es nicht immer sinnvoll, immer wieder Stream.Write(aByte, 1); aufzurufen. Dies könnte man duch den Puffer sinnvoller gestallten:
Delphi-Quellcode:
aFiFoBuffer.Write(aByte, 1);
if aFifoBuffer.GetFilledSize > 1024 then // wenn mehr als 1KB Daten gespeichert sind
begin
  GetMem(Buffer, aFiFoBuffer.GetFilledSize);
  try
    aReaded := aFiFoBuffer.Read(Buffer^, aFiFoBufferSize);
    Stream.Write(Buffer^, aReaded);
  finally
     FreeMem(Buffer);
  end;
end;
Zwar ist die Routine mit dem Puffer etwas länger, aber mann könnte z.B. das Schreiben in den eigentlichen Stream in einen Thread verlegen, damit das Hauptprogramm nicht vom Schreiben in den Stream angehalten wird (z.B. bei langsamen Disketten-Laufwerken oder bei Netzwerk-Operationen).

Auch komplexe Sachen wie einen Schreibpuffer für ein Brenn-Programm könnten damit erstellt werden.

Benutzung
Ich habe in den ersten Zeilen eine Beispielroutine hinzugefügt. Diese kann einfach in eine Button-Click-Methode eingefügt werden. Man benötigt dazu nur eine Form mit zwei Edits ("Edit1" und "Edit") eine Memo-Komponenten ("Memo1") und natürlich einen Button, der das Ganze dann ausführt.

Funktionsweise anhand eines Beispiels
Ich will kurz die Funktionsweise anhand eines einfachen Beispiels
zeigen.
Code:
Legende:
  .   : freier Puffer-Speicher (das muss nicht heißen, dass die Werte alle
                                 gleich 0 sind. Es kann sich alles mögliche
                                 hier befinden.)
  |    : - über dem Puffer: Schreibkopfposition
         - unter dem Puffer: Lesekopfposition
Start:
Code:
(nach dem Start)
  |
  ............................................ (der Puffer)
  |
Schritt 1: speichere "Hallo Welt!"
Code:
(nach dem Schreiben)
             |
  Hallo Welt!................................. (der Puffer)
  |
Schritt 2: speichere "Mir gehts gut!"
Code:
(nach dem Schreiben)
                           |
  Hallo Welt!Mir gehts gut!................... (der Puffer)
  |
Schritt 3: lese den ersten String aus (Ergebnis: "Hallo Welt!")
Code:
(nach dem Lesen)
                           |
  ...........Mir gehts gut!................... (der Puffer)
             |
Schritt 4: speichere "Das ist aber eine tolle Sache" (Der Buffer ist jetzt komplett voll)
Code:
(nach dem Schreiben)
            |
  olle Sache.Mir gehts gut!Das ist aber eine t (der Puffer)
             |
Schritt 5: lese den zweiten und dritten String aus
Code:
(nach den Lese-Vorgängen)
            |
  olle Sache...............Das ist aber eine t (der Puffer)
                           |

            |
  ............................................ (der Puffer)
            |
Der Puffer ist jetzt wieder leer (obwohl die Lese und Schreibposition nicht = 0 sind)

Und was noch?
Das wars schon wieder von meiner Seite. Vielleicht kann dies ja der ein oder andere brauchen

Und hier ist die Unit:
Delphi-Quellcode:
unit FiFoBuffer;

{*******************************************************************************
Type          : FifoBuffer
Filename      : FifoBuffer.pas
Date          : 2008-03-19
Version      : 1.0.0.0
Last modified : 2008-03-19
Author        : David Huettig a.k.a littleDave
URL          : [url]www.godlikesoft.de[/url]
Copyright    : Copyright (c) 2008 David Huettig
History      :

      v. 1.0.0.0
      ==========
        - Initial release

*******************************************************************************}


{*******************************************************************************

Copyright (c) 2008, David Huettig ["copyright holder(s)"]

1. You are free to use this unit in any project, freeware or commercial.
    You are allowed to release this unit in OpenSource-projects - as long
    as you do not claim that you are the original author.
2. You are NOT allowed to remove these comment lines
3. If you redistribute this unit, please fill the history with any
    changes you made.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*******************************************************************************}
 

{*******************************************************************************

Diese Unit erstellt einen FiFo-Puffer (FirstIn - FirstOut - Puffer). Dieser
Puffer erlaubt es, sequentiell Daten in den Puffer zu schreiben um diese dann
später IN DER GLEICHEN REIHENFOLGE auslesen zu können. Die größe des Puffers
ist fix und wird am Anfang festgelegt. Es können nur soviel Bytes im Puffer
gespeichert werden, wie die maximale Größe.
Dieser Puffer ist bis auf die Funktion "AllocateMemory", die durch die Property

TotalSize := xxx

aufgerufen wird, thread-save. Das heißt, es kann ohne Probleme mit Threads
gearbeitet werden, ohne CriticalSections oder Synchronizes zu benutzen. Dies
wird alles intern geregelt!


===============================================
=  Kurzes Beispiel zur Benutzung              =
===============================================

var Buffer: TFiFoBuffer;  // Das Puffer-Object

    len  : integer;      // Daten die gelesen / geschrieben werden sollen
    s    : string;      // Daten die gelesen / geschrieben werden sollen
begin
  Buffer := TFiFoBuffer.Create(1024*5); // Puffer mit einer Füllmenge von 5KB erstellen
  try
    s := Edit1.Text;      // Den ersten Text auslesen
    len := length(s);    // die Länge des Textes auslesen

    if Buffer.Write(len, SizeOf(len)) = -1 then // Die Länge des Textes speichern
      raise Exception.Create('Fehler beim Schreiben - Puffer zu klein');
    if Buffer.Write(s[1], len) = -1 then        // Den Text an sich speichern
      raise Exception.Create('Fehler beim Schreiben - Puffer zu klein');

    s := Edit2.Text;      // Dann den zweiten Text auslesen
    len := length(s);    // die Länge des Textes auslesen

    if Buffer.Write(len, SizeOf(len)) = -1 then // Die Länge des Textes speichern
      raise Exception.Create('Fehler beim Schreiben - Puffer zu klein');
    if Buffer.Write(s[1], len) = -1 then        // Den Text an sich speichern
      raise Exception.Create('Fehler beim Schreiben - Puffer zu klein');

    (* Jetzt stehen beide Strings im Puffer - jetzt kann ich sie auslesen *)

    if Buffer.Read(len, SizeOf(len)) = -1 then  // Zuerst die Länge auslesen
      raise Exception.Create('Fehler beim Lesen - nicht genügend Daten im Puffer');
    SetLength(s, len);                          // Den String initialisieren
    if Buffer.Read(s[1], len) = -1 then        // Den String auslesen
      raise Exception.Create('Fehler beim Lesen - nicht genügend Daten im Puffer');

    (* Jetzt steht in s wieder der Inhalt von Edit1 *)
    Memo1.Lines.Add(s); // String ausgeben

    if Buffer.Read(len, SizeOf(len)) = -1 then  // Zuerst die Länge auslesen
      raise Exception.Create('Fehler beim Lesen - nicht genügend Daten im Puffer');
    SetLength(s, len);                          // Den String initialisieren
    if Buffer.Read(s[1], len) = -1 then        // Den String auslesen
      raise Exception.Create('Fehler beim Lesen - nicht genügend Daten im Puffer');

    (* Jetzt steht in s wieder der Inhalt von Edit2 *)
    Memo1.Lines.Add(s); // String ausgeben

  finally
    Buffer.Free;
  end;
end;


*******************************************************************************}


interface

uses
  SyncObjs;

type
  TFiFoBuffer = class(TObject)
  private
    FData : Pointer;
    FSize : integer;

    FReadPointer : integer;
    FWritePointer : integer;

    FWriteSection : TCriticalSection;
    FReadSection : TCriticalSection;
  protected
    function GetFilledSize : integer;
    procedure AllocateMemory(newSize: integer);
  public
    constructor Create(Size: integer);
    destructor Destroy; override;

    procedure ResetBuffer;

    function Write(const Buffer; Count: integer): integer;
    function Read(var Buffer; Count: integer): integer;

    property TotalSize : integer read FSize write AllocateMemory;
    property FilledSize : integer read GetFilledSize;
  end;

implementation

{ TFiFoBuffer }

procedure TFiFoBuffer.AllocateMemory(newSize: integer);
begin
  if newSize < 10 then // 10 Bytes sind minimum - könnten theoretisch auch 2 sein, doch wer braucht
     newSize := 10; // schon einen Buffer mit 2 Byte

  if Assigned(FData) then // Ist bereits ein Puffer vorhanden
     FreeMem(FData, FSize); // Müssen wir ihn erst freigeben

  GetMem(FData, newSize); // Speicher reservieren
  FSize := newSize; // Größe speichern
  ResetBuffer; // Lese- und Schreib "Kopf" zurücksetzen
end;

constructor TFiFoBuffer.Create(Size: integer);
begin
  inherited Create;
  FReadSection := TCriticalSection.Create;
  FWriteSection := TCriticalSection.Create;
  AllocateMemory(Size);
end;

destructor TFiFoBuffer.Destroy;
begin
  if Assigned(FData) then
     FreeMem(FData, FSize);
  FWriteSection.Free;
  FReadSection.Free;
  inherited;
end;

function TFiFoBuffer.GetFilledSize: integer;
begin
  FWriteSection.Enter; // Benötige Zugriff auf FWritePointer
  FReadSection.Enter; // Benötige Zugriff auf FReadPointer
  try
    if (FReadPointer > FWritePointer) then
       result := FSize - FReadPointer + FWritePointer // Puffer hat einen Rap-Around
    else
       result := FWritePointer - FReadPointer;
  finally
    FReadSection.Leave; // Und die kritischen Bereiche wieder verlassen
    FWriteSection.Leave;
  end;
end;

function TFiFoBuffer.Read(var Buffer; Count: integer): integer;
var Address : Pointer;
    SplitSize : integer;
begin
  result := -1; // Ergebniss initialisieren
  if GetFilledSize < Count then
     exit; // Es soll mehr gelesen werden, als im Puffer drinnsteht - abbrechen

  FReadSection.Enter; // Benötige Zugriff auf FReadPointer
  try
    if FSize - FReadPointer < Count then // Daten sind gesplittet
    begin
      SplitSize := FSize - FReadPointer;
      Move(Pointer(LongInt(FData) + FReadPointer)^, Buffer , SplitSize); // Den hinteren Teil lesen

      FReadPointer := 0;
      Address := Addr(Buffer);
      Address := Pointer(LongInt(Address) + (SplitSize));
      Move(FData^, Address^, Count - SplitSize); // Den vorderen Teil lesen
      inc(FReadPointer, Count - SplitSize); // Read Pointer anpassen

      result := Count; // Anzahl gelesener Bytes zurückgeben
    end else
    begin // Daten sind nicht gesplittet
      Move(Pointer(LongInt(FData) + FReadPointer)^, Buffer, Count); // Einfach fröhlich auslesen
      result := Count; // Anzahl der gelesenen Bytes zurückgeben
      inc(FReadPointer, Count); // Read-Position für den nächsten Zugriff anpassen
    end;
    if FReadPointer > FSize then // Falls doch ein Splitting erfolgt ist - anpassen
       FReadPointer := FReadPointer - FSize;
  finally
    FReadSection.Leave; // LesePointer wieder Freigeben
  end;
end;

procedure TFiFoBuffer.ResetBuffer;
begin
  FReadSection.Enter; // Benötige Zugriff auf Lese-Pointer
  FWriteSection.Enter; // Benötige Zugriff auf Schreib-Pointer
  try
    FReadPointer := 0; // Positionen zurücksetzen
    FWritePointer := 0;
  finally
    FReadSection.Leave; // Und Zugriff wieder Freigeben
    FWriteSection.Leave;
  end;
end;

function TFiFoBuffer.Write(const Buffer; Count: integer): integer;
var Address : Pointer;
    SplitSize : integer;
begin
  result := -1; // Anzahl geschriebener Bytes initialisieren
  if Count < 1 then // Wer will schon 0 oder -5 byte schreiben
     exit; // Falls es jemanden gibt, bekommt er nichts

  if FSize - GetFilledSize < Count then // Falls nicht mehr genügend Platz ist
     exit; // Kann nicht mehr in den Puffer geschrieben werden - abbrechen

  FWriteSection.Enter; // Benötige Zugriff auf SchreibPointer
  try
    if FSize - FWritePointer < Count then // Daten müssen gesplittet werden
    begin
      SplitSize := FSize - FWritePointer;
      Address := Addr(Buffer);
      Move(Buffer, Pointer(LongInt(FData) + FWritePointer)^, SplitSize); // Ersten Block ans Ende schreiben

      FWritePointer := 0;
      Address := Pointer(LongInt(Address) + (SplitSize));
      Move(Address^, FData^, Count - SplitSize); // Zweiten Block an den Anfang schreiben
      inc(FWritePointer, Count - SplitSize); // Write-Position setzen

      result := Count; // Anzahl geschrieber Bytes zurückgeben
    end else
    begin // Daten müssen nicht gesplittet werden
      Move(Buffer, Pointer(LongInt(FData) + FWritePointer)^, Count); // Fröhliches Kopieren
      result := Count; // Anzahl geschriebener Bytes zurückgeben
      inc(FWritePointer, Count); // Schrei-Position anpassen
    end;
    if FWritePointer > FSize then // Falls doch ein Splitting erfolgt ist - anpassen
       FWritePointer := FWritePointer - FSize;
  finally
    FWriteSection.Leave; // Und nun den Zugriff auf FWritePointer wieder freigeben
  end;
end;

end.
[edit]nen Rechtschreibfehler ausgebessert [/edit]
 
Dax
 
#2
  Alt 19. Mär 2008, 04:39
Ein paar Tipps:
  • Lasse verkleinern unter den Pufferfüllstand nicht zu und erhalte den Puffer beim vergößern (nicht unbedingt nötig, aber dann ließe sich der Puffer prima als Basis für Queues verwenden)
  • Da du beim Lesen/Schreiben sowieso immer beide Sections mindestens einmal brauchst, schnapp dir zu anfang beide und gib sie direkt nach Ende des Bedarfs wieder frei, das dürfte den Durchsatz ein wenig erhöhen.
  Mit Zitat antworten Zitat
Benutzerbild von littleDave
littleDave

 
Delphi 7 Professional
 
#3
  Alt 19. Mär 2008, 06:02
Zitat:
Lasse verkleinern unter den Pufferfüllstand nicht zu und erhalte den Puffer beim vergößern (nicht unbedingt nötig, aber dann ließe sich der Puffer prima als Basis für Queues verwenden)
Das könnte schierig werden: sobald Daten sich am "Ende" des Puffers befinden, müsste ich diese von vorne nach hinten kopieren. Das kann bei großen Puffern etwas Zeit dauern, die vielleicht bei zeitkritischen Anwendungen nicht da ist. Außerdem weiß ich nicht, ob mal mal eben den Speicherbereich, der einmal angefordert wurde, im Nachhinein zu vergrößern. Sonst müsste ich den kompletten Speicher neu erstellen und die alten Daten in den neuen kopieren.

Wenn es jemand braucht, sollte er sich vielleicht selbst was dazu überlegen und den Source als kleine Anregung nehmen

Zitat:
Da du beim Lesen/Schreiben sowieso immer beide Sections mindestens einmal brauchst, schnapp dir zu anfang beide und gib sie direkt nach Ende des Bedarfs wieder frei, das dürfte den Durchsatz ein wenig erhöhen.
Das stimmt nicht ganz - beim Lesen brauch ich die Read-Section, beim Schreiben die Schreib-Section. Die unterschiedliche Objekte haben schon ihren sinn. Wäre ja sonst auch blödsinn, sonst wäre das MultiThreading total umsonst, da man sonst nur Schreiben könnte wenn nicht gerade gelesen wird - und umgekehrt
  Mit Zitat antworten Zitat
Dax
 
#4
  Alt 19. Mär 2008, 16:48
Zitat von littleDave:
Das stimmt nicht ganz - beim Lesen brauch ich die Read-Section, beim Schreiben die Schreib-Section. Die unterschiedliche Objekte haben schon ihren sinn. Wäre ja sonst auch blödsinn, sonst wäre das MultiThreading total umsonst, da man sonst nur Schreiben könnte wenn nicht gerade gelesen wird - und umgekehrt
Und du brauchst jeweils beide, um die benutze Speichermenge im Puffer auszulesen
  Mit Zitat antworten Zitat
Antwort Antwort


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 01:54 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