Я хочу внедрить ведение журнала sql в несколько методов. В основном я хочу преобразовать
public static object IDbCommandTest_ExecuteScalar(IDbCommand command)
{
// .. do stuff
command.CommandText = "SELECT ...";
var obj = command.ExecuteScalar();
Console.WriteLine("SQL returned " + obj);
// do other stuff
return obj;
}
в
public static object IDbCommandTest_ExecuteScalar_Transformed(IDbCommand command)
{
// .. do stuff
command.CommandText = "SELECT ...";
object obj;
using (SqlLogger.Enter(command, "IDbCommand", "ExecuteScalar"))
{
obj = command.ExecuteScalar();
}
Console.WriteLine("SQL returned " + obj);
// do other stuff
return obj;
}
У меня работают простые случаи, проблема, с которой я сейчас сталкиваюсь, заключается в том, что стек должен быть пустым при вводе .try
.
Мое примитивное предположение заключалось в том, что сам IDbCommand
был загружен непосредственно перед вызовом ExecuteScalar
, поэтому я ищу callvirt, а затем использую Instruction.Previous
в качестве начала .try
.
Но если возвращаемое значение ExecuteScalar
используется впоследствии, компилятор генерирует следующий IL:
IL_004d: ldarg.0
IL_004e: ldloc.1
IL_004f: callvirt instance object [System.Data]System.Data.IDbCommand::ExecuteScalar()
IL_0054: call string Class_which_uses_obj::DoStuff(object)
С моим примитивным алгоритмом это превращается в
IL_0050: ldarg.0
.try
{
IL_0051: ldloc.1
IL_0052: dup
IL_0053: ldstr "IDbCommand"
IL_0058: ldstr "get_FileName"
IL_005d: call class [mscorlib]System.IDisposable SqlLogger::Enter(class [System.Data]System.Data.IDbCommand, string, string)
IL_0062: stloc.3
IL_0063: callvirt instance object [System.Data]System.Data.IDbCommand::ExecuteScalar()
IL_0068: stloc.s 4
IL_006a: leave.s IL_0077
} // end .try
finally
{
IL_006c: nop
IL_006d: ldloc.3
IL_006e: brfalse.s IL_0076
IL_0070: ldloc.3
IL_0071: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_0076: endfinally
} // end handler
IL_0077: nop
IL_0078: ldloc.s 4
IL_007a: call string IL_0050: ldarg.0
.try
{
IL_0051: ldloc.1
IL_0052: dup
IL_0053: ldstr "IDbCommand"
IL_0058: ldstr "ExecuteScalar"
IL_005d: call class [mscorlib]System.IDisposable Nemetschek.Allready.SqlLogger::Enter(class [System.Data]System.Data.IDbCommand, string, string)
IL_0062: stloc.3
IL_0063: callvirt instance object [System.Data]System.Data.IDbCommand::ExecuteScalar()
IL_0068: stloc.s 4
IL_006a: leave.s IL_0077
} // end .try
finally
{
IL_006c: nop
IL_006d: ldloc.3
IL_006e: brfalse.s IL_0076
IL_0070: ldloc.3
IL_0071: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_0076: endfinally
} // end handler
IL_0077: nop
IL_0078: ldloc.s 4
IL_007a: call string Nemetschek.Allready.Logistics.DbTools.CDbTools::GetSafeStringEmpty(object)(object)
Затем PEVERIFY жалуется, что я ввожу .try с непустым стеком.
Есть ли простой способ внедрить try-finally только через ExecuteScalar или мне нужно выполнить полный анализ потока по всему методу, вычислив глубину стека в любой точке, а затем загрузив/восстановив значения до/после попытки/наконец ?
ИЗМЕНИТЬ:
Мне удалось заставить его работать, сканируя вверх/вниз, пока я не найду две точки, где глубина стека равна 0. В моих ограниченных тестах это, кажется, работает, но я все равно был бы заинтересован в «чистой» реализации вместо слепое сканирование ИЛ.