2015-12-07 4 views
2

Je souhaite injecter la journalisation SQL dans quelques méthodes. Fondamentalement, je veux transformerRéécrire IL pour injecter try-finally around appel de méthode

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; 
    } 

dans

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; 
    } 

Je suis les cas faciles à travailler, la question que je suis actuellement confronté est que la pile doit être vide lors de la saisie d'un .try.

Mon hypothèse primitive était que le IDbCommand lui-même a été chargé directement avant l'appel à ExecuteScalar, donc je recherche pour callvirt puis utilisez Instruction.Previous comme le début de la .try.

Mais si la valeur de retour de ExecuteScalar est utilisé par la suite, le compilateur génère l'IL suivante:

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) 

Avec mon algorithme primitivy, cela est transformé en

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) 

Alors PEVerify se plaint que je entrez un .try avec une pile non vide. Existe-t-il un moyen facile d'injecter le try-finally juste sur ExecuteScalar ou dois-je faire une analyse complète du flux sur toute la méthode, en calculant la profondeur de la pile à tout moment puis en chargeant/restaurant les valeurs avant/après l'essai/finalement?

EDIT:

Je suis en mesure de le faire fonctionner en balayant le haut/bas jusqu'à ce que je trouve deux points où la pile profondeur est 0. Dans mes tests limités, cela semble fonctionner, mais je serait toujours intéressé par une implémentation "propre" au lieu de scanner aveuglément l'IL.

Répondre

4

Je réécris un appel à ExecuteScalar dans un appel à une méthode d'assistance:

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

ExecuteScalarWrapper serait une méthode d'assistance statique que vous pouvez écrire en C# et référence.

Ensuite, vous n'avez pas besoin d'injecter des blocs try. Il vous suffit de remplacer le motif

ld... command 
call ExecuteScalar 

avec

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

qui devrait être plus facile parce que la mise en page de la pile et les modifications sont définies localement et ne nécessitent pas de raisonnement complexe.

Maintenant, le JIT fait tout le travail de levage pour vous.