开发者

Easiest way to inject code to all methods and properties that don't have a custom attribute

There are a a lot of questions and answers around AOP in .NET here on Stack Overflow, often mentioning PostSharp and other third-party products. So there seems to be quite a range of AOP optons in the .NET and C# world. But each of those has their restrictions, and after downloading the promising PostSharp I found in their documentation that 'methods have to be virtual' in order to be able to inject code (edit: see ChrisWue's answer and my comment - the virtual constraint must have been on one of the conten开发者_Go百科ders, I suppose). I haven't investigated the accuracy of this statement any further, but it's categoricality made me return back to Stack Overflow.

So I'd like to get an answer to this very specific question:

I want to inject simple "if (some-condition) Console.WriteLine" style code to every method and property (static, sealed, internal, virtual, non-virtual, doesn't matter) in my project that does not have a custom annotation, in order to dynamically test my software at run-time. This injected code should not remain in the release build, it is just meant for dynamic testing (thread-related) during development.

What's the easiest way to do this? I stumbled upon Mono.Cecil, which looks ideal, except that you seem to have to write the code that you want to inject in IL. This isn't a huge problem, it's easy to use Mono.Cecil to get an IL version of code written in C#. But nevertheless, if there was something simpler, ideally even built into .NET (I'm still on .NET 3.5), I'd like to know. [Update: If the suggested tool is not part of the .NET Framework, it would be nice if it was open-source, like Mono.Cecil, or freely available]


I was able to solve the problem with Mono.Cecil. I am still amazed how easy to learn, easy to use, and powerful it is. The almost complete lack of documentation did not change that.

These are the 3 sources of documentation I used:

  • static-method-interception-in-net-with-c-and-monocecil
  • Migration to 0.9
  • the source code itself

The first link provides a very gentle introduction, but as it describes an older version of Cecil - and much has changed in the meantime - the second link was very helpful in translating the introduction to Cecil 0.9. After getting started, the (also not documented) source code was invaluable and answered every question I had - expect perhaps those about the .NET platform in general, but there's tons of books and material on that somewhere online I'm sure.

I can now take a DLL or EXE file, modify it, and write it back to disk. The only thing that I haven't done yet is figuring out how to keep debugging information - file name, line number, etc. currently get lost after writing the DLL or EXE file. My background isn't .NET, so I'm guessing here, and my guess would be that I need to look at mono.cecil.pdb to fix that. Somewhere later - it's not that super important for me right now. I'm creating this EXE file, run the application - and it's a complex GUI application, grown over many years with all the baggage you would expect to find in such a piece of, ahem, software - and it checks things and logs errors for me.

Here's the gist of my code:

DefaultAssemblyResolver assemblyResolver = new DefaultAssemblyResolver();
// so it won't complain about not finding assemblies sitting in the same directory as the dll/exe we are going to patch
assemblyResolver.AddSearchDirectory(assemblyDirectory);
var readerParameters = new ReaderParameters { AssemblyResolver = assemblyResolver };

AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(assemblyFilename, readerParameters);

foreach (var moduleDefinition in assembly.Modules)
{
    foreach (var type in ModuleDefinitionRocks.GetAllTypes(moduleDefinition))
    {
        foreach (var method in type.Methods)
        {
            if (!HasAttribute("MyCustomAttribute", method.method.CustomAttributes)
            {
              ILProcessor ilProcessor = method.Body.GetILProcessor();
              ilProcessor.InsertBefore(method.Body.Instructions.First(), ilProcessor.Create(OpCodes.Call, threadCheckerMethod));
// ...

private static bool HasAttribute(string attributeName, IEnumerable<CustomAttribute> customAttributes)
{
    return GetAttributeByName(attributeName, customAttributes) != null;
}

private static CustomAttribute GetAttributeByName(string attributeName, IEnumerable<CustomAttribute> customAttributes)
{
    foreach (var attribute in customAttributes)
        if (attribute.AttributeType.FullName == attributeName)
            return attribute;
    return null;
}

If someone knows an easier way how to get this done, I'm still interested in an answer and I won't mark this as the solution - unless no easier solutions show up.


I'm not sure where you got that methods have to be virtual from. We use Postsharp to time and log calls to WCF service interface implementations utilizing the OnMethodBoundaryAspect to create an attribute we can decorate the classes with. Quick Example:

[Serializable]
public class LogMethodCallAttribute : OnMethodBoundaryAspect
{
    public Type FilterAttributeType { get; set; }

    public LogMethodCallAttribute(Type filterAttributeType)
    {
        FilterAttributeType = filterAttributeType;
    }

    public override void OnEntry(MethodExecutionEventArgs eventArgs)
    {
        if (!Proceed(eventArgs)) return;
        Console.WriteLine(GetMethodName(eventArgs));
    }

    public override void OnException(MethodExecutionEventArgs eventArgs)
    {
        if (!Proceed(eventArgs)) return;
        Console.WriteLine(string.Format("Exception at {0}:\n{1}", 
                GetMethodName(eventArgs), eventArgs.Exception));
    }

    public override void OnExit(MethodExecutionEventArgs eventArgs)
    {
        if (!Proceed(eventArgs)) return;
         Console.WriteLine(string.Format("{0} returned {1}", 
                GetMethodName(eventArgs), eventArgs.ReturnValue));
    }
    private string GetMethodName(MethodExecutionEventArgs eventArgs)
    {
        return string.Format("{0}.{1}", eventArgs.Method.DeclaringType, eventArgs.Method.Name);
    }
    private bool Proceed(MethodExecutionEventArgs eventArgs)
    {
         return Attribute.GetCustomAttributes(eventArgs.Method, FilterAttributeType).Length == 0;
    }
}

And then us it like this:

 [LogMethodCallAttribute(typeof(MyCustomAttribute))]
 class MyClass
 {
      public class LogMe()
      {
      }

      [MyCustomAttribute]
      public class DoNotLogMe()
      {
      }
 }

Works like a charm without having to make any methods virtual in Postsharp 1.5.6. Maybe they have changed it for 2.x but I certainly don't hope so - it would make it way less useful.

Update: I'm not sure if you can easily convince Postsharp to only inject code into certain methods based on with which attributes they are decorated. If you look at this tutorial it only shows ways of filtering on type and method names. We have solved this by passing the type we want to check on into the attribute and then in OnEntry you can use reflection to look for the attributes and decide whether to log or not. The result of that is cached so you only have to do it on the first call.

I adjusted the code above to demonstrate the idea.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜