2011-05-04 1 views
4

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

В Visual Studio в режиме отладки это нормально работает [F5] (Отладка -> Начать отладку), но при старте без отладки происходит сбой [Ctrl + F5] (Debug -> Start Without Debugging).

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

Click для пастабин.

// 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 

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

+0

У вас есть исключение? Ваш код отлично работает со мной в обоих режимах отладки и выпуска. – oxilumin

+0

Как я уже говорил, я не отношусь к режимам отладки или выпуска, а скорее к «Начать отладку» и «Начать без отладки». –

+0

Хорошо. Кажется, я нашел проблему. Теперь я действительно не знаю, почему он работал в режиме отладки. – oxilumin

ответ

6

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

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

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

+0

Вы ссылаетесь на настройку «Цель платформы»? Если вы это сделаете: 'x86' уже выбран. –

+0

Я только что подтвердил, что это заставит проблему уйти на всех моих доступных платформах. :) - Спасибо, сэр! –