zoukankan      html  css  js  c++  java
  • Visual Studio调试器指南---Natvis 语法参考

    AutoVisualizer 元素

    AutoVisualizer 元素是 .natvis 文件的根节点,并包含命名空间 xmlns: 属性。

    XML
    <?xml version="1.0" encoding="utf-8"?>
    <AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
    .
    .
    </AutoVisualizer>
    

    AutoVisualizer 元素可以具有类型、 HResult、 UIVisualizer和CustomVisualizer子项。

    Type 元素

    基本 Type 如下例所示:

    XML
    <Type Name="[fully qualified type name]">
      <DisplayString Condition="[Boolean expression]">[Display value]</DisplayString>
      <Expand>
        ...
      </Expand>
    </Type>
    

    Type 元素指定:

    1. 可视化对象应使用的类型(Name 特性)。

    2. 该类型的对象的值是什么样的( DisplayString 元素)。

    3. 用户在变量窗口中展开类型时,该类型的成员应当以什么形式显示(Expand 节点)。

    模板类

    Type 元素的 Name 属性接受星号 * 作为可用于模板化类名的通配符。

    在以下示例中,无论对象是 CAtlArray<int> 还是 CAtlArray<float>,都使用了相同的可视化效果。 如果 CAtlArray<float> 有特定的可视化条目,它的优先级将高于通用的条目。

    XML
    <Type Name="ATL::CAtlArray&lt;*&gt;">
        <DisplayString>{{Count = {m_nSize}}}</DisplayString>
    </Type>
    

    可以使用 $T1 和 $T2 这样的宏,在可视化条目中引用模板参数。 有关这些宏的示例,请参阅 Visual Studio 随附的 .natvis 文件。

    可视化工具类型匹配

    如果无法验证某个可视化条目,则使用下一个可用的可视化效果。

    可继承的特性

    可选的 Inheritable 属性用于指定,一个可视化效果是仅适用于一个基类型,还是适用于一个基类型和所有的派生类型。 Inheritable 的默认值为 true

    在下面的示例中,可视化效果仅适用于 BaseClass 类型:

    XML
    <Type Name="Namespace::BaseClass" Inheritable="false">
        <DisplayString>{{Count = {m_nSize}}}</DisplayString>
    </Type>
    

    优先级特性

    如果某个定义的分析失败,可选的 Priority 属性会指定使用备用定义的顺序。 Priority 的值包括:LowMediumLowMediumMediumHighHigh默认值是 MediumPriority属性只区分同一个 .natvis 文件中的优先级。

    下面的示例会首先分析与 2015 STL 匹配的条目。 如果分析失败,就会使用 STL 的 2013 版本的备用条目:

    XML
    <!-- VC 2013 -->
    <Type Name="std::reference_wrapper&lt;*&gt;" Priority="MediumLow">
         <DisplayString>{_Callee}</DisplayString>
        <Expand>
            <ExpandedItem>_Callee</ExpandedItem>
        </Expand>
    </Type>
    
    <!-- VC 2015 -->
    <Type Name="std::reference_wrapper&lt;*&gt;">
        <DisplayString>{*_Ptr}</DisplayString>
        <Expand>
            <Item Name="[ptr]">_Ptr</Item>
        </Expand>
    </Type>
    

    可选特性

    你可以将 Optional 属性放在任一节点上。 如果某个可选节点内的某个子表达式的分析失败,调试器就会忽略该节点,但会应用 Type 规则的其余部分。 在下面的类型中, [State] 不可选,但 [Exception] 可选。 如果 MyNamespace::MyClass 包含名为 M_exceptionHolder 的字段,就会同时显示 [State] 节点和 [Exception] 节点,但如果不包含 _M_exceptionHolder 字段,就会只显示 [State] 节点。

    XML
    <Type Name="MyNamespace::MyClass">
        <Expand>
          <Item Name="[State]">_M_State</Item>
          <Item Name="[Exception]" Optional="true">_M_exceptionHolder</Item>
        </Expand>
    </Type>
    

    条件属性

    可选的 Condition 属性可用于许多可视化元素,指定何时使用可视化规则。 如果条件属性内的表达式解析为 false,就不应用可视化规则。 如果其计算结果为 true,或者没有 Condition 属性,就会应用可视化规则。 你可以将此属性用于可视化条目中的 if-else 逻辑。

    例如,下面的可视化对象具有智能指针类型的两个 DisplayString 元素。 _Myptr 成员为空时,第一个 DisplayString 元素的条件解析为 true,以便显示该窗体。 如果 _Myptr 成员不为空,则条件的计算结果为 false,第二个 DisplayString 元素显示。

    XML
    <Type Name="std::auto_ptr&lt;*&gt;">
      <DisplayString Condition="_Myptr == 0">empty</DisplayString>
      <DisplayString>auto_ptr {*_Myptr}</DisplayString>
      <Expand>
        <ExpandedItem>_Myptr</ExpandedItem>
      </Expand>
    </Type>
    

    IncludeView 和 ExcludeView 特性

    IncludeViewExcludeView 属性用于指定是否在特定视图内显示元素。 例如,在下面的 Natvis std::vector 规范中,simple 视图不显示 [size][capacity] 项。

    XML
    <Type Name="std::vector&lt;*&gt;">
        <DisplayString>{{ size={_Mylast - _Myfirst} }}</DisplayString>
        <Expand>
            <Item Name="[size]" ExcludeView="simple">_Mylast - _Myfirst</Item>
            <Item Name="[capacity]" ExcludeView="simple">_Myend - _Myfirst</Item>
            <ArrayItems>
                <Size>_Mylast - _Myfirst</Size>
                <ValuePointer>_Myfirst</ValuePointer>
            </ArrayItems>
        </Expand>
    </Type>
    

    可以针对类型和各个成员使用 IncludeViewExcludeView 属性。

    Version 元素

    Version 元素用于将可视化条目的范围限定在特定模块和版本内。 Version 元素有助于避免名称冲突、减少无意间的不匹配,还允许在不同的类型版本中使用不同的可视化效果。

    如果某个由不同模块使用的公共头文件定义了一个类型,则只有当该类型位于指定的模块版本中时,版本化的可视化效果才会显示。

    在下面的示例中,可视化效果只适用于在 DirectUI::Border 版本 1.0 到 1.5 中找到的 Windows.UI.Xaml.dll 类型。

    XML
    <Type Name="DirectUI::Border">
      <Version Name="Windows.UI.Xaml.dll" Min="1.0" Max="1.5"/>
      <DisplayString>{{Name = {*(m_pDO->m_pstrName)}}}</DisplayString>
      <Expand>
        <ExpandedItem>*(CBorder*)(m_pDO)</ExpandedItem>
      </Expand>
    </Type>
    

    不需要 MinMax它们是可选属性。 不支持通配符。

    Name 特性的格式为filename. ext,如hellosome不允许使用路径名。

    DisplayString 元素

    DisplayString 元素用于指定要显示为变量值的字符串。 它接受混合表达式的任意字符串。 大括号内的所有内容都可解释为表达式。 例如,以下 DisplayString 条目:

    XML
    <Type Name="CPoint">
      <DisplayString>{{x={x} y={y}}}</DisplayString>
    </Type>
    

    表示 CPoint 类型的变量显示如下图所示:

    使用 DisplayString 元素

    DisplayString 表达式中,属于 x 成员的 yCPoint 位于大括号内,因此它们的值会被计算。 该示例还介绍了如何用双层大括号({{}})对大括号进行转义。

    备注

    DisplayString 元素是唯一接受任意字符串和大括号语法的元素。 所有其他可视化元素只接受调试器可以计算的表达式。

    StringView 元素

    StringView 元素用于定义一个值,调试器可以将该值发送给内置的文本可视化工具。 例如,假设 ATL::CStringT 类型有以下可视化效果:

    XML
    <Type Name="ATL::CStringT&lt;wchar_t,*&gt;">
      <DisplayString>{m_pszData,su}</DisplayString>
    </Type>
    

    CStringT 对象将显示在一个变量窗口中,如下例所示:

    CStringT DisplayString 元素

    添加 StringView 元素会告诉调试器,它可以将值显示为文本可视化。

    XML
    <Type Name="ATL::CStringT&lt;wchar_t,*&gt;">
      <DisplayString>{m_pszData,su}</DisplayString>
      <StringView>m_pszData,su</StringView>
    </Type>
    

    在调试过程中,可以选择变量旁边的放大镜图标,然后选择 "文本可视化工具" 以显示m_pszData指向的字符串。

    CStringT StringView 可视化工具的数据

    表达式 {m_pszData,su} 包含一个 C++ 格式说明符 su,用于将值显示为 Unicode 字符串。 有关详细信息,请参阅 C++ 中的格式说明符。

    Expand 元素

    可选的 Expand 节点用于自定义当你在变量窗口中展开类型时,该可视化类型的子项。 Expand 节点接受用于定义子元素的子节点列表。

    • 如果未在可视化条目中指定 Expand 节点,子项将使用默认的展开规则。

    • 如果指定的 Expand 节点下面没有子节点,类型就无法在调试器窗口中展开。

    Item 展开

    Item 元素是 Expand 节点中最基本、最常见的元素。 Item 定义单个子元素。 例如,一个包含 CRecttopleftright 字段的 bottom 类具有以下可视化条目:

    XML
    <Type Name="CRect">
      <DisplayString>{{top={top} bottom={bottom} left={left} right={right}}}</DisplayString>
      <Expand>
        <Item Name="Width">right - left</Item>
        <Item Name="Height">bottom - top</Item>
      </Expand>
    </Type>
    

    在调试器窗口中,CRect 类型如下例所示:

    带有项元素扩展的 CRect

    调试器将计算 WidthHeight 元素中指定的表达式,然后在变量窗口的值列中显示值。

    调试器会为每个自定义展开自动创建 [Raw View] 节点。 上面的屏幕截图中的 [Raw View] 节点是展开的,显示了对象的默认原始视图与其 Natvis 可视化效果的区别。 默认展开会为基类创建一个子树,并将基类的所有数据成员以子项的形式列出。

    备注

    如果项元素的表达式指向复杂类型,则项节点本身是可展开的。

    Size

    使用 ArrayItems 节点,让 Visual Studio 调试器将类型解释为一个数组并显示其各个元素。 std::vector 的可视化效果是一个很好的示例:

    XML
    <Type Name="std::vector&lt;*&gt;">
      <DisplayString>{{size = {_Mylast - _Myfirst}}}</DisplayString>
      <Expand>
        <Item Name="[size]">_Mylast - _Myfirst</Item>
        <Item Name="[capacity]">(_Myend - _Myfirst)</Item>
        <ArrayItems>
          <Size>_Mylast - _Myfirst</Size>
          <ValuePointer>_Myfirst</ValuePointer>
        </ArrayItems>
      </Expand>
    </Type>
    

    std::vector 在变量窗口中展开时显示其自身的元素:

    std:: vector 使用 ArrayItems 扩展

    ArrayItems 节点必须具有:

    • 用于使调试器了解数组长度的 Size 表达式(必须计算为整数)。
    • 一个 ValuePointer 表达式,指向第一个元素(必须为非 void* 元素类型的指针)。

    数组下限的默认值为 0。 要修改此值,请使用 LowerBound 元素。 Visual Studio 随附的 .Natvis 文件中提供了示例。

    备注

    可以使用 [] 运算符(例如 vector[i])以及任何使用了 ArrayItems 的一维数组可视化效果,即使该类型本身(例如 CATLArray)不允许使用此运算符。

    还可以指定多维数组。 在这种情况下,调试器需要稍微详细地显示子元素:

    XML
    <Type Name="Concurrency::array&lt;*,*&gt;">
      <DisplayString>extent = {_M_extent}</DisplayString>
      <Expand>
        <Item Name="extent">_M_extent</Item>
        <ArrayItems Condition="_M_buffer_descriptor._M_data_ptr != 0">
          <Direction>Forward</Direction>
          <Rank>$T2</Rank>
          <Size>_M_extent._M_base[$i]</Size>
          <ValuePointer>($T1*) _M_buffer_descriptor._M_data_ptr</ValuePointer>
        </ArrayItems>
      </Expand>
    </Type>
    
    • Direction 指定数组是采用行优先顺序还是列优先顺序。
    • Rank 指定数组的秩。
    • Size 元素接受将其替换为维索引以查找该维度中数组的长度的隐式 $i 参数。 在前面的示例中,表达式 _M_extent.M_base[0] 应为第0个维度指定长度,_M_extent._M_base[1] 为1,依此类推。

    下面是调试器窗口中显示的一个二维 Concurrency::array 对象:

    具有 ArrayItems 扩展的二维数组

    IndexListItems 展开

    仅当数组元素在内存中连续排列时,才能使用 ArrayItems 展开。 调试器只需递增其指针,就可以获取下一个元素。 如果你需要操作值节点的索引,可以使用 IndexListItems 节点。 下面是 IndexListItems 节点的可视化处理:

    XML
    <Type Name="Concurrency::multi_link_registry&lt;*&gt;">
      <DisplayString>{{size = {_M_vector._M_index}}}</DisplayString>
      <Expand>
        <Item Name="[size]">_M_vector._M_index</Item>
        <IndexListItems>
          <Size>_M_vector._M_index</Size>
          <ValueNode>*(_M_vector._M_array[$i])</ValueNode>
        </IndexListItems>
      </Expand>
    </Type>
    

    ArrayItemsIndexListItems 之间的唯一区别是 ValueNode,它期望带有隐式元素 参数的第 i$i 个元素的完整表达式。

    备注

    可以使用 [] 运算符(例如 vector[i])以及任何使用了 IndexListItems 的一维数组可视化效果,即使该类型本身(例如 CATLArray)不允许使用此运算符。

    LinkedListItems 展开

    如果可视化类型表示一个链接列表,则调试器可以通过使用 LinkedListItems 节点显示其子级。 以下 CAtlList 类型的可视化效果使用 LinkedListItems

    XML
    <Type Name="ATL::CAtlList&lt;*,*&gt;">
      <DisplayString>{{Count = {m_nElements}}}</DisplayString>
      <Expand>
        <Item Name="Count">m_nElements</Item>
        <LinkedListItems>
          <Size>m_nElements</Size>
          <HeadPointer>m_pHead</HeadPointer>
          <NextPointer>m_pNext</NextPointer>
          <ValueNode>m_element</ValueNode>
        </LinkedListItems>
      </Expand>
    </Type>
    

    Size 元素引用该列表的长度。 HeadPointer 指向第一个元素, NextPointer 引用下一个元素,而 ValueNode 引用项的值。

    调试器将在 NextPointer 节点元素(而不是父列表类型)环境中计算 ValueNodeLinkedListItems 表达式。 在前面的示例中,CAtlList 有一个 CNode 类(位于 atlcoll.h 中),它是链接列表的节点。 m_pNextm_elementCNode 类(而不是 CAtlList 类)的字段。

    ValueNode 可以保留为空或使用 this 来引用 LinkedListItems 节点本身。

    CustomListItems 展开

    CustomListItems 展开允许编写自定义逻辑,以遍历数据结构(如哈希表)。 使用 CustomListItems 来可视化数据结构,这些数据结构可以使用 C++ 表达式进行所有运算,但不太适合 ArrayItemsIndexListItemsLinkedListItems 模式。

    借助在展开内定义的变量和对象,你可以在 Exec 展开中使用 CustomListItems 来执行内部代码。 可以将逻辑运算符、算术运算符和赋值运算符与 Exec 一起使用。 不能使用 Exec 来计算函数( C++表达式计算器支持的调试器内部函数除外)。

    下面的 CAtlMap 可视化工具是一个很好的例子,其中的 CustomListItems 用得很恰当。

    XML
    <Type Name="ATL::CAtlMap&lt;*,*,*,*&gt;">
        <AlternativeType Name="ATL::CMapToInterface&lt;*,*,*&gt;"/>
        <AlternativeType Name="ATL::CMapToAutoPtr&lt;*,*,*&gt;"/>
        <DisplayString>{{Count = {m_nElements}}}</DisplayString>
        <Expand>
          <CustomListItems MaxItemsPerView="5000" ExcludeView="Test">
            <Variable Name="iBucket" InitialValue="-1" />
            <Variable Name="pBucket" InitialValue="m_ppBins == nullptr ? nullptr : *m_ppBins" />
            <Variable Name="iBucketIncrement" InitialValue="-1" />
    
            <Size>m_nElements</Size>
            <Exec>pBucket = nullptr</Exec>
            <Loop>
              <If Condition="pBucket == nullptr">
                <Exec>iBucket++</Exec>
                <Exec>iBucketIncrement = __findnonnull(m_ppBins + iBucket, m_nBins - iBucket)</Exec>
                <Break Condition="iBucketIncrement == -1" />
                <Exec>iBucket += iBucketIncrement</Exec>
                <Exec>pBucket = m_ppBins[iBucket]</Exec>
              </If>
              <Item>pBucket,na</Item>
              <Exec>pBucket = pBucket->m_pNext</Exec>
            </Loop>
          </CustomListItems>
        </Expand>
    </Type>
    

    TreeItems 展开

    如果可视化类型表示一个树,则调试器可以通过使用 TreeItems 节点遍历该树并显示其子级。 下面是使用 TreeItems 节点的 std::map 类型的可视化效果:

    XML
    <Type Name="std::map&lt;*&gt;">
      <DisplayString>{{size = {_Mysize}}}</DisplayString>
      <Expand>
        <Item Name="[size]">_Mysize</Item>
        <Item Name="[comp]">comp</Item>
        <TreeItems>
          <Size>_Mysize</Size>
          <HeadPointer>_Myhead->_Parent</HeadPointer>
          <LeftPointer>_Left</LeftPointer>
          <RightPointer>_Right</RightPointer>
          <ValueNode Condition="!((bool)_Isnil)">_Myval</ValueNode>
        </TreeItems>
      </Expand>
    </Type>
    

    语法类似于 LinkedListItems 节点。 LeftPointerRightPointerValueNode 是在树节点类的上下文中计算的。 ValueNode 可以保留为空或使用 this 来引用 TreeItems 节点本身。

    ExpandedItem 展开

    通过将基类或数据成员的属性显示为可视化类型的子项,ExpandedItem 元素生成了一个聚合的子视图。 调试器将计算指定的表达式,并将结果的子节点附加到该可视化类型的子列表中。

    例如,智能指针类型 auto_ptr<vector<int>> 通常显示为:

    自动_ptr<向量<int> >默认扩展

    要查看矢量的值,就必须在变量窗口中穿过 _Myptr 成员,向下深入两个级别。 通过添加 ExpandedItem 元素,就可以从层次结构中去除 _Myptr 变量并直接查看矢量元素:

    XML
    <Type Name="std::auto_ptr&lt;*&gt;">
      <DisplayString>auto_ptr {*_Myptr}</DisplayString>
      <Expand>
        <ExpandedItem>_Myptr</ExpandedItem>
      </Expand>
    </Type>
    

    自动_ptr<向量<int> > ExpandedItem 扩展

    下面的示例介绍了如何将基类的属性聚合到一个派生类中。 假定 CPanel 类派生自 CFrameworkElementCFrameworkElement 节点的可视化会将基 ExpandedItem 类的属性附加到 CPanel 类的子列表中,而不是重复这些属性。

    XML
    <Type Name="CPanel">
      <DisplayString>{{Name = {*(m_pstrName)}}}</DisplayString>
      <Expand>
        <Item Name="IsItemsHost">(bool)m_bItemsHost</Item>
        <ExpandedItem>*(CFrameworkElement*)this,nd</ExpandedItem>
      </Expand>
    </Type>
    

    关闭派生类的可视化匹配的 nd 格式说明符肯定在这。 否则,*(CFrameworkElement*)this 表达式将导致再次应用 CPanel 可视化,因为默认可视化类型匹配规则认为它是最适合的类型。 使用nd格式说明符指示调试器使用基类可视化,如果基类没有可视化效果,则使用默认扩展。

    Synthetic Item 展开

    ExpandedItem 元素通过消除层次结构提供更简单的数据视图,Synthetic 节点则恰好相反。 它允许您创建不是表达式结果的人工子元素。 人工智能元素可以具有其自己的子元素。 在下面的示例中, Concurrency::array 类型的可视化效果使用 Synthetic 节点向用户显示诊断消息:

    XML
    <Type Name="Concurrency::array&lt;*,*&gt;">
      <DisplayString>extent = {_M_extent}</DisplayString>
      <Expand>
        <Item Name="extent" Condition="_M_buffer_descriptor._M_data_ptr == 0">_M_extent</Item>
        <ArrayItems Condition="_M_buffer_descriptor._M_data_ptr != 0">
          <Rank>$T2</Rank>
          <Size>_M_extent._M_base[$i]</Size>
          <ValuePointer>($T1*) _M_buffer_descriptor._M_data_ptr</ValuePointer>
        </ArrayItems>
        <Synthetic Name="Array" Condition="_M_buffer_descriptor._M_data_ptr == 0">
          <DisplayString>Array members can be viewed only under the GPU debugger</DisplayString>
        </Synthetic>
      </Expand>
    </Type>
    

    Concurrency:: Array 与综合元素扩展

    HResult 元素

    借助 HResult 元素,你可以自定义显示在调试器窗口中的 HRESULT 信息。 HRValue 元素必须包含要自定义的 HRESULT 的 32 位值。 HRDescription 元素包含要显示在调试器窗口中的信息。

    XML
    
    <HResult Name="MY_E_COLLECTION_NOELEMENTS">
      <HRValue>0xABC0123</HRValue>
      <HRDescription>No elements in the collection.</HRDescription>
    </HResult>
    

    UIVisualizer 元素

    UIVisualizer 元素用于向调试器注册图形可视化工具插件。 图形可视化工具会创建一个对话框或其他界面,用符合其数据类型的方式显示变量或对象。 可视化工具插件必须被编写为 VSPackage,并且必须公开一项调试器可以使用的服务。 .Natvis 文件包含插件的注册信息,例如名称、所公开服务的 GUID 以及它可以直观显示的类型。

    下面是 UIVisualizer 元素的示例:

    XML
    <?xml version="1.0" encoding="utf-8"?>
    <AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
        <UIVisualizer ServiceId="{5452AFEA-3DF6-46BB-9177-C0B08F318025}"
            Id="1" MenuName="Vector Visualizer"/>
        <UIVisualizer ServiceId="{5452AFEA-3DF6-46BB-9177-C0B08F318025}"
            Id="2" MenuName="List Visualizer"/>
    .
    .
    </AutoVisualizer>
    
    • ServiceId - Id 属性对用来标识 UIVisualizerServiceId 是可视化工具包公开的服务的 GUID。 如果服务提供了多个可视化工具,Id 是用来区分它们的唯一标识符。 在上面的示例中,同一个可视化工具服务提供了两个可视化工具。

    • MenuName 属性用于定义可视化工具名称,该名称会显示在调试器的放大镜图标旁边的下拉列表中。 例如:

      UIVisualizer 菜单快捷菜单

    .natvis 文件中定义的每种类型都必须明确列出能够显示它的所有 UI 可视化工具。 调试器会将类型条目中的可视化工具引用与注册的可视化工具相匹配。 例如,下面的 std::vector 类型条目引用的是前一个示例中的 UIVisualizer

    XML
    <Type Name="std::vector&lt;int,*&gt;">
      <UIVisualizer ServiceId="{5452AFEA-3DF6-46BB-9177-C0B08F318025}" Id="1" />
    </Type>
    

    你可以在用于查看内存中位图的图像监视扩展中查看 UIVisualizer 的示例。

    CustomVisualizer 元素

    CustomVisualizer 是一个扩展点,用于指定你编写的 VSIX 扩展,以便在 Visual Studio 代码中控制可视化效果。 有关编写 VSIX 扩展的更多信息,请参阅 Visual Studio SDK。

    编写自定义可视化工具比 XML Natvis 定义要费事得多,但 Natvis 在支持方面的限制对你没有影响。 自定义可视化工具有权访问所有的调试器扩展性 API,因此可以查询和修改调试对象进程,也可以与 Visual Studio 的其他部分通信。

    可以在 Condition 元素上使用 IncludeViewExcludeViewCustomVisualizer 属性。

    限制

    Natvis 自定义项适用于类和结构,但不能使用 typedef。

    Natvis 不支持用于基元类型的可视化工具(例如 intbool)或指向基元类型的指针。 在此方案中,一种选择是使用适合用例的格式说明符。 例如,如果您在代码中使用 double* mydoublearray,则可以在调试器的 "监视" 窗口中使用数组格式说明符,如表达式 mydoublearray, [100],这将显示前100个元素。

  • 相关阅读:
    自己搭建一个vue项目
    nodejs 后台开发入门
    bootstrap table入门例子
    datatable入门
    猜数字案例
    Cookie
    管理系统案例
    PHP操作数据库(以MySQL为例)
    数据库(以MySQL为例)
    案例:音乐列表
  • 原文地址:https://www.cnblogs.com/yilang/p/12485431.html
Copyright © 2011-2022 走看看