Entwickler-Ecke

Internet / Netzwerk - Speicherfresser TWebBrowser¨?


sky21 - Fr 15.09.06 17:19
Titel: Speicherfresser TWebBrowser¨?
Hallo

Ich habe eine Instanz von TWebBrowser welche eine URL laden soll. Dieser Prozess wird N mal wiederholt. Der "Test" und damit auch die Instanz wird jedoch immer wieder neu erzeugt. Mit .free gebe ich natürlich die Instanz vorher wieder frei.

Trotzdem, meine Applikation frisst mächtig Speicher... immer und immer wieder und ich habe echt keine Ahnung, was los ist!

Interessant ist vielleicht dieses da:


Delphi-Quelltext
1:
2:
FIEBrowser := TWebBrowser.Create(Mainform.tsWebBrowser);
FIEBrowser.HostDockSite := MainForm.tsWebBrowser;


Die "View" ist eingebettet.. d.h. die HTML seite wird dann im Sheet angezeigt. Kann es nun sein, dass dafür extra Speicher gebraucht wird und dann nicht mehr freigegeben wird? Wenn ja, wie kann ich den Speicher wieder freigeben?

Danke für hilfreiche Antworten


MrSaint - Fr 15.09.06 19:59

Hallo!
Wozu brauchst du das HostDockSite?

user profile iconsky21 hat folgendes geschrieben:
Die "View" ist eingebettet.. d.h. die HTML seite wird dann im Sheet angezeigt.

Bitte :?:



MrSaint


sky21 - Fr 15.09.06 20:07

user profile iconMrSaint hat folgendes geschrieben:
Hallo!
Wozu brauchst du das HostDockSite?

user profile iconsky21 hat folgendes geschrieben:
Die "View" ist eingebettet.. d.h. die HTML seite wird dann im Sheet angezeigt.

Bitte :?:

MrSaint


Damit die View dann eine von mir vorgegebene Grösse hat. (?). Habe nicht wirklich eine Erklärung dafür - ist nicht mein Code - muss ihn leider nur warten und den Memoryfehler beheben. Haste eine Idee woran das Speicherproblem liegen könnte?


MrSaint - Fr 15.09.06 20:23

Mir fehlt eine Definition der Begriffe "View" und "Sheet"...
Das HostDockSite würde ich persönlich durch "Parent" ersetzen... Okay, "Sheet" könnte ein TabSheet sein... Dann hast du den Webbrowser also in einem TabSheet. Okay. Aber warum erzeugst du ihn immer neu? reicht es nicht, den Webbrowser einmal in der IDE auf das TabSheet zu pflanzen und dann immer mit dieser einen Instanz zu arbeiten?
Dass der Webbrowser viel Speicher frisst, den er u.U. nicht mehr komplett freigibt könnte schon passieren... Das ist ja ein "Fremdprodukt", von dem du nicht genau weißt, wie sauber es geschrieben wurde (das ist im Endeffekt ja der IE von Microsoft, den du in deiner Anwendung hostest...).



MrSaint


sky21 - Mo 18.09.06 08:14

Hallo MrSaint

Korrekt, es ist ein TabSheet, welches die HTML Seite anzeigt. Kann dort nun gerade das Speicherproblem liegen? Diese wird ja immer nur aktualisiert, jedoch nie zerstört (während der Laufzeit).

Mit 'View' habe ich übrigends nur die darstellende Komponente gemeint. In meinem konkreten fall ist dies eben ein Tabsheet.


wulfskin - Mo 18.09.06 08:54

user profile iconsky21 hat folgendes geschrieben:
Korrekt, es ist ein TabSheet, welches die HTML Seite anzeigt. Kann dort nun gerade das Speicherproblem liegen? Diese wird ja immer nur aktualisiert, jedoch nie zerstört (während der Laufzeit).
Hallo,

das wird wohl das Problem sein. Wenn du die Objekte, die du zur Laufzeit erstellst, nicht während dieser freigibst, benötigt dein Programm unnötig viel Speicher. Was spricht dagegen, wie von MrSaint vorgeschlagen, nur eine Komponente zu benutzen? Ansonsten musst du dir die Komponenten wohl in einer globalen Variable merken und entsprechend freigeben!
Auch wenn Windows den Speicher am Programmende freigibt, gilt Grundsätzlich (ausgenommen DLL-Handles), dass man diese selber freigeben sollte, wenn man sie nicht mehr benötigt!

Gruß Hape!


sky21 - Mo 18.09.06 09:19

user profile iconwulfskin hat folgendes geschrieben:
user profile iconsky21 hat folgendes geschrieben:
Korrekt, es ist ein TabSheet, welches die HTML Seite anzeigt. Kann dort nun gerade das Speicherproblem liegen? Diese wird ja immer nur aktualisiert, jedoch nie zerstört (während der Laufzeit).
Hallo,

das wird wohl das Problem sein. Wenn du die Objekte, die du zur Laufzeit erstellst, nicht während dieser freigibst, benötigt dein Programm unnötig viel Speicher. Was spricht dagegen, wie von MrSaint vorgeschlagen, nur eine Komponente zu benutzen? Ansonsten musst du dir die Komponenten wohl in einer globalen Variable merken und entsprechend freigeben!
Auch wenn Windows den Speicher am Programmende freigibt, gilt Grundsätzlich (ausgenommen DLL-Handles), dass man diese selber freigeben sollte, wenn man sie nicht mehr benötigt!

Gruß Hape!


Ja aber, ....
tsWebBrowser (meine darstellende Komponente) ist ein TTabSheet Das TabSheet wiederum ist eine VCL Komponente und gehört zu meiner MainForm. tsWebBrowser wird weder von mir (also mittels eigenem code) erstellt noch freigegeben. Aufgrund meines minimalistischen Delphikenntnisse behaupte ich nun, dass das GUI (also FormCreate/FormDestroy) diese Tätigkeiten für mich erledigt. Was passiert nun? Ich binde die WebBroweserinstanz an das TabSheet, lade eine komplette Hompage (HTML mit Bilder, ...) herunter. Der benötigte Speicher (sichtbar im Windows Task Manager wächst an). Die Files sind im Cache des IE - die Anzeige, also das TabSheet muss die heruntergeladenen Komponenten anzeigen. Ich nehme jetzt mal an, dass deshalb der Speicher angewachsen ist. So, ich lösche jetzt wieder den Cache und fange nochmals von vorne an (mit herunterladen einer Seite). Der Speicher wächst weiter. What the hell? Der IE kann ja schliesslich auch nicht nur EINE Seite herunterladen und dann ist fertig. hihi. Muss ich irgendwie den Content (aber wie?) im TabSheet zurücksetzen?

Hoffe auf Hilfe!

Edit:
Ich glaube, ich muss einfach auf irgend eine Weise den vom tsWebBrowser (TTabsheet) benötigten Speicher zur Anzeige der Daten wieder freigeben können.... wer weiss Rat?


alias5000 - Mo 18.09.06 11:56

Hallo,

könntest du uns bitte den relevanten Code der Komponente zeigen, der dieses verhalten verursachen könnte? (Zur not halt den ganzen, aber bitte kürze das raus, was absolut nicht relevant ist.

Gruß alias5000


sky21 - Mo 18.09.06 17:47

So, nach etlichen Stunden habe ich das Problem gefunden. Einen Workaround habe ich leider nicht... also hier kommts:


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:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
procedure MyBrowser.BrowserDocumentComplete(Sender: TObject;
  const pDisp: IDispatch; var URL: OleVariant);

var numOfImages, numOfFrames : integer;
    endTime : TDateTime;
    tp : integer;
    doc : IHTMLDocument2;

    title : string;
    testsize : integer;
    retval: bool;
    size : Cardinal;
    browserControlInterface: IDispatch;
    NewURL : OleVariant;
begin

  //stop eventhandling
    FIEBrowser.OnDocumentComplete := nil;
    FIEBrowser.OnNewWindow2 := nil;
    FIEBrowser.OnNavigateError := nil;


  numOfImages := 0;
  numOfFrames := 0;
  size := 0;

 

  try
    // HIER heftiges Problem!!!!
    doc := IHTMLDocument2(FIEBrowser.Document);
  except
    // escpation wird keine geworfen!
  end;

  //get number of frames/images
  try
    CalcSizeOfFrame(doc, testsize, numOfImages, numOfFrames);
    except
    end;
  end;

  // release the interface
  doc := nil;

  LogInfo('Size: ' + IntToStr(size) + ' Bytes');
end;

// recursive function to travel through the HTML document and count bytes,
// images and frames
procedure MyBrowser.CalcSizeOfFrame(htmlDocument: IHTMLDocument2;
                             var totalBytes, numOfImages, numOfFrames: integer);
var imageCollection: IHTMLElementCollection;
    image: HTMLImg;
    currentFrame, currentImage: integer;
    frameCollection: IHTMLFramesCollection2;
    frame: IHTMLDocument2;
    currentFrameVariant1, currentFrameVariant2: OleVariant;
    htmlWindow: IHTMLWindow2;

begin
  // check for frames
  frameCollection := IHTMLFramesCollection2(htmlDocument.frames);

    // for every frame
    for currentFrame := 0 to frameCollection.Length - 1 do
    begin
      currentFrameVariant1 := currentFrame;
      currentFrameVariant2 := frameCollection.item(currentFrameVariant1);
      // a frame is embedded in a IHTMLWindow2
      IDispatch(currentFrameVariant2).QueryInterface(IID_IHTMLWindow2,
                                                     htmlWindow);
      if htmlWindow <> nil then
      begin
        try
          frame := IHTMLDocument2(htmlWindow.document);
          numOfFrames := numOfFrames + 1;
          // restart (recursiv) with new document
          CalcSizeOfFrame(frame, totalBytes, numOfImages, numOfFrames);
        except
        end;
      end;
    end;

    // check for images
    imageCollection := IHTMLElementCollection(htmlDocument. images);
    for currentImage := 0 to imageCollection.length - 1 do
    begin
      image := HTMLImg(imageCollection.Item(currentImage, varEmpty));
      // add size of image, if a new one
      if FImgList.IndexOf(image.nameProp) = -1 then
      begin
        totalBytes := totalBytes + StrToIntDef(image.filesize, 0);
        FImgList.Add(image.nameProp);
      end;
      numOfImages := numOfImages + 1;
    end;

    frame := nil;

    currentImage:=nil;
end;


Das Hauptproblem liegt bei 'doc'. Wenn ich dieses zuweise, und nach (oder auch gleich sofort) wieder auf nil setze, dann scheint der Speicher nicht freigegeben zu werden. Dumm nur, ich benötige das doc um die Funktion 'CalcSizeOfFrame' aufzurufen.


alias5000 - Mo 18.09.06 18:28

hmmm, aber eigentlich erstellst du doch gar keine neue Instanz, oder? Du benutzt doch nur das Webbrowser.Document, was bereits im Speicher erzeugt ist. Und mit der nil-Zuweisung setzt du doch auch nur einen Pointer auf nil, das Objekt wird nicht aus dem speicher gelöscht (was ja auch fatal wäre, da es zum WB gehört).


MrSaint - Mo 18.09.06 18:55

Falsch. doc ist ein Interface. Das wird automatisch zerstört, wenn es nichtmehr benötigt wird (also alle Variablen, die es benutzen auf nil gesetzt wurden). Der obige Code ist absolut in Ordnung, das geht nicht anders. versichere dich lieber, dass auch das FIEBrowser.Free aufgerufen wird (mit Breakpoints).


MrSaint


sky21 - Mo 18.09.06 20:49

Das Property "Document" gibt ein Interface zurück, welches man nicht explizit zerstören muss. D.h. doch, man muss einfach das Objekt (bei mir 'doc') auf nil setzen, dann wird es autmatisch freigegeben (aufgrund des internen reference counters oder so ähnlich).

Die Instanz FIEBrowser wird freigegeben. Hab ich schon im Debugger gecheked und gebe im Log auch eine Messagebox aus. Anderer Ansatz: Kann es eine Fehlimplementiertung in Delphi sein? (Benutzer Delphi v2005). Oder ist mein Datentyp 'doc' einfach sh**?

Interessanterweise sehe ich auch nichts mit dem FastMemoryManager, welcher eigentlich Memoryleaks finden sollte... und trotzdem. Das dingt stirbt mir weg, wenn kein Speicher mehr verfügbar ist.