Adressen verlieren ihre Gültigkeit -> bsp. TButton-Array (zur Laufzeit erzeugt) -> ERLEDIGT



  • Hallo Zusammen

    Einführung:
    Als ich meine Anwendung auf die Mehrsprachigkeit auslegen wollte, sind technische Probleme aufgetreten, welche ich mir bis jetzt nicht erklären kann.

    Bei dieser Mehrsprachigkeit, wurden die Bezeichnungen verschiedener Labels in Ressourcendateien verpackt. Sobald die Sprache geändert wird, werden diese Bezeichnungen zur Laufzeit geladen und die entsprechenden Formelemente angepasst.

    Code-Aufbau:

    • Ich habe ein Struct "lineSettings" definiert, welches aus mehreren Komponenten-Arrays besteht.
    //Datei - Main.h
    struct lineSettings{
    	TLabel *lblLineNumber[SETTINGS_SELF_DEFINED_LINES];
    
    	TLabel *lblLineTop[SETTINGS_SELF_DEFINED_LINES];
    	TLabel *lblLineWidth[SETTINGS_SELF_DEFINED_LINES];
    	TLabel *lblLineHeight[SETTINGS_SELF_DEFINED_LINES];
    	TLabel *lblLineLeft[SETTINGS_SELF_DEFINED_LINES];
    	TLabel *lblLineColor[SETTINGS_SELF_DEFINED_LINES];
    
    	TSpinEdit *txtLineLeft[SETTINGS_SELF_DEFINED_LINES];
    	TSpinEdit *txtLineTop[SETTINGS_SELF_DEFINED_LINES];
    	TSpinEdit *txtLineWidth[SETTINGS_SELF_DEFINED_LINES];
    	TSpinEdit *txtLineHeight[SETTINGS_SELF_DEFINED_LINES];
    
    	TCheckBox *chkLineEnabled[SETTINGS_SELF_DEFINED_LINES];
    
    	TColorBox *cbLineColor[SETTINGS_SELF_DEFINED_LINES];
    };
    
    • Die Objekte werden im Konstruktor des Hauptformulars initialisiert
    //Datei - Main.cpp // Funktion: InitFormElements()
    //Generieren der dynamischen Felder für die Liniendefinitionen
    for (int i = 0; i < SETTINGS_SELF_DEFINED_LINES; i++) {
    	//Mainlabel
    	stLineSettings.lblLineNumber[i]=new TLabel(G19_ProcessListMainForm);
    	stLineSettings.lblLineNumber[i]->Parent=grpLines;
    	stLineSettings.lblLineNumber[i]->Name="lblLine" + IntToStr(i);
    	...
    	...
    

    Problem 1:
    Will ich nun die Bezeichnungen dieser erstellten Labels anpassen, so erfolgt dies gemäss hier, so erhalte ich beim Anpassen des zweitletzten Elements, eines Arrays, welches nicht als letztes initialisiert wurden, eine Zugriffsverletzung:

    //Datei - Main.cpp // Funktion: SetApplicationLanguage
    for (int i = 0; i < SETTINGS_SELF_DEFINED_LINES; i++) {
    	if (sComponentName==L"-SPEZ-lblLineNumber") {
    		stLineSettings.lblLineNumber[i]->Caption=sComponentValue + " " + IntToStr(i + 1);
    		stLineSettings.lblLineNumber[i]->Update();
    	}
    	...
    	...
    

    Bezüglich des Auftretens gibt es jedoch ein merkwürdigen Phenomen.
    Initialisiere ich die Labels lblLineLeft[ i ] in der Zeile 235 mit der Bezeichnung "Open", so tritt der Fehler nicht auf. Verändere ich jedoch die Position (X-Achse) oder die Bezeichnung (Caption) auf "X", so erscheint untenstehender Fehler.

    -> Fehlermeldung: "Access violation at address 21514D8D in module 'BORLANDMM.DLL'. Read of address 73656364."
    -> Fehlerzeile 1211: stLineSettings.lblLineTop[ i ]->Update();
    -> In der Zeile 1211: stLineSettings.lblLineTop[ i ]->Caption=...;
    --> Unter Caption dieses Elements wird auch ein falscher Wert angezeigt.

    Irgendwie scheint er dann die Adressen zu überschreiben oder für ungültig zu erklären.
    Klicke ich auf die Schaltfläche, dass er trotzdem weitermachen soll, so sehe ich nachher bei den Labels die er nicht mehr anpassen konnte chinesische Zeichen.

    Problem 2:
    Hier handelt es sich um einen ähnlichen Fehler. Dieser tritt jedoch unabhängig der Daten der Initialisierung auf (Problem 1).
    Zwar geht es hier um den Destruktor des Hauptformulars oder besser gesagt um das Schliessen der Applikation.
    Denn dort tritt beim löschen der erzeugten Instanzen der selbe Fehler auf:

    //Datei - Main.cpp // Funktion: ~TG19_ProcessListMainForm()
    for (int i = 0; i < SETTINGS_SELF_DEFINED_LINES; i++) {
    	delete stLineSettings.lblLineNumber[i];
    
    	delete stLineSettings.lblLineTop[i];
    	delete stLineSettings.lblLineWidth[i];
    	delete stLineSettings.lblLineHeight[i];
    	...
    	...
    

    -> Fehlermeldung:: "Im Projekt G19_Taskmanager.exe ist eine Exception der Klasse EAccessViolation mit der Meldung 'Access violation at address 21514D8D in module 'BORLNDMM.DLL'. Read of address 3B65411C' aufgetreten.

    Problem 3:
    Das dritte Problem ist nur halb so schlimm und konnte mit einer kleinen Unschönheit behoben werden.
    Alle Labels und Interaktionselemente der Applikation befinden sich auf Registerkarten.
    Passe ich diese Labels nun ungeachtet dieser dynamisch erstellten Problemelemente der Problembeschreibung 1 und 2 an, so erhalte ich beim eine Fehlermeldung im Sinne:
    -> Fehlermeldung: "Im Projekt G19_Taskmanager.exe ist eine Exception der Klasse EInvalidPointer mit der Meldung 'Invalid pointer operation' aufgetreten'

    ERKENNTNIS:
    1. Werden vorher alle Registerkarten einmal geöffnet, während die Applikation aktiv ist, so tritt dieser Fehler nicht auf.
    2. Wurde vorher keine weitere Registerkarte geöffnet, so friert die Applikation ein.
    3. Wurde vorher die zweite Registerkarte geöffnet, so kommt obige Fehlermeldung mit dem invaliden Pointer -> Selbe Position wie beim Problem 1

    Die Gründe kann ich mir nicht ganz erklären. Aus diesem Grund werden nun vor der Anpassung der Sprache alle Registerkarten durchgeschaltet.

    for (int i = 0; i < Taskmanaget_Settings_PageControl->PageCount; i++) {
    		Taskmanaget_Settings_PageControl->ActivePageIndex=Taskmanaget_Settings_PageControl->Pages[i]->PageIndex;
    		Application->ProcessMessages();
    	}
    	Taskmanaget_Settings_PageControl->ActivePageIndex=Taskmanaget_Settings_PageControl->Pages[0]->PageIndex;
    	Application->ProcessMessages();
    

    Applikationsbeschreibung:
    Kurze Nebeninformation:
    Bei dieser Anwendung handelt es sich um einen Taskmanager für die G19-Tastatur von Logitech.

    Problemnachvollzug:
    Oben genannte Probleme konnte ich mit dem "C++Builder 2010" und dem "C++Builder XE" nachvollziehen.
    Der Sourcecode des Projekts kann unter nachfolgendem Link herungergeladen werden, so dass auch ihr es testen könntet.
    http://rrworldfiles.foroomy.com/download/file.php?id=10

    Für die Probleme 1 und 3 habe ich entsprechende Variabeln deklariert, so dass diese durch ändern des Wertes Provoziert werden können.

    //DEBUG-VERSION für c-plusplus.net
    	G_bDevelopperMode=true;
    	G_bPROVOCATE_ERROR_NUMBER_1=false;
    	G_bDISABLE_ERROR_NUMBER_3=true;
    

    -> Sämmtliche Probleme treten in der Datei "Main.cpp" auf.

    Schlusswort:
    Ich möchte mich schon vielmals bei allen bedanken, die sich an dem Problem versuchen und vielleicht sogar einen Lösungsvorschlag haben.
    Ich habe schon mehrere Tage bis in die Nacht daran gesessen, konnte das Problem aber einfach noch nicht beheben 😕

    Statdessen habe ich es nur fertiggebracht, eine Version fertigzustellen, in denen die Fehler 1 und 3 nicht auftreten.
    Vor allem beim Problem 1 bin ich nur durch Zufall auf diese Konstellation gestossen, in der der Fehler nicht zum vorschein kommt.

    Trotzallem wünsche ich euch schöne und behagliche Weihnachten :xmas1:

    EDIT: Problemlösung
    Schuld waren nachfolgende Zeilen. Wie man sieht, waren daran zwei Klammern schuld.
    -> Was für ein EPIC-Fail

    //Alte defekte Version 
    i64BufferSize=resLanguages->Size; 
    char *wcharBuffer = new char(i64BufferSize); 
    resLanguages->ReadBuffer(wcharBuffer,i64BufferSize); 
    
    //Neue funktionierende Version 
    i64BufferSize=resLanguages->Size; 
    char *wcharBuffer = new char[i64BufferSize]; 
    resLanguages->ReadBuffer(wcharBuffer,i64BufferSize);
    


  • Du hast aller Wahrscheinlichkeit irgendwo einen "Speicherüberschreiber" drin, also schreibst außerhalb der Array-Grenzen und zerstörst dadurch die Zeiger.

    Ich würde auch eher die Struktur so anlegen

    TLabel *lblLineNumber;
    TLabel *lblLineTop;
    TLabel *lblLineWidth;
    TLabel *lblLineHeight;
    TLabel *lblLineLeft;
    TLabel *lblLineColor;
    
    TSpinEdit *txtLineLeft;
    ...
    

    und dann ein Array "stLineSettings[SETTINGS_SELF_DEFINED_LINES]" anlegen. Der Zugriff darauf ist dann auch etwas bequemer:

    lineSettings &lineSettings = stLineSettings[i];
    lineSettings->lblLineNumber = new TLabel(...);
    

    So brauchst du dann nicht jedesmal den Index anzugeben.

    (ich schaue mir dann auch mal dein Projekt an und geb dir später Bescheid, was ich sonst noch so finde -)

    Edit:
    So, ich habe mich jetzt mal durch dein Projekt "gequält" und kann aber auch einen direkten Speicherüberschreiber nicht finden (zumindestens bei den LineSettings nicht).
    Jedoch solltest du dringend an dem gesamten Design deines Programms arbeiten.
    Anstatt ein Mega-Formular mit -zig von Controls zu erstellen sowie Strukturen mit -zig von Membern, solltest du ersteinmal dein Programm "refactor"ieren und mittels Frames und Unterstukturen/Klassen ersteinmal Ordnung in dein Design bringen.

    Dann lassen sich auch Fehler einfacher finden, da man dann lokaler suchen kann...



  • Hallo Th69

    Vielen Dank für deinen Kommentar. 👍

    So, ich habe mich jetzt mal durch dein Projekt "gequält" und kann aber au

    Ich wusste nicht, dass es gleich so schlimm ist 😮

    Ich werde mir deinen Rat zu Gemüte führen und mich als nächstes um die Überarbeitung kümmern. 😉

    Speicherüberlauf
    Ich habe mir auch schon darüber Gedanken gemacht, dass es hier irgendwo happern könnte.
    Aus diesem Grund habe ich mal sämtliche Programmfunktionen stillgelegt.
    Das heisst, der Thread,... wird nicht angelegt und im Hintergrund läuft absolut gar nichts mehr. So dass nur noch das Formular angezeigt wird.

    Trotz sämtlicher Deaktivierungen tritt das Problem immer noch auf 😕
    Aus diesem Grund habe ich es auch mal mit deinem Vorschlag der "lineSettings" versucht. Jedoch verlieren auch die die Gültigkeit.

    Ergänzung:
    Wenn ich die Elemente über die GroupBox (FindControl) anspreche, so scheint auch dieser eine ungültige Adresse zu haben.

    Frames
    Klar, durch die Frames wird das ganze intern nochmals ein bisschen geordnet.
    Aber haben diese gegenüber den Group-Boxen noch einen anderen Vorteil? Oder besser gesagt, wann sollte man auf Frames oder Group-Boxen setzen? Wo ist der Unterschied?



  • Hallo Deforation,

    sorry für diesen Ausdruck, aber deine Klassen bzw. Methoden sind einfach zu groß. Ein großer Teil davon ist ja quasi Copy&Paste-Code und diesen solltest du in kleinere Klassen/Strukturen bzw. Methoden auslagern.
    Deine Hauptform sollte quasi nur die Verwaltungsklasse sein (fast ohne jedliche Logik).
    Und für die Umgestaltung der GUI eignen sich eben Frames, da diese quasi Unterforms darstellen, d.h. du kapselst z.B. die einzelnen Register (TTabSheet) als je ein Frame und implementierst dort die Logik. Ein Frame kann dann natürlich auch eine GroupBox beinhalten und sogar andere Frames. Es geht nur um die vernünftige Aufteilung der einzelnen Funktionalitäten (anstatt den gesamten Sourcecode in einer Klasse/Form zu haben).

    Gerade wenn du dein Programm noch erweitern willst, solltest du ein gutes Design als Grundlage haben (ich selber nutze immer die ca. 1000 Zeilen Regel, d.h. sobald ich merke, eine Klasse wird zu groß schaue ich, ob man nicht den Code refaktorieren kann).

    Ich weiß zwar, daß es sehr bequem mit dem VCL-Designer ist, mal eben ein paar PageControls, TabSheets etc. zu bestücken, aber wenn man dann sieht, daß die "main.h" einige Dutzende Controls beinhaltet, dann sollte man sich Gedanken machen. Alleine die Namensbenennung mittels Präfixen z.B. "InfobarColTotalCPUUsageInfoLabel" oder "ProcessKillYesFontSize" deutet doch darauf hin, daß dies einzelne gekapselte Controls sein sollten.

    Im Detail könnte ich dir auch noch ein paar andere Tipps geben, besonders bzgl.
    - Application.ProcessMessages() nicht unnötigerweise aufrufen
    - statt "new TLabel(G19_ProcessListMainForm)" besser "new TLabel(this)" verwenden (da G19_ProcessListMainForm eine globale Variable darstellt, die man eigentlich nicht nutzen sollte)
    - static_cast<TEdit*>(FindComponent(...) ist auch kein guter Stil und laufzeitintensiv
    - ...

    Da ich mir nur den Quellcode bisher angesehen habe (den BCB habe ich nur auf meinem 2. Rechner drauf), würde mich mal "sizeof(TG19_ProcessListMainForm)" interessieren. :xmas1:

    Edit:
    Mir ist gerade noch etwas eingefallen, und zwar, daß der Fehler evtl. ja nicht die lineSettings direkt betrifft sondern evtl. woanders her kommt. Pack mal die Deklaration der "lineSettings stLineSettings;" in der main.h an eine andere Stelle, z.B. ganz an den Anfang oder ganz ans Ende und schau dann mal nach ob der Fehler immer noch auftritt bzw. ob dein Programm dann an anderer Stelle abstürzt.

    P.S. Einen ganz kleinen C&P-Fehler habe ich auch noch (auf der Suche nach dem "-SPEZ-") in deinen Sprachdateien "deutsch.txt" und "englisch.txt" entdeckt: dort steht zweimal "lblProcessKillProcessNameLeft" anstatt "Left" und "Top".



  • Hallo,

    Noch was.
    Wenn du deine Elemente so new TLabel(G19_ProcessListMainForm) oder so new TLabel(this) erzeugst, dann setzt du den Owner der Elemente auf deine Mainform. Damit ist diese auch für das Speicherhandling zuständig. D.h. das die so erzeugten Elemente nicht mehr separat im Destructor von dir gelöscht werden müssen.
    Wenn du das Speicherhandling selbst in der Hand behalten willst erzeuge deine Elemente besser so new TLabel(0). Dann gibt es keinen Owner und du musst selber löschen, wie du es ja schon tust.



  • Hallo Zusammen

    Nochmals vielen Dank für eure Unterstützung.

    @th69:
    "sizeof(TG19_ProcessListMainForm)" gibt bei mir 2040 Bytes zurück.
    Ebenfalls danke für die Sichtung des Copy-Paste-Fehlers. 😉

    @Braunstein:
    Leuchtet mir irgendwie ein.
    Ich dachte mir, vielleicht könnte es ja genau daran liegen, dass der manuelle Rausfurt fehlt.
    Da hab ich mich wohl getäuscht.
    -> Ebenfalls danke für diese Erkenntnis. 💡

    Problem ist nun behoben:
    Nach einer neuen Suche, konnte ich den Fehler nun finden.
    Die Ursache waren zwei klammern. -> Die folge, unzählige Bytes wurden überschrieben:

    Schuld waren diese Zeilen, welche beim Ändern der Sprache ausgeführt wurden.

    //Alte defekte Version
    i64BufferSize=resLanguages->Size;
    char *wcharBuffer = new char(i64BufferSize);
    resLanguages->ReadBuffer(wcharBuffer,i64BufferSize);
    
    //Neue funktionierende Version
    i64BufferSize=resLanguages->Size;
    char *wcharBuffer = new char[i64BufferSize];
    resLanguages->ReadBuffer(wcharBuffer,i64BufferSize);
    

    Wie man sieht, waren die Klammer bei der dynamischen Erstellung des Arrays schuld. 🙄

    Ich möchte mich bei euch entschuldigen, aufgrund eines solch lächerlichen Fehlers eure Zeit beansprucht zu haben.

    Nach dem der Fehler nun behoben ist, werde ich mich wohl mal an das Refactoring machen. 😃



  • Hallo Deforation,

    entschuldigen brauchst du dich nicht. Irgendwie war es ja klar, daß es nur ein kleiner Fehler war (ist ja meistens bei solchen Fehlern der Fall).
    Aber jedenfalls hat es dich ja zur Erkenntnis gebracht, daß ein Refactoring hier angebracht ist (der Hint mit "sizeof(Form)" sollte dich nur darauf mal bringen - auch wenn jetzt 2040Byte speichertechnisch nicht viel sind, so zeigen sie ja, daß es zuviele verschiedene Daten für eine Klasse sind).

    Wenn du designtechnisch weitere Hilfe benötigst, so darfst du dich gerne wieder melden (ich finde das Projekt als solches nämlich sehr interessant -).

    Gruß und schöne Weihnachten schon mal :xmas1:


Anmelden zum Antworten