Memberfunktion einer Klasse als Callback bei WinApi nutzen?



  • @Finnegan sagte in Memberfunktion einer Klasse als Callback bei WinApi nutzen?:

    Igitt! man glaubt es kaum, aber das gibts wirklich. Warzen wie std::map<c_library_struct*, my_class*> und solche Spässe. Bäh!

    Gibt auch noch die Möglichkeit dynamisch Thunks zu generieren die den fehlenden "this" Parameter dazubasteln. Das funktioniert dann auch wenn es nichtmal einen c_library_struct* gibt, und es kommt ohne globale Datenstrukturen/Locks aus.



  • @hustbaer sagte in Memberfunktion einer Klasse als Callback bei WinApi nutzen?:

    Gibt auch noch die Möglichkeit dynamisch Thunks zu generieren die den fehlenden "this" Parameter dazubasteln. Das funktioniert dann auch wenn es nichtmal einen c_library_struct* gibt, und es kommt ohne globale Datenstrukturen/Locks aus.

    Das ist natürlich auch ne schicke Idee, bis auf dass sie wahrscheinlich einiges an plattformspezifischem Code braucht. Gibt es für sowas eine gut gepflegte Bibliothek, welche die verbreiteteren Systeme abdeckt? Auch die Schnelle habe ich nur sowas hier gefunden.

    Ist zwar eine Weile her, dass ich mit so einem Problem zu tun hatte, aber das nächste Mal kommt bestimmt. Allerdings bin ich auch kein Fan von Lösungen, welche die Portierbarkeit behindern, nur weil lediglich "Software" etwas nicht kann. Wahrscheinlich würde ich eher die C-Bibliothek patchen, wenn möglich.



  • @Finnegan sagte in Memberfunktion einer Klasse als Callback bei WinApi nutzen?:

    bis auf dass sie wahrscheinlich einiges an plattformspezifischem Code braucht.

    Ich glaube, wenn es um die WinApi geht, ist es an der Stelle auch schon zu spät 😉



  • @Mechanics sagte in Memberfunktion einer Klasse als Callback bei WinApi nutzen?:

    Ich glaube, wenn es um die WinApi geht, ist es an der Stelle auch schon zu spät 😉

    Schon klar, war aber eine allgemeine Frage, die ich hier einfach mal Huckepack in den Thread schmuggeln wollte. Sei mal nicht so kleinlich 😉



  • @Finnegan Ne, fertige Library kenne ich leider keine. Ich hatte mir nur vor langer Zeit mal die ATL angesehen, weil ich wissen wollte wie die das macht - und gesehen dass die dynamisch generierte Thunks verwendet.



  • @Mechanics sagte in Memberfunktion einer Klasse als Callback bei WinApi nutzen?:

    Ich glaube, wenn es um die WinApi geht, ist es an der Stelle auch schon zu spät

    winapi ist gar nicht so schlecht, nur eben nicht ganz c++ kompatibel.

    aber ich bin mir sicher, dass man mit c++ auch winapi-konzepte wie callbacks wrappen kann. einige vorschläge wurden hier schon gemacht.

    schade nur, dass mfc und atl nicht mehr weiterentwickelt werden. aber immerhin sind sie im neusten visual c++ noch vorhanden.



  • Ich wollte auch nicht sagen, dass die WinApi schlecht ist. Die WinApi ist auch recht umfangreich und da gibt es ganz unterschiedliche Funktionen, manche sind moderner/besser, andere schlechter. Das ganze Window/GUI-Handling mag ich nicht so, weil das einfach umständlich zum Programmieren ist. Aber mit vielen anderen Funktionen habe ich echt kein Problem. Und die ist ziemlich gut dokumentiert.
    MFC finde ich hingegen schon ziemlich schlecht. Das ist noch umständlicher als die WinApi und schlechter C++ Stil.

    Was ich ursprünglich wollte, war nochmal darauf hinzuweisen, dass es hier um die WinApi ging und die sowieso schon plattformspezifisch ist. Weil das wurde in den letzten Posts nicht mehr erwähnt und ich musste selber nochmal hochscrollen, um mich zu vergewissern. Die letzten paar Posts hätte man auch so interpretieren können, dass es um irgendeine (plattformunabhängige) C-Bibliothek geht.



  • ps:
    @Finnegan sagte in Memberfunktion einer Klasse als Callback bei WinApi nutzen?:

    Gibt es für sowas eine gut gepflegte Bibliothek, welche die verbreiteteren Systeme abdeckt?

    Wenn man die Frage wörtlich nimmt und sich keine weiteren Forderungen dazudenkt, dann gibt es schon was womit man dynamisch allen möglichen Code generieren kann, halbwegs plattformunabhängig. Und zwar LLVM 😃
    Das ist dann aber wirklich mit Kanonen auf Spatzen schiessen. Fast schon mit Atombomben auf Spatzen schiessen.
    Und vermutlich ist es sogar mehr Aufwand es mit LLVM zu machen als sich den nötigen Code für die nötigen Plattformen selbst zu schreiben.



  • @Mechanics
    Ich habe auf Stackoverflow eine schöne Templatefunktion gefunden, mit welcher man einen WinAPI Dialog in Form einer Klasse entwickeln kann, ohne das man auf globale Variablen zurückgreifen muss.

    Stackoverflow Link

    Wenn man danach noch std::basic_string Wrapperfunktionen für die GetDlgItemText Funktionen schreibt, ist die WinAPI fast angenehm.



  • @Mechanics sagte in Memberfunktion einer Klasse als Callback bei WinApi nutzen?:

    MFC finde ich hingegen schon ziemlich schlecht. Das ist noch umständlicher als die WinApi und schlechter C++ Stil.

    ich finde es praktisch dass vs einfache mfc-grundgerüste generieren kann. so ist eine dialogfeld-basierte anwendung in einer minute zusammengeklickt.

    davor gab es ein framework namens quickwin (oder so). damit konnte man bestehende konsolenprogramme in windows-anwendungen umwandeln. war auch schön, aber das gab es nur bis visual studio 1.52 (für win 16).

    ich liebe dieses retro zeug. 🙂



  • @hustbaer sagte in Memberfunktion einer Klasse als Callback bei WinApi nutzen?:

    ps:
    @Finnegan sagte in Memberfunktion einer Klasse als Callback bei WinApi nutzen?:

    Gibt es für sowas eine gut gepflegte Bibliothek, welche die verbreiteteren Systeme abdeckt?

    Wenn man die Frage wörtlich nimmt und sich keine weiteren Forderungen dazudenkt, dann gibt es schon was womit man dynamisch allen möglichen Code generieren kann, halbwegs plattformunabhängig. Und zwar LLVM 😃

    Hehe, jo. Das ist sicher Overkill. Für den Anwendungsfall reicht ja eigentlich eine dynamisch generierbare Minimalfunktion mit einer einzigen CALL-Instruktion, deren Parameter man programmatisch vorgeben kann. Schade, dass C++ keine Hausmittel für sowas hat.

    Etwas schlanker wäre dieser In-Memory-Assember hier, der ist nur leider auch nicht sonderlich portabel und kann zuviel - mehr als man für so simple Callbacks braucht. Auch sind Assembler wieder CPU-spezifisch.

    Eine andere Idee wäre vielleicht Code, der immer zu einer eine Funktion mit Call-Instruktion auf eine absolute Adresse kompiliert wird, in der Hoffnung, dass man den erzeugten Code dann per memcpy kopieren und on-the-fly die Adressen austauschen kann. Ich hab das tatsächlich mal aus Neugier ausprobiert. Viel Vertrauen hab ich nicht in diesen Code, aber er könnte sogar CPU-portabel sein, falls man tatsächlich davon ausgehen kann, dass sich fixe, direkt im Maschinencode eingebettete, absolute Speicheradressen immer mit byteweisem Suchen und Ersetzen austauschen lassen (?). Der Code ist Windows-spezifisch und benötigt einen GCC-Compiler für Attribute, mit den ich Anfang und Ende einer Funktion besser im Speicher lokalisieren kann:

    #include <cstddef>
    #include <cstring>
    #include <iostream>
    #include <memory>
    #include <functional>
    #include <algorithm>
    
    #include <windows.h>
    
    template <typename T>
    void replace_value(
        std::byte* first,
        std::byte* last,
        const T& old_value,
        const T& new_value
    )
    {
        auto old_bytes = reinterpret_cast<const std::byte*>(&old_value);
        auto position = std::search(first, last, old_bytes, old_bytes + sizeof(T));
        if (position != last)
            std::memcpy(position, &new_value, sizeof(T));  
    }
    
    __attribute__((noinline, used, section("callback_helper")))
    void callback_helper()
    {
        reinterpret_cast<void(*)(void*)>(
            static_cast<std::uintptr_t>(0xabcdabcdabcdabcd)
        )(reinterpret_cast<void*>(static_cast<std::uintptr_t>(0xdcbadcbadcbadcba)));
    }
    
    template <typename F>
    auto create_callback(F* function, void* pointer_arg)
    {
        extern std::byte __start_callback_helper[];
        extern std::byte __stop_callback_helper[];
        auto code_size = __stop_callback_helper - __start_callback_helper;
        auto code_buffer = reinterpret_cast<std::byte*>(VirtualAllocEx(
            GetCurrentProcess(),
            nullptr,
            code_size,
            MEM_COMMIT,
            PAGE_EXECUTE_READWRITE
        ));
        std::copy_n(__start_callback_helper, code_size, code_buffer);
        replace_value(
            code_buffer,
            code_buffer + code_size,
            static_cast<std::uintptr_t>(0xdcbadcbadcbadcba),
            reinterpret_cast<std::uintptr_t>(pointer_arg)
        );
        replace_value(
            code_buffer,
            code_buffer + code_size,
            static_cast<std::uintptr_t>(0xabcdabcdabcdabcd),
            reinterpret_cast<std::uintptr_t>(function)
        );
        return std::unique_ptr<void(), std::function<void(void(*)())>>{ 
            reinterpret_cast<void(*)()>(code_buffer),
            [](void(*code_buffer)()) {
                VirtualFree(reinterpret_cast<void*>(code_buffer), 0, MEM_RELEASE);
            }
        };
    }
    
    struct C
    {
        C(int a)
        : a{ a }
        {
        }
        
        auto f()
        {
            std::cout << "callback C::f(): " << a << "\n";
        }
        
        int a;
    };
    
    __attribute__((noinline))
    void callback(C* object)
    {
        object->f();
    }
    
    auto main() -> int
    {
        auto c1 = C{ 10 };
        auto c2 = C{ 20 };
        auto c3 = C{ 30 };
    
        auto f1 = create_callback(&callback, &c1);
        auto f2 = create_callback(&callback, &c2);
        auto f3 = create_callback(&callback, &c3);
    
        f1.get()();
        f2.get()();
        f3.get()();
    }
    

    Ausgabe:

    callback C::f(): 10
    callback C::f(): 20
    callback C::f(): 30
    

    ... nur ne Spielerei - Bitte nicht produktiv einsetzen! 😉

    Dennoch bleibe ich vorerst bei der bodenständigen Lösung wie die mit std::map. Solcher Code ist mir definitiv zu explosiv.



  • @Finnegan sagte in Memberfunktion einer Klasse als Callback bei WinApi nutzen?:

    Für den Anwendungsfall reicht ja eigentlich eine dynamisch generierbare Minimalfunktion mit einer einzigen CALL-Instruktion,

    Es kommt total auf die Calling-Convention und Parameteranzahl an. Es kann 1 mov + 1 jmp reichen. Kann aber auch sein dass man ein paar push/mov/call/pop braucht. Ist aber eher keine Hexerei.

    falls man tatsächlich davon ausgehen kann, dass sich fixe, direkt im Maschinencode eingebettete, absolute Speicheradressen immer mit byteweisem Suchen und Ersetzen austauschen lassen (?)

    Ausgehen kann man von viel, aber spätestens bei PowerPC stimmt es nicht 😉 Weil PPC kein "load immediate" mit voller Registerbreite kann. Das sind immer 2 oder mehr Befehle. Gibt glaube ich sogar ein paar Exoten die gar keine Immediates können - bzw. nix was breiter als ein paar wenige Bit ist. D.h. dort stünden die Konstanten dann irgendwo im Datensegment und würden von dort geladen.

    Und davon abgesehen finde ich es auch immer sehr krass sich überhaupt darauf zu verlassen dass eine Funktion überhaupt "am Stück" im Speicher liegt. Egal wie klein sie ist.



  • @hustbaer sagte in Memberfunktion einer Klasse als Callback bei WinApi nutzen?:

    Ausgehen kann man von viel, aber spätestens bei PowerPC stimmt es nicht 😉 Weil PPC kein "load immediate" mit voller Registerbreite kann. Das sind immer 2 oder mehr Befehle. Gibt glaube ich sogar ein paar Exoten die gar keine Immediates können - bzw. nix was breiter als ein paar wenige Bit ist. D.h. dort stünden die Konstanten dann irgendwo im Datensegment und würden von dort geladen.

    Ja, ich sehe schon, das macht man besser mit nem externen Assembler. Oder vielleicht sogar mit LLVM und einem passenden Linker-Skript? Wenn es möglich ist, die Konstanten auch aus dem Codesegment zu laden, könnte man die Funktion durchaus in eine kompakte Form bringen, die man mit memcpy duplizieren kann, ohne gleich einen halben Linker dazu schreiben zu müssen.

    Und davon abgesehen finde ich es auch immer sehr krass sich überhaupt darauf zu verlassen dass eine Funktion überhaupt "am Stück" im Speicher liegt. Egal wie klein sie ist.

    Ich hoffe halt, dass noinline und die eigene section mit den compilergenerierten start/stop-Markern ausreichen. Aber wie schon gesagt, besonders viel Vertrauen habe ich in meinen Code da oben nicht... aber: "works for me!" 😉



  • @Finnegan
    Ja in real ist das entweder ein kleines unsigned char Array das mit memcpy in den neu zu erstellenden Thunk kopiert wird, und wo man danach an den wohlbekannten Offsets den "userdata" Wert reinschreibt.
    Oder überhaupt gleich Code der den Thunk ala

    newThunk[0] = 0x11;
    newThunk[1] = 0x22;
    newThunk[2] = 0x33;
    ...
    

    zusammenbaut. Weil's typischerweise so wenig Bytes sind dass es wörscht ist.



  • @Bushmaster sagte in Memberfunktion einer Klasse als Callback bei WinApi nutzen?:

    so ist eine dialogfeld-basierte anwendung in einer minute zusammengeklickt.

    Das ging schon zu Windows 3.1 Zeiten mit Delphi oder VB wesentlich bequemer. Und mit Qt geht es jetzt auch wesentlich bequemer, damals evtl. auch schon. Selbst wenn du bei MFC die Assistenten benutzt, ist der Code über mehrere Codestellen und Macros verteilt. Das ist alles nicht toll.



  • @Bushmaster sagte in Memberfunktion einer Klasse als Callback bei WinApi nutzen?:

    war auch schön, aber das gab es nur bis visual studio 1.52 (für win 16).

    ich liebe dieses retro zeug. 🙂

    Ich bin mal gespannt ob/wann der Commander X16 fertig wird. Das ist dann richtig retro, und wird hoffentlich richtig cool! 🙂
    (OK, ich fände es noch cooler mit einem 68k drinnen, aber naja, wenn man so wie ich nur rumsitzt und wartet dass andere sowas machen kann man sich das dann leider nicht aussuchen)



  • @hustbaer sagte in Memberfunktion einer Klasse als Callback bei WinApi nutzen?:

    Commander X16

    hier möchte auch noch einer die 8-bit ära aufleben lassen: https://mega65.org/
    🙂



  • @Bushmaster sagte in Memberfunktion einer Klasse als Callback bei WinApi nutzen?:

    hier möchte auch noch einer die 8-bit ära aufleben lassen: https://mega65.org/
    🙂

    Ich bedauere echt, dass ich so ein Intel-Geschädigter bin. Meine Nostalgiegefühle für weniger als 32 Bits werden durch Erinnerungen an Dinge wie ein segmentiertes Speichermodell erheblich getrübt.

    Habe vor einiger Zeit allerdings nochmal ein DJGPP mit aktuellem GCC gebaut. DOS-Spielereien mit Flat Memory und C++17 sind genau die richtige Mischung aus Vergnügen und Schmerzen 😉



  • @hustbaer sagte in Memberfunktion einer Klasse als Callback bei WinApi nutzen?:

    (OK, ich fände es noch cooler mit einem 68k drinnen, aber naja, wenn man so wie ich nur rumsitzt und wartet dass andere sowas machen kann man sich das dann leider nicht aussuchen)

    Wer sich für neue Amiga Hardware interessiert, sollte sich das Vampire V4 Projekt anschauen. Die V2 ist eine Beschleunigerkarte mit diversen zusätzlichen Schnittstellen, die V4 ist eine Standalone Lösung auf FPGA Basis. Für Puristen nicht die ideale Lösung, aber schnelle 68k gibt es nicht mehr. Da es bei beiden HDMI Output gibt, ist auch das dämliche Flickerfixer Problem gelöst.

    Apollo Accelerators



  • @Finnegan Genau deswegen, also wegen flachem Adressraum und 32 Bit Registern, hätte ich ja nen 68k so cool gefunden.


Anmelden zum Antworten