将代码注入到没有自定义属性的所有方法和属性的最简单方法

发布于 2024-11-19 12:25:45 字数 1054 浏览 3 评论 0 原文

AOP 有很多关于 .NET 位于 Stack Overflow 上,经常提到 PostSharp 和其他第三方产品。因此,.NET 和 C# 世界中似乎有相当多的 AOP 选项。但其中每一个都有其限制,在下载了有前途的 PostSharp 之后,我在他们的文档中发现“方法必须是虚拟的”以便能够注入代码(编辑:请参阅 ChrisWue 的答案和我的评论 - 我想,虚拟约束一定是在竞争者之一身上)。我没有进一步调查这个陈述的准确性,但它的明确性让我回到了 StackOverflow。

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

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

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

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]

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

零崎曲识 2024-11-26 12:25:45

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

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

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

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

这是我的代码的要点:

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

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

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.

别理我 2024-11-26 12:25:45

我不确定您从哪里得知方法必须是虚拟的。我们使用 Postsharp 来计时并记录对 WCF 服务接口实现的调用,利用 OnMethodBoundaryAspect 来创建我们可以用来装饰类的属性。快速示例:

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

然后像这样使用它:

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

      [MyCustomAttribute]
      public class DoNotLogMe()
      {
      }
 }

在 Postsharp 1.5.6 中像魅力一样工作,无需使任何方法虚拟。也许他们已经在 2.x 中更改了它,但我当然不希望如此 - 这会使它变得不那么有用。

更新:我不确定您是否可以轻松说服 Postsharp 仅根据装饰的属性将代码注入到某些方法中。如果您查看本教程< /a> 它仅显示过滤类型和方法名称的方法。我们通过将要检查的类型传递到属性中解决了这个问题,然后在 OnEntry 中您可以使用反射来查找属性并决定是否记录。其结果会被缓存,因此您只需在第一次调用时执行此操作。

我调整了上面的代码来演示这个想法。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文