Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

  1. #1
    Rédacteur

    Nouvelle bibliothèque libre de réflexion pour C++
    Bonjour à tous

    Tegesoft vient de publier la première version publique de CAMP, une bibliothèque libre permettant d'étendre les classes C++ pour leur donner des capacités d'introspection.

    CAMP permet de binder classes, propriétés, fonctions et objets de manière complètement non-intrusive, afin de pouvoir les manipuler de manière homogène en runtime. Cela permet par exemple d'utiliser ses propres classes dans des scripts (Python, Lua, ...), de les sérialiser automatiquement via des formats textuels ou binaires, de les envoyer sur le réseau, de construire des éditeurs de propriétés, etc.

    Le système est fortement inspiré de boost.python ou encore de luabind, sauf qu'il est plus abstrait et peut ensuite être exploité pour n'importe quelle utilisation, pas seulement dans un cadre particulier (un langage de script pour les exemples cités).

    CAMP est distribué sous licence LGPL v3.

    Un petit exemple d'utilisation sera plus parlant qu'un long discours :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
     #include <camp/camptype.hpp>
     #include <camp/class.hpp>
     #include <string>
     #include <iostream>
     
     // Let's define a class for handling persons
     class Person
     {
     public:
     
         // Construct a person from its name
         Person(const std::string& name) : m_name(name), m_age(0)
         {
         }
     
         // Retrieve the name of a person
         std::string name() const
         {
             return m_name;
         }
     
         // Retrieve the age of a person
         unsigned int age() const
         {
             return m_age;
         }
     
         // Set the age of a person
         void setAge(unsigned int age)
        {
             m_age = age;
         }
     
         // Make a person speak (tell about its name and age)
         void speak()
         {
             std::cout << "Hi! My name is " << m_name << " and I'm " << m_age << " years old." << std::endl;
         }
     
     private:
     
         std::string m_name;
         unsigned int m_age;
     };
     
     // Make the Person type available to CAMP
     CAMP_TYPE(Person);
     
     
     int main()
     {
         // Bind our Person class to CAMP
         camp::Class::declare<Person>("Person")
             .constructor1<std::string>()
             .property("name", &Person::name)
             .property("age", &Person::age, &Person::setAge)
             .function("speak", &Person::speak);
     
         // Retrieve it by its name
         const camp::Class& metaclass = camp::classByName("Person");
     
         // Construct a new person named John
         Person* john = metaclass.construct<Person>(camp::Args("John"));
     
         // Print its name
         std::string name = metaclass.property("name").get(john);
         std::cout << "John's name is: " << name << std::endl;
     
         // Set its age to 24
         metaclass.property("age").set(john, 24);
     
         // Make John say something
         metaclass.function("speak").call(john);
     
         // Kill John
         metaclass.destroy(john);
     
         return 0;
     }

  2. #2
    screetch
    Invité(e)
    ca ressemble un peu a ce que je fais
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    class RTTIEXPORT Test : public BugEngine::Object
    {
    private:
        int m_value;
        refptr<Test> m_other;
    public:
        Test();
        ~Test();
     
        u8& prop() const;
        void setProp(u8 value);
        void setValue(int v);
        void setValue2(const int& v);
     
        void test(const std::string& str) const;
     
        be_metaclass(RTTIEXPORT,Test,BugEngine::Object)
            void doStuff(const std::string& arg);
        be_properties
            be_property(Value)
                [be_read(m_value)]
                [be_write(m_value)];
     
            be_property(Value2)
                [be_read(m_value)]
                [be_set(setValue)];
     
            be_property(prop)
                [be_get(prop)]
                [be_set(setProp)];
     
            be_method(test);
            be_classmethod(doStuff);
        be_end
    };


    j'ai juste été embeté par le RTTIEXPORT qui est le classique declspec(dllimport)/dllexport. j'ai été obligé de le coller dans ma macro et ca c'est moche.

  3. #3
    Expert éminent sénior
    @screetch : ce que je trouve super avec CAMP, c'est que l'on a rien à faire depuis la classe elle-même. On pourrait faire, si l'on voulait, l'introspection de la SL avec

    Certes on doit déclarer à l'engine les attributs/fonctions qu'on veut "introspecter", mais ça peut se faire à l'extérieur. C'est possible aussi avec ton code d'introspection ?

  4. #4
    Rédacteur

    C'est du très beau travail. Plus le temps passe et plus je me dis que le C++ a de moins de chose à envier à Java du coté introspection/choses dynamiques.

    Mais il me reste une question (de noob): en pratique, a quoi ca peut servir ?(l'introspection en général).

    Merci.
    "Never use brute force in fighting an exponential." (Andrei Alexandrescu)

    Mes articles dont Conseils divers sur le C++
    Une très bonne doc sur le C++ (en) Why linux is better (fr)

  5. #5
    Rédacteur

    Citation Envoyé par Davidbrcz Voir le message
    Mais il me reste une question (de noob): en pratique, a quoi ca peut servir ?(l'introspection en général).
    J'avoue que je me le demande aussi.

    Si quelqu'un avait une explication simple avec un exemple concret à proposer, je lui en serait éternellement reconnaissant.
    Raymond
    Vous souhaitez participer à la rubrique Réseaux ? Contactez-moi

    Cafuro Cafuro est un outil SNMP dont le but est d'aider les administrateurs système et réseau à configurer leurs équipements SNMP réseau.
    e-verbe Un logiciel de conjugaison des verbes de la langue française.

    Ma page personnelle sur DVP
    .

  6. #6

  7. #7
    Membre averti
    Perso je m'étais servis de l'introspection (en java) dans un de mes cours sur le calcul réparti:

    Un client demande un objet à un serveur, cet objet est crée coté serveur et une "interface" est envoyée au client. Le client peut alors appeler les méthodes de l'interface qui se charge de transférer au serveur la classe, la méthode et les paramètres. Le serveur récupère le tout et utilise l'introspection pour invoquer la méthode sur l'objet.

    Au final ça permet de réaliser les calculs côté serveur avec l'impression pour le client que tout se passe en local (la syntaxe notamment est la même).

    L'idée était de leur faire redévelopper une architecture de type CORBA, JNI ou encore DCOM.

  8. #8
    Rédacteur/Modérateur

    Parmi les applications classiques :
    - La sérialisation automatique ou semi-automatique des données (pour sauvegarde ou application répartie)
    - L'écriture simplifiée et haut niveau d'outils autour de langage (par exemple générer automatiquement une boîte de propriétés pour un composant graphique dans un éditeur de boîtes de dialogue, la génération de wrapper pour interfacer le code avec un autre langage...)
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  9. #9
    Expert éminent sénior
    Citation Envoyé par Alp Voir le message
    L'autre bibliothèque de Tegesoft, GICS, utilise CAMP.
    Cf :
    Dependencies Qt >= 4.5.0
    CAMP >= 0.6.0
    http://www.tegesoft.com/products/gics
    Ce que je me demande perso, c'est qu'est-ce qu'apporte CAMP par rapport à Qt qui propose déjà quelque chose d'assez évolué (surtout s'il y a la dépendance à Qt) ?

    Au niveaux des utilités du truc, ça peut être très utile pour s'interfacer avec un interpréteur de script et rendre facilement ses objets C++ utilisables depuis un langage de script. C'est d'ailleurs ce que permet de faire Qt avec QtScript. Ca économise le processus de wrapping avec SWIG par exemple.

  10. #10
    Expert éminent sénior
    Citation Envoyé par Aurelien.Regat-Barrel Voir le message
    Ce que je me demande perso, c'est qu'est-ce qu'apporte CAMP par rapport à Qt qui propose déjà quelque chose d'assez évolué (surtout s'il y a la dépendance à Qt) ?
    Peut-être qu'ils prévoient de choisir le "backend" (la bibliothèque sous-jacente) dans des versions futures ? Auquel cas ils ne peuvent pas se reposer sur le système de méta-objets de Qt.

  11. #11
    Membre éclairé
    Ca m'a l'air vraiment pas mal.
    La première utilisation à laquelle je pense, et qui est citée, serait d'effectuer des bindings avec des langages de scripts.

    Je n'ai pas de liens sous la main, mais je me rappelle avoir lu des discussions (anciennes) de David Abrahams à propos d'étendre Boost.Python à un framework plus vaste et général pour exposer des types C++ à des langages de scripts.

    Faire des bindings pour mozilla spidermonkey puis google v8 (javascript) furent des projets inspirés de Boost.Python que j'avais commencé puis lâchement abandonné. Je vais étudier les sources de CAMP avec beaucoup d'intérêt

  12. #12
    Rédacteur

    surtout s'il y a la dépendance à Qt
    C'est GICS qui dépend de Qt, pas CAMP
    CAMP dépend uniquement de boost (qui est le minimum syndical pour ce genre de bibliothèque qui torture le compilo).

    Ce que je me demande perso, c'est qu'est-ce qu'apporte CAMP par rapport à Qt qui propose déjà quelque chose d'assez évolué
    Le système de Qt est très bien tant que l'on ne sort pas du cadre de Qt. En dehors de ça, on se rend vite compte qu'il est peu flexible et très orienté vers une utilisation spécifique.

    Pêle-mêle, voici quelques avantages de CAMP par rapport aux meta-objets Qt :
    - non-intrusif pour la classe bindée
    - possibilité de binder des classes externes
    - pas besoin d'appartenir à une hiérarchie de QObject
    - pas besoin du précompilateur MOC
    - possibilité d'héritage multiple
    - on peut binder tout type de membre sans limitation, pas seulement des couples de getter/setter au prototype figé
    - les propriétés peuvent avoir des attributs et des tags dynamiques, qui peuvent servir à personnaliser et étendre le système au-delà de ce qu'il prévoit initialement
    - on peut avoir des objets qui ont comme propriétés d'autres objets, qui eux-même ont des objets, etc. ; avec Qt, un meta-objet ne peut contenir que des propriétés de type POD
    - pas mal de détails techniques assez chiadés dont on ne se rend compte qu'en utilisation poussée
    - ...

    La première utilisation à laquelle je pense, et qui est citée, serait d'effectuer des bindings avec des langages de scripts.
    Avec les prochaines versions seront distribués des modules de ce genre. Par exemple, un module pour interfacer CAMP avec Python, ou encore Lua.
    Il y aura également un module de sérialisation XML et binaire, et un éditeur de propriétés développé avec Qt.

  13. #13
    screetch
    Invité(e)
    Citation Envoyé par Alp Voir le message
    @screetch : ce que je trouve super avec CAMP, c'est que l'on a rien à faire depuis la classe elle-même. On pourrait faire, si l'on voulait, l'introspection de la SL avec

    Certes on doit déclarer à l'engine les attributs/fonctions qu'on veut "introspecter", mais ça peut se faire à l'extérieur. C'est possible aussi avec ton code d'introspection ?
    c'est euh, possible mais un peu tordu pour l'instant. mais l'avantage du RTTI c'est que tu ne sais pas vraiment ce que tu manipules, c'est dynamique. Je ne comprends pas comment ils s'en sortent sans au moins une methode virtuelle qui te permet d'optenir la classe de l'objet ?

    dans ma bibliotheque, j'ai besoin de la classe Objet qui contient juste une methode virtuelle (qui pourrait etre pure) : metaclass(). c'est cette methode qui fait tout. De plus, une metaclasse a aussi une metaclasse ce qui permet aussi de 'introspecter; de meme une proprieté a une metaclasse, une fonction a une metaclasse. Pour l'instant les seuls trucs qui n'ont pas de metaclasse c'est les attributs (je me demande pourquoi ils ont pas de metaclasse d'ailleurs)
    la seule propriété necessaire de tous les objets du RTTI est d'avoir une metaclasse, dés qu'ils en ont une alors on peut les manipuler.

    Dans l'exemple du debut, si tu envoies un objet de type Person a un script, sans connaitre son type exact, comment fais tu pour le manipuler (sachant que si ca se trouve c'etait une instance de Client)

  14. #14
    screetch
    Invité(e)
    sinon aussi, pour les idées d'utilisation, moi j'avais couplé Mono avec mon RTTI, et Lua est couplé depuis un moment, et j'avais couplé Gtk aussi, ce qui me permettait depuis Lua de créer des fenetres GTK et d'appeler des methodes ecrites en C#

    le RTTI est un noeud central qui permet de faire circuler beaucoup d'informations; tout les trucs qui se branchent dessus peuvent communiquer ensemble; c'est vraiment très puissant

  15. #15
    Rédacteur

    Je ne comprends pas comment ils s'en sortent sans au moins une methode virtuelle qui te permet d'optenir la classe de l'objet ?
    C'est la macro CAMP_TYPE qui associe un identificateur à la classe, de cette manière :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    #define CAMP_TYPE(type) \
                template <> struct StaticTypeId<type> \
                { \
                    static const char* get() {return #type;} \
                }; \

    Ensuite on récupère très simplement l'identificateur CAMP d'un objet comme ceci :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    template <typename T>
    const char* staticTypeId(const T&)
    {
        return StaticTypeId<T>::get();
    }

    Puis sa metaclasse :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    template <typename T>
    const Class& classByObject(const T& object)
    {
        return detail::ClassManager::instance().getById(staticTypeId(object));
    }

    (le code a été volontairement simplifié, il y a dans le vrai code diverses feintes en plus pour pallier certains problèmes tordus, ainsi que d'autres surchages / variantes pour gérer tous les cas possibles)

    Il y a toutefois une situation où l'on doit introduire une fonction virtuelle dans la classe (masquée par une macro) : c'est dans le cas de hiérarchies de classes, si l'on veut que CAMP puisse exploiter les notions de parenté (ie. récupérer la metaclasse du type dérivé en passant un pointeur sur classe de base).

    Dans l'exemple du debut, si tu envoies un objet de type Person a un script, sans connaitre son type exact, comment fais tu pour le manipuler (sachant que si ca se trouve c'etait une instance de Client)
    Je ne suis pas sûr de comprendre. Au moment où tu l'envoies au script tu connais son type, soit via son type statique C++ soit via sa metaclasse si tu ne peux plus trimballer son type statique. CAMP définit une classe pour ça : camp::Object, qui contient le pointeur vers l'objet plus sa metaclasse.

  16. #16
    Rédacteur

    Salut,
    Globalement, ça a l'air d'être un beau boulot d'orfèvrerie. Chapeau bas

    Citation Envoyé par Laurent Gomila Voir le message
    [...] tu connais son type, soit via son type statique C++ soit via sa metaclasse [...]
    Salut,
    Dans ton exemple, un ligne m'intrigue :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
         const camp::Class& metaclass = camp::classByName("Person");
     
         // Construct a new person named John
         Person* john = metaclass.construct<Person>(camp::Args("John")); // celle-ci

    Pourquoi faut-il spécifier à nouveau la classe dans metaclass.construct alors que metaclass semblerait être déjà la 'méta-classe' de la classe Person ?

  17. #17
    Expert éminent sénior
    Citation Envoyé par 3DArchi Voir le message

    Pourquoi faut-il spécifier à nouveau la classe dans metaclass.construct alors que metaclass semblerait être déjà la 'méta-classe' de la classe Person ?
    A cause de l'impossibilité de déduire automatiquement le type de retour d'une fonction template, je pense.
    C'est comme si tu pouvais récupérer automatiquement le bon type en voulant avoir la valeur contenue dans un boost::any. Pas possible, on a perdu l'information du type, et en fait pour être exact on ne l'a jamais eue sauf sous forme de chaîne, chose dont le compilo se contrefiche.

  18. #18
    screetch
    Invité(e)
    dans un script tu n'aurais pas cette information de type disponible par contre...

  19. #19
    Rédacteur

    Pourquoi faut-il spécifier à nouveau la classe dans metaclass.construct alors que metaclass semblerait être déjà la 'méta-classe' de la classe Person ?
    Parce que la classe camp::Class n'est pas template, c'est la même pour tous les types C++ bindés. Donc à partir d'une instance de camp::Class on ne peut pas retrouver le type statique C++ correspondant (d'autant plus qu'il peut y en avoir plusieurs).
    On peut retrouver dynamiquement toutes les infos que tu veux sur la classe cible, mais jamais le type statique à la compilation.

    dans un script tu n'aurais pas cette information de type disponible par contre...
    Si bien sûr, via la metaclasse de l'objet, qui sera normalement associée à l'objet du côté du module C++ qui gère le binding. Sinon comment pourrait-on retrouver les propriétés et fonctions associées à l'objet ?

  20. #20
    screetch
    Invité(e)
    oui c'est bien ce que je me demandais
    donc en fait tu n'as pas un objet, tu as un couple metaclasse-objet
    l'objet lui meme ne connait pas sa metaclasse, donc les clients manipulent en general le ocuple

    pk je comprend mainnant

###raw>template_hook.ano_emploi###