Journal CAMP 0.7.0 - Bibliothèque de réflexion C++ sous LGPL

Posté par (page perso) .
Tags : aucun
23
15
juin
2010
La réflexion (ou introspection) est un mécanisme qui permet à un programme d'examiner, voire de modifier, ses structures internes. En d'autres termes, c'est la capacité d'un programme à examiner son propre état.

Certains langages de programmation offrent ce type de fonctionnalité, notamment Java, Smalltalk ou C# qui fournissent des outils pour connaître et utiliser la classe d'un objet, ses propriétés et ses méthodes (ou parle en général de méta-classe, de méta-propriété et de méta-fonction).

L'introspection peut être utilisée pour fournir des bindings vers des langages de script, pour écrire des éditeurs de propriétés ou faire de la sérialisation. L'intérêt étant que le code de ces outils est écrit une fois pour fonctionner sur la structure abstraite (méta-classe, méta-propriété, méta-fonction), et peut ensuite être utilisé dans n'importe quel programme.

Malheureusement, le langage C++ ne fournit aucun moyen de faire de la réflexion. Il existe cependant un certain nombre de bibliothèques fournissant ce type de fonctionnalité, la plus connue étant probablement Qt avec ses QObjects.

Cependant, pour générer toutes les "méta-informations" nécessaires à la réflexion, la majeure partie des ces bibliothèques requiert soit d'utiliser un pré-compilateur (moc pour Qt), soit de déclarer ses informations en ajoutant tout un tas de macros dans le header de chaque classe (à l'exception de C++ Mirror). De plus, il est souvent nécessaire d'hériter d'une classe de base (QObject pour Qt).

Ceci nous a amené à développer CAMP, une bibliothèque de réflexion généraliste pour C++. CAMP utilise intensivement les templates C++ (et boost) et est non-intrusif (à l'exception de la gestion du polymorphisme pour les méta-classes ; une alternative basée sur le RTTI du langage devrait être ajoutée dans la prochaine version).

CAMP ressemble à Luabind ou à boost::python mais est généraliste. De ce fait, il est possible d'écrire un module pour CAMP permettant d'embarquer un interpréteur Python, un autre pour embarquer un interpréteur Lua, ou encore un pour faire de la sérialisation XML. L'intérêt étant que le binding des classes n'est fait ici qu'une seule fois. Lorsqu'un nouveau module pour CAMP est disponible, vous n'avez aucun binding supplémentaire à écrire.

Le mieux dans tout ça ? C'est sous LGPL !
La version 0.6.0 de CAMP était déjà distribuée sous GPL, mais une licence plus permissive a été choisie afin de favoriser l'utilisation de la bibliothèque.

De plus, un nouveau site internet (basé sur Redmine) est disponible, avec bug tracker, wiki, documentation de l'API. Le code est quand à lui hébergé sur GitHub. Un forum est également disponible.
  • # Comparaisons

    Posté par (page perso) . Évalué à 5.

    Alors quelle est la différence, et les avantages/inconvénient par rapport au autre frameworks.

    Le journal donne quelques avantages par rapport a Qt, par example
    - Pas besoin du préprocesseur comme Moc. (Mais est-ce que un préprocesseur est vraiment un problème ?)
    - Pas besoin d'hériter de QObject. (Ça c'est cool car QObject a un overhead de ~100 octets par objects sur une arch 64bit)

    Par contre, un inconvénient de taille est que il faut enregistrer sois même toutes les fonctions manuellement.

    Mais qu'en est-il des performances et de l'utilisation de la mémoire ?
    Qt ajoute toutes les chaînes de caractère l'une a la suite des autres de manière assez compacte. Et sans presque aucun impact lors du lancement.
    CAMP, va éparpier les chaînes un peu partout dans le programme, plus avoir plein de structures sur le tas. (Sans compter aussi
    toutes les tables virtueles (mémoire non-partagable entre proccesses))
    Et le coût lors du démarrage est il ou non négligable ? (Enregistrements de toutes les classes dans les structures internes + rélocations)

    Comptez vous a long terme avoir une compatibilité source ou binaire ?
    • [^] # Re: Comparaisons

      Posté par . Évalué à 2.

      Salut

      Soyons clairs : CAMP fournit un service d'abstraction haut niveau pour programmes plus ou moins conséquents, pas un truc dont le but est d'être embarqué sur des systèmes avec 32 Ko de mémoire vive. Si tu cherches quelque chose qui économise 100 octets ou qui ne fragmente pas la mémoire, alors clairement CAMP ne t'intéressera pas, ce n'est pas la voie que l'on poursuit.

      En ce qui concerne les avantages, utiliser un préprocesseur n'est pas un luxe que l'on peut toujours se permettre car cela complexifie la chaîne de compilation. Par exemple, avec Visual Studio sans le plugin d'intégration Qt (qui n'est pas disponible librement), c'est quasiment impossible de développer, les règles d'invocation des différents outils de précompilation de Qt sont bien trop laborieuses à écrire.

      L'avantage de ne pas hériter de QObject n'est bien sûr pas d'économiser 100 octets, mais de se débarasser d'une dépendance sur les classes et de rendre le système non-intrusif. C'est un gage de maintenabilité et de d'adaptabilité très important, et ça évite les petites surprises du genre "on ne peut pas hériter de 2 bases QObject". En clair, avec CAMP tu peux binder std::string ; pas avec Qt.

      Quant à l'enregistrement manuel des metaclasses, rien n'empêche d'utiliser un précompilateur du genre gccxml ou open-c++ (désolé si les noms sont approximatifs) pour générer le code d'enregistrement. L'important est que l'on n'impose pas d'outil externe à l'utilisateur, c'est lui qui choisit.

      Nous sommes soucieux des performances, de sorte que CAMP ne soit pas un frein quelque soit l'utilisation que l'on en fait ; concrètement il faut qu'on puisse utiliser CAMP pour binder des classes en Python ou en Lua et garder le maximum de performances que ces langages fournissent.

      Enfin, oui nous comptons bien entendu préserver une compatibilité binaire et source. A partir de la version 1.0, les choses seront faites dans les règles.
      • [^] # Re: Comparaisons

        Posté par (page perso) . Évalué à 1.

        Je trouve justement que de ne pas avoir de précompilateur est un gros défaut. Ça te mâche tout le travail et te garanti l'absence d'erreurs, ce qui n'est pas le cas si tu le fait à la main. De plus instrumenter un grand nombre de classes à la main peut s'avérer extrêmement fastidieux.

        Je bosse depuis plusieurs années avec Qt et je l'utilisation de moc est complètement transparente pour moi:
        sous unix les systèmes de build qmake/cmake font le boulot tout seuls.
        sous visual studio, pas besoin de plugin, il suffit d'écrire une "Build Rule" une fois pour toutes pour faire passer moc sur les .h et après on l'oublie. Pas besoin de version pro pour ça, c'est possible avec la version "express" de visual.

        Je trouve donc que ces critiques sur moc ne sont pas vraiment justifiées vu que c'est assez facile de l'oublier si ton système de build tient la route.

        Par contre, je suis d'accord avec toi sur les autres remarques (obligation d'hériter de QObject et la macro Q_OBJECT à ajouter)
        • [^] # Re: Comparaisons

          Posté par . Évalué à 1.

          CAMP n'est pas incompatible avec l'utilisation d'un précompilateur. Les gens peuvent en utiliser un s'ils le veulent, pour générer les déclarations de meta-classes. Donc je trouve que c'est au contraire un avantage que CAMP n'impose pas directement l'utilisation d'un précompilateur, c'est à l'utilisateur de choisir ce qu'il préfère.

          En plus, utiliser un précompilateur implique que l'utilisateur n'a aucun degré de personnalisation sur les infos qui sont exportées :
          - choisir ce qui est exporté et ce qui ne l'est pas
          - ajouter des informations non présentes dans le code C++ (tags, visibilité, informations d'ownership, ajouter de l'aide, ...)
          - changer le nom des symboles exportés
          - ...
          Pour pallier à ça il faudrait ajouter des informations spécifiques dans le code (intrusives !), et dans ce cas on perd tout l'intérêt du précompilateur, autant écrire le binding directement.

          Ceci-dit, idéalement nous pourrions choisir un précompilateur parmi les outils libres existant, et l'adapter pour proposer directement aux utilisateurs notre propre précompilateur pour CAMP. Je pense que ce serait un très bon ajout pour CAMP.
          • [^] # Re: Comparaisons

            Posté par (page perso) . Évalué à 1.

            Avec Qt on choisis, à l'aide de macro ce qui est exporté ou non
            (on dit "public slots:" pour exporter les slots)

            On peux aussi ajouter des attributs and le code source pour "tagger" des fonctions (fonction dépréciée, internes, ...)
            Je ne vois pas en quoi ses informations sont intrusive. au contraire, elle sont là où elle doivent être, à coté de la déclaration de la fonction.

            Tu dis que vous pouvez utilisé un précompilateur, mais quels sont les précompilateur qui marche "out-of-the-box" pour le moment.
            Car dit comme ça, Qt ne force pas l'utilisation du moc non plus, n'importe quel préprocesseur qui peux générer le code qui va bien convient, (il pourrait même être écrit à la main pour les mazo). C'est juste que le moc fonctionne bien et que donc il n'y a besoin de rien d'autre.
            • [^] # Re: Comparaisons

              Posté par . Évalué à 1.

              Avec Qt on est obligé d'ajouter du code spécifique pour "piloter" le précompilateur, donc où est l'intérêt de celui-ci ? Autant écrire directement le binding, c'est un peu plus verbeux mais au moins on évite l'utilisation du précompilateur, et on atteint un degré de flexibilité bien supérieur (et ne me dis pas que le binding entre membres C++ et proriétés/slots est flexible avec Qt).

              Ces informations sont intrusives car elles se trouvent à l'intérieur de la définition de la classe. Qu'elles soient bien placé ou non n'y change rien : ta classe sera forcément fortement couplée au système de meta-objet et dépendante de celui-ci. Or je ne t'apprendrai rien en te disant que le découplage des fonctionnalités orthogonales est une bonne chose en C++.
              Par exemple, limitation très simple : avec un tel système (intrusif) il est impossible de binder une classe qui n'a pas été prévue dès le départ pour cette utilisation (et on ne bosse pas toujours avec des classes qu'on a écrites soi-même).

              En ce qui concerne les précompilateurs, on ne s'est pas encore penchés sur le problème, mais il y a des projets connus qui devraient répondre à ces attentes. C'est un projet à creuser un peu plus.
        • [^] # Re: Comparaisons

          Posté par . Évalué à 2.

          "Par contre, je suis d'accord avec toi sur les autres remarques (obligation d'hériter de QObject et la macro Q_OBJECT à ajouter) "
          N'étant pas un développeur C++ (et donc encore moins développeur Qt), pourrait-on en savoir d'avantage à ce sujet ? Quelles sont les contraintes que cela impose d'hériter de QObject et d'utiliser la macro Q_OBJECT ?

          Merci.
          • [^] # Re: Comparaisons

            Posté par (page perso) . Évalué à 2.

            Pour écrire un nouveau QObject, on fait ceci:

            class MaClasseAMoi : public QObject
            {
            Q_OBJECT
            public:
            MaClasseAMoi() : QObject()
            {
            }
            virtual ~MaClasseAMoi()
            {
            }
            }


            La macro Q_OBJECT, on s'en fout, c'est juste un placeholder pour des methodes que Qt va ajouter.
            Par contre le fait de devoir hériter de QObject peut être ennuyeux parfois: si tu dois hériter d'une classe de base d'un framework que tu utilise et que tu aimerai en faire un QObject aussi => tu es obligé de faire de l'héritage multiple, c'est pas terrible et surtout QObject doit être la première classe de base, dans la déclaration d'héritage sinon, ça marche pas (je sais pas si c'est toujours le cas).

            Après pour l'intrusivité des macros Qt, je trouve pas ça gènant non plus, c'est comme les annotation en Java ou Python et comme dit plus haut: les information sont dans le contexte où elles doivent être: dans la declaration de la classe et pas éparpillées dans d'autres endroits du code.

            Bon, j'ai pas une vision très objective : j'ai toujours considéré que le C++/Qt était un langage différent du C++.
            • [^] # Re: Comparaisons

              Posté par . Évalué à 2.

              A mon souvenir une classe template ne peut hériter de QObject non plus.
          • [^] # Re: Comparaisons

            Posté par . Évalué à 1.

            Tout d'abord, QObject impose des restrictions purement techniques :
            - en cas d'héritage multiple, QObject doit être la première base
            - on ne peut pas hériter de plusieurs classes dérivant de QObject
            - un objet dérivé de QObject n'est pas copiable

            Ensuite, ce système est intrusif. Il faut en effet taper directement dans la déclaration de la classe pour modifier ses meta-informations.
            Conséquence directe : on ne peut pas appliquer le système sur des classes figées et non prévues pour fonctionner avec Qt (par exemple je ne peux pas créer une meta-classe Qt pour la classe Toto de la bibliothèque SuperLib que je viens d'installer -- dommage).

            Dans une moindre mesure, le côté intrusif est également pénalisant au niveau des dépendances entre les fichiers (temps de compilation accrus) et de la maintenance (le code relatif aux meta-informations est imbriqué dans la déclaration de la classe et mélangé au reste).

            Enfin, on ne pourrait pas non plus utiliser les meta-informations uniquement de manière interne, avec un tel système on est obligé d'exposer les meta-informations à toute personne extérieure qui utilise la classe.

            De manière générale, on apprend toujours que découpler les fonctionnalités orthogonales est meilleur que de tout imbriquer ensemble.

Suivre le flux des commentaires

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