2015-12-07 61 views
2

我想將sql日誌記錄注入到幾個方法中。基本上,我想改造重寫IL以注入try-finally方法調用

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) 

隨着我primitivy算法,這是轉化成

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。在我有限的測試工作,這似乎工作,但我仍然對「乾淨」的實施感興趣,而不是盲目地掃描IL。

回答

4

我會重寫ExecuteScalar呼叫進入到一個輔助方法的調用:

static object ExecuteScalarWrapper(SqlCommand command, string logString) { 
     using (SqlLogger.Enter(command, logString)) 
     { 
      return command.ExecuteScalar(); 
     } 
} 

ExecuteScalarWrapper是,你可以在C#和參考寫一個靜態輔助方法。

然後,你不需要注入任何try-blocks。你只需要與

ld... command 
ld... logString 
call ExecuteScalarWrapper 

因爲堆棧佈局和修改局部定義,不需要任何複雜的推理應該是更容易更換模式

ld... command 
call ExecuteScalar 

現在JIT爲您做了所有繁重的工作。