Mit dem Hammer passt C++ durch C



  • @knivil: Na klar, das reinterpret_cast muss natürlich davon ausgehen können, dass der void*-Zeiger durch das inverse reinterpret_cast entstand. Das irgendwie sicherer zu machen ist wohl kaum möglich, zumindest fällt mir da nichts ein, hast Du eine Idee? Da muss die Erweiterung der API trauen und die API muss der Erweiterung trauen, dass sie das schon irgendwie richtig macht.



  • Lol, ich merke was du eigentlich machst. Hier in einfach:

    #include <iostream> 
    using std::cout;
    
    typedef void(*callback_t)(void*);
    
    void call_cb(callback_t func, void* pobj) {
        func(pobj);
    }
    
    struct Test {
        void func() {
            std::cerr << "i am Test" << '\n';
        }
    };
    
    void wrap_test_func(void* pobj) {
        static_cast<Test*>(pobj)->func();
    }
    
    int main() {
        Test a;
        call_cb(wrap_test_func, &a);
    
        return 0;
    }
    

    Du kannsty gerne wrap_test_func in ein Template verwandeln, aber das was du machst wuerde ich aufgrund der Layoutzweifel als falsch empfinden. Und darueber hinaus: Einfach nur Overkill.

    Nachtrag mit wrapper als Templatate:

    template<typename T, typename void (T::*method)(void)>
    void wrapper(void* pobj)
    {
        (static_cast<T*>(pobj)->*method)();
    }
    
    int main()
    {
        Test a;
        call_cb(wrap_test_func, &a);
        call_cb(wrapper<Test, &Test::func>, &a);
    
        return 0;
    }
    


  • Ja, die Idee war, dass ich die Callbacks für die C-API in solcher Form definiere, dass ein template, das eine C-Wrapper-Funktion für eine Methode erzeugt, wenigstens die Signatur auf Kompatibilität checken kann.

    Und jetzt ist doch eine interessante Fragestellung aus diesem Beispiel entstanden: Ist das wegen etwaiger Regeln zu C/C++-Layouts wirklich UB, was ich da mache? Darf man von einer C-Struktur sozusagen ableiten und bedeutet dass, das der Teil des Objektes dem C-Alignment folgt? Ich slice ja den Rest weg (genau genommen ist da ja im Moment gar nichts anderes, den abgeleiteten Typ mal außen vor).
    Mit der Technik könnte ich halt einen Satz von Wrapper-Funktionen per Makro definieren, ohne immer einen kompletten Funktionskörper mit void*-cast schreiben zu müssen.
    Andererseits könnte ich den Körper auch direkt aus einem Makro stampfen. Müsste mal schauen, wie das aussähe.



  • Ich habe das Template nachgeliefert. Trotzdem bedeutet es, dass du zumindestens fuer jede Signatur einen extra Template brauchst.

    Darf man von einer C-Struktur sozusagen ableiten und bedeutet dass, das der Teil des Objektes dem C-Alignment folgt?

    Das weiss ich nicht. Da es fuer C++ aber keine ABI spezifiziert wurde, kann eine Ableitung von c_callback_t durchaus anders aussehen als c_callback_t.

    Mit der Technik könnte ich halt einen Satz von Wrapper-Funktionen per Makro definieren

    Kannst du jetzt auch, ganz ohne Makros. Ich verstehe nicht, warum alle so auf Makros abfahren, obwohl der eigentliche Konsenz ist, das sie boese sind.



  • Wobei man sich das typedef da in der C-API jetzt auch sparen kann, weil man ja decltype zur hand hat. Ich spiele einmal mit den Alternativen herum.

    Das hier ist auf jeden Fall nur als Beispiel gedacht für die Möglichkeiten, die man evtl. hat, wenn man bei der C-API auf ein gewisses Muster achtet.



  • Ich fahre nicht unbedingt darauf ab, Makros zu verwenden, aber wenn Makros das einzige Mittel sind, eigentlich unnötige Wiederholungen zu vermeiden, dann verwende ich sie durchaus lokal. Definieren -> Ausstampfen -> Definition löschen sozusagen. Dadurch, finde ich, lassen sich die "höheren Muster", die in etwas stecken, besser überblicken. Ist wahrscheinlich Geschmackssache, weil das ja nun auch auf jeden Fall mit Nachteilen erkauft wird, zugegeben. Auf jeden Fall greife ich zu diesem Mittel erst, wenn es keine schönere Möglichkeit gibt.



  • Meist nur fuer denjenigen, der das Makro schreibt. Ansonsten: Was ist noch schlecht am wrapper-Template, wo wuerdest du hier Makros ansetzen?



  • EinerVonUns schrieb:

    Ja, die Idee war, dass ich die Callbacks für die C-API in solcher Form definiere, dass ein template, das eine C-Wrapper-Funktion für eine Methode erzeugt, wenigstens die Signatur auf Kompatibilität checken kann.

    Falsch.

    zuerst die interne API definieren, danach ist die C API drüber zu legen trivial. Ehrlich. Wenn du gutes C++ hast, legt sich die C API wie Samt darüber.

    Deshalb: schmeiss das weg und mache es in C++ sauber. Dann komm wieder her und zeig uns die saubere C++ API und wir zeigen dir die saubere C API die du drüber legen kannst.



  • Du kannst auch bei bestehenden Bibliotheken nachschauen.

    SFML ist zum Beispiel in sehr sauberem C++ geschrieben. CSFML ist das offizielle C-Binding, welches das C++-API wrappt.



  • Dass der Quellcode, denn ihr gesehen habt, auf der Plugin-Seite für Kompatibilität zu der C-API sorgen soll, das ist aber klar geworden, oder? Ich frag nur mal, weil ich ja schon sagte, dass die C-API auf gewöhnlichem Wege auf der "Host"-Seite um den bestehenden C++-Code entsteht. Also alles worüber ich hier sprechen wollte, ist es, evtl. bei der C-API ein Muster einzuhalten, sodass ein in C++ geschriebenes Plugin ein möglichst leichtes Spiel mit der C-API hat.

    (C)SFML kommt auch auf die Liste, danke! Schaue ich mir alles in meiner Stöberstunde heute Abend an!



  • EinerVonUns schrieb:

    Dass der Quellcode, denn ihr gesehen habt, auf der Plugin-Seite für Kompatibilität zu der C-API sorgen soll, das ist aber klar geworden, oder?

    Nein, ich dachte das sei dein interner Code.

    Warum unterscheidest du C und C++ hier so komisch, und wozu die Templates?

    Du solltest zuerst ein simples C++ Interface bauen, zB

    #include <iostream>
    #include <functional>
    #include <vector>
    
    class Test {
    public:
      typedef std::function<void(Test*)> callback_t;
    
    private:
      std::vector<callback_t> callbacks;
    
    public:
      void registerCallback(std::function<void(Test*)> func) {
        callbacks.push_back(func);
      }
    
      void callCallbacks() {
        for(auto f : callbacks) {
          f(this);
        }
      }
    };
    
    void func(Test* p) {
      std::cout<<"func\n";
    }
    
    int main_cpp() {
      Test t;
      t.registerCallback(func);
      t.callCallbacks();
      return 0;
    }
    
    //und hier das C interface:
    
    typedef void callback_t(void*);
    class Wrapper {
    private:
      callack_t func;
    public:
      Wrapper(callback_t func) : func(func) {}
      void operator()(Test* p) {
        func(static_cast<void*>(p));
      }
    };
    
    void* test_create() {
      return new Test();
    }
    
    void test_destroy(void* o) {
      Test* p=static_cast<Test*>(o);
      delete p;
    }
    
    void test_registerCallback(void* o, callback_t func) {
      Test* p=static_cast<Test*>(o);
      p->registerCallback(Wrapper(func));
    }
    
    void test_callCallbacks(void* o) {
      Test* p=static_cast<Test*>(o);
      p->callCallbacks();
    }
    
    void func2(void* p) {
      std::cout<<"func2\n";
    }
    
    int main_c() {
      void* o=test_create();
      test_registerCallback(o, func2);
      test_callCallbacks(o);
      test_destroy(o);
      return 0;
    }
    
    int main() { return main_c(); }
    

    Edit:
    test_destroy vergessen 😮 (deshalb raii ftw)
    oder übersehe ich irgendwas relevantes?


Anmelden zum Antworten