在 natvis 中使用 std::type_info 进行强制转换

Posted

技术标签:

【中文标题】在 natvis 中使用 std::type_info 进行强制转换【英文标题】:Using std::type_info for casting in natvis 【发布时间】:2019-12-11 12:34:18 【问题描述】:

在我正在工作的代码库中,我们使用std::any 而不是void* 来通过一些通用的非模板代码传递类。具体来说,我们使用 Visual Studio 2019、它的编译器和标准库。

为了可视化std::any,微软已经给出了natvis:

  <Type Name="std::any">
      <Intrinsic Name="has_value"   Expression="_Storage._TypeData != 0"/>
      <Intrinsic Name="_Rep"        Expression="_Storage._TypeData &amp; _Rep_mask"/>
      <Intrinsic Name="type"        Expression="(const type_info*)(_Storage._TypeData &amp; ~_Rep_mask)"/>
      <Intrinsic Name="_Is_trivial" Expression="has_value() &amp;&amp; _Rep() == 0"/>
      <Intrinsic Name="_Is_big"     Expression="has_value() &amp;&amp; _Rep() == 1"/>
      <Intrinsic Name="_Is_small"   Expression="has_value() &amp;&amp; _Rep() == 2"/>
      <DisplayString Condition="!has_value()">[empty]</DisplayString>
      <DisplayString Condition="_Is_trivial() || _Is_small()">[not empty (Small)]</DisplayString>
      <DisplayString Condition="_Is_big()">[not empty (Large)]</DisplayString>
      <Expand>
          <Synthetic Name="has_value">
              <DisplayString>has_value()</DisplayString>
          </Synthetic>
          <Synthetic Name="type" Condition="has_value()">
              <DisplayString>type()</DisplayString>
          </Synthetic>
          <Synthetic Name="[representation]" Condition="_Is_trivial()">
              <DisplayString>(Small/Trivial Object)</DisplayString>
          </Synthetic>
          <Synthetic Name="[representation]" Condition="_Is_small()">
              <DisplayString>(Small Object)</DisplayString>
          </Synthetic>
          <Synthetic Name="[representation]" Condition="_Is_big()">
              <DisplayString>(Dynamic Allocation)</DisplayString>
          </Synthetic>
      </Expand>
  </Type>

但是,这最终向我们显示了(Small Object),而不是我们存储在其中的std::string。 我已经设法用一些额外的行来扩展它以获取指向数据的指针:

          <Item Name="[castable_ptr]" Condition="_Is_trivial()">(void*)(&amp;_Storage._TrivialData)</Item>
          <Item Name="[castable_ptr]" Condition="_Is_small()">(void*)(&amp;_Storage._SmallStorage._Data)</Item>
          <Item Name="[castable_ptr]" Condition="_Is_big()">(void*)(_Storage._BigStorage._Ptr)</Item>

但是,这会将数据显示为 void*,您必须手动将其转换为实际类型 std::string* 的指针。 然而,这个std::any 实现/可视化也带有std::type_info。 (参见字段:类型),它知道我们拥有哪种底层类型。

有没有办法使用这个std::type_info 以便(void*) 可以替换为实际存储类型的转换?

编辑: Visual Studio 为以下类型提供的信息示例:mydll.dll!class std::tuple&lt;__int64,double,double,double&gt; 'RTTI Type Descriptor' ... 在将地址显式转换为 std::type_info* 时,我可以在调试器中访问 _Data,其中包含 _DecoratedName (.?AV?$tuple@_JNNN@std@@) 和 _UndecoratedName (nullptr)。 不幸的是,我似乎不知道如何编写一个利用这些信息的演员表。

【问题讨论】:

std::type_info 包含字段 name ,你能到达吗? 就像@Swift-FridayPie 说的那样,有没有办法使用std::any.type().name() 方法,也许与std::any_cast&lt;type T&gt;() 结合使用? @Swift-FridayPie 所有我能找到的都在编辑中提到 @arc-menace 我不认为std::any_cast可以在NatVis中使用 注意:目前,我已经记录了 STL 实现的一个错误:github.com/microsoft/STL/issues/929 【参考方案1】:

我不知道上下文(实施限制)。 如果使用 std::any 被确定为一个强有力的先决条件(我可以理解)并且在微软改进本机 vizualizer 之前,下面可能是一个战术上的快速胜利。

在natvis xml配置文件中:

<Synthetic Name="[representation]" Condition="_Is_trivial()">
    <!--<DisplayString>(Small/Trivial Object)</DisplayString>-->
    <DisplayString>_Storage._TrivialData._Val</DisplayString>
</Synthetic>
<Synthetic Name="[representation]" Condition="_Is_small()">
    <!--<DisplayString>*displayStdAnyContent(*this)</DisplayString>-->
    <Expand>
        <Item Name="[value]">*displayStdAnyContent(*this)</Item>
    </Expand>
</Synthetic>

<Type Name="AnyCastedValue&lt;*&gt;">
    <DisplayString>value</DisplayString>
    <Expand>
        <Item Name="[value]">value</Item>
    </Expand>
</Type>

C++

//----------------------------------------------------------------------------------
class IAnyValue

public:
    virtual ~IAnyValue() = default;
;

template <typename T>
struct AnyCastedValue final :public IAnyValue

    AnyCastedValue(const T& pi_value) :value(pi_value) 

    T value;
;

struct NonCastableValue final :public IAnyValue

    static constexpr auto message = "I'm not castable! Please register my type!";
;

//----------------------------------------------------------------------------------
class IAnyCaster

public:
    virtual std::unique_ptr<IAnyValue> castValue(const std::any& pi_any) const = 0;
    virtual ~IAnyCaster() = default;
;

template <typename T>
class AnyCaster final :public IAnyCaster

public:
    AnyCaster() = default;

    virtual std::unique_ptr<IAnyValue> castValue(const std::any& pi_any) const override
    
        return std::make_unique<AnyCastedValue<T>>(std::any_cast<T>(pi_any));
    
;
    
//----------------------------------------------------------------------------------
class AnyCasterService final :public std::unordered_map<std::string, std::unique_ptr<IAnyCaster>>

public:
    AnyCasterService()
    
        //here we register the types you would like be able to watch under the debugger
        registerType<int>();
        registerType<std::string>();
    

    std::unique_ptr<IAnyValue> castValue(const std::any& pi_any) const
    
        auto im = find(pi_any.type().name());
        if (im != end())
        
            return im->second->castValue(pi_any);
        
        else return std::make_unique<NonCastableValue>();
    

private:
    template <typename T>
    void registerType()
    
        this->insert(std::make_pair(typeid(T).name(),std::make_unique<AnyCaster<T>>()));
    
;

//----------------------------------------------------------------------------------
std::unique_ptr<IAnyValue> displayStdAnyContent(const std::any& pi_any)
   
    static const AnyCasterService s_service;
    return s_service.castValue(pi_any);

我在 VS2017 v15.9.8 下验证了这个提案

请记住,Natvis 只能根据您的需求在运行时评估方法(显式用户调用)。

std::any l_any;
l_any = 5;
l_any = std::string("it works!");

【讨论】:

我喜欢这个解决方案的创意!它确实需要我在代码中的某处迭代所有类型,但是它可以工作。我会更详细地检查一下 据我所知,natvis 调试器运行在与您调试的进程不同的进程上。因此,它只能读取内存并根据它们的类型对其进行解释。无法通过 natvis 配置文件 (.xml) 动态识别类型,因为它需要调用方法(例如:不能在“条件”标签中调用 typeid)。在 natvis 中调用方法/函数是非常严格的(背后的想法:不破坏被调试进程)。对于 std::any,您需要一种方法,该方法根据 natvis 限制动态向下转换嵌入值。 请记住,我的建议只是用于调试目的的解决方法! 是的,对于一个永久的解决方案,我们需要对 natvis 进行扩展【参考方案2】:

std::any 将取消输入您的数据。它必须被视为标准中 void* 的安全包装器。 因此,您无法在运行时通过 natvis 查看其键入的数据。

我的建议是通过依赖模板类型的具体类继承的通用接口来重新设计您的实现以避免 std::any 或 void*。 例如:

class IData //base interface

public:
    ~virtual IData()=default;


template <typename T> 
class TypedData:public IData //abstract or concrete

    //...
    T m_data;


void genericFunction(const IData& pi_data); //instead of void* or std::any

int main()

    TypedData<std::string> l_data("my typed data");
    genericFunction(l_data);
    //...

通过这种设计,您可以定义单个 natvis 条目来管理 TypedData

<Type Name="TypedData&lt;*&gt;"> //TypedData<*>
    <DisplayString>m_data</DisplayString>
</Type>

【讨论】:

虽然我同意您的解决方案可以在特定情况下工作,但它不能解决您无法摆脱 std::any 的情况 这里同样重要的是 std::any 所有权会占用您输入的内容。(通过复制或移动) 许多动态环境(例如网络协议、消息代理等)需要某种类型的类型擦除。从业务规则到线路时可以使用静态类型的东西,但从线路回到业务规则通常需要某种类型的擦除机制。因此是最初的问题。

以上是关于在 natvis 中使用 std::type_info 进行强制转换的主要内容,如果未能解决你的问题,请参考以下文章

使用 natvis 解构结构数组

NatVis:允许 typedefs 吗?

在 natvis 中使用 std::type_info 进行强制转换

如何从 C 语言中的 natvis 表达式中引用变量本身?

如何在 natvis 中传播 ExcludeView/IncludeView?

NatVis 显示枚举的子字符串