Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Win32/Win64 API (native code) (https://www.delphipraxis.net/17-win32-win64-api-native-code/)
-   -   Delphi Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben (https://www.delphipraxis.net/187797-objekte-einer-tobjectlist-direkt-aus-dem-speicher-lesen-ihn-schreiben.html)

Benmik 4. Jan 2016 20:13

Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Ich habe eine generische TObjectList, in der u.a. auch Vorschaubilder als TBitmap gespeichert sind. Ich möchte diese Vorschaubilder gern auf der Platte speichern, damit sie nicht jedes Mal unnötiger Weise erstellt werden müssen.

Ich habe jetzt eine funktionierende Lösung, die mit Streams arbeitet. Nachteil nur, dass nach dem Wiedereinlesen in die TObjectList bestimmte Bilder auf dem Kopf stehen. Das hängt meiner Vermutung nach damit zusammen, dass bei Bitmaps gern bei der untersten Scanline angefangen wird und nicht bei der obersten. Ich weiß, dass ich den Bitmap-Header auslesen könnte, um die Leserichtung zu ermitteln.

Meine Idee wäre jetzt aber: Die Bitmap liegt doch im Speicher exakt so vor, wie sie richtig ist. Kann ich sie nicht einfach als einfache Bytes unter Umgehung aller Bitmap-Routinen in einen Stream schreiben und - das vor allem - aus dem (File-)Stream wieder in die Objektliste?

Sir Rufo 4. Jan 2016 20:32

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Wie speicherst du die Bitmaps denn jetzt?

Benmik 4. Jan 2016 20:42

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Delphi-Quellcode:
  procedure SchreibeBilderListeInDB;
  var BildNr:integer; Writer: TWriter; Stream : TFileStream;
  begin
    Try
      Stream := TFileStream.Create(Dateiname,fmCreate);
    Except
      exit;
    End;
    Writer := TWriter.Create(Stream,4096);
    Try
      Writer.WriteListBegin;
      // Anzahl Datensätze
      Writer.WriteInteger(Bilderliste.Count);
      For BildNr := 1 to BilderListe.Count do
        WriteBitmapToWriter(Bilderliste[BildNr - 1].VSB,Writer);
      Writer.WriteListEnd;
      Writer.FlushBuffer;
    Finally
      Stream.Free;
      Writer.Free;
    End;
  end;

procedure WriteBitmapToWriter(Bmp: TBitmap; Writer: TWriter);
var Stream: TMemoryStream;
//-----------------------------------------------------------------------------------------------------------------------------
  procedure WriteStreamToWriter(Stream: TMemoryStream; Writer: TWriter);
  const
    BufBytes = 1024*16; // 16 Kb
  var
    ReadBytes: Integer;
    Buf: array [0..BufBytes-1] of Byte;
  begin
    Stream.Position := 0;
    Writer.WriteInteger(Int64(Stream.Size));
    while Stream.Position < Stream.Size do
    begin
      ReadBytes := Stream.Read(Buf, BufBytes);
      Writer.Write(Buf, ReadBytes);
    end;
  end;
//-----------------------------------------------------------------------------------------------------------------------------
begin
  Stream := TMemoryStream.Create;
  try
    Bmp.SaveToStream(Stream);
    WriteStreamToWriter(Stream, Writer);
  finally
    Stream.Free;
  end;
end;
Die exakte Größe der Bitmap im Speicher müsste nach
Delphi-Quellcode:
GetDIBSizes(Bmp.Handle, InfoHeaderSize, ImageSize);
InfoHeaderSize + ImageSize sein. Ich stelle mir also vor, diese Anzahl Bytes ab Addr(Bilderliste[BildNr - 1].VSB) auszulesen und in eine Datei zu schreiben. Lesen kann auch kein Problem sein. Aber wie schreibe ich die Bytes in den Speicher? Speicher reservieren, Bytes reinschreiben, und dann den Pointer von Bilderliste[BildNr - 1].VSB auf diese Adresse setzen oder wie?

Sir Rufo 4. Jan 2016 22:39

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Also wenn ich Bitmaps aus einer Liste in einen Stream speichern wollte, dann würde ich das so machen:
Delphi-Quellcode:
procedure TBitmapList.SaveToStream( AStream: TStream );
var
  idx: Integer;
begin
  AStream.WriteData( Count );
  for idx := 0 to Count - 1 do
    Self[ idx ].SaveToStream( AStream );
end;
Das Lesen ist etwas aufwändiger (aber nicht wirklich)
Delphi-Quellcode:
procedure TBitmapList.LoadFromStream( AStream: TStream );
var
  idx, lCount: Integer;
  lBitmap   : TBitmap;
begin
  Clear;
  AStream.ReadData( lCount );
  for idx := 0 to lCount - 1 do
    begin
      lBitmap := TBitmap.Create;
      try
        lBitmap.LoadFromStream( AStream );
        Add( lBitmap );
        lBitmap := nil;
      finally
        lBitmap.Free;
      end;
    end;
end;
und zur Vollständigkeit
Delphi-Quellcode:
type
  TBitmapList = class( TObjectList<TBitmap> )
  public
    procedure SaveToStream( AStream: TStream );
    procedure LoadFromStream( AStream: TStream );
  end;

Benmik 4. Jan 2016 23:35

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Eine sehr schöne, kurze und elegante Lösung - aber die Bilder stehen nach dem Einlesen aus der Datei auf dem Kopf.
Das muss mit den Bitmap-Routinen zu tun haben, die ich aus diesem Grund ja vermeiden wollte.

Ich habe mir mittlerweile einen Weg gebastelt, die Bytes direkt in einen Stream zu schreiben und sie dann auch wieder auszulesen.
Das Schreiben funktioniert auch, aber - obwohl die Anzahl der Bytes genau stimmt - meldet Delphi beim Einlesen "Ungültiges Bitmap".
Ich werde morgen die Routinen mal nach deinem Vorbild anpassen und dann sehen, ob es funktioniert.
Vielen Dank einstweilen.

Uwe Raabe 5. Jan 2016 08:06

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Zitat:

Zitat von Benmik (Beitrag 1326010)
Eine sehr schöne, kurze und elegante Lösung - aber die Bilder stehen nach dem Einlesen aus der Datei auf dem Kopf.
Das muss mit den Bitmap-Routinen zu tun haben, die ich aus diesem Grund ja vermeiden wollte.

Es ist aber auch nicht auszuschließen, daß das gar nichts mit dem Speichern und Laden zu tun hat. Nach einem TBitmap.SaveToStream und TBitmap.LoadFromStream hat die Bitmap exakt denselben Inhalt wie zuvor. Ich vermute also einen anderen Grund für das auf-den-Kopf-stellen.

Blup 5. Jan 2016 09:47

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Zitat:

Zitat von Benmik (Beitrag 1326010)
Die Bitmap liegt doch im Speicher exakt so vor, wie sie richtig ist. Kann ich sie nicht einfach als einfache Bytes unter Umgehung aller Bitmap-Routinen in einen Stream schreiben und - das vor allem - aus dem (File-)Stream wieder in die Objektliste?

Das Delphi-TBitmap-Object kapselt eigentlich nur ein Handle eines Windows-GDI-Bitmaps.
Die eigentlichen Daten verwaltet das GDI (BitmapInfoHeader, Palette, Buffer der Pixeldaten), die liegen also nicht alle zusammen schön geordnet in einem Speicherbereich.

Wenn man die Pixeldaten per Scanline einlesen möchte, vorher also mindestens diese Schritte:
- Erzeugen der TBitmap-Instance
- Setzen von Pixelformat, Breite, Höhe

Benmik 5. Jan 2016 20:39

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Danke für die Antworten, ich komme heute leider nicht mehr zu weiteren Erforschungen.
Zitat:

Zitat von Blup (Beitrag 1326033)
die liegen also nicht alle zusammen schön geordnet in einem Speicherbereich.

Das wage ich, mit allem schuldigen Respekt, zu bezweifeln. Alle Strukturbeschreibungen, die ich gesehen habe, arbeiten mit einfacher Bytezählerei und Offsets.

himitsu 5. Jan 2016 20:56

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Zitat:

Zitat von Benmik (Beitrag 1326100)
arbeiten mit einfacher Bytezählerei und Offsets.

Ja, aber halt mit Lücken, je nach dem wie breit das Bitmap ist und wieviele Bits pro Pixel es sind.

Und manchmal auch noch in einer anderen Reihenfolge, als man denkt, z.B. die Zeilen gern von unten nach oben. :stupid:

Benmik 6. Jan 2016 20:48

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
So, und für Folgendes bitte ich um eine Erklärung.
Ich lade ein Bitmap aus einer Datei, zeichne sie auf die Form, speichere sie per Stream in einer Datei, lese sie wieder aus und zeichne sie erneut. Ergebnis: Beide Bilder werden richtig angezeigt.
Danach lade ich ein JPG aus einer Datei, extrahiere eine Bitmap und zeige sie an. Ergebnis: Sie wird richtig angezeigt.
Danach speichere sie per Stream in einer Datei, lese sie wieder aus und zeichne sie erneut. Ergebnis: Das Bild wird auf dem Kopf stehend angezeigt.
In allen 4 Fällen ist im InfoHeader der Wert von biHeight positiv.
Delphi-Quellcode:
procedure TForm1.ZeigeBitmap;
var Bmp:TBitmap; Stream : TMemoryStream; FStream:TFileStream; BildAnz:integer;
const Verz = 'C:\Temp\'; JPGDatei = 'Test.JPG'; BMPDatei = 'Test.bmp'; DBDatei = 'Test.db';
begin
  // Bitmap laden
  Bmp := TBitmap.Create;
  Bmp.LoadFromFile(Verz + BMPDatei);
  // Auf Form zeichnen
  Self.Canvas.Draw(10,10,Bmp);
  // In DB speichern
  FStream := TFileStream.Create(Verz + DBDatei,fmCreate);
  BildAnz := 1;
  FStream.WriteData(BildAnz);
  Bmp.SaveToStream(FStream);
  FStream.Free;
  Bmp.Free;
  // Wieder laden und anzeigen
  Bmp := TBitmap.Create;
  Stream := TMemoryStream.Create;
  Stream.LoadFromFile(Verz + DBDatei);
  Stream.ReadData(BildAnz);
  Bmp.LoadFromStream(Stream);
  Self.Canvas.Draw(10,250,Bmp);
  Bmp.Free;
  // Thumbnail aus JPG-Datei
  Bmp := TBitmap.Create;
  ExtractThumbnail(Bmp,Verz + JPGDatei,400,0);
  // Auf Form zeichnen
  Self.Canvas.Draw(450,10,Bmp);
  // In DB speichern
  FStream := TFileStream.Create(Verz + DBDatei,fmCreate);
  BildAnz := 1;
  FStream.WriteData(BildAnz);
  Bmp.SaveToStream(FStream);
  FStream.Free;
  Bmp.Free;
  // Wieder laden und anzeigen
  Bmp := TBitmap.Create;
  Stream := TMemoryStream.Create;
  Stream.LoadFromFile(Verz + DBDatei);
  Stream.ReadData(BildAnz);
  Bmp.LoadFromStream(Stream);
  Self.Canvas.Draw(450,250,Bmp);
  Bmp.Free;
  Stream.Free;
end;

procedure ExtractThumbnail(Bitmap:TBitmap; FileName:string; DesiredWidth:integer; DesiredHeight:integer);
var
  Malloc:IMalloc;
  DesktopFolder,SourceFolder:IShellFolder;
  eaten,flags,prio:cardinal;
  id:PItemIDList;
  ex:IExtractImage;
  s:TSize;
  h:HBITMAP;
  w:WideString;
begin
  try
    OleCheck(SHGetMalloc(Malloc));
    OleCheck(SHGetDesktopFolder(DesktopFolder));
    flags:=0;
    w:=ExtractFilePath(FileName);
    OleCheck(DesktopFolder.ParseDisplayName(0,nil,PWideChar(w),eaten,id,flags));
    try
      OleCheck(DesktopFolder.BindToObject(id,nil,IShellFolder,SourceFolder));
    finally
      Malloc.Free(id);
    end;
    w:=ExtractFileName(FileName);
    OleCheck(SourceFolder.ParseDisplayName(0,nil,PWideChar(w),eaten,id,flags));
    If SourceFolder.GetUIObjectOf(0,1,id,IExtractImage,nil,ex) <> S_OK
      then exit;
    s.cx:=DesiredWidth;
    s.cy:=DesiredHeight;
    If (s.cx > 160) or (s.cy > 90)
      then flags:=IEIFLAG_SCREEN or IEIFLAG_OFFLINE or IEIFLAG_QUALITY or IEIFLAG_ORIGSIZE
      else flags:=IEIFLAG_SCREEN or IEIFLAG_OFFLINE or IEIFLAG_QUALITY;
    prio:=0;
    SetLength(w,MAX_PATH);
    OleCheck(ex.GetLocation(PWideChar(w),Length(w)*2,prio,s,32,flags));
    OleCheck(ex.Extract(h));
    Bitmap.Handle:=h;
  finally
    Malloc.Free(id);
    DesktopFolder:=nil;
    SourceFolder:=nil;
    Malloc:=nil;
  end;
end;

Uwe Raabe 6. Jan 2016 21:57

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Hört sich irgendwie nach diesem Fehler an: GetObject() Always Returns Positive Height For DIB Sections

Uwe Raabe 6. Jan 2016 22:17

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Du kannst übrigens den upside-down Effekt auch schon beim ersten Zeichnen hervorrufen:

Delphi-Quellcode:
    ExtractThumbnail(Bmp, Verz + JPGDatei, 400, 0);
    Bmp.Dormant;
Damit ist eigentlich schon sehr wahrscheinlich, daß Windows offenbar das biHeight im InfoHeader falsch angibt.

Es hat somit nicht mit dem Speichern und Laden zu tun.

himitsu 7. Jan 2016 07:19

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Nein, das Bild hat natürlich immer eine positive Höhe,
aber wie intern die Daten im RAM liegen, das hat damit absolut nichts zu tun, sondern das hängt vom Datenformat ab.

Beispiel: BigEndian und LittleEndian :stupid:

Uwe Raabe 7. Jan 2016 07:51

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Zitat:

Zitat von himitsu (Beitrag 1326215)
Nein, das Bild hat natürlich immer eine positive Höhe,

Sicher, aber der Parameter biHeight im InfoHeader gibt über sein Vorzeichen die Orientierung an - normalerweise.

Zitat:

For uncompressed RGB bitmaps, if biHeight is positive, the bitmap is a bottom-up DIB with the origin at the lower left corner. If biHeight is negative, the bitmap is a top-down DIB with the origin at the upper left corner.

uligerhardt 7. Jan 2016 08:11

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Zitat:

Zitat von Sir Rufo (Beitrag 1326009)
Delphi-Quellcode:
      lBitmap := TBitmap.Create;
      try
        // ...
        lBitmap := nil;
      finally
        lBitmap.Free;
      end;

Das gibt ein Memory leak, wenn ich mich nicht sehr irre. :mrgreen:

himitsu 7. Jan 2016 08:16

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Na dann. :oops:

Aber wer glaubt schon einer Dokumentation? :stupid:
Wenn ich das täte, dann wäre in Delphi Vieles nicht "vorhanden", weil die OH immer behauptet "hier steht nix". :lol:

Uwe Raabe 7. Jan 2016 08:29

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Zitat:

Zitat von uligerhardt (Beitrag 1326225)
Zitat:

Zitat von Sir Rufo (Beitrag 1326009)
Delphi-Quellcode:
      lBitmap := TBitmap.Create;
      try
        // ...
        lBitmap := nil;
      finally
        lBitmap.Free;
      end;

Das gibt ein Memory leak, wenn ich mich nicht sehr irre. :mrgreen:

Nur, wenn die ObjectList, der direkt vor der nil-Zuweisung die Bitmap-Instanz zugefügt wird, diese nicht frei gibt.

Sir Rufo 7. Jan 2016 08:35

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Zitat:

Zitat von uligerhardt (Beitrag 1326225)
Zitat:

Zitat von Sir Rufo (Beitrag 1326009)
Delphi-Quellcode:
      lBitmap := TBitmap.Create;
      try
        // ...
        lBitmap := nil;
      finally
        lBitmap.Free;
      end;

Das gibt ein Memory leak, wenn ich mich nicht sehr irre. :mrgreen:

Nein, so sieht das Standard-Muster aus, wenn man das Lifetime-Management der Instanz delegiert.
Delphi-Quellcode:
      lBitmap := TBitmap.Create;
      try
        // Übergabe des Lifetime-Managements
        ...
        // Jetzt bin ich nicht mehr verantwortlich
        lBitmap := nil;
      finally
        lBitmap.Free;
      end;

uligerhardt 7. Jan 2016 08:36

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1326230)
Zitat:

Zitat von uligerhardt (Beitrag 1326225)
Zitat:

Zitat von Sir Rufo (Beitrag 1326009)
Delphi-Quellcode:
      lBitmap := TBitmap.Create;
      try
        // ...
        lBitmap := nil;
      finally
        lBitmap.Free;
      end;

Das gibt ein Memory leak, wenn ich mich nicht sehr irre. :mrgreen:

Nur, wenn die ObjectList, der direkt vor der nil-Zuweisung die Bitmap-Instanz zugefügt wird, diese nicht frei gibt.

Dann halt so: lBitmap := nil gefolgt von lBitmap.Free ist sinnfrei.

Edit: Ah, jetzt hab ich's kapiert. Im Falle einer Exception greift das Free ja noch. :wall:

Perlsau 7. Jan 2016 09:25

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Irgendwie entzieht sich mir der Sinn des Ganzen. Wenn ich doch bereits eine generische TObjectList habe, wieso verwendet man dann nicht einfach die Methoden, die das Objekt bereitstellt oder ergänzt das Objekt um eben diese Methoden? Wenn ich z.B. ein Objekt erstelle, das sagen wir mal ein TImage und ein TLabel enthält, dann sieht das z.B. so aus:
Delphi-Quellcode:
UNIT Bilder;
INTERFACE
USES
  Classes, ExtCtrls, StdCtrls, Graphics, Controls;

TYPE
  TBild = CLASS

    PRIVATE { Private-Deklarationen }
      Var
        fBild    : TImage;
        fTitel   : TLabel;
      Function GetfBild               : TImage;
      Procedure SetfBild(Const Value   : TImage);
      Function GetfTitel              : TLabel;
      Procedure SetfTitel(Const Value  : TLabel);

    PUBLIC { Public-Deklarationen }
      Constructor Create();
      Destructor Destroy; override;
      Property Bild : TImage read GetfBild write SetfBild;
      Property Titel : TLabel read GetfTitel write SetfTitel;
  END;

IMPLEMENTATION
{ TBild }

Function TBild.GetfBild: TImage;
begin
  Result := fBild;
end;

Procedure TBild.SetfBild(Const Value: TImage);
begin
  fBild.Assign(Value);
end;

Function TBild.GetfTitel: TLabel;
begin
  Result := fTitel;
end;

Procedure TBild.SetfTitel(Const Value: TLabel);
begin
  fTitel.Assign(Value);
end;

Constructor TBild.Create;
begin
  inherited;

  fBild                                  := TImage.Create(nil);
  fBild.Visible                          := False;
  fBild.AutoSize                         := False;
  fBild.Stretch                          := True;
  fBild.Proportional                     := True;
  fBild.Center                           := True;

  fBild.Picture.Bitmap.Canvas.Brush.Style := bsClear;
  fBild.Picture.Bitmap.Canvas.Pen.Color  := clRed;
  fBild.Picture.Bitmap.Canvas.Pen.Style  := psSolid;
  fBild.Picture.Bitmap.Canvas.Pen.Width  := 5;

  fTitel                                 := TLabel.Create(nil);
  fTitel.AutoSize                        := False;
  fTitel.Alignment                       := taCenter;
  fTitel.Layout                          := tlCenter;
  fTitel.WordWrap                        := False;
  fTitel.Visible                         := False;
end;

Destructor TBild.Destroy;
begin
  If Assigned(fBild) Then
     fBild.Free;
  If Assigned(fTitel) Then
     fTitel.Free;

  inherited;
end;
end.
So, jetzt brauche ich eine Methode, die das Bild speichert. Also ergänze ich das Objekt um die benötigte Methode. Wäre in diesem Fall, da ich bereits ein TImage verwende, nicht notwendig, da TImage (z.B. in TBitmap oder TPicture) bereits mehrere Methoden zum Speichern bereitstellt.

Wieso muß ich da umständlich ermitteln, wo das Bild im Speicher liegt, wo es beginnt und wo es aufhört, wenn ich das Ganze doch sowieso in einer TObjectList verwalte?
Delphi-Quellcode:
...
USES
  Windows, Messages, ..., Bilder, Generics.Collections, ...;
...

  PRIVATE { Private-Deklarationen }
    Var
      BList : Generics.Collections.TObjectList<TBild>;
Wozu dann überhaupt die TObjectList? Die TObjectList weiß doch genau, wo ihre Objekte liegen, darum muß mich doch gar nicht mehr kümmern. Ich greife einfach über den Index auf das jeweilige Objekt zu:
Delphi-Quellcode:
Procedure TFormMain.SaveBitmapFromList(Const Id : Integer; Const Datei : String);
begin
  BList[Id].Bild.Picture.Bitmap.SaveToFile(Datei);
end;

HolgerX 7. Jan 2016 13:27

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Hmm..

Anscheinend wird bei dem Bitmap, welches per ExtractThumbnail geholt wird etwas bei SaveToStream anders gemacht, wie bei Draw..

Wenn Du zunächst das Thumbnail mit einem 2. Bitmap holts und es dann auf dein eigentliches Bitmap malst (.Draw) dann gehts..

Delphi-Quellcode:
 
// Thumbnail aus JPG-Datei
  Bmp := TBitmap.Create;
  Bmp2:= TBitmap.Create;
  try
    ExtractThumbnail(Bmp2,Verz + JPGDatei,400,0);
    // Auf Form zeichnen
    Self.Canvas.Draw(450,10,Bmp2);

    Bmp.Assign(bmp2);
    Bmp.Canvas.Draw(0,0,bmp);

  ..

  finally
    Bmp2.Free;
    Bmp.Free;
  end;
So ist dann das Bitmap in bmp mit der korrekten Orientierung.

Uwe Raabe 7. Jan 2016 14:47

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Zitat:

Zitat von HolgerX (Beitrag 1326286)
Hmm..

Anscheinend wird bei dem Bitmap, welches per ExtractThumbnail geholt wird etwas bei SaveToStream anders gemacht, wie bei Draw..

Wie schon erwähnt, hat das nichts mit dem
Delphi-Quellcode:
SaveToStream
zu tun. Ein simples
Delphi-Quellcode:
bmp.Dormant
, was lediglich ein neues GDI-Objekt forciert, zeigt schon den Fehler. Vielleicht hat man den verlinkten Bug in der Implementation des IExtractImage Interfaces nicht mit gefixt.

HolgerX 7. Jan 2016 18:51

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1326305)
Zitat:

Zitat von HolgerX (Beitrag 1326286)
Hmm..

Anscheinend wird bei dem Bitmap, welches per ExtractThumbnail geholt wird etwas bei SaveToStream anders gemacht, wie bei Draw..

Wie schon erwähnt, hat das nichts mit dem
Delphi-Quellcode:
SaveToStream
zu tun. Ein simples
Delphi-Quellcode:
bmp.Dormant
, was lediglich ein neues GDI-Objekt forciert, zeigt schon den Fehler. Vielleicht hat man den verlinkten Bug in der Implementation des IExtractImage Interfaces nicht mit gefixt.

Schon klar, mir ging es nur um einen Workarround, damit der TE seine Bitmaps speichern kann, ohne dass Sie gedreht werden..

Benmik 7. Jan 2016 20:25

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Danke an euch alle. Ich bin natürlich froh, dass ich jetzt nicht als Depp dastehe.

Die Lösung von HolgerX ist ziemlich schräg, funktioniert aber (und warum?).
Das doppelte Zeichnen ist unter Umständen verschmerzbar, weil ja nur die sichtbaren Vorschaubilder gezeichnet werden, das ist überschaubar.

Übrigens war es bei mir so, dass nicht immer alle Bilder auf dem Kopf standen, sondern nur die Hochkantbilder, während die querkantigen richtig herum waren. Ich werde mal sehen, ob HolgerX' Lösung auch hier wirkt.

@Perlsau: Danke für deine Mühe. Ich möchte natürlich alle Bilder in einer einzigen Datei speichern. Das könnte ich natürlich auch in die Klasse der Objectlist integrieren. Der direkte Speicherzugriff war ja doch nur wegen der Rotationsproblematik.

Benmik 8. Jan 2016 20:51

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Die Implementierung von HolgerX' Idee hat nicht funktioniert und sie war mir ohnehin nicht so geheuer, weil ich nicht nachvollziehen konnte, warum sie überhaupt manchmal funktioniert.

Ich bin jetzt den Weg gegangen, einfach biHeight manuell negativ zu setzen, denn die Bitmap muss doch gar nicht gedreht, sondern es müssen nur die Scanlines in anderer Richtung ausgelesen werden. Das funktioniert prima. Dabei bin auch auch gleich Perlsaus Rat gefolgt, die Funktion in die Klasse zu verlegen.
Wird hier noch Verbesserungspotenzial gesehen (bis auf die Sünde, Umlaute zu verwenden)? Dieses doppelte "Free" zum Beispiel stört mich immer.

Zur Erläuterung der PosBiHeight: Die Größe des FileHeaders ist immer fix bei 14 Bytes. Beim nachfolgenden DIB header gibt es 7 Größen, aber alle fangen mit 3 x 4 Byte an. Die hartkodierte numerische Angabe erscheint mir sicherer als so etwas wie SizeOf(BitmapFileHeader) + SizeOf(InfoHeader.biSize) + SizeOf(InfoHeader.biWidth).
Delphi-Quellcode:
function TBilderListe.LeseVSBAusDatei(Dateiname: string): Boolean;
var BildNr,AnzDS:integer; Stream,VSBStream:TMemoryStream; VSBBmp:TBitMap; VSBHöhe:integer;
const PosBiHeight = 14 + 4 + 4;
begin
  Result := False;
  If not FileExists(Dateiname)
    then exit;
  Stream := TMemoryStream.Create;
  Try
    Stream.LoadFromFile(Dateiname);
  Except
    Stream.Free;
    exit;
  End;
  Stream.Position := 0;
  Stream.ReadData(AnzDS);
  If AnzDS <> BilderListe.Count
    then exit;
  VSBStream := TMemoryStream.Create;
  Try
    For BildNr := 0 to AnzDS - 1 do begin
      VSBBmp := TBitMap.Create;
      VSBBmp.LoadFromStream(Stream);
      VSBStream.Clear;
      VSBBmp.SaveToStream(VSBStream);
      VSBStream.Position := PosBiHeight;
      VSBStream.ReadData(VSBHöhe,SizeOf(VSBHöhe));
      VSBStream.Position := PosBiHeight;
      VSBStream.WriteData(-Abs(VSBHöhe));
      VSBStream.Position := 0;
      VSBBmp.LoadFromStream(VSBStream);
      Self[BildNr].VSB := VSBBmp;
    end;
    Stream.Free;
    VSBStream.Free;
  Except
    Stream.Free;
    VSBStream.Free;
    exit;
  End;
  Result := True;
end;

Sir Rufo 8. Jan 2016 21:47

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Dann schau dir mal
Delphi-Quellcode:
try finally
an, denn dafür ist das da.

Benmik 8. Jan 2016 22:29

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Ja. Nur dass dann bei einer Exception der Code durchläuft und Result damit True ist.
Ich habe jetzt zu einem Sprunglabel gegriffen. Bei dem "exit" da oben kommt es ja auch zu einem Speicherleck.

Sir Rufo 8. Jan 2016 22:42

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Nein, tut er nicht.

http://www.delphi-treff.de/tutorials.../exceptions/5/

Perlsau 8. Jan 2016 22:55

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Zitat:

Zitat von Benmik (Beitrag 1326458)
Ja. Nur dass dann bei einer Exception der Code durchläuft und Result damit True ist.
Ich habe jetzt zu einem Sprunglabel gegriffen. Bei dem "exit" da oben kommt es ja auch zu einem Speicherleck.

In diesem Fall verwendet man die Try-Finally-Variante:
Delphi-Quellcode:
function TBilderListe.LeseVSBAusDatei(Dateiname: string): Boolean;
var BildNr,AnzDS:integer; Stream,VSBStream:TMemoryStream; VSBBmp:TBitMap; VSBHöhe:integer;
const PosBiHeight = 14 + 4 + 4;
begin
  Result := False;
  If not FileExists(Dateiname)
    then exit;
  Stream := TMemoryStream.Create;
  Try
    Stream.LoadFromFile(Dateiname);
    Result := True;
  Finally
    Stream.Free;
  End;
  If Not Result Then Exit;
  ...
Wenn das Laden der Datei in den Stream schiefläuft, bleibt Result = False und somit wird die Methode nach dem Try-Finally-Block mit Exit verlassen. Normalerweise kann hier nichts schieflaufen, da ja die Existenz der Datei zuvor abgefragt wird. Einzige Fehlerquelle, die mir jetzt noch einfällt: Die Datei ist zu groß für den verfügbaren Arbeitsspeicher. Ich verwende daher immer noch einen Try-Except-Block innerhalb des Try-Finally-Blocks, um die Fehlermeldung zu erhalten, die in eine globale Fehlervariable geschrieben wird. Aber das kannst du letztendlich halten, wie du willst ...

BUG 8. Jan 2016 23:30

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Zitat:

Zitat von Perlsau (Beitrag 1326461)
Einzige Fehlerquelle, die mir jetzt noch einfällt: Die Datei ist zu groß für den verfügbaren Arbeitsspeicher.

Mir fallen noch einige ein: die Datei wurde zwischendurch gelöscht, umbenannt, der USB-Stick wurde gezogen; oder es fehlen die nötigen Zugriffsrechte.

Benmik 8. Jan 2016 23:59

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Zitat:

Zitat von Sir Rufo (Beitrag 1326459)
Nein, tut er nicht.

Ich fasse es nicht. Warum sagt einem sowas keiner?
Ich habe gerade noch ein bisschen gesucht. Nirgendwo wird man explizit darauf hingewiesen, dass es nach einer Exception nach dem Finally nicht weitergeht.
Ich verstehe übrigens nicht, warum es nach all den vielen Jahren immer noch kein Try...except...finally...end gibt. Man muss immer noch einen Block zwischen Try und Finally dazwischenklemmen.

Sir Rufo 9. Jan 2016 00:03

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Nirgendwo?

http://docwiki.embarcadero.com/RADSt..._try...finally

Bei Google suchentry finally (ist nicht auf Delphi beschränkt und funktioniert überall gleich)

Benmik 9. Jan 2016 00:42

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Irgendwo bestimmt, aber auch in deiner Zitatstelle finde ich das nicht.

BUG 9. Jan 2016 00:51

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Gar nicht so schwer zu finden.
Zitat:

Zitat von Exceptions (Delphi)
Bei einer Exception wird die Steuerung an Anweisungsliste2 übergeben und danach die Exception erneut ausgelöst.

Aber das muss man tatsächlich einmal gehört haben, um es zu wissen. Mich hatte das damals auch überrascht, als ich es das zum ersten Mal gehört habe :wink:

Perlsau 9. Jan 2016 01:29

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Zitat:

Zitat von BUG (Beitrag 1326462)
Zitat:

Zitat von Perlsau (Beitrag 1326461)
Einzige Fehlerquelle, die mir jetzt noch einfällt: Die Datei ist zu groß für den verfügbaren Arbeitsspeicher.

Mir fallen noch einige ein: die Datei wurde zwischendurch gelöscht, umbenannt, der USB-Stick wurde gezogen; oder es fehlen die nötigen Zugriffsrechte.

Da mußt du aber ganz ganz flink im Millisekundenbereich sein, wenn du zwischen dem Testen auf Existenz und dem Beginn des Einlesen noch schnell den Datenträger entfernen willst. Während des Einlesens dann schon eher ... Übrigens sollte man USB-Sticks ebenso wie USB-Platten niemals einfach so abziehen, sondern diese zuvor abmelden. Und da erhält man dann die Meldung, daß das nicht geht, weil der Datenträger noch in Verwendung ist. Wie schützt man ein Programm vor dem Betätigen des Ausschalters?

Sir Rufo 9. Jan 2016 01:49

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Sagen wir mal so: Wenn es die Datei eben noch gegeben hat, dann kann diese jetzt schon wieder über den Jordan sein. Wozu also der "DoppelTest" (Prüfen und beim Laden wird auch geprüft), wenn die Routine eh abgebrochen werden soll?

Ich würde da gar nicht so einen Aufriss von machen:
Delphi-Quellcode:
procedure TBilderListe.LeseVSBAusDatei(Dateiname: string);
var
  BildNr,AnzDS: integer;
  Stream,VSBStream: TMemoryStream;
  VSBBmp: TBitMap;
  VSBHöhe: integer;
const
  PosBiHeight = 14 + 4 + 4;
begin
  Stream := nil;
  VSBStream := nil;
  Try
    Stream := TMemoryStream.Create;
    Stream.LoadFromFile(Dateiname);
    Stream.Position := 0;
    Stream.ReadData(AnzDS);
    If AnzDS <> BilderListe.Count
    then
      raise EInvalidOperation.Create('Die Anzahl der Bilder stimmt nicht');
    VSBStream := TMemoryStream.Create;
    For BildNr := 0 to AnzDS - 1 do
    begin
      VSBBmp := TBitMap.Create;
      try
        VSBBmp.LoadFromStream(Stream);
        VSBStream.Clear;
        VSBBmp.SaveToStream(VSBStream);
        VSBStream.Position := PosBiHeight;
        VSBStream.ReadData(VSBHöhe,SizeOf(VSBHöhe));
        VSBStream.Position := PosBiHeight;
        VSBStream.WriteData(-Abs(VSBHöhe));
        VSBStream.Position := 0;
        VSBBmp.LoadFromStream(VSBStream);
        Self[BildNr].VSB := VSBBmp;
        VSBBmp := nil;
      finally
        VSBBmp.Free;
      end;
    end;
  finally
    Stream.Free;
    VSBStream.Free;
  End;
end;
Wenn es jetzt rummst (sollte im Normalfall aber gar nicht rummsen), dann wieß man auch direkt warum :stupid:

Uwe Raabe 9. Jan 2016 08:41

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Zitat:

Zitat von Benmik (Beitrag 1326466)
Irgendwo bestimmt, aber auch in deiner Zitatstelle finde ich das nicht.

Nicht?

Zitat:

Bei einer Exception wird die Steuerung an Anweisungsliste2 übergeben und danach die Exception erneut ausgelöst.
ergänzt durch ganz oben in dem Artikel
Zitat:

Eine Exception wird ausgelöst, wenn die normale Programmausführung durch einen Fehler oder ein anderes Ereignis unterbrochen wird. Die Steuerung wird dadurch an eine Exception-Behandlungsroutine übergeben.

Benmik 9. Jan 2016 13:05

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Na ja, nach dem Hinweis von Sir Rufo war die besagte Textstelle natürlich das Erste, was mir ins Auge gefallen ist. Ich möchte aber auf das "explizit darauf hingewiesen" abheben. Nachdem ich den Sachverhalt kannte, habe ich auch die Textstelle verstanden. Es sollte aber umgekehrt sein!
Wie bestimmt zahllose andere vor mir habe ich mir ein kleines Beispielprogramm (natürlich Divison durch Null) aufgesetzt, um die Sache zu verstehen. Ich denke, bei so ganz alte Hasen wie Sir Rufo und Uwe Raabe sind diese Dinge längst so verinnerlicht, dass sie die Irrwege von nicht so Beschlagenen nicht ganz nachvollziehen können. Natürlich steht das alles da, aber Meister der Didaktik sind weder bei Embarcadero noch bei Microsoft zu finden. Bei msdn werden die Erklärungen der API-Funktionen nicht umsonst regelmäßig von Kommentaren von Anwendern begleitet.
Vielleicht fehlen mir die höheren Weihen, aber ich finde die Exception-Behandlung einfach bescheuert.
Bei
Delphi-Quellcode:
Try
...
Finally
...
End
ist zwar gesichert, dass ein bestimmter Code (Speicherfreigabe wohl in der Regel) ausgeführt wird, aber es knallt nach wie vor, da in Finally keine Exception-Behandlung enthalten ist. Soweit ich das sehe, ist es auch bei dem Codebeispiel von Sir Rufo so, dass das Programm anhält, wenn es knallt, da es ja kein Except gibt (oder???).
Das Ganze ist ja schon oft in der DP diskutiert worden. Der Standardvorschlag ist doch
Delphi-Quellcode:
Try
  Try
  ...
  Except
  ...
  End
Finally
...
End;
Dann aber läuft der Code über das Finally hinaus, weil die Exception ja behandelt ist, und man muss irgendwie hinbekommen, dass Result dann trotzdem FALSE ist. Vermutlich durch ein Exit nach dem Except? Eingängig ist das nicht. Wirklich was für die guten alten Real Programmer.
Sir Rufo hat aus meiner Function eine Procedure gemacht, was hier nicht in meinem Sinn ist, da ich wissen möchte, ob das Einlesen funktioniert hat. Wenn nicht (weil es noch keine DB gibt, die DB veraltet ist oder aus sonstigen Gründen), werden die Vorschaubilder neu erzeugt.
Ansonsten wollte ich noch einiges an Fehlerbehandlung einfügen, aber nach der Kopfsteh-Problematik ging es erstmal um die Machbarkeit.
In jedem Fall habe ich wieder was gelernt, danke dafür.

Sir Rufo 9. Jan 2016 13:13

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Eine VCL/FMX Anwendung wird bei einer nicht abgefangenen
Delphi-Quellcode:
Exception
NICHT angehalten.

Es fallen auch keine Kühe oder Häuser um, es wird lediglich eine MessageBox angezeigt, die den Message-Text der
Delphi-Quellcode:
Exception
anzeigt.

Eine
Delphi-Quellcode:
Exception
ist nicht böse sondern bewahrt mich davor unnötig viel Code zu schreiben oder irgendetwas auszuführen was gar nicht ausgeführt werden darf.

Zudem kann man gezielt auf eine
Delphi-Quellcode:
Exception
reagieren, wenn man dafür einen Plan B hat. Sonst lässt man die einfach ziehen und somit zur Anzeige bringen.

Benmik 9. Jan 2016 13:38

AW: Objekte einer TObjectList direkt aus dem Speicher lesen / in ihn schreiben
 
Yess, Sir!
Auch ich finde Exceptions nicht böse! Ich finde sie gut! Sehr gut sogar! Honestly!
Ich bin nur der Meinung, das könne man besser machen. Programmiererfreundlicher. Nur als eine kleine Facette: Wie wäre es mit einem "Resume Next"?
Und nicht angehalten - na ja, es kommt eine MessageBox für den Anwender, und weiter geht es nicht, bis er was tut. Das ist sehr oft nicht das, was man will. Und die Routine wird sehr wohl verlassen, eine Schleife nicht weitergeführt. Geht in aller Regel auch nicht.
Wenn ich hier richtig mitlese, ist die endlose Kärrnerarbeit von zahllosen Try-Einfügungen auch bei Profis äußerst unbeliebt. Das kann man wirklich nicht besser machen?


Alle Zeitangaben in WEZ +1. Es ist jetzt 07:19 Uhr.
Seite 1 von 2  1 2      

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