Memberfunktion einer Klasse als Callback bei WinApi nutzen?



  • @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.



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

    Meine Nostalgiegefühle für weniger als 32 Bits werden durch Erinnerungen an Dinge wie ein segmentiertes Speichermodell erheblich getrübt.

    das war aber doch recht nostalgisch. 🙂

    ich glaube der 8086 war pinkompatibel zum vorgängermodell, der 20 adressleitungen hatte. das schema nach dem die effektive adresse berechnet wurde, war schon super strange. sieht für mich so aus, als wollte man kosten sparen.



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

    ich glaube der 8086 war pinkompatibel zum vorgängermodell, der 20 adressleitungen hatte. das schema nach dem die effektive adresse berechnet wurde, war schon super strange. sieht für mich so aus, als wollte man kosten sparen.

    8085, 8080 hatte nur 16Bit Adreßraum und der 8008 nur 14Bit. Der „Vorteil“ des 8086/8088 war, dass durch das rangefrickelte Segmentregister Assemblerprogramme vom 8085/8080 unverändert in 16Bit Adressraum laufen konnten. Man musste nur etwas Code hinzu fügen, um die Segmente zu ändern. Das war damals, als fast alle Programme in Assembler geschrieben wurden ein großer Vorteil. Bei Intel merkt man sehr stark, dass eine evolutionäre Entwicklung vom 4Bit Prozessor 4004 hin zum 8Bit 8008 und dann schlußendlich zum 8080 stattfand. Erst mit dem 80386 wurde dann ein halbwegs sauberer Befehlssatz entworfen. Nur die Altlasten sind bei allen CPUs bis zur Gegenwart vorhanden.

    Wenn man das mit 68000 oder den DEC VAX vergleicht, kommt einen das kalte Grausen.



  • @john-0
    aber der intel-schrott hat sich durchgesetzt. wohl weil jeder (lizenzfrei?) ibm-pc clones bauen durfte.

    die entwickler des vax-betriebssystems wurden von microsoft abgeworben, um windows nt zu bauen. anfänglich sollte ein risc-prozessor mit linearem adressraum eingesetzt werden. aber dann schwenkten sie auf den 386er um.

    es gibt ja schon seit langem die superguten arm-prozessoren. aber aus irgendeinem grund stecken in 'normalen' pc's immer noch aufgebohrte x86er. 😞


Anmelden zum Antworten