J'ai une fonction récursive emit : Map<string,LocalBuilder> -> exp -> unit
donde il : ILGenerator
est globale à la fonction et exp
est une union discriminante représentant un langage analysé vérifié par type avec casse InstanceCall of exp * MethodInfo * exp list * Type
y Type
est une propriété sur exp
représentant le type de l'expression.
Dans le fragment suivant, j'essaie d'émettre des opcodes IL pour un appel d'instance où instance.Type
peut ou non être un ValueType
. Je comprends donc que je peux utiliser OpCodes.Constrained
pour effectuer de manière souple et efficace des appels virtuels sur les types référence, valeur et enum. Je suis novice dans le domaine de Reflection.Emit et des langages machine en général, c'est pourquoi la compréhension de la documentation liée de OpCodes.Constrained
n'est pas forte pour moi.
Voici ma tentative, mais le résultat est une VerificationException
, "L'opération pourrait déstabiliser le temps d'exécution." :
let rec emit lenv ast =
match ast with
...
| InstanceCall(instance,methodInfo,args,_) ->
instance::args |> List.iter (emit lenv)
il.Emit(OpCodes.Constrained, instance.Type)
il.Emit(OpCodes.Callvirt, methodInfo)
...
En regardant la documentation, je pense que la clé est peut-être "Un pointeur géré, ptr, est poussé sur la pile. Le type de ptr doit être un pointeur géré (&) vers thisType. Notez que ceci est différent du cas d'une instruction callvirt non fixée, qui attend une référence de thisType."
Mise à jour
Merci @Tomas et @desco, je comprends maintenant quand il faut utiliser OpCodes.Constrained
( instance.Type
est un ValueType, mais methodInfo.DeclaringType
est un type de référence).
Mais il s'avère que je n'ai pas besoin de considérer ce cas pour l'instant, et mon vrai problème était l'argument d'instance sur la pile : il ne m'a fallu que 6 heures pour apprendre qu'il a besoin d'une adresse au lieu de la valeur (regarder le code source de DLR m'a donné des indices, et ensuite utiliser ilasm.exe sur un simple programme C# a rendu les choses claires).
Voici ma version finale de travail :
let rec emit lenv ast =
match ast with
| Int32(x,_) ->
il.Emit(OpCodes.Ldc_I4, x)
...
| InstanceCall(instance,methodInfo,args,_) ->
emit lenv instance
//if value type, pop, put in field, then load the field address
if instance.Type.IsValueType then
let loc = il.DeclareLocal(instance.Type)
il.Emit(OpCodes.Stloc, loc)
il.Emit(OpCodes.Ldloca, loc)
for arg in args do emit lenv arg
if instance.Type.IsValueType then
il.Emit(OpCodes.Call, methodInfo)
else
il.Emit(OpCodes.Callvirt, methodInfo)
...