Kann der BCB-Debugger die Klasse eines Objekts anzeigen?



  • Hallo,

    ich hab schon in der Hilfe und hier im Forum gesucht, aber nichts dazu gefunden, wie man sich im Debugger anzeigen lassen kann, zu welcher Klasse ein Objekt gehört. Ich habe ein Array mit Pointern auf Klassen, die in eine Vererbungshierarchie eingebettet sind und da wäre es für mich wichtig zu wissen, welcher Klasse genau das Objekt angehört.
    Geht das im Borland Builder überhaupt?
    Ich habe den BCB 5.

    Danke schonmal
    susie



  • Theoretisch könntest du im "Auswerten/Ändern"-Dialog oder unter "Überwachte Ausdrücke" (hier "Seiteneffekte zulassen" aktivieren!) einen Ausdruck wie
    typeid (*Sender).name ()
    eingeben. In der Praxis erhältst du dabei die folgende Fehlermeldung:

    C++Builder 6 schrieb:

    E2368 RTTI steht für die Ausdrucksauswertung nicht zur Verfügung

    Allerdings läßt sich der Evaluator austricksen: Sofern es sich um VCL-Klassen handelt (wenn sie von TObject abgeleitet sind), benutzt du stattdessen den folgenden Ausdruck:
    Sender->ClassName ()

    Falls du eine nicht von TObject abgeleitete Basisklasse hast, nimm so etwas in deinen Code auf:

    #ifdef _DEBUG
    const char* getNameOf (const MyBaseClass* obj)
    {
        if (!obj)
            return 0;
        return typeid (*obj).name ();
    }
    
    void MyBaseClassStartupFunc (void)
    { getNameOf (static_cast <MyBaseClass*> (0)); }
    
    #pragma startup MyBaseClassStartupFunc // damit die Funktion nicht wegoptimiert wird und für den Debugger zur Verfügung steht
    #endif
    

    Dann kannst du, vorausgesetzt, MyBaseClass ist polymorph, mit dem Ausdruck
    getNameOf (pointer_to_obj)
    den Typ des Objekts erhalten.



  • Super, danke, ich dachte schon, dafür gibts keine Lösung...

    Trotzdem hab ich noch Probleme:
    1. Wo schreib ich das hin, in die .h- oder .cpp-Datei, wo MyBaseClass deklariert/definiert ist? (wenn in .h, wie mach ich das mit der Schachtelung hinsichtlich #ifndef...#define...#endif?)
    2. unabhängig davon bekomme ich beim übersetzen in der zeile 6 die Fehlermeldung
    E2470 Für typeid muß der Header <typeinfo> einbezogen werden
    E2315 'name' ist kein Element von 'type_info', weil der Typ nicht definiert ist

    Bis auf Ersetzen des Namens von MyBaseClass hab ich den Code 1:1 übernommen. Und typeid hab ich auch sonst im Code (aber ohne .name), aber das übersetzt ohne Probleme.



  • susie schrieb:

    2. unabhängig davon bekomme ich beim übersetzen in der zeile 6 die Fehlermeldung
    E2470 Für typeid muß der Header <typeinfo> einbezogen werden
    E2315 'name' ist kein Element von 'type_info', weil der Typ nicht definiert ist

    Bis auf Ersetzen des Namens von MyBaseClass hab ich den Code 1:1 übernommen. Und typeid hab ich auch sonst im Code (aber ohne .name), aber das übersetzt ohne Probleme.

    Das Problem kannst du lösen, indem du <typeinfo> einbindest 😉
    typeid ist teils Compilermagie, teils bibliotheksgestützt. Offenbar kann der Compiler einen einfachen Vergleich zweier typeid()s ohne die Headerdatei übersetzen, für den Aufruf einer Methode des zurückgegebenen Objekts benötigt er aber die Klassendeklaration.

    susie schrieb:

    1. Wo schreib ich das hin, in die .h- oder .cpp-Datei, wo MyBaseClass deklariert/definiert ist? (wenn in .h, wie mach ich das mit der Schachtelung hinsichtlich #ifndef...#define...#endif?)

    Es genügt, daß die Funktion überhaupt existiert; sie muß nicht in der Headerdatei deklariert oder gar definiert sein.

    Um die Umstände zu vereinfachen, falls du das für mehrere Typen brauchen kannst, wäre es ratsam, eine Headerdatei wie diese zu erstellen:

    #ifndef _DEBUG_GETNAMEOF_HPP
    #define _DEBUG_GETNAMEOF_HPP
    
    #include <typeinfo>
    
    #ifdef _DEBUG
    template <class C>
        struct _GetNameOfInstantiatorContainer
    {
        struct Instantiator
        {
            Instantiator (void)
            { getNameOf (static_cast <C*> (0)); }
        };
    
        static Instantiator inst;
    };
    
    #define DEFINE_GETNAMEOF(Type)          \
    const char* getNameOf (const Type* obj) \
    {                                       \
        if (!obj)                           \
            return 0;                       \
        return typeid (*obj).name ();       \
    }                                       \
                                            \
    _GetNameOfInstantiatorContainer <Type>::Instantiator \
        _GetNameOfInstantiatorContainer <Type>::inst;
    #else
    #define DEFINE_GETNAMEOF(Type)
    #endif
    
    #endif // _DEBUG_GETNAMEOF_HPP
    

    Dann erstellst du für dein Projekt einfach eine .cpp-Datei, die lediglich folgendes enthält:

    #include "_debug_getnameof.hpp"
    
    #include "MyBaseClass.hpp"
    DEFINE_GETNAMEOF(MyBaseClass)
    
    #include "AnotherBaseClass.hpp"
    DEFINE_GETNAMEOF(SomeNamespace::AnotherBaseClass)
    
    ...
    

    So kannst du getNameOf() mit minimalem Aufwand für beliebige Klassen implementieren.



  • Hallo,

    danke nochmal, jetzt funktionierts 🙂
    Was meinst Du damit, wenn ich das für mehrere Typen brauchen kann? Wenn ich z.B. oben ein Array mit Pointern auf A hatte, und eine weitere klasse B habe, die von A erbt und jetzt ein weiteres Array mit Pointern direkt auf B deklariert habe (so daß ich nur eine Teilhierarchie von A betrachte), muß ich dann das getNameOf auch für B machen? (hab nicht ganz verstanden, was ich da mit dieser StartupFunc gemacht habe und Deinen zweiten allgemeinen Vorschlag verstehe ich noch weniger, übersteigt doch meine bisherigen C++ - Kenntnisse). Wäre nicht so tragisch, weil, wenn überhaupt, es bei mir maximal 4 solche Teilhierarchien geben würde.

    Was noch wichtiger wäre für mich, inwiefern man auf die Instanzvariablen der Unterklassen zugreifen kann. Im Debug-Inspektor kann ich nur auf die Variablen der Basisklasse zugreifen. Klar kann ich da bei jedem Mal es manuell einstellen, indem ich mir erst den Typ geben lasse und dann im Auswertefenster den Pointer entsprechend caste und auf die interessierenden Variablen zugreife. Aber das ist doch ein bißchen nervig, weil es beim Debuggen ja oft nicht so zielgerichtet vorgeht/vorgehen kann. Gibt es da keine einfachere Lösung?
    Ehrlich gesagt wundert mich das, daß das nicht automatisch geht, weil das doch was ganz wesentliches bei objektorientierter Programmierung ist (kenn ich auch anders von den Entwicklungsumgebungen in Smalltalk und Java)



  • susie schrieb:

    Ehrlich gesagt wundert mich das, daß das nicht automatisch geht, weil das doch was ganz wesentliches bei objektorientierter Programmierung ist (kenn ich auch anders von den Entwicklungsumgebungen in Smalltalk und Java)

    Na komm, BCB5 ist ja auch nicht mehr der jüngste (auch wenn es in Smalltalk schon immer möglich war). Interessant wäre es, ob die neueren Versionen ein solches Vorgehen unterstützen.



  • Na ja, stimmt schon, aber die Objektorientierung hatte C++ ja schon von Anfang an... Ist mein Anliegen, Objekte zu debuggen, sowas ungewöhnliches (oder ungewöhnlich zu Zeiten der BCB5-Entwicklung)?

    Interessant wäre es, ob die neueren Versionen ein solches Vorgehen unterstützen.

    Stimmt, das ist ne gute Frage. Aber mehr als BCB6 geht bei uns leider eh nicht.

    Kann man die Funktionsweise des Debuggers eigentlich irgendwo nachlesen? Ich hab den Richard Kaiser hier "C++ mit dem Borland C++ Builder", aber da steht zum Debugger fast nichts drin.



  • susie schrieb:

    Was meinst Du damit, wenn ich das für mehrere Typen brauchen kann? Wenn ich z.B. oben ein Array mit Pointern auf A hatte, und eine weitere klasse B habe, die von A erbt und jetzt ein weiteres Array mit Pointern direkt auf B deklariert habe (so daß ich nur eine Teilhierarchie von A betrachte), muß ich dann das getNameOf auch für B machen? (hab nicht ganz verstanden, was ich da mit dieser StartupFunc gemacht habe und Deinen zweiten allgemeinen Vorschlag verstehe ich noch weniger, übersteigt doch meine bisherigen C++ - Kenntnisse). Wäre nicht so tragisch, weil, wenn überhaupt, es bei mir maximal 4 solche Teilhierarchien geben würde.

    Damit meinte ich, daß es einfacher wird, wenn du mehrere voneinander unabhängige Basisklassen hast. In deinem Fall müßtest du aber für B keine zusätzliche Definition anfügen, da ein B* sich implizit in A* konvertieren läßt.

    Mein (zugegebenermaßen etwas undurchsichtiger) Code sorgt mithilfe von Templates und statischen Variablen dafür, daß die getNameOf-Funktion in der Überladung für einen speziellen Typen mindestens einmal im Code aufgerufen wird, damit der Linker sie nicht hinauswirft und sie dem Debugger zur Laufzeit zur Verfügung steht.

    susie schrieb:

    Was noch wichtiger wäre für mich, inwiefern man auf die Instanzvariablen der Unterklassen zugreifen kann. Im Debug-Inspektor kann ich nur auf die Variablen der Basisklasse zugreifen. Klar kann ich da bei jedem Mal es manuell einstellen, indem ich mir erst den Typ geben lasse und dann im Auswertefenster den Pointer entsprechend caste und auf die interessierenden Variablen zugreife. Aber das ist doch ein bißchen nervig, weil es beim Debuggen ja oft nicht so zielgerichtet vorgeht/vorgehen kann. Gibt es da keine einfachere Lösung?

    Vermutlich geht das so ohne weiteres nicht einfacher.

    susie schrieb:

    Ehrlich gesagt wundert mich das, daß das nicht automatisch geht, weil das doch was ganz wesentliches bei objektorientierter Programmierung ist (kenn ich auch anders von den Entwicklungsumgebungen in Smalltalk und Java)

    Ja, das eine oder andere hilfreiche Debugger-Feature könnte man sich in der Delphi-/C++-Welt schon noch von anderen Sprachen abschauen.

    Ich halte es übrigens für nicht ganz ausgeschlossen, daß sich so etwas in Form eines IDE-Plug-ins implementieren ließe. (wie z.B. dieses hier).

    Welches Problem habt ihr eigentlich mit C++Builder >6?

    susie schrieb:

    Kann man die Funktionsweise des Debuggers eigentlich irgendwo nachlesen? Ich hab den Richard Kaiser hier "C++ mit dem Borland C++ Builder", aber da steht zum Debugger fast nichts drin.

    Gib im Index der C++Builder-Dokumentation einfach mal "Debugger" ein 😉



  • Hallo,

    danke nochmal für Deine Erklärung. Ich verstehe es zwar trotzdem noch nicht so richtig, da fehlen mir einfach die Grundlagen, aber da ich nur eine Klassenhierarchie habe und Deiner Antwort zufolge es mit dem ersten Ansatz dann reicht, ist das ja nicht so schlimm (bzw. kann ich mir bei Bedarf nochmal zu Gemüte führen, wenn ich es doch noch brauche).

    Das Problem mit C++Builder >6 ist, daß wir eine spezielle Software von jemand anderem zur Visualisierung unserer Ergebnisse bzw. Programmläufe haben (was fachspezifisches), die setzt den BorlandBuilder 5 oder 6 voraus. Bei uns ist die Programmiererei halt nur Mittel zum Zweck und derjenige, der das Programm mal für BCB5 oder 6 gemacht hat, hat es ausnahmsweise so quasi als Hobby mal gemacht (dabei schätz ich mal, daß das prinzipiell gar nicht sooo schwierig zu machen ist)
    Ich weiß gar nicht, wie weit ich diese Zusatzsoftware zukünftig noch brauche, aber das kann ich jetzt noch nicht absehen. Wenn ich es nicht brauche, bräuchte ich vom Borland Builder nur noch die Zeitreihen. Dann könnte ich ggf. auch überlegen, eine höhere BCB-Version zu verwenden oder aber auch eine andere IDE. Mal gucken, wie weit ich so jetzt erstmal komme.

    Gib im Index der C++Builder-Dokumentation einfach mal "Debugger" ein

    Ja, in der Hilfe gucke ich schon ab und zu nach, aber um erstmal eine Übersicht zu bekommen, was es da alles gibt, finde ich die Hilfe nicht sooo toll (z.B. wie das alles mit den Stacks funktioniert- bin bisher mal drüber gestolpert, aber gebraucht hab ich das noch nicht.)


Anmelden zum Antworten