関数に対してグローバルであり、大文字と小文字を区別して型チェックされた解析済み言語を表す判別ユニオンであり、式の型を表すプロパティである再帰関数emit : Map<string,LocalBuilder> -> exp -> unit
があります。il : ILGenerator
exp
InstanceCall of exp * MethodInfo * exp list * Type
Type
exp
次のフラグメントでは、インスタンス呼び出しに対してILオペコードを発行しようとしています。インスタンス呼び出しはである場合とそうでinstance.Type
ない場合がありますValueType
。OpCodes.Constrained
したがって、参照、値、および列挙型に対して柔軟かつ効率的に仮想呼び出しを行うために使用できることを理解しています。私はReflection.Emitと機械語全般に慣れていないので、のリンクされたドキュメントを理解することはOpCodes.Constrained
私にとって強くありません。
これが私の試みですがVerificationException
、「操作によってランタイムが不安定になる可能性があります。」という結果になります。
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)
...
ドキュメントを見ると、キーは「マネージポインタptrがスタックにプッシュされます。ptrのタイプはthisTypeへのマネージポインタ(&)である必要があります。これはプレフィックスなしの場合とは異なることに注意してください。 thisTypeの参照を期待するcallvirt命令。」
アップデート
@Tomasと@descoに感謝します。これで、いつ使用するかがわかりましたOpCodes.Constrained
(instance.Type
はValueTypeですmethodInfo.DeclaringType
が、参照型です)。
しかし、まだそのケースを考慮する必要はないことがわかりました。私の本当の問題は、スタック上のインスタンス引数でした。値ではなくアドレスが必要であることを知るのに6時間しかかかりませんでした(DLRソースを見てください)。コードから手がかりが得られ、単純なC#プログラムでilasm.exeを使用すると明らかになりました)。
これが私の最終的な作業バージョンです:
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)
...