Dynamische Parameterliste mit Templates beim definieren von operator()



  • Hi!

    Ich habe hier eine Basisklasse, welche einen Funktionszeiger als Member enthält. Der Typ des Funktionszeigers wird beim Ableiten einer weiteren Klasse vom Template festgelegt.
    Hier mal ein Beispiel:

    Basisklasse

    template <typename D3D_Func_type>
    class D3D_Hook
    {
    public:
    	D3D_Func_type call_original; // <--- Funktionszeiger
    ...
    };
    

    Abgeleitete Klasse

    class D3D_Hook_EndScene : public D3D_Hook<EndScene_t>
    {
    ...
    };
    

    2 Funktionszeiger Typen als Beispiel

    HRESULT __stdcall EndScene(LPDIRECT3DDEVICE9 pDevice);
    typedef HRESULT(__stdcall* EndScene_t)(LPDIRECT3DDEVICE9);
    
    HRESULT __stdcall Reset(LPDIRECT3DDEVICE9 pDevice, D3DPRESENT_PARAMETERS* pPresentationParameters);
    typedef HRESULT(__stdcall* Reset_t)(LPDIRECT3DDEVICE9, D3DPRESENT_PARAMETERS*);
    

    Das Aufrufen des Funktionszeigers call_original(...) aus einer Instanz klappt wunderbar:

    D3D_Hook_EndScene pEndScene(...);
    
    pEndScene.call_original(...)
    

    Ich fände es jedoch sehr sexy, wenn ich durch definieren von operator() das .call_original weglassen könnte und direkt sowas wie:

    D3D_Hook_EndScene pEndScene(...);
    
    pEndScene(...)
    

    schreiben könnte.

    Was mir dabei jedoch Probleme macht, ist die Parameterliste beim definieren von operator() in der Basisklasse. Kann ich die irgendwie vom Template setzen lassen oder ist das, was ich da vor habe gar nicht möglich?
    Alternativ klappt das setzen von operator() in jeder abgeleiteten klasse, wo ich dann einfach call_original() aufrufe, doch muss ich dann jedesmal die parameter für den jeweiligen Typ in operator() für jede abgeleitete Klasse kopieren. Daher wäre es wie gesagt echt schick, wenn man das nur einmal in der basisklasse definieren müsste.

    Gruß Stephan



  • Es gibt seit C++11 so etwas in der Standardbibliothek.
    Das ganze nennt sich std::function und befindet sich im Header functional.
    Die Klasse ist ein Wrapper um eine aufrufbare Einheit (Klasse mit operator (), Funktionszeiger,...) und lässt sich wie eine Funktion behandlen.
    In deinem Beispiel müsstest du nur von std::function<ReturnType(Arg1, Arg2, ...)> ableiten und könntest operator().
    Aber Vererbung ist für so etwas in der Regel falsch und von std::* sollte man grundsätzlich nie ableiten (Ausnahmen sind std::exception und die Streams).
    Was bringt dir die Vererbung da also? Wozu brauchst du sie?



  • Im Grunde verwende ich die Ableitungen nur, um dem Nutzer schönere Typen anzubieten und die Kapselung ein wenig zu erhöhen.
    In der Implementierung einer Abgeleiteten Klasse rufe ich eigentlich nur den Konstruktor der Basisklasse mit dem Funktionszeigertyp und einer Zahl auf. Der Rest der Klasse ist leer:

    Ableitung

    class D3D_Hook_EndScene : public D3D_Hook<EndScene_t>
    {
    public:
    	D3D_Hook_EndScene(EndScene_t hook_func);
    };
    

    Implementierung des Konstruktors der Ableitung

    D3D_Hook_EndScene::D3D_Hook_EndScene( EndScene_t hook_func ) : D3D_Hook(42, hook_func) 
    {
        // leerer konstruktor
    }
    

    Hintergrund: Ich bastel eine Klasse um Direct3D Funktionen zu hooken. Die Zahl 42 von Parameter 1 ist der Index zur Adresse der EndScene() funktion in der virtuellen adress Tabelle der d3d9.dll.
    Ich möchte es dem Nutzer aber nicht zumuten diesen Index kennen zu müssen und lege sie selbst beim Konstruktor des jeweiligen Typs fest.

    Statt also:

    D3D_Hook<EndScene_t> pEndScene(42, hkEndScene);
    

    Kann der Nutzer die Zahl einfach weglassen und:

    D3D_Hook_EndScene pEndScene(hkEndScene);
    

    schreiben.



  • Genau für diesen Anwendungsfall gibt es in der C++11-Standardbibliothek die Lösung:

    namespace hook
    {
        auto endscene = std::bind(hook_func, 42); // bindet die Zahl 42 an das erste Argument von hook_func
        auto middlescene = std::bind(hook_func, 43);
        ...
    }
    

    Der Aufruf ist dann ganz normal wie eine normale Funktion:

    auto result = endscene( /* evtl. andere Argumente */ );
    


  • bind klingt interessant, allerdings möchte ich die 42 nicht an hook_func binden, sondern an den Konstruktor von D3D_Hook. Sowas ist nicht zufällig auch möglich?



  • Aber dann musst du wieder

    D3D_Hook_EndScene pEndScene(...);
    
    pEndScene.call_original(...)
    

    schreiben, was du nicht wolltest?



  • Ich sollte vielleicht noch erwähnen dass im Gegensatz zu den Abgeleiteten Klassen der Konstruktor der Basisklasse nicht leer ist, sondern etwas macht. Die Basisklasse brauch ich also in jedem Fall.

    func_hook ist ein Callback den der User reinwirft. Die Funktion selbst hat mit der zahl 42 nix am Hut, außer dass der Konstruktor der Basisklasse anhand der Zahl entscheidet, welche Adresse der Zeiger bekommt.



  • Was mir dabei jedoch Probleme macht, ist die Parameterliste beim definieren von operator() in der Basisklasse. Kann ich die irgendwie vom Template setzen lassen oder ist das, was ich da vor habe gar nicht möglich?

    Seit C++11 ist das wunderbar möglich. Mit variadic templates und perfect forwarding:

    // Helfer-Makro
    #define AUTO_RETURN(...) -> decltype(__VA_ARGS__) { return (__VA_ARGS__); }
    
    template <typename D3D_Func_type>
    class D3D_Hook
    {
    public:
        D3D_Func_type call_original; // <--- Funktionszeiger
    
        template<typename ... ArgTs>
        auto operator()(ArgTs&&... args) AUTO_RETURN( call_original( std::forward<ArgTs>(args)...) )
    };
    


  • Hey danke sieht vielversprechend aus. Mein Compiler (Visual Studio 2010) meckert leider bei dem Source, was auch kein Wunder ist, da der c++11 Standard erst 2011 erschien.

    Werde mir den Source mal auskommentiert reinpasten und bei Gelegenheit versuchen nen c++11 Compiler für visual studio 2010 aufzutreiben.
    Kann man eigentlich den Compiler von visual Studio 2012 mit 2010 verwenden? Hab zwar ne Studentenlizenz dafür aber ich kann mich ich mit Windows 8 und VS2012 noch nicht so recht anfreunden.



  • Du koenntest durchaus den Compi von VS2012 mit samt seiner erweiterten Libraries und Header in die entsprechenden VS2010-Verzeichnisse kopieren und damit weiter arbeiten -- theoretisch. Praktisch allerdings kannst du da einiges falsch machen und dir VS zerschiessen. Ich wuerd's jedenfalls nicht empfehlen.
    Nutz einfach VS2012. SuppressUpperCaseConversion zu 1 in Registry-Schluessel xyz und die GUI schreit dich nicht mehr an; ab Update 1 oder 2 gibt's sogar das alte VS2010-Farbschema wieder. Wenn du VS Professional hast, kannst du mit dem AddIn VS Color Theme Editor sogar noch mehr rumdoktern, bis es dir gefaellt.
    Und es laeuft auch auf Win7. Zumal du dich nicht "anfreunden" musst, da der Arbeitsablauf in 99% der Faelle der gleiche wie in VS 2010 ist



  • Genau, was mich störte waren diese Uppercase-Menüs, und dass die Menüs nicht mehr klar voneinander getrennt sind, also alles grau ineinander überläuft. Das finde ich unübersichtlich.

    Wenn ich mir VS2012 allerdings so zurechtfrickeln kann, dass es wie 2010 aussieht, steige ich gerne um. Hab damals allerdings auch den Release Candidate benutzt. Werds mir nochmal bei Dreamsparc saugen und mein Glück versuchen. Vielen Dank für die Infos. 🙂

    edit:
    Hab in der Studilizenz sogar die Ultimate Edition. 🙂



  • Stephan++ schrieb:

    Wenn ich mir VS2012 allerdings so zurechtfrickeln kann, dass es wie 2010 aussieht, steige ich gerne um.

    Kannst du. Du brauchst nur:
    1. http://stackoverflow.com/questions/10859173/how-to-disable-all-caps-menu-titles-in-visual-studio-2012-rc
    2. http://visualstudiogallery.msdn.microsoft.com/366ad100-0003-4c9a-81a8-337d4e7ace05



  • So, habs nun installiert.

    Visual Studio 2012 wollte zunächst genauso wenig wie 2010 compilen, da es mit dem variadic templates nichts anfangen konnte. Die installation von Service Pack 3 brachte auch nix. Allerdings ein uralter Patch vom November enthällt einen Compiler, der mit variadic templates umgehen kann:
    Download: http://www.microsoft.com/en-us/download/confirmation.aspx?id=35515
    Finds echt komisch, dass der compiler nicht im SP3 enthalten ist...
    Ein weiteres Problem was ich nach der Installation von VS2012 bekam war, dass VS2010 nicht mehr kompilieren wollte. Nach Installation von SP1 gings aber wieder.

    Jedenfalls wirft mir der Compiler nun folgenden Fehler:

    error C2893: Failed to specialize function template 'unknown-type D3D_Hook<EndScene_t>::operator ()(ArgTs &&...)'
    With the following template arguments:
    'LPDIRECT3DDEVICE9 &'

    Das Ganze passiert beim Aufrufen des () operators:

    // Hier nochmal die Funktionszeiger definition
    HRESULT __stdcall EndScene(LPDIRECT3DDEVICE9 pDevice); 
    typedef HRESULT(__stdcall* EndScene_t)(LPDIRECT3DDEVICE9);
    ...
    
    D3D_Hook_EndScene pEndScene(hkEndScene);
    
    pEndScene(pDevice); // <--- hier krachts
    ...
    

    Um ehrlich zu sein verstehe ich den Code von Sone nicht zu 100% aber ich bin nochmal alles durchgegangen und theoretisch sind alle Typen zur Kompilierzeit bekannt. Die Parameter werden jedenfalls schon mal richtig erkannt und eingesetzt.

    Folgende Zeilen interpretiere ich so:

    #define AUTO_RETURN(...) -> decltype(__VA_ARGS__) { return (__VA_ARGS__); } 
    
    auto operator()(ArgTs&&... args) AUTO_RETURN( call_original( std::forward<ArgTs>(args)...) )
    

    operator() bekommt eine variable argumentliste und einen noch nicht definierten rückgabewert. Die Argumente werden beim Aufruf vom Makro aus operator() in call_original() weitergereicht und der Typ des Rückgabewerts von call_original() bekommt auch operator().
    Mich wundert hierbei, dass der Kompiler ausgerechnet beim Aufruf von pEndScene(pDevice) meckert, da zu dem Zeitpunkt der Rückgabewert lange bekannt ist, da im Funktionszeiger definiert!? 😕


Anmelden zum Antworten