2011-05-04 56 views
4

大學的任務是使用Reflection.Emit實現簡單的代理生成器/攔截器機制。 我想出了以下程序。通過Reflection.Emit生成代理僅在調試時啓動

看來工作在Visual Studio中就好在調試模式[F5](調試 - >啓動調試),但是當沒有調試[Ctrl + F5]開始崩潰的大部分時間(調試 - >啓動不調試)。

這兩種模式有什麼區別? (我不要參考調試<>發佈模式)。 該問題發生在多臺機器/設置(Win XP SP3 32位和64位,Windows 7 32位)上。

Click for pastebin。

// 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(); 
    } 
} 

另一個問題:縮小這些問題的最佳方法是什麼? 我試圖將生成的程序集保存到磁盤並在Reflector中打開生成的dll,但它看起來是空的。

如上所述,當在調試模式下啓動程序似乎工作並打印以下輸出。

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

感謝您的時間。

+0

你有什麼例外?你的代碼在調試和發佈模式下都能正常工作。 – oxilumin 2011-05-07 20:28:34

+0

正如我所提到的,我沒有提到調試或發佈模式,而是「開始調試」和「無需調試即可開始」。 – 2011-05-07 21:01:47

+0

好的。我想我已經發現了這個問題。現在我真的不知道它爲什麼在調試模式下工作。 – oxilumin 2011-05-07 21:28:37

回答

6

嘗試在項目設置選項卡上明確設置x86模式。

只有在x64AnyCpu模式下運行程序時,我纔得到致命異常。

啊,我明白了。將Ldflda替換爲Ldfld。它工作正常,即使沒有調試器(我只是跑.exe)。 Ldflda適用於您以refout關鍵字作爲參數傳入方法的字段。

+0

您是否參考了「平臺目標」的設置?如果你這樣做:'x86'已被選中。 – 2011-05-07 21:08:35

+0

我剛剛證實,這將使問題在我所有可用的平臺上消失。 :) - 謝謝你,先生! – 2011-05-11 21:11:10

相關問題