Как написать собственную библиотеку DLL визуализатора для отладчика Visual Studio 2012?

Что необходимо для написания собственной библиотеки DLL визуализатора на C++ для отладчика Visual Studio 2012? Я хочу отобразить значение, которое может быть вычислено только из класса/структуры по запросу, поэтому требуется встроенная DLL визуализатора. Visual Studio 2012 использует новый метод реализации собственных визуализаторов под названием Natvis. На данный момент очень мало достоверной информации о Natvis и особенно об использовании Natvis для вызова библиотеки DLL визуализатора. DLL будет вычислять отображаемую строку на основе значений членов класса/структуры.


person BSalita    schedule 18.07.2012    source источник
comment
code.msdn.microsoft.com/windowsdesktop/   -  person Hans Passant    schedule 18.07.2012
comment
Это отличный пост о natvis, о котором я также упомянул ниже, но в нем нет ни слова о написании пользовательской родной dll визуализатора. Я буду великодушен и скажу, что для Microsoft еще слишком рано освещать всю тему.   -  person BSalita    schedule 18.07.2012


Ответы (3)


Вот код C++, который содержит DLL AddIn. Я назвал файл 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;
}

Вот файл .natvis, который отладчик Visual Studio 2012 использует для отображения значения. Поместите его в файл .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\Microsoft\VisualStudio\11.0_Config\Debugger]

"EnableNatvisDiagnostics"=dword:00000001

Если все пойдет хорошо, вы увидите сообщения natvis в окне вывода отладчика Visual Studio. В сообщениях будет показано, удалось ли Natvis проанализировать файлы .natvis. Результаты анализа каждого файла .natvis отображаются в окне вывода. Если что-то не так, используйте команду «dumpbin/exports», чтобы дважды проверить, что имена методов DLL точно соответствуют Type= файла .navis. Также убедитесь, что текущие файлы .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

person BSalita    schedule 18.07.2012
comment
Спасибо за вопрос. Возможно, было несколько отрицательных голосов, потому что я не заполнил все заглушки. Я добавлял в реальном времени. Я узнал, что я должен завершить пост на начальном посте, если это возможно. Пост готов, так что должно быть много голосов. - person BSalita; 18.07.2012
comment
Во-первых, спасибо за публикацию, но я не думаю, что это сработает для 64-битных приложений. (DWORD используется, например, для адреса и других вещей). Кроме того, как сделать один плагин, который каким-то образом работает с 32-битным/64-битным кодом на другой стороне? - person malkia; 15.02.2013
comment
Отличный вопрос. Извините, я не знаю. Мне интересно, есть ли дополнительные сложности, потому что отладчик Visual Studio — это 32-разрядный процесс. Любой? - person BSalita; 04.03.2013
comment
@malkia Вы можете использовать комбинацию GetRealAddress и ReadDebuggeeMemoryEx для работы с 64-битными адресами. - person user2093113; 17.05.2013
comment
Я не могу заставить это работать, когда я переместил класс и структуру в другую DLL - person BlueTrin; 21.11.2018
comment
Может ли путь к DLL содержать переменную среды? Каков синтаксис? Уже пробовал %ENV_VAR% и ${ENV_VAR}, не повезло. - person Nikola Malešević; 17.06.2019

Для отладки 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;
}
person k4hvd1    schedule 13.05.2016

Мне нужно одно уточнение, связанное с путями поиска, которые ищутся для загрузки вышеупомянутой "NatvisAddIn.dll". Позвольте мне попытаться объяснить, расширив приведенный выше пример.

Чтобы визуализировать мой пользовательский объект класса C++ (скажем, MyCustomeType), мне нужно вызвать некоторые дополнительные API (для вычисления отображаемой строки для MyCustomeType) в реализации функции (скажем, MyClassFormatter), которая упоминается в атрибуте «Экспорт» «DisplayString». XML.

Вызов этих дополнительных API создает зависимость библиотеки/dll от вышеупомянутой "NatvisAddIn.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. Когда я пытаюсь визуализировать объект MyCustomeType в отладчике Visual Studio, диагоностика 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

Когда я попытался использовать .dll в своем testApplication и вычислить DisplayString для MyCustomeType, зависимость получает разрешение, загружается .dll, и я получаю ожидаемую строку без копирования a1.dll, a2.dll и a3.dll. зависимые dll разрешаются/подхватываются из переменной PATH окна. Но в случае отладчика Visual Studio зависимые DLL НЕ разрешаются из переменной PATH.

Как определено инструментом зависимости, некоторые библиотеки 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. У меня возникла одна и та же проблема с обеими визуальными студиями.

person Santosh Chauhan    schedule 17.03.2019