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

后端 未结 2 695
无人及你
无人及你 2020-12-31 15:13

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 qu

2条回答
  •  清歌不尽
    2020-12-31 15:47

    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 customAttributes)
    {
        return GetAttributeByName(attributeName, customAttributes) != null;
    }
    
    private static CustomAttribute GetAttributeByName(string attributeName, IEnumerable 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.

提交回复
热议问题