2016-03-23 5 views
-2

In einer C++ - App muss ich eine Schnittstelle mit einer C-Bibliothek. Diese Bibliothek hat mehrere Komponenten mit jeweils einer eigenen API. Die APIs sind sehr ähnlich und unterscheiden sich nur im Präfix der Funktionen und in einem Handler-Typ. Beispiel:Strategien zum Wrappen von C-API für C++ - Verbrauch

Ich möchte dies für C++ - Verbrauch in einer generischen Weise mit minimaler Code-Duplizierung umschließen. So wird zuerst der naive Ansatz:

class bull_backend 
{ 
public: 
    ... 
    void get_data (uint8_t* buffer) 
    { 
     error_details_t err; 
     bull_get_data(handle, buffer, &err); 
    } 
    ... 

private: 
    bull_handle_t* handle; 
}; 

class frog_backend 
{ 
public: 
    ... 
    void get_data (uint8_t* buffer) 
    { 
     error_details_t err; 
     frog_get_data(handle, buffer, &err); 
    } 
    ... 

private: 
    frog_handle_t* handle; 
}; 

class bullfrog_backend 
{ 
public: 
    ... 
    void get_data (uint8_t* buffer) 
    { 
     error_details_t err; 
     bullfrog_get_data(handle, buffer, &err); 
    } 
    ... 

private: 
    bullfrog_handle_t* handle; 
}; 

Das nicht ganz es geschnitten. Ich werde nur die C-API duplizieren, aber jetzt in Form von Klassen. Das einzige, was anders ist, ist nur das Präfix, das die C-Entitäten identifiziert.

Die nächste Sache in meinem Kopf war ein, vorbereitet sein, ein Makro, z.

#define GENERATE_BACKEND(...) 
    ... 

das würde nur für das Präfix ersetzen. Aber das mag ich nicht. Es fühlt sich nicht wie moderne C++ an.

Eine andere Sache, die ich tun könnte, ist alles in einer Klassenvorlage zu gruppieren und tun Tag senden oder verwenden Sie enable_if, z.

template <typename Component> 
class backend 
{ 
public: 

    ... 
    template<typename = typename std::enable_if<std::is_same<Component, bull_component>::value>::type> 
    void get_data (uint8_t* buffer) 
    { 
     error_details_t err; 
     bull_get_data(handle, buffer, &err); 
    } 

    template<typename = typename std::enable_if<std::is_same<Component, frog_component>::value>::type> 
    void get_data (uint8_t* buffer) 
    { 
     error_details_t err; 
     frog_get_data(handle, buffer, &err); 
    } 

    template<typename = typename std::enable_if<std::is_same<Component, bullfrog_component>::value>::type> 
    void get_data (uint8_t* buffer) 
    { 
     error_details_t err; 
     bullfrog_get_data(handle, buffer, &err); 
    } 
    ... 

private: 
    typename Component::handle_type handle; 
}; 

struct frog_component 
{ 
    using handle_type = frog_handle_t*; 
}; 

struct bull_component 
{ 
    using handle_type = bull_handle_t*; 
}; 

struct bullfrog_component 
{ 
    using handle_type = bullfrog_handle_t*; 
}; 

using frog_backend = backend<frog_component>; 
using bull_backend = backend<bull_component>; 
using bullfrog_backend = backend<bullfrog_component>; 

Noch fühlt es sich nicht richtig an. Eine andere Sache, die mich neben dem scheinbaren Code Duplikation stört, ist, dass, abgesehen von der Makro-Version :), sie nicht schön mit die Zugabe von C-Komponenten skalieren.

Ich habe immer den Eindruck, dass es etwas anderes geben muss, was besser ist. Kennt also jemand eine bessere, würdigere der modernen C++ - Name, Technik diese Art von Situation zu behandeln?

+1

Vorlage und Funktionszeiger? – Jarod42

+1

Ich verstehe die Downvotes nicht. Bitte sei wenigstens höflich und erkläre es. – celavek

+0

@ Jarod42 könnten Sie ein Beispiel geben? – celavek

Antwort

3

Was ich im Sinn haben, ist

template <typename H> using create_t = void (*)(H**, error_details_t*); 
template <typename H> using destroy_t = void (*)(H*, error_details_t*); 
template <typename H> using get_data_t = void (*)(H*, uint8_t* buffer, error_details_t*); 

template <typename H, 
      create_t<H> create, 
      destroy_t<H> destroy, 
      get_data_t<H> get_data> 
class backend 
{ 
    backend() { error_details_t err; create(&handle, err);} 
    ~backend() { error_details_t err; destroy(handle, err);} 

    backend(const backend&) = delete; 
    backend& operator =(const backend&) = delete; 
    backend(backend&&) = delete; 
    backend& operator =(backend&&) = delete; 

    void get_data(uint8_t* buffer) 
    { 
     error_details_t err; 
     get_data(handle, buffer, &err); 
    } 

private: 
    H* handle = nullptr; 
}; 

using bull = backend<bull_handle_t, &bull_create, &bull_destroy, &bull_get_data>; 
using frog = backend<frog_handle_t, &frog_create, &frog_destroy, &frog_get_data>; 
using bullfrog = backend<bullfrog_handle_t, 
         &bullfrog_create, 
         &bullfrog_destroy, 
         &bull_get_data>; 

Vielleicht error_details_t Mitglied sein kann, und Sie sollten auch Fehler behandeln.

+0

Das ist in der Tat interessant. Ich denke, ich kann damit weiter arbeiten. Ich habe bereits über Funktionszeiger nachgedacht, aber ich war mir nicht sicher, wie und mit was ich "kombinieren" sollte. – celavek

+0

Implementiert es und es funktioniert. Vielen Dank – celavek

1

Ich denke, dass @ Jarod42

etwas was impliziert, wurde dann
template <typename T> 
class component 
{ 
private: 
    typedef void (*destroyMethod)(T*,error_details_t*); 
    ... 
public: 
    component(destroyMethod,...) 
    { 
     destroy = destroyMethod; 
     ... 
    } 
    void Destroy() 
    { 
     error_details_t err; 
     destroy(handle, err); 
    } 
    ... 
private: 
    destroyMethod destroy; 
    T * handle; 
}; 

mit etwas instanziiert wie:

component<bull_handle_t> yourBull(bull_destroy, ...) 

Und Sie könnten wahrscheinlich den letzten Teil mit etwas mehr Arbeit

+0

Aber es gibt mehr als eine Funktion in der API. Soll ich Funktionszeiger für jeden von ihnen übergeben? Wie skaliert das, wenn ich der API weitere Funktionen hinzufüge? – celavek

+0

Wenn die Komponentenklasse jeden Typ spiegeln muss, solange die Schnittstelle Ihrer verschiedenen Typen nicht voneinander abweicht, wäre die Skalierung linear mit der Größe der Schnittstelle. Sie sollten sich den Code @ Jarod42 anschauen, der da geliefert wurde, da er die Funktionen in die Template-Deklaration stellt, die es Ihnen erlauben würde, einen netten Typedef zu machen, um jede Instanziierung stark zu reduzieren. – nissefors

0

wickeln weg Sie verfügen über eine Sammlung von C-Funktionen, die in Gruppen angeordnet sind und deren Namen und Signaturen einem einheitlichen Muster folgen. Wenn Ihr C++ - Wrapper eine dieser Funktionen aufrufen soll, muss der Name der Funktion im C++ - Code auf die eine oder andere Art angezeigt werden. Ihre Wahl dafür ist, die Namen an den entsprechenden Stellen in Ihrem Wrapper-Code wörtlich einzufügen oder systematisch über Makros zu generieren. Sie haben beide Alternativen abgelehnt.

Wenn Sie darauf bestehen, Makros zu vermeiden, können Sie einen Code-Generator für die Ausgabe von C++ - Quellen für alle Klassendefinitionen (die die benötigten Funktionen wörtlich nennen) schreiben. Vielleicht wäre das befriedigender, als alles von Hand zu codieren, und es würde Ihnen erlauben, die Klassen einfach neu zu erstellen, wenn sie sich als fehlerhaft erweisen, wenn die C-Bibliothek Änderungen oder Funktionen hinzufügt oder aus irgendeinem anderen Grund.

+0

Ich habe noch nichts zurückgewiesen. Ich wäge die Alternativen ab und ich frage mich auch, ob Leute mit besseren Ideen gekommen sind, daher die Frage. – celavek

+0

@celavek, es gibt keine anderen Optionen, nur Variationen von denen, die Sie bereits kennen. –

Verwandte Themen