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


von The A. (the_a343)


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
1
#include <iostream>
2
#include <functional>
3
using namespace std;
4
5
#include <stdint.h>
6
#include <stddef.h>
7
8
// -------- C API here
9
typedef struct {
10
    void *cb_object;    // pointer to object
11
    void (*cb_function)(void *, char *, size_t); // pointer to function
12
} foo_ctx_t;
13
14
static void foo_init(foo_ctx_t *ctx, void *caller, void(*cb)(void*, char*,size_t)) {
15
    ctx->cb_function = cb;
16
    ctx->cb_object = caller;
17
}
18
19
static void foo_process(foo_ctx_t *ctx) {
20
    static char data[] = "hello";
21
    ctx->cb_function(ctx->cb_object, data, sizeof(data));
22
}
23
24
// ----------- CPP wrapper for C API
25
class Foopp {
26
public:
27
    /*
28
    void init(???) {
29
        foo_init(&ctx, ???, ???);
30
    }
31
    */
32
    void process(void) {
33
        // cb();
34
    }
35
private:
36
    foo_ctx_t ctx;
37
    // ??? cb
38
};
39
40
// ----------- usage
41
class Obj {
42
public:
43
    // CB function as would use the CPP API
44
    void printpp(char *data, size_t length) {
45
        std::cout << std::string(data, length) << endl;;
46
    }
47
48
    // intermediate CB function as would use the C API to call CPP
49
    static void printcb(void *obj, char *data, size_t length) {
50
        Obj *p = reinterpret_cast<Obj *>(obj);
51
        p->printpp(data, length);
52
    }
53
};
54
55
// CB function as would use the C API
56
void print(void *, char *data, size_t length)
57
{
58
    printf("%.*s\n", (int)length, data);
59
}
60
61
int main()
62
{
63
    // C style
64
    foo_ctx_t foo_c;
65
    foo_init(&foo_c, NULL, print);
66
    foo_process(&foo_c);
67
68
    Obj o;
69
70
    // C style using object
71
    foo_ctx_t foo_cpp;
72
    foo_init(&foo_cpp, &o, &Obj::printcb);
73
    foo_process(&foo_cpp);
74
75
    // CPP way
76
    Foopp foopp;
77
    // foopp.init(??? o.print);
78
    foopp.process();
79
80
    return 0;
81
}

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


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.
1
class Base
2
{
3
public:
4
    Base()
5
    {
6
        foo_init(ctx(), this, stub);
7
    }
8
9
    virtual void process(char* data, size_t size) = 0;
10
11
    foo_ctx_t* ctx() { return &ctx_; }
12
13
14
private:
15
    static void stub(void* obj, char* data, size_t size)
16
    {
17
        reinterpret_cast<Base*>(obj)->process(data, size);
18
    }
19
20
21
private:
22
    foo_ctx_t ctx_;
23
};
24
25
class Printer: public Base
26
{
27
    void process(char *data, size_t length) override
28
    {
29
        std::cout << std::string(data, length) << endl;;
30
    }
31
};
32
33
int main()
34
{
35
    Printer p;
36
    foo_process(p.ctx());
37
38
}

: Bearbeitet durch User
von The A. (the_a343)


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:
1
#include <iostream>
2
#include <functional>
3
using namespace std;
4
using namespace std::placeholders;
5
6
#include <stdint.h>
7
#include <stddef.h>
8
#include <stdlib.h>
9
10
// -------- C API here
11
typedef struct {
12
    void *cb_object;    // pointer to object
13
    void (*cb_function)(void *, char *, size_t); // pointer to function
14
    char data[80];
15
    size_t size;
16
17
} foo_ctx_t;
18
19
static void foo_init(foo_ctx_t *ctx, void *caller, void(*cb)(void*, char*,size_t)) {
20
    ctx->cb_function = cb;
21
    ctx->cb_object = caller;
22
    ctx->size = sprintf(ctx->data, "hallo\n");
23
24
}
25
26
static void foo_process(foo_ctx_t *ctx) {
27
    ctx->cb_function(ctx->cb_object, ctx->data, ctx->size);
28
}
29
30
// ----------- CPP wrapper for C API
31
class Foopp {
32
public:
33
    typedef std::function<void(char *, size_t)> Callback;
34
    Foopp() {
35
        foo_init(&ctx, this, receiver);
36
    }
37
    void init(Callback f) {
38
        cb = f;
39
    }
40
    void process(void) {
41
        foo_process(&ctx);
42
    }
43
    static void receiver(void *base, char *data, size_t length) {
44
        ((Foopp *)base)->cb(data, length);
45
    }
46
private:
47
    foo_ctx_t ctx;
48
    Callback cb;
49
};
50
51
// ----------- usage
52
class Obj {
53
public:
54
    // CB function as would use the CPP API
55
    void printpp(char *data, size_t length) {
56
        std::cout << std::string(data, length) << endl;
57
        std::cout.flush();
58
    }
59
60
    // intermediate CB function as would use the C API to call CPP
61
    static void printcb(void *obj, char *data, size_t length) {
62
        Obj *p = reinterpret_cast<Obj *>(obj);
63
        p->printpp(data, length);
64
    }
65
};
66
67
// CB function as would use the C API
68
void print(void *, char *data, size_t length)
69
{
70
    printf("%.*s\n", (int)length, data);
71
}
72
73
int main()
74
{
75
    // C style
76
    foo_ctx_t foo_c;
77
    foo_init(&foo_c, NULL, print);
78
    foo_process(&foo_c);
79
80
    Obj o;
81
82
    // C style using object
83
    foo_ctx_t foo_cpp;
84
    foo_init(&foo_cpp, &o, &Obj::printcb);
85
    foo_process(&foo_cpp);
86
87
88
    // CPP way
89
    Foopp foopp;
90
    foopp.init(std::bind(&Obj::printpp, &o, _1, _2));
91
    foopp.process();
92
93
    return 0;
94
}

von Noname (Gast)


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.:

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

Viele Grüße

von Mikro 7. (mikro77)


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...
1
struct Interface
2
{
3
  virtual void some_service(char*,size_t) = 0 ;
4
} ;

...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)


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.
1
while(uart_ready()) {
2
  foo_process(&ctx, uart_read());
3
}

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)


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:
1
struct Reader
2
{
3
  struct Interface
4
  {
5
    virtual void process(char const*,size_t) = 0 ;
6
  } ;
7
  Reader(Interface *consumer) ;
8
  // ...
9
} ;
10
11
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...
1
void (*)(void*,char*,size_t)
...nicht den Weg gefunden hast direkt die Objektmethode...
1
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!

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.