[gelöst] Stack Überlauf bei Instanziierung eines Form mit überladenem Konstruktor
-
Hallo Gemeinschaft,
in einem BDS2006-Projekt wird in einer Click-Methode des Hauptform ein Form mit überladenem Konstruktor instanziiert:
std::auto_ptr<TFormOverload> FrmOvld(new TFormOverload(this, 2));
Sobald diese Zeile ausgeführt wird, erhalte ich einen Stack-Überlauf!
Der überladene Konstruktor sieht so aus:
// in Overload.h: public: __fastcall TFormOverload(TComponent* Owner, int); // in Overload.cpp: __fastcall TFormOverload::TFormOverload(TComponent* Owner, int iVar) : TForm(Owner) { // hier möchte ich mit iVar eine enum-Variable initialisieren... } //---------------------------------------------------------------------------
Der Debugger schreibt, wenn die Zeile mit der Instanziierung ausgeführt wird:
Benachrichtigung über Debugger-Problem
In Projekt Overload.exe trat ein Problem mit folgender Meldung auf: 'access violation at 0x7c91e8c5: write of address 0x00030db0'. Prozess angehalten. Mit Einzelne Anweisung oder Start fortsetzen.Der Debugger macht dann ein CPU-Fenster mit folgender Zeile ganz oben auf:
7C91E8C5 53 push ebxWenn ich den zusätzlichen Parameter aus dem Konstruktor entferne, funktioniert Alles reibungslos.
Ich habe schon einige Male Konstruktoren überladen und nie Probleme damit gehabt... Ich bin relativ ratlos.Was kann hier das Problem sein?
MfG
Edit: [gelöst]-Tag im Thread-Titel ergänzt
-
Kolumbus schrieb:
in einem BDS2006-Projekt wird in einer Click-Methode des Hauptform ein Form mit überladenem Konstruktor
Das hat mit deinem Problem nicht unbedingt etwas zu tun, aber es ist hierbei generell wichtig:
C++Builder unterstützt das Überschreiben virtueller Konstruktoren. (Aufrufen kann man sie gegenwärtig nur in Delphi.) Alle Komponenten, also alles, was von TComponent ableitet, erben den wohlbekannten virtuellen Konstruktor
constructor TComponent.Create(Owner: TComponent);
.
Für eine Klasse, die einen virtuellen Konstruktor erbt, zusätzliche Konstruktoren einzuführen ist zwar möglich, aber tendentiell zu vermeiden. Zumindest mußt du immer damit rechnen, daß jemand eine Instanz deiner Klasse auch mit dem geerbten virtuellen Konstruktor erstellt (aus Delphi-Code heraus ist das möglich, oder auch indirekt aus C++-Code, z.B. mit Application.CreateForm().), d.h., du /mußt/ zusätzlich auch den virtuellen Konstruktor überschreiben und sinnvoll implementieren (oder, wenn es gar nicht geht, wenigstens eine Exception werfen).
Dein Problem habe ich in einem einfachen Beispielprojekt zu reproduzieren versucht, aber es trat bei mir nicht auf (ich habe allerdings mit C++Builder XE getestet). Kannst du mir ein einfaches, aber vollständiges Beispielprojekt erstellen, das das Problem demonstriert?
-
Hallo audacia,
erstmal vielen Dank für die Antwort.
audacia schrieb:
[...]Kannst du mir ein einfaches, aber vollständiges Beispielprojekt erstellen, das das Problem demonstriert?
Ein nachvollziehbares Beispiel ist aus meiner Sicht nicht möglich. Ich hatte ja erwähnt, dass ich schon desöfteren Konstruktoren überladen habe und es noch nie zu Problemen gekommen ist. Dabei habe ich nie etwas Anderes gemacht als im Eröffnungspost beschrieben: In der Parameterliste des Konstruktor eine Anzahl von Parametern hinzugefügt. Wenn ich mir jetzt die Mühe mache um ein zweites Projekt zu machen, welches das Vorgehen nachempfindet, bin ich mir zu 99% sicher dass es dort nicht zu einem Stack-Überlauf kommt...
audacia schrieb:
[...] Für eine Klasse, die einen virtuellen Konstruktor erbt, zusätzliche Konstruktoren einzuführen ist zwar möglich, aber tendentiell zu vermeiden. [...]
Ich habe das Problem momentan umschifft, indem ich eine public-Variable nach dem Instanziieren des Form setze. Ist das der bessere Weg? Ich habe damit allerdings zwei Probleme:
1. Heißt es nicht immer "public-Variablen sind böse"? Mit dem überladenen Konstruktor würde ich public vermeiden...
2. Immer wenn ich das Form instanziiere (und mit einigen Forms mache ich das oft), muss ich nun eine zweite Codezeile zur Initialisierung der Variablen schreiben. Wieder ein Schritt auf dem Weg zum unübersichtlichen Quelltext!
audacia schrieb:
[...]zusätzlich auch den virtuellen Konstruktor überschreiben und sinnvoll implementieren (oder, wenn es gar nicht geht, wenigstens eine Exception werfen).
...virtuellen Konstruktor auch überschreiben... so:
// in Overload.h: public: __fastcall TFormOverload(TComponent* Owner); __fastcall TFormOverload(TComponent* Owner, int); // in Overload.cpp: __fastcall TFormOverload::TFormOverload(TComponent* Owner) : TForm(Owner) {} //--------------------------------------------------------------------------- __fastcall TFormOverload::TFormOverload(TComponent* Owner, int iVar) : TForm(Owner) {} //---------------------------------------------------------------------------
?
Danke schonmal für deine Mühen!
MfG
Edit: Schönheitskorrekturen.
-
Gerade habe ich eine ausführliche Nachricht verfaßt, da muß ich bei einer dieser "Soll der Computer neugestartet werden?"-Nachfragen natürlich versehentlich auf "Ja" klicken. Verdammte Gedankenlosigkeit.
Jedenfalls fällt daher meine Antwort mangels Motivation, mich zu wiederholen, etwas knapper aus.
Ein nachvollziehbares Beispiel erstellst du am Besten in einer Kopie/einem Branch deines Projektes, wo du alles Unnötige entfernst. Ohne ein solches kann ich auch nur raten.
Eine öffentliche Membervariable ist tatsächlich keine gute Idee; nimm stattdessen ein Property, ggf. mit Setter.
Allgemein ist natürlich besser, wenn der Wert im Konstruktor übergeben wird - schon aufgrund der Konsistenzanforderung für Komponenten, aber auch dann, wenn es überhaupt nicht sinnvoll ist, diesen Wert auch während der Lebenszeit der Komponente zu verändern.
Das mit dem Überschreiben des Konstruktors hast du richtig verstanden.
-
Guten Morgen,
audacia schrieb:
Gerade habe ich eine ausführliche Nachricht verfaßt, da muß ich bei einer dieser "Soll der Computer neugestartet werden?"-Nachfragen natürlich versehentlich auf "Ja" klicken. Verdammte Gedankenlosigkeit. [...]
Jaja, der liebe Alltag am PC...
audacia schrieb:
[...] Jedenfalls fällt daher meine Antwort mangels Motivation, mich zu wiederholen, etwas knapper aus. [...]
Ich kann doch nun wirklich Nichts dafür. Trotzdem Danke für deine weitere Antwort!
audacia schrieb:
[...]Allgemein ist natürlich besser, wenn der Wert im Konstruktor übergeben wird - schon aufgrund der Konsistenzanforderung für Komponenten, aber auch dann, wenn es überhaupt nicht sinnvoll ist, diesen Wert auch während der Lebenszeit der Komponente zu verändern. [...]
Es wäre mir deswegen auch am liebsten, wenn ich es mit dem überladenen Konstruktor lösen könnte. Ich habe eben nochmal nachgeschaut.
Ich habe im selben Projekt ein Form, bei dem der virtuelle Konstruktor nicht überschrieben wird und im überladenen Konstruktor 8 zusätzliche Parameter (int, AnsiString, etc.) übergeben werden. Mit diesem Form habe ich gar keine Probleme!audacia schrieb:
[...]Das mit dem Überschreiben des Konstruktors hast du richtig verstanden.
Ich habe den virtuellen Konstruktor mal überschrieben und den überladenen Konstruktor wieder mit rein genommen. Beim Instanziieren des Form bekomme ich EXAKT dieselbe Fehlermeldung wie im Eingangspost, nur ist die Schreib-Adresse etwas höher geworden:
alt: write of address 0x00030db0
neu: write of address 0x00030e1caudacia schrieb:
[...] Ein nachvollziehbares Beispiel erstellst du am Besten in einer Kopie/einem Branch deines Projektes, wo du alles Unnötige entfernst. Ohne ein solches kann ich auch nur raten. [...]
Würdest du trotzdem mal versuchen ohne Beispiel in die Kristallkugel zu schauen? Im Moment (diese Woche) habe ich echt keine Zeit ein Beispiel zu basteln.
MfG
Edit1: Hier der Aufruf-Stack nach dem Fehler beim Instanziieren:
Aufruf-Stack schrieb:
:7c91e8c5 ntdll.stchr + 0xd8
:7c92349b ntdll.RtlCreateUnicodeString + 0x89
:7c92277f ntdll.LdrFindResource_U + 0x18
:7c80ad7b kernel32.FindResourceExW + 0x63
:7e36c12d ; C:\WINDOWS\system32\USER32.DLL
:7e36c153 ; C:\WINDOWS\system32\USER32.DLL
:7e36c065 ; C:\WINDOWS\system32\USER32.DLL
:7e36c1b5 ; C:\WINDOWS\system32\USER32.DLL
:7e377c01 USER32.LoadImageW + 0x6a
:7e377c30 USER32.LoadImageA + 0x28
:746cf580 ; C:\WINDOWS\system32\MSCTF.dll
:746c3622 ; C:\WINDOWS\system32\MSCTF.dll
:746c3eb5 ; C:\WINDOWS\system32\MSCTF.dll
:746bfdb9 ; C:\WINDOWS\system32\MSCTF.dll
:746c037f ; C:\WINDOWS\system32\MSCTF.dll
:7e368734 USER32.GetDC + 0x6d
:7e368816 ; C:\WINDOWS\system32\USER32.DLL
:7e3689cd ; C:\WINDOWS\system32\USER32.DLL
:7e368a10 USER32.DispatchMessageW + 0xf
:7e377721 ; C:\WINDOWS\system32\USER32.DLL
:7e3749c4 ; C:\WINDOWS\system32\USER32.DLL
:7e38a956 ; C:\WINDOWS\system32\USER32.DLL
:7e38a2bc ; C:\WINDOWS\system32\USER32.DLL
:7e3b63fd USER32.MessageBoxTimeoutW + 0x7a
:7e3b64a2 USER32.MessageBoxTimeoutA + 0x9c
:7e3a0877 USER32.MessageBoxExA + 0x1b
:7e3a082f USER32.MessageBoxA + 0x45
:004F72B3 Forms::TApplication::MessageBox(Self=:01002D80, Text=:01035F58, Caption=:01053CE8, Flags=16)
:004F73CB Forms::TApplication::ShowException(Self=:01002D80, E=:01013AD0)
:004F7192 Forms::TApplication::HandleException(Self=:01002D80, Sender=:00FCE110)
:0050C91C Controls::TWinControl::MainWndProc(Self=:00FCE110, Message=:0012FDB8)
:00495932 Classes::StdWndProc(Window=2557762, Message=273, WParam=7, LParam=0)
:7e368734 USER32.GetDC + 0x6d
:7e368816 ; C:\WINDOWS\system32\USER32.DLL
:7e3689cd ; C:\WINDOWS\system32\USER32.DLL
:7e3696c7 USER32.DispatchMessageA + 0xf
:004F6D4B Forms::TApplication::ProcessMessage(Self=:01002D80, Msg=:0012FEF0)
:004F6D90 Forms::TApplication::HandleMessage(Self=:01002D80)
:004F702B Forms::TApplication::Run(Self=:01002D80)
:004019B7 WinMain( =:00400000, =NULL, =:00141F11, =9)
:005938db ; __startupSo sieht das Ereignisprotokoll aus:
Ereignisprotokoll schrieb:
Thread-Start: Thread-ID: 1852. Prozess Overload.exe (3260)
Prozessstart: C:\...\Overload.exe. Basisadresse: $00400000. Prozess Overload.exe (3260)
Modul laden: Overload.exe. Enthält Debug-Infos. Basisadresse: $00400000. Prozess Overload.exe (3260)
Modul laden: ntdll.dll. Ohne Debug-Infos. Basisadresse: $7C910000. Prozess Overload.exe (3260)
Modul laden: KERNEL32.dll. Ohne Debug-Infos. Basisadresse: $7C800000. Prozess Overload.exe (3260)
Modul laden: cg32.dll. Ohne Debug-Infos. Basisadresse: $0CD00000. Prozess Overload.exe (3260)
Modul laden: ADVAPI32.dll. Ohne Debug-Infos. Basisadresse: $77DA0000. Prozess Overload.exe (3260)
Modul laden: RPCRT4.dll. Ohne Debug-Infos. Basisadresse: $77E50000. Prozess Overload.exe (3260)
Modul laden: Secur32.dll. Ohne Debug-Infos. Basisadresse: $77FC0000. Prozess Overload.exe (3260)
Modul laden: USER32.dll. Ohne Debug-Infos. Basisadresse: $7E360000. Prozess Overload.exe (3260)
Modul laden: GDI32.dll. Ohne Debug-Infos. Basisadresse: $77EF0000. Prozess Overload.exe (3260)
Modul laden: VERSION.dll. Ohne Debug-Infos. Basisadresse: $77BD0000. Prozess Overload.exe (3260)
Modul laden: WINSPOOL.DRV. Ohne Debug-Infos. Basisadresse: $72F70000. Prozess Overload.exe (3260)
Modul laden: msvcrt.dll. Ohne Debug-Infos. Basisadresse: $77BE0000. Prozess Overload.exe (3260)
Modul laden: COMCTL32.dll. Ohne Debug-Infos. Basisadresse: $5D450000. Prozess Overload.exe (3260)
Modul laden: comdlg32.dll. Ohne Debug-Infos. Basisadresse: $76350000. Prozess Overload.exe (3260)
Modul laden: SHELL32.dll. Ohne Debug-Infos. Basisadresse: $7E670000. Prozess Overload.exe (3260)
Modul laden: SHLWAPI.dll. Ohne Debug-Infos. Basisadresse: $77F40000. Prozess Overload.exe (3260)
Modul laden: ole32.dll. Ohne Debug-Infos. Basisadresse: $774B0000. Prozess Overload.exe (3260)
Modul laden: OLEAUT32.dll. Ohne Debug-Infos. Basisadresse: $770F0000. Prozess Overload.exe (3260)
Modul laden: IMM32.dll. Ohne Debug-Infos. Basisadresse: $76330000. Prozess Overload.exe (3260)
Modul laden: COMCTL32.dll. Ohne Debug-Infos. Basisadresse: $773A0000. Prozess Overload.exe (3260)
Modul laden: borlndmm.dll. Ohne Debug-Infos. Basisadresse: $21660000. Prozess Overload.exe (3260)
Modul laden: UxTheme.dll. Ohne Debug-Infos. Basisadresse: $5B0F0000. Prozess Overload.exe (3260)
Modul laden: MSCTF.dll. Ohne Debug-Infos. Basisadresse: $746A0000. Prozess Overload.exe (3260)
Modul laden: msctfime.ime. Ohne Debug-Infos. Basisadresse: $75250000. Prozess Overload.exe (3260)
Modul laden: HHCTRL.OCX. Ohne Debug-Infos. Basisadresse: $7E400000. Prozess Overload.exe (3260)
Modul laden: UNKNOWN_MODULE_9. Ohne Debug-Infos. Basisadresse: $68DA0000. Prozess Overload.exe (3260)
Modul laden: mfa_logo.dll. Ohne Debug-Infos. Basisadresse: $01110000. Prozess Overload.exe (3260)
Modul laden: mfa_lngen.dll. Ohne Debug-Infos. Basisadresse: $015A0000. Prozess Overload.exe (3260)
Modul laden: briu08b.dll. Ohne Debug-Infos. Basisadresse: $6A900000. Prozess Overload.exe (3260)
Modul laden: SPOOLSS.DLL. Ohne Debug-Infos. Basisadresse: $74250000. Prozess Overload.exe (3260)
Modul laden: WS2_32.dll. Ohne Debug-Infos. Basisadresse: $71A10000. Prozess Overload.exe (3260)
Modul laden: WS2HELP.dll. Ohne Debug-Infos. Basisadresse: $71A00000. Prozess Overload.exe (3260)
Modul laden: UNKNOWN_MODULE_10. Ohne Debug-Infos. Basisadresse: $01A30000. Prozess Overload.exe (3260)CodeGuard ist jetzt vollständig aktiviert, bringt jedoch keine Fehler- / Warnmeldungen.
Mir ist noch etwas eingefallen: Das Problem-Form hatte schonmal einen überladenen Konstruktor, sogar 2-fach. Das war allerdings bevor ich das Projekt nach BDS2006 (von C++Builder 3) übernommen habe. Vielleicht spielt das eine Rolle!??
MfG
Edit2: So sieht der Aufruf-Stack ohne den überladenen Konstruktor aus, nachdem das Form instanziiert wurde und angezeigt wird:
Aufruf-Stack schrieb:
:7c91e4f4 ntdll.KiFastSystemCallRet
:7e369418 USER32.WaitMessage + 0xc
:004F6D5F Forms::TApplication::HandleMessage(Self=:01002D80)
:004F28B7 Forms::TCustomForm::ShowModal(Self=:00F7BC80)
:0042D4B4 TFormMain::LoadConfigFromFile(this=:00FCE110)
:00421038 TFormMain::MCfgLoadClick(this=:00FCE110, Sender=????)
:004CB3CB Menus::TMenuItem::Click(Self=:0100A530)
:004CCBAC Menus::TMenu::DispatchCommand(Self=:00FED3F8, ACommand=7)
:004F10DE Forms::TCustomForm::WMCommand(Self=:00FCE110, Message=:0012FDB8)
:005084A8 Controls::TControl::WndProc(Self=:00FCE110, Message=:0012FDB8)
:0050D1B2 Controls::TWinControl::WndProc(Self=:00FCE110, Message=:0012FDB8)
:004EDBA2 Forms::TCustomForm::WndProc(Self=:00FCE110, Message=:0012FDB8)
:0050C89F Controls::TWinControl::MainWndProc(Self=:00FCE110, Message=:0012FDB8)
:004958F2 Classes::StdWndProc(Window=919330, Message=273, WParam=7, LParam=0)
:7e368734 USER32.GetDC + 0x6d
:7e368816 ; C:\WINDOWS\system32\USER32.DLL
:7e3689cd ; C:\WINDOWS\system32\USER32.DLL
:7e3696c7 USER32.DispatchMessageA + 0xf
:004F6D0B Forms::TApplication::ProcessMessage(Self=:01002D80, Msg=:0012FEF0)
:004F6D50 Forms::TApplication::HandleMessage(Self=:01002D80)
:004F6FEB Forms::TApplication::Run(Self=:01002D80)
:004019B7 WinMain( =:00400000, =NULL, =:00141F11, =9)
:0059389b ; __startupUnd so das Ereignisprotokoll:
Ereignisprotokoll schrieb:
Thread-Start: Thread-ID: 3736. Prozess Overload.exe (2552)
Prozessstart: C:\...\Overload.exe. Basisadresse: $00400000. Prozess Overload.exe (2552)
Modul laden: Overload.exe. Enthält Debug-Infos. Basisadresse: $00400000. Prozess Overload.exe (2552)
Modul laden: ntdll.dll. Ohne Debug-Infos. Basisadresse: $7C910000. Prozess Overload.exe (2552)
Modul laden: KERNEL32.dll. Ohne Debug-Infos. Basisadresse: $7C800000. Prozess Overload.exe (2552)
Modul laden: cg32.dll. Ohne Debug-Infos. Basisadresse: $0CD00000. Prozess Overload.exe (2552)
Modul laden: ADVAPI32.dll. Ohne Debug-Infos. Basisadresse: $77DA0000. Prozess Overload.exe (2552)
Modul laden: RPCRT4.dll. Ohne Debug-Infos. Basisadresse: $77E50000. Prozess Overload.exe (2552)
Modul laden: Secur32.dll. Ohne Debug-Infos. Basisadresse: $77FC0000. Prozess Overload.exe (2552)
Modul laden: USER32.dll. Ohne Debug-Infos. Basisadresse: $7E360000. Prozess Overload.exe (2552)
Modul laden: GDI32.dll. Ohne Debug-Infos. Basisadresse: $77EF0000. Prozess Overload.exe (2552)
Modul laden: VERSION.dll. Ohne Debug-Infos. Basisadresse: $77BD0000. Prozess Overload.exe (2552)
Modul laden: WINSPOOL.DRV. Ohne Debug-Infos. Basisadresse: $72F70000. Prozess Overload.exe (2552)
Modul laden: msvcrt.dll. Ohne Debug-Infos. Basisadresse: $77BE0000. Prozess Overload.exe (2552)
Modul laden: COMCTL32.dll. Ohne Debug-Infos. Basisadresse: $5D450000. Prozess Overload.exe (2552)
Modul laden: comdlg32.dll. Ohne Debug-Infos. Basisadresse: $76350000. Prozess Overload.exe (2552)
Modul laden: SHELL32.dll. Ohne Debug-Infos. Basisadresse: $7E670000. Prozess Overload.exe (2552)
Modul laden: SHLWAPI.dll. Ohne Debug-Infos. Basisadresse: $77F40000. Prozess Overload.exe (2552)
Modul laden: ole32.dll. Ohne Debug-Infos. Basisadresse: $774B0000. Prozess Overload.exe (2552)
Modul laden: OLEAUT32.dll. Ohne Debug-Infos. Basisadresse: $770F0000. Prozess Overload.exe (2552)
Modul laden: IMM32.dll. Ohne Debug-Infos. Basisadresse: $76330000. Prozess Overload.exe (2552)
Modul laden: COMCTL32.dll. Ohne Debug-Infos. Basisadresse: $773A0000. Prozess Overload.exe (2552)
Modul laden: borlndmm.dll. Ohne Debug-Infos. Basisadresse: $21660000. Prozess Overload.exe (2552)
Modul laden: UxTheme.dll. Ohne Debug-Infos. Basisadresse: $5B0F0000. Prozess Overload.exe (2552)
Modul laden: MSCTF.dll. Ohne Debug-Infos. Basisadresse: $746A0000. Prozess Overload.exe (2552)
Modul laden: msctfime.ime. Ohne Debug-Infos. Basisadresse: $75250000. Prozess Overload.exe (2552)
Modul laden: HHCTRL.OCX. Ohne Debug-Infos. Basisadresse: $7E400000. Prozess Overload.exe (2552)
Modul laden: UNKNOWN_MODULE_17. Ohne Debug-Infos. Basisadresse: $68DA0000. Prozess Overload.exe (2552)
Modul laden: mfa_logo.dll. Ohne Debug-Infos. Basisadresse: $01110000. Prozess Overload.exe (2552)
Modul laden: mfa_lngen.dll. Ohne Debug-Infos. Basisadresse: $015A0000. Prozess Overload.exe (2552)
Modul laden: briu08b.dll. Ohne Debug-Infos. Basisadresse: $6A900000. Prozess Overload.exe (2552)
Modul laden: SPOOLSS.DLL. Ohne Debug-Infos. Basisadresse: $74250000. Prozess Overload.exe (2552)
Modul laden: WS2_32.dll. Ohne Debug-Infos. Basisadresse: $71A10000. Prozess Overload.exe (2552)
Modul laden: WS2HELP.dll. Ohne Debug-Infos. Basisadresse: $71A00000. Prozess Overload.exe (2552)
Modul laden: UNKNOWN_MODULE_18. Ohne Debug-Infos. Basisadresse: $01A30000. Prozess Overload.exe (2552)MfG
Edit3: Ich habe in beiden geposteten Aufruf-Stack-Kopien die Zeile fett markiert, bis zu der sich beide Kopien mehr oder weniger gleichen (einschliesslich der fetten Zeile). Möglicherweise hilft das bei der Fehlersuche...
MfG
-
Ich habe das in C++Builder 2006 getestet und konnte das Problem dort reproduzieren - nicht jedoch in C++Builder 2009 und höher. Der Grund ist in folgendem "Diff" von TCustomForm.Create() ersichtlich, das die zwischen Delphi 2006 und 2009 vollzogenen Änderungen veranschaulicht:
constructor TCustomForm.Create(AOwner: TComponent); begin + inherited Create(AOwner); GlobalNameSpace.BeginWrite; try + FCreatingMainForm := Application.FCreatingMainForm; + if FCreatingMainForm then + Application.FCreatingMainForm := False; + - CreateNew(AOwner); + InitializeNewForm; if (ClassType <> TForm) and not (csDesigning in ComponentState) then begin Include(FFormState, fsCreating); try if not InitInheritedComponent(Self, TForm) then raise EResNotFound.CreateFmt(SResNotFound, [ClassName]); finally Exclude(FFormState, fsCreating); end; if OldCreateOrder then DoCreate; end; finally GlobalNameSpace.EndWrite; end; end;
Zum Verständnis muß ich kurz auf drei Sprachfeatures eingehen, die Delphi unterstützt, C++ aber nicht:
Erstens tragen Konstruktoren in Delphi Namen. Der meistverwendete Name ist "Create", aber man findet öfters auch etwas auführlichere Bezeichnungen. TCustomForm hat beispielsweise zwei virtuelle Konstruktoren: einen namens "Create" (der übliche) und einen namens "CreateNew". Die sich dabei ergebende Problematik in C++ ist ausführlich in der Dokumentation beschrieben. Aus genau diesem Grund ist TCustomForm.CreateNew() auch folgendermaßen deklariert:
constructor CreateNew(AOwner: TComponent; Dummy: Integer = 0); virtual;
In Delphi-Code kann man das Dummy-Argument einfach ignorieren und
TMyForm.CreateNew (Owner)
aufrufen; in C++ muß man das Argument übergeben, um die beiden Konstruktoren auseinanderhalten zu können.Zweitens kennt Delphi virtuelle Konstruktoren und Klassenreferenzen bzw. "Metaklassen". In C++Builder bekommst du etwa mit dem __classid()-Operator einen Zeiger auf die zu einer Klasse gehörige Metaklasse (mangels Sprachunterstützung kannst du aber nicht viel damit anfangen). In Delphi kann man über eine Klassenreferenz virtuelle Konstruktoren oder Klassenmethoden aufrufen. Ein nützliches kleines Beispiel:
type TComponentClass = class of TComponent; // from Classes.pas var CompClass: TComponentClass; Comp: TComponent; begin if ... then CompClass := TButton else CompClass := TEdit; Comp := CompClass.Create (Self); ... end;
In C++Builder sind Klassenreferenzen schwach typisiert, daher kannst du für eine Klassenreferenz keine virtuellen Klassenmethoden oder Konstruktoren aufrufen. Aber du kannst sie an Delphi-Code übergeben, der solches tut.
So funktioniert übrigens auch das DFM-Streaming: In der DFM-Datei ist für jede Komponente der Klassenname eingetragen (z.B. "TButton"). Wenn der TForm-Konstruktor die DFM-Datei liest und auf eine Unterkomponente stößt, sucht er nach einer Klasse mit diesem Namen (erst in der Typinformation der Form-Klasse - deshalb müssen die Form-Komponenten in der __published-Sektion sein -, danach ggf. in einer globalen Klassentabelle) und ruft dann deren virtuellen Konstruktor auf. So kann die in Delphi geschriebene VCL den Konstruktor einer in C++ geschriebenen Komponente aufrufen.
Drittens, und das ist hier entscheidend, kennt Delphi ein ein Feature namens "constructor forwarding", das auch für C++ einmal im Gespräch war und, wenn ich recht informiert bin, im nächsten Standard auftauchen wird. Das bedeutet, daß ein Konstruktor einfach einen anderen Konstruktor mit anderen Argumenten aufrufen kann, der die weitere Konstruktion übernimmt. Ein Beispiel, das sich irgendwo in meinem Code findet:
constructor EOutOfRange.Create(ContextName: String; Index, Lo, Hi: Integer); begin inherited Create (Format (SRangeError, [ContextName, Index, Lo, Hi])); // <-- Aufruf des Basisklassen-Konstruktors end; constructor EOutOfRange.Create(ContextClass: TClass; Index, Lo, Hi: Integer); begin Create (ContextClass.ClassName, Index, Lo, Hi); // <-- "constructor forwarding": Weitergabe des Aufrufes an den anderen Konstruktor end;
Das funktioniert im Allgemeinen hervorragend. Offenbar ist Constructor-Forwarding aber anfällig für Endlosrekursion:
constructor TFoo.MyCreate; begin YourCreate; end; constructor TFoo.YourCreate; begin MyCreate; end;
Solche Fehler fallen natürlich direkt beim ersten Test auf und kommen daher nicht weit. Mit einer Ausnahme: dann nämlich, wenn einer oder beide Konstruktoren virtuell sind. Dann kann eine abgeleitete Klasse einen davon überschreiben und ihrerseits wieder eine Endlosrekursion auslösen. Genau das passiert in deinem Fall:
__fastcall TForm1::TForm1(TComponent* Owner, int foo) // <-- überschreibt TCustomForm.CreateNew()! : TForm(Owner) // <-- ruft TCustomForm.Create() auf { }
constructor TCustomForm.Create(AOwner: TComponent); begin GlobalNameSpace.BeginWrite; try CreateNew(AOwner); // <-- ruft TForm1::TForm1(TComponent*, int) auf ...
Ich denke, du siehst, wohin das führt.
Der Fehler ist hier in der VCL; Constructor-Forwarding macht man nicht mit virtuellen Konstruktoren. Das Problem ist, wie im eingangs erwähnten Diff zu sehen, in C++Builder 2009 und aufwärts behoben.
Ein möglicher Workaround für dich wäre die Einführung eines weiteren Dummy-Parameters. Oder natürlich ein Upgrade
-
Wow,
sehr interessant und lehrreich! Vielen Dank für die Mühen der ausführlichen Erklärung. Das ist meiner Meinung nach ein FAQ-Beitrag - kann ich das irgendwo oder irgendwem vorschlagen?
Ich bin zwar noch lange nicht soweit ein solches Problem selbst zu identifizieren und somit zu lösen (dafür muss man schon die Borland-Delphi-VCL-C++-Zusammenhänge im Blut haben), aber ich denke ich habe soweit Alles verstanden. Erstaunlich finde ich, dass so eine Kleinigkeit so grundlegende und umfangreiche Wurzeln hat...
Ich werde einen weiteren Paramter im überladenen Konstruktor einfügen. Ich habe auch schon eine sinvolle Verwendung im Hinterkopf, so dass es nicht nur ein Dummy-Parameter ist.
Gute Idee mit dem Upgrade, aber das werde ich mir wohl aus Kostengründen aus dem Kopf schlagen können.
Vielen Dank nochmal audacia
MfG
-
Kolumbus schrieb:
Gute Idee mit dem Upgrade, aber das werde ich mir wohl aus Kostengründen aus dem Kopf schlagen können.
Du könntest deinem Chef einmal vorrechnen, wie viel Zeit du so damit verbringst, Workarounds für Probleme zu finden, die in aktuelleren Versionen behoben wurden, dann mit deinem auf die Stunde umgerechneten Arbeitslohn multiplizieren, das Ganze über die Zeit vom Erscheinen von C++Builder 2009 bis jetzt integrieren und dann dem Upgrade-Preis gegenüberstellen
Übrigens sind regulär nur noch Besitzer von C++Builder 2007 und höher für ein Upgrade auf C++Builder XE berechtigt.
Edit:
Für C++Builder 2006-Benutzer gibt es da wohl bis zum Jahresende noch eine Ausnahme (Quelle).