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

时间:2012-07-18 15:54:12

标签: c++ debugging visual-studio-2012 visualizer

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

3 个答案:

答案 0 :(得分:38)

这是包含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。调试器将在Auto,Watch或工具提示显示中为指定类型的每个实例(MyClass,MyStruct)显示方法的格式化值。

<?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%\Common7\Packages\Debugger\Visualizers (requires admin access)

%USERPROFILE%\My Documents\Visual Studio 2012\Visualizers\

VS extension folders

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

  

[HKEY_CURRENT_USER \ SOFTWARE \微软\ VisualStudio的\ 11.0_Config \调试器]

     

“EnableNatvisDiagnostics”= DWORD:00000001

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

Natvis: Parsing natvis xml file: C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\Packages\Debugger\Visualizers\atlmfc.natvis.
Natvis: Done parsing natvis xml file: C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\Packages\Debugger\Visualizers\atlmfc.natvis.
Natvis: Parsing natvis xml file: C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\Packages\Debugger\Visualizers\concurrency.natvis.
Natvis: Done parsing natvis xml file: C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\Packages\Debugger\Visualizers\concurrency.natvis.
Natvis: Parsing natvis xml file: C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\Packages\Debugger\Visualizers\NatvisAddIn.natvis.
Natvis: Done parsing natvis xml file: C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\Packages\Debugger\Visualizers\NatvisAddIn.natvis.
Natvis: Parsing natvis xml file: C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\Packages\Debugger\Visualizers\stl.natvis.
Natvis: Done parsing natvis xml file: C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\Packages\Debugger\Visualizers\stl.natvis.
Natvis: Parsing natvis xml file: C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\Packages\Debugger\Visualizers\windows.natvis.
Natvis: Done parsing natvis xml file: C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\Packages\Debugger\Visualizers\windows.natvis.
Natvis: Parsing natvis xml file: C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\Packages\Debugger\Visualizers\winrt.natvis.
Natvis: Done parsing natvis xml file: C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\Packages\Debugger\Visualizers\winrt.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 \架构\ 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

答案 1 :(得分:1)

对于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;
}

答案 2 :(得分:0)

我需要澄清与搜索路径有关的信息,这些信息用于加载上述“ NatvisAddIn.dll”。让我尝试通过扩展上面的示例来解释

要可视化我的自定义C ++类对象(比方说MyCustomeType),我需要在函数的实现(如MyClassFormatter)中调用一些其他API(以计算MyCustomeType的显示字符串), “ DisplayString” XML。

调用这些其他API会在上述“ NatvisAddIn.dll”上创建库/ dll依赖项。如果此附加依赖项只是一个或两个库,则可以将这些库放在存在NatvisAddIn.dll的相同位置。但是,就我而言,依赖项包含一长串库。

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

为演示我的用例,让我们考虑以下依赖关系树: NatvisAddIn.dll     a.dll
        a1.dll         a2.dll         a3.dll

我将a.dll复制到了NatvisAddIn.dll的相同位置。它的相关dll(a1,a2和a3)位于添加到PATH变量的位置。当我尝试在Visual Studio调试器中可视化MyCustomeType对象时,natvis对角线在输出窗口中给出以下错误

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

我对上述错误的理解是,Visual Studio调试器无法解析a.dll(a1,a2和a3)的依赖项,因此无法加载NatvisAddIn.dll

当我尝试在testApplication中使用a.dll并为MyCustomeType计算DisplayString时,依赖关系得到解决,将加载a.dll,并且得到预期的输出字符串,而不复制a1.dll,a2.dll和a3.dll 。从窗口PATH变量解析/挑选了依赖的dll。 但是,如果使用Visual Studio调试器,则无法从PATH变量中解析相关的DLL

如依赖工具所标识,一些调试器无法解析的dll是:

api-ms-win-core-errorhandling-l1-1-0.dll api-ms-win-crt-time-l1-1-0.dll api-ms-win-crt-heap-l1-1-0.dll

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

我已经在Visual Studio 2012和Visual Studio 2017上试用了我的用例。我在两个Visual Studio中都遇到了同样的问题。