Генерация прокси через Reflection.Emit работает только при запуске с отладкой - PullRequest
4 голосов
/ 05 мая 2011

Задача в университете состояла в том, чтобы реализовать простой механизм прокси-генератора / перехватчика, используя Reflection.Emit. Я придумал следующую программу.

Кажется, что он отлично работает внутри Visual Studio в режиме отладки [F5] (Отладка -> Начать отладку), но в большинстве случаев происходит сбой при запуске без отладки [Ctrl + F5] (Отладка -> Запуск без отладки).

В чем разница между этими двумя режимами? (Я не ссылаюсь на режим отладки <> Release). Эта проблема возникает на нескольких компьютерах / в установках (Win XP SP3 32-разрядная и 64-разрядная, Windows 7 32-разрядная).

Нажмите для вставки.

// The proxy generator; I assume that the error is buried along the lines emitting the IL code
public static class ProxyGenerator
{
    public static T Create<T>(object obj, IInterception interception)
    {
        Type type = obj.GetType();

        TypeBuilder proxy = DefineProxy(type);

        FieldBuilder wrappedField = DefinePrivateField(proxy, "wrappedObject", type);
        FieldBuilder interceptionField = DefinePrivateField(proxy, "interception", interception.GetType());

        DefineConstructor(proxy, wrappedField, interceptionField);
        DefineInterfaceMethods(type, proxy, wrappedField, interceptionField);

        return (T) Activator.CreateInstance(proxy.CreateType(), obj, interception);
    }

    private static TypeBuilder DefineProxy(Type type)
    {
        var assemblyName = new AssemblyName {Name = "GeneratedProxyAssembly"};
        AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
            assemblyName, AssemblyBuilderAccess.Run);

        ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("GeneratedProxyModule");

        return moduleBuilder.DefineType(
            type.Name + "Proxy",
            type.Attributes,
            typeof (object),
            type.GetInterfaces());
    }

    private static FieldBuilder DefinePrivateField(TypeBuilder typeBuilder, string fieldName, Type fieldType)
    {
        return typeBuilder.DefineField(fieldName, fieldType, FieldAttributes.Private);
    }

    private static void DefineConstructor(TypeBuilder typeBuilder, params FieldBuilder[] parameters)
    {
        ConstructorBuilder ctor = typeBuilder.DefineConstructor(
            MethodAttributes.Public, CallingConventions.Standard, parameters.Select(f => f.FieldType).ToArray());

        // Emit constructor
        ILGenerator g = ctor.GetILGenerator();

        // Load "this" pointer and call base constructor
        g.Emit(OpCodes.Ldarg_0);
        g.Emit(OpCodes.Call, typeof(object).GetConstructor(new Type[0]));

        // Store parameters in private fields
        for (int i = 0; i < parameters.Length; i++)
        {
            // Load "this" pointer and parameter and store paramater in private field
            g.Emit(OpCodes.Ldarg_0);
            g.Emit(OpCodes.Ldarg, i + 1);
            g.Emit(OpCodes.Stfld, parameters[i]);
        }

        // Return
        g.Emit(OpCodes.Ret);
    }

    private static void DefineInterfaceMethods(Type type, TypeBuilder proxy, FieldInfo wrappedField, FieldInfo interceptionField)
    {
        // Loop through all interface methods
        foreach (MethodInfo interfaceMethod in type.GetInterfaces().SelectMany(i => i.GetMethods()))
        {
            MethodInfo method = type.GetMethod(interfaceMethod.Name);

            MethodBuilder methodBuilder = proxy.DefineMethod(
                method.Name,
                method.Attributes,
                method.ReturnType,
                method.GetParameters().Select(p => p.ParameterType).ToArray());

            // Emit method
            ILGenerator g = methodBuilder.GetILGenerator();

            // Intercept before
            EmitMethodCallOnMember(g, interceptionField, "Before", false);

            // Delegate method call
            EmitMethodCallOnMember(g, wrappedField, method.Name, true);

            // Intercept after
            EmitMethodCallOnMember(g, interceptionField, "After", false);

            // Return
            g.Emit(OpCodes.Ret);
        }
    }

    private static void EmitMethodCallOnMember(ILGenerator g, FieldInfo field, string methodName, bool delegateParameters)
    {
        // Load "this" pointer to get address of field
        g.Emit(OpCodes.Ldarg_0);
        g.Emit(OpCodes.Ldflda, field);

        MethodInfo method = field.FieldType.GetMethod(methodName);
        if (delegateParameters)
        {
            // Load method parameters
            for (int i = 0; i < method.GetParameters().Length; i++)
            {
                g.Emit(OpCodes.Ldarg, i + 1);
            }
        }

        // Emit call
        g.Emit(OpCodes.Call, method);
    }
}

// Some infrastructure
public interface IInterception
{
    void Before();
    void After();
}

public class LogInterception : IInterception
{
    public void Before()
    {
        Console.WriteLine("Before ... ");
    }

    public void After()
    {
        Console.WriteLine("... After");
    }
}

public interface ITest
{
    string DoSomething(string s1, string s2);
}

public class Test : ITest
{
    public string DoSomething(string s1, string s2)
    {
        Console.WriteLine("... doing something ...");
        return s1 + s2;
    }
}

 // The test program, expected output is down below

internal class Program
{
    internal static void Main(string[] args)
    {
        var test = new Test();
        var proxy = ProxyGenerator.Create<ITest>(test, new LogInterception());

        Console.WriteLine(test.DoSomething("Hello", " World"));
        Console.WriteLine("----------------------------------------");
        Console.WriteLine(proxy.DoSomething("Hello", " World"));

        Console.ReadKey();
    }
}

Еще один вопрос: как лучше всего сузить круг вопросов? Я попытался сохранить сгенерированную сборку на диск и открыть полученную DLL в Reflector, но она оказалась пустой.

Как упоминалось выше, при запуске в режиме отладки программа, кажется, работает и выводит следующий вывод.

... doing something ...
Hello World
----------------------------------------
Before ...
... doing something ...
... After
Hello World

Спасибо за ваше время.

1 Ответ

6 голосов
/ 08 мая 2011

Попробуйте явно установить режим x86 на вкладке настроек проекта.

Я получил фатальное исключение только при запуске программы в режиме x64 или AnyCpu.

Ах, я понял. Заменить Ldflda на Ldfld. Он отлично работает даже без отладчика (я только что запустил .exe). Ldflda - для полей, которые вы передаете в метод в качестве параметров с ключевым словом ref или out.

...