使用自定义序列化器构建 Visual Studio 调试器可视化工具





0/5 (0投票)
对于几乎任何可序列化的对象,创建 DebuggerVisualizer 都非常简单,并且有很多示例。然而,如果您要为不可序列化或序列化和反序列化耗时过长的对象创建 DebuggerVisualizer,情况就没那么简单了。

引言
DebuggerVisualizers 允许您在 Visual Studio 调试时自定义对象的显示方式。这对于大量的应用程序都很有用。例如:
- 构建复杂对象的简化模型。
- 将由多种基本类型组成的对象的渲染成人类可读的形式。
- 以一种更有组织、更容易访问的方式呈现调试数据。
在我们的 DotImage Toolkit 中,我们提供了一个 AtalaImage 对象,其中存储了图像数据本身。使用 DebuggerVisualizer 可以在调试时即时地以视觉方式检查该图像。正如您所料,这比在代码中注入语句来写入中间图像或尝试解释图像内存的原始内容要容易得多。
对于大多数对象,构建 Visual Studio Debugger Visualizers 的过程非常直接。然而,对于未实现 ISerializable、序列化速度过慢或其序列化形式不包含您希望看到的所有信息的对象,有必要将自定义序列化器与 Debugger Visualizer 一起打包。
需要考虑的因素
- 使用自定义序列化器的 Visualizer 需要能够引用正在被自定义序列化的对象的程序集。
- 使用自定义序列化器的 Visualizer 依赖于 Visualizer、序列化器和当前项目都引用相同版本的程序集。
- 如果序列化或反序列化耗时过长,Visualizers 将会超时。
- Visualizers 仅适用于 Visual Studio 2005 及更高版本。
代码
在构建具有自定义序列化功能的 Visualizer 时,主要涉及四个组件:自定义序列化器、传输对象、Visualizer/反序列化器和数据查看器。
自定义序列化器 (AtalaImageSerializer.cs)
自定义序列化器允许您以任何您喜欢的方式定义对象的序列化,即使它已经可序列化。它会在被调试端实例化并运行,因此可以访问整个调试环境。实现自定义序列化器就像在 VisualizerObjectSource 中重写 GetData 方法一样简单。
默认情况下,我们将 AtalaImages 序列化为 PNG 格式。PNG 格式在节省空间方面效果很好,但编码和解码速度太慢,导致我们的 DebuggerVisualizer 在处理大型图像时超时。为了解决这个问题,我们构建了一个自定义序列化器,将其输出到 BMP 格式。使用 BMP 的一个额外好处是它与 System.Windows.Forms.PictureBox 兼容,使我们无需在 Debugger 端加载我们的程序集。
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using Atalasoft.Imaging;
namespace AtalaImageDebuggerVisualizer
{
class AtalaImageSerializer :
Microsoft.VisualStudio.DebuggerVisualizers.VisualizerObjectSource
{
public override void GetData(object inObject, Stream outStream)
{
if (inObject != null && inObject is AtalaImage)
{
AtalaImage atalaImage = inObject as AtalaImage;
Bitmap bmp = atalaImage.ToBitmap();
AtalaImageTransporter transporter =
new AtalaImageTransporter(bmp, atalaImage.PixelFormat.ToString());
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(outStream, transporter);
}
}
}
}
inObject
是您要可视化的对象,outStream
是您与 Visualizer 的数据连接。outStream
只是一个普通流,因此您可以将任何您想要的内容放入其中。在这种情况下,我使用了一个传输对象来打包我需要的所有信息,以便在 Visualizer 中方便且易于阅读。
Visual Studio 如果您在序列化(或反序列化)上花费的时间过长,将会超时,因此序列化的速度至关重要。如果您遇到超时问题,您可能想查看 Code Project 上关于加速序列化的以下文章:SimmoTech 的 Optimizing Serialization in .NET。
传输对象 (AtalaImageTransporter.cs)
传输对象的任务是将我想在 Visualizer 中访问的所有信息打包成一个整洁的包。将所有信息放在一个对象中也使得序列化和反序列化更加容易。
using System;
using System.Drawing;
using System.Runtime.Serialization;
namespace AtalaImageDebuggerVisualizer
{
[Serializable]
public class AtalaImageTransporter
{
public AtalaImageTransporter(Image pic, string pf)
{
Picture = pic;
PixelFormat = pf;
}
Image _picture;
public Image Picture
{
get { return _picture; }
set { _picture = value; }
}
string _pixelformat;
public string PixelFormat
{
get { return _pixelformat; }
set { _pixelformat = value; }
}
}
}
就我而言,字符串和图像的默认序列化方法效果很好。如果您想要更精细地控制数据的序列化,您应该让您的传输对象实现 ISerializable。
Visualizer/反序列化器 (AtalaImageVisualizer.cs)
Visualizer 接收您的序列化数据,并负责对其进行反序列化并将其呈现给用户。它在调试器端被实例化并运行,因此只能访问 objectProvider
中提供的数据。与自定义序列化器一样,实现 DebuggerVisualizer 所需的全部内容就是继承一个父类并重写一个方法。在这种情况下,类是 DialogDebuggerVisualizer,方法是 Show。
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using Atalasoft.Imaging;
using Microsoft.VisualStudio.DebuggerVisualizers;
namespace AtalaImageDebuggerVisualizer
{
public class AtalaImageVisualizer : Microsoft.VisualStudio.
DebuggerVisualizers.DialogDebuggerVisualizer
{
protected override void Show(Microsoft.VisualStudio.
DebuggerVisualizers.IDialogVisualizerService windowService,
Microsoft.VisualStudio.DebuggerVisualizers.
IVisualizerObjectProvider objectProvider)
{
Stream stm = objectProvider.GetData();
if (stm.Length != 0)
{
BinaryFormatter bf = new BinaryFormatter();
AtalaImageTransporter imageData =
bf.Deserialize(stm) as AtalaImageTransporter;
if (imageData != null)
{
AtalaImageViewer view = new AtalaImageViewer(imageData);
view.ShowDialog();
}
}
}
}
}
IVisualizerObjectProvider
提供了一个名为 GetObject
的方法,该方法直接返回一个对象。但是,根据您的数据序列化方式,直接反序列化为对象可能不起作用,因此从流中反序列化更安全。
数据查看器 (AtalaImageViewer.cs)
数据查看器只是一个用于向用户显示可视化数据的简单窗体。使用窗体并非绝对必要,但这是标准的做法。
using System;
using System.Drawing;
using System.Windows.Forms;
namespace AtalaImageDebuggerVisualizer
{
public partial class AtalaImageViewer : Form
{
public AtalaImageViewer()
{
InitializeComponent();
}
public AtalaImageViewer( AtalaImageTransporter imageData )
{
InitializeComponent();
PicViewer.Image = imageData.Picture;
PixelFormatLabel.Text = imageData.PixelFormat;
PicViewer.SizeMode = PictureBoxSizeMode.Zoom;
this.Width = imageData.Picture.Width / 2;
this.Height = imageData.Picture.Height / 2;
}
}
}
程序集描述 (AssemblyInfo.cs)
DebuggerVisualizerAttribute 描述了 Visual Studio 如何使用该 Visualizer。
[assembly: System.Diagnostics.DebuggerVisualizer(
typeof(AtalaImageDebuggerVisualizer.AtalaImageVisualizer),
typeof(AtalaImageDebuggerVisualizer.AtalaImageSerializer),
Target = typeof(Atalasoft.Imaging.AtalaImage),
Description = "Atalasoft Image Visualizer" )]
格式为
[assembly: System.Diagnostics.DebuggerVisualizer(
typeof( Your Visualizer ),
typeof( Your Custom Serializer ),
Target = typeof( Your Visualized Object ),
Description = "Description Here" )]
替换被可视化对象
如果您想在 Visualizer 中更改您的对象并将其注入到正在调试的程序中,您还需要做两件事。
- Visualizer 中的 IVisualizerObjectProvider 提供了用于发送数据的
ReplaceStream
方法。您需要序列化并使用此方法将您的传输对象发送回。 - 在您的自定义序列化器中,您需要重写 VisualizerObjectSource 的 CreateReplacementObject 方法。您需要反序列化您的传输对象,构建一个被可视化对象的替换项,并将其分配给目标参数。
结论
本文档绝不包含 DebuggerVisualizers 或自定义序列化的所有细节。如果您正在构建一个,我鼓励您访问 MSDN 文档,它将提供更深入的理解。
关于 Atalasoft
Atalasoft, Inc. 是 .NET 开发工具包的领先供应商,为需要将企业内容管理成像 (ECMi) 添加到其解决方案中的 ISV、集成商和企业提供服务。Atalasoft 的产品在金融服务、法律、医疗保健和制造业等许多行业中使用,这些行业对分布式捕获、零足迹 Web 查看和文档标记有需求。