Работая над классом SQLHelper для автоматизации вызовов хранимых процедур аналогично тому, как это делается в библиотеке XmlRpc.Net , я столкнулся с очень странной проблемой при запуске метода, созданного вручную из кода IL.
Я сузил его до простого сгенерированного метода (возможно, его можно было бы еще упростить). Я создаю новую сборку и тип,содержащие два метода для соответствия
public interface iTestDecimal
{
void TestOk(ref decimal value);
void TestWrong(ref decimal value);
}
Методы тестирования просто загружают десятичный аргумент в стек, упаковывают его, проверяют, является ли он NULL, а если нет, распаковывают.
Генерация метода TestOk () выполняется следующим образом:
static void BuildMethodOk(TypeBuilder tb)
{
/* Create a method builder */
MethodBuilder mthdBldr = tb.DefineMethod( "TestOk", MethodAttributes.Public | MethodAttributes.Virtual,
typeof(void), new Type[] {typeof(decimal).MakeByRefType() });
ParameterBuilder paramBldr = mthdBldr.DefineParameter(1, ParameterAttributes.In | ParameterAttributes.Out, "value");
// generate IL
ILGenerator ilgen = mthdBldr.GetILGenerator();
/* Load argument to stack, and box the decimal value */
ilgen.Emit(OpCodes.Ldarg, 1);
ilgen.Emit(OpCodes.Dup);
ilgen.Emit(OpCodes.Ldobj, typeof(decimal));
ilgen.Emit(OpCodes.Box, typeof(decimal));
/* Some things were done in here, invoking other method, etc */
/* At the top of the stack we should have a boxed T or null */
/* Copy reference values out */
/* Skip unboxing if value in the stack is null */
Label valIsNotNull = ilgen.DefineLabel();
ilgen.Emit(OpCodes.Dup);
/* This block works */
ilgen.Emit(OpCodes.Brtrue, valIsNotNull);
ilgen.Emit(OpCodes.Pop);
ilgen.Emit(OpCodes.Pop);
ilgen.Emit(OpCodes.Ret);
/* End block */
ilgen.MarkLabel(valIsNotNull);
ilgen.Emit(OpCodes.Unbox_Any, typeof(decimal));
/* Just clean the stack */
ilgen.Emit(OpCodes.Pop);
ilgen.Emit(OpCodes.Pop);
ilgen.Emit(OpCodes.Ret);
}
Построение TestWrong () почти идентично:
static void BuildMethodWrong(TypeBuilder tb)
{
/* Create a method builder */
MethodBuilder mthdBldr = tb.DefineMethod("TestWrong", MethodAttributes.Public | MethodAttributes.Virtual,
typeof(void), new Type[] { typeof(decimal).MakeByRefType() });
ParameterBuilder paramBldr = mthdBldr.DefineParameter(1, ParameterAttributes.In | ParameterAttributes.Out, "value");
// generate IL
ILGenerator ilgen = mthdBldr.GetILGenerator();
/* Load argument to stack, and box the decimal value */
ilgen.Emit(OpCodes.Ldarg, 1);
ilgen.Emit(OpCodes.Dup);
ilgen.Emit(OpCodes.Ldobj, typeof(decimal));
ilgen.Emit(OpCodes.Box, typeof(decimal));
/* Some things were done in here, invoking other method, etc */
/* At the top of the stack we should have a boxed decimal or null */
/* Copy reference values out */
/* Skip unboxing if value in the stack is null */
Label valIsNull = ilgen.DefineLabel();
ilgen.Emit(OpCodes.Dup);
/* This block fails */
ilgen.Emit(OpCodes.Brfalse, valIsNull);
/* End block */
ilgen.Emit(OpCodes.Unbox_Any, typeof(decimal));
ilgen.MarkLabel(valIsNull);
/* Just clean the stack */
ilgen.Emit(OpCodes.Pop);
ilgen.Emit(OpCodes.Pop);
ilgen.Emit(OpCodes.Ret);
}
Единственная разница в том, что я использую BrFalse вместо BrTrue , чтобы проверить, есть ли значение в стек имеет значение null.
Теперь выполняется следующий код:
iTestDecimal testiface = (iTestDecimal)SimpleCodeGen.Create();
decimal dectest = 1;
testiface.TestOk(ref dectest);
Console.WriteLine(" Dectest: " + dectest.ToString());
SimpleCodeGen.Create () создает новую сборку и тип и вызывает BuildMethodXX, указанный выше, для генерации кода для TestOk и TestWrong.
static void BuildMethodOk(TypeBuilder tb)
{
/* Create a method builder */
MethodBuilder mthdBldr = tb.DefineMethod( "TestOk", MethodAttributes.Public | MethodAttributes.Virtual,
typeof(void), new Type[] {typeof(decimal).MakeByRefType() });
ParameterBuilder paramBldr = mthdBldr.DefineParameter(1, ParameterAttributes.In | ParameterAttributes.Out, "value");
// generate IL
ILGenerator ilgen = mthdBldr.GetILGenerator();
/* Load argument to stack, and box the decimal value */
ilgen.Emit(OpCodes.Ldarg, 1);
ilgen.Emit(OpCodes.Dup);
ilgen.Emit(OpCodes.Ldobj, typeof(decimal));
ilgen.Emit(OpCodes.Box, typeof(decimal));
/* Some things were done in here, invoking other method, etc */
/* At the top of the stack we should have a boxed T or null */
/* Copy reference values out */
/* Skip unboxing if value in the stack is null */
Label valIsNotNull = ilgen.DefineLabel();
ilgen.Emit(OpCodes.Dup);
/* This block works */
ilgen.Emit(OpCodes.Brtrue, valIsNotNull);
ilgen.Emit(OpCodes.Pop);
ilgen.Emit(OpCodes.Pop);
ilgen.Emit(OpCodes.Ret);
/* End block */
ilgen.MarkLabel(valIsNotNull);
ilgen.Emit(OpCodes.Unbox_Any, typeof(decimal));
/* Just clean the stack */
ilgen.Emit(OpCodes.Pop);
ilgen.Emit(OpCodes.Pop);
ilgen.Emit(OpCodes.Ret);
}
Построение TestWrong () почти идентично:
static void BuildMethodWrong(TypeBuilder tb)
{
/* Create a method builder */
MethodBuilder mthdBldr = tb.DefineMethod("TestWrong", MethodAttributes.Public | MethodAttributes.Virtual,
typeof(void), new Type[] { typeof(decimal).MakeByRefType() });
ParameterBuilder paramBldr = mthdBldr.DefineParameter(1, ParameterAttributes.In | ParameterAttributes.Out, "value");
// generate IL
ILGenerator ilgen = mthdBldr.GetILGenerator();
/* Load argument to stack, and box the decimal value */
ilgen.Emit(OpCodes.Ldarg, 1);
ilgen.Emit(OpCodes.Dup);
ilgen.Emit(OpCodes.Ldobj, typeof(decimal));
ilgen.Emit(OpCodes.Box, typeof(decimal));
/* Some things were done in here, invoking other method, etc */
/* At the top of the stack we should have a boxed decimal or null */
/* Copy reference values out */
/* Skip unboxing if value in the stack is null */
Label valIsNull = ilgen.DefineLabel();
ilgen.Emit(OpCodes.Dup);
/* This block fails */
ilgen.Emit(OpCodes.Brfalse, valIsNull);
/* End block */
ilgen.Emit(OpCodes.Unbox_Any, typeof(decimal));
ilgen.MarkLabel(valIsNull);
/* Just clean the stack */
ilgen.Emit(OpCodes.Pop);
ilgen.Emit(OpCodes.Pop);
ilgen.Emit(OpCodes.Ret);
}
Единственная разница в том, что я использую BrFalse вместо BrTrue , чтобы проверить, является ли значение в стеке нулевым. .
Теперь выполняется следующий код:
iTestDecimal testiface = (iTestDecimal)SimpleCodeGen.Create();
decimal dectest = 1;
testiface.TestOk(ref dectest);
Console.WriteLine(" Dectest: " + dectest.ToString());
SimpleCodeGen.Create () создает новую сборку и тип и вызывает BuildMethodXX, указанный выше, для генерации кода для TestOk и TestWrong.
static void BuildMethodOk(TypeBuilder tb)
{
/* Create a method builder */
MethodBuilder mthdBldr = tb.DefineMethod( "TestOk", MethodAttributes.Public | MethodAttributes.Virtual,
typeof(void), new Type[] {typeof(decimal).MakeByRefType() });
ParameterBuilder paramBldr = mthdBldr.DefineParameter(1, ParameterAttributes.In | ParameterAttributes.Out, "value");
// generate IL
ILGenerator ilgen = mthdBldr.GetILGenerator();
/* Load argument to stack, and box the decimal value */
ilgen.Emit(OpCodes.Ldarg, 1);
ilgen.Emit(OpCodes.Dup);
ilgen.Emit(OpCodes.Ldobj, typeof(decimal));
ilgen.Emit(OpCodes.Box, typeof(decimal));
/* Some things were done in here, invoking other method, etc */
/* At the top of the stack we should have a boxed T or null */
/* Copy reference values out */
/* Skip unboxing if value in the stack is null */
Label valIsNotNull = ilgen.DefineLabel();
ilgen.Emit(OpCodes.Dup);
/* This block works */
ilgen.Emit(OpCodes.Brtrue, valIsNotNull);
ilgen.Emit(OpCodes.Pop);
ilgen.Emit(OpCodes.Pop);
ilgen.Emit(OpCodes.Ret);
/* End block */
ilgen.MarkLabel(valIsNotNull);
ilgen.Emit(OpCodes.Unbox_Any, typeof(decimal));
/* Just clean the stack */
ilgen.Emit(OpCodes.Pop);
ilgen.Emit(OpCodes.Pop);
ilgen.Emit(OpCodes.Ret);
}
Построение TestWrong () почти идентично:
static void BuildMethodWrong(TypeBuilder tb)
{
/* Create a method builder */
MethodBuilder mthdBldr = tb.DefineMethod("TestWrong", MethodAttributes.Public | MethodAttributes.Virtual,
typeof(void), new Type[] { typeof(decimal).MakeByRefType() });
ParameterBuilder paramBldr = mthdBldr.DefineParameter(1, ParameterAttributes.In | ParameterAttributes.Out, "value");
// generate IL
ILGenerator ilgen = mthdBldr.GetILGenerator();
/* Load argument to stack, and box the decimal value */
ilgen.Emit(OpCodes.Ldarg, 1);
ilgen.Emit(OpCodes.Dup);
ilgen.Emit(OpCodes.Ldobj, typeof(decimal));
ilgen.Emit(OpCodes.Box, typeof(decimal));
/* Some things were done in here, invoking other method, etc */
/* At the top of the stack we should have a boxed decimal or null */
/* Copy reference values out */
/* Skip unboxing if value in the stack is null */
Label valIsNull = ilgen.DefineLabel();
ilgen.Emit(OpCodes.Dup);
/* This block fails */
ilgen.Emit(OpCodes.Brfalse, valIsNull);
/* End block */
ilgen.Emit(OpCodes.Unbox_Any, typeof(decimal));
ilgen.MarkLabel(valIsNull);
/* Just clean the stack */
ilgen.Emit(OpCodes.Pop);
ilgen.Emit(OpCodes.Pop);
ilgen.Emit(OpCodes.Ret);
}
Единственная разница в том, что я использую BrFalse вместо BrTrue , чтобы проверить, является ли значение в стеке нулевым. .
Теперь выполняется следующий код:
iTestDecimal testiface = (iTestDecimal)SimpleCodeGen.Create();
decimal dectest = 1;
testiface.TestOk(ref dectest);
Console.WriteLine(" Dectest: " + dectest.ToString());
SimpleCodeGen.Create () создает новую сборку и тип и вызывает BuildMethodXX, указанный выше, для генерации кода для TestOk и TestWrong. Это работает должным образом: ничего не делает, значение dectest не изменяется. Однако при выполнении:
iTestDecimal testiface = (iTestDecimal)SimpleCodeGen.Create();
decimal dectest = 1;
testiface.TestWrong(ref dectest);
Console.WriteLine(" Dectest: " + dectest.ToString());
значение dectest повреждено (иногда оно принимает большое значение, иногда сообщает «недопустимое десятичное значение», ...), и программа дает сбой.
Май это ошибка JIT, или я что-то делаю не так?
Некоторые подсказки:
Я опускаю остальную часть кода, создавая сборку и тип. Если вам нужен полный код, просто спросите меня.
Большое спасибо!
Edit: Я включаю остальную часть кода сборки и создания типа, для завершения:
class SimpleCodeGen
{
public static object Create()
{
Type proxyType;
Guid guid = Guid.NewGuid();
string assemblyName = "TestType" + guid.ToString();
string moduleName = "TestType" + guid.ToString() + ".dll";
string typeName = "TestType" + guid.ToString();
/* Build the new type */
AssemblyBuilder assBldr = BuildAssembly(typeof(iTestDecimal), assemblyName, moduleName, typeName);
proxyType = assBldr.GetType(typeName);
/* Create an instance */
return Activator.CreateInstance(proxyType);
}
static AssemblyBuilder BuildAssembly(Type itf, string assemblyName, string moduleName, string typeName)
{
/* Create a new type */
AssemblyName assName = new AssemblyName();
assName.Name = assemblyName;
assName.Version = itf.Assembly.GetName().Version;
AssemblyBuilder assBldr = AppDomain.CurrentDomain.DefineDynamicAssembly(assName, AssemblyBuilderAccess.RunAndSave);
ModuleBuilder modBldr = assBldr.DefineDynamicModule(assName.Name, moduleName);
TypeBuilder typeBldr = modBldr.DefineType(typeName,
TypeAttributes.Class | TypeAttributes.Sealed | TypeAttributes.Public,
typeof(object), new Type[] { itf });
BuildConstructor(typeBldr, typeof(object));
BuildMethodOk(typeBldr);
BuildMethodWrong(typeBldr);
typeBldr.CreateType();
return assBldr;
}
private static void BuildConstructor(TypeBuilder typeBldr, Type baseType)
{
ConstructorBuilder ctorBldr = typeBldr.DefineConstructor(
MethodAttributes.Public | MethodAttributes.SpecialName |
MethodAttributes.RTSpecialName | MethodAttributes.HideBySig,
CallingConventions.Standard,
Type.EmptyTypes);
ILGenerator ilgen = ctorBldr.GetILGenerator();
// Call the base constructor.
ilgen.Emit(OpCodes.Ldarg_0);
ConstructorInfo ctorInfo = baseType.GetConstructor(System.Type.EmptyTypes);
ilgen.Emit(OpCodes.Call, ctorInfo);
ilgen.Emit(OpCodes.Ret);
}
static void BuildMethodOk(TypeBuilder tb)
{
/* Code included in examples above */
}
static void BuildMethodWrong(TypeBuilder tb)
{
/* Code included in examples above */
}
}