IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
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

C++/CLI Discussion :

recoder MethodInfo->Invoke avec il.Emit


Sujet :

C++/CLI

  1. #1
    Membre averti

    Profil pro
    Étudiant
    Inscrit en
    Décembre 2004
    Messages
    499
    Détails du profil
    Informations personnelles :
    Âge : 37
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Décembre 2004
    Messages : 499
    Points : 422
    Points
    422
    Par défaut recoder MethodInfo->Invoke avec il.Emit
    Bonjour,
    Quelqu'un a déjà utilisé du il.Emit pour générer des méthodes au runtime ?
    Il faut forcément un délégate pour appeler la méthode ainsi générée ?
    En C++/CLI je peux aussi récupérer un pointeur de méthode, non ? (avec InteropServices.Marshal.GetFunctionPointerForDelegate ou MethodInfo RuntimeMethodHandle.GetFunctionPointer ?)

    Ici je voudrais faire un générateur dynamique de stub interprété->compilé, c'est à dire un stub qui permet appeler une méthode en lui passant un tableau de paramètres (donc exactement ce que fait MethodInfo.Invoke mais en plus efficace en terme de perf vu que MethodInfo.Invoke fait plein de choses "inutiles", alors qu'un stub ne fait que 10 instructions MSIL) :

    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
     
    /// classe et méthode compilée
    ref class A {
     String^ UneMethode(Int32 k, array<Type^> t) {
        /* ... */
     }
    };
     
    /// classe qui génère des stub
    ref class StubFactory {
       D^ GenerateStubFor(MethodInfo ^m) {
          il.Emit(...)
          il.Emit(...)
       }
     
       /// méthode générée dynamiquement avec il.Emit
       static Object^ A_UneMethode_Stub(Object^ obj, array<Object^>^params) {
           // implémentation en C++/CLI en guise d'exemple, c'est en MSIL qu'il       
          //faut savoir la coder pour pouvoir la générer dynamiquement
          return (*((A^*)&obj))->UneMethode((Int32)params[0],(array<Object^>^)params[1]);
          /// le cast *((A^*)&obj)) est un static_cast : il permet d'éviter le dynamic_cast qui fait le runtime check du type (qui est coûteux si on sait déjà à l'avance que le type est bon). 
          /// Pour params[0] et params[1] je pense qu'on ne peut pas y échapper.
       }
    };
    Je m'étonne de ne pas trouver sur google quelqu'un qui aurait déjà codé la même chose.

    Je pense que StubFactory.GenerateStubFor devrait renvoyer un D^ qui serait un type de delegate :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    delegate Object ^ D(Object^ obj, array<Object^>^params);
    Ensuite, j'aimerais aussi faire la même chose pour PropertyInfo/FieldInfo.Get/Set Value.

    Une dernière question : c'est compliqué d'appeler une méthode C++ native __stdcall depuis du IL généré avec il.Emit ?
    Je pense m'en sortir en utilisant ILdasm sur des bouts de code C++/CLI très simple me servant d'exemple

    Une toute dernière question : est-ce qu'il faut vraiment générer dynamiquement (avec il.emit) un stub par méthode, ou est-ce qu'il serait possible d'écrire, statiquement, donc sans passer par il.emit, une seule méthode (entièrement écrite en MSIL) qui pour n'importe quel type de méthode arrive à pousser le tableau de paramètre sur la pile et à faire le call ?

    A mon avis pour écrire un champ d'un objet on est obligé de passer par il.Emit (il n'y a aucune fonction de la réflection qui donne l'offset runtime d'un champ, donc impossible de faire une fonction générique capable d'écrire/lire un champ)

  2. #2
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 369
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 369
    Points : 41 519
    Points
    41 519
    Par défaut
    J'ai déjà utilisé System.Reflection.Emit pour créer des getters/setters autour de champs (afin d'obtenir des delegates, bien plus performants que FieldInfo.SetValue()), mais c'est tout. Et c'était en C#.

    Aussi, d'après mes travaux de l'époque, il me semblait que C++/CLI n'autorisait pas les pointeurs de membre vers des types managés.

    PS: Je suppose que MethodInfo.Invoke() et Delegate.DynamicInvoke() sont équivalents et donc, tout aussi pauvres en performance?

    PPS: Lire/écrire chaque champ d'un objet est beaucoup plus facile que faire des proxys de fonctions non-managées, car la signature est fixe et le code entièrement managé.
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  3. #3
    Membre averti

    Profil pro
    Étudiant
    Inscrit en
    Décembre 2004
    Messages
    499
    Détails du profil
    Informations personnelles :
    Âge : 37
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Décembre 2004
    Messages : 499
    Points : 422
    Points
    422
    Par défaut
    Ça y est première version de DynamicMethod_Invoker utilisant ILGenerator.Emit !

    Il permet d'appeler dynamiquement (donc en passant un tableau d'Object^ comme paramètres) la plupart des "fonctions" .NET

    • type de fonction:
      1. méthode normale, virtuelle, statique
      2. constructeur
      3. field getter/setter/address (il est préférable de pin_ptr l'objet contenant le champ avant d'en récupérer l'addresse)
      4. property getter/setter
      5. méthode/constructeur de value type (pour l'argument this : passage du value type par référence)

    • type des paramètres :
      1. reference type (class, delegate, array, interface, boxed type)
      2. value type (dont type primitif), pointeur natif
      3. tracking reference

        • variable locale, paramètre, gcroot (instruction ldloca)
        • élément de tableau (instruction ldelema)
        • champ de classe ou de value type (instruction ldflda / ldsflda)


    Pour prendre en charge les méthodes renvoyant une tracking reference, il faudrait un type de delegate ne renvoyant pas un Object^ mais un T% (ce qui va à l'encontre du côté dynamique puisqu'on doit stocker ce T% dans une variable typée statiquement T%). Une solution serait de renvoyer un Object^% ou un Int32%.
    On peut pinner n'importe quelle tracking référence (dont on ne sait pas si elle est à addresse statique, de tableau, ou de champ) et l'envoyer dynamiquement à une méthode.

    Pour les gcroot, il se trouve qu'après inspection du getter GCHandle.Target des versions 32/64bit de .NET (4.x), un GCHandle normal est directement un Object^*. Pour les GCHandle pinned ou weak c'est un Object^* avec un flag dans les 4 premiers bits qu'il faut enlever avant la conversion en Object^* qui peut être converti en Object^%, ou passé dynamiquement à une méthode avec mon type TrackingReference_Ldelema.

    À faire : permettre d'appeler dynamiquement une fonction privée (il suffit que la DynamicMethod soit déclarée comme méthode statique du type à ce que j'ai compris).
    Fichiers attachés Fichiers attachés

  4. #4
    Expert éminent sénior
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 073
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 073
    Points : 12 119
    Points
    12 119
    Par défaut
    GG.

    T'as des benchmark pour connaitre le gain ?

  5. #5
    Membre averti

    Profil pro
    Étudiant
    Inscrit en
    Décembre 2004
    Messages
    499
    Détails du profil
    Informations personnelles :
    Âge : 37
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Décembre 2004
    Messages : 499
    Points : 422
    Points
    422
    Par défaut
    Salut, le but c'est un gain en perf mais c'est surtout de pouvoir appeler dynamiquement une méthode qui a des paramètres par référence (si on oublie ça on peut se passer de IlGenerator et coder tout en génériques), ou encore de récupérer l'adresse (ou un interior_ptr) sur un champ d'un objet (pour les champs IlGenerator est indispensable, quoiqu'il y a aussi le hack de GetFieldOffset que j'ai expliqué dans un autre post).

    Pour les perfs ça a déjà été pas mal discuté (voir fasterflect par exemple)

    méthode simple avec 3 paramètres fabriqués avant les 10000000 appels en boucle, donc on prend en compte surtout la lenteur du protocole d'appel et des checks

    Direct : 00.5990343
    Delegate.Invoke : 00.6010344
    DynamicDelegate.Invoke : 00.7240414 <---
    Delegate.DynamicInvoke : 11.9426916
    MethodInfo.Invoke : 05.4323108


    ( les 3 derniers ont un prototype d'appel dynamique avec un array<Objet^> )

    Je trouve ça hallucinant que .NET ne propose pas un dynamicInvoker dans l'esprit de celui que j'ai fait, avec peut-être plein d'options pour intégrer des checks des paramètres plus ou moins coûteux et safe.
    Je reproche également à .NET de ne pas proposer de méthode
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Object^% GCHandle::getTrackingReference
    Et on ne peut pas allouer de tracking reference (interior_ptr) autre part que sur la pile, il y a une raison valable pour ça ?
    Il y a aussi le manque de propriété Type.IsReallyValueType pour distinguer les value-type avec ou sans Object^ (dans le second cas on peut les allouer dans le tas non managé).
    Ou le fait qu'on ne peut pas débugger avec visual le code IL d'une DynamicMethod. Même le code assembleur est invisible (sauf en faisant DebugBreak() en native mode debug mais bon..).
    Et les value-types boxed, pourquoi il n'y a pas de moyen d'avoir une propriété pour accéder au value-type caché à l'intérieur (vous avez déjà essayé de modifier la valeur à l'intérieur d'un Int32^ ?).

    Quand on fouille un peu on se rend compte que .NET n'est pas si sérieux que ça, et pourtant il manque si peu !

  6. #6
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 369
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 369
    Points : 41 519
    Points
    41 519
    Par défaut
    Pour les types valeur boxés, je pense qu'ils sont volontairement immuables (comme les classes wrappant les types primitifs en Java), pour garder leur sémantique de valeur.
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  7. #7
    Membre averti

    Profil pro
    Étudiant
    Inscrit en
    Décembre 2004
    Messages
    499
    Détails du profil
    Informations personnelles :
    Âge : 37
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Décembre 2004
    Messages : 499
    Points : 422
    Points
    422
    Par défaut
    je sais pas trop,
    sémantiquement les types boxed sont plus ou moins

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    generic<typename U> where U : value class
     ref struct Box {
        U u;
        Type GetuType() { return U::typeid; }
    };
    Si le "U u" doit être pensé comme const, pourquoi peut-on accéder à ses champs en écriture, et appeler des méthodes non const dessus ?

    pour Int32^ (qui n'a ni champ ni méthode non const) c'est clairement équivalent à
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    ref struct BoxInt32 {
        const Int32 u;
        Type GetuType() { return Int32::typeid; }
    };
    en IL il y a l'instruction Opcodes::Box qui prend un handle boxed et push l'adresse de la valeur : l'instruction IL correspond donc parfaitement à un objet qui contient une valeur accessible en écriture.

    À mon sens tout cela n'est donc pas très cohérent.

  8. #8
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 369
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 369
    Points : 41 519
    Points
    41 519
    Par défaut
    À ma connaissance, les types valeur boxés ne sont accessibles en tant que tels qu'en C++/CLI et en CIL.
    En C#, ils sont complètement immuables, car le seul accès possible est "lire la valeur pour la mettre dans le vrai type" (via unboxing cast, ou Convert.ToXxxx(object)).

    Je considère C++/CLI comme un cas un peu "à part" qui permet de faire certains trucs non-orthodoxes.
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  9. #9
    Membre averti

    Profil pro
    Étudiant
    Inscrit en
    Décembre 2004
    Messages
    499
    Détails du profil
    Informations personnelles :
    Âge : 37
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Décembre 2004
    Messages : 499
    Points : 422
    Points
    422
    Par défaut
    En fait je bosse sur une sorte de Pascal.NET interprété et compilé (je rajoute juste le .NET, et juste la consommation, on ne peut pas créer de types à part des types de delegates).
    Pour les values types j'ai décidé de faire comme ça :

    Si on déclare une variable de type .NET value type, alors c'est un handle vers le type boxed qu'on déclare, on ne peut pas mettre un value type directement dans la pile comme en C# ou C++/CLI.

    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
     
    /*supposons qu'on a un assembly avec un value type :*/
    value struct XY {
        Int32 x,y;
        void ChangeX(Int32 _x) { x = _x; }
    };
     
    /// ----------------
    /// code pascal.NET
    /// ----------------
    var k : int // c'est un int natif (du langage Pascal) et donc sur la pile 
    var u : Int32 // c'est un Int32^, donc juste un handle, à null pour le moment
    var xy : XY // c'est un XY^, à null pour le moment
     
    u = nil // c'est un handle donc on peut le mettre à null
    xy = nil // c'est un handle donc on peut le mettre à null
     
    u = 8 // construit un nouveau boxed Int32 (donc dans le managed heap)
    u = u + 2 // correspond à    u = int(u) + 2 avec la convertion implicite boxed Int32 vers int  
                  // donc ça construit un nouveau boxed Int32 de valeur 10
     u = int(u) + 2 // est valide aussi
                         // on a donc encore construit un nouveau boxed Int32 de valeur 12
     
    u = u + u // erreur sémantique au parsing : pas d'opérateur + sur les types .NET, uniquement sur les types Pascal
     
    u = nil
    k = u // erreur à l'exécution car u est null
     
    xy = gcnew XY() // construit un nouveau boxed value type
     
    xy.x = 9 // change juste la valeur du champ
    u = 19
    xy.x = u // on change à nouveau la valeur du champ donc sans réallouer d'objet XY
    xy.ChangeX(10) // appelle la méthode en passant xy (comme argument this) par référence
                           // donc ici xy.x vaut bien 10
     
    xy.x = nil // erreur sémantique : on ne peut pas mettre à null un value type qui n'est pas une variable locale

    • Les gros avantages :
      • À première vue c'est comme en Java : les value type n'existent pas, et les conversions implicites entre le type primitif et son équivalent boxed ( le type int Pascal <-> le type Int32 Pascal.NET qui correspond à Int32 boxed ) permettent de tout faire (je rappelle qu'aucun opérateur n'est défini sur Int32 et autres, uniquement sur les types primitifs Pascal).
      • Les value types que le développeur déclare comme variables locales, comme tous les objets .NET qu'il peut déclarer, sont garbage collectés.
    • Les gros désavantages :
      • le développeur doit accepter ou comprendre que déclarer des Int32 et faire des calculs dessus est coûteux en perf : il est préférable d'utiliser le type Pascal natif (int)
      • xy.x = nil donne une erreur sémantique au parsing, et k = Int32(nil) donne une erreur à l'exécution,
        donc le développeur doit apprendre ce que sont les value types pour comprendre ça. Mais il peut aussi facilement esquiver la question..
      • Un développeur C# ou C++/CLI doit comprendre ce concept de value types uniquement manipulés par handle, concept qui n'existe pas du tout en C#.



    En fait si j'avais donné aux value types une sémantique de pile comme en C#, le problème se serait posé quand le développeur aurait voulu déclarer un value type dans un objet Pascal : si le value type ne contient que des Int32 et autres c'est bon, mais si le value type contient des Object^ alors le garbage collector doit les connaître (ce qui oblige à passer par un gcroot et un value type boxed) ce qui rend donc le langage aussi compliqué que C++/CLI : il y a les types primitifs, les value types ne contenant que des types primitifs, les objets .NET, les objets Pascal, et les value types qui contiennent des handle ! Je pense que ma solution est plus simple à appréhender (tout objet .NET est garbage collecté) et permet grosso-modo de faire les mêmes choses c'est à dire instancier/référencer des objets .NET dans des objets natifs (Pascal) et appeler des méthodes dessus.

  10. #10
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 369
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 369
    Points : 41 519
    Points
    41 519
    Par défaut
    En fait ce que tu appelles Pascal.Net, c'est plus une couche entre un interpréteur Pascal classique et le Framework, plutôt qu'une version de Pascal qui compile directement en CIL et repose entièrement sur le Framework?
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  11. #11
    Membre averti

    Profil pro
    Étudiant
    Inscrit en
    Décembre 2004
    Messages
    499
    Détails du profil
    Informations personnelles :
    Âge : 37
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Décembre 2004
    Messages : 499
    Points : 422
    Points
    422
    Par défaut
    Citation Envoyé par Médinoc Voir le message
    En fait ce que tu appelles Pascal.Net, c'est plus une couche entre un interpréteur Pascal classique et le Framework, plutôt qu'une version de Pascal qui compile directement en CIL et repose entièrement sur le Framework?
    en interprété oui, en compilé c'est transformé en C++/CLI donc ensuite en CIL.

Discussions similaires

  1. Utilise p/invoke avec un lib static
    Par teddyalbina dans le forum Framework .NET
    Réponses: 0
    Dernier message: 21/06/2011, 00h13
  2. [Prototype] Avoir plusieurs "invoke"
    Par brunoperel dans le forum Bibliothèques & Frameworks
    Réponses: 1
    Dernier message: 30/07/2008, 10h36
  3. Method invoke avec List<generics>
    Par youx21 dans le forum Langage
    Réponses: 4
    Dernier message: 06/12/2007, 09h11
  4. Invoke avec arguments null
    Par Shiftane dans le forum API standards et tierces
    Réponses: 1
    Dernier message: 28/06/2006, 10h51
  5. pb avec invoke de java.lang.reflect.
    Par sebastien2222 dans le forum API standards et tierces
    Réponses: 2
    Dernier message: 28/04/2006, 13h33

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo