Suppose we have an interface:
public interface ICalculator
{
decimal Calculate(decimal x, decimal y);
}
the calculate logic is implemented in javascript (actually is TypeScript) code, we want to dynamically create the follow implementation using Reflection.Emit, so we can share the unit tests with the C# implementation:
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);
}
}
we can get the IL from 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
We created the TypeCreator to do that:
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());
}
}
Use it like this:
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();
It throw an exception:
Method 'Calculate' in type 'Imp_ICalculator' from assembly 'MyAssyFor_ICalculator, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' does not have an implementation.
What's the problem?
There are several problems in your code:
The reported error is because methods that implement interfaces have to be virtual (see §II.12.2 Implementing virtual methods on interfaces of ECMA-335). This can also be seen from the disassembled IL (I believe the newslot
and final
modifiers are there so that the method doesn't behave like C# virtual
method):
.method public hidebysig newslot virtual final
instance valuetype [mscorlib]System.Decimal
Calculate(valuetype [mscorlib]System.Decimal x,
valuetype [mscorlib]System.Decimal y) cil managed
To fix this, you need to add | MethodAttributes.Virtual
to the DefineMethod()
call.
You're calling Activator.CreateInstance()
with a single ArrayList
parameter. If you want to call the constructor with two parameters, you need to either pass in a single object[]
or use params
:
Activator.CreateInstance(t, new ScriptEngine(), jsFileFullPath)
You're using local variables in IL, but you're not declaring them. Use DeclareLocal()
to fix that.
This is where I stopped, so it's possible your code has other issues.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With