Form zur Laufzeit erstellen aber vorher schon designen?
-
Hallo Gemeinschaft,
entschuldigt die Überschrift, ich weiß nich genau wie ich's kurz fassen soll.
Ich designe zur Entwurfszeit ein Formular und füge Methoden und Steuerelemente hinzu. Wenn ich das Programm übersetze und starte, werden ja zB. globale und Membervariablen aller Formulare im Speicher angelegt. Nun habe ich aber ziemlich viele Formulare, die nicht immer benötigt werden. Auch sind diese Formulare und ihre Methoden recht "filigran", so dass ich sie nicht zur Laufzeit erstellen kann / will.
Gibt es einen Weg ein Formular erst in den Speicher zu laden, wenn ich es benötige?
Folgendes habe ich versucht:Ich habe mal das Application->FormCreate eines Formulars (mit try-catch zum exceptions Abfangen) in das OnClick eines Buttons geschoben.
In die OnClose-Methode des Formulars habe ich Action= caFree; eingefügt. Ich habe mir gedacht, dann wird das Formular erst auf Knopfdruck in den Speicher geladen und nach dem Schliessen wieder daraus entfernt.
Leider ändert sich der Speicherbedarf nicht bzw extrem minimal (lt Taskmanager). Sind nur 40K bei 8100K Gesamtbedarf.
Ich habe auch versucht 50% der Formulare wie beschrieben zu behandeln, jedoch änderte sich der Speicherbedarf kaum. Mach ich was falsch?Was macht den Löwenanteil an Speicherbedarf aus? Wie kann ich ihn reduzieren?
-
Projekt->Optionen->Formulare
Dort das Formular aus den automatisch erstellten entfernen.
-
Kommt das nicht aufs Selbe raus, als wenn ich FormCreate nicht beim Programmstart, sondern erst auf Knopfdruck aufrufe? Ich probiers gleich mal aus ...
-
Kolumbus schrieb:
Kommt das nicht aufs Selbe raus, als wenn ich FormCreate nicht beim Programmstart, sondern erst auf Knopfdruck aufrufe? Ich probiers gleich mal aus ...
Nein. Die Form wird trotzdem instanziert.
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { try { Application->Initialize(); Application->CreateForm(__classid(TForm1), &Form1); Application->CreateForm(__classid(TForm2), &Form2); Application->Run(); }
-
instanzer schrieb:
Kolumbus schrieb:
Kommt das nicht aufs Selbe raus, als wenn ich FormCreate nicht beim Programmstart, sondern erst auf Knopfdruck aufrufe? Ich probiers gleich mal aus ...
Nein. Die Form wird trotzdem instanziert.
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { try { Application->Initialize(); Application->CreateForm(__classid(TForm1), &Form1); Application->CreateForm(__classid(TForm2), &Form2); Application->Run(); }Ich habs aber so gemacht:
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { try { Application->Initialize(); Application->CreateForm(__classid(TForm1), &Form1); // Application->CreateForm(__classid(TForm2), &Form2); Application->Run(); }void __fastcall TForm1::Button1Click(TObject *Sender) { try { Application->CreateForm(__classid(TForm2), &Form2); } catch { //... } Form2->ShowModal(); }Das ist doch ein Unterschied oder nicht?

-
Hallo
Änderungen an der Hauptprojektdatei (die mit der WinMain) solltest du nur über die IDE machen, solange du nicht weißt was du da tust. Also benutzt die bereits angesprochene Option um das Formular auszuschalten.
Auch die dynamische Erzeugung ist so besser da standardkonformer
Form2 = new TForm2(Application);Wenn du mit dynamischen Forms arbeitest solltest du unbedingt vermeiden den globalen Pointer Form2 zu verwenden, sondern immer nur eigene lokale.
bis bald
akari
-
Guten Morgen,
hmm... Danke für die Tips akari.
akari schrieb:
Änderungen an der Hauptprojektdatei (die mit der WinMain) solltest du nur über die IDE machen, solange du nicht weißt was du da tust. Also benutzt die bereits angesprochene Option um das Formular auszuschalten.
Ich verstehe allerdings nicht, warum ich das nicht so machen kann, wie ich zuletzt gepostet habe!?! Ob ich das "CreateForm" nun selber auskommentiere oder es über die Optionen entferne - was macht das für einen Unterschied??? Für mich im 1. Moment keinen... ! Natürlich deaktiviere ich es über die Optionen, ist ja sauberer - was es für einen Unterschied macht interesiert mich trotzdem!
(Und der rebellische Gedanke dazu: Obs jetzt die Hauptprojektdatei ist oder nicht... ist doch keine Hexerei und auch nicht der Kaiser von China, also hab ich auch keine Angst davor.
)akari schrieb:
Auch die dynamische Erzeugung ist so besser da standardkonformer
Form2 = new TForm2(Application);Wenn du sagst ich solle das Formular(Objekt) als Instanz von TForm2 dynamisch am Besten mit new erstellen, dann mach ich das doch so! Ich machs trotzdem in try-catch oder reicht auch eine Überprüfung ob es geklappt hat? So etwa:
TForm2 *TestForm= new TForm2(Application) if(TestForm) { TestForm->ShowModal(); }Was bewirkt der Initialisierer "Application"? Ist das dann der Parent oder der Owner??
Dann muss ich's hinterher mit delete wieder löschen, right? So etwa:delete(TestForm);akari schrieb:
Wenn du mit dynamischen Forms arbeitest solltest du unbedingt vermeiden den globalen Pointer Form2 zu verwenden, sondern immer nur eigene lokale.
Das heißt, ich nenn das Ding nicht Form2, sondern zum Beispiel TestForm, right?
-
So,
habs jetzt mal für mehrere Formulare getestet und es funktioniert:
void __fastcall TForm1::BtnShowForm2Click(TObject *Sender) { TForm2 *Frm2= new TForm2(Application); if(Frm2) Frm2->ShowModal(); delete(Frm2); } //---------------------------------------------------------------------------Es gibt allerdings ein Problem:
Ich starte das Programm (aus dem BCB3 heraus mit F9) und der Taskmanager zeigt mir 8248k Speicherbedarf für den Prozess an. Dann klicke ich auf einen Menüpunkt des Programms und Form1 wird angezeigt (Form1 wird beim Programmstart erstellt) - Speicherbedarf jetzt: 8648k.
Nun drücke ich auf BtnShowForm2 und obiger Code wird ausgeführt: Frm2 wird angezeigt - Speicherbedarf nun 8860k. Wenn ich Frm2 schliesse, wird Frm2 gelöscht (siehe Code) - Speicherbedarf ist aber noch bei 8832k statt bei den vorherigen 8648k???
Nach mehrmaligem Erstellen, Anzeigen und Löschen von Frm2, bleibt der Speicherbedarf immer bei 8848k.Ich habe das Gefühl, dass das erstellte Objekt nicht vollständig gelöscht und freigegeben wird, kann das sein?
-
Bei allen GUI Klassen existiert ein Konstruktor in der Form TClassXY( TComponent* Owner ). Dieser Owner ist für die korrekte Zerstörung des Objekts verantwortlich, falls es der Programmierer nicht vorher schon wieder abräumt.
void __fastcall OnSomeButtonClick( TObject* Sender ) { TSomeForm* pForm = new TSomeForm( Application ); pForm->ShowModal(); }Beim oben gezeigten wird ein neues Formular erzeugt, dessen Besitzer das Application Objekt ist. Sobald die Anwendung beendet wird (bzw. das Application Objekt zerstört wird), zerstört das Application Objekt sämtliche Objekte, die es besitzt.
Auf den ersten Blick hört sich das vernünftig an, aber bei häufigem Aufruf der Funktion werden immer wieder neue Formular Objekte erzeugt, die erst bei Zerstörung des Application Objekts wieder freigegeben werden. Praktisch betrachtet ist das ein Speicherleck.
Vernünftigerweise packt man modale Formulare in einen shared_ptr oder scoped_ptr, wenn man die boost Library nicht hat kann man auch den auto_ptr aus der STL benutzen:void __fastcall OnSomeButtonClick( TObject* Sender ) { boost::scoped_ptr<TSomeForm> pForm( new TSomeForm( NULL ) ); // oder boost::shared_ptr<TSomeForm> pForm( new TSomeForm( NULL ) ); // oder std::auto_ptr<TSomeForm> pForm( new TSomeForm( NULL ) ); pForm->ShowModal(); }Die Kapselung in einen Smart Pointer hat den Vorteil, dass das erzeugt Objekt in jedem Fall wieder freigegeben wird, selbst wenn pForm->ShowModal() eine Exception erzeugt.
Gruß,
Doc
-
Nachtrag:
Das Vorgehen in der Art
void __fastcall SomeForm::OnSomeButtonClick( TObject* Sender ) { TSomeOtherForm* pForm = new TSomeOtherForm( SomeOwner ); pForm->ShowModal(); delete pForm; }ist prinzipiell fehleranfällig. Wenn pForm->ShowModal() eine Exception wirft und die Methode SomeForm::OnSomeButtonClick verlassen wird, wird der Aufruf delete pForm niemals ausgeführt -> Speicherleck. Um solches Verhalten zu vermeiden sollte man dynamische Ressource immer in Holder Objekte kapseln, die die Ressource z.B. im Konstruktor anfordern und im Destruktor wieder freigeben. Damit steht man immer auf der sicheren Seite und bekommt robustere Anwendungen.
Gruß,
Doc/Edit akari : Codetags korrigiert
-
Hallo
@ DocShoe : Verwende doch die Cpp-Tags anstelle der Code-Tages
Dann hast du auch Syntaxhighlightning.bis bald
akari
-
@DocShoe: Danke für deine Vorschläge, aber könntest du dich bitte mal auf den von mir zuletzt geposteten Code beziehen? Alle deiner Ausführungen lassen irgendwo die Eine oder Andere Kleinigkeit aus meinem Codesschnipsel weg, was meiner Meinung nach zu ganz anderen Ergebnissen führt... Deswegen kann ich mit Deinen Vorschlägen auch nicht viel anfangen!
1. Ich zerstöre das Objekt selbst:
delete(Frm2);Meine Frage dazu war nun eigentlich, ob es sein kann dass der Speicher trotz delete() nicht vollständig freigegeben wird und warum?
2. Ich rufe das Objekt nur auf, wenn es erstellt werden konnte:
if(Frm2) Frm2->ShowModal();Kann es beim Aufruf ->ShowModal trotzdem noch zu exceptions kommen? Wenn ja warum bzw wie?
-
Hallo
Ein delete gibt den Speicher immer komplett frei. Aus C++ Sicht kannst du nicht mehr machen
Das if ist überflüssig. Ein Pointer weiß nicht ob er auf eine gültige, nicht gelöschte Instanz zeigt. Eine if-Anweisung auf einen Pointer veegleicht nur ob der Pointer eine Adresse ungleich 0 bzw. NULL hat. Da aber delete auf 0 erlaubt ist ist die manuelle if-Anweisung überflüssig.
Irgendwie verstehe ich dein Problem nicht. Da beim mehrmaligen erstellen und löschen nicht jeweils mehr Speicher von Windows angefordert wird kann ein Speicherloch durch C++ ausgeschlossen werden. Das die Speicherbelegung beim Löschen nicht wieder gesenkt wird wird an vorausschauendem Speichermanagement von Windows und/oder C++ liegen. Ist aber auch kein Grund zur Sorge.
bis bald
akari
-
akari schrieb:
Ein delete gibt den Speicher immer komplett frei. Aus C++ Sicht kannst du nicht mehr machen
Gut.

akari schrieb:
Das if ist überflüssig. Ein Pointer weiß nicht ob er auf eine gültige, nicht gelöschte Instanz zeigt. Eine if-Anweisung auf einen Pointer veegleicht nur ob der Pointer eine Adresse ungleich 0 bzw. NULL hat. Da aber delete auf 0 erlaubt ist ist die manuelle if-Anweisung überflüssig.
Hm, irgendwie hatte ich mir das schon gedacht, dass ich damit nur den Zeiger prüfe. Aber schön es bestätigt zu bekommen. Also dann doch try-catch oder DocShoes-Methode?
akari schrieb:
Irgendwie verstehe ich dein Problem nicht. Da beim mehrmaligen erstellen und löschen nicht jeweils mehr Speicher von Windows angefordert wird kann ein Speicherloch durch C++ ausgeschlossen werden. Das die Speicherbelegung beim Löschen nicht wieder gesenkt wird wird an vorausschauendem Speichermanagement von Windows und/oder C++ liegen. Ist aber auch kein Grund zur Sorge.
Fein! Super! Das wollte ich wissen! Dann gibt es auch kein Problem das du verstehen kannst, dann gibt es nämlich überhaupt kein Problem.

-
Hallo,
mit meinem Post wollte ich auf das RAII Prinzip hinweisen, das man überall dort benutzen sollte, wo Ressourcen dynamisch angefordert und auch wieder freigegeben werden müssen. Im Beispiel mit den Formularen von oben mag das zwar nicht direkt zutreffend sein, aber zu einem guten Programmierstil gehört m.E. eine konsequente Umsetzung von Prinzipien.
Hier ein kleines Beispiel:void func() { // Ressource dynamisch anfordern SomeResource* pRes = new SomeResource(); // irgendetwas mit der Ressource anfangen do_something( pRes ); // Ressource wieder freigeben delete pRes; }Solange kein Fehler auftritt ist alles wunderbar, die Ressource wird angefordert, benutzt und wieder freigegeben. Wenn die Funktion do_something() aus irgendwelchen Gründen, warum auch immer, eine Exception auslöst haben wir allerdings ein Problem. Da sie sich in keinem try-catch Block befindet wird die Exception nicht lokal behandelt und im Call Stack weiter nach oben gereicht. Das heisst, dass die Zeile delete pRes niemals aufgerufen wird.
Lösung 1: Aufruf in try-catch Blöcke fassenvoid func() { try { // Ressource dynamisch anfordern SomeResource* pRes = new SomeResource(); // irgendetwas mit der Ressource anfangen do_something( pRes ); // Ressource wieder freigeben delete pRes; } catch( ... ) { // Ressource freigeben delete pRes; // Exception erneut werfen throw; } }Funktioniert, ist aber unschön und wird zunehmend aufwändiger, falls mehrere dynamische Ressourcen benötigt werden.
Lösung 2: Benutzung von Holder Objekten
class ResourceHolder { SomeResource* m_pResource; public: ResourceHolder( SomeResource* pResource ) : m_pResource( pResource ) { } ~ResourceHolder() { delete m_pResource; } SomeResource* get() const { return m_pResource; } }; void func() { // Ressource Holder bekommt Pointer auf dynamisch angefordere Ressource ResourceHolder Res( new SomeResource() ); // irgendwas mit der Ressource anstellen do_something( Res.get() ); }Fertig. Wir müssen uns um die Freigabe der Ressource nicht mehr selbst kümmern, da sobald das Holder Objekt abgeräumt wird es die dynamisch angeforderte Ressource freigibt. Dabei ist es egal, ob die Funktion eine Exception wirft oder regulär verlassen wird, sobald das Holder Objekt seinen scope verlässt wird es zerstört und gibt die Ressource frei.
So Dinge wie Copy Construction, Assignment und shared Ressource behandelt mein kleines Beispiel nicht, für diese Anwendungen ist boost::shared_ptr Maß der Dinge. Wer die boost Bibliothek nicht einsetzen will oder kann hat immer noch die Option, auto_ptr aus der STL einzusetzen. Leute, die auch das nicht können oder wollen sei dieses Template an´s Herz gelegt. Allen anderen... nunja
template<typename T> class Holder { T* m_pResource; public: explicit Holder( T* pRes ) : m_pResource( pRes ) { } ~Holder() { delete m_pResource; } operator T() { return m_pResource; } private: Holder( const T* ); Holder& operator=( const T* ); };Gruß,
Doc
-
Nachtrag zur Holder Klasse, bitte um diese beiden Methoden ergänzen
T* operator->() { return m_pResource; } T& operator*() { return *m_pResource; }