Introduction
Visual Studio is, no doubt, a great and indispensible productivity tool for any serious project. One of the great things about VS is the numerous wizards that are available to assist in generating new projects and adding new items. These wizards save a tremendous amount of time by automating repetitive and frequent tasks. Also, the folks at Microsoft realized that the wizards can't meet every need that developers might desire in order to avoid repetition of frequent tasks. So, they came up with many ways to extend the development environment. These include macros, add-ins, and wizards. The choice of the method depends, among other things, on the level of comfort with the programming languages that are commonly used in each method. For example, if the programmer is good at VB.NET, then he or she will, most probably, use the macros or add-in method. On the other hand, if he/she is good at HTML/JavaScript, then the choice will be the standard wizard model in Visual Studio.
VC++ programmers often use static libraries to gather common code in order to be able to reuse or re-abuse the same code in several projects. As a starter for any static library development, they usually use the "Add a new project" menu item, and they select "Win32 project" to generate the project files. The Win32 project wizard adds only two configurations to the project. Those two configurations are Release and Debug configurations. But those two configurations are not good enough for realistic projects. In many projects that I worked on in the past, I usually added 8 32-bit configurations for each library:
- Debug configuration with ANSI charset, dynamically linked to CRT
- Debug configuration with Unicode charset, dynamically linked to CRT
- Debug configuration with ANSI charset, statically linked to CRT
- Debug configuration with Unicode charset, statically linked to CRT
- Release configuration with ANSI charset, dynamically linked to CRT
- Release configuration with Unicode charset, dynamically linked to CRT
- Release configuration with ANSI charset, statically linked to CRT
- Release configuration with Unicode charset, statically linked to CRT
Here, CRT stands for "C Runtime Library".
In addition to adding those configurations manually, I needed to do three more things:
- Choose appropriate naming scheme for the output library name to distinguish the various configurations. For example, if the library base name is MyBeautifullLib, then the "Debug, Unicode, Dynamic CRT" configuration will produce MyBeautifullLib_U_DCRT_D.Lib, while the "Release, ANSI, Dynamic CRT" configuration will produce MyBeautifullLib_DCRT.Lib.
- Make sure that all library output files are copied to a common "Lib" folder.
- Write an auto-link header file that automatically links the library file that matches the configuration of the using application. This file uses
#pragma comment(lib,..."
that tells the linker to select a particular version of the library.
I used to do this manually for every single library, and I had a lot of them!!! And I was wasting too much time on this overhead until I learned all about the wizard engine in VS.
The rest of this article explains how I automated those steps. I hope that somebody finds this article useful.
Is this article for me?
Before you go on, let me just say this: you can continue reading this article if:
- You have a dozen or more of C or C++ static libraries and you use them in many VC projects with many configurations. You might find that the wizard and the tips in this article will save you many hours of work.
- You want to customize the way the standard wizards in VC generate code and files.
- You are just curious about those little things called VC wizards....
If you are not any of those, you can just stop here.
Background
In Visual Studio, a wizard is any COM object that implements the IDTWizard
interface. I will not repeat all the gory details that can be found in MSDN online. In the standard wizard model, the COM object that implements IDTWizard
is VCWizCtl
which has ProgID="VsWizard.VsWizardEngine"
. Most of the wizards that are packaged with VS rely on this object to add new projects and new items.
This wizard allows you to use HTML and JavaScript to automate adding new items and new projects. In order to do that, you have to tell this wizard where to find your HTML, JavaScript, and other files that make up your wizard:
- Create a directory for your wizard. I named mine CodeRock_CPPLibrary.
- Create four subfolders in the new directory: HTML, SCRIPTS, IMAGES (optional), and TEMPLATES.
- In the HTML, SCRIPTS, and TEMPLATES folders, create a subdirectory with the locale ID of the target user language. For English, this is 1033. This is because your HTML dialogs and scripts and template files might need to be localized to different languages and locales.
- You should have default.htm in your "HTML\1033" subfolder and default.js in your "SCRIPTS\1033" subfolder. The default.htm file is the first page that the wizard will display to the user.
- At the very least, you should have the
OnFinish(selProj, selObj)
function in your default.js. This function will be called by the wizard when the user clicks "Finish" in your pages or by other means.
- Develop your other pages and add navigation links to them in your default.htm.
- Create a .vsz file and an icon file for your wizard. In the .vsz file, tell the standard wizard, among other things, where to find your files by using named PARAM variables.
- Copy the your new .vsz file and the icon file to the appropriate directory under the VS installation directory. For me, I'm interested in VC++ projects, and so I copy my .vsz; I copy those two files to the C:\Program Files\Visual Studio 2005\vc\vcprojects folder.
Let's take an example: when you click on the "Add new project" from within VS, you are presented with a list of project types. If you select "Win32 Project" under "Visual C++", and click OK, you will see a sequence of dialogs like the following one:
This dialog is actually a web page written in HTML and JavaScript. The vsz file for this project type is $(VCInstallDirectory)\vcprojects\Win32Wiz.vsz. In this file, you will find the following:
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"
This file tells VS to use VsWizard.VsWizardEngine.8.0
as the COM object that handles this kind of projects. VS creates an instance of this COM object, and passes the parameters to the wizard. The wizard then figures out the paths to the HTML and script files using the RELATIVE_PATH
and WIZARD_NAME
parameters. Here, the full path is $(VCInstallDir)\VCWizards\AppWiz\Generic\Application. For example, on my computer, VCInstallDir
is "C:\Program Files\Microsoft Visual Studio 8\VC\", so the full paths to the wizard folders are:
- HTML folder is: "C:\Program Files\Microsoft Visual Studio 8\VC\VCWizards\AppWiz\Generic\Application\html"
- SCRIPTS folder is: "C:\Program Files\Microsoft Visual Studio 8\VC\VCWizards\AppWiz\Generic\Application\scripts"
- TEMPLATES folder is: "C:\Program Files\Microsoft Visual Studio 8\VC\VCWizards\AppWiz\Generic\Application\templates"
- IMAGES folder is: "C:\Program Files\Microsoft Visual Studio 8\VC\VCWizards\AppWiz\Generic\Application\images"
Looking into the default.htm and default.js files for this wizard, you can see the source code for the Win32 app wizard in HTML and JavaScript.
What I wanted to do is to change the source code for this wizard so that I could add more configurations, rename output files, and generate more files. So, I did the following steps:
- Copy the source directory for the wizard from $(VCInstallDir)\VCWizards\AppWiz\Generic\Application to C:\MyLibWizard\CodeRock_CPPLibrary.
- Modify default.htm and AppSettings.htm and remove all the DLL and console app stuff in them. I just wanted to keep static library stuff.
- Modify default.htm to add more symbols that define which configurations to add, and the suffixes to use when naming the output files.
- Add one more page (LibraryConfigurations.htm) in the same directory as default.htm and adjust the navigation links between it and the other HTML files (default.htm and AppSettings.htm).
- Modify all the HTML files and make sure the HTML controls IDs match the new symbols. The wizard binds the values of those HTML elements to the values of the symbols. For MFC programmers out there, this is very similar to the DDX mechanism in MFC dialogs.
- Change default.js to add more configurations and rename output files. I used DTE automation objects and the wizard object. Those objects are made available to the script by the wizard engine when it executes it. For example, the
Project
object in VS Automation model is passed in to OnFinish(selProj, selObj)
as the first parameter, and selProj.Configurations
is the collection of the configurations for the project. Also, I used the AddSymbol
, FindSymbol
methods on the "wizard" object to find the values of the symbols that I added in default.htm.
- Create a new vsz file, and adjust the paths to my new wizard. Also, create an icon file. I named those files CodeRock_CPPLibrary.vsz and CodeRock_CPPLibrary.ico.
- Copy CodeRock_CPPLibrary.vsz and CodeRock_CPPLibrary.ico to the $(VCInstallDir)\VCProjects directory.
That's it!! Now, when I click "Add New Project" in VS, I can see my wizard and I can create my static library project the way I like. If you are interested in the details, please check the files attached with this article.
Using the code
Here are the steps that you can follow if you are interested in seeing the CodeRock_CPPLibrary wizard in action:
- Download the source files attached with this article.
- Unzip to a folder. Let us assume that you unzipped the files to the C:\MyWizards folder.
- Open the vsz file. You will see stuff like this:
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"
Assuming that you unzipped to the C:\MyWizards folder, you should change the paths like this:
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"
- Copy CodeRock_CPPLibrary.vsz and CodeRock_CPPLibrary.ico to the $(VCInstallDir)\VCProjects folder.
Now, when you click "Add New Project..." in VS, you should see the CodeRock library wizard icon, and you can create a static library project with all the selected configurations.
In order to really see the value of this wizard, do the following:
- Create any library using the wizard, and add your public functions and classes to it. If you accept the defaults, you should see three generated header files: YOUR_LIB_PROJECT_NAME_master.h, YOUR_LIB_RPOJECT_NAME_AutoLink.h, and YOUR_LIB_RPOJECT_NAME_Version.h.
- Make sure that you add the declarations of all public functions and classes and other types to the YOUR_LIB_PROJECT_NAME_master.h file.
- Create a test application, or just use any other VC application.
- Configure the application to use the library in three steps:
- Add the path to the YOUR_LIB_PROJECT_DIRECTORY\Lib folder to "Additional Library Directories" in the linker settings of the application.
- Add the path to the YOUR_LIB_PROJECT_DIRECTORY folder to "Additional Include Directories" in the compiler settings of the application.
- Include YOUR_LIB_PROJECT_NAME_master.h in stdafx.h of the application
And that's it!!!
Without the wizard, if you have several configurations for the application, then for every single application configuration, you would have to do the following: choose the most appropriate library file in the linker settings, which means you have to remember the name of the matching library configuration and the path to its output file. If no matching library configuration exists, you would add a matching configuration to the library project and rebuild it, and then adjust the path to this new library file in the application project. Also, if you have several applications that use the library, you would have to repeat the same procedure again and again for all of those applications. Let's say that you spend 15-20 minutes on every configuration, and you have 8 configurations. That means two hours for every single application at the very least. If you have 20 libraries and 5 applications, simple math tells you that you are spending 200 hours on configuration management. On the other hand, when using this wizard, all you have to do is just build the library once, and then do three simple steps for each application that uses it. This means that you are spending just 1-3 minutes max on every single application. This is what every sane programmer calls productivity enhancement!!!!
Points of interest
Here are a few things to keep in mind if you want to modify the standard wizard HTML and JavaScript files:
- Use
<SYMBOL>
tags in the head section of the default.htm file to define any parameters you wish to use. There are several predefined symbol types such as "checkbox" and "text". In HTML files, if you want to bind a control's value to any of those symbols, make sure that they have the control's ID the same as the symbol name.
- You can also add symbols using JavaScript code in HTML by using "
window.external.AddSymbol(...)
", or in the default.js file using wizard.AddSymbol(...)
. To see examples of this, just see the default.htm and default.js files attached with this article.
- In the template files, the standard VS wizard allows you to use a very simple "macro" language to substitute the symbols with their values. For example, in the ReadMe.TXT file, you will see something like:
[!if LIB_APP]
========================================================================
STATIC LIBRARY : [!output PROJECT_NAME] Project Overview
========================================================================
CodeRock Wizard has created this [!output PROJECT_NAME] library project for you.
[!endif]
This means that if the boolean LIB_APP
is true, then include all the text that follows in the rendered file until you see the matching [!endif]. Also, if the standard wizard sees [!output PROJECT_NAME], it will insert the value of the PROJECT_NAME
symbol.
In my sample wizard, I use those macros heavily in the auto-link header files. Please check root_AutoLink.h to see how this is done.
- In the default.js file, you have full access to the Visual Sautomation model and
VCProjectLibraryEngine
. For example, when you call the function CreateProject
, the function returns the VCProject
object that is defined in the VCProjectEngine
namespace. This object has a collection of VCConfiguration
objects.
- To change any of the linker, librarian, resource compiler, or any of the tool settings for a configuration, you can use the
Tools
property of the VC configuration object to access the tool. For example, to access the resource compiler tool, you would do something like this:
var RCTool = config.Tools("VCResourceCompilerTool");
And after you get a hold of the tool object, you can set/get any property on it. To see a list of the properties available for each tool, you need to examine the type library (tlb) of VCProjectEngine
. You can use the OLE TypeLib viewer utility to view the type library of VCProjectEngine
. By the way, the name of the tool you pass to the config.Tools
method is the same as the the interface name for the tool in the type library. For example, the compiler's name is VCCLCompilerTool, while the linker's name is VCLinker. Those names are the same interface names in the VCProjectEngine
namespace.
- All VC project wizards use common.js script functions. This file works as a "framework" for your default.js. The file contains many useful and shared functions. Also, many functions in this file call back your functions. For example, when you use the
AddFilesToProjectWithInfFile
function, the function calls back your GetTargetName
and SetFileProperties
for every single template file that is added to your generated project.
- You can examine the common.js file in the VC installation directory to use or replace the common functions that are used repeatedly in many VC wizards. For example, instead of keeping
AddCommonConfig
in Win32 wizards, I created another function AddConfigurations
, and adjusted all the configurations according to my needs.
- If you want to debug the JavaScript code in default.js, you can use some functions in common.js that are specifically written to debug and display error messages to the user. For example, you can use the
YesNoAlert
function like this:
wizard.YesNoAlert("After CreateCRConfigurations");
- A lot of VC wizards don't really use JavaScript object-based features. In my sample, I created a JavaScript object called
CRConfig
to encapsulate all the variables and functions that are related to the configurations that I want to add (you can examine the default.js file to see this):
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;
this.GetOutputFileName = FormatOutputFileName;
this.GetImportLibFileName = FormatImportLibFileName;
this.GetPreprocessorDefines = FormatPreprocessorDefines;
this.GetAdditionalIncludeDirectories = FormatAdditionalIncludeDirectories;
this.GetAdditionalIncludeResourceDirectories =
FormatAdditionalIncludeResourceDirectories;
this.GetAdditionalLibraryDirectories = FormatAdditionalLibraryDirectories;
}
function FormatOutputFileName()
{
var strOutputName = new String();
var strVSVersionSuffix = GetVSVersionSuffix();
strOutputName = this.m_ProjectName + this.m_strVersion;
if(this.m_bStaticCRT)
strOutputName = strOutputName + "_SCRT";
else
strOutputName = strOutputName + "_DCRT";
if(this.m_bStaticLib)
strOutputName = strOutputName +"_S";
if(this.m_bUnicode)
strOutputName = strOutputName + "_U";
....
....
- In default.js, you can use any COM or ActiveX object to help you out in case JS or VS automation objects are not enough. For my sample, I needed to create a directory called Lib to store the output of all configuration builds, so I used
Scripting.FileSystemObject
like this:
var objFileSystem = new ActiveXObject("Scripting.FileSystemObject");
g_strLibDirPath = strProjectPath + "\\" + g_strLibDirName;
bDirectoryExists = objFileSystem.FolderExists(g_strLibDirPath);
if(!bDirectoryExists)
objFileSystem.CreateFolder(g_strLibDirPath);
...
...
- Now, for any application that uses my library, all I need to do is just add this Lib directory to "Additional Library Directories" in Linker settings, and use the auto-link header file to select the most appropriate file version. The auto-link header file usually goes into the stdafx.h file of the application, or it could be part of a master header file that usually includes all the public functions and classes that could be used by the application. This master file is automatically generated by my wizard if the user desires so.
As you can see, Visual Studio wizard model provides you with powerful techniques to cut down on the overhead time and focus on what's important in your project.
Well, that's it for now!!!
Hey, you!!! Did you vote for me? If you did, thank you and please vote again! And if you didn't, also thank you and please vote now and do it again later...
History
Initial release to CodeProject.