第一部分 AppWizard及其工作原理 |
AppWizard即应用程序向导,它是Visual Studio开发环境中强大的编程工具之一,用它可以创建各种不同类型的程序。比如Win32应用、ATL、MFC应用等等。在Windows的术语中,向导(wizard)一词指得是一个应用程序,它的一个主要特点就是提供一系列对话框引导用户进行必要的选择来完成给定的任务。VC中的应用程序向导——AppWizard提供一系列特定工程类型对话框来让程序员定义各种类型的新工程。其中每一个对话框都显示一些用来指定工程类型的选项。例如,用AppWizard创建Windows DLL的时候,第一个对话框让程序员指定诸如要创建什么类型的DLL,是常规类型还是MFC扩展类型,是否要包括自动化支持,以及要不要源代码注释等等。 根据程序员所填充的对话框,AppWizard会自动创建构造工程所需的框架文件,它们包括:工程文件、工作间文件、源代码文件、头文件、资源文件等等。AppWizard是Visual Studio开发环境中使用最多的工具之一。尽管如此,AppWizard也有它的不足之处。那就是常用的工程类型都是内建在Visual Studio中,无法创建自己的AppWizard。自从有了Custom AppWizard(Visual C++ 4.0)以后,这个问题得到了解决。Custom AppWizard也就是定制的AppWizard。在创建类似的多个工程时,Custom AppWizards显得特别有用。例如你创建的工程都是SDI,并且都支持自动化(automation),那么你就可以创建一个自己定制的 AppWizard,将SDI自动化设为默认选项。这样可以提高工作效率。此外,利用Custom AppWizard也可以创建具有个性化的工程。例如你想要所有工程都有一个“关于”对话框,并且在这个对话框中显示个人信息或者公司的标徽及其它专有信息,每个源代码文件中都加上自己的专门注释。那么通过创建一个Custom AppWizard很容易实现这个需求。你甚至可以定义并显示自己定制的对话框来收集工程类型所需的信息和选项。本文的第一部分我们将讨论AppWizard的工作原理,然后在后续部分中循序渐进地学习如何创建Custom AppWizard。最终我们将创建一个在实际编程中非常实用的Custom AppWizard。并提供全部源代码。 在学会使用Custom AppWizard之前,首先必须了解AppWizard的工作原理,理解 AppWizard是如何根据不同的用户选择来创建工程的。 AppWizard有一个管理装置(manager),它不是一个单独的应用程序。Custom AppWizard运行于Visual Studio框架之中。AppWizard的这个所谓的“管理器”,实际上就是MFCAPWZ.DLL,它控制不同的AppWizard执行。在创建新工程的对话框中,“Project”标签是默认的选项,列表框中显示出内建的工程类型。此外,这个列表框中还列出用户定制的AppWizard,如图一所示。 |
图一 |
这些定制AppWizard文件扩展名为*.awx,它们存放在一个特定的目录中。如果安装VC6.0时是按照默认的路径安装的,则定制的AppWizard文件在成功编译后都会被存放到\Program Files\Microsoft Visual Studio\Common\MSDev98\Template文件夹中。注意列表框中此新的列表项“MFC AppWizard (exe) – VC知识库”,这就是我们后面要定制的AppWizard。从这里可以看出,只要产生了*.awx文件,那么它就会与标准的(或者说内建的)Visual C++ AppWizard一起自动显示在这个列表框中。 |
——CCustomAppWiz 类和Dictionary字典 |
CCustomAppWiz 基类提供了MFCAPWZ.DLL 和Custom AppWizard之间的通讯服务。CCustomAppWiz()成员函数的实现就在MFCAPWZ.DLL中。为了实现特定应用的行为,你只要从CCustomAppWiz派生一个类,改写相应的虚拟函数,然后在MFCAPWZ.DLL运行时调用SetCustomAppWizClass()函数注册派生类即可。 通常,AppWizard显示一系列对话框获取创建新工程所需的设置。每一个步进对话框显示不同的选项。AppWizard将这些选项的值存储在一个串映射中。这个串映射就叫做Dictionary字典。Dictionary字典实际上是一个CCustomAppWiz 类的成员变量(m_Dictionary),其类型为CMapStringToString。Dictionary将AppWizard宏映射到相关联的值。这里所说的宏是指工程选项或设置的名称。例如,在创建MFC的时候,你可以选择应用程序为SDI,那么,Dictionary中就会有一个名为PROJTYPE_SDI的宏。Dictionary中这个项目的值就是1,否则这个与这个宏关联的值为0。 m_Dictionary成员变量可以被用于创建宏,删除宏或者更新宏的值。因为m_Dictionary是一个CMapStringToString对象,肯定有相应的成员函数存取不同宏的值。下面的代码返回PROJTYPE_SDI宏的值,它被用于判断这个工程是不是一个SDI应用。 m_Dictionary.Lookup("PROJTYPE_SDI", m_strProjType); if (_T("1") == m_strProjType) { // SDI类型应用 } else // 其它类型的程序 { } MFCAPWZ.DLL提供了一些标准宏,任何其它定制AppWizard所需要的宏都可以用SetAt函数添加到Dictionary字典中。你从在线文档中可以找到六十多个标准宏 当你创建Custom AppWizard并按下Finish按钮后,MFCAPWZ.DLL用Dictionary创建新的工程文件。每一个AppWizard(不论是标准的还是定制的)都有一套模板文件用于创建AppWizard生成的工程源文件。Dictionary中的值被用于与模板文件相连接来创建最终的输出(新的工程文件)。下面是一个例子,中文的基于对话框程序的模板资源文件名叫DlgLoc_chs.rc。下面是从中摘录出的一段: ... IDD_ABOUTBOX DIALOGEX 0, 0, 160, 129 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "关于$$ROOT$$" ... 注意这里 $$ROOT$$ 串的用法。当MFCAPWZ.DLL创建新工程文件时,它首先扫描每一个模板,查找以$$开始并以$$结尾的串。在这两个前缀和后缀之间的文本串叫做占位苻。每一个占位符是Dictionary中一个宏的名字。MFCAPWZ.DLL在Dictionary中查询占位符的值并用这个值替换占位符。当所有的占位符都被Dictionary中相应的值替换之后,工程文件也就产生了。 ——用MFCAPWZ.DLL替代占为符 为了理解MFCAPWZ.DLL用Dictionary中宏的值替换模板文件的占位符。我们来做一个实验: 1、 用AppWizard创建一个基于对话框的应用程序,将工程取名为MyTestDlgApp。 2、 创建完工程之后,以文本方式打开MyTestDlgApp.rc文件。 3、 找到IDD_ABOUT的对话框模板资源。 4、 你应该看到原来模板文件中$$ROOT$$占位符已经被工程的名字(MyTestDlgApp)替换掉了。这是因为Dictionary有一个ROOT宏,其缺省值被设置为工程的名字。 宏即可被用于定义模板文件中指定的占位符的替换值,有时AppWizard也用宏来协助步进对话框的显示,或者确定用哪个模板来创建新的工程文件。例如,如果Dictionary中PROJTYPE_SDI宏的值为1,则创建的应用程序是SDI类型。但是,如果PROJTYPE_DLG宏的值为1的话,创建的应用程序是基于对话框的。根据宏的值是否为1,MFCAPWZ.DLL使用不同的模板文件来创建工程文件。 大多数AppWizard都由一系列固定的对话框组成。其中后一个对话框的显示完全依赖于前一个对话框所选择的选项来决定。这种多步进对话框形式称为轨迹。创建MFC应用程序的AppWizard是一个多轨迹的AppWizard。 ——多轨迹AppWizard 多轨迹AppWizard提供了更为复杂的应用程序设置。为了理解多轨迹AppWizard概念,请做一个如下实验: 1、 按下Ctrl+N创建新工程 2、 单击“Project”标签,然后选中“MFC AppWizard (exe)”。 3、 注意对话框的标题条内容为“MFC AppWizard - Step 1”,没有指明总共有几步,这是因为总共的步进数在你决定要创建的MFC工程类型前时未知的。 4、 看一下不同类型工程的选项有何差别:single document (SDI),multiple document (MDI),和 dialog-based。选择multiple document (MDI)类型,然后单击Next按钮。 5、 注意标题条的内容中指定了总共的步进数——“MFC AppWizard - Step 2 of 6”。由于你选择了MDI类型,这个类型总共有六个步进对话框,每一个步进对话框包含特定的基于文档的MFC应用程序选项。 6、 单击Back按钮,选择基于对话框的工程类型,然后单击Next按钮。这一次标题条的内容指定的步进总数是——“MFC AppWizard - Step 2 of 4”。这说明创建基于对话框的应用程序共有四个步进对话框。 以上是对AppWizards 及其工作原理的讨论。在下一部分我们将尝试创建一个简单的Custom AppWizard。 |
第二部分 创建一个简单的AppWizard |
在第一部分中我们介绍了Custom AppWizard的概念及其工作原理。在这一部分,我们将尝试用Custom AppWizards来创建一个最简单的Custom AppWizard。 首先,我们先创建一个什么事情也不做的Custom AppWizard,主要是了解它的创建过程和步骤。按Ctrl+N 打开New对话框,新建一个Custom AppWizards工程。填入工程名字后单击OK,从步进对话框的标题中,我们可以了解到总共有两个步骤,在第一个步的对话框中包含三个输入域。 第一个输入域是新Custom AppWizards的起点。它有三个单选按钮: |
l Existing project——这个选项是以一个现存的工程作为蓝本来创建Custom AppWizards。使用这个选项有两个缺点。第一,AppWizards创建的是一个已经存在的工程。第二,AppWizards创建的工程文件名和类名必须与现存工程的文件名和类名一致。 l Standard MFC AppWizard steps——这个选项是最常用的选项,它创建的AppWizard模板可用于每一种MFC支持的工程类型。从修改各种MFC工程模板文件的灵活性方面,这个选项也是最灵活的。因为这是最通用的一种定制AppWizard类型,所以我们将以它为例。 l Your own customized steps——这个选项全新定制一组步进对话框和选项。例如,假设你需要一个定制的AppWizard来自动创建一个非MFC应用程序。这时你就得用这个选项创建所有自己定制的对话框。 第二个输入域让你命名新建的定制AppWizard。这个名字将被用于显示在New Project List对话框中. 最后一个输入域用来指定定制步进的数目或者对话框的数目,它将被添加到新的定制AppWizard中。有时候我们不需要额外的步进对话框,比如我们即将创建的简单AppWizard就不用任何步进。但在第三部分中,我们将会学习如何定制步进对话框。 接下来,按Next按钮继续到定制AppWizard的第二步(对话框),也是最后一步。这个对话框中有两个域都是自解释的。第一个域定义新定制的AppWizard是个可执行程序还是一个DLL。第二个域指定语言支持选项。 |
设置工程的缺省选项 |
前面我们讲过用AppWizard创建工程时可以有多种类型可以选择。这一部分我们创建的AppWizard名字叫SDIAutomationWiz,在默认情况下,用这个AppWizard创建的工程类型是支持自动化的SDI工程。 打开工程的New 对话框,在Project List中选择Custom AppWizard,在Project Name编辑框中输入SDIAutomationWiz,单击OK进入第一个步进对话框,选择“Standard MFC AppWizard steps”,然后指定一个它在Project List中显示的名字。因为这个AppWizard没有额外的步进对话框,因此步进数编辑框中填写0,单击Next按钮到下一步。选择“MFC AppWizard Executable”,语言支持为中文,单击Finish按钮,出现确认对话框。单击OK后便开始创建新的AppWizard工程。 ——定义CCustomAppWiz 类 虽然编译后的Custom AppWizard文件扩展名都是.awx,但是它实际上就是一个通常我们使用的Windows动态链接库(DLL)文件。如果你打开SDIAutomationWiz.cpp文件,你就会看到如下的DLLMain()函数代码: // Defining the DLLMain() Function extern "C" int APIENTRY DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) { if (dwReason == DLL_PROCESS_ATTACH) { TRACE0("SDIAUTOMATIONWIZ.AWX Initializing!\n");
// Extension DLL one-time initialization AfxInitExtensionModule(SDIAutomationWizDLL, hInstance);
// Insert this DLL into the resource chain new CDynLinkLibrary(SDIAutomationWizDLL);
// Register this Custom AppWizard with MFCAPWZ.DLL SetCustomAppWizClass(&SDIAutomationWizaw); } else if (dwReason == DLL_PROCESS_DETACH) { TRACE0("SDIAUTOMATIONWIZ.AWX Terminating!\n");
// Terminate the library before destructors are called AfxTermExtensionModule(SDIAutomationWizDLL); } return 1; // ok } 除了常规的CDynLinkLibrary MFC扩展动态链接库例程之外,还有一个对SetCustomAppWizClass()函数的调用。这个函数是从MFCAPWZ.DLL输出的,用于传递定制AppWizard 中CCustomAppWiz派生类的指针。因为MFCAPWZ.DLL通过调用CCustomAppWiz的成员函数来控制所有AppWizard的执行,因此它必须用这个指针来调用CCustomAppWiz派生类中重载的成员函数。 有一点必须牢记在心,那就是尽管你创建了定制的AppWizard,但MFCAPWZ.DLL仍然控制着一切。换句话说,你定制的AppWizard只是用于显示步进对话框,确定对话框以什么顺序显示,以及设置新工程的模人选项。一些重要的工作,诸如解析模板文件,合并Dictionary中的宏和模板文件中的占为符,创建工程文件等等还是要MFCAPWZ.DLL来做。 前面我们讲过,CCustomAppWiz类负责AppWizard与MFCAPWZ.DLL之间的通讯。实际上这种通讯是单边的。MFCAPWZ.DLL告诉你的CCustomAppWiz对象什么时候需要调用相应的虚拟成员函数。 CCustomAppWiz类中大约有十来个函数,其中只有五个函数是可以看到并使用的常用例程。通过这些函数的命名你基本上就能了解其主要作用。例如,InitCustomAppWiz()函数是进行初始化,包括初始化宏。除此之外,ExitCustomAppWiz()函数的作用是卸载AppWizard。 另外,还有两个函数用来控制步进对话框的显示顺序。Next()和Back()。不说肯定你也知道,这两个函数与AppWizard对话框中的Next和Back按钮是关联的。 最后一个很重要的函数是CustomizeProject(),一旦程序员完成工程选项的设置,AppWizard便创建工程的make文件并定义工程缺省的debug和release配置。然后AppWizard调用CustomizeProject()函数,以便定制的AppWizard能在存储工程之前修改make文件设置。 ——宏指令的处理 我们已经知道了宏的初始化是在InitCustomAppWiz()中进行的,我们也知道了CCustomAppWiz类有一个成员变量m_Dictionary,它存储宏名及其值。因为这个成员变量是CMapStringToString类型的,用标准的MFC映射函数就能get或者set不同的宏,请看下列代码: // retrieve value for Automation CString strValue; m_Dictionary.Lookup(_T("AUTOMATION"), strValue);
// Include support for context sensitive help m_Dictionary.SetAt(_T("HELP"), strValue.Compare("1")); 现在打开SDIAutomationWizAW.cpp文件,在InitCustomAppWiz()函数末尾敲入: m_Dictionary.SetAt(_T("PROJTYPE_SDI"), _T("1")); m_Dictionary.SetAt(_T("PROJTYPE_MDI"), _T("0")); m_Dictionary.SetAt(_T("AUTOMATION"), _T("1")); 然后构造(build)定制的AppWizard工程。如果没有出错的话,AppWizard的.awx文件会被自动拷贝到专门的目录中,以便MFCAPWZ.DLL能找到它。 现在按下Ctrl+N,新定制的AppWizard应该出现在New对话框的Project清单中。如果你使用新的AppWizard,你会看到工程的默认选项是SDI程序并支持自动化。通过这个简单的Demo,我们基本上了解了如何通过定制AppWizard来设置默认的工程选项。 如果要分发你创建的AppWizard,只要分发.awx文件就可以了,把它拷到Visual Studio的模板文件目录即可。 在这一部分,我们定制了一个简单的AppWizard,通过一个例子示范了如何处理宏字典。在第三部分中,我们将涉及更多定制AppWizard的内容,并且还要制作一个实用价值很高的AppWizard。包括新增加一个步进对话框,获得新步进对话框中的输入信息。用这个定制的AppWizard创建的所有工程都会在其“关于”对话框中显示在步进对话框输入的信息,并通过静态控制和图像建立URL链接。此外,用这个AppWizard创建的每一个源文件都会自动建立程序员自己的专用注释。 |
我们在第二部分中示范的AppWizard例子很简单,没有任何实用性。在这一部分我们将讨论几个关于制作AppWizard的高级话题。然后利用VC提供的Custom AppWizard来创建一个在编程中非常实用的AppWizard。与MFC AppWizard(exe) 产生的常规应用程序相比,用这个定制的AppWizard所创建的工程构造出来的应用程序有两个定制特点:
一是所有程序都会有一个定制的“关于”对话框,在这个对话框中显示自己或公司的有关信息,对话框中还有一个将用户定向到Web站点的静态文字控制或图像(icon和bmp)。
二是工程中每一个源代码文件(*.h和*.cpp)的最上面都会有程序编写着的名字及程序创建日期以及简单的程序说明和注释。
这一部分要介绍的主要技术包括:
1、 如何定义和添加AppWizard要用到步进对话框。
2、 如何将Custom AppWizard的专用宏添加到字典中。
3、 如何修改定制AppWizard要用到的模板文件,包括inf文件,资源模板文件等。
4、 将输入信息存储到注册表中,使得每一个工程的公共信息都不用重复输入。
下面我们就开始吧: 进入Visual C++开发环境,如图一:
图一
选择“Project”标签,工程名字可以随便取。这里我取的名字是“VckbaseWiz”,其它选项都默认。
然后单击OK。进入下一个对话框。如图二:
图二
因为我们要建一个标准的MFC AppWizard,所以选择“Standard MFC AppWizard steps”单选按钮。AppWizard的命名最好规范一些,这样便于记忆和辨认。与AppWizard的工程名不同,这个名字要在Project类型清单中列出。我们把它命名为“MFC AppWizard(exe)——VC知识库”。因为在我们创建的这个Custom AppWizard中有一个额外的对话框,所以在设置步进步骤的数目时输入1。单击“Next”进入下一个对话框。如图三:
1、 打开VckbaseWizAw.cpp,在“#include "chooser.h"” 包含语句后面加上“#include "registry.h"” 2、 在InitCustomAppWiz成员函数前面加上如下注册表键值定义: 3、 在VCKBASEWIZ_KEY #define指令之后,定义下列静态结构,其中包含:宏名、注册表值名、宏的缺省值。注册表值名被用来在注册表中查找VCKBASEWIZ_KEY指定的键。如果没有找到键值(例如第一次运行程序时),则会使用宏的缺省值: static struct { char szMacroName[50]; char szRegistryValueName[50]; char szMacroDefaultValue[1024];//512个汉字 } macroPairs[] = { "PROGRAMMER", "Programmer", "程序员", "WEB_PAGE", "Web Page", "网站", "GENERAL_INFO", "General Info", "程序描述", "COMMENT_INFO", "Comment Info", "程序注释" }; 4、 将下列代码添加到CVckbaseWizAppWiz::InitCustomAppWiz()函数末尾,其作用是在堆栈创建CRegistry对象,然后遍历上一步定义的静态结构。对于这个宏结构数组每一个元素,程序都会在注册表中找对应值,如果找到则取注册表中的值,否则取缺省值。不论哪一种情况,宏的值一旦建立,Dictionary字典的值就被更新为当前宏的值: CRegistry registry(HKEY_LOCAL_MACHINE, VCKBASEWIZ_KEY); CString strValue; for (int i = 0; i < sizeof macroPairs / sizeof macroPairs[0]; i++) { if (registry.ReadString(macroPairs[i].szRegistryValueName, strValue.GetBuffer(strValue.GetLength()))) { m_Dictionary.SetAt(macroPairs[i].szMacroName, strValue); } else { m_Dictionary.SetAt(macroPairs[i].szMacroName, macroPairs[i].szMacroDefaultValue); if (m_Dictionary.Lookup(macroPairs[i].szMacroName, strValue)) { registry.WriteString(macroPairs[i].szRegistryValueName, strValue.GetBuffer(strValue.GetLength())); } } }5、 将下列代码添加到CVckbaseWizAppWiz::ExitCustomAppWiz()函数末尾,其作用是当卸载定制的AppWizard DLL时程序会调用这个函数存储数据。 CRegistry registry(HKEY_LOCAL_MACHINE, VCKBASEWIZ_KEY); CString strValue; for (int i = 0; i < sizeof macroPairs / sizeof macroPairs[0]; i++) { if (m_Dictionary.Lookup(macroPairs[i].szMacroName, strValue)) { registry.WriteString(macroPairs[i].szRegistryValueName, strValue.GetBuffer(strValue.GetLength())); } } 图五 输入相应的信息后,单击“Finish”按钮,显示确认对话框,你在定制对话框中输入的信息也应该在此确认对话框中显示。单击“OK”按钮创建工程。然后编译并运行。对话框中可以见到三个按钮:“确定”、“取消”、“关于”。单击“关于”按钮,弹出对话框如图六: 图六 这就是我们定制的“关于”对话框。这个对话框中有带URL链接的静态文字、icon和Bitmap图像。 我真的觉得它很酷! [全文完] |
这为仁兄说的真好,小弟我是受益匪浅啊! ( yaofuzhi 发表于 2004-2-26 15:47:00) 在头文件中添加 #import "devbld.pkg" #include <bldauto.h> #include <blddefs.h> #include <bldguid.h> 在cpp文件中修改CustomizeProject函数 CustomizeProject(IBuildProject* pProject) { using namespace DSProjectSystem; long lNumConfigs; IConfigurationsPtr pConfigs; IBuildProjectPtr pProj; CString sTemp; // Needed to convert IBuildProject to the DSProjectSystem namespace pProj.Attach((DSProjectSystem::IBuildProject*)pProject, true); pProj->get_Configurations(&pConfigs); pConfigs->get_Count(&lNumConfigs); //Get each individual configuration for (long j = 1 ; j < lNumConfigs+1 ; j++) { _bstr_t varTool; _bstr_t varSwitch; IConfigurationPtr pConfig; _variant_t varj = j; pConfig = pConfigs->Item(varj); varTool = "link.exe";//修改链接选项 varSwitch = "yourlib.lib"; pConfig->AddToolSettings(varTool, varSwitch, varj); } } ( liuke716 发表于 2003-12-29 14:32:00) |
转载自:http://www.vckbase.com/document/viewdoc/?id=276
毕业以后想从事计算机的大学生必须去一个网站:http://www.rupeng.com/