Question

Je dois passer une fonction une instance d'un objet, donc évidemment toutes les informations à prendre comme argument doivent être chargées sur la pile d'évaluation ici est le code que je recherche

someClass SomeObject = new someClass();

il.Emit(OpCodes.LoadObject, SomeObject);
il.Emit(OpCodes.CallVirt, MethodInfo Function);


public void Function(Object obj)
{
       Type type = typeof(obj);
       //do something w.r.t to the type
}

Je n'ai besoin d'aucune information stockée dans la classe, juste du type et je ne peux utiliser aucun des types primitifs pour prendre ma décision.

La dernière fois, j'ai lu que je pouvais utiliser un pointeur pour charger le type en utilisant certains opcodes...mais je suis complètement perdu ici, toute aide ou pointeur vers la bonne direction serait génial :)

[MISE À JOUR]

Eh bien, j'ai trouvé une réponse à ma propre question, je l'ai essayée et cela fonctionne, je ne sais pas si c'est la bonne façon ou non, mais je peux créer et charger avec succès un objet dans la pile et le passer à une fonction

ConstructorInfo ci = typeof(SomeClass).GetConstructor(System.Type.EmptyTypes);
IL.Emit(OpCodes.Newobj, ci);
IL.Emit(OpCodes.Call, SomeFunctionMethodInfo);

SomeFunctionMethodInfo est une fonction qui prend Object comme argument, j'ai réussi à passer l'objet dans la fonction et je peux également le manipuler et renvoyer la classe en tant qu'objet.

Nulle part je n'ai pu trouver la référence à cet exemple, je viens de le comprendre via MSDN, est-ce que je fais quelque chose de mal ou y a-t-il un inconvénient ?Experts, s'il vous plaît, si vous pouviez le corriger ou fournir une meilleure réponse

Était-ce utile?

La solution

Vous ne pouvez pas extraire une référence de nulle part IL, sauf si vous codez la référence comme un IntPtr littéral, auquel cas :
un.ne le fais pas
b.tu aurais besoin de épingle, et
c.ne le fais pas.

La meilleure approche dépend de la signature de la méthode que vous écrivez.S'il est statique et ne prend aucun argument...eh bien, c'est un peu délicat.Personnellement, je serais enclin à passer un objet dans la méthode générée et demandez au délégué de récupérer toutes les données externes dont il a besoin.Mais une autre approche consiste plutôt à générer un classe, et écrivez la méthode en tant que méthode d'instance qui accède aux champs sur les types.

La différence (d'où ma préférence) est que le premier nécessite (au maximum) un object[] paramètre sur la méthode - et vous pouvez utiliser DynamicMethod;la seconde nécessite MethodBuilder, TypeBuilder, ModuleBuilder, AssemblyBuilder, etc., et représentent donc plus de travail.

La raison pour laquelle je mentionne object[] est-ce en général vous souhaitez une signature commune sur les méthodes générées, même si elles nécessitent des entrées différentes.Cela vous permet de vous lier à un type de délégué fixe et d'utiliser le plus rapide Invoke exécution (DynamicInvoke est lent).

Par exemple:

class SomeType { }
delegate void SomeDelegateType(params object[] args);
public class Program
{
    public static void Main()
    {
        var dn = new DynamicMethod("foo", (Type)null, new[] {typeof(object[])});
        var il = dn.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldc_I4_0);
        il.Emit(OpCodes.Ldelem_Ref);
        il.EmitCall(OpCodes.Call, typeof(Program).GetMethod("Function"), null);
        il.Emit(OpCodes.Ret);
        var action = (SomeDelegateType)dn.CreateDelegate(typeof(SomeDelegateType));

        var obj = new SomeType();
        action(obj);
    }
    public static void Function(object obj)
    {
        Type type = obj.GetType();
        Console.WriteLine(type);
    }
}

Si vous ne pouvez pas avoir d'argument d'entrée, vous devrez alors utiliser des champs sur un type que vous créez, ce qui est en fait exactement ce que fait le compilateur si vous écrivez (par exemple)

object someObj = ...
Action action = () => Function(someObj);

Ceci est créé comme :

class <>somehorriblename {
    public object someObj;
    public void SomeGeneratedName() { Function(someObj); }
}
...
var captureClass = new <>somehorriblename();
captureClass.someObj = ...
Action action = captureClass.SomeGeneratedName;

Autres conseils

Une méthode simple que j'ai utilisée consistait à obtenir le GCHandle, puis obtenir son IntPtr (via la méthode statique GCHandle.ToIntPtr) puis en le convertissant en un long ou integer (en utilisant soit VersPointeur ou ÀInt64).

De cette façon, j'ai pu appeler ILGenerator.Emit(OpCodes.Ldc_I8, ptr).

Une autre possibilité qui n'a pas été mentionnée (par aucune des excellentes réponses publiées jusqu'à présent) consiste à stocker la référence de l'objet d'exécution quelque part dans l'un des le tien instances, et émettre votre personnalisé IL code pour y accéder là où vous savez que vous l'aurez placé.

C'est plus simple si l'instance d'objet (étranger) en question se révèle être un singleton par AppDomain, car vous pouvez établir un champ statique bien connu (de vous) dans l'un de vos propres singletons à partir duquel votre IL sera bien sûr assuré de le trouver.

Si vous avez plutôt besoin de gérer un nombre inconnu d'instances arbitraires du type étranger au moment de l'exécution, ou si vous ne pouvez pas exclure leur retard arbitraire (l'une ou l'autre situation semble impliquer des dispositions pour les garder toutes droites), vous pouvez toujours les publier globalement, dans ce cas dans un tableau de Object[] (ou autre type), et d'une manière préétablie et comprise par le IL code.

Comme indiqué, il devrait probablement y avoir un moyen de coordonner l'activité de publication (mise en œuvre par un « module de gestion » pertinent dans votre système) avec la consommation ultérieure (par le module de gestion personnalisé). IL, également le vôtre, mais probablement soumis aux contraintes de signature de méthode), de sorte que le IL sera capable de distinguer et de sélectionner l'instance appropriée dans le tableau publié uniquement sur la base des arguments - ou de toute autre preuve - qu'il fait en fait avoir accès, ou en fait fait recevoir dans sa liste d'arguments (vraisemblablement contrainte).

En fonction de la situation, vous devrez peut-être choisir la façon dont la liste singleton des instances publiées est conçue.Dans tous les cas, on s'attend à ce que le IL Le consommateur ne modifie jamais les entrées publiées, mais un facteur à considérer est de savoir si vous avez besoin de plusieurs éditeurs ou non.Les options incluent :

  • Pré-remplir un readonly tableau avec toutes les instances étrangères attendues une seule fois (c'est-à-dire pendant l'initialisation), en veillant à ce que cela soit avant la possibilité d'accès par l'un de vos utilisateurs personnalisés. IL.C’est évidemment le plan le plus simple.
  • Utilisez un tableau à croissance monotone (les instances étrangères sont uniquement ajoutées, jamais supprimées pendant le AppDomain durée de vie).Cela simplifie la coordination avec le IL car les indices du tableau, une fois émis, n'expireront ni ne changeront jamais.Cela permet également IL être produit comme « à définir et à oublier », ce qui signifie qu'à la création, chaque DynamicMethod L'instance peut voir son index de tableau pertinent directement gravé.La publication à perpétuité apporte des avantages aux deux parties :le DynamicMethod au moment de l'exécution, il ne peut en principe pas s'agir de son brûlé IL, en privilégiant ce mode de personnalisation autant que possible, tandis que la monotonie de la liste fait que l'éditeur/gestionnaire n'a pas besoin de conserver ni les éléments créés. DynamicMethod identités, ni rien qui soit associé aux instances d'objets étrangers qu'il publie.
  • La simplicité de la stratégie « définir et oublier » est particulièrement efficace dans les scénarios où chaque DynamicMethod est émis une instance privée ou distincte qui lui est propre.
  • Si la sécurité des threads est nécessaire pour l'un des scénarios dynamiques répertoriés ici (c'est-à-dire en raison de la présence de plusieurs gestionnaires ou sources de publication), elle est réalisée de manière triviale via le concurrence sans verrouillage technique de chaque éditeur utilisant toujours Interlocked.Exchange (avec protection SpinWait) pour échanger une nouvelle version, mais strictement étendue, du tableau publié précédemment.
  • Si les instances étrangères sont réellement et nécessairement nombreuses et éphémères, vous pourriez alors être contraint d'adopter une approche plus complexe, dans laquelle votre liste publiée s'ajuste de manière dynamique.Dans ce cas, les instances du corps étranger n'existent dans votre liste que de manière transitoire et en coordination avec le IL le code devra peut-être utiliser des méthodes de signalisation ou de communication plus sophistiquées.
  • Notez que, même si cela n'est pas fondamentalement nécessaire, vous pouvez toujours choisir un schéma plus sophistiqué de gestion « à la volée » si les instances étrangères sont coûteuses en ressources et si la conservation des instances expirées dans votre baie est la seule référence GC qui est empêcher que ces ressources soient libérées et récupérées.

Voici une mise en œuvre complète de la solution décrite par d'autres sur cette page ici et ici.Ce code vous permet d'importer ou de "coder en dur" toute référence d'objet actif que vous êtes en mesure de fournir dans le IL flux d'un DynamicMethod en tant que littéral 32 ou 64 bits gravé en permanence.

Veuillez noter qu’il ne s’agit évidemment pas d’une technique recommandée et qu’elle est présentée ici uniquement à des fins éducatives et/ou expérimentales.

Comme indiqué dans un des commentaires, tu n'as pas besoin de épingle le GCHandle;c'est parfaitement bien pour GC de déplacer l'objet normalement puisque le valeur numérique du handle ne changera pas tant que l’instance reste active.La vraie raison pour laquelle vous avez besoin du GCHandle voici que le terminé DynamicMethod l'exemple sera pas détenir une référence à (ni en fait avoir aucune connaissance) de la poignée intégrée en elle-même.Sans le GCHandle, l'instance pourrait être collecté lorsque/si toutes les autres références à celui-ci sortent du champ d'application.

Ce code ci-dessous adopte une approche trop prudente en abandonnant intentionnellement le GCHandle struct (c'est-à-dire par pas en le libérant) après l'avoir utilisé pour extraire la référence de l'objet.Cela signifie que l'instance cible ne sera jamais collectée.Si vous disposez de connaissances particulières sur votre application spécifique qui vous permettent de le faire, n'hésitez pas à utiliser d'autres moyens pour garantir que la cible survivra à la durée de vie et/ou à toutes les applications particulières. DynamicMethod il est émis via cette technique ;dans ce cas vous pouvez libérer le GCHandle (code affiché commenté) après l'avoir utilisé pour obtenir la valeur du handle.

/// <summary>
/// Burn an reference to the specified runtime object instance into the DynamicMethod
/// </summary>
public static void Emit_LdInst<TInst>(this ILGenerator il, TInst inst)
    where TInst : class
{
    var gch = GCHandle.Alloc(inst);

    var ptr = GCHandle.ToIntPtr(gch);

    if (IntPtr.Size == 4)
        il.Emit(OpCodes.Ldc_I4, ptr.ToInt32());
    else
        il.Emit(OpCodes.Ldc_I8, ptr.ToInt64());

    il.Emit(OpCodes.Ldobj, typeof(TInst));

    /// Do this only if you can otherwise ensure that 'inst' outlives the DynamicMethod
    // gch.Free();
}

Une contribution de cette réponse non mentionnée par d'autres est que vous devez utiliser le Opcodes.Ldobj instruction pour contraindre le temps d'exécution approprié Type sur le littéral nouvellement codé en dur, comme indiqué ci-dessus.Il est facile de vérifier qu'il s'agit d'une bonne pratique avec la séquence de tests suivante.Il produit un bool indiquant si le System.Type de l'instance fraîchement importée est ce à quoi nous nous attendons, et elle ne renvoie que true quand le Opcodes.Ldobj Cette instruction est présente dans la méthode d’extension indiquée ci-dessus.

TInst _inst = new MyObject();

// ...
il.Emit_LdInst(_inst);                  //  <-- the function shown above
il.Emit(OpCodes.Isinst, typeof(TInst));
il.Emit(OpCodes.Ldnull);
il.Emit(OpCodes.Ceq);
il.Emit(OpCodes.Ldc_I4_1);
il.Emit(OpCodes.Xor);

Quoi n'a pas semble être nécessaire après notre grossièreté Ldc_I4 / Ldc_I8 pousser est Conv_I, et cela semble être vrai même si l'on laisse tomber le IntPtr.Size vérifier et utiliser simplement Ldc_I8 de toujours charger un long, même sur x86.C'est encore grâce à Opcodes.Ldobj adoucir de tels méfaits.

Voici un autre exemple d'utilisation.Celui-ci vérifie l'égalité de référence entre l'instance intégrée (importée au moment de la création de DynamicMethod) et les objets de type référence qui pourraient être fournis de diverses manières lors de l'appel de cette méthode à tout moment dans le futur.ça revient seulement true quand son ancêtre perdu depuis longtemps apparaît.(On se demande comment pourraient se passer les retrouvailles...)

il.Emit_LdInst(cmp);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ceq);

Enfin, une remarque sur where TInst : class contrainte sur la méthode d’extension indiquée en haut.D'une part, il n'y a aucune raison d'importer un type valeur de cette manière, puisque vous pouvez simplement importer ses champs sous forme de littéraux.Vous avez peut-être remarqué cependant que le point crucial Opcodes.Ldobj ce qui rend l'importation plus fiable est documenté comme étant destiné aux types valeur, et non aux types référence comme nous le faisons ici.La clé simple est de se rappeler qu'en réalité, une référence d'objet est un handle qui n'est lui-même qu'un modèle de 32 ou 64 bits qui est toujours copié par valeur.En d'autres termes, fondamentalement un ValueType.

J'ai testé tout cela assez largement sur les deux x86 et x64, débogage et publication, et cela fonctionne très bien sans aucun problème pour l'instant.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top