Работая над классом 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 , чтобы проверить, является ли значение в стеке нулевым.
Теперь запустим следующий код:
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 или я что-то не так делаю?
Некоторые подсказки:
- В отладчике это происходит только тогда, когда «Подавить оптимизацию JIT» отключено. Если «Подавить оптимизацию JIT» включен, он работает. Это заставляет меня думать, что проблема должна быть в оптимизированном коде JIT.
- Запуск такого же теста на Mono 2.4.6, он работает как положено, так что это что-то особенное для Microsoft .NET.
- Проблема появляется при использовании даты и времени или десятичных типов. По-видимому, это работает для int или для ссылочных типов (для ссылочных типов сгенерированный код не идентичен, но я опускаю этот случай, когда он работает).
- Я думаю эта ссылка , о которой сообщалось давно, может быть связана.
- Я пробовал .NET Framework v2.0, v3.0, v3.5 и v4, и поведение точно такое же.
Я опускаю остальную часть кода, создавая сборку и тип. Если вам нужен полный код, просто спросите меня.
Большое спасибо!
Редактировать: я включаю остальную часть сборки и код создания типа, для завершения:
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 */
}
}