// Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using MemoryStreamObjectSource; using Microsoft.VisualStudio.Extensibility.DebuggerVisualizers; using Microsoft.VisualStudio.Extensibility.UI; using Microsoft.VisualStudio.RpcContracts.DebuggerVisualizers; using System; using System.Buffers; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; namespace MemoryStreamVisualizer; /// /// Remote UI user control for the MemoryStreamVisualizer. /// internal class MemoryStreamVisualizerUserControl : RemoteUserControl { private readonly VisualizerTarget visualizerTarget; private readonly MemoryStreamData dataContext; /// /// Initializes a new instance of the class. /// /// The visualizer target to be used to retrieve data from. public MemoryStreamVisualizerUserControl(VisualizerTarget visualizerTarget) : base(new MemoryStreamData()) { visualizerTarget.Changed += this.VisualizerTargetStateChangedAsync; this.dataContext = (MemoryStreamData)this.DataContext!; this.visualizerTarget = visualizerTarget; } private Task VisualizerTargetStateChangedAsync(VisualizerTargetStateNotification args) { if (args == VisualizerTargetStateNotification.Available || args == VisualizerTargetStateNotification.ValueUpdated) { this.dataContext.Data.Clear(); this.dataContext.Position = 0; this.dataContext.Length = 0; this.dataContext.LoadingVisibility = Visibility.Visible; return this.RetrieveDataAsync(); } return Task.CompletedTask; } private async Task RetrieveDataAsync() { ReadOnlySequence data; do { using MemoryStream memoryStream = new(sizeof(int)); using BinaryWriter binaryWriter = new(memoryStream); int index = this.dataContext.Data.Count * MemoryStreamVisualizerObjectSource.RowLength; if (index >= 1024 * 1024) { break; // Let's not retrieve more than 1MB of data } binaryWriter.Write(index); binaryWriter.Flush(); try { data = (await this.visualizerTarget.ObjectSource.RequestDataAsync(new ReadOnlySequence(memoryStream.ToArray()), CancellationToken.None)).Value; } #pragma warning disable CA1031 // Do not catch general exception types catch (Exception) #pragma warning restore CA1031 // Do not catch general exception types { // I can get an exception if the debug session is unpaused, so I need to handle it gracefully break; } } while (data.Length > 0 && this.Read(data)); this.dataContext.LoadingVisibility = Visibility.Hidden; } private bool Read(ReadOnlySequence data) { int byteInRowCount = 0; StringBuilder binaryText = new(); StringBuilder asciiText = new(); SequenceReader reader = new(data); if (!reader.TryReadLittleEndian(out long position) || !reader.TryReadLittleEndian(out long length)) { return false; } this.dataContext.Position = position; this.dataContext.Length = length; if (reader.UnreadSpan.Length == 0) { return false; // We always receive data unless we are at the end of the MemoryStream } List rows = new(MemoryStreamVisualizerObjectSource.RowsCountPerRequest); byte[] tmp = new byte[1]; while (reader.TryRead(out byte b)) { byteInRowCount++; if (byteInRowCount > 1) { binaryText.Append(' '); } binaryText.Append(b.ToString("X2", CultureInfo.InvariantCulture)); tmp[0] = b; asciiText.Append(char.IsControl((char)b) || b == 0xAD ? '•' : Encoding.Latin1.GetChars(tmp)[0]); if (byteInRowCount == MemoryStreamVisualizerObjectSource.RowLength) { CompleteRow(); } } if (byteInRowCount > 0) { CompleteRow(); this.dataContext.Data.AddRange(rows); return false; // We only receive partial rows at the end of the MemoryStream } this.dataContext.Data.AddRange(rows); return true; void CompleteRow() { rows.Add(new HexEditorRow( index: (this.dataContext.Data.Count + rows.Count) * MemoryStreamVisualizerObjectSource.RowLength, data: binaryText.ToString(), ascii: asciiText.ToString())); byteInRowCount = 0; binaryText.Clear(); asciiText.Clear(); } } }