将代码注入所有没有自定义属性的方法和属性的最简单方法 [英] Easiest way to inject code to all methods and properties that don't have a custom attribute

查看:17
本文介绍了将代码注入所有没有自定义属性的方法和属性的最简单方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

AOP 有很多问题和答案http://en.wikipedia.org/wiki/.NET_Framework" rel="nofollow">.NET 在 StackOverflow 上,经常提到 PostSharp 和其他第三方产品.所以在 .NET 和 C# 世界中似乎有相当多的 AOP 选项.但是每个都有自己的限制,在下载了有前途的 PostSharp 我在他们的文档中发现方法必须是虚拟的"才能注入代码(参见 ChrisWue 的回答和我的评论 - 我想虚拟约束一定是在其中一个竞争者身上).我没有进一步调查这个陈述的准确性,但它的分类让我回到 StackOverflow.

所以我想得到这个非常具体的问题的答案:

我想注入简单if (some-condition) Console.WriteLine"样式代码到每个方法和属性(静态、密封、内部、虚拟、非- 虚拟,无所谓)在我没有自定义注释的项目中,以便在运行时动态测试我的软件.注入的代码不应保留在发布版本中,它仅用于开发期间的动态测试(与线程相关).

最简单的方法是什么?我偶然发现了 Mono.Cecil,它看起来很理想,只是你似乎必须编写代码你想注入 IL.这不是一个大问题,很容易使用 Mono.Cecil 来获得用 C# 编写的代码的 IL 版本.但是,如果有更简单的东西,最好甚至内置到 .NET 中(我仍然使用 .NET 3.5),我想知道.[更新:如果建议的工具不是 .NET Framework 的一部分,最好是开源的,比如 Mono.Cecil,或者免费提供]

解决方案

我能够用 Mono.Cecil 解决这个问题.我仍然对它的易学性、易用性和强大功能感到惊讶.几乎完全没有文档并没有改变这一点.

这些是我使用的 3 个文档来源:

第一个链接提供了一个非常温和的介绍,但由于它描述了旧版本的 Cecil - 并且在此期间发生了很大变化 - 第二个链接非常有助于将介绍翻译成 Cecil 0.9.开始后,源代码(也未记录在案)非常宝贵,并回答了我遇到的每一个问题 - 可能是关于 .NET 平台的一般问题,但我敢肯定,网上有大量有关这方面的书籍和材料.

我现在可以获取 DLL 或 EXE 文件,对其进行修改,然后将其写回磁盘.我唯一还没有做的事情是弄清楚如何保留调试信息 - 文件名、行号等.目前在写入 DLL 或 EXE 文件后会丢失.我的背景不是 .NET,所以我在这里猜测,我的猜测是我需要查看 mono.cecil.pdb 来解决这个问题.以后的某个地方 - 现在对我来说并不是那么重要.我正在创建这个 EXE 文件,运行该应用程序 - 它是一个复杂的 GUI 应用程序,经过多年的发展,包含了你期望在这样一个软件中找到的所有包袱 - 它会检查事物并记录错误我.

这是我的代码的要点:

DefaultAssemblyResolver assemblyResolver = new DefaultAssemblyResolver();//因此它不会抱怨找不到与我们要修补的 dll/exe 位于同一目录中的程序集assemblyResolver.AddSearchDirectory(assemblyDirectory);var readerParameters = new ReaderParameters { AssemblyResolver = assemblyResolver };AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(assemblyFilename, readerParameters);foreach(在 assembly.Modules 中的 var moduleDefinition){foreach(ModuleDefinitionRocks.GetAllTypes(moduleDefinition) 中的变量类型){foreach(type.Methods 中的 var 方法){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){返回 GetAttributeByName(attributeName, customAttributes) != null;}私有静态 CustomAttribute GetAttributeByName(string attributeName, IEnumerable customAttributes){foreach(customAttributes 中的 var 属性)if (attribute.AttributeType.FullName == attributeName)返回属性;返回空;}

如果有人知道如何更简单地完成这项工作,我仍然对答案感兴趣,我不会将其标记为解决方案 - 除非没有更简单的解决方案出现.

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 contenders, 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:

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.

这篇关于将代码注入所有没有自定义属性的方法和属性的最简单方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆