Forum: Compiler & IDEs C++-ifizierung von C-API mit callback


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von The A. (the_a343)


Bewertung
0 lesenswert
nicht lesenswert
Hallo Leute,

ich benutze in meinem C++ Programm eine  C-Bibliothek.
Diese benutzt Callbacks und zwicngt mich im C++ immer über einen Umweg 
einer Statischen Member-funktion. Diese Statische Member-Funktion ruft 
dann die eigentliche Routine.

Ich möchte über eine Wrapper-Klasse das für mein Programm eleganter, auf 
C++ Weise lösen. Dabei die statische Member-Funktion vermeiden. Komme 
aber nicht weiter.
Ich schaue hier in die C++ Spezifikation 
https://en.cppreference.com/w/cpp/utility/functional/bind , stell mich 
aber zu blöd an, die richtigen Sequenzen zu treffen.

Könnt ihr mir helfen? Die betreffenden Stellen sind mit ??? markiert.
Unten mein minimalistischer Code. Beispielhaft ist die Benutzung in C 
und C++ ruft C. Was jetzt fehlt ist ist die korrekte Implementierung der 
C++-Wrapper Klasse.

Vielen Dank schon mal für eure Anmerkungen ... :-)

Danke und Grüße, Adib.
--
und jetzt mein Code
#include <iostream>
#include <functional>
using namespace std;

#include <stdint.h>
#include <stddef.h>

// -------- C API here
typedef struct {
    void *cb_object;    // pointer to object
    void (*cb_function)(void *, char *, size_t); // pointer to function
} foo_ctx_t;

static void foo_init(foo_ctx_t *ctx, void *caller, void(*cb)(void*, char*,size_t)) {
    ctx->cb_function = cb;
    ctx->cb_object = caller;
}

static void foo_process(foo_ctx_t *ctx) {
    static char data[] = "hello";
    ctx->cb_function(ctx->cb_object, data, sizeof(data));
}

// ----------- CPP wrapper for C API
class Foopp {
public:
    /*
    void init(???) {
        foo_init(&ctx, ???, ???);
    }
    */
    void process(void) {
        // cb();
    }
private:
    foo_ctx_t ctx;
    // ??? cb
};

// ----------- usage
class Obj {
public:
    // CB function as would use the CPP API
    void printpp(char *data, size_t length) {
        std::cout << std::string(data, length) << endl;;
    }

    // intermediate CB function as would use the C API to call CPP
    static void printcb(void *obj, char *data, size_t length) {
        Obj *p = reinterpret_cast<Obj *>(obj);
        p->printpp(data, length);
    }
};

// CB function as would use the C API
void print(void *, char *data, size_t length)
{
    printf("%.*s\n", (int)length, data);
}

int main()
{
    // C style
    foo_ctx_t foo_c;
    foo_init(&foo_c, NULL, print);
    foo_process(&foo_c);

    Obj o;

    // C style using object
    foo_ctx_t foo_cpp;
    foo_init(&foo_cpp, &o, &Obj::printcb);
    foo_process(&foo_cpp);

    // CPP way
    Foopp foopp;
    // foopp.init(??? o.print);
    foopp.process();

    return 0;
}

Beitrag #6219285 wurde vom Autor gelöscht.
von Rolf M. (rmagnus)


Bewertung
0 lesenswert
nicht lesenswert
The A. schrieb:
> Hallo Leute,
>
> ich benutze in meinem C++ Programm eine  C-Bibliothek.
> Diese benutzt Callbacks und zwicngt mich im C++ immer über einen Umweg
> einer Statischen Member-funktion. Diese Statische Member-Funktion ruft
> dann die eigentliche Routine.
>
> Ich möchte über eine Wrapper-Klasse das für mein Programm eleganter, auf
> C++ Weise lösen. Dabei die statische Member-Funktion vermeiden. Komme
> aber nicht weiter.

Das C++-Pendant sind virtuelle Memberfunktionen. Ich würde also eine 
abstrakte Basisklasse schreiben und die statische Memberfunktion dort 
genau einmal definieren.
class Base
{
public:
    Base()
    {
        foo_init(ctx(), this, stub);
    }

    virtual void process(char* data, size_t size) = 0;

    foo_ctx_t* ctx() { return &ctx_; }


private:
    static void stub(void* obj, char* data, size_t size)
    {
        reinterpret_cast<Base*>(obj)->process(data, size);
    }


private:
    foo_ctx_t ctx_;
};

class Printer: public Base
{
    void process(char *data, size_t length) override
    {
        std::cout << std::string(data, length) << endl;;
    }
};

int main()
{
    Printer p;
    foo_process(p.ctx());

}

: Bearbeitet durch User
von The A. (the_a343)


Bewertung
0 lesenswert
nicht lesenswert
Habs jetzt mit std::bind gelöst:
Hilfreich war 
https://stackoverflow.com/questions/28055080/how-to-set-a-member-function-as-callback-using-stdbind/28055321

Ihr könnt ja drüberschauen, ob die callback immer im Context des Members 
ausgeführt wird.

Danke.
--
mein Code sieht jetzt so aus:
#include <iostream>
#include <functional>
using namespace std;
using namespace std::placeholders;

#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>

// -------- C API here
typedef struct {
    void *cb_object;    // pointer to object
    void (*cb_function)(void *, char *, size_t); // pointer to function
    char data[80];
    size_t size;

} foo_ctx_t;

static void foo_init(foo_ctx_t *ctx, void *caller, void(*cb)(void*, char*,size_t)) {
    ctx->cb_function = cb;
    ctx->cb_object = caller;
    ctx->size = sprintf(ctx->data, "hallo\n");

}

static void foo_process(foo_ctx_t *ctx) {
    ctx->cb_function(ctx->cb_object, ctx->data, ctx->size);
}

// ----------- CPP wrapper for C API
class Foopp {
public:
    typedef std::function<void(char *, size_t)> Callback;
    Foopp() {
        foo_init(&ctx, this, receiver);
    }
    void init(Callback f) {
        cb = f;
    }
    void process(void) {
        foo_process(&ctx);
    }
    static void receiver(void *base, char *data, size_t length) {
        ((Foopp *)base)->cb(data, length);
    }
private:
    foo_ctx_t ctx;
    Callback cb;
};

// ----------- usage
class Obj {
public:
    // CB function as would use the CPP API
    void printpp(char *data, size_t length) {
        std::cout << std::string(data, length) << endl;
        std::cout.flush();
    }

    // intermediate CB function as would use the C API to call CPP
    static void printcb(void *obj, char *data, size_t length) {
        Obj *p = reinterpret_cast<Obj *>(obj);
        p->printpp(data, length);
    }
};

// CB function as would use the C API
void print(void *, char *data, size_t length)
{
    printf("%.*s\n", (int)length, data);
}

int main()
{
    // C style
    foo_ctx_t foo_c;
    foo_init(&foo_c, NULL, print);
    foo_process(&foo_c);

    Obj o;

    // C style using object
    foo_ctx_t foo_cpp;
    foo_init(&foo_cpp, &o, &Obj::printcb);
    foo_process(&foo_cpp);


    // CPP way
    Foopp foopp;
    foopp.init(std::bind(&Obj::printpp, &o, _1, _2));
    foopp.process();

    return 0;
}

von Noname (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Hallo,

inwieweit ist den die jetzige Lösung besser als die Vorherigen? Elegant 
wäre es doch, wenn die Klasse Foopp den gewünschten Aspekt der C Lib 
abstrahiert, z. B.:

Receiver receiver; // Use C-API
//...
auto status = receiver.receive_data_frame();

Viele Grüße

von Mikro 7. (mikro77)


Bewertung
0 lesenswert
nicht lesenswert
The A. schrieb:
> Habs jetzt mit std::bind gelöst:

Wie Rolf oben geschrieben hat werden in C++ virtuelle Funktionen für 
"Interfaces" benutzt. Das Interface wird abstrakt definiert...
struct Interface
{
  virtual void some_service(char*,size_t) = 0 ;
} ;

...und dann für die konkrete Umsetzung implementiert (abgeleitet).

In deinem Code habe ich weder verstanden, wie dein C-Interface aussieht, 
noch wie deine vorgeschlagene Art der C++-"Kapselung" die Situation 
verbessert. Tatsächlich wird dein Code dadurch noch unverständlicher.

Das mag jetzt am konkreten Beispiel liegen. Ich nehme mal an, du weisst 
was du da tust und worin deine "Verbesserung" liegt. Dann ist alles gut.

Für einen Aussenstehenden wie mich ist dein Code ein Beispiel wie man 
nicht programmieren sollte. -- Das ist jetzt aber nicht böse gemeint.

von The A. (the_a343)


Bewertung
0 lesenswert
nicht lesenswert
Mikro 7. schrieb:
...
> Wie Rolf oben geschrieben hat werden in C++ virtuelle Funktionen für
> "Interfaces" benutzt. Das Interface wird abstrakt definiert...
...
>

Hallo Mikro 7,

Bin eher in der C-Welt zuhause und dankbar für Anregungen.

Die C-Library ist ein Parser, der fertige Datenpakete im Callback 
zurückmeldet.
Der Parser wird gespeist aus einer Datenquelle, einem zB UART Interface.
Der Parser kennt den eigentlichen Dateninhalt nicht.
while(uart_ready()) {
  foo_process(&ctx, uart_read());
}

Der Callback ist wie eine DataReady Funktion, die die Daten eine Schicht 
zurück liefert. Un dann applikationsspezifisch interpretiert und 
verarbeitet.
Der C-Callback implementiert dabei eine Art Observer Pattern.

Im C++ hatte ich dabei die Schwierigkeit, dass ich zusätzlich eine 
statische Methode definieren musste, als Ziel für den Callback. Um dann 
die eigentliche Eventbehandlung aufzurufen.
(im Qt hätte ich den Signal/Slot Mechanismus genommen)

Die Behandlung der Daten ist je nach Applikations und Schnittstelle 
unterschiedlich.
Also die Klasse die die Daten behandelt sollte hat ein Parser und 
nicht ist ein Parser sein. Daher "gibt es eine Parser-Instanz" und 
nicht "implementiert ein Parser-Interface".

Was gäbe es denn da noch für Möglichkeiten?

Danke und Grüße.

von Mikro 7. (mikro77)


Bewertung
0 lesenswert
nicht lesenswert
Hallo!

Du hast also für eine Datenquelle (bspw. UART) einen Reader der Pakete 
aus dem Bitstream baut und einen Parser der die Pakete liest. Reader und 
Parser sollen entkoppelt werden. -- Das ist eine klassische 
Programmieraufgabe.

Bei dir gibt es mehrere Reader die mit dem gleichen Parser arbeiten 
(oder umgekehrt?) Ein Ansatz in C++ zum Entkoppeln sind wie gesagt 
Interfaces. Im folgenden Beispiel wird der Reader vom Parser entkoppelt:
struct Reader
{
  struct Interface
  {
    virtual void process(char const*,size_t) = 0 ;
  } ;
  Reader(Interface *consumer) ;
  // ...
} ;

struct Parser : Reader::Interface { // ...

Man kann aber auch den Signal-Slot-Mechanismus aus Qt nehmen, oder sich 
sowas selbst bauen, oder std::function nehmen, oder...

Was ich in deinem Beitrag nicht wirklich verstanden hatte, war dein 
genaues Problem, und wie du es gelöst hast. Das gibt dein 
Code/Beschreibung imho nicht her. (Und das war mein Punkt: 
verständlichen Code schreiben.)

Wenn ich spekuliere, dein Problem war, dass du für deine 
Callback-Signatur, also für...
void (*)(void*,char*,size_t)
...nicht den Weg gefunden hast direkt die Objektmethode...
void (T::*)(char*,size_t)
...zu benutzen, und statt dessen eine statische Methode eingeführt hast. 
-- Müssen wir hier aber nicht weiter vertiefen.

Schönen Abend noch!

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.