Fortschrittsanzeige



  • Wenn es temporär ist hängt das CWnd doch dann in die Luft?!

    Nö, es liegt in einer temporären HandleMap, wo es irgendwann wieder rausgelöscht wird.

    Die Methode gehört doch zu einer Instanz ?!?

    Nur für den Compiler.

    Die Adresse der Funktion muss es doch irgendwo geben, also zu einer Instanz gehören ?

    Die Adressen der Funktionen gibts nur ein einziges Mal, egal, wieviele Instanzen einer Klasse Du anlegst.

    Wenny Polymorph wäre würde der dynamic_cast gehen ?!

    dynamic_cast funzt nur mit virtuellen Funktionen! Hat CProgressCtrl sowas? Ich denke nicht

    [ Dieser Beitrag wurde am 27.03.2003 um 16:13 Uhr von RenéG editiert. ]



  • 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 verstehen 🤡

    Wenn 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.



  • @HumeSikkins

    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 0x25242322

    Was macht nun die Funktion func():
    mov eax, dword ptr [this]
    mov [eax], 1

    Wir 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 ?!


Anmelden zum Antworten