用于添加更多项目配置的 Visual Studio 向导
本文介绍如何使用 Visual Studio 向导引擎自动化 Visual C++ 项目配置。
引言
毫无疑问,Visual Studio 是任何严肃项目不可或缺的强大生产力工具。VS 的一大优点是提供了大量的向导来帮助生成新项目和添加新项。这些向导通过自动化重复性和频繁的任务来节省大量时间。此外,微软的开发人员也认识到,向导无法满足开发者避免重复性任务的所有需求。因此,他们想出了许多扩展开发环境的方法,包括宏、外接程序和向导。方法的选择取决于多种因素,包括对每种方法常用编程语言的熟悉程度。例如,如果程序员擅长 VB.NET,那么他/她很可能会使用宏或外接程序。另一方面,如果他/她擅长 HTML/JavaScript,那么就会选择 Visual Studio 中的标准向导模型。
VC++ 程序员通常使用静态库来收集常用代码,以便在多个项目中重复使用。作为任何静态库开发的起点,他们通常使用“添加新项目”菜单项,并选择“Win32 项目”来生成项目文件。Win32 项目向导只会为项目添加两个配置:Release 和 Debug。但这两个配置对于实际项目来说远远不够。在我过去参与的许多项目中,我通常会为每个库添加 8 个 32 位配置。
- Debug 配置,使用 ANSI 字符集,动态链接到 CRT
- Debug 配置,使用 Unicode 字符集,动态链接到 CRT
- Debug 配置,使用 ANSI 字符集,静态链接到 CRT
- Debug 配置,使用 Unicode 字符集,静态链接到 CRT
- Release 配置,使用 ANSI 字符集,动态链接到 CRT
- Release 配置,使用 Unicode 字符集,动态链接到 CRT
- Release 配置,使用 ANSI 字符集,静态链接到 CRT
- Release 配置,使用 Unicode 字符集,静态链接到 CRT
此处,CRT 代表“C 运行时库”。
除了手动添加这些配置之外,我还需要做三件事:
- 选择适当的输出库命名方案,以区分不同的配置。例如,如果库的基本名称是 MyBeautifullLib,则“Debug, Unicode, Dynamic CRT”配置将生成 MyBeautifullLib_U_DCRT_D.Lib,而“Release, ANSI, Dynamic CRT”配置将生成 MyBeautifullLib_DCRT.Lib。
- 确保所有库输出文件都复制到通用的“Lib”文件夹。
- 编写一个自动链接头文件,该文件会自动链接与使用应用程序的配置匹配的库文件。此文件使用
#pragma comment(lib,..."
指令,告诉链接器选择特定版本的库。
我过去曾手动为每一个库执行此操作,而我有很多库!我浪费了太多时间在这种开销上,直到我了解了 VS 中的向导引擎。
本文的其余部分将解释我是如何自动化这些步骤的。我希望这篇文章对您有所帮助。
这篇文章适合我吗?
在继续阅读之前,我想说明一下:如果您满足以下条件,可以继续阅读本文:
- 您有十几个或更多的 C 或 C++ 静态库,并且在许多 VC 项目中使用它们,并且有许多配置。您可能会发现本文中的向导和技巧将为您节省许多工作时间。
- 您想自定义 VC 中标准向导生成代码和文件的方式。
- 您只是对那些称为 VC 向导的小东西感到好奇……
如果您不属于以上任何一种情况,您可以到此为止。
背景
在 Visual Studio 中,向导是实现 IDTWizard
接口的任何 COM 对象。我不会重复 MSDN 在线 上提供的所有详细信息。在标准向导模型中,实现 IDTWizard
的 COM 对象是 VCWizCtl
,其 ProgID
为“VsWizard.VsWizardEngine”。VS 随附的大多数向导都依赖于此对象来添加新项目和新项。
此向导允许您使用 HTML 和 JavaScript 自动化添加新项和新项目的过程。为此,您必须告诉此向导在哪里可以找到构成向导的 HTML、JavaScript 和其他文件。
- 为您的向导创建一个目录。我将其命名为 CodeRock_CPPLibrary。
- 在新目录中创建四个子文件夹:HTML、SCRIPTS、IMAGES(可选)和 TEMPLATES。
- 在 HTML、SCRIPTS 和 TEMPLATES 文件夹中,创建一个带有目标用户语言区域 ID 的子目录。对于英语,区域 ID 是 1033。这是因为您的 HTML 对话框、脚本和模板文件可能需要本地化为不同的语言和区域。
- 您的“HTML\1033”子文件夹中应有一个 default.htm 文件,您的“SCRIPTS\1033”子文件夹中应有一个 default.js 文件。default.htm 文件是向导向用户显示的第一个页面。
- 至少,您的 default.js 文件中**应该**有一个
OnFinish(selProj, selObj)
函数。当用户在您的页面上点击“完成”或通过其他方式操作时,向导将调用此函数。 - 开发您的其他页面,并在 default.htm 文件中添加指向它们的导航链接。
- 创建一个 .vsz 文件和一个图标文件用于您的向导。在 .vsz 文件中,使用命名的 PARAM 变量,告诉标准向导您的文件位置等信息。
- 将您新创建的 .vsz 文件和图标文件复制到 VS 安装目录下的相应目录。对我来说,我关心的是 VC++ 项目,所以我将我的 .vsz 文件复制到 C:\Program Files\Visual Studio 2005\vc\vcprojects 文件夹。
举个例子:当您在 VS 中点击“添加新项目”时,您会看到一个项目类型列表。如果您在“Visual C++”下选择“Win32 项目”并点击“确定”,您将看到一系列对话框,如下所示:
此对话框实际上是一个用 HTML 和 JavaScript 编写的网页。此项目类型的 vsz 文件是 $(VCInstallDirectory)\vcprojects\Win32Wiz.vsz。在该文件中,您会发现以下内容:
VSWIZARD 7.0
Wizard=VsWizard.VsWizardEngine.8.0
Param="WIZARD_NAME = Application"
Param="RELATIVE_PATH = VCWizards\AppWiz\Generic"
Param="CONSOLE_TYPE_ONLY = false"
Param="WIZARD_ID = 76"
此文件告诉 VS 使用 VsWizard.VsWizardEngine.8.0
作为处理此类项目的 COM 对象。VS 会创建一个该 COM 对象的实例,并将参数传递给向导。然后,向导会通过 RELATIVE_PATH
和 WIZARD_NAME
参数确定 HTML 和脚本文件的路径。在此,完整路径为 $(VCInstallDir)\VCWizards\AppWiz\Generic\Application。例如,在我的计算机上,VCInstallDir
是“C:\Program Files\Microsoft Visual Studio 8\VC\”,因此向导文件夹的完整路径为:
- HTML 文件夹是:“C:\Program Files\Microsoft Visual Studio 8\VC\VCWizards\AppWiz\Generic\Application\html”
- SCRIPTS 文件夹是:“C:\Program Files\Microsoft Visual Studio 8\VC\VCWizards\AppWiz\Generic\Application\scripts”
- TEMPLATES 文件夹是:“C:\Program Files\Microsoft Visual Studio 8\VC\VCWizards\AppWiz\Generic\Application\templates”
- IMAGES 文件夹是:“C:\Program Files\Microsoft Visual Studio 8\VC\VCWizards\AppWiz\Generic\Application\images”
查看此向导的 default.htm 和 default.js 文件,您可以看到 HTML 和 JavaScript 中的 Win32 应用程序向导的源代码。
我的目标是修改此向导的源代码,以便添加更多配置、重命名输出文件并生成更多文件。因此,我执行了以下步骤:
- 将向导的源目录从 $(VCInstallDir)\VCWizards\AppWiz\Generic\Application 复制到 C:\MyLibWizard\CodeRock_CPPLibrary。
- 修改 default.htm 和 AppSettings.htm,并删除其中的所有 DLL 和控制台应用程序内容。我只想保留静态库相关的内容。
- 修改 default.htm 以添加更多定义要添加的配置以及命名输出文件时要使用的后缀的符号。
- 在与 default.htm 相同的目录中添加一个新页面 (LibraryConfigurations.htm),并调整其与其他 HTML 文件(default.htm 和 AppSettings.htm)之间的导航链接。
- 修改所有 HTML 文件,并确保 HTML 控件 ID 与新符号匹配。向导将这些 HTML 元素的值绑定到符号的值。对于 MFC 程序员来说,这与 MFC 对话框中的 DDX 机制非常相似。
- 修改 default.js 以添加更多配置并重命名输出文件。我使用了 DTE 自动化对象和向导对象。当向导引擎执行脚本时,这些对象会提供给脚本。例如,VS 自动化模型中的
Project
对象作为第一个参数传递给OnFinish(selProj, selObj)
,而selProj.Configurations
是该项目的配置集合。此外,我还使用“向导”对象上的AddSymbol
和FindSymbol
方法来查找我在 default.htm 中添加的符号的值。 - 创建一个新的 vsz 文件,并调整指向我的新向导的路径。另外,创建一个图标文件。我将这些文件命名为 CodeRock_CPPLibrary.vsz 和 CodeRock_CPPLibrary.ico。
- 将 CodeRock_CPPLibrary.vsz 和 CodeRock_CPPLibrary.ico 复制到 $(VCInstallDir)\VCProjects 目录。
搞定!!现在,当我点击 VS 中的“添加新项目”时,我可以看到我的向导,并且可以按照我喜欢的方式创建我的静态库项目。如果您对详细信息感兴趣,请查看本文附带的文件。
使用代码
如果您有兴趣查看 CodeRock_CPPLibrary 向导的实际运行情况,可以按照以下步骤操作:
- 下载本文附带的源文件。
- 解压到一个文件夹。假设您已将文件解压到 C:\MyWizards 文件夹。
- 打开 vsz 文件。您将看到类似以下内容:
- 将 CodeRock_CPPLibrary.vsz 和 CodeRock_CPPLibrary.ico 复制到 $(VCInstallDir)\VCProjects 文件夹。
VSWIZARD 7.0
Wizard=VsWizard.VsWizardEngine.8.0
Param="CONSOLE_TYPE_ONLY = false"
Param="WIZARD_ID = 76"
Param="START_PATH = C:\projects\CodeProjectArticles"
Param="WIZARD_NAME = CodeRock_CPPLibrary"
Param="HTML_PATH = C:\projects\CodeProjectArticles\CodeRock_CPPLibrary\html"
Param="SCRIPT_PATH = C:\projects\CodeProjectArticles\CodeRock_CPPLibrary\scripts"
Param="IMAGES_PATH = C:\projects\CodeProjectArticles\CodeRock_CPPLibrary\images"
Param="TEMPLATES_PATH = C:\projects\CodeProjectArticles\CodeRock_CPPLibrary\templates"
Param="CreatedByCodeRock = TRUE"
假设您已解压到 C:\MyWizards 文件夹,您应该这样更改路径:
VSWIZARD 7.0
Wizard=VsWizard.VsWizardEngine.8.0
Param="CONSOLE_TYPE_ONLY = false"
Param="WIZARD_ID = 76"
Param="START_PATH = C:\MyWizards"
Param="WIZARD_NAME = CodeRock_CPPLibrary"
Param="HTML_PATH = C:\MyWizards\CodeRock_CPPLibrary\html"
Param="SCRIPT_PATH = C:\MyWizards\CodeRock_CPPLibrary\scripts"
Param="IMAGES_PATH = C:\MyWizards\CodeRock_CPPLibrary\images"
Param="TEMPLATES_PATH = C:\MyWizards\CodeRock_CPPLibrary\templates"
现在,当您在 VS 中点击“添加新项目...”时,您应该会看到 CodeRock 库向导图标,并且可以创建一个带有所有选定配置的静态库项目。
为了真正体会到这个向导的价值,请执行以下操作:
- 使用向导创建任何库,并将您的公共函数和类添加到其中。如果接受默认设置,您应该会看到三个生成的头文件:YOUR_LIB_PROJECT_NAME_master.h、YOUR_LIB_RPOJECT_NAME_AutoLink.h 和 YOUR_LIB_RPOJECT_NAME_Version.h。
- 确保将所有公共函数、类和其他类型的声明添加到 YOUR_LIB_PROJECT_NAME_master.h 文件中。
- 创建一个测试应用程序,或者使用任何其他 VC 应用程序。
- 分三步配置应用程序以使用该库:
- 在应用程序的链接器设置中,将 YOUR_LIB_PROJECT_DIRECTORY\Lib 文件夹的路径添加到“附加库目录”。
- 在应用程序的编译器设置中,将 YOUR_LIB_PROJECT_DIRECTORY 文件夹的路径添加到“附加包含目录”。
- 在应用程序的 stdafx.h 文件中包含 YOUR_LIB_PROJECT_NAME_master.h。
就这样!!!
如果没有向导,如果您有多个应用程序配置,那么对于每一个应用程序配置,您都必须执行以下操作:在链接器设置中选择最合适的库文件,这意味着您必须记住匹配的库配置的名称及其输出文件的路径。如果不存在匹配的库配置,您将向库项目中添加一个匹配的配置并重新生成它,然后调整应用程序项目中该新库文件的路径。此外,如果您有多个应用程序使用该库,您将不得不一遍又一遍地为所有这些应用程序重复相同的过程。假设您花费 15-20 分钟来处理每一个配置,而您有 8 个配置。这意味着对于每一个应用程序,最少也要花费两个小时。如果您有 20 个库和 5 个应用程序,简单的数学计算表明您在配置管理上花费了 200 个小时。另一方面,使用此向导时,您只需一次构建库,然后为每个使用它的应用程序执行三个简单的步骤。这意味着您在每个应用程序上最多花费 1-3 分钟。这就是每个理智的程序员所称的生产力提升!!!!
关注点
如果您想修改标准的向导 HTML 和 JavaScript 文件,请注意以下几点:
- 在 default.htm 文件的 head 部分使用
<SYMBOL>
标签来定义您希望使用的任何参数。有几种预定义的符号类型,例如“checkbox”和“text”。在 HTML 文件中,如果您想将控件的值绑定到这些符号中的任何一个,请确保控件的 ID 与符号名称相同。 - 您还可以通过在 HTML 中使用“
window.external.AddSymbol(...)
”或在 default.js 文件中使用wizard.AddSymbol(...)
来添加符号。要查看此示例,请参阅本文附带的 default.htm 和 default.js 文件。 - 在模板文件中,标准 VS 向导允许您使用非常简单的“宏”语言来替换符号的值。例如,在 ReadMe.TXT 文件中,您会看到类似这样的内容:
- 在 default.js 文件中,您可以完全访问 Visual Sautomation 模型和
VCProjectLibraryEngine
。例如,当您调用CreateProject
函数时,该函数返回VCProjectEngine
命名空间中定义的VCProject
对象。该对象包含一个VCConfiguration
对象集合。 - 要更改配置的任何链接器、库管理器、资源编译器或任何其他工具设置,您可以使用 VC 配置对象的
Tools
属性来访问工具。例如,要访问资源编译器工具,您可以这样做: - 所有 VC 项目向导都使用 common.js 脚本函数。此文件充当您 default.js 的“框架”。该文件包含许多有用且共享的函数。此外,此文件中的许多函数会回调您的函数。例如,当您使用
AddFilesToProjectWithInfFile
函数时,该函数会为添加到生成项目中的每个模板文件回调您的GetTargetName
和SetFileProperties
函数。 - 您可以检查 VC 安装目录中的 common.js 文件,以使用或替换 VC 中许多向导重复使用的通用函数。例如,我没有像 Win32 向导那样保留
AddCommonConfig
,而是创建了另一个函数AddConfigurations
,并根据我的需求调整了所有配置。 - 如果您想调试 default.js 中的 JavaScript 代码,可以使用 common.js 中专门用于调试和向用户显示错误消息的函数。例如,您可以像这样使用
YesNoAlert
函数: - 许多 VC 向导实际上并不使用面向对象的 JavaScript 功能。在我的示例中,我创建了一个名为
CRConfig
的 JavaScript 对象来封装与我要添加的配置相关的所有变量和函数(您可以在 default.js 文件中查看)。 - 在 default.js 中,您可以使用任何 COM 或 ActiveX 对象来协助您,以防 JS 或 VS 自动化对象不够用。对于我的示例,我需要创建一个名为 Lib 的目录来存储所有配置构建的输出,因此我使用了
Scripting.FileSystemObject
,如下所示: - 现在,对于使用我库的任何应用程序,我只需要在链接器设置中将此 Lib 目录添加到“附加库目录”,然后使用自动链接头文件来选择最合适的库版本。自动链接头文件通常放在应用程序的 stdafx.h 文件中,或者可以作为主头文件的一部分,该主头文件通常包含应用程序可以使用的所有公共函数和类。如果用户需要,我的向导会自动生成此主文件。
[!if LIB_APP]
========================================================================
STATIC LIBRARY : [!output PROJECT_NAME] Project Overview
========================================================================
CodeRock Wizard has created this [!output PROJECT_NAME] library project for you.
[!endif]
这意味着,如果布尔值 LIB_APP
为 true,则将以下所有文本包含在渲染的文件中,直到看到匹配的 [!endif]。同样,如果标准向导看到 [!output PROJECT_NAME],它将插入 PROJECT_NAME
符号的值。
在我的示例向导中,我在自动链接头文件中大量使用了这些宏。请查看 root_AutoLink.h 以了解如何实现。
var RCTool = config.Tools("VCResourceCompilerTool");
在获取到工具对象后,您可以设置/获取其中的任何属性。要查看每个工具可用的属性列表,您需要检查 VCProjectEngine
的类型库 (tlb)。您可以使用 OLE TypeLib 查看器实用程序来查看 VCProjectEngine
的类型库。顺便说一句,您传递给 config.Tools
方法的工具名称与类型库中该工具的接口名称相同。例如,编译器的名称是 VCCLCompilerTool,而链接器的名称是 VCLinker。这些名称在 VCProjectEngine
命名空间中是相同的接口名称。
wizard.YesNoAlert("After CreateCRConfigurations");
//A function object that represents the configuration
function CRConfig(strProjectName,strVersion, strConfigName, strOutputFileName,
bDebug, bUnicode, bIsWin32, bStaticCRT)
{
this.m_ProjectName = strProjectName;
this.m_strVersion = strVersion;
this.m_ConfigName = strConfigName;
this.m_OutputFileName = strOutputFileName;
this.m_ImportLibFileName = "";
this.m_PreprocessorDefines = "";
this.m_AdditionalIncludeDirectories = "";
this.m_AdditionalResourceIncludeDirectories = "";
this.m_AdditionalLibraryDirectories = "";
this.m_bDebug = bDebug;
this.m_bUnicode = bUnicode;
this.m_bIsWin32 = bIsWin32;
this.m_bStaticCRT = bStaticCRT;
this.m_bIsStaticLib = true;
//Output File Name is the name of the DLL or static library.
this.GetOutputFileName = FormatOutputFileName;
this.GetImportLibFileName = FormatImportLibFileName;
this.GetPreprocessorDefines = FormatPreprocessorDefines;
this.GetAdditionalIncludeDirectories = FormatAdditionalIncludeDirectories;
this.GetAdditionalIncludeResourceDirectories =
FormatAdditionalIncludeResourceDirectories;
this.GetAdditionalLibraryDirectories = FormatAdditionalLibraryDirectories;
}
//Member functions of CRConfig
function FormatOutputFileName()
{
var strOutputName = new String();
var strVSVersionSuffix = GetVSVersionSuffix();
strOutputName = this.m_ProjectName + this.m_strVersion;
//CRT flags
if(this.m_bStaticCRT)
strOutputName = strOutputName + "_SCRT";
else
strOutputName = strOutputName + "_DCRT";
//A flag to indicate whether the library is an import library or static library
if(this.m_bStaticLib)
strOutputName = strOutputName +"_S";
//Unicode flag
if(this.m_bUnicode)
strOutputName = strOutputName + "_U";
....
....
var objFileSystem = new ActiveXObject("Scripting.FileSystemObject");
//Now create the directory it does not exist. strLibPath
//Lib
g_strLibDirPath = strProjectPath + "\\" + g_strLibDirName;
bDirectoryExists = objFileSystem.FolderExists(g_strLibDirPath);
if(!bDirectoryExists)
objFileSystem.CreateFolder(g_strLibDirPath);
...
...
正如您所见,Visual Studio 向导模型提供了强大的技术,可以减少开销时间,让您专注于项目中的重要内容。
好了,就到这里!!!
嘿,你!你为我投票了吗?如果投了,谢谢你,请再次投票!如果没有,也谢谢你,请现在投票,稍后也请再投一次……
历史
首次发布到 CodeProject。