C#と諸々

C#がメインで他もまぁ諸々なブログです
おかしなこと書いてたら指摘してくれると嬉しいです(´・∀・`)
つーかコメント欲しい(´・ω・`)

--/--/-- --:--
上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。
タグ:
トラックバック(-) | コメント(-) | このエントリーを含むはてなブックマーク
2010/01/26 02:18

ByRef parameter and C# - 猫とC#について書くmatarilloの日記

MethodInfo.Invoke で動的に実行したメソッド内で例外が発生した場合 out 引数が書き換えられない、という話。
Reflection.Emit でゴリゴリ自前実装してみたものをコメントに投稿しましたが、インスタンスメソッドの動的実行に対応できてなかったので、対応したコードをこちらで晒しときます。あと、NyaRuRu さんからの指摘事項含め、リファクタリングもしときました。

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using Invoker = System.Func<object, object[], object>;

public static class MethodInfoExtension
{
    public static object InvokeStrictly(this MethodInfo source, object obj, params object[] parameters)
    {
        if (source == null)
        {
            throw new NullReferenceException();
        }
        int parametersLength = (parameters != null) ? parameters.Length : 0;
        if (source.GetParameters().Length != parametersLength)
        {
            throw new ArgumentException("引数 parameters の要素数とメソッドのパラメータ数が一致しません。");
        }

        var invokerBuilder = new DynamicMethod("DynamicMethod", typeof(object), new[] { typeof(object), typeof(object[]) }, true);
        GenerateCode(source, invokerBuilder);
        var invoker = (Invoker)invokerBuilder.CreateDelegate(typeof(Invoker));

        return invoker(obj, parameters);
    }

    private static void GenerateCode(MethodInfo source, DynamicMethod invokerBuilder)
    {
        ILGenerator ilGenerator = invokerBuilder.GetILGenerator();

        List<LocalBuilder> paramLocalBuilderList = GenerateCodeForLocalVariableDeclarationsForParameters(source, ilGenerator);
        LocalBuilder resultLocal = GenerateCodeForLocalVariableDeclarationForResult(ilGenerator);
        GenerateCodeForSubstitutingsParameterToLocalVariable(paramLocalBuilderList, ilGenerator);
       
        /* Begin try block. */
        Label exBlockLabel = ilGenerator.BeginExceptionBlock();
        GenerateCodeForCallingSourceMethod(source, paramLocalBuilderList, resultLocal, ilGenerator);
        ilGenerator.Emit(OpCodes.Leave, exBlockLabel);
        /* End try block. */

        /* Begin finally block. */
        ilGenerator.BeginFinallyBlock();
        GenerateCodeForRestorationParameters(paramLocalBuilderList, ilGenerator);
        ilGenerator.EndExceptionBlock();
        /* End finally block. */

        ilGenerator.Emit(OpCodes.Ldloc_S, resultLocal);
        ilGenerator.Emit(OpCodes.Ret);
    }

    private static List<LocalBuilder> GenerateCodeForLocalVariableDeclarationsForParameters(MethodInfo source, ILGenerator ilGenerator)
    {
        List<LocalBuilder> localBuilderList = new List<LocalBuilder>();
        ParameterInfo[] paramInfos = source.GetParameters();
        for (int i = 0; i < paramInfos.Length; i++)
        {
            var paramInfo = paramInfos[i];
            var paramType = paramInfo.ParameterType;
            var localType = paramType.IsByRef ? paramType.GetElementType() : paramType;

            localBuilderList.Add(ilGenerator.DeclareLocal(localType));
        }
        return localBuilderList;
    }

    private static LocalBuilder GenerateCodeForLocalVariableDeclarationForResult(ILGenerator ilGenerator)
    {
        return ilGenerator.DeclareLocal(typeof(object), false);
    }

    private static void GenerateCodeForSubstitutingsParameterToLocalVariable(List<LocalBuilder> paramLocalBuilderList, ILGenerator ilGenerator)
    {
        for (int i = 0; i < paramLocalBuilderList.Count; i++)
        {
            var paramLocalBuilder = paramLocalBuilderList[i];

            ilGenerator.Emit(OpCodes.Ldarg_1);
            ilGenerator.Emit(OpCodes.Ldc_I4, i);
            ilGenerator.Emit(OpCodes.Ldelem_Ref);

            Label passLabel = ilGenerator.DefineLabel();
            ilGenerator.Emit(OpCodes.Brfalse, passLabel);

            ilGenerator.Emit(OpCodes.Ldarg_1);
            ilGenerator.Emit(OpCodes.Ldc_I4, i);
            ilGenerator.Emit(OpCodes.Ldelem_Ref);
            ilGenerator.Emit(OpCodes.Unbox_Any, paramLocalBuilder.LocalType);
            ilGenerator.Emit(OpCodes.Stloc_S, paramLocalBuilder);

            ilGenerator.MarkLabel(passLabel);
        }
    }

    private static void GenerateCodeForCallingSourceMethod(MethodInfo source, List<LocalBuilder> paramLocalBuilderList, LocalBuilder resultLocal, ILGenerator ilGenerator)
    {
        if (!source.IsStatic)
        {
            GenerateCodeForPushingContextObject(source, ilGenerator);
        }
        GenerateCodeForPushingParameters(source, paramLocalBuilderList, ilGenerator);

        ilGenerator.Emit(OpCodes.Call, source);
        if (source.ReturnType == typeof(void))
        {
            ilGenerator.Emit(OpCodes.Ldnull);
        }
        ilGenerator.Emit(OpCodes.Stloc_S, resultLocal);
    }

    private static void GenerateCodeForPushingContextObject(MethodInfo source, ILGenerator ilGenerator)
    {
        ilGenerator.Emit(OpCodes.Ldarg_0);
        if (source.DeclaringType.IsValueType)
        {
            ilGenerator.Emit(OpCodes.Unbox, source.DeclaringType);
        }
        else
        {
            ilGenerator.Emit(OpCodes.Castclass, source.DeclaringType);
        }
    }

    private static void GenerateCodeForPushingParameters(MethodInfo source, List<LocalBuilder> paramLocalBuilderList, ILGenerator ilGenerator)
    {
        ParameterInfo[] paramInfos = source.GetParameters();
        for (int i = 0; i < paramInfos.Length; i++)
        {
            var paramInfo = paramInfos[i];
            var paramLocalBuilder = paramLocalBuilderList[i];
            bool paramIsByRef = paramInfo.ParameterType.IsByRef;

            if (paramIsByRef)
            {
                ilGenerator.Emit(OpCodes.Ldloca_S, paramLocalBuilder);
            }
            else
            {
                ilGenerator.Emit(OpCodes.Ldloc_S, paramLocalBuilder);
            }
        }
    }

    private static void GenerateCodeForRestorationParameters(List<LocalBuilder> paramLocalBuilderList, ILGenerator ilGenerator)
    {
        for (int i = 0; i < paramLocalBuilderList.Count; i++)
        {
            var paramLocalBuilder = paramLocalBuilderList[i];
            var localType = paramLocalBuilder.LocalType;

            ilGenerator.Emit(OpCodes.Ldarg_1);
            ilGenerator.Emit(OpCodes.Ldc_I4, i);
            ilGenerator.Emit(OpCodes.Ldloc_S, paramLocalBuilder);
            if (localType.IsValueType)
            {
                ilGenerator.Emit(OpCodes.Box, localType);
            }
            ilGenerator.Emit(OpCodes.Stelem, typeof(object));
        }
    }
}



using System;
using System.Reflection;

class P
{
    public static void Method(out string arg)
    {
        arg = "out";
        throw new Exception();
    }

    static void Main()
    {
        object[] args = new object[1];
        try
        {
            MethodInfo targetMethod = typeof(P).GetMethod("Method");
            targetMethod.InvokeStrictly(null, args);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
            Console.WriteLine();
        }
        Console.WriteLine(args[0]);
        Console.ReadLine();
    }
}



こーゆー IL を動的に構築するコードって、もしかしてメソッド分割しない方が見やすい?
スポンサーサイト
タグ: .NET C# Reflection IL
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。