以 DateTime 为目标的调试可视化工具不传输值

Zev*_*itz 9 c# debuggervisualizer visual-studio deserialization

我写了一个Visual Studio 调试可视化工具,它的目标是DateTimerepo)。我的问题是,如果目标表达式是 of object,而不是DateTime( issue ) ,则调试器端仅将目标值传递给调试对象端。

我已经发布了一个 GH 存储库,其中包含一个重现问题MCVE。调试器端看起来像这样:

protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider) {
    var response = objectProvider.TransferObject(5);

    var msg = response switch {
        string s => s,
        IEnumerable e => string.Join(", ", e.Cast<object>()),
        _ => "Unhandled type"
    };

    MessageBox.Show(msg);
}
Run Code Online (Sandbox Code Playgroud)

被调试方看起来像这样:

public override void TransferData(object target, Stream incomingData, Stream outgoingData) {
    int? repetitions = Deserialize(incomingData) switch {
        int i when i > 0 => i,
        string s when int.TryParse(s, out int i) && i > 0 => i,
        _ => null
    };

    object toSerialize =
        repetitions is null ? $"Invalid value for repetitions" :
        target switch {
            DateTime dt => Repeat(dt, repetitions.Value).ToArray(),
            null => $"{nameof(target)} is null",
            _ => $"Not implemented for target of type {target.GetType().FullName}" as object
        };

    Serialize(outgoingData, toSerialize);
}
Run Code Online (Sandbox Code Playgroud)

构建并安装可视化工具后,开始调试以下代码:

var dte = DateTime.UtcNow;
object o = dte;
Run Code Online (Sandbox Code Playgroud)

如果我将鼠标悬停在上面o并触发可视化器,则目标DateTime将传递到调试对象端,并返回一个DateTime. 但是,如果我在 上触发可视化器dte,我会返回字符串target is null,这意味着调试端已nulltarget参数中接收到。

什么可能导致这种情况?我该如何解决?


一些随机笔记

  • 这不是因为调试器端总是 32 位,而被调试器端有时是 64 位。
  • 也不是因为不同的 TFM - 当调试器端面向 .NET Framework,而被调试端可以面向 .NET Standard 或 .NET Core 时。
  • TransferData影响覆盖;在GetData覆盖总是得到目标值(我实际使用GetData要解决这一点,但我真的宁愿用GetData别的东西。)我已经尝试测试ReplaceData/ ReplaceObject,但IsObjectReplaceable物业始终回报false
  • 我已经针对其他值类型进行了测试 - TimeSpanDateTimeOffset和自定义struct- 并看到了相同的行为。int但是,当我针对 进行测试时,目标进程崩溃并且调试会话被中断。
  • 针对 aDateTime?显示与DateTime;相同的行为。我想这是因为它们都以相同的方式序列化。

当类似地针对一个目标时,异常命中的堆栈跟踪 int

针对此评论,可视化an时的错误信息int如下:

目标进程在评估函数“Microsoft.VisualStudio.DebuggerVisualizers.DebuggeeSide.Impl.ClrCustomVisualizerDebuggeeHost.TransferData”时退出,代码为 -1073740791 (0xC0000409)。

如果问题经常发生,请考虑禁用工具->选项设置“调试->常规->启用属性评估和其他隐式函数调用”或通过从立即窗口评估表达式来调试原因。有关执行此操作的信息,请参阅帮助。

接着是另一条消息:

无法加载自定义查看器。

目标进程崩溃并且调试会话结束。

我尝试使用代码断点 ( Debugger.Break())附加调试器失败。如果我从可视化器 ( new System.Diagnostics.StackTrace().ToString())返回调用堆栈并且可视化器成功运行,我会得到以下信息:

在 SimpleValueTypeVisualizer.Debuggee.VisualizerObjectSource.TransferData(对象目标,流传入数据,流传出数据)

在 Microsoft.VisualStudio.DebuggerVisualizers.DebuggeeSide.Impl.ClrCustomVisualizerDebuggeeHost.TransferData(对象可视化对象,字节 [] uiSideData)

在 TestNoRef.Program.Main(String[] args)

这似乎意味着在Microsoft.VisualStudio.DebuggerVisualizers.DebuggeeSide.Impl.ClrCustomVisualizerDebuggeeHost.TransferData.

当我打开DebuggerVisualizers.dllusing ILSpy 时,相关TransferData方法如下所示:

// Microsoft.VisualStudio.DebuggerVisualizers.DebuggeeSide.Impl.ClrCustomVisualizerDebuggeeHost
using System.IO;

public byte[] TransferData(object visualizedObject, byte[] uiSideData)
{
    MemoryStream memoryStream = new MemoryStream();
    MemoryStream incomingData = ((uiSideData != null) ? new MemoryStream(uiSideData) : null);
    m_debuggeeSideVisualizerObject.TransferData(visualizedObject, incomingData, memoryStream);
    return memoryStream.ToArray();
}
Run Code Online (Sandbox Code Playgroud)

我猜想例外是在方法 ( MemoryStream incomingData = ...)的第三行。但是我仍然不清楚异常的细节,特别是为什么问题只出现在未装箱的值而不是装箱的值中。


事件日志详细信息

根据此评论,我将在类型表达式上打开可视化工具时创建的事件日志中包含数据int

Log Name:      Application
Source:        Application Error
Date:          22/04/2021 12:14:36
Event ID:      1000
Task Category: (100)
Level:         Error
Keywords:      Classic
User:          N/A
Computer:      LAPTOP-7O43T4OO
Description:
Faulting application name: TestNoRef.exe, version: 1.0.0.0, time stamp: 0xd9f9e12d
Faulting module name: clr.dll, version: 4.8.4341.0, time stamp: 0x6023024f
Exception code: 0xc0000409
Fault offset: 0x00574845
Faulting process ID: 0x94c4
Faulting application start time: 0x01d73757c33e87c0
Faulting application path: ***********
Faulting module path: C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll
Report ID: 1dcf070b-71ff-4279-be71-822698cc6168
Faulting package full name: 
Faulting package-relative application ID: 
Event Xml:
<Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event">
  <System>
    <Provider Name="Application Error" />
    <EventID Qualifiers="0">1000</EventID>
    <Version>0</Version>
    <Level>2</Level>
    <Task>100</Task>
    <Opcode>0</Opcode>
    <Keywords>0x80000000000000</Keywords>
    <TimeCreated SystemTime="2021-04-22T09:14:36.4507272Z" />
    <EventRecordID>1180760705</EventRecordID>
    <Correlation />
    <Execution ProcessID="0" ThreadID="0" />
    <Channel>Application</Channel>
    <Computer>LAPTOP-7O43T4OO</Computer>
    <Security />
  </System>
  <EventData>
    <Data>TestNoRef.exe</Data>
    <Data>1.0.0.0</Data>
    <Data>d9f9e12d</Data>
    <Data>clr.dll</Data>
    <Data>4.8.4341.0</Data>
    <Data>6023024f</Data>
    <Data>c0000409</Data>
    <Data>00574845</Data>
    <Data>94c4</Data>
    <Data>01d73757c33e87c0</Data>
    <Data>***********</Data>
    <Data>C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll</Data>
    <Data>1dcf070b-71ff-4279-be71-822698cc6168</Data>
    <Data>
    </Data>
    <Data>
    </Data>
  </EventData>
</Event>
Run Code Online (Sandbox Code Playgroud)

小智 2

我找不到合适的解决方案。这可能只是最新版本之一中引入的错误,到目前为止,没有人遇到过值类型的此问题。事实上,我尝试过

DateTime dte = DateTime.UtcNow;
ValueType vt = dte;
Run Code Online (Sandbox Code Playgroud)

同样,它确实可以与 一起使用vt,但不能与dte. 我向 net48 添加了一个明确的目标以防万一,但它没有改变任何东西。

我能想出的最好办法是一种与我猜测 Zev Spitz 使用的解决方法非常相似的解决方法,但尽量不要仅仅为了获取目标值而浪费 GetData 覆盖。恐怕这不是一个很好的解决方案。

如果您想要使用 GetData 检索不同的值,但它将在 DialogDebuggerVisualizer.Show 重写中使用,则可以在调用 GetData 时将值存储在 VisualizerObjectSource 对象中,并在调用 TransferData 时检索它,而无需实际传输它从被调试者到调试者。

 public class VisualizerObjectSource : Microsoft.VisualStudio.DebuggerVisualizers.VisualizerObjectSource 
    {
       /*static*/ DateTime? _lastDatetime=null;
        public override void TransferData(object target, Stream incomingData, Stream outgoingData) 
        {
            target = _lastDatetime;

            //Calculate here the output value        
            object toSerialize = " is null = " + (target==null).ToString();
       
            Serialize(outgoingData, toSerialize);
        }

        public override void GetData(object target, Stream outgoingData)
        {
            _lastDatetime = (DateTime)target;
            
            //Calculate here what you want to be returned by GetData
            base.GetData(" The stuff you want to return ", outgoingData);
        }   

    }
Run Code Online (Sandbox Code Playgroud)

在调试器端,确保在调用 TransferObject() 之前调用 GetObject/GetData


       protected override void Show(IDialogVisualizerService windowService, 
IVisualizerObjectProvider objectProvider)
        {            
            object MyCustomStuff =objectProvider.GetObject();
            var response = objectProvider.TransferObject(5);

           //[...]          

             string msg =  response .ToString();

            MessageBox.Show(msg);
        }

Run Code Online (Sandbox Code Playgroud)