Fortschrittsanzeige
-
Wenn es temporär ist hängt das CWnd doch dann in die Luft?!
Was meinst du/ihr mit temporär in diesem Fall?
-
So verstehen tu ich das immer noch nicht.
Sorry wenn ich damit nerve, aber das will ich nun verstehenWenn ich aus der HandleMap genau das CWnd bekomme von dem das MFC Control geerbt hat, müsste der cast gehen. Wo bleiben denn da die Laufzeit informationen wenn es genau das CWnd ist ?
Kommen wir mal darauf zurück was passieren soll wenn kein Eintrag in der Liste gefunden wird. Wir bekommen ein CWnd zurück dem ein HWND zugewiesen wurde.
Die Methode SetPos() z.B. gehört aber nicht zum CWnd, egal ob es nur ne Api kapselt oder nicht. Es gibt keine Instanz zu dieser Methode.
Ich zeig euch mal ne E-Mail an der ich gedanklich dabei festhalte, vllt. versteht ihr dann warum ich ein Problem habe das zu verstehen:
Emial mit HumeSikkins - 27.03.2003
struct a { a(){std::cout<<"Konstruktor a\n";}; }; struct b : public a { b(){std::cout<<"Konstruktor b\n";}; void foo(){std::cout<<12;}; }; int main(int argc, char* argv[]) { a aa; a * A = &aa; reinterpret_cast<b*>(A)->foo(); return 0; } Aufruf für foo() ok ? Wenn ja warum wenn nein warum
Aufruf für foo() ok ?
Nope. Der Aufruf erzeugt undefiniertes Verhalten!a aa;
Jetzt hast du ein a. Und zwar wirklich nur ein a!
a * A = &aa;
Jetzt hast du ein Zeiger vom Typ a auf ein a. Vergiss nicht, der Zeiger
zeigt auf ein a.> reinterpret_cast<b*>(A)->foo();
ALARM! Das ruft die Methode foo von b auf. Es gibt aber überhaupt kein b! Du
rufst eine Methode für ein nicht existierendes Objekt auf.
Es wurde kein Konstruktor aufgerufen. Es existiert kein entsprechender
Speicher.Das das Programm auf den meisten Compilern funktioniert, liegt daran, dass
du innerhalb von foo nicht den this-Pointer verwendest.
Bedenke: Die meisten Compiler wandeln Methoden in simple Funktionen mit
einem zusätzlichen Parameter um.
Aus b::foo wird quasi:
foo_b(b* this);
Wenn du jetzt nichts mit dem this-Pointer machst, fällt es auch nicht auf,
dass gar kein b-Objekt existiert.
Mach aber mal das Folgende:
struct b : public a
{
b(){std::cout<<"Konstruktor b\n";};
void foo(){s = "Hallo";};
string s;
};Spätestens jetzt sollte dir der Aufruf um die Ohren fliegen.
Aber nochmal:
Der Aufruf ist auch schon vorher illegal! Er verursacht undefiniertes
Verhalten! Egal wie die Methode foo aussieht.PS: Ich stütze meine Behauptung auf die Abschnitte 5.2.9/8 und 5.2.10 des C++ Standards
So wie ich das CWnd von GetDlgItem interpretiere verhält es sich hier wie das a.
Würde mich freuen wenn mir das jemand mit dem CWnd mal für jemanden der schwer von begriff ist zu erklären
[ Dieser Beitrag wurde am 27.03.2003 um 22:37 Uhr von Knuddlbaer editiert. ]
-
ooh man das ist doch so einfach zu verstehen. kannst du nicht wenigstens vernünftigen erklären, was du nicht verstehst?
-
PS: Ich stütze meine Behauptung auf die Abschnitte 5.2.9/8 und 5.2.10 des C++ Standards
Gibts da einen Link für? Hab den Standard im Moment nicht parat!
Folgendes, was theoretisch legal sein müsste:
struct a { string s; }; struct b : public a { void foo() { s = "abc"; } } int main() { a A; reinterpret_cast<b*>(A)->foo(); }
Warum:
((b->this == a->this) && (sizeof(b) == sizeof(a))
-
@Rene
http://www.comnets.rwth-aachen.de/doc/c++std/contents.html
@Magnus
Warum kann ich kein dynamic_cast machen wenn ich exakt das CWnd aus der HandleMap zurückbekomme das zum MFCCOntrol gehört.
Oder anderst: Wieso gehen die Laufzeitinformationen verloren ?Wenn das CWnd erzeugt wurde weil in der HandleMap kein passendes CWnd gefunden wurde, warum ist der Aufruf an Methoden die NICHT zur Klasse CWnd gehören legal?
[ Dieser Beitrag wurde am 28.03.2003 um 08:58 Uhr von Knuddlbaer editiert. ]
-
warum ist der Aufruf an Methoden die NICHT zur Klasse CWnd gehören legal
Na weil dem Linker egal ist, zu welcher Klasse die Funktion gehört, Hauptsache der Adressbereich der Variablen stimmt, auf die die Funktion zugreift
-
Aber wenn zum Beispiel CProgressCtrl noch extra Membervariablen haben auf die die Membermethoden zugreifen, kann das nicht funktionieren.
-
Folgendes, was theoretisch legal sein müsste:
struct a
{
string s;
};
struct b : public a
{
void foo() { s = "abc"; }
}
int main()
{
a A;
reinterpret_cast<b*>(A)->foo();
}
Warum:
((b->this == a->this) && (sizeof(b) == sizeof(a))Nein. Das ist nicht legal. Mal abgesehen davon, dass da ein Semikolon fehlt und du wahrscheinlich ein a* nach b* casten wolltest. Ein struct a kann überhaupt nicht nach b* gecastet werden.
Der Standard garantiert kein Objektlayout. b->this == a->this muss also schonmal nicht gelten. Und ich zweifle auch sehr stark daran, dass sizeof(b) == sizeof(a) garantiert ist.
Das spielt aber eigentlich auch keine Rolle.
1. Hat ein reinterpret_cast sowieso immer implementations-spezifisches Verhalten.
2. Ist ein reinterpret_cast hier überhaupt nicht nötig. Ein static_cast tuts auch.
3. Beides führt aber zu undefiniertem Verhalten.
Mit dem reinterpret_cast kannst du zwar einen beliebigen Pointer in einen anderen Pointer casten. Du darfst mit diesem Pointer dann aber nicht einfach so wieder auf das referenzierte Objekt zugreifen. Das einzige was garantiert ist, ist dass ein cast zurück zum alten Pointertyp wieder exakt den Originalpointer liefert. Hier ist IMO also erst der Aufruf von foo undefiniert.Beim static_cast ist die Sache eindeutig. A ist kein b, damit resultiert bereits der cast in undefiniertem Verhalten.
Das ganze sollte eigentlich aber auch intuitiv klar sein. Es wird versucht auf eine Methode von b zuzugreifen, obwohl kein b existiert. Es wurde nie ein B-Konstruktor aufgerufen. Und das Lebenszyklusmodell von C++ sieht vor, dass ein Objekt erst lebt und benutztbar ist, *nachdem* sein Ctor abgeschlossen wurde.
Du verlässt dich hier einfach auf ein paar Implementationsdetails des VCs.
-
class A { protected: HWND m_hWnd; }; class B : public A { public: void func() { m_hWnd = NULL; }; static void Sfunc( B* p) { ((A*)p)->m_hWnd = NULL; } }; A a; // Aufruf1 ((B*)&a)->func(); // Aufruf2 B::Sfunc( (B*)&a);
Aufruf1 und Aufruf2 machen doch immer das gleiche, oder nicht?
-
Aufruf1 und Aufruf2 machen doch immer das gleiche, oder nicht?
Nö. Ganz und garnicht.
B::Sfunc( (B*)&a);
Hier castest du erst A* nach B*, bevor du aber über den Zeiger auf das referenzierte Objekt zugreifst castest du wieder zurück.
Du dereferenzierst also *niemals* einen Zeiger vom Typ B. Sondern immer nur einen vom Typ A.((B*)&a)->func();
Hier hingegen *dereferenzierst* du einen Zeiger vom Typ B, obwohl ein Objekt vom Typ A referenziert wird. Der operator -> macht den Unterschied. Und dieser ist gewaltig.
Sicher, die meisten Compiler wandeln eine Methode B::Func() in eine Funktion
ähnlich Func_B(B* this) um. Und falls es sich nicht um eine virtuelle Methode handelt wird der Aufruf ((B*)&a)->func(); bei solchen Compilern auch
zu Func_B((B*)&a);Das muss aber nicht so sein. Fakt ist, dass der op-> laut C++ Standard zu einer Dereferenzierung des Zeigers führt. Egal ob das eine Implementation dann auch wirklich tut. Und diese Dereferenzierung ist es, die IMO zu undefiniertem Verhalten führt.
Das ist auch der Grund warum sowas undefiniertes Verhalten hat:
struct A { void Func() {cout << "Hallo" << endl;} }; int main() { ((A*)0)->Func(); }
Das wird bei den meisten Compiler funktionieren, da der Nullzeiger nicht dereferenziert wird. Es ist aber trotzdem undefiniert, da laut Standard hier ein Nullzeiger dereferenziert wird.
-
Gehen wir auf Assemberebene, um zu verdeutlichen, was ich meine!
class A { int i; }; class B : public A { void func() { i=1; } }; A a1, a2; ((B*)&a1)->func(); ((B*)&a2)->func();
// Asm
lea ecx, [a1]
call 0x25242322
lea ecx, [a2]
call 0x25242322Was macht nun die Funktion func():
mov eax, dword ptr [this]
mov [eax], 1Wir sehen also folgendes:
Für eine Funktion aus B existiert ein realer Function call, ob nun eine Objekt dafür instanziert wurde oder nicht bleibt egal!
Die Funktion in B schiebt die zugewiesene 1 auf den this-Pointer, was uns zeigt, dass this der Adresse des ersten Datenelements des Objekts entspricht.
->
Der function call wird immer funktionieren, solange auf einen Datenbereich zugegriffen wird, der vom Basisobjekt auch zur Verfügung gestellt wird.Selbst wenn die Funktion virtuell ist, klappt der Aufruf, da das Schlüsselwort virtual nur sagt, dass der Funktionszeiger in der Basisklasse durch den der abgeleiteten Klasse zu ersetzen ist.
-
Hallo,
hier nochmal ein paar wichtige Stellen aus dem C++ Standard:((B*)&a)->func();
Member-Access über den ->:
5.2.5/1:A postfix expression followed by a dot . or an arrow ->,
optionally followed by the keyword template (14.8.1), and
then followed by an id-expression, is a postfix expression.
The postfix expression before the dot or arrow
is evaluated, the result of that evaluation, together
with the id-expression, determine the result
of the entire postfix expression.Und Fußnote 58:
The evaluation happens even if the result is unnecessary to
determine the value of the entire postfix expression,
for example if the id-expression denotes a static
member.Die Fußnote macht klar, warum mein letztes Beispiel mit dem Nullpointer undefiniertes Verhalten hat.
5.2.5/2:
[...] For the second option (arrow) the type of
the first expression (the pointer expression)
shall be "pointer to class" (of a complete type).
In these cases, the id-expression shall name a member
of the class or of one of its base classes[...]
Soweit so gut. ((B*)&a) ist vom "pointer to class" (of a complete type) (hier B). Und func ist ein Name eines Member der Klasse B.5.2.5/3
If E1 has type "pointer to class X", then the expression
E1->E2 is converted to the equivalent form
(*(E1)).E2; the remainder of 5.2.5 will address only
first option (dot).Dazu Fußnote 59:
Note that if E1 has type "pointer to class X",
then (*(E1)) is an lvalue.Jetzt wird's wieder wichtig:
5.2.5/4:[...]
Otherwise, if E1.E2 refers to a non-static member function, and the type
of E2 is "function of (parameter type list) cv returning T", then E1.E2
is not an lvalue. The expression designates a non-static member function.
The expression can be used only as the left-hand operand of a member
function call (9.3).[...]Also flux nach 9.3 geschaut:
9.3.1/1:
A nonstatic member function may be called for an object of its
class type, or for an object of a class derived (clause 10) from
its class type, using the clas member access syntax.
[...]
If a nonstatic member function of a class X is called for
an object that is not of type X, or of a type
derived from X, the behavior is undefined.Der letzte Satz bestätigt meine Aussage. Func ist eine nicht-statische Methode der Klasse B. Das Objekt für das wir diese Methode aufrufen ist aber vom Typ A. A ist nicht von B abgeleitet, also ist das Verhalten undefiniert.
Hier ist es also nicht der cast der Bösewicht sondern der anschließende member-access.
-
@RenéG
Du scheint mich nicht zu verstehen. Mir geht es darum was der Ausdruck für Standard-C++ bedeutet. Und da ist er *undefiniert*. Es kann also alles passieren. Vom Formatieren deiner Festplatte bis zum Programmabsturz. Von läuft toll bis läuft nur an Dienstagen.Es hilft da nichts, wenn du mit Assembler oder sonstigen *Implementationsdetails* die von Compiler zu Compiler variieren kommst. C++ ist nicht auf Assemblerebene definiert!
Ich bezweifle nicht, dass das ganze auf dem VC funktioniert. Ich sage nur, dass es kein legales Standard-C++ ist.
Selbst wenn die Funktion virtuell ist, klappt der Aufruf, da das Schlüsselwort virtual nur sagt, dass der Funktionszeiger in der Basisklasse durch den der abgeleiteten Klasse zu ersetzen ist.
Nö. Das ist wieder eine Implementationssicht. Es steht nirgends im Standard geschrieben, dass virtuelle Methoden über eine VMT mit Funktionszeigern und einem vptr implementiert sein müssen.
-
@Hume...
Womit wir ganz vom eigentlichen Thema abgewichen sind.
Mir ging es eigentlich von Anfang an darum, warum der Cast von GetDlgItem mit der MFC und Visual C++ funktioniert. Denn schliesslich sind wir ja hier nicht im Standard-C++-Forum.
Leider hab ich mich auch selber auf diese Diskussion eingelassen
-
Jaja, jetzt ausweichen...wenn man eine Diskussion anfängt, führt man sie auch zu Ende.
-
Womit wir ganz vom eigentlichen Thema abgewichen sind.
Da dies meine Schuld ist, muss ich mich dafür entschuldigen.
Ich habe von dem eigentlichen Thema überhaupt keine Ahnung (muss gestehen, dass ich den Thread nicht mal wirklich gelesen habe) sondern bin ja nur über den Email-Verkehr mit Knuddlbaer darauf gekommen.Insofern: Sorry für jedwedige Irritation die durch meine Beiträge ausgelöst wurde
Jaja, jetzt ausweichen...wenn man eine Diskussion anfängt, führt man sie auch zu Ende
Die Diskussion ist am Ende.
Die Standard-Sicht wurde von mir vertreten und spielt hier im *VC-Forum* nur eine untergeordnete Rolle. Die VC-Sicht wurde von René vertreten, sie ist für das *VC-Forum* entscheidend.Aus Standard-Sicht kann nach undefiniert nichts mehr folgen. Und aus VC-Sicht funktioniert alles.
Über was willst du also noch diskutieren?
-
Ok soweit thx, ich habe das nun verstanden, das wenn die Routinen so implementiert sind das sie auf keine Member oder auf kein this zugreifen einfach gecastet werden können (und zwar nur auf MFC + VC)
Daraus resultiert sich aber nun die Frage:
Ist es denn garantiert das die MFCControls das eben nicht tun ? bzw. wie bekomme
ich das raus ?Und die Frage, warum die Laufzeitinformation verloren geht wenn es denn das CWnd aus der HandleMap ist ist noch offen. Das hab ich noch nicht verstanden.
-
Aus AFXCMN.h:
///////////////////////////////////////////////////////////////////////////// // CProgressCtrl class CProgressCtrl : public CWnd { DECLARE_DYNAMIC(CProgressCtrl) // Constructors public: CProgressCtrl(); BOOL Create(DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID); // Attributes void SetRange(short nLower, short nUpper); void SetRange32(int nLower, int nUpper); void GetRange(int& nLower, int& nUpper); int GetPos(); int SetPos(int nPos); int OffsetPos(int nPos); int SetStep(int nStep); // Operations int StepIt(); // Implementation public: virtual ~CProgressCtrl(); };
-> keine eigenen Membervariablen, mit Ausnahme von DECLARE_DYNAMIC, aber die sind eh static und damit nicht von belang
Aus WINCTRL2.CPP:
///////////////////////////////////////////////////////////////////////////// // CProgressCtrl BOOL CProgressCtrl::Create(DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID) { // initialize common controls VERIFY(AfxDeferRegisterClass(AFX_WNDCOMMCTL_PROGRESS_REG)); CWnd* pWnd = this; return pWnd->Create(PROGRESS_CLASS, NULL, dwStyle, rect, pParentWnd, nID); } CProgressCtrl::~CProgressCtrl() { DestroyWindow(); }
Aus AFXCMN.INL:
_AFXCMN_INLINE CProgressCtrl::CProgressCtrl() { } _AFXCMN_INLINE void CProgressCtrl::SetRange(short nLower, short nUpper) { ASSERT(::IsWindow(m_hWnd)); ::SendMessage(m_hWnd, PBM_SETRANGE, 0, MAKELPARAM(nLower, nUpper)); } _AFXCMN_INLINE void CProgressCtrl::SetRange32(int nLower, int nUpper) { ASSERT(::IsWindow(m_hWnd)); ::SendMessage(m_hWnd, PBM_SETRANGE32, (WPARAM) nLower, (LPARAM) nUpper); } _AFXCMN_INLINE int CProgressCtrl::GetPos() { ASSERT(::IsWindow(m_hWnd)); return (int) ::SendMessage(m_hWnd, PBM_GETPOS, 0, 0); } _AFXCMN_INLINE int CProgressCtrl::OffsetPos(int nPos) { ASSERT(::IsWindow(m_hWnd)); return (int) ::SendMessage(m_hWnd, PBM_DELTAPOS, nPos, 0L); } _AFXCMN_INLINE int CProgressCtrl::SetStep(int nStep) { ASSERT(::IsWindow(m_hWnd)); return (int) ::SendMessage(m_hWnd, PBM_SETSTEP, nStep, 0L); } _AFXCMN_INLINE int CProgressCtrl::StepIt() { ASSERT(::IsWindow(m_hWnd)); return (int) ::SendMessage(m_hWnd, PBM_STEPIT, 0, 0L); }
-> Wenn Du z.B. weißt ein CWnd* pWnd ist ein ProgressCtrl, dann geht folgendes definitiv:
CWnd* pWnd = ... CProgressCtrl* pCtrl = (CProgressCtrl*)pWnd;
Nach Fragen?
-
Jepp
Das ich den cast machen kann hab ich ja gesehen. Rene und Hume haben sich ja ausführlich darüber unterhalten.
Was ich aber nicht verstehe ist warum der dynamic_cast nicht geht.
Denn das CWnd das ich von GetDlgItem bekomme ist doch das CWnd das im MFC Control steckt ?!
-
Tja, hab mal kurz in die MSDN geschaut, scheint wohl extra so gewollt zu sein, dass das mit dynamic_cast nicht geht. Da ich aber auch nicht wirklich erkennen kann, wozu dynamic_cast wirklich gut sein soll, bzw. ob an dem Ausdruck
A* pA = dynamic_cast<A*>(pB);
irgendetwas besser ist, als bei
A* pA = (A*)pB;
wüsste ich nicht warum ich ersteren verwenden sollte. Zumal die zweite Form deutlich kürzer ist.