How to access a custom logger, from a VS breakpoin

2019-07-29 05:47发布

问题:

Background

Visual studio has a really cool feature in that allows you to log a message to the output window when a breakpoint is hit. Even more cool, you can reference live values by surrounding them with curly brackets. But wait: there's more; you can even call a method, with arbitrary signature (passing arguments and returning a value). Whatever value is returned is written in the output window. Awsome.

In VS 2013, this is the "When Hit" feature and in VS 2015 (and 2017 RC), it's called "Action".

Motivation

I am trying to understand what's going on in the WPF source code, so I set breakpoints with an "Action" to output trace info. But, because WPF is not constraining the types of the values I'm interested in (they are passed as object and the type inferred according to context), the values are more often than not logged as an error because the breakpoint Action is not aware of this context and therefore not aware of the type to expect.

My plan to deal with this is to call a custom logger from the breakpoint Action and pass arguments sufficient for the logger can resolve the type of the value.
The custom logger is a member of my MainWindow partial class.

Proof of concept

I verified that this works by setting a breakpoint action in my MainWindow partial class, it references the logger member and passes arguments no problem. This is fine for breakpoints within my app but I want to use it on breakpoints in other assemblies.

The problem

As stated above, this works fine when the breakpoint is in my own assembly but fails for breakpoints set in third party assemblies (like the wpf source code). Obviously the object needs to be qualified so that the debugger can resolve it.

How do I qualify it, how do I reference the instance of the window and therefore the member I'm looking for?
I guess I could make it a static singleton but I might need more than one logger.

回答1:

I figured out that I can reference my logger. For example, if my main window partial class looks like this...

namespace EventSetterNull_SO_41604891_2670182
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window

...then I can reference it like this...

((EventSetterNull_SO_41604891_2670182.MainWindow)System.Windows.Application.Current.MainWindow)

So, my breakpoint is like...

Then, I can set breakpoints in, for example, System.Windows.Markup.WpfXamlLoader.TransformNodes, pass the xamlReader to my logger, and log out the node structure that the parser generates from the baml.

xaml

<Window x:Class="EventSetterNull_SO_41604891_2670182.BuildInXaml"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:EventSetterNull_SO_41604891_2670182"
        mc:Ignorable="d"
        Title="BuildInXaml" Height="350" Width="525">
    <Window.Resources>
        <SetterBaseCollection x:Key="ButtonStyleSetters">
            <Setter Property="FrameworkElement.Height" Value="30"></Setter>
        </SetterBaseCollection>
    </Window.Resources>

    <Button Name="Button1"
            local:Behaviours.StyleSetters="{StaticResource ButtonStyleSetters}" />

Output window

"Line:    6    NamespaceDeclaration    http://schemas.microsoft.com/winfx/2006/xaml/presentation"
"Line:    6    NamespaceDeclaration    http://schemas.microsoft.com/winfx/2006/xaml"
"Line:    6    NamespaceDeclaration    http://schemas.microsoft.com/expression/blend/2008"
"Line:    6    NamespaceDeclaration    http://schemas.openxmlformats.org/markup-compatibility/2006"
"Line:    6    NamespaceDeclaration    clr-namespace:EventSetterNull_SO_41604891_2670182;assembly=EventSetterNull-SO-41604891-2670182"
"Line:    6    StartObject    BuildInXaml" 
"Line:    6        StartMember    Title"
"Line:    6            Value    BuildInXaml"
"Line:    6        EndMember    "
"Line:    8        StartMember    Height"
"Line:    8            Value    System.Windows.Baml2006.TypeConverterMarkupExtension"
"Line:    8        EndMember    "
"Line:    8        StartMember    Width"
"Line:    8            Value    System.Windows.Baml2006.TypeConverterMarkupExtension"
"Line:    8        EndMember    "
"Line:    8        StartMember    Resources"
"Line:    9            GetObject    System.Windows.Baml2006.Baml2006SchemaContext"
"Line:    9                StartMember    DeferrableContent"
"Line:    9                    Value    System.IO.MemoryStream"
"Line:    9                EndMember    "
"Line:    9            EndObject    "
"Line:   15        EndMember    "
"Line:   17        StartMember    Content"
"Line:   17            StartObject    Button" 
"Line:   17                StartMember    ConnectionId"
"Line:   17                    Value    1"
"Line:   17                EndMember    "
"Line:    0                StartMember    Name"
"Line:    0                    Value    Button1"
"Line:    0                EndMember    "
"Line:   17                StartMember    StyleSetters"
"Line:   17                    Value    System.Windows.StaticResourceExtension"
"Line:   17                EndMember    "
"Line:   10                StartObject    SetterBaseCollection" 
"Line:   10                    StartMember    _Items"
"Line:   11                        StartObject    Setter" 
"Line:   11                            StartMember    Property"
"Line:   11                                Value    Height"
"Line:   11                            EndMember    "
"Line:   11                            StartMember    Value"
"Line:   11                                Value    30"
"Line:   11                            EndMember    "
"Line:   11                        EndObject    "
"Line:   11                    EndMember    "
"Line:   11                EndObject    "
"Line:   18            EndObject    "
"Line:   18        EndMember    "
"Line:   18    EndObject    "

So, I can see exactly how wpf sees the xaml, which is handy if it screws up...

xaml

<Window x:Class="EventSetterNull_SO_41604891_2670182.BuildInXaml"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:EventSetterNull_SO_41604891_2670182"
        mc:Ignorable="d"
        Title="BuildInXaml" Height="350" Width="525">
    <Window.Resources>
        <SetterBaseCollection x:Key="ButtonStyleSetters">
            <Setter Property="FrameworkElement.Height" Value="30"></Setter>
            <EventSetter Event="ButtonBase.Click"  Handler="StyleClick"
                          HandledEventsToo="False" />
        </SetterBaseCollection>
    </Window.Resources>

    <Button Name="Button1"
            local:Behaviours.StyleSetters="{StaticResource ButtonStyleSetters}" />

Output window

"Line:    6    NamespaceDeclaration    http://schemas.microsoft.com/winfx/2006/xaml/presentation"
"Line:    6    NamespaceDeclaration    http://schemas.microsoft.com/winfx/2006/xaml"
"Line:    6    NamespaceDeclaration    http://schemas.microsoft.com/expression/blend/2008"
"Line:    6    NamespaceDeclaration    http://schemas.openxmlformats.org/markup-compatibility/2006"
"Line:    6    NamespaceDeclaration    clr-namespace:EventSetterNull_SO_41604891_2670182;assembly=EventSetterNull-SO-41604891-2670182"
"Line:    6    StartObject    BuildInXaml" 
"Line:    6        StartMember    Title"
"Line:    6            Value    BuildInXaml"
"Line:    6        EndMember    "
"Line:    8        StartMember    Height"
"Line:    8            Value    System.Windows.Baml2006.TypeConverterMarkupExtension"
"Line:    8        EndMember    "
"Line:    8        StartMember    Width"
"Line:    8            Value    System.Windows.Baml2006.TypeConverterMarkupExtension"
"Line:    8        EndMember    "
"Line:    8        StartMember    Resources"
"Line:    9            GetObject    System.Windows.Baml2006.Baml2006SchemaContext"
"Line:    9                StartMember    DeferrableContent"
"Line:    9                    Value    System.IO.MemoryStream"
"Line:    9                EndMember    "
"Line:    9            EndObject    "
"Line:   15        EndMember    "
"Line:   17        StartMember    Content"
"Line:   17            StartObject    Button" 
"Line:   17                StartMember    ConnectionId"
"Line:   17                    Value    1"
"Line:   17                EndMember    "
"Line:    0                StartMember    Name"
"Line:    0                    Value    Button1"
"Line:    0                EndMember    "
"Line:   17                StartMember    StyleSetters"
"Line:   17                    Value    System.Windows.StaticResourceExtension"
"Line:   17                EndMember    "
"Line:   10                StartObject    SetterBaseCollection" 
"Line:   10                    StartMember    _Items"
"Line:   11                        StartObject    Setter" 
"Line:   11                            StartMember    Property"
"Line:   11                                Value    Height"
"Line:   11                            EndMember    "
"Line:   11                            StartMember    Value"
"Line:   11                                Value    30"
"Line:   11                            EndMember    "
"Line:   11                        EndObject    "
"Line:   12                        StartObject    EventSetter" 
"Line:   12                            StartMember    Event"
"Line:   12                                Value    System.Windows.Baml2006.TypeConverterMarkupExtension"
"Line:   12                            EndMember    "
"Line:   12                            StartMember    Event"
Exception thrown: 'System.Xaml.XamlDuplicateMemberException' in System.Xaml.dll

As you can see, wpf is getting confused and finding a second Event member in the EventSetter object, that's not there and throwing an error.

This is a bug in wpf.