Bug in RAD Studio 2007 (C++) und typeid mit VCL-Klassen?



  • Hallo,

    ich habe folgendes Problem:
    Ich will eine Abwandlung von Loki::FnDispatcher zu benutzen. Dieser benutzt typeid zur Typidentifizierung - was soweit auch gut klappt, auch mit VCL-Klassen.

    ABER - die typeid des statischen Typs unterscheidet sich von der des dynamischen Typs, wenn der Typ in einem Namespace liegt 😮

    Beispiel:
    GUI::TMyForm* form = new GUI::TMyForm( Owner);
    Loki::TypeInfo info1( typeid(GUI::TMyForm));
    Loki::TypeInfo info2( typeid(*form));
    assert( info1 == info2);

    Btw:
    info1.name() -> "GUI::TMyForm"
    info2.name() -> "TMyForm"

    Das schlägt fehl. Entferne ich den Namespace, geht es. Mit Nicht-VCL-Klassen funktioniert es auch mit Namespace. Namespaces sollten an der Stelle kein Problem sein, da z.B. TForm wie auch alle anderen VCL-Klassen ja selbst auch in einem Namespace liegt.

    Ist das ein Bug (sieht IMHO nach einer halbherzigen Umsetzung von typeid für VCL-Klassen aus)? Mache ich irgendwas falsch? Kennt jemand das Problem und/oder einen Workaround? An den Stellen wo typeid benutzt wird, ist überall artig <typeinfo> eingebunden.



  • 7H3 N4C3R schrieb:

    Ist das ein Bug (sieht IMHO nach einer halbherzigen Umsetzung von typeid für VCL-Klassen aus)?

    Ja, so sieht es aus. Das Problem ist, daß für Delphi-Klassen (also alles von TObject Abgeleitete) nur Delphi-RTTI erzeugt wird, und die scheint keine Namespaces zu berücksichtigen. typeid(<dynamisches VCL-Objekt>).name() überprüft zunächst, ob es sich überhaupt um eine Delphi-Klasse handelt; falls ja, wird der Typname über TObject::ClassName() erfragt. (Was genau diese Funktion macht, ist in der Runtime-Library in $(BCB)\source\cpprtl\Source\except\xxtype.cpp nachlesbar.)

    7H3 N4C3R schrieb:

    Kennt jemand das Problem und/oder einen Workaround?

    Es ist zwar nicht ohne weiteres möglich, die Namespaces beim dynamischen typeid hinzufügen, aber dafür kann man einen Ersatz für das statische typeid schaffen, der keinen Namespace präfiziert. Das ist allerdings implementationsspezifisch und funktioniert nur, solange CodeGear in diesem Teil der Implementation von std::type_info keine Änderungen vornimmt (wovon aber nicht ausgegangen werden muß).

    Dazu binde einfach das folgende als Headerdatei in deinen Code ein:

    // delphitypeinfo.hpp
    
    #ifndef _DELPHITYPEINFO_HPP
    #define _DELPHITYPEINFO_HPP
    
    #include <typeinfo>
    
    #if defined (__BCPLUSPLUS__)
    
    namespace detail
    {
        // aus xxtype.cpp
    #pragma option  push    -a1
    class type_info_hash : public std::type_info
    {
    public:
        void            *vmt;
        type_info_hash  *next;
        mutable char    *name;
    
        type_info_hash(void *_tpp, void *_vmt, type_info_hash *_next)
         : std::type_info((tpid *)_tpp)
        {
            vmt  = _vmt;
            next = _next;
            name = 0;
        }
    };
    #pragma option  pop
    };
    
    template <class T>
        const std::type_info& _static_delphi_typeid (void)
    {
        static detail::type_info_hash static_typeinfo (0, __classid (T), 0);
        return static_typeinfo;
    }
    #define static_delphi_typeid(T) _static_delphi_typeid <T> ()
    #else
    #define static_delphi_typeid(T) typeid (T)
    #endif
    
    #define static_typeid(T) typeid (T)
    
    template <typename T>
        const std::type_info& dynamic_typeid (T& obj)
    {
        return typeid (obj);
    }
    
    #endif // _DELPHITYPEINFO_HPP
    

    Verwenden kannst du das so:

    #include <delphitypeinfo.hpp>
    ...
    GUI::TMyForm* form = new GUI::TMyForm (Owner);
    Loki::TypeInfo info1 (static_delphi_typeid (GUI::TMyForm));
    Loki::TypeInfo info2 (dynamic_typeid (*form));
    assert (info1 == info2);
    

    Nun gilt:
    info1.name() -> "TMyForm"
    info2.name() -> "TMyForm"

    Und da std::type_info::operator == so implementiert ist:

    // xxtype.cpp, Z. 582ff
    bool __cdecl type_info::operator==(const type_info & other) const
    {
        if (tpp == other.tpp)
            return true;
        if (        tpp && (((tpidPtr)      tpp)->tpMask & TM_LOCALTYPE)
           || other.tpp && (((tpidPtr)other.tpp)->tpMask & TM_LOCALTYPE))
            return false;
        return strcmp(name(), other.name()) == 0;
    }
    

    sollte auch assert (info1 == info2) funktionieren, da zwar nicht die std::type_info-Objekte, aber zumindest die Namen identisch sind.

    Edit: Schönheitsfehler



  • Danke für die ausführliche Antwort! 👍 Wenn DelphiObject.ClassName() bemüht wird, ist natürlich klar, warum es nicht gehen kann.

    Ich hätte allerdings nicht erwartet, dass das Implementierungsdetail von std::type_info "offen" zugänglich ist. Das werde ich mir mal zu gemüte führen.

    Danke auch für den Code. Da das hier allerdings produktiv eingesetzt werden soll, wäre mir das eine etwas zu riskante Sache, vor allem weil dann auch nur ich primär darüber Bescheid weiß. 🙂

    Auf jeden Fall ist es mir vom Verständnis her jetzt klar. 💡 Ich denke ich werde wohl aber hierfür die Lösung ohne Namespaces benutzen. .oO(auch wenn ich hierauf vielleicht genausowenig eine Sicherheit habe, dass es nach dem nächsten Patch noch funktioniert)

    Edith
    So bleibt der Code zumindest überall gleich lesbar. Wobei ich Loki::BasicDispatcher auch beibringen könne, auf Konvertierbarkeit nach TObject zu testen und jenachdem static_delphi_typeid und dynamic_typeid zu bemühen. *grübel*



  • 7H3 N4C3R schrieb:

    Ich hätte allerdings nicht erwartet, dass das Implementierungsdetail von std::type_info "offen" zugänglich ist. Das werde ich mir mal zu gemüte führen.

    Die gesamte RTL ist offen zugänglich, also neben der C- und C++-Standard-Library auch die Implementationen von Exception-Handling, RTTI, Startup-Code, Debugger-Hooks, Memory-Manager, Name-Unmangler etc. Die Lektüre kann ich wärmstens empfehlen 👍

    7H3 N4C3R schrieb:

    Ich denke ich werde wohl aber hierfür die Lösung ohne Namespaces benutzen. .oO(auch wenn ich hierauf vielleicht genausowenig eine Sicherheit habe, dass es nach dem nächsten Patch noch funktioniert)

    Du meinst, daß du deine Klassen einfach nicht mehr in Namespaces packst? Das sollte völlig unabhängig von der Compilerversion funktionieren.

    Oder willst du nun doch meine Lösung nutzen? Die ist zwar prinzipiell compilerabhängig und bedarf daher hinreichender Dokumentation, aber daß dieses Detail sich ändert, ist äußerst unwahrscheinlich (es ist schlicht kein Anlaß gegeben; 1996, also während der Entwicklung von C++Builder 1.0, wurde dieser Code zum letzten Mal verändert).

    7H3 N4C3R schrieb:

    Wobei ich Loki::BasicDispatcher auch beibringen könne, auf Konvertierbarkeit nach TObject zu testen und jenachdem static_delphi_typeid und dynamic_typeid zu bemühen. *grübel*

    Das könnte sich mit boost::type_traits::is_base_of machen lassen. Ich versuchs mal.



  • audacia schrieb:

    Die gesamte RTL ist offen zugänglich, also neben der C- und C++-Standard-Library auch die Implementationen von Exception-Handling, RTTI, Startup-Code, Debugger-Hooks, Memory-Manager, Name-Unmangler etc. Die Lektüre kann ich wärmstens empfehlen 👍

    Oh das klingt ziemlich gut. Ich glaube das muss ich mir mal detailliert anschauen. 🙂 Du arbeitest nicht zufällig für Borland/CodeGear? 😃

    audacia schrieb:

    Du meinst, daß du deine Klassen einfach nicht mehr in Namespaces packst? Das sollte völlig unabhängig von der Compilerversion funktionieren.

    Ja, genau. Das ist denke ich die einfachste und sicherste (vor allem auch in dem Sinne, dass andere Leute damit arbeiten) Variante. Den Verlust der Namespaces kann ich wohl verkraften.

    audacia schrieb:

    Oder willst du nun doch meine Lösung nutzen? Die ist zwar prinzipiell compilerabhängig und bedarf daher hinreichender Dokumentation, aber daß dieses Detail sich ändert, ist äußerst unwahrscheinlich (es ist schlicht kein Anlaß gegeben; 1996, also während der Entwicklung von C++Builder 1.0, wurde dieser Code zum letzten Mal verändert).

    Es juckt mir ja schon ziemlich unter den Fingern. Aber für was produktives ist mir das wohl doch zu heiß. 😃 Werde das aber mal zuhause genauer begutachten. Vielleicht schafft man es ja auch, den Namespace noch in's std::type_info zu integrieren. Müsste mal drüber nachdenken, ob man die dynamische Typ-Info nicht irgendwie mit der statischen abgeglichen bekommt.

    audacia schrieb:

    Das könnte sich mit boost::type_traits::is_base_of machen lassen. Ich versuchs mal.

    In Modern C++ Design gibt's eine ganz ähnliche Lösung. Wenn ich jetzt noch Boost installieren will, werde ich wohl geschlagen *fg*



  • 7H3 N4C3R schrieb:

    Du arbeitest nicht zufällig für Borland/CodeGear? 😃

    Nein, zufällig nicht 😉

    audacia schrieb:

    Vielleicht schafft man es ja auch, den Namespace noch in's std::type_info zu integrieren. Müsste mal drüber nachdenken, ob man die dynamische Typ-Info nicht irgendwie mit der statischen abgeglichen bekommt.

    Das ist auch nicht ganz auszuschließen. Die Typinformation existiert ja ganz offensichtlich (sonst wäre sie in der statischen Variante nicht verfügbar), aber über ein Objekt daranzukommen scheint auch den RTL-Autoren zu schwierig gewesen zu sein:

    // xxtype.cpp, Z. 657ff, __GetTypeInfo():
    		if	(aptpidPtr->tpClass.tpcFlags & CF_DELPHICLASS)
    		{
    			//	This is a delphi class - we can't find the runtime
    			//	rtti easily and instead use the vtable to get the class name
    
    			mdtpidPtr = 0;
    		}
    ...
    

    audacia schrieb:

    In Modern C++ Design gibt's eine ganz ähnliche Lösung. Wenn ich jetzt noch Boost installieren will, werde ich wohl geschlagen *fg*

    Du brauchst ja nicht gleich Boost installieren; es reicht ja, wenn du nachschaust, wie es dort implementiert ist. Es dürfte sich dabei ja nicht um mehr als ein paar Zeilen handeln.



  • audacia schrieb:

    7H3 N4C3R schrieb:

    Du arbeitest nicht zufällig für Borland/CodeGear? 😃

    Nein, zufällig nicht 😉

    Also eher absichtlich? 🤡



  • Auch das nicht :p



  • Hihi dieses Stück Code hatte ich vorhin auch schon gesehen und mich ein wenig darüber amüsiert. 🙂 Ich frage mich jetzt nur ein wenig, ob eine von einer VCL-Klasse abgeleitete Klasse jetzt als Delphi-Klasse betrachtet wird (oder vielleicht sogar dahin gehend transformiert und kompiliert wird). Virtuelle Konstruktoren scheinen damit ja immerhin auch zu funktionieren.

    Und ja, letzteres ist ein einfacher Test Identität mit void und Konvertierbarkeit. So ein Konstrukt habe ich hier auch schon im Einsatz.



  • Hallo,

    7H3 N4C3R schrieb:

    Ich frage mich jetzt nur ein wenig, ob eine von einer VCL-Klasse abgeleitete Klasse jetzt als Delphi-Klasse betrachtet wird (oder vielleicht sogar dahin gehend transformiert und kompiliert wird).

    Ja, wird sie.

    7H3 N4C3R schrieb:

    Und ja, letzteres ist ein einfacher Test Identität mit void und Konvertierbarkeit. So ein Konstrukt habe ich hier auch schon im Einsatz.

    Na wunderbar. Dann kannst du dir doch ein generisches static_typeid basteln 😉

    Edit:
    Mit der folgenden Anleihe bei Boost funktioniert es bei mir:

    // delphitypeinfo.hpp
    
    #ifndef _DELPHITYPEINFO_HPP
    #define _DELPHITYPEINFO_HPP
    
    #include <typeinfo>
    
    #if defined (__BCPLUSPLUS__)
    
    #include <System.hpp> // System::TObject
    
    namespace detail
    {
        // from cpprtl/Source/except/xxtype.cpp
    #pragma option push -a1 /* 1-byte alignment */
    class type_info_hash : public std::type_info
    {
    public:
        void            *vmt;
        type_info_hash  *next;
        mutable char    *name;
    
        type_info_hash(void *_tpp, void *_vmt, type_info_hash *_next)
         : std::type_info((tpid *)_tpp)
        {
            vmt  = _vmt;
            next = _next;
            name = 0;
        }
    };
    #pragma option pop
    
        // from boost/type_traits/is_convertible.hpp
    // Copyright 2000 John Maddock (john@johnmaddock.co.uk)
    // Copyright 2000 Jeremy Siek (jsiek@lsc.nd.edu)
    // Copyright 1999, 2000 Jaakko J„rvi (jaakko.jarvi@cs.utu.fi)
    /*
    Boost Software License - Version 1.0 - August 17th, 2003
    
    Permission is hereby granted, free of charge, to any person or organization
    obtaining a copy of the software and accompanying documentation covered by
    this license (the "Software") to use, reproduce, display, distribute,
    execute, and transmit the Software, and to prepare derivative works of the
    Software, and to permit third-parties to whom the Software is furnished to
    do so, all subject to the following:
    
    The copyright notices in the Software and this entire statement, including
    the above license grant, this restriction and the following disclaimer,
    must be included in all copies of the Software, in whole or in part, and
    all derivative works of the Software, unless such copies or derivative
    works are solely in the form of machine-executable object code generated by
    a source language processor.
    
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
    SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
    FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
    ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    DEALINGS IN THE SOFTWARE.
    */
    
    // BEGIN boost
    
    typedef char yes_type;
    struct no_type
    {
       char padding[8];
    };
    
    struct any_conversion
    {
        template <typename T> any_conversion(const volatile T&);
        template <typename T> any_conversion(T&);
    };
    
    template <typename T> struct checker
    {
        static no_type _m_check(any_conversion ...);
        static yes_type _m_check(T, int);
    };
    
    template <typename From, typename To>
    struct is_convertible_basic_impl
    {
        static From _m_from;
        static bool const value = sizeof(checker<To>::_m_check(_m_from, 0) )
            == sizeof(yes_type);
    };
    
    // END boost
    
    template <bool IsDelphiClass>
        struct _static_typeid_struct
    {
        template <class T>
            static inline const std::type_info& _static_typeid (void);
    };
    
    template <>
        struct _static_typeid_struct <1>
    {
        template <class T>
            static inline const std::type_info& _static_typeid (void)
        {
            static detail::type_info_hash static_typeinfo (0, __classid (T), 0);
            return static_typeinfo;
        }
    };
    template <>
        struct _static_typeid_struct <0>
    {
        template <class T>
            static inline const std::type_info& _static_typeid (void)
        {
            return typeid (T);
        }
    };
    
    } // namespace detail
    
    #define static_typeid(T)        detail::_static_typeid_struct               \
        <detail::is_convertible_basic_impl <const T*, const TObject*>::value>   \
        ::_static_typeid <T> ()
    #define static_delphi_typeid(T) detail::_static_typeid_struct <1>   \
        ::_static_typeid <T> ()
    #define static_cpp_typeid(T)    detail::_static_typeid_struct <0>   \
        ::_static_typeid <T> ()
    
    #else
    #define static_typeid(T)        typeid (T)
    #define static_delphi_typeid(T) typeid (T)
    #define static_cp_typeid(T)     typeid (T)
    #endif
    
    template <typename T>
        const std::type_info& dynamic_typeid (T& obj)
    {
        return typeid (obj);
    }
    
    #endif // _DELPHITYPEINFO_HPP
    


  • Appendix zum Abschluß der Angelegenheit:

    7H3 N4C3R schrieb:

    ABER - die typeid des statischen Typs unterscheidet sich von der des dynamischen Typs, wenn der Typ in einem Namespace liegt 😮

    Beispiel:
    GUI::TMyForm* form = new GUI::TMyForm( Owner);
    Loki::TypeInfo info1( typeid(GUI::TMyForm));
    Loki::TypeInfo info2( typeid(*form));
    assert( info1 == info2);

    Btw:
    info1.name() -> "GUI::TMyForm"
    info2.name() -> "TMyForm"

    Das Problem wurde in C++Builder XE2 endlich behoben. Dies ist möglich geworden durch die Einführung von Unit Scopes in Delphi (aka Namespaces in C++), so daß der Namespace bzw. Unit-Scope einer Klasse jetzt Teil der RTTI ist und zur Laufzeit ausgelesen werden kann - wie etwa von std::type_info::name():

    // xxtype.cpp, l. 750ff
    const
    char * __cdecl type_info::name    () const
    {
    	type_info_hash *h = (type_info_hash *)this;
    
    	if	(!h->tpp)
    	{
    		if	(h->name)
    			return	h->name;
    
    		if	(!h->vmt)
    			return	"<notype>";
    
    		// This is the Delphi class case.
    		// Since XE2, the namespace is encoded in the UnitName field for Delphi classes.
    		// The encoding format for a namespace foo::bar in baz.cpp is "@foo.bar:baz".
    		// To get a C++ namespace from this, we need to substitute '.' with "::".
    		// For classes declared in Delphi, the unit name equals the scope name. In this
    		// case we also need to enforce the C++ capitalization of Delphi namespaces (first
    		// letter uppercase, all subsequent letters lowercase).
    
    		[...] // Implementation
    
    		memcpy (pos, className + 1, (unsigned char) className[0]);
    		h->name[fullNameLength] = 0;
    
    		return	h->name;
    	}
    
    	return  __typeIDname(tpp);
    }
    

Anmelden zum Antworten