Forum Programmation.c++ Wrapper

Posté par . Licence CC by-sa
2
3
avr.
2016

Bonjour,

Je développe principalement en C++, mais je dois régulièrement appeler des API C (gstreamer par exemple).
Jusque là, je ne me posais pas trop de question et je faisais toujours attention à ne pas mélanger les allocation mémoires (pour les GstBuffer par exemple).

Là j'ai besoin de dessiner en overlay sur une surface vidéo.

J'ai créé une première version qui wrappe la mémoire d'un GstBuffer vers une QImage Qt, et je dessine facilement à l'aide d'un QPainter. Cette solution convenait bien mais je voudrais supprimer cette dépendance à Qt.

Je choisis donc cairo, qui a de forte chance d'être présent si gstreamer est là. Je vois qu'un binding cairomm existe mais le package de ma distribution a une version de retard (Gentoo). Se pose donc le problème de l'avancé du binding par rapport à la bibliothèque.

Ma question est donc quel intérêt (au delà de la syntaxe) ai-je à favoriser le binding C++ plutôt qu'utiliser directement cairo ?

D'autre part, si vous connaissez une lib C++ à la CImg pour faire ce genre de chose ?
(CImg ne convient pas car la composition nécessite un buffer argb classique entrelacé (A1R1G1B1, A2R2G2B2…) alors que CImg utilise un buffer de pixel par bloc (R1R2R3..,G1G2G3..,B1B2B3..,A1A2A3..) et que je veux dessiner directement sur la composition pour éviter les copies de buffers.

  • # L'intérêt? Éviter de refaire l'encapsulation, mais...

    Posté par . Évalué à 3.

    Ma question est donc quel intérêt (au delà de la syntaxe) ai-je à favoriser le binding C++ plutôt qu'utiliser directement cairo ?

    Simple: quand on utilise une lib C, il faut se fader l'encapsulation. Alors, quand ce sont des pointeurs, ça va encore, un coup de

    //code filé par la lib
    //{
    struct my_damn_struct
    {
    };
    my_damn_struct* make_my_damn_struct( ... )
    {
      return malloc my_damn_struct;
    }
    void clean_my_damn_struct( my_damn_struct* p )
    {
      free( p );
    }
    //}
    //code que tu dois te fader
    typedef std::unique_ptr<my_damn_struct,void (*)(my_damn_struct*)> my_damn_struct_ptr;
    my_damn_struct_ptr make_my_damn_struct_ptr( ... )
    {
      return my_damn_struct_ptr( make_my_damn_struct( ... ), clean_my_damn_struct );
    }

    et te résigner à ne plus utiliser que make_my_damn_struct_ptr pour initialiser tes données et la méthode get() pour y accéder. Bien sûr, aucun outil pour le garantir. Et, bien sûr, si tu dois instancier beaucoup de pointeurs, tu vas avoir un callback en rab à chaque fois, ce qui va fatalement consommer de la mémoire (mais j'imagine que la variante tableaux de unique_ptr permets d'éviter ça… tant qu'on n'a pas besoin de vector ou de list…).
    Mais, ça va encore.
    Tu as aussi l'alternative de faire une classe dédiée à chaque structure, mais c'est long, chiant, avec un haut risque d'erreur (comme tout code long et chiant en fait).

    Par contre, si la bibliothèque n'utilise PAS des pointeurs (comme OpenGL ou les sockets BSD), la, tu l'as dans le… tu es obligé de faire des classes spécialisées: std::unique_ptr ne sait gérer que de la mémoire (et avec une interface bien lourde à utiliser en code, sans parler du fait d'avoir des callbacks dans chaque instance de pointeur…)
    Personnellement, je me suis bricolé un template unique_res pour ça, qui me sert au final aussi pour les pointeurs.

    Donc, le voila, l'intérêt d'utiliser des bindings: laisser le boulot chiant aux autres.
    Mais, parce que tu auras noté le "mais…" de mon titre, ça veut aussi dire que tu te lies à une bibliothèque précise, de qualité variable, un peu partout dans ton code, que tu ne contrôles que peu ou prou.
    Moi, je me suis aperçu qu'au final je finis toujours par encapsuler ce que je récupère de l'«étranger», alors je préfère utiliser les sources C. Les fois ou je me suis laissé aller à utiliser (par flemme) les bindings C++, j'ai finit par le regretter rapidement, pour plusieurs raisons:

    • API pas forcément bien faite (libpqxx est ma dernière expérience à ce sujet) par exemple au niveau des exceptions (d'ailleurs, il semble que celles-ci n'aient pas d'ABI standard, ce qui signifie que se lier à une DLL/so qui lance des exceptions c'est dangereux).
    • API bugguées (j'ai eu une mauvaise expérience avec opencv, par le passé, ou j'étais du coup repartit sur l'API C parce que celle en C++ semblait bugguer. Mais ça date.)
    • API mal documentées (pas d'exemple pour le coup, mais je me souviens l'avoir vécu…)

    Alors que souvent, les libs C, utilisées par plus de monde, souffrent moins (pas: "ne souffrent pas", mais "souffrent moins", j'insiste) de ces problèmes.
    Du coup, je préfère utiliser directement les libs C et les encapsuler moi-même. De toute façon, même les objets C++ que j'importe je les encapsule, pour limiter l'impact d'un changement de lib à un nombre de fichiers restreints dans mon code.
    Comme ça, le cœur est entièrement «maison» et fait appel à des interface qui elles interagissent avec les choses sur lesquelles je n'ai pas le contrôle direct.
    Par contre, mon approche à un inconvénient de taille: plus de lignes de code, maintenance «plus chère» tant que les interfaces bougent (mais une fois stable, c'est le contraire).

    • [^] # Re: L'intérêt? Éviter de refaire l'encapsulation, mais...

      Posté par . Évalué à 1.

      Merci,

      Je vais donc continuer à utiliser l'api C en gérant l'instance et la destruction à la main. Dans mon cas cairo fait bien le job.
      Par contre, je fais ça avec des pointeurs nus, avec tous les pointeur C dans une classe private d-pointer, avec les deleter dans le destructeur de cette classe.
      C'est pas tip top C++ mais ça fonctionne. Je vais regarder un peu si il y a une alternative pas trop chiante.

      Merci en tout cas de partager ton expérience.

  • # Mon modèle

    Posté par . Évalué à 1.

    Pour information, j'encapsule de cette manière :

    struct CairoSurfaceDeleter { void operator() ( cairo_surface_t *cs ) { cairo_surface_destroy( cs ); } };
    std::unique_ptr<cairo_surface_t, CairoSurfaceDeleter> cairoSurface;

    mais comme j'utilise c++11, j'aimerais bien utiliser une lambda pour le destructeur.
    Si quelqu'un sait faire, je gagnerai un peu de temps.

    • [^] # Re: Mon modèle

      Posté par . Évalué à 2.

      Tu te compliques la vie, la.

      Déjà, pas besoin d'écrire un foncteur, une simple fonction suffit:

      void delete_foo( foo *f )
      {
          foo_delete( f );
      }
      
      typedef std::unique_ptr<foo, delete_foo> foo_ptr;

      Et du coup, tu peux utiliser facilement les lambda de cette façon:

      typedef std::unique_ptr<foo, []( foo *f ){ foo_delete f; } > foo_ptr;

      Enfin, la syntaxe n'est peut-être pas exactement la bonne…

      PS: ça ne te fera pas gagner de temps à l'écriture, surtout si tu ne fais pas de typedef, mais tu pourrais éventuellement en gagner à la maintenance.

  • # re

    Posté par . Évalué à -1.

    Bonjour,

    Eh bien, c'est toujours prudent de le réaliser avec C++, parce que c'est plus professionnel. Alors, si tu veux un coup de pouce en ce qui concerne la programmation C++, cette formation va t'aider. voici le lien:http://www.alphorm.com/tutoriel/formation-en-ligne-cplusplus

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.