Поделиться через


Реализация пользовательской встроенной функции NatVis для C++

В этой статье описаны рекомендации по реализации пользовательской встроенной функции в визуализации NatVis. Дополнительные сведения о NatVis см. в статье Создание пользовательских представлений объектов C++.

Синтаксис

Файл NatVis может определить встроенную функцию с помощью следующего синтаксиса:

<Intrinsic Name="Func" Expression="arg + 1">
  <Parameter Name="arg" Type="int" />
</Intrinsic>

Когда определение существует, любое выражение отладчика может вызывать функцию, как и любую другую функцию. Например, используя предыдущее определение NatVis, выражение Func(3) оценивается в 4.

Элемент <Intrinsic> может отображаться на уровне файла или внутри элемента <Type> перед любыми другими элементами. Встроенные функции, определенные в <Type> определяют функции-члены этого типа, а встроенные функции, определенные вне <Type> определяют глобальные функции. Например:

<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
  <Type Name="std::vector&lt;*&gt;">
    <Intrinsic Name="size" Expression="_Mypair._Myval2._Mylast - _Mypair._Myval2._Myfirst" />
    <Intrinsic Name="capacity" Expression="_Mypair._Myval2._Myend - _Mypair._Myval2._Myfirst" />
  </Type>
  <Intrinsic Name="LOWORD" Expression="arg & 0xFFFF">
    <Parameter Name="arg" Type="unsigned int" />
  </Intrinsic>
</AutoVisualizer>

В предыдущем примере size() и capacity() определяются как функции-члены класса std::vector, поэтому всякий раз, когда выражение оценивает vector.size(), оно фактически вычисляет vector._Mypair._Myval2._Mylast - vector._Mypair._Myval2._Myfirst, а не выполняет func-eval. Аналогично, LOWORD(0x12345678) возвращает 0x5678, также без func-eval.

В другом примере см. Встроенное расширение.

Рекомендации по использованию встроенной функции

Помните о следующих рекомендациях при использовании встроенной функции:

  • Встроенные функции могут быть перегружены, либо с функциями, определяемыми в PDB, либо между собой.

  • Если встроенная функция конфликтует с определяемой PDB функцией с тем же именем и списком аргументов, встроенная функция выиграет. Вы не можете вычислять функцию PDB, если существует эквивалентная встроенная функция.

  • Невозможно получить адрес встроенной функции; её можно только вызвать.

  • Функции-члены, являющиеся встроенными, должны принадлежать виду по умолчанию для соответствующего типа, чтобы быть вычисленными в выражении. Например, запись типа с ограничением IncludeView может не указывать встроенную функцию.

  • Встроенные функции могут вызываться из любого выражения, включая выражения NatVis. Встроенные функции также могут вызывать друг друга. Однако встроенные функции, использующие рекурсию, в настоящее время не поддерживаются. Например:

    <!-- OK -->
    <Intrinsic Name="Func2" Expression="this-&gt;Func3(arg + 1)">
      <Parameter Name="arg" Type="int" />
    </Intrinsic>
    <Intrinsic Name="Func3" Expression="arg + 1">
      <Parameter Name="arg" Type="int"/>
    </Intrinsic>
    
    <!-- Unsupported -->
    <Intrinsic Name="Func2" Expression="this-&gt;Func3(arg + 1)">
      <Parameter Name="arg" Type="int" />
    </Intrinsic>
    <Intrinsic Name="Func3" Expression="Func2(arg + 1)">
      <Parameter Name="arg" Type="int"/>
    </Intrinsic>
    
    <!-- Also unsupported-->
    <Intrinsic Name="Fib" Expression="(n &lt;= 1) ? 1 : Fib(n - 1) + Fib(n - 2)">
      <Parameter Name="n" Type="int"/>
    </Intrinsic>
    
  • По умолчанию встроенные функции считаются не имеющими побочных эффектов. То есть их можно вызывать в контекстах, которые запрещают побочные эффекты, и выражение реализации не допускается содержать побочные эффекты.

    Определение может переопределить это поведение, указав атрибут SideEffect в объявлении. Если функция помечена как имеющая побочные эффекты, побочные эффекты в выражении реализации становятся допустимыми. Однако вызов функции в контекстах, в которых запрещены побочные эффекты, больше не разрешен.

  • Когда определения двух встроенных функций конфликтуют друг с другом (одно и то же имя, одна и та же сигнатура), последняя имеет приоритет (в пределах одного файла).

    В разных файлах экземпляр в файле с более высоким приоритетом имеет приоритет (проект имеет более высокий приоритет, чем директория пользователя, а она, в свою очередь, выше директории установки). Если определение с более высоким приоритетом содержит выражение, которое не анализируется, это определение игнорируется, а вместо этого используется следующее определение с наивысшим приоритетом.

Рекомендации по реализации встроенной функции

Встроенные функции поддерживают две возможные формы реализации:

  • На основе выражений

    Файл NatVis определяет выражение, которое оценивает возвращаемое значение функции. Выражение может использовать любые аргументы, переданные в него, объявленные как элементы <Parameter>. Функции, определенные в классе, также считаются функциями экземпляра, а также могут получить доступ к указателю "this".

  • Основывается на расширениях

    Файл NatVis предоставляет инструкции по вызову расширения отладчика для фактической оценки функции. Расширение отладчика имеет полный доступ к API Конкорда и имеет возможность выполнять задачи, которые недоступны в выражении NatVis.

Чтобы обеспечить реализацию на основе расширений, элемент <Intrinsic> должен опустить атрибут Expression, а вместо этого указать SourceId, LanguageId, Idи ReturnType атрибуты, как показано в следующем примере:

<Intrinsic Name="MyFunc" SourceId="a665fa54-6e7d-480e-a80b-1fc1202e9646" LanguageId="3a12d0b7-c26c-11d0-b442-00a0244a1dd2" Id="1000" ReturnType="double">
  <Parameter Type="int" />
  <Parameter Type="int" />
  <Parameter Type="int" />
</Intrinsic>

Чтобы реализовать функцию, расширение отладчика должно реализовать интерфейс IDkmIntrinsicFunctionEvaluator140, используя LanguageId и фильтры SourceId, соответствующие соответствующим значениям элемента <Intrinsic> в файле NatVis. При вызове функции это переводится в метод Execute() компонента:

STDMETHOD(Execute)(
    _In_ Evaluation::IL::DkmILExecuteIntrinsic* pExecuteIntrinsic,
    _In_ Evaluation::DkmILContext* pILContext,
    _In_ Evaluation::IL::DkmCompiledILInspectionQuery* pInspectionQuery,
    _In_ const DkmArray<Evaluation::IL::DkmILEvaluationResult*>& Arguments,
    _In_opt_ DkmReadOnlyCollection<Evaluation::DkmCompiledInspectionQuery*>* pSubroutines,
    _Out_ DkmArray<Evaluation::IL::DkmILEvaluationResult*>* pResults,
    _Out_ Evaluation::IL::DkmILFailureReason* pFailureReason
    );

Компонент получает байты каждого аргумента через аргумент Arguments. Если рассматриваемая функция является функцией-членом, указатель this идет первым, за ним следуют явные аргументы. Компонент должен вернуть результат, распределив массив с одним элементом в pResults, сохраняя байты возвращаемого значения.

Используйте следующие рекомендации для реализации функций:

  • Использовать обе формы реализации вместе — незаконно. То есть нельзя включать как выражение, так и исходный идентификатор.

  • Указание возвращаемого типа для реализации на основе выражений разрешено, но не обязательно. Если указан тип возвращаемого значения, возвращаемый тип выражения должен точно совпадать с ним (не допускается неявное приведение). Если возвращаемый тип не указан, возвращаемый тип выводится из выражения. Любая реализация на основе расширений должна указывать тип возвращаемого значения в файле NatVis.

  • Разрешены не рекурсивные вызовы к другим встроенным функциям. Рекурсия не допускается.

  • Если функция имеет побочные эффекты, необходимо указать SideEffect="true" в объявлении. Незаконно, чтобы реализация, основанная на выражениях, имела побочные эффекты, не объявив функцию таковой. Использование реализации на основе расширения для создания побочных эффектов без объявления функции имеющей побочные эффекты является неопределённым поведением и его следует избегать.

  • Разрешены встроенные функции Varargs. Чтобы объявить функцию varargs, укажите Varargs="true" в объявлении. Хотя реализация на основе выражений является законной для объявления функции vararg, в настоящее время только реализации на основе расширений имеют способ доступа к аргументам переменной. При реализации на основе расширения функция Execute() получает все аргументы, которые фактически передаются, а не только объявленные аргументы.

  • Встроенные функции, потребляющие тип класса/ структуры или объединения в качестве типа аргумента, не поддерживаются. Возврат типа класса/структуры или объединения является приемлемым. (Указатель или ссылка на тип класса/структуры или объединения — "ОК" в качестве типа аргумента).

Настройте значок для вызовов встроенных функций.

По умолчанию при вызове встроенной функции выражение получает розовый значок бриллианта в окне Watch, связанном с вызовами функций. Это поведение можно переопределить, указав атрибут Category с помощью одного из следующих значений:

  • Метод. Используйте значок розового бриллианта, обычно используемый с вызовами методов (по умолчанию).
  • Свойство. Используйте черный значок гаечного ключа, который обычно используется для работы со свойствами.
  • Данные. Используйте синий значок бриллианта, обычно используемый с данными.

Сочетая встроенные функции с элементом <Item>, можно создать файл NatVis, где выражения элементов имеют значок свойства wrench:

<Type Name="MyClass">
  <Intrinsic Name="GetValue" ReturnType="int" Expression="m_value" Category="Property" />
  <Expand>
    <Item Name="Value">this-&gt;GetValue()</Item>
  </Expand>
</Type>

Заметка

Размещение выбора значка на уровне функции, а не на уровне <Item>, позволяет избежать проблем, при которых настройка значка теряется при оценке полного имени. Поскольку полное имя включает вызов функции, оно имеет ту же настройку значка, что и сам <Item>.