在VS2008中定制Visual C++项目向导

本文事实上是参考文献[1]的一篇翻译,但有些我不认为重要的地方省略了。本文虽然是针对AddProject向导展开的,但同样适用于AddItem和AddClass向导。

另外,对VSExpress版本和其它VS版本,这些向导文件的存放位置有所不同,但不难找到。

对于JS代码的编写,很多时候需要参考文献[2]和[3]的帮助,另外,一个很好的方式是去阅读现有向导的JS代码,差不多都能找到想要的功能。

之前写的两个向导可以参考:用于VS2008的CppUnit项目向导     VS2008 C++ 文件模板向导


1. 向导执行过程

1.1 弹出项目模板列表对话框

    当用户打开“新建项目”对话框时,VS Shell将通过注册表查找所有已安装的项目模板。首先,在注册表项 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\Projects 中枚举所有类型的项目,并查找名称为 ProjectTemplatesDir 的字符串值,该值的数据就是存放该类项目的所有项目模板的目录(模板目录);然后,VS Shell从模板目录中读取所有项目模板的向导描述文件(*.vsz)和目录描述文件(*.vsdir)。最后,VS Shell在“新建项目”对话框中显示这些项目模板。
    同样,对于添加新项目、添加新项、添加类……,也是类似的一个过程。
    在新建项目、添加新项、添加类时,每一个向导都一个图标显示,该图标文件在两个位置进行查找:向导文件所在目录或者目录描述文件所指定的位置。

表1 向导类型
类型
目的
位置
是否可扩展
新建项目
创建一个新的项目
“新建项目”对话框
Yes
添加新项
向项目添加文件
“添加新项”对话框
Yes

向项目添加类
“添加类”对话框
Yes
代码
向项目添加代码
右键单击类视图节点
No

2.2 启动向导和初始化上下文

    当用户选择了一个项目向导,并单击“确认”按钮之后,VS Shell使用.vsz文件中第二行的ProgID来创建 向导引擎 (一个COM对象)。比如,对于VC项目,引擎名为 VSWizard ,ProgID为 VsWizard.VsWizardEngine.9.0

2.2.1 向导引擎

    VSWizard引擎为向导提供了框架和一些可使用的辅助函数。向导的用户接口(UI)主要是HTML;后端是JScript,其能够完全访问VS的富对象模型。VSWizard引擎的工作是布局UI和在用户点击“完成”时执行后端脚本。 它也提供一些辅助函数来增强对象模型,这些辅助函数能够提供那些复杂的或者难实现的功能。 例如,引擎暴露了一个API来弹出OleDB对话框,这在脚本中是不可实现的。请参考 VCWizCtl Object Properties, Methods, and Events  ,以获得所有控件API的完整列表。

2.2.2 符号表

    .vsz文件还包含了一些传递给向导引擎的参数,这些参数可用于定制向导行为。当VSWizard引擎实例化时,它会创建一个符号表来存储这些参数,并在 符号表中记录用户在UI中的所有选择。该符号表为“名字/值”对的集合。除了.vsz文件中的那些自定义参数,VS Shell,这些参数提供了当前项目和引擎运行环境的详细信息。对于上下文参数,它们只能由VS Shell设置,但可以在向导的任何地方使用。

2.2.3 定位向导文件

    一旦引擎初始化完毕,它将按如下步骤定位向导的UI和脚本文件:
    (1)向VS Shell查询VS安装位置的注册表项。
    (2)打开查询得到的注册表项的setup子项,作为当前注册表项。
    (3)从.vsz文件中取得符号PROJECT_TYPE的值,如果没有指定则默认为C++项目。对于C++,该值为VCPROJ,向导引擎把它映射到注 册表项“VC”;对于第三方语言没有这种自动映射,因此引擎直接在setup子项下查找PROJECT_TYPE值。把该子项作为当前注册表项。
    (4)引擎在当前注册表项下查找ProductDir字符串值,并把该值保存在PRODUCT_INSTALLATION_DIR符号中。
    (5)引擎在*.vsz文件中查找参数RELATIVE_PATH或参数ABSOLUTE_PATH。如果找到RELATIVE_PATH,那么它的值被 追加到PRODUCT_INSTALLATION_DIR符号的值后,得到的新路径就为向导文件所在目录。如果找到的是ABSOLUTE_PATH,那么 它的值就是向导文件所在目录。如果两个符号都没找到,那么引擎把默认的向导目录VCWizards添加到 PRODUCT_INSTALLATION_DIR符号的值后,作为向导文件所在目录。
    (6)一旦引擎找到向导文件所在目录,它将确定向导文件在哪个子目录中,这是通过.vsz文件中的WIZARD_NAME参数确定的。引擎最终把此子目录保存在符号START_PATH中供向导使用。

2.2.4 向导文件布局

    在START_PATH目录中,通常有四个子目录:html、image、scripts和templates。html目录存放着作为UI的HTML文 件;images目录存放着UI所使用的图片文件;scripts目录存放着用于控制向导行为的脚本文件;而templates目录存放着向导用来产生输 出的模板文件。
    每一个向导可以有文件的多个本地化版本,以便向导能够用于不同的区域设置。因此,真正的文件可能在每个语言代码页的子目录下。另外,使用<VS Installation Directorty>\VC\VCWizards\<locale>中的内容来显示和执行向导,所有通用脚本函数被放在 common.js文件中。区域设置是在安装VS时确定的,但可以通过devenv.exe的命令行参数/LCID改变。

2.3 执行向导

    一旦初始化完成,引擎需要确认向导是否有UI,这是通过检查.vsz文件中的WIZARD_UI参数来完成的(如果没有设置此参数,则默认有UI)。如果 该参数为TRUE或者完成没有指定,那么引擎打开一个对话框,并从<START_PATH>\html\<locale>目录中 加载主向导HTML页(default.htm)。该页使用符号表中的信息来寝化它的状态,并基于用户的选择向符号表中添加符号。可以通过HTML文件 的<symbol>标签或者JScript来向符号表中添加新的符号,例如:
    <symbol NAME="CUSTOM_SYMBOL_1" TYPE="text" VALUE="My value"></symbol>
或者,
    wizard.AddSymbol("CUSTOM_SYMBOL_2", "Another value");
其中,<symbol>标签的TYPE属性有如下几种取值:text、checkbox、radio和bool。

    HTML页可以有到其它HTML页的链接。在显示下一页之前,当前页必须把它的状态保存在符号表中,未保存的状态信息将丢失。

    当用户单击向导的“完成”按钮时,当前HTML页的OnFinish方法将被调用。该方法通常会调用common.js中的OnWizFinish方法, 而OnWizFinish方法又会去调用向导引擎的Finish方法。该方法将定位并加载<START_PATH>\scripts \<locale>目录下的default.js文件,并调用其中的OnFinish方法。简而言之,当用户单击“完成”按钮时,将导致 default.js中的OnFinish方法被执行。

    在大多数情形下,向导的核心功能都是在default.js文件的OnFinish方法中。用户选择与脚本是通过符号表进行通信的,具体而言,是通过向导引擎接口暴露的FindSymbol和AddSymbol方法来查询和修改符号的。

2.3.1 通用OnFinish行为

    对于“新建项目”向导,OnFinish方法的动作基本上是标准化的。它们首先创建一个项目,然后在模板目录中读取templates.inf文件,并确 定哪些模板需要分析和加入到项目中。基于符号集,对templates.inf中的文件进行有选择地分析、处理和添加到项目中。

    templates.inf文件和模板文件本身都可以使用符号表中的值来控制产生的项目。比如,根据符号的定义来在产生的项目中排除或包含模板文件,把符号的值嵌入到输出中,等等。后面的“模板文件”一节将对此进行详细讨论。

    在分析完一个模板文件后,函数GetTargetName将被调用。该函数通常是在向导的default.js文件中实现的,被用来给从模板文件产生的文件赋予一个最终的名字。
    在分析、处理、添加完所有文件后,向导通常还需要设置一些项目选项,这还是通过脚本来实现。
    “添加新项”向导也使用相同的机制,只是它们是向已有项目添加文件;而“代码”向导却更加多样化,其OnFinish方法的动作高度依赖于向导目的。

2. 深入向导架构

2.1 向导描述文件

    .vsz文件包含了启动向导所需的一些信息,每个向导都必须有一个.vsz文件。.vsz文件内容如下:
    (1)文件第一行通常是“VSWIZARD 7.0”。这里的版本是指文件格式的版本,而不是使用此文件的产品的版本。
    (2)第二行指定了向导的ProgID。对于VS2008,该值为VSWizard ProgID: Wizard=VsWizard.VsWizardEngine.9.0。
    (3)文件其它行的每一行包含一个用户定义的自定义参数,其格式为: Param="<PARAM_NAME> = <PARAM_VAL>"。 “自定义参数”事实上是一种误称,因此某些参数(如WIZARD_NAME)有特殊的意义。下表中的参数服务于向导引擎的特殊目的,因此它们的名字是保留的。

表2 内置的向导符号
参数名
描述信息
__<ANY NAME>
以双划线开始的符号名由Microsoft保留为内部使用。
ABSOLUTE_PATH
指定了向导文件所在位置的绝对路径。
FALLBACK_LCID
如果向导不支持当前产品区域,该参数可以用来为向导指定一个默认的区域。默认值为1033(英语)。
PREPROCESS_FUNCTION
该参数指定了向导执行之前需要先调用的函数名;只有当它返回TRUE,向导才会执行。该函数必须在common.js或default.js中声明。该功能可以用来确定向导是否应该运行。
PROJECT_TYPE
指定了项目的类型。如果该值没有指定,则引擎默认为C++项目。
RELATIVE_PATH
指定了向导文件所在位置的相对路径,相对于PRODUCT_INSTALLATION_DIR。
WIZARD_NAME
在对应的向导存储目录中的向导目录名。设定该参数是强制的。
WIZARD_UI
如果该参数设置为FALSE,那么向导没有UI,并将立即调用default.js中的OnFinish方法。如果没有指定该参数,那么默认是有UI的。

2.2 目录描述文件

    .vsdir文件描述了如何把向导信息呈现给用户。例如,当用户打开VC++的“新建项目”对话框时,向导引擎通常会读取VCProjects目录下的所有.vsdir文件来获得可用的向导信息。
    .vsdir文件的每一行标识了一个向导,并且必须以换行符结束(包括文件的最后一行)。每一行的参数必须按照下表的顺序,并且以符号‘|’分隔。对于可选的参数,如果不指定必须用一个空格代替。

表3 VSDIR 条目参数

描述
是否可选
Vsz 文件名
向导的.vsz文件路径名。
No
包标识符
该GUID代表了包含本地化向导资源的VS包,该域是可选的。大多数自定义向导的此域为空。
Yes
名称
向导名,可以是一个字符串或者一个VS包资源ID(以#ResID的形式)。
Yes
优先级
向导优先级的数字值。小优先级值的向导将显示在前面。
No
描述
一个字符串或一个VS包资源ID(以#ResID的形式),描述了向导的目的。
No
图标容器标识符
包含了用于显示向导的图标资源的可执行或DLL文件的路径名。它可以是一个VS包GUID。大多数自定义向导此域留空,并在.vsz文件所在目录下安装一个同名的图标文件。
Yes
图标标识符
图标容器内的图标资源标识符。大多数自定义向导不会使用此域。
Yes
标志
用于指定UI选项。可用的值参见表4,可以通过“或”运算对这些值进行组合。
No
建议名
该名字用于构造一个建议的项目名。它可以是一个字符串或一个资源标识符(以#ResID的形式)。向导引擎会追加一个连续的数字来使名字唯一。
No

表4 VSDIR 用户接口标志

描述
1
使用非本地UI和保存机制。
2
创建一个空解决方案而非一个项目。
4
禁用浏览按钮。
8
不追加扩展名到产生的名字
32
禁用“位置”域。
4096
不初始化名称域。
8192
禁用名称域。

    关于.vsdir文件的更多信息请参考  这里  。

2.3 符号表

    符号表是向导在执行过程中收集和使用信息的字典。每一个符号由一个区分大小写的名字和一个值构成。当向导被创建时,符号表包含在.vsz文件中指定的自定义参数和一些基于项目类型的参数。之后,向导可能会基于用户的动作向符号表中添加更多的符号。

表5 向导引擎自动生成的符号
参数名
描述
CLOSE_SOLUTION
该符号指示了在调用向导时是否需要关闭解决方案。对于项目向导,它的值依赖于用户在“新建项目”对话框中指定的选项;对于其它向导类型,其值为false。
DOCUMENT_FIRST_LOAD
当第一次载入向导的HTML页时,该符号被设置为true。HTML页使用该符号来确定什么时候为它的控件设置默认状态。该符号仅为拥有HTML UI的向导设置。
HTML_PATH
向导的HTML页所在目录。默认为<START_PATH>\html\<LangID>,其中,LangID为语言标识符。
IMAGES_PATH
向导的图片文件所在目录。默认为<START_PATH>\images。
ITEM_NAME
对于“添加新项”向导,其存储了添加的项的名字。对其它向导类型没有定义。
PARENT_NAME
当从一个类视图代码元素调用一个代码向导时(例如,添加成员函数向导),该符号存储父对象的名字。对于添加新项向导,它包含了文件名。对其它向导没有定义。
PRODUCT_INSTALLATION_DIR
产品安装目录。产品是由.vsz文件中的PROJECT_TYPE自定义参数确定的。该值从注册表项HKLM\SOFTWARE\Microsoft\VisualStudio\8.0\Setup\<Product>\ProductDir
获得,其中<Product>为VS所支持任何语言。
PROJECT_NAME存储当前项目或者由项目向导产生的项目的名称。
PROJECT_PATH
对于项目向导,该符号代表项目的创建位置;对其它向导,它代表当前项目的位置。
PROJECT_TEMPLATE_PATH
项目模板所在目录。例如,VC++包含三种不同的项目模板(default.vcproj、defaultsql.vcproj和DefaultTest.vcproj)。对于VC++,该值为<VS根目录>\VC\VCWizards。
SCRIPT_COMMON_PATH
公共向导文件所在目录(样式表、图片和commom.js)。该值依赖于PRODUCT_INSTALLATION_DIR和语言标识符。例如,VC++中文版为<VS根目录>\VC\VCWizards\2052。
SCRIPT_PATH
向导的脚本文件所在目录。默认为<START_PATH>\scripts\<LangID>。
START_PATH
向导的起始目录。向导引擎基于PRODUCT_INSTALLATION_DIR及RELATIVE_PATH或ABSOLUTE_PATH来确定该值。
TEMPLATES_PATH
向导的模板文件所在目录。默认为<START_PATH>\templates\<LangID>。
VS_INSTALLATION_DIR
VS安装目录,仅对项目向导定义。该值通常为<VS根目录>\Common7\IDE。
WIZARD_TYPE
该符号标识正调用的向导类型,有三个值:
(1)添加新项向导:{0F90E1D1-4999-11D1-B6D1-00A0C90F2744}
(2)添加项目:{0F90E1D2-4999-11D1-B6D1-00A0C90F2744}
(3)新建项目:{0F90E1D0-4999-11D1-B6D1-00A0C90F2744}

2.4 模板文件

    模板文件为向导产生文件提供了一种灵活的方式。模板文件的语法非常简单,指令可以在  这里  找到。文件templates.inf是其它模板文件的总清单,而它本身也是一个模板文件。templates.inf的格式如下:
    (1)每一行(除了模板指令)标识了一个将添加到项目中的模板文件。
    (2)每一行(包括文件的最后一行)必须以换行符结束。
    (3)文件中的文件名都是相对于templates.inf文件的位置的。
    (4)文件名之前可以有可选的标志。如果有标志,那么它们它们之间以及和文件名之间用符号“|”分隔。可用的标志如下表所示。

表6 可选的文件标志
标志名
描述
ChildOf(<PARENT>)
在产生的项目中,产生的文件将作为指定的父文件的一个子节点。注意<PARENT>应该引用一个父模板文件名,而不是项目中产生的文件的名字。
CopyOnly
对应的模板文件将被拷贝到目标目录而不会发生修改(可以重命名文件):不会发生符号替换和模板指令处理,模板文件的代码页也不会改变。
OpenFile
产生的文件在项目创建完成后将在默认的编辑器中打开。

3. 创建一个自定义向导

    创建自定义向导有两种方式:其一,修改现有的向导,以满足自己的需要;其二,使用“自定义向导”向导来产生一个向导骨架,并设计自己的向导。这里只讨论第二种方式。
    “自定义向导”产生的向导项目的文件如下表所示。

表7 自定义向导文件

向导信息文件
“新建上期”对话框使用这些文件来显示向导信息。这些文件主要有:.vsz文件、vsdir文件和一个图标文件。
用户接口文件
HTML页、图片和样式表
default.js脚本文件
该JScript文件包含了用于产生项目的函数。
templates.inf模板列表文件
列出了所有需要被添加到产生的项目中的文件。
模板文件
向导用来产生项目的文件。默认情况下,有两个文本文件和一个基本的VC++项目文件(.vcproj)。

    一旦创建好了向导骨架,需要进行如下工作:
    (1)修改.vsdir和.vsz文件。
    (2)为向导添加或修改一个图标。
    (3)创建和修改向导的HTML页。
    (4)创建需要添加到产生的项目中的模板文件。
    (5)写JScript代码来执行向导中的动作。
    (6)测试和调试向导。
    (7)为不同的区域本地化向导。
    (8)部署向导。

3.1 修改 .vsdir 和 .vsz 文件

    这些文件内容前面已经进行了介绍,这里只讨论一些额外的注意事项:
    (1)如果你修改了一个现有向导,.vsdir文件很可能引用了与VS一块发布的一个二进制包的资源。确保你会在自己的.vsdir文件中添加一个新行来引用你自己的资源。
    (2)你必须决定是使用 RELATIVE_PATH 还是 ABSOLUTE_PATH。默认产生的.vsz文件是使用的 ABSOLUTE_PATH 。
    (3)如果需要删除一个自定义向导,必须从产生安装目录中删除相应的.vsz、.vsdir和图标文件。

     在VCProjects子目录中,可以创建子目录,这样该子目录会出现在“新建项目”对话框左边的目录树中,并可以使用类似语句“MyProjects| |我的项目|1”对对话框中出现的名字进行修改。在.vsdir文件中如果用到了相对路径,一定要带上“.”和“..”来引用当前目录和父目录,否则会因 找不到而被忽略。

3.2 添加或修改图标文件

    自定义向导的图标文件通常和.vsz文件放在同一目录下,并与.vsz文件有相同的名字。由于“新建项目”对话框有两种图标列表模式(大图标和小图标),因此需要为图标文件创建两个不同的分辨率。

3.3 创建HTML页

    VS有一个HTML编辑器,即支持设计视图也支持源码视图。向导页在IE的自定义区域内运行,该区域允许执行JScript代码和访问Safe-for-scripting的Active控件,但是禁止很多其它操作。
    确保所需要的信息都保存在符号表中。

3.4 创建模板文件

    模板文件很容易通过“新建项目”和“添加新项”向导创建。需要特别注意templates.inf文件中每一个模板文件的标志,因此它们可能会影响本地化和项目部署。

3.5 为向导写JScript代码

    JScript代码是向导的核心功能所在。最起码,你不得不写default.js文件中的OnFinish方法。如果向导使用了模板文件,还必须实现GetTargetName方法。
    向导的脚本会在两种不同的环境中执行:在向导的UI阶段,是在IE内执行;在单击“完成”后,是在主机的脚本环境中执行。在两种情形中,common.js都会在你的代码被向导引擎加载之前包含进来,以便你可以使用其中的功能。
    向导引擎是一个COM对象,向脚本代码暴露了一些辅助函数。在前面的两种执行环境中,访问此对象的方法有些许不同:在HTML页中使用的是 windows.external 对象,而在default.js中使用的是 wizard 对象。例如,为了查找一个符号的值,在HTML页中调用 window.external.FindSymbol("PROJECT_NAME") ,而在default.js中调用 wizard.FindSymbol("PROJECT_NAME")  。如果你想写一段能够在两种环境中都可以使用的代码,那么可以这样做:
    var oWizard;
    try
    {
        oWizard = wizard;
    }
    catch (e)
    {
        oWizard = window.external;
    }
    oWizard.FindSymbol("PROJECT_NAME")

    要想获得可以调用的完整API列表,可以打开VSWizard.dll的typelib,或者参考MSDN文档   VCWizCtrl   。

3.5.1 错误报告

    向导引擎提供的一个重要功能是向用户报告错误的功能。向导引擎的SetErrorInfo方法被用来设置在退出向导时显示的错误信息,该方法以一个 Error对象作为参数。另一个方法是ReportError,立即显示错误信息,并把控制权返回给向导代码。ReportError以一个字符串为参 数,但也可以不提供参数直接调用,此时将显示先前SetErrorInfo设置的错误信息。

3.5.2 DTE属性

    向导引擎对象的DTE(Developer Tools Environment)属性使你能够访问VS的富对象模型,更多信息请参考  这里  。下面对项目模型和代码模型进行简要介绍。

3.5.3 项目模型(Project Model)

    VS对象模型中的项目模型可以让你以编程的方式与项目进行交互,改变它的属性(比如编译选项)、添加或删除项目项(比如代码或资源文件)。主要接口如下表所示。

表8 项目模型接口
Project
这个接口代表一个VC++项目。它可以用于改变属性(比如项目名或生成配置)或者执行一个动作(比如保存)。
ProjectItem
这个接口代表独立的项目子元素。它可以用于改变它的属性或执行一个动作。注意ProjectItem自身可以有嵌套的项目项。

    项目创建向导在调用CreateProject方法时获得被创建的项目的引用。所有其它向导获得项目实例的引用是通过default.js文件的OnFinish函数的参数。项目模型的详细信息请参考  这里

3.5.4 代码模型

    通过向导控制函数,很容易创建新的项目和向已有项目添加新的文件。然而,有时你不得不向已有文件添加代码或修改已有代码。此时,可以利用代码模型。它分析代码并为向导提供一种方便的方式来理解和修改已有代码。主要接口如下表所示。

表9 代码模型接口
CodeModel
该对象是项目代码树的根。它的CodeElements属性是一个集合,包含了所有在项目的全局名字空间中声明的代码元素(比如,类、函数、变量,等等)。
FileCodeModel
该对象与CodeModel非常类型,只是它只代表了一个文件的代码树的根(而不是整个项目)。
CodeElement
这个接口代表了项目文件中声明的一个特定代码元素。每一个代码元素都可以有嵌套的子元素,例如名字空间可以有嵌套的类甚至名字空间。
它的Children属性是一个集合,包含了所有嵌套的子元素。
语言构建接口
这是一组从CodeElement派生而来的接口,提供了特定于每一类代码元素的功能。例如,CodeClass、CodeStruct、CodeVariable,等等。
C++特定接口
这是一组对象,提供了访问特定于语言的各种元素的接口。例如,VCCodeClass、VCCodeFunction,等等。

    关于代码模型的更多信息请参考  这里
    为了引用这种富对象模型的对象、接口、方法和属性,请参考  这里

3.6 调试

    为了调试向导的HTML和脚本文件,必须首先启用脚本调试:在IE的“工具 | Internet 选项 | 高级”,清除浏览标签下的“禁用脚本调试”复选框。之后,可以把另一个VS进程作为脚本调试器附在你的当前VS进程上。可以在HTML文件和 default.js文件及其它脚本文件的脚本块中设置断点。

3.7 本地化

    默认的向导模板被设计为易于本地化的。基于这个原因,所有向导都有模板文件、脚本文件和HTML文件在各自的本地化子目录中,比如,英文版是在1033子 目录中,而中文版是在2052子目录中。VS能够根据区域设置自动访问相应的区域子目录,如果没有找到,则VS会根据FALLBACK_LCID符号来确 定默认的区域。

3.8 部署

    部署向导的工作仅仅是把相应的文件复制到正确的位置。把.vsdir文件、.vsz文件及图标文件复制到正确的产品目录中;把所有的HTML文件、脚本文件、模板文件和其它文件复制到正确的位置(与.vsz保持一致)。

4. 总结

    乍一看,VS向导架构过于复杂。的确,向导由JScript、HTML、模板、图片、甚至一些自定义文件组成。这表面上的复杂性是为了满足使向导引擎尽可 能灵活的需要。我们可以很方便地对现有向导进行修改以满足我们的需要,也可以很容易地定制全新的向导。此外,对向导进行本地化也相当容易。

5. 参考

[1]  Inside Visula C++ Wizards
[2]  Visual C++ Extensibility Object Model
[3]  Automation and Extensibility Reference
[4]  Creating your own Visual Studio Project Template
[5]  Custom wizards in MS Visual Studio 2003 for C++ projects
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值