TFrame, ich möchte zusätzlich von Interface erben



  • Ich habe eine Klasse, die von TFrame erbt (erstellt mit Designer), diese bräuchte aber noch ein Interface von der sie erbt.
    Aber wenn ich dem Interface, ein virtuellen Destruktor gebe, erhalte ich Linker Probleme.
    Hab ich was falsch gemacht?

    //---------------------------------------------------------------------------
    class FrameInterface
    {
    public:
        // das hier einkommentieren -> Linker Fehler
        //virtual ~FrameInterface() __attribute__((fastcall)) = default;
    
        virtual void translate() = 0;
    };
    //---------------------------------------------------------------------------
    
    //---------------------------------------------------------------------------
    class THeaderOptionsFrame : public TFrame
                              , public FrameInterface
    {
    __published:	// Von der IDE verwaltete Komponenten
    	TComboBox *HeaderLayer;
    	TLabel *Label1;
    	void __fastcall Label1Click(TObject *Sender);
    private:	// Benutzer-Deklarationen
    public:		// Benutzer-Deklarationen
    	// ~THeaderOptionsFrame() __attribute__((fastcall)) = default;
    	void translate() override;
    	__fastcall THeaderOptionsFrame(TComponent* Owner);
    };
    //---------------------------------------------------------------------------
    extern PACKAGE THeaderOptionsFrame *HeaderOptionsFrame;
    //---------------------------------------------------------------------------
    

    "[ilink32 Fehler] Error: Nicht auflösbares externes '__fastcall _ZTh-696_THeaderOptionsFrame::~_ZTh-696_THeaderOptionsFrame()' referenziert von ***\WIN32\DEBUG\HEADER_OPTIONS.OBJ"
    also "unresolved external..."

    btw: ich kriege ich meine IDE (Berlin 10.1) nicht auf Englisch umgestellt, das Tool ignoriert mich einfach...



  • 5cript schrieb:

    Ich habe eine Klasse, die von TFrame erbt (erstellt mit Designer)...
    Hab ich was falsch gemacht?

    Man kann mich gerne korrigieren, aber nach meiner Erkenntnis geht es nicht. Bei allen auf Delphi basierenden Klassen (VCL-Datentypen) scheiterte mein Versuch der Mehrfachvererbung.

    Ich habe mir dann mit Templates ausgeholfen, die bei einer Klasse eine bestimmte Schnittstelle (Nicht im Sinne von Vererbung, sondern rein von der Methodensignatur her) erwartet hatten.



  • Hallo,

    Soviel wie ich weiß geht das auch nicht.
    Ich behelfe mir so, dass ich von TFrame eine abstrakte Basisklasse ableite die das Interface implementiert. Von der wiederum leite ich dann die konkreten Frames ab.



  • Delphi unterstützt keine echte Mehrfachvererbung, man kann allerdings Interfaces (Klassen, die nur pure virtual Methoden besitzen) vererben:

    struct __declspec( uuid( "{EC2265C9-9A41-4E52-9D28-F67613A93A2D}" ) ) IPrintable
    {
       virtual void print()   = 0;
       virtual void preview() = 0;
    };
    
    class MyFrame : 
       public TFrame,
       public IPrintable
    {
       ...
    }
    

    Über einen dynamic_cast kommt man da allerdings nicht wieder dran, sondern muss einige Verrenkungen übder das Delphi Typsystem machen (deshalb auch das __declspec( uuid (...) ) .

    template<typename DestT>
    static DestT delphi_dynamic_cast( TObject* obj )
    {
       if( obj )
       {
          TInterfaceEntry* Entry = obj->GetInterfaceEntry ( __uuidof( DestT ) );
    
          // funktioniert nur in 32bit Umgebungen, bei 64bit muss
          // man erst auf __int64 casten
          if( Entry ) return (DestT) ((int) obj + Entry->IOffset);
       }
       return nullptr;
    }
    

    Und so setzt man das ein:

    IPrintable* Printable = delphi_dynamic_cast<IPrintable>( theFrame );
    if( Printable ) 
    {
       Printable->print();
       Printable->preview();
    }
    


  • DocShoe schrieb:

    ...

    Wow, das ist irgendwie eklig, aber sehr gut zu wissen.
    Wenn ich das gleich gewusst hätte, hätte ich das so gemacht.

    Braunstein schrieb:

    ...

    Hmmm, da muss ich nochmal drüber nachdenken, wenn ich vor dem code sitze.

    Ich hatte in der Zwischenzeit ein Workaround mit templates gebaut.



  • DocShoe schrieb:

    Über einen dynamic_cast kommt man da allerdings nicht wieder dran, sondern muss einige Verrenkungen übder das Delphi Typsystem machen [...]

    Verzeih, aber das ist ja furchtbar. Zum Glück geht das viel einfacher, nämlich mit interface_cast<>() .



  • Aber interface_cast verlangt, dass die Methoden QueryInterface, AddRef und Release implementiert sind, wie bei den Delphi Interfaces.

    class THeaderOptionsFrame : public TFrame
                              , public TCppInterfacedObject<IOptionsFrame>
    

    ist ja schon wieder quark, weil das ist wieder Mehrfachvererbung.
    (Für folgenden code)

    __interface __declspec(uuid("{5898CCAF-1EE4-4EB4-A785-48920E5E97A5}")) IOptionsFrame
        : public IInterface
    {
    	virtual void translate() = 0;
    	virtual void setOwner(WikiElements::BasicElement* element) = 0;
    };
    

    Alternativ:

    __interface __declspec(uuid("{5898CCAF-1EE4-4EB4-A785-48920E5E97A5}")) IOptionsFrame
    {
    	virtual void translate() = 0;
    	virtual void setOwner(WikiElements::BasicElement* element) = 0;
    };
    

    resultiert in:

    [bcc32c Fehler] systobj.h(264): kein Member mit Namen 'AddRef' in 'IOptionsFrame'

    oder, dass THeaderOptionsFrame abstrakt ist, weil QueryInterface, AddRef und Release nicht implementiert sind.
    bei Aufruf von:

    auto* frame = element->getOptionsFrame(); // auto = THeaderOptionsFrame
    if (frame)
    {
    	// Ich hatte es kurz nachgeprüft: interface_cast <IOptionsFrame*> ist falsch.
    	interface_cast <IOptionsFrame> (frame)->translate();
    	// ...
    }
    

    EDIT: Zur Zeit benutze ich die Verrenkung von DocShoe



  • Und renne gleich wieder gegen eine Wand, weil: ???
    Habe jetzt ein weiteres Frame hinzugefügt und sobald ich das auch erben lasse vom Interface, dann erhalte ich untenstehenden Linkerfehler. -.-

    //---------------------------------------------------------------------------
    
    #ifndef text_optionsH
    #define text_optionsH
    //---------------------------------------------------------------------------
    #include "frame_interface.h"
    #include "../element_fwd.h"
    
    #include <System.Classes.hpp>
    #include <Vcl.Controls.hpp>
    #include <Vcl.StdCtrls.hpp>
    #include <Vcl.Forms.hpp>
    //---------------------------------------------------------------------------
    class TTextOptionsFrame : public TFrame
    						, public IOptionsFrame
    {
    __published:	// Von der IDE verwaltete Komponenten
    	TLabel *Label1;
    private:	// Benutzer-Deklarationen
    	bool translated_;
    	WikiElements::Text* owner_;
    public:		// Benutzer-Deklarationen
    	void translate();
    	void setOwner(WikiElements::BasicElement* owner);
    	__fastcall TTextOptionsFrame(TComponent* Owner);
    };
    //---------------------------------------------------------------------------
    extern PACKAGE TTextOptionsFrame *TextOptionsFrame;
    //---------------------------------------------------------------------------
    #endif
    

    [ilink32 Fehler] Error: Nicht auflösbares externes 'TTextOptionsFrame::' referenziert von D:\DEVELOPMENT_IWS\WIKI-PROJECT\WIKI-EDITOR-VCL\WIN32\DEBUG\TEXT.OBJ

    "externes 'TTextOptionsFrame::'" was soll das für nen Schmarn?



  • audacia schrieb:

    DocShoe schrieb:

    Über einen dynamic_cast kommt man da allerdings nicht wieder dran, sondern muss einige Verrenkungen übder das Delphi Typsystem machen [...]

    Verzeih, aber das ist ja furchtbar. Zum Glück geht das viel einfacher, nämlich mit interface_cast<>() .

    Witzigerweise habe ich das von dir 😉



  • 5cript schrieb:

    Aber interface_cast verlangt, dass die Methoden QueryInterface, AddRef und Release implementiert sind, wie bei den Delphi Interfaces.

    Natürlich. Interface-Implementierung bei Delphi-Klassen wird nur für COM-Interfaces unterstützt, dein Interface muß also von IUnknown erben. Das Implementieren von Interfaces, die nicht von IUnknown erben, wird nicht unterstützt. (Gab es dazu nicht mal eine Compilerwarnung? Vielleicht hat es die nicht zum Clang-Compiler geschafft?)

    5cript schrieb:

    class THeaderOptionsFrame : public TFrame
                              , public TCppInterfacedObject<IOptionsFrame>
    

    ist ja schon wieder quark, weil das ist wieder Mehrfachvererbung.

    Genau. Aber TFrame erbt von TComponent , welches IInterface (= IUnknown ) implementiert und also schon Implementationen der drei Methoden mitbringt.

    Hier erweist es sich als hinderlich, daß Delphi anders mit Methodenüberschreibungen umgeht als C++. Folgendes Beispiel:

    type
      TBase = class(TInterfacedObject)
        procedure Foo;
      end;
    
      IMyInterface = interface
        procedure Foo;
      end;
    
      TDerived = class(TBase, IMyInterface);
    

    In Delphi benutzt TDerived die geerbte (nicht notwendig virtuelle!) Methode Foo() von TBase , um implizit IMyInterface.Foo() zu implementieren. In C++ passiert das nicht, hier mußt du explizit an die geerbte Methode verweisen. Für den Spezialfall der IUnknown -Methoden gibt es dafür das Makro INTFOBJECT_IMPL_IUNKNOWN() :

    class TTextOptionsFrame : public TFrame
                            , public IOptionsFrame
    {
    public:
        INTFOBJECT_IMPL_IUNKNOWN(TFrame)
        ...
    };
    

    "externes 'TTextOptionsFrame::'" was soll das für nen Schmarn?

    Das ist die VMT zur Klasse TTextOptionsFrame . Den Linkerfehler verstehe ich gerade auch nicht; nimm doch mal TDUMP zur Hilfe, um nachzusehen, warum text.obj auf diese VMT verweist und warum text_options.obj das VMT-Symbol nicht exportiert.

    DocShoe schrieb:

    Witzigerweise habe ich das von dir 😉

    Erwischt 😃

    Genaugenommen hast du es aber nicht von mir, sondern gemeinsam mit mir aus dieserm QC-Report, auf den ich in der fraglichen Diskussion vor 8 Jahren verlinkt hatte. Die bessere Lösung heißt TObject::GetInterface<>(), was ich in demselben Post auch erwähne.

    Außerdem schrieb ich in demselben Thread drei Jahre später, daß es seit C++Builder XE den interface_cast<>() gibt, der Casts in beide Richtungen unterstützt und einem die ganzen haarigen Details abnimmt. Die Hoffnung, daß dadurch die furchtbaren Workarounds wieder verschwinden möchten, war wohl leichtfertig 🙂



  • (Die späte Antwort, weil ich nur sporadisch an diesem Projekt arbeite)

    Ich habe das mit der vtable Sache nicht "schnell genug" lösen können, deswegen habe ich den ganzen Kram über den Haufen geworfen und benutze jetzt ein Klassentemplate als Adapter.



  • 😮

    jetzt wo ich alles umgebaut habe ist mir aufgefallen wo der Fehler her kam,
    bzw ich wette es zu wissen.

    void TTextOptionsFrame::translate();
    

    war nicht aufgelöst. Das fehlte.

    bescheuerte unverständliche Fehlermeldung....