다음을 통해 공유


Visual Studio 디버거 시각화 도우미 만들기

디버거 시각화 도우미는 디버그 세션 중에 특정 .NET 형식의 변수 또는 개체에 대한 사용자 지정 시각화를 제공하는 Visual Studio 기능입니다.

디버거 시각화 도우미는 변수를 마우스로 가리킬 때 나타나는 DataTip 또는 Autos, LocalsWatch 창에서 액세스할 수 있습니다.

관찰 창의 디버거 시각화 도구 화면 캡처

시작하기

시작 섹션 의 확장 프로젝트 만들기 섹션을 따릅니다.

그런 다음 DebuggerVisualizerProvider을 확장하는 클래스를 만들고, VisualStudioContribution 특성을 적용합니다.

/// <summary>
/// Debugger visualizer provider class for <see cref="System.String"/>.
/// </summary>
[VisualStudioContribution]
internal class StringDebuggerVisualizerProvider : DebuggerVisualizerProvider
{
    /// <summary>
    /// Initializes a new instance of the <see cref="StringDebuggerVisualizerProvider"/> class.
    /// </summary>
    /// <param name="extension">Extension instance.</param>
    /// <param name="extensibility">Extensibility object.</param>
    public StringDebuggerVisualizerProvider(StringDebuggerVisualizerExtension extension, VisualStudioExtensibility extensibility)
        : base(extension, extensibility)
    {
    }

    /// <inheritdoc/>
    public override DebuggerVisualizerProviderConfiguration DebuggerVisualizerProviderConfiguration => new("My string visualizer", typeof(string));

    /// <inheritdoc/>
    public override async Task<IRemoteUserControl> CreateVisualizerAsync(VisualizerTarget visualizerTarget, CancellationToken cancellationToken)
    {
        string targetObjectValue = await visualizerTarget.ObjectSource.RequestDataAsync<string>(jsonSerializer: null, cancellationToken);

        return new MyStringVisualizerControl(targetObjectValue);
    }
}

이전 코드는 형식 string의 개체에 적용되는 새 디버거 시각화 도우미를 정의합니다.

  • 이 속성은 DebuggerVisualizerProviderConfiguration 시각화 도우미 표시 이름과 지원되는 .NET 형식을 정의합니다.
  • CreateVisualizerAsync 이 메서드는 사용자가 특정 값에 대한 디버거 시각화 도우미의 표시를 요청할 때 Visual Studio에서 호출됩니다. CreateVisualizerAsync에서는 VisualizerTarget 개체를 사용하여 시각화할 값을 가져와서 원격 사용자 정의 컨트롤에 전달합니다(원격 UI 설명서 참조). 그런 다음 원격 사용자 컨트롤이 반환되고 Visual Studio의 팝업 창에 표시됩니다.

여러 형식 대상 지정

구성 속성을 사용하면 시각화 도우미가 편리한 경우 여러 형식을 대상으로 지정할 수 있습니다. 이것의 완벽한 예는 DataSet Visualizer로, DataSet, DataTable, DataView, DataViewManager 객체의 시각화를 지원합니다. 이 기능은 유사한 형식이 동일한 UI, 뷰 모델 및 시각화 도우미 개체 원본을 공유할 수 있으므로 확장 개발을 용이하게 합니다.

    /// <inheritdoc/>
    public override DebuggerVisualizerProviderConfiguration DebuggerVisualizerProviderConfiguration => new DebuggerVisualizerProviderConfiguration(
        new VisualizerTargetType("DataSet Visualizer", typeof(System.Data.DataSet)),
        new VisualizerTargetType("DataTable Visualizer", typeof(System.Data.DataTable)),
        new VisualizerTargetType("DataView Visualizer", typeof(System.Data.DataView)),
        new VisualizerTargetType("DataViewManager Visualizer", typeof(System.Data.DataViewManager)));

    /// <inheritdoc/>
    public override async Task<IRemoteUserControl> CreateVisualizerAsync(VisualizerTarget visualizerTarget, CancellationToken cancellationToken)
    {
        ...
    }

시각화 도구 개체 소스

시각화 도우미 개체 원본은 디버그 중인 프로세스에서 디버거에 의해 로드되는 .NET 클래스입니다. 디버거 시각화 도우미는 VisualizerTarget.ObjectSource에 의해 노출된 메서드를 통해 시각화 도우미 개체 원본에서 데이터를 검색할 수 있습니다.

기본 시각화 도우미 개체 원본을 사용하면 디버거 시각화 도우미가 메서드를 호출 RequestDataAsync<T>(JsonSerializer?, CancellationToken) 하여 시각화할 개체의 값을 검색할 수 있습니다. 기본 시각화 도우미 개체 원본은 Newtonsoft.Json 을 사용하여 값을 직렬화하고 VisualStudio.Extensibility 라이브러리는 역직렬화에 Newtonsoft.Json도 사용합니다. 또는 직렬화된 값을 JToken로 조회할 수 있는 RequestDataAsync(CancellationToken)을 사용할 수 있습니다.

Newtonsoft.Json에서 기본적으로 지원되는 .NET 형식을 시각화하거나 고유한 형식을 시각화하고 직렬화할 수 있도록 하려는 경우 이전 지침은 간단한 디버거 시각화 도우미를 만들기에 충분합니다. 더 복잡한 형식을 지원하거나 고급 기능을 사용하려면 계속 읽어보세요.

사용자 지정 시각화 도우미 개체 원본 사용

Newtonsoft.Json에서 시각화할 형식을 자동으로 serialize할 수 없는 경우 serialization을 처리하는 사용자 지정 시각화 도우미 개체 원본을 만들 수 있습니다.

  • 대상을 지정하는 새 .NET 클래스 라이브러리 프로젝트를 만듭니다 netstandard2.0. 필요한 경우, 시각화할 개체를 직렬화하려면 보다 구체적인 버전의 .NET Framework 또는 .NET(net472 또는 net6.0 예:)을 대상으로 삼을 수 있습니다.
  • 버전 17.6 이상에 DebuggerVisualizers 패키지 참조를 추가합니다.
  • 클래스를 VisualizerObjectSource 확장하고 GetData 를 재정의하여 target 의 serialize된 값을 outgoingData 스트림에 씁니다.
public class MyObjectSource : VisualizerObjectSource
{
    /// <inheritdoc/>
    public override void GetData(object target, Stream outgoingData)
    {
        MySerializableType result = Convert(match);
        SerializeAsJson(outgoingData, result);
    }

    private static MySerializableType Convert(object target)
    {
        // Add your code here to convert target into a type serializable by Newtonsoft.Json
        ...
    }
}

사용자 지정 serialization 사용

이 메서드를 사용하여 VisualizerObjectSource.SerializeAsJson 개체를 Stream 형태로 Serialize할 수 있으며, 이를 위해 라이브러리에 Newtonsoft.Json의 참조를 추가할 필요가 없습니다. SerializeAsJson 호출은 리플렉션을 통해 Newtonsoft.Json 어셈블리의 버전을 디버그 중인 프로세스에 로드합니다.

Newtonsoft.Json을 참조해야 하는 경우에는 패키지에서 참조하는 Microsoft.VisualStudio.Extensibility.Sdk 것과 동일한 버전을 사용하는 것이 좋습니다. 하지만 DataContractDataMember 특성을 사용하여 개체 직렬화를 지원하는 것이 Newtonsoft.Json 형식에 의존하는 것보다 바람직합니다.

또는 outgoingData에 직접 쓰는 사용자 지정 serialization(예: 이진 serialization)을 구현할 수 있습니다.

확장에 시각화 도우미 개체 원본 DLL 추가

확장이 패키지되기 전에 시각화 도우미 개체 원본 라이브러리가 빌드되도록 ProjectReference을 시각화 도우미 개체 원본 라이브러리 프로젝트에 추가하는 .csproj 파일을 수정합니다.

확장의 netstandard2.0 하위 폴더에 소스 라이브러리 DLL이 포함된 시각화 개체 항목을 Content 추가하세요.

  <ItemGroup>
    <Content Include="pathToTheObjectSourceDllBinPath\$(Configuration)\netstandard2.0\MyObjectSourceLibrary.dll" Link="netstandard2.0\MyObjectSourceLibrary.dll">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\MyObjectSourceLibrary\MyObjectSourceLibrary.csproj" />
  </ItemGroup>

.NET Framework 또는 .NET을 대상으로 하는 시각화 도우미 개체 원본 라이브러리를 빌드한 경우, net4.6.2 또는 netcoreapp 하위 폴더를 사용할 수 있습니다. 시각화 도우미 개체 원본 라이브러리의 버전이 서로 다른 세 하위 폴더를 모두 포함할 수도 있지만 대상으로 netstandard2.0 만 지정하는 것이 좋습니다.

시각화 도우미 개체 원본 라이브러리 DLL의 종속성 수를 최소화해야 합니다. 시각화 도우미 개체 원본 라이브러리에 Microsoft.VisualStudio.DebuggerVisualizers 및 디버그 중인 프로세스에서 이미 로드되도록 보장된 라이브러리 이외의 종속성이 있는 경우 해당 DLL 파일도 시각화 도우미 개체 원본 라이브러리 DLL과 동일한 하위 폴더에 포함해야 합니다.

사용자 지정 시각화 도우미 개체 원본을 사용하도록 디버거 시각화 도우미 공급자 업데이트

사용자 지정 뷰어 개체 소스를 참조하도록 DebuggerVisualizerProvider 구성을 업데이트할 수 있습니다.

    public override DebuggerVisualizerProviderConfiguration DebuggerVisualizerProviderConfiguration => new("My visualizer", typeof(TypeToVisualize))
    {
        VisualizerObjectSourceType = new(typeof(MyObjectSource)),
    };

    public override async Task<IRemoteUserControl> CreateVisualizerAsync(VisualizerTarget visualizerTarget, CancellationToken cancellationToken)
    {
        MySerializableType result = await visualizerTarget.ObjectSource.RequestDataAsync<MySerializableType>(jsonSerializer: null, cancellationToken);
        return new MyVisualizerUserControl(result);
    }

크고 복잡한 개체와 작업하다

시각화 도우미 개체 원본에서 데이터 검색을 단일 매개 변수 없는 호출 RequestDataAsync로 수행할 수 없는 경우 시각화 도우미 개체 원본을 여러 번 호출 RequestDataAsync<TMessage, TResponse>(TMessage, JsonSerializer?, CancellationToken) 하고 다른 메시지를 시각화 도우미 개체 원본으로 전송하여 보다 복잡한 메시지 교환을 수행할 수 있습니다. 메시지와 응답은 Newtonsoft.Json을 사용하여 VisualStudio.Extensibility 인프라에 의해 직렬화됩니다. 사용자 정의 재정의 RequestDataAsync 기능을 통해 JToken 객체를 사용하거나 사용자 지정 직렬화 및 역직렬화를 구현할 수 있습니다.

다른 메시지를 사용하여 시각화 도우미 개체 원본에서 정보를 검색하는 사용자 지정 프로토콜을 구현할 수 있습니다. 이 기능의 가장 일반적인 사용 사례는 잠재적으로 큰 개체의 검색을 여러 호출로 분리하여 시간 초과를 방지하는 RequestDataAsync 것입니다.

이는 잠재적으로 큰 컬렉션의 콘텐츠를 한 번에 하나의 항목으로 검색하는 방법의 예입니다.

for (int i = 0; ; i++)
{
    MySerializableType? collectionEntry = await visualizerTarget.ObjectSource.RequestDataAsync<int, MySerializableType?>(i, jsonSerializer: null, cancellationToken);
    if (collectionEntry is null)
    {
        break;
    }

    observableCollection.Add(collectionEntry);
}

위의 코드는 호출에 대한 메시지로 간단한 인덱스 사용 RequestDataAsync 해당 visualizer 객체의 소스 코드는 GetData 대신 TransferData 메서드를 재정의합니다.

public class MyCollectionTypeObjectSource : VisualizerObjectSource
{
    public override void TransferData(object target, Stream incomingData, Stream outgoingData)
    {
        var index = (int)DeserializeFromJson(incomingData, typeof(int))!;

        if (target is MyCollectionType collection && index < collection.Count)
        {
            var result = Convert(collection[index]);
            SerializeAsJson(outgoingData, result);
        }
        else
        {
            SerializeAsJson(outgoingData, null);
        }
    }

    private static MySerializableType Convert(object target)
    {
        // Add your code here to convert target into a type serializable by Newtonsoft.Json
        ...
    }
}

위의 시각화 도우미 개체 원본은 메서드를 VisualizerObjectSource.DeserializeFromJson 활용하여 시각화 도우미 공급자 incomingData가 보낸 메시지를 역직렬화합니다.

시각화 도우미 개체 원본과 복잡한 메시지 상호 작용을 수행하는 디버거 시각화 도우미 공급자를 구현하는 경우 일반적으로 컨트롤이 로드되는 동안 메시지 교환이 비동기적으로 발생할 수 있도록 시각화 도우미 VisualizerTarget 에 전달하는 RemoteUserControl 것이 좋습니다. 또한 이를 VisualizerTarget 전달하면 시각화 도우미 개체 원본에 메시지를 보내 시각화 도우미의 UI와 사용자의 상호 작용에 따라 데이터를 검색할 수 있습니다.

public override Task<IRemoteUserControl> CreateVisualizerAsync(VisualizerTarget visualizerTarget, CancellationToken cancellationToken)
{
    return Task.FromResult<IRemoteUserControl>(new MyVisualizerUserControl(visualizerTarget));
}
internal class MyVisualizerUserControl : RemoteUserControl
{
    private readonly VisualizerTarget visualizerTarget;

    public MyVisualizerUserControl(VisualizerTarget visualizerTarget)
        : base(new MyDataContext())
    {
        this.visualizerTarget = visualizerTarget;
    }

    public override async Task ControlLoadedAsync(CancellationToken cancellationToken)
    {
        // Start querying the VisualizerTarget here
        ...
    }
    ...

도구 창으로 시각화 도우미 열기

기본적으로 모든 디버거 시각화 도우미 확장은 Visual Studio 전경에서 모달 대화 상자 창으로 열립니다. 따라서 사용자가 IDE와 계속 상호 작용하려는 경우 시각화 도우미를 닫아야 합니다. Style 속성이 DebuggerVisualizerProviderConfiguration 속성에 ToolWindow으로 설정된 경우, 시각화 도구는 디버그 세션의 나머지 동안 열어둘 수 있는 모달이 아닌 도구 창으로 열립니다. 선언된 스타일이 없으면 기본값 ModalDialog 이 사용됩니다.

    public override DebuggerVisualizerProviderConfiguration DebuggerVisualizerProviderConfiguration => new("My visualizer", typeof(TypeToVisualize))
    {
        Style = VisualizerStyle.ToolWindow
    };

    public override async Task<IRemoteUserControl> CreateVisualizerAsync(VisualizerTarget visualizerTarget, CancellationToken cancellationToken)
    {
        // The control will be in charge of calling the RequestDataAsync method from the visualizer object source and disposing of the visualizer target.
        return new MyVisualizerUserControl(visualizerTarget);
    }

시각화 도우미를 ToolWindow로 열도록 선택할 때마다, VisualizerTargetStateChanged 이벤트를 구독해야 합니다. 시각화 도우미를 도구 창으로 열면 사용자가 디버그 세션의 일시 중지를 차단하지 않습니다. 따라서 앞서 언급한 이벤트는 디버그 대상의 상태가 변경될 때마다 디버거에 의해 발생합니다. 시각화 도우미 확장 작성자는 디버그 세션이 활성 상태이고 디버그 대상이 일시 중지된 경우에만 시각화 도우미 대상을 사용할 수 있기 때문에 이러한 알림에 특히 주의해야 합니다. 시각화 대상이 없으면 ObjectSource 메서드 호출이 VisualizerTargetUnavailableException 오류와 함께 실패합니다.

internal class MyVisualizerUserControl : RemoteUserControl
{
    private readonly VisualizerDataContext dataContext;

#pragma warning disable CA2000 // Dispose objects before losing scope
    public MyVisualizerUserControl(VisualizerTarget visualizerTarget)
        : base(dataContext: new VisualizerDataContext(visualizerTarget))
#pragma warning restore CA2000 // Dispose objects before losing scope
    {
        this.dataContext = (VisualizerDataContext)this.DataContext!;
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            this.dataContext.Dispose();
        }
    }

    [DataContract]
    private class VisualizerDataContext : NotifyPropertyChangedObject, IDisposable
    {
        private readonly VisualizerTarget visualizerTarget;
        private MySerializableType? _value;
        
        public VisualizerDataContext(VisualizerTarget visualizerTarget)
        {
            this.visualizerTarget = visualizerTarget;
            visualizerTarget.StateChanged += this.OnStateChangedAsync;
        }

        [DataMember]
        public MySerializableType? Value
        {
            get => this._value;
            set => this.SetProperty(ref this._value, value);
        }

        public void Dispose()
        {
            this.visualizerTarget.Dispose();
        }

        private async Task OnStateChangedAsync(object? sender, VisualizerTargetStateNotification args)
        {
            switch (args)
            {
                case VisualizerTargetStateNotification.Available:
                case VisualizerTargetStateNotification.ValueUpdated:
                    Value = await visualizerTarget.ObjectSource.RequestDataAsync<MySerializableType>(jsonSerializer: null, CancellationToken.None);
                    break;
                case VisualizerTargetStateNotification.Unavailable:
                    Value = null;
                    break;
                default:
                    throw new NotSupportedException("Unexpected visualizer target state notification");
            }
        }
    }
}

알림은 RemoteUserControl이 생성된 후, 새로 만든 시각화 도구 창에 표시되기 직전에 Available를 통해 수신됩니다. 시각화 도우미가 열려 있는 한 디버그 대상이 상태를 변경할 때마다 다른 VisualizerTargetStateNotification 값을 받을 수 있습니다. 이 ValueUpdated 알림은 디버거가 멈춘 지점에서 시각화 도구로 연 마지막 식이 성공적으로 다시 평가되었으며 UI에서 새로 고쳐야 함을 나타냅니다. 반면에 디버그 대상이 다시 시작되거나 중지 후 식을 다시 평가할 수 없을 때마다 알림이 Unavailable 수신됩니다.

시각화된 개체 값 업데이트

true이면 VisualizerTarget.IsTargetReplaceable 디버거 시각화 도우미는 ReplaceTargetObjectAsync 이 메서드를 사용하여 디버그 중인 프로세스에서 시각화된 개체의 값을 업데이트할 수 있습니다.

시각화기 개체는 CreateReplacementObject 메서드를 재정의해야 합니다.

public override object CreateReplacementObject(object target, Stream incomingData)
{
    // Use DeserializeFromJson to read from incomingData
    // the new value of the object being visualized
    ...
    return newValue;
}

샘플에서 RegexMatchDebugVisualizer 이러한 기법의 작동을 확인해 보세요.