Entwickler-Ecke

Dateizugriff - BILD dirct in den Speicher


mimi - Mi 13.08.03 14:00
Titel: BILD dirct in den Speicher
Hallo,
ich arbeitet zur zeit mit TStreams.
und habe einen file arichver geschrieben der auch funktioniert.
nun möchte ich ohne das die bilder auf die festplatte geschrieben werden angezeit werden.
ich habe es so versucht:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
function TMFa.GetImageFile(Index:Integer):TStream;
var
  Datei:TStream;
  Datei2:TStream;
  DateiName:String;
begin
  try
    DateiName:=FileName;

    with ExtractFileHeader(Index) do begin
      Datei := TFileStream.Create(GetExeDir + 'temp\' + ExtractFileName(DateiName)+'.tmp',fmOpenRead); // Dateiarichv
      Datei.Seek(APOS,soFromBeginning);
      Datei2:=TMemoryStream.Create;
      Datei2.CopyFrom(Datei,FileSize);
      result:=Datei2;
    end;
  finally
    Datei.Free;
    Datei2.Free;
  end;
end;

aber da kommt blos eine zugriefs verletzung
wenn ich die funktion benutze:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
procedure TForm1.BitBtn8Click(Sender: TObject);
var
  test:TBitmap;
begin
  a.FileName:=Edit1.Text;
 
  test:=TBitmap.Create;
  test.LoadFromStream( a.GetImageFile(1) );
  image1.Picture.Graphic:=test;

end;

kann mir jemmand sagen warum da eine fehlermeldung kommt ?
im stream steht das richtige drin, das weiß ich, aus tests die ich angestellt habe....


Keldorn - Mi 13.08.03 14:13

Zitat:

result:=Datei2;
end;
finally
Datei.Free;
Datei2.Free;

du kopierst mit result:=Datei2 nix, result "zeigt" nur auf datei2

und wenn du jetzt datei2.free machst dann gibst du automatisch auch dein Result frei und damit deinen Rückgabewert und damit muß

Zitat:

test.LoadFromStream( a.GetImageFile(1) );

in die Hose gehen, weil du mit loadfromstream auf etwas zugreifen willst, das du schon freigeben hast.

mit Objekten als Rückgabewert sollte man vorishctig sein, hier kannst du dir schnell ein Speicherloch schaffen, wenn du nicht aufpasst
Mfg Frank


mimi - Mi 13.08.03 14:56

mm, und wie solllte ich es am besten lösen ?
ich hate mal geschaut ob der rückgabe wert nil ist, war er aber nicht


Keldorn - Mi 13.08.03 18:12

bei einem .free wird das Objekt nicht automatisch auf Nil gesetzt. Das kannst du aber mit FreeAndNil machen.
FreeAndNil macht auch nix anderes als

Quelltext
1:
2:
  objekt.free;
  objekt:=NIL;

probiers mal: nur sl2 ist Nil

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
procedure TForm1.Button1Click(Sender: TObject);
var sl1,sl2:Tstringlist;
begin
  sl1:=TStringList.Create;
  sl2:=TStringList.Create;
  sl1.free;
  FreeAndNil(sl2);

  if sl1=Nil then Memo1.Lines.Add('sl1=Nil')
             else Memo1.Lines.Add('sl1<>Nil');
  if sl2=Nil then Memo1.Lines.Add('sl2=Nil')
             else Memo1.Lines.Add('sl2<>Nil');
end;

ich seh in deinem Code nicht durch, warum du 2 streams nimmst und diese kopierst.
Wenn du auf den Rueckgabewert bestehst:
du darfst den rueckgabewert nicht freigeben und mußt dieses freigeben später erledigen, in dem du eine Zwischenvariable einsetzt
stell dir die Procedure Rueckgabe wie einen Konstruktor vor.das hier ist auch sehr lesenswert [http://www.delphi-groups.de/YaBBSe/index.php?board=7;action=display;threadid=16182]


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
function Rueckgabe:Tmyobjekt;
  begin
    result:=tmyobjekt.create;
    ...
  end;
procedure machwas;
var myobjekt:Tmyobjekt;
  begin
    myobjekt:=rueckgabe;
    ...
    machwas(myobjekt);
    ...
    myobjekt.free;
  end;

und dabei noch try finally einbauen


außerdem solltest du dir evtl angewöhnen, assign anzuwenden

Delphi-Quelltext
1:
  image1.Picture.Assign(test);                    

zwar wird mit image1.Picture.Graphic:=test auch die assign-methode durch die Set-Methode bei Tpicture.graphic aufgerufen, aber es ist übersichtlicher da dann auf anhieb klar ist, das du das ganze Objekt kopierst und nicht bloß den "Zeiger" wie bei Result:=datei2.

Mfg Frank


mimi - Mi 13.08.03 18:42

ich sehe leider kein bild, woran kann das liegen ?
die daten sind richtig, das weiß ich, also das was im stream drinen steht.
warum 2 stream ?
ganseinfach:
ich baue gerade einen FileArichver, der eine stream ist das arichfiel und der andere soll die variable sein, in der es hinneinkommt.
verwende ich vieleicht TMemoryStream falsch an ?


Keldorn - Do 14.08.03 20:10

ein andere variante wäre noch die sache anders herum aufzubauen
also z.B. a.LadeBildInImage(index,Image) und du übergibst das entsprechende Image an eine Methode deiner klasse. hätte für mich den Vorteil, das du den code nicht über mehrere Units verstreust.

mimi hat folgendes geschrieben:
ich sehe leider kein bild, woran kann das liegen ?

keine ahnung. da gibt es zuviele unbekannte (Apos, Extractfileheader etc.)

Zitat:

warum 2 stream ?
ganseinfach:
ich baue gerade einen FileArichver, der eine stream ist das arichfiel und der andere soll die variable sein, in der es hineinkommt.

versteh ich nicht ganz, du speicherst Daten hintereinadner in einen Stream und liest sie später wieder aus. z.B. kannst du 3 bitmaps mit savetostream in einen stream reinschreiben und nachher mit 3x loadfromstream wieder laden.


mimi - Do 14.08.03 20:25

ich habe mal den stream abgespeichert und die bmp war gültig.
es liegt also an TMemoryStream.
wie kann ich ein stream anzeigen ob es nun in einem image ist oder in einem richtedit ist ja egal.... aber wie geht das überhaupt ?
ist TMemoryStream vileicht auch die falsche wahl ???


Keldorn - Do 14.08.03 21:21

eigentlich ist es logisch

onlinehilfe zu tstream.copyfrom hat folgendes geschrieben:

CopyFrom kopiert die in Count angegebene Anzahl Bytes aus dem in Source angegebenen Stream in den Stream. Dann verschiebt es die aktuelle Position um die in Count angegebene Anzahl Bytes

also bist du mit
Zitat:

Datei2.CopyFrom(Datei,FileSize);

am ende vom stream datei2 und da steht nix mehr zum auslesen
setz mal

Delphi-Quelltext
1:
datei2.Seek(0,soFromBeginning);                    

dahinter


Aya - Fr 15.08.03 02:50

Hi,

versuch es mal so...


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
function TMFa.GetImageFile(Index:Integer):TMemoryStream; 
var 
  Datei:TFileStream; 
  DateiName:String; 
begin 
  try 
    DateiName:=FileName; 
    with ExtractFileHeader(Index) do begin 
      Datei := TFileStream.Create(GetExeDir + 'temp\' + ExtractFileName(DateiName)+'.tmp',fmOpenRead); // Dateiarichv 
      Datei.Seek(APOS,soFromBeginning); 
      Result:=TMemoryStream.Create; 
      Result.CopyFrom(Datei,FileSize); 
      Result.Seek(0,soFromBeginning);
    end; 
  finally 
    Datei.Free; 
  end; 
end;


wobei das eigentlich unsinnig ist, macht ja im grunde dasselbe wie dein zuvoriges.. (aber man spart eine Var).

Was genau ist denn APOS, FileSize und ExtreactFileHeader()???
Wenn du uns das nicht sagst können wir dir wohl nicht weiterhelfen..

Au'revoir,
Aya


mimi - Fr 15.08.03 11:41

Apos ist die anfangs position von der datei und FileSize ist die datei größe
ExtreactFileHeader() wie der name schon sagt: filtert den datei header aus der datei.
alles funktioniert auch die entpack funktion es muss aber an tmemorystream liegen.


mimi - Fr 15.08.03 11:51

Apos ist die anfangs position von der datei und FileSize ist die datei größe
ExtreactFileHeader() wie der name schon sagt: filtert den datei header aus der datei.
alles funktioniert auch die entpack funktion es muss aber an tmemorystream liegen

edit:
jetzt geht es, aber ich kann keine jpeg bilder anzeigen. weiß jemmand warum ?
qullcode:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
var
  test:TBitmap;
begin
  a.FileName:=Edit1.Text;

  test:=TBitmap.Create;
  test.LoadFromStream( a.GetStream(0) );
  image1.Picture.Assign(test);

BMP bilder kann ich prima anzeigen.


Keldorn - Fr 15.08.03 11:58

Zitat:

jetzt geht es, aber ich kann keine jpeg bilder anzeigen. weiß jemmand warum ?

weil ein jpeg keine Tbitmap ist?

gugg in der hilfe zu Tjpegimage

ich würd das aber trotzdem anders machen und nicht den umweg über den 2. stream gehen. du kopierst ja auch den kompletten teil von Apos bis ende des streams datei1. wenn du 10 mb daten hast und das erste bild auslesen willst, kopierst du die ganzen 10 mb, obwohl du nur einen Teil brauchst.

Ich würde wie oben beschrieben, eine bmp oder das image übergeben und das bild reinladen und nicht den stream zurückgeben.


mimi - Fr 15.08.03 12:00

ich glaube du versteht das prinzip nicht(oder ich verstehe dich nicht):
ich öffne erst das arichv fiel dann gehe ich davon den header druch und suche mir die anfang pos und end pos und fertig.


Keldorn - Fr 15.08.03 20:01

sorry, hab auf arbeit in der hektik überlesen, das Filesize die dateigröße des zu ladenen bildes ist (so versteh ich zumindestens jetzt) und nich die des gesamten streams.
und dein Prinzip versteh ich schon (denke ich :mrgreen:)

ich geh mal jetzt davon aus, das du nur die methode GetImageFile in getstream umbenannt hast. wenn das der fall ist, sag noch dir nochmal, das du es so nicht machen kannst, weil du dir ein speicherloch schaffst. (der zurückgegebene Memorystream wird immer wieder neu erstellt, aber nie freigegeben) wenn du im Taskmanager den Speicherbedarf verlauf anguggst, wirst du das sehen, deine Anwendung wird bei jedem laden immer mehr speicher verbrauchen, die Kurve geht immer nach oben, bis nix dann mehr geht. und wenn du Bilder lädst, wird der Speicherbedarf nicht unerheblich sein.

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
var
  test:TBitmap;
  stream:Tstream;
begin
  a.FileName:=Edit1.Text;

  test:=TBitmap.Create;
  stream:=a.GetStream(0);
  test.LoadFromStream( stream );
  stream.free;
  image1.Picture.Assign(test);
  test.free;

ich persönlich würde vermeiden, das eine eigene Klasse irgendwo ein Objekt als Rückgabe hat, das neu erstellt wird. Die gefahr wäre mir zu groß, das ich vergesse, die Rückgabe freizugeben.

ich mein das so: (sinnbildlich)

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:
Procedure TMFa.GetImageFile(Index:Integer, bmp:Tbitmap);
var
  Datei:TStream;
  DateiName:String;
begin
  try
    DateiName:=FileName;

    with ExtractFileHeader(Index) do begin
      Datei := TFileStream.Create(GetExeDir + 'temp\' + ExtractFileName(DateiName)+'.tmp',fmOpenRead); // Dateiarichv
      Datei.Seek(APOS,soFromBeginning);
      bmp.loadfromstream(datei);
    end;
  finally
    Datei.Free;
  end;
end;

Procedure Aufruf;
Var test:Tbitmap;
  begin  
     test:=Tbitmap.create;
     GetImageFile(0,test); //bereits erzeugte bitmap übergeben
     image1.Picture.Assign(test);
     test.free;    
  end;

du sparst dir di umkopiererei und es wäre für mich eindeutiger, da die creates und frees zusammen sind und nicht evtl über mehrere Units verteilt sind.
Alternativ anstatt eines Bitmaps gleich ein image übergeben Procedure TMFa.GetImageFile(Index:Integer, image:Timage);


Aya - Fr 15.08.03 20:15

Hi,

dashier ist keine lösung für dein Problem, sondern nur nen kleiner logik anschub.

Ich hab bei dem Spiel woran ich arbeite auch etwas ähnliches, nämlich eine riesen große datei in der ALLE Texturen des Spieles gespeichert sind.

Das ganze funktioniert mit 2 Dateien, so:
Datei 1 ist die "Textures.tlft" die aus ganz vielen solchen blöcken besteht:
-----
Name der Textur (32 Zeichen = 32 Byte)
Typ der Textur (3 Zeichen, also JPG, TGA, BMP was weiß ich..)
Größe der Textur (1 Integer)
Die Textur selbst.
-----

So.. jetzt hab ich da also ganz viele solche blöche hintereinander... was wenn ich jetzt Textur nummer 471 haben will...? Dann müßte ich erstmal 470 andere Texturen durchlaufen um rauszufinden wo Textur 471 beginnt, ja???

Deswegen gibt es bei mir noch die Textures.tlfi. Eine Indexdatei in der jeder anfang eines TextureBlockes einfach als Integer gespeichert ist. Also ganz viele Integer Zahlen aneinandergereiht.

Wenn ich jetzt also Textur nr 471 Laden möchte, öffne ich die Textures.tlfi (die Index Datei) und springe an die Position 471 * 4. Denn ein Integer ist 4 Byte groß und jede Textur belegt in dieser datei einen Integer.

Dort angekommen (also nach einer Zeile -> Seek(471 * 4,soFromBeginning) ) lese ich einen Integer wert aus und speichere mir den in ner Variable (var StartPos: Integer).

Nun kann ich diese Datei wieder zumachen.

Als nächstes öffne ich nun die Textures.tlft und springe dort an die stelle, die ich eben ermittelt habe, zur StartPos.


Delphi-Quelltext
1:
Stream.Seek(StartPos,soFromBeginning);                    


hier angekommen lese ich nun 32 Byte für den Namen und 3 Byte für den Typ aus. Als nächtes lese ich die größe der Textur aus (Also 4 Byte = 1 Integer) und speichere das in sagen wir 'FileSize'.

Nun mache ich mir einen 2ten Stream und kopiere die menge "FileSize" aus Stream 1 dort hinein:


Delphi-Quelltext
1:
Stream2.CopyFrom(Stream1,FileSize);                    


da der Zeiger in Stream2 nun am ende steht, setze ich ihn nun wieder an den anfang:


Delphi-Quelltext
1:
Stream2.Seek(0,soFromBeginning);                    


Jetzt noch die Textur aus Stream2 laden, bzw in deinem fall das bitmap daraus laden und fertig ist's.

So, das war jetzt nicht DIE lösung für dein Problem, aber evtl hat es dir ein wenig geholfen es zuverstehen....

Au'revoir,
Aya~


mimi - Fr 15.08.03 20:55

das problem hat sich gelöst, danke..... aber deine lösung mit der index datei ist auch blöd finde ich.
ich habe es jetzt so gemacht:
es wird erst der gesammt heder in einer TStringList gespeichert und wenn ich jetzt die 2 texture haben will ist es ja sehr leicht diese zu laden oder ?
heute habe ich noch das hinzufügen von dateien in einem arichv hinbekommen *g*.
dachte nicht das ist so einfach ist mit TFileStream umzugehen *angeben*


Aya - Fr 15.08.03 20:57

Hi,

mimi hat folgendes geschrieben:
das problem hat sich gelöst, danke..... aber deine lösung mit der index datei ist auch blöd finde ich.

Wieso?

mimi hat folgendes geschrieben:
es wird erst der gesammt heder in einer TStringList gespeichert und wenn ich jetzt die 2 texture haben will ist es ja sehr leicht diese zu laden oder ?

*schaut verwirrt* Erklär mal genauer ;)

Au'revoir,
Aya~


mimi - Fr 15.08.03 21:47

das mit der index datei finde ich deshlab blöd weil du dann zwei dateien hast !

ich kann nicht gut erklären, deshlab poste ich am besten gleich den die funktion:

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:
function TMFA.ExtractFileHeader(Index:Integer):TDateiInfo;  // List den datei header hast
var
  DateiA:TStream;
  DateiAW:TReader;
  DateiSignatur:String;
  i,len:Integer;
begin
  DateiA:=TFileStream.Create(FileName, fmOpenRead);
  DateiHeader:=TStringList.Create;

  DateiAW:=TReader.Create(DateiA, 100);
  DateiSignatur:=DateiAW.ReadStr;

  if DateiSignatur <> FileDes then begin
    ShowMessage('Es ist keine FileArichver Datei !');
    Exit;
  end;
  len:=DateiAW.ReadInteger;
  DateiCount:=Len;

  for i:=0 to Len do DateiHeader.Add(DateiAW.ReadStr);
  
  result.FileName:=GetToken(DateiHeader.Strings[Index],';',1);
  result.CreateDateTime:=StrToDateTime(GetToken(DateiHeader.Strings[Index],';',2));
  result.usesDateTime:=StrToDateTime(GetToken(DateiHeader.Strings[Index],';',3));
  result.WriteusesDateTime:=StrToDateTime(GetToken(DateiHeader.Strings[Index],';',4));
  result.FileSize:=StrToInt(GetToken(DateiHeader.Strings[Index],';',5));
  result.FileDes:=GetToken(DateiHeader.Strings[Index],';',6);
  result.APos:=DateiAW.position + StrToInt(GetToken(DateiHeader.Strings[Index],';',7));
  result.Kometare:=GetToken(DateiHeader.Strings[Index],';',8);

  DateiA.Free;
end;

diesse funktion holt mir nur den datei header aus der datei geschrieben wird wo andras:)


Aya - Fr 15.08.03 21:56

mimi hat folgendes geschrieben:
das mit der index datei finde ich deshlab blöd weil du dann zwei dateien hast !

Sicher, man könnte die Indexe auch direkt in die tlft datei an den anfang schreiben.. aber das ist geschmackssache.

Ausserdem möchte ich bei mir auch immermal wissen wieviele Texturen in der Datei sind... so kann ich einfach die FileSize der Index Datei durch 4 Teilen und hab das ergebniss... ansonsten müßte ich die anzahl der Dateien mit in der datei speichern (da ich ja sonst nich weiß wo der bereich der Index'e endet).

Au'revoir,
Aya


mimi - Fr 15.08.03 22:21

ich mach das so änlich:
ich schreibe zuerst meine datei signatur rein und anschließend die anzahl der dateien.


Keldorn - Sa 16.08.03 13:07

da sind wieder 3 Speicherlöcher drin:
mimi hat folgendes geschrieben:

function TMFA.ExtractFileHeader(Index:Integer):TDateiInfo;
...
DateiHeader:=TStringList.Create;

du erzeugst eine Stringlist und gibst die nie wieder frei.

Zitat:

...
DateiAW:=TReader.Create(DateiA, 100);
...

du erzeugst ein Treader, gibst es aber niemals frei. hier wäre ich mir auch nicht sicher, ob du deswegen nicht auch Probleme bekommst, weil beim destroy evtl noch die Position im stream geändert wird.

Zitat:

...
if DateiSignatur <> FileDes then begin
ShowMessage('Es ist keine FileArichver Datei !');
Exit;
end;

wenn die if-bedingung wahr wird, wird der Stream nie geschlossen, da das dateiA.free nie erreicht wird.

wenn du Probleme damit hast Suche in: Delphi-Forum, Delphi-Library MEMPROOF Suche bei Google MEMPROOF diese Programm hilft dir dabei, Speicherlecks zu finden.

Den Umweg über die Stringlist bräuchtest du auch nicht. Treader/Twriter stellt dir schon _Methoden zur Verfügung, mit der du Integer, Strings, Dates etc in einen Stream schreiben/lesen kannst

Mfg Frank


mimi - Sa 16.08.03 14:13

doch ich brauche den umweg über die TStringliste weil:
ich sonst nicht auf heder eintrag z.b. 2 zugreifen kann. das ist nämlich so am einfachsten:)