2015-04-08 13 views
6

Załóżmy, że mamy interfejs:Jak napisać klasy C# z Reflection.Emit dynamicznie według IL

public interface ICalculator 
{ 
    decimal Calculate(decimal x, decimal y); 
} 

logika oblicz jest realizowany w javascript (w rzeczywistości jest maszynopis) kod, chcemy dynamicznie tworzyć realizację działań następczych, używając Reflection.Emit, więc możemy udostępnić testy jednostkowe z wykonania C#:

public class Calculator : ICalculator 
{ 

    private ScriptEngine ScriptEngine; 

    public Calculator(ScriptEngine scriptEngine, string jsFileFullPath) 
    { 
     this.ScriptEngine = scriptEngine; 
     var jsFileContent = File.ReadAllText(jsFileFullPath); 
     this.ScriptEngine.Execute(jsFileContent); 
    } 

    public decimal Calculate(decimal x, decimal y) 
    { 

     string script = @" 
       var rf1013 = new TotalTaxation.TaxformCalculation.RF1013({0},{1}); 
       rf1013.Calculate(); 
       var result = rf1013.RF1013Sum; 
      "; 

     this.ScriptEngine.Evaluate(string.Format(script, x, y)); 

     var result = this.ScriptEngine.Evaluate("result"); 
     return Convert.ToDecimal(result); 

    } 
} 

możemy uzyskać od IL IL DASM:

.class public auto ansi beforefieldinit Calculator 
     extends [mscorlib]System.Object 
     implements ICalculator 
{ 
} // end of class Calculator 


.field private class [ClearScript]Microsoft.ClearScript.ScriptEngine ScriptEngine 

.method public hidebysig specialname rtspecialname 
     instance void .ctor(class [ClearScript]Microsoft.ClearScript.ScriptEngine scriptEngine, 
          string jsFileFullPath) cil managed 
{ 
    // Code size  37 (0x25) 
    .maxstack 2 
    .locals init ([0] string jsFileContent) 
    IL_0000: ldarg.0 
    IL_0001: call  instance void [mscorlib]System.Object::.ctor() 
    IL_0006: nop 
    IL_0007: nop 
    IL_0008: ldarg.0 
    IL_0009: ldarg.1 
    IL_000a: stfld  class [ClearScript]Microsoft.ClearScript.ScriptEngine Calculator::ScriptEngine 
    IL_000f: ldarg.2 
    IL_0010: call  string [mscorlib]System.IO.File::ReadAllText(string) 
    IL_0015: stloc.0 
    IL_0016: ldarg.0 
    IL_0017: ldfld  class [ClearScript]Microsoft.ClearScript.ScriptEngine Calculator::ScriptEngine 
    IL_001c: ldloc.0 
    IL_001d: callvirt instance void [ClearScript]Microsoft.ClearScript.ScriptEngine::Execute(string) 
    IL_0022: nop 
    IL_0023: nop 
    IL_0024: ret 
} // end of method JsRF1013Wrapper::.ctor 


.method public hidebysig newslot virtual final 
     instance valuetype [mscorlib]System.Decimal 
     Calculate(valuetype [mscorlib]System.Decimal x, 
        valuetype [mscorlib]System.Decimal y) cil managed 
{ 
    // Code size  65 (0x41) 
    .maxstack 4 
    .locals init ([0] string script, 
      [1] object result, 
      [2] valuetype [mscorlib]System.Decimal CS$1$0000) 
    IL_0000: nop 
    IL_0001: ldstr  "\r\n     var rf1013 = new TotalTaxati" 
    + "on.TaxformCalculation.RF1013({0},{1});\r\n     rf1013.Calc" 
    + "ulate();\r\n     var result = rf1013.RF1013Sum;\r\n   " 
    + "  " 
    IL_0006: stloc.0 
    IL_0007: ldarg.0 
    IL_0008: ldfld  class [ClearScript]Microsoft.ClearScript.ScriptEngine Calculator::ScriptEngine 
    IL_000d: ldloc.0 
    IL_000e: ldarg.1 
    IL_000f: box  [mscorlib]System.Decimal 
    IL_0014: ldarg.2 
    IL_0015: box  [mscorlib]System.Decimal 
    IL_001a: call  string [mscorlib]System.String::Format(string, 
                   object, 
                   object) 
    IL_001f: callvirt instance object [ClearScript]Microsoft.ClearScript.ScriptEngine::Evaluate(string) 
    IL_0024: pop 
    IL_0025: ldarg.0 
    IL_0026: ldfld  class [ClearScript]Microsoft.ClearScript.ScriptEngine Calculator::ScriptEngine 
    IL_002b: ldstr  "result" 
    IL_0030: callvirt instance object [ClearScript]Microsoft.ClearScript.ScriptEngine::Evaluate(string) 
    IL_0035: stloc.1 
    IL_0036: ldloc.1 
    IL_0037: call  valuetype [mscorlib]System.Decimal [mscorlib]System.Convert::ToDecimal(object) 
    IL_003c: stloc.2 
    IL_003d: br.s  IL_003f 
    IL_003f: ldloc.2 
    IL_0040: ret 
} // end of method Calculator::Calculate 

Stworzyliśmy TypeCreator to zrobić:

namespace TypeCreator 
{ 
    public interface ICalculator 
    { 
     decimal Calculate(decimal x, decimal y); 
    } 

    public class TypeCreator 
    { 
     private Type targetType; 
     private ScriptEngine scriptEngine; 
     private string jsFileFullPath; 

     public TypeCreator(Type targetType, ScriptEngine scriptEngine, string jsFileFullPath) 
     { 
      this.targetType = targetType; 
      this.scriptEngine = scriptEngine; 
      this.jsFileFullPath = jsFileFullPath; 
     } 
     public Type build() 
     { 
      AppDomain currentAppDomain = AppDomain.CurrentDomain; 
      AssemblyName assyName = new AssemblyName(); 
      assyName.Name = "MyAssyFor_" + targetType.Name; 
      AssemblyBuilder assyBuilder = currentAppDomain.DefineDynamicAssembly(assyName, AssemblyBuilderAccess.Run); 
      ModuleBuilder modBuilder = assyBuilder.DefineDynamicModule("MyModFor_" + targetType.Name); 
      String newTypeName = "Imp_" + targetType.Name; 
      TypeAttributes newTypeAttribute = TypeAttributes.Class | TypeAttributes.Public; 

      Type[] ctorParams = new Type[] { typeof(ScriptEngine), typeof(string) }; 
     Type newTypeParent; 
     Type[] newTypeInterfaces; 
     if (targetType.IsInterface) 
     { 
      newTypeParent = null; 
      newTypeInterfaces = new Type[] { targetType }; 
     } 
     else 
     { 
      newTypeParent = targetType; 
      newTypeInterfaces = new Type[0]; 
     } 
     TypeBuilder typeBuilder = modBuilder.DefineType(newTypeName, newTypeAttribute, newTypeParent, newTypeInterfaces); 

     FieldBuilder scriptEngineField = typeBuilder.DefineField("scriptEngine", typeof(ScriptEngine), 
                  FieldAttributes.Public); 
     FieldBuilder jsFileFullPathField = typeBuilder.DefineField("jsFileFullPath", typeof(string), 
                  FieldAttributes.Public); 
     Type objType = Type.GetType("System.Object"); 
     ConstructorInfo objCtor = objType.GetConstructor(new Type[0]); 

     ConstructorBuilder wrapperCtor = typeBuilder.DefineConstructor(
         MethodAttributes.Public, 
         CallingConventions.Standard, 
         ctorParams); 
     ILGenerator ctorIL = wrapperCtor.GetILGenerator(); 
     ctorIL.Emit(OpCodes.Ldarg_0); 
     ctorIL.Emit(OpCodes.Call, objCtor); 
     ctorIL.Emit(OpCodes.Nop); 
     ctorIL.Emit(OpCodes.Nop); 
     ctorIL.Emit(OpCodes.Ldarg_0); 
     ctorIL.Emit(OpCodes.Ldarg_1); 
     ctorIL.Emit(OpCodes.Stfld, scriptEngineField); 
     ctorIL.Emit(OpCodes.Ldarg_2); 
     ctorIL.Emit(OpCodes.Call, typeof(File).GetMethod("ReadAllText", new Type[] { typeof(string) })); 
     ctorIL.Emit(OpCodes.Stloc_0); 
     ctorIL.Emit(OpCodes.Ldarg_0); 
     ctorIL.Emit(OpCodes.Ldfld, scriptEngineField); 
     ctorIL.Emit(OpCodes.Ldloc_0); 
     ctorIL.Emit(OpCodes.Callvirt, typeof(ScriptEngine).GetMethod("Execute", new Type[] { typeof(string) })); 
     ctorIL.Emit(OpCodes.Nop); 
     ctorIL.Emit(OpCodes.Nop); 
     //ctorIL.Emit(OpCodes.Stfld, jsFileFullPathField); 
     ctorIL.Emit(OpCodes.Ret); 

     string methodName = "Calculate"; 

     MethodBuilder getFieldMethod = typeBuilder.DefineMethod(methodName, MethodAttributes.Public, typeof(decimal), new Type[] { typeof(decimal), typeof(decimal) }); 
     ILGenerator methodIL = getFieldMethod.GetILGenerator(); 
     methodIL.Emit(OpCodes.Nop); 
     methodIL.Emit(OpCodes.Ldstr, @"var rf1013 = new TotalTaxation.TaxformCalculation.RF1013({0},{1}); 
       rf1013.Calculate(); 
       var result = rf1013.RF1013Sum;"); 
     methodIL.Emit(OpCodes.Stloc_0); 
     methodIL.Emit(OpCodes.Ldarg_0); 
     methodIL.Emit(OpCodes.Ldfld, scriptEngineField); 
     methodIL.Emit(OpCodes.Ldloc_0); 
     methodIL.Emit(OpCodes.Ldarg_1); 
     methodIL.Emit(OpCodes.Box, typeof(decimal)); 
     methodIL.Emit(OpCodes.Ldarg_2); 
     methodIL.Emit(OpCodes.Call, typeof(String).GetMethod("Format", new Type[] { typeof(string), typeof(object), typeof(object) })); 

     methodIL.Emit(OpCodes.Callvirt, typeof(ScriptEngine).GetMethod("Execute", new Type[] { typeof(string) })); 
     methodIL.Emit(OpCodes.Pop); 
     methodIL.Emit(OpCodes.Ldarg_0); 
     methodIL.Emit(OpCodes.Ldfld, scriptEngineField); 
     methodIL.Emit(OpCodes.Ldstr, "result"); 
     methodIL.Emit(OpCodes.Callvirt, typeof(ScriptEngine).GetMethod("Execute", new Type[] { typeof(string) })); 
     methodIL.Emit(OpCodes.Stloc_0); 
     methodIL.Emit(OpCodes.Ldloc_0); 
     methodIL.Emit(OpCodes.Call, typeof(Convert).GetMethod("ToDecimal", new Type[] { typeof(object) })); 
     methodIL.Emit(OpCodes.Stloc_2); 
     methodIL.Emit(OpCodes.Br_S); 
     methodIL.Emit(OpCodes.Ldloc_2); 
     methodIL.Emit(OpCodes.Ret); 

     return (typeBuilder.CreateType()); 
    } 
} 

Używaj go tak:

 var jsFileFullPath = "JsFiles\\Total.js"; 
     TypeCreator tc = new TypeCreator(typeof(ICalculator), new JScriptEngine(), jsFileFullPath); 
     Type t = tc.build(); 

     // Prepares the parameters 
     var scriptArgs = new System.Collections.ArrayList(); 
     scriptArgs.Add(new JScriptEngine()); 
     scriptArgs.Add(jsFileFullPath); 

     ICalculator calculator = (ICalculator)Activator.CreateInstance(t, scriptArgs); 
     var result = calculator.Calculate(3.0m, 5.0m); 
     Console.Write(string.Format("calculator.Calculate(3.0m, 5.0m)={0}", result)); 
     Console.Read(); 

To wyjątek:

Metoda 'Oblicz' typu ' Imp_ICalculator 'from assembly' MyAssyFor_ICalculator, Version = 0.0.0.0, Culture = neutral, PublicKeyToken = null 'nie ma implementacji .

Na czym polega problem?

+1

Na czym dokładnie polega problem? Masz IL, wiesz o Reflection.Emit, więc po prostu użyj tego. – svick

+0

Ponadto, przed użyciem Reflection.Emit, prawdopodobnie spróbuję użyć [Castle DynamicProxy] (http://www.castleproject.org/projects/dynamicproxy/), prawdopodobnie będzie to prostsze. – svick

+0

svick, zrobiłem kilka prób i napisałem kod jak powyżej, proszę zobaczyć zaktualizowane pytanie, możesz mi pomóc znaleźć problem? –

Odpowiedz

1

Istnieje kilka problemów w kodzie:

  1. Podawane błędu ponieważ metody implementujące interfejsy muszą być wirtualny (patrz §II.12.2 Implementing virtual methods on interfaces of ECMA-335). Można to również wynika z zdemontowanego IL (wierzę, że newslot i final modyfikatory są tam tak, że metoda nie zachowuje się jak C# virtual metoda):

    .method public hidebysig newslot virtual final 
         instance valuetype [mscorlib]System.Decimal 
         Calculate(valuetype [mscorlib]System.Decimal x, 
            valuetype [mscorlib]System.Decimal y) cil managed 
    

    Aby rozwiązać ten problem, należy dodać | MethodAttributes.Virtual do wywołanie DefineMethod().

  2. Dzwonisz pod numer Activator.CreateInstance() za pomocą pojedynczego parametru ArrayList. Jeśli chcesz wywołać konstruktora z dwoma parametrami, trzeba albo przekazać w jednym object[] lub użyj params:

    Activator.CreateInstance(t, new ScriptEngine(), jsFileFullPath) 
    
  3. Używasz zmiennych lokalnych w IL, ale nie jesteś ich deklarowania. Użyj DeclareLocal(), aby to naprawić.

Tutaj się zatrzymałem, więc możliwe, że Twój kod ma inne problemy.

+0

svick, dzięki za odpowiedź. Poprawiłem kod zgodnie z twoimi komentarzami, jest wyjątek ("Nieobsługiwany wyjątek typu" System.Reflection.TargetInvocationException "wystąpił w mscorlib.dll") po uruchomieniu Activator.CreateInstance. –

+0

@jasonbie Zakładając, że to nie problem # 2 Wspomniałem, powinieneś prawdopodobnie zadać nowe pytanie na ten temat, z twoim kodem i pełnym komunikatem wyjątku (włączając wewnętrzny wyjątek). – svick

+0

svick, zadałem kolejne pytanie: http://stackoverflow.com/questions/29534019/system-reflection-targetinvocationexception-dynamicznie-define-constructor-, czy widzisz, na czym polega problem? –

Powiązane problemy