如何为Visual Studio 2012调试器编写自定义本机可视化工具DLL

How to write a custom native visualizer DLL for Visual Studio 2012 debugger?

本文关键字:自定义 本机 可视化 DLL 工具 调试器 Visual Studio 2012      更新时间:2023-10-16

在C++中为Visual Studio 2012调试器编写自定义本机可视化工具DLL需要什么?我想显示一个只能根据类/结构按需计算的值,因此需要一个本机可视化工具DLL。Visual Studio 2012使用一种称为Natvis的新方法来实现本机可视化工具。到目前为止,关于Natvis的正确信息很少,尤其是关于使用Natvis调用可视化工具DLL的信息。DLL将根据类/结构成员值计算显示字符串。

这是包含AddIn DLL的C++代码。我将文件命名为NatvisAddIn.cpp,项目创建了NatvisAddIn.dll。

#include "stdafx.h"
#include <iostream>
#include <windows.h>
#define ADDIN_API __declspec(dllexport)
typedef struct tagDEBUGHELPER
{
    DWORD dwVersion;
    HRESULT (WINAPI *ReadDebuggeeMemory)( struct tagDEBUGHELPER *pThis, DWORD dwAddr, DWORD nWant, VOID* pWhere, DWORD *nGot );
    // from here only when dwVersion >= 0x20000
    DWORDLONG (WINAPI *GetRealAddress)( struct tagDEBUGHELPER *pThis );
    HRESULT (WINAPI *ReadDebuggeeMemoryEx)( struct tagDEBUGHELPER *pThis, DWORDLONG qwAddr, DWORD nWant, VOID* pWhere, DWORD *nGot );
    int (WINAPI *GetProcessorType)( struct tagDEBUGHELPER *pThis );
} DEBUGHELPER;
typedef HRESULT (WINAPI *CUSTOMVIEWER)( DWORD dwAddress, DEBUGHELPER *pHelper, int nBase, BOOL bUniStrings, char *pResult, size_t max, DWORD reserved );
extern "C" ADDIN_API HRESULT MyClassFormatter( DWORD dwAddress, DEBUGHELPER *pHelper, int nBase, BOOL bUniStrings, char *pResult, size_t max, DWORD reserved );
extern "C" ADDIN_API HRESULT MyStructFormatter( DWORD dwAddress, DEBUGHELPER *pHelper, int nBase, BOOL bUniStrings, char *pResult, size_t max, DWORD reserved );
class MyClass
{
public:
    int publicInt;
};
struct MyStruct { int i; };
ADDIN_API HRESULT MyClassFormatter( DWORD dwAddress, DEBUGHELPER *pHelper, int nBase, BOOL bUniStrings, char *pResult, size_t max, DWORD reserved )
{
    MyClass c;
    DWORD nGot;
    pHelper->ReadDebuggeeMemory(pHelper,dwAddress,sizeof(MyClass),&c,&nGot);
    sprintf_s(pResult,max,"Dll MyClass: max=%d nGot=%d MyClass=%x publicInt=%d",max,nGot,dwAddress,c.publicInt);
    return S_OK;
}
ADDIN_API HRESULT MyStructFormatter( DWORD dwAddress, DEBUGHELPER *pHelper, int nBase, BOOL bUniStrings, char *pResult, size_t max, DWORD reserved )
{
    MyStruct s;
    DWORD nGot;
    pHelper->ReadDebuggeeMemory(pHelper,dwAddress,sizeof(MyStruct),&s,&nGot);
    sprintf_s(pResult,max,"Dll MyStruct: max=%d nGot=%d MyStruct=%x i=%d",max,nGot,dwAddress,s.i);
    return S_OK;
}

这是Visual Studio 2012调试器用来显示值的.natvis文件。将其放在.natvis文件中。我将其命名为NatvisAddIn.natvis。该文件指示VS 2012调试器调用NatvisAddIn.dll。该dll包含两个可视化工具方法调用;MyClassFormatter格式化MyClass,MyStructFormatter格式化MyStruct。调试器将在指定类型(MyClass、MyStruct)的每个实例的Auto、Watch或工具提示显示中显示方法的格式化值。

<?xml version="1.0" encoding="utf-8"?>
    <AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
    <Type Name="MyClass">
        <DisplayString LegacyAddin="NatvisAddIn.dll" Export="MyClassFormatter"></DisplayString>
    </Type>
    <Type Name="MyStruct">
        <DisplayString LegacyAddin="NatvisAddIn.dll" Export="MyStructFormatter"></DisplayString>
    </Type>
</AutoVisualizer>

将已编译的NatvisAddIn.dll文件和NatvisAddIn.natvis文件放在以下三个位置之一:

%VSINSTALLDIR%Common7PackagesDebuggerVisualizers (requires admin access)
%USERPROFILE%My DocumentsVisual Studio 2012Visualizers
VS extension folders

您需要确保以下注册表项存在,并且值为1:

[HKEYCURRENT_USER\Software\Microsoft\VisualStudio\11.0_Config\Degger]

"EnableNatvisDiagnostics"=dword:000000001

如果一切顺利,您将在VisualStudio的调试器"输出"窗口中看到natvis消息。这些消息将显示Natvis是否能够解析.Natvis文件。分析每个.natvis文件的结果显示在输出窗口中。如果出现问题,请使用命令"dumpbin/exports"仔细检查DLL方法的名称是否与.navis文件的Type=完全匹配。还要确保当前的.dll和.natvis文件已复制到相应的目录中。

Natvis: Parsing natvis xml file: C:Program Files (x86)Microsoft Visual Studio 11.0Common7PackagesDebuggerVisualizersatlmfc.natvis.
Natvis: Done parsing natvis xml file: C:Program Files (x86)Microsoft Visual Studio 11.0Common7PackagesDebuggerVisualizersatlmfc.natvis.
Natvis: Parsing natvis xml file: C:Program Files (x86)Microsoft Visual Studio 11.0Common7PackagesDebuggerVisualizersconcurrency.natvis.
Natvis: Done parsing natvis xml file: C:Program Files (x86)Microsoft Visual Studio 11.0Common7PackagesDebuggerVisualizersconcurrency.natvis.
Natvis: Parsing natvis xml file: C:Program Files (x86)Microsoft Visual Studio 11.0Common7PackagesDebuggerVisualizersNatvisAddIn.natvis.
Natvis: Done parsing natvis xml file: C:Program Files (x86)Microsoft Visual Studio 11.0Common7PackagesDebuggerVisualizersNatvisAddIn.natvis.
Natvis: Parsing natvis xml file: C:Program Files (x86)Microsoft Visual Studio 11.0Common7PackagesDebuggerVisualizersstl.natvis.
Natvis: Done parsing natvis xml file: C:Program Files (x86)Microsoft Visual Studio 11.0Common7PackagesDebuggerVisualizersstl.natvis.
Natvis: Parsing natvis xml file: C:Program Files (x86)Microsoft Visual Studio 11.0Common7PackagesDebuggerVisualizerswindows.natvis.
Natvis: Done parsing natvis xml file: C:Program Files (x86)Microsoft Visual Studio 11.0Common7PackagesDebuggerVisualizerswindows.natvis.
Natvis: Parsing natvis xml file: C:Program Files (x86)Microsoft Visual Studio 11.0Common7PackagesDebuggerVisualizerswinrt.natvis.
Natvis: Done parsing natvis xml file: C:Program Files (x86)Microsoft Visual Studio 11.0Common7PackagesDebuggerVisualizerswinrt.natvis.

测试程序:

#include "stdafx.h"
#include <iostream>
class MyClass
{
public:
    int publicInt;
};
struct MyStruct { int i; };
int _tmain(int argc, _TCHAR* argv[])
{
    struct MyStruct s = {1234};
    std::cout << s.i << std::endl;
    MyClass *c = new MyClass;
    c->publicInt = 1234;
    std::cout << c->publicInt << std::endl;
    return 0;
}

信息资源:

\Xml\Schemas\natvis.xsd

http://code.msdn.microsoft.com/windowsdesktop/Writing-type-visualizers-2eae77a2

http://blogs.msdn.com/b/mgoldin/archive/2012/06/06/visual-studio-2012-and-debugger-natvis-files-what-can-i-do-with-them.aspx

http://blogs.msdn.com/b/vcblog/archive/2012/07/12/10329460.aspx

对于64位版本调试,应使用以下行:

auto realAddress = pHelper->GetRealAddress(pHelper);
pHelper->ReadDebuggeeMemoryEx(pHelper, realAddress, sizeof(MyClass), &c, &nGot );

对于前面的例子,64位版本可能看起来像这样:

#include "stdafx.h"
#include <iostream>
#include <windows.h>
#define ADDIN_API __declspec(dllexport)
typedef struct tagDEBUGHELPER
{
    DWORD dwVersion;
    HRESULT (WINAPI *ReadDebuggeeMemory)( struct tagDEBUGHELPER *pThis, DWORD dwAddr, DWORD nWant, VOID* pWhere, DWORD *nGot );
    // from here only when dwVersion >= 0x20000
    DWORDLONG (WINAPI *GetRealAddress)( struct tagDEBUGHELPER *pThis );
    HRESULT (WINAPI *ReadDebuggeeMemoryEx)( struct tagDEBUGHELPER *pThis, DWORDLONG qwAddr, DWORD nWant, VOID* pWhere, DWORD *nGot );
    int (WINAPI *GetProcessorType)( struct tagDEBUGHELPER *pThis );
} DEBUGHELPER;
typedef HRESULT (WINAPI *CUSTOMVIEWER)( DWORD dwAddress, DEBUGHELPER *pHelper, int nBase, BOOL bUniStrings, char *pResult, size_t max, DWORD reserved );
extern "C" ADDIN_API HRESULT MyClassFormatter( DWORD dwAddress, DEBUGHELPER *pHelper, int nBase, BOOL bUniStrings, char *pResult, size_t max, DWORD reserved );
class MyClass
{
public:
    int publicInt;
};
ADDIN_API HRESULT MyClassFormatter( DWORD dwAddress, DEBUGHELPER *pHelper, int nBase, BOOL bUniStrings, char *pResult, size_t max, DWORD reserved )
{
    MyClass c;
    DWORD nGot;
    auto realAddress = pHelper->GetRealAddress(pHelper);
    pHelper->ReadDebuggeeMemoryEx(pHelper, realAddress, sizeof(MyClass), &c, &nGot );
    sprintf_s(pResult,max,"Dll MyClass: max=%d nGot=%d MyClass=%llx publicInt=%d",max, nGot, realAddress, c.publicInt);
    return S_OK;
}

我需要一个与加载上述"NatvisAddIn.dll"所查找的搜索路径相关的说明。让我尝试通过扩展上述示例来解释

为了可视化我的自定义C++类对象(比如MyCustomeType),我需要在"DisplayString"XML的"Export"属性中提到的函数(比如MyClassFormatter)的实现中调用一些额外的API(来计算MyCustomeTType的显示字符串)。

调用这些额外的API会在上面提到的"NatvisAddIn.dll"上创建一个库.dll依赖项。如果这个额外的依赖项只是一个或两个库,我可以将这些库放在NatvisAddIn.dll所在的位置。然而,在我的案例中,依赖项包含一长串库。

有人能建议我一些优雅的方法来解决依赖关系,而不必将整个库链拉到%USERPROFILE%\My Documents\Visual Studio 2012\Visualizers文件夹中吗?

为了演示我的用例,让我们考虑下面的依赖关系树:NatvisAddIn.dlla.dll
a1.dlla2.dlla3.dll

我在NatvisAddIn.dll的同一位置复制了.dll。它的依赖dll(a1、a2和a3)存在于添加到PATH变量的位置。当我试图在visual studio调试器中可视化MyCustomeType对象时,natvis diagonostic在输出窗口中给出了以下错误

Natvis:C:\Users\myUser\Documents\Visual Studio 2017\Visualizers\mydata.Natvis(9,6):错误:无法从C:\Users\myUser\Documents\WVisual Studio 2017\DVisualizers\ NatvisAddIn.dll加载MyCustomType:类型的加载项。找不到指定的模块

根据我对上述错误的理解,visual studio调试器无法解决.dll(a1、a2和a3)的依赖关系,因此无法加载NatvisAddIn.dll

当我尝试在我的testApplication中使用.dll并计算MyCustomeType的DisplayString时,依赖关系得到解析,加载.dll并在不复制a1.dll、a2.dll和a3.dll的情况下获得预期的输出字符串。依赖dll从窗口PATH变量中解析/提取但是,在visual studio调试器的情况下,依赖DLL不会从PATH变量中解析

根据dependent工具的标识,调试器未解析的一些dll有:

api-ms-win-coreerrorHandling-l1-1.0.dllapi-ms-win-crt-time-l1-1-0.dllapi-ms-win-crt-heap-l1-1-0.dll

其中一些dll存在于visual studio安装中,其他dll存在于c:\windows\WinSxS 中

我已经在visualstudio2012和visualstudio2017上试用了我的用例。我在两个视觉工作室都遇到了同样的问题。