第15章 定制一个AppWizard

我们的Scribble 程序一路走来,大家可还记得它一开始并不是平地而起,而是由AppWizard 以「程序代码产生器」的身份,自动为我们做出一个我所谓的「骨干程序」来?

Developer's Studio 提供了一个开放的 AppWizard 接口。现在,我们可以轻易地扩充AppWizard:从小规模的扩充,到几乎改头换面成为一种全新类型的程序代码产生器。

Developer's Studio 提供了许多种不同的项目类型,供你选择。当你选按 Visual C++ 5.0 整合环境中的【File/New】命令项,并选择【Projects】附页,便得到这样的对话窗画面:

除了上述这些内建的程序类型,它还可以显示出任何自定程序类型(custom types)。Developer's Studio(整合环境)和 AppWizard 之间的接口借着一组类和一些组件表现出来,使我们能够轻易订制合乎自己需求的 AppWizard。制造出来的所谓custom AppWizard(一个扩展名为 .AWX 的动态链接函数库,注),必须被放置于磁盘目录\DevStudio\SharedIDE\Template 中,才能发挥效用。Developers Studio 和 AppWizard 和AWX 之间的基本架构如图 15-1。

注:我以 DUMPBIN(Visual C++ 附的一个观察文件类型的工具)观察 .AWX 檔,得到结果如下:

E:\DevStudio\SharedIDE\BIN\IDE>dumpbin addinwz.awx
Microsoft (R) COFF Binary File Dumper Version 5.00.7022
Copyright (C) Microsoft Corp 1992-1997\. All rights reserved.
Dump of file addinwz.awx
File Type: DLL <--- 这证明 .AWX 的确是个动态链接函数库。
Summary
1000 .data
1000 .reloc
3A000 .rsrc
3000 .text

事实上AWX(Application Wizard eXtension)就是一个32位元的MFC extension DLL。

是不是Visual C++ 系统中早已存在有一些 .AWX 檔了呢?当然,它们是:

Directory of E:\DevStudio\SharedIDE\BIN\IDE
ADDINWZ  AWX      255,872  03-29-97  16:43 ADDINWZ.AWX
ATLWIZ   AWX      113,456  03-29-97  16:43 ATLWIZ.AWX
CUSTMWZ  AWX      278,528  03-29-97  16:43 CUSTMWZ.AWX
INETAWZ  AWX      91,408  03-29-97  16:43 INETAWZ.AWX
MFCTLWZ  AWX      146,272  03-29-97  16:43 MFCTLWZ.AWX
5 file(s)          885,536 bytes

请放心,你只能够扩充(新增)项目类型,不会一不小心取代了某一个原已存在的项目类型。

图15-1 Developers Studio和AppWizard 和*.AWX 之间的基本架构

到底Wizard是什么?

所谓Wizard,就是一个扩展名为.AWX 的动态链接函数库。Visual C++ 的 "project manager" 会检查整合环境中的Template 子目录(\DevStudio\SharedIDE\Template),然后显示其图标于【New Project】对话窗中,供使用者选择。

Wizard 本身是一个所谓的「template 直译器」。这里所谓的"template" 是一些文字文件,内有许多特殊符号(也就是本章稍后要介绍的macros 和 directives)。Wizard 读取这些template,对于正常文字,就以正常的output stream 输出到另一个文件中;对于特殊符号或保留字,就解析它们然后再把结果以一般的 output stream 输出到文件中。Wizard 所显示给使用者看的「步骤对话窗」可以接受使用者的指定项目或文字输出,于是会影响 template 中的特殊符号的内容或解析,连带也就影响了Wizard 的stream 输出。这些stream 输出,最后就成为你的项目的源文件。

Custom AppWizard 的基本操作

Developers Studio提供了一个让我们制作custom AppWizard的Wizard,就叫作 Custom AppWizard。让我们先实地操作一下这个工具,再来谈程序技术问题。

注意:以下我以 Custom AppWizard 表示 Visual C++ 所附的工具,custom AppWizard 表示我们希望做出来的「订制型 AppWizard」。

选按【File/New】,在对话窗中选择 Custom AppWizard,然后在右边填写你的项目名称:

按下【OK】钮,进入步骤一画面:

你可以选择三种可能的扩充型式:

1. An existing project:根据一个原已存在的项目文件(*.dsp)来产生一个custom AppWizard。

2. Standard MFC AppWizard steps:根据某个原有的AppWizard,为它加上额外的几个步骤,成为一个新的 custom AppWizard。这是一般最被接受的一种方式。

3. Your own custom steps:有全新的步骤和全新的对话窗画面。这当然是最大弹性的展现啦,并同时也是最困难的一种作法,因为你要自行负责所有的工作。哪些工作呢?稍后我有一个例子使用第二种型式,将介绍所谓的macros和directievs,你可以从中推而想之这第三种型式的繁重负担。

我的目的是做出一个属于我个人研究室("Top Studio")专用的custom AppWizard,以原本的MFC AppWizard(exe)为基础(有六个步骤),再加上一页(一个步骤),让程序员填入姓名、简易说明,然后Top Studio AppWizard 能够把这些数据加到每一个原始代码文件最前端。所以,我应该选择上述三种情况的第二种:Standard MFC AppWizard steps,并在上图下方选择欲增加的步骤数量。本例为1。

接下来按【Next】进入 Custom AppWizard 的第二页:

既然刚刚选择的是 Standard MFC AppWizard steps,这第二页便问你要制造出MFC Exe或MFC Dll。我选择MFC Exe。并在对话窗下方选择使用的文字:英文。很可惜目前这里没有中文可供选择。

这样就完成了订制的程序。按下【Finish】钮,你获得一张清单:

再按下【OK】钮,开始产生程序代码。然后点选整合环境中的【Build/Top Studio.awx】。整合环境下方出现 "Making help file..." 字样。这时候你要注意了,上个厕所喝杯咖啡后它还是那样,一点动静都没有。原来,整合环境启动了 Microsoft Help Workshop,而且把它极小化;你得把它叫出来,让它动作才行。

如果你不想要那些占据很大磁盘空间的 HLP 文件和 HTM 檔,也可以把 Microsoft Help Workshop 关掉,控制权便会回到整合环境来,开始进行编译链接的工作。

建造过程完毕,我们获得了一个Top Studio.Awx文件。这个文件会被整合环境自动拷贝到\DevStudio\SharedIDE\Template 磁盘目录中:

Directory of E:\DevStudio\SharedIDE\Template
ATL          <DIR>         03-29-97  14:12 ATL
MFC       RCT    4,744     12-04-95  16:09 MFC.RCT
README    TXT    115       10-30-96  17:54 README.TXT
TOPSTU~1  AWX    523,776   04-07-97  17:01 Top Studio.awx
TOPSTU~1  PDB    640,000   04-07-97  17:01 Top Studio.pdb

现在,再一次选按整合环境的【File/New】,在【Projects】对话窗中我们看到 Top Studio AppWizard 出现了:

试试它的作用。请像使用一般的MFC AppWizard 那样使用它(像第4章那样),你会发现它有7个步骤。前6个和MFC AppWizard 完全一样,第7个画面如下:

哇喔,怎么会这样?当然是这样,因为你还没有做任何程序动作嘛!目前 Top Studio AppWizard 产生出来的程序代码和第4章的Scribble step0 完全相同。

剖析 AppWizard Components

图15-2是AppWizard components 的架构图。所谓AppWizard components,就是架构出一个AppWizard 的所有「东西」,包括:

1. Dialog Templates(Dialog Resources)

2. Dialog Classes

3. Text Templates(Template 子目录中的所有 .H 檔和 .CPP 檔)

4. Macro Dictionary

5. Information Files

图15-2 用以产生一个custom AppWizard的各种components

Dialog Templates 和 Dialog Classes

以Top Studio AppWizard 为例,由于多出一个对话窗画面,我们势必需要产生一个对话窗面板(template),还要为这面板产生一个对应的C++ 类,并以 DDX/DDV(第10章)取得使用者的输入数据。这些技术我们已经在第10章中学习过。

获得的使用者输入数据如何放置到程序代码产生器所产生的项目原始代码中?

喔,到底谁是程序代码产生器?老实说我也没有办法明确指出是哪个模块,哪个文件(也许就是 AWX 本身)。但是我知道,程序代码产生器会读取 .AWX 文件,做出适当的原始代码来。而 .AWX 不正是前面才刚由Custom AppWizard 做出来吗?里面有些什么蹊跷呢?是的,有许多所谓的macros和directives存在于 Custom AppWizard 所产生的"text template"(也就是template子目录中的所有 .CPP 和 .H 檔)中。以Top Studio AppWizard 为例,我们获得这些文件:

H:\U004\PROG\TOP.15:
Top Studio.h
StdAfx.h
Top StudioAw.h
Debug.h
Resource.h
Chooser.h
Cstm1Dlg.h <---- 稍后要修改此文件内容
Top Studio.cpp
StdAfx.cpp
Top StudioAw.cpp
Debug.cpp
Chooser.cpp
Cstm1Dlg.cpp  <---- 稍后要修改此文件内容
H:\U004\PROG\TOP.15\TEMPLATE:<---- 稍后要修改所有这些文件的内容
DlgRoot.h
Dialog.h
Root.h
StdAfx.h
Frame.h
ChildFrm.h
Doc.h
View.h
RecSet.h
SrvrItem.h
IpFrame.h
CntrItem.h
DlgRes.h
Resource.h
DlgRoot.cpp
Dialog.cpp
Root.cpp
StdAfx.cpp
Frame.cpp
ChildFrm.cpp
Doc.cpp
View.cpp
RecSet.cpp
SrvrItem.cpp
IpFrame.cpp
CntrItem.cpp
NewProj.inf
Confirm.inf

Macros

我们惯常所说的程序中的 macro,通常带有「动作」。这里的 macro 则是用来代表一个常数。前后以包夹起来的字符串即为一个macro 名称,例如:

class $$FRAME_CLASS$$ : public $$FRAME_BASE_CLASS$$

程序代码产生器看到这样的句子,如果发现$$FRAME_CLASS$$ 被定义为"CMDIFrameWnd",$$FRAME_BASE_CLASS$$ 被定义为 "CFrameWnd",就产生出这样的句子:

class CMDIFrameWnd : public CFrameWnd

Developer Studio 系统已经内建一组标准的 macros 如下,给 AppWizard 所产生的每一个项目使用:

宏名称               意义
APP            应用程序的CWinApp-driven class.
FRAME          应用程序的main frame class.
DOC            应用程序的document class.
VIEW           应用程序的view class.
CHILD_FRAME    应用程序的MDI child frame class(如果有的话)
DLG            应用程序的main dialog box class(在dialog-based 程序中)
RECSET         应用程序的recordset class(如果有的话)
SRVRITEM       应用程序的main server-item class(如果有的话)
CNTRITEM       应用程序的main container-item class(如果有的话)
IPFRAME        应用程序的in-place frame class(如果有的话)

另外还有一组macro,可以和前面那组搭配运用:

宏名称              意义
class             类名称(小写)
CLASS             类名称(大写)
base_class        基类的名称(小写)
BASE_CLASS        基类的名称(大写)
ifile             实现文件名称(.CPP 文件,不含扩展名)(小写)
IFILE             实现文件名称(.CPP 文件,不含扩展名)(大写)
Hfile             表头文件名称(.H 文件,不含扩展名)(小写)
hFILE             表头文件名称(.H 文件,不含扩展名)(大写)
ROOT              应用程序的项目名称(全部大写)
root              应用程序的项目名称(全部小写)
Root              应用程序的项目名称(可以引大小写)

图 15-3 列出项目名称为 Scribble 的某些个标准宏内容。

宏               实际内容

图15-3 项目名称为Scribble的数个标准宏内容

Directives

所谓directives,类似程序语言中的条件控制句(像是if、else 等等),用来控制text templates 中的流程。字符串前面如果以$$开头,就是一个 directive,例如:

$$IF(PROJTYPE_MDI)
...
$$ELSE
...
$$ENDIF

每一个 directive 必须出现在每一行的第一个字符。

系统提供了一组标准的 directives 如下:

$$IF
$$ELIF
$$ELSE
$$ENDIF
$$BEGINLOOP
$$ENDLOOP
$$SET_DEFAULT_LANG
$$//
$$INCLUDE

动手修改 Top Studio AppWizard

我的目的是做出一个属于我个人研究室专用的 Top Studio AppWizard,以原本的MFC AppWizard(exe)为基础,加上第7个步骤,让程序员填入姓名、简易说明,然后Top Studio AppWizard 就能够把这些数据加到每一个原始代码文件最前端。

看来我们已经找到出口了。我们应该先为Top Studio AppWizard 产生一个对话窗,当做步骤7的画面,再产生一个对应的C++ 类,于是DDX功能便能够取得对话窗所接收到的输入字符串(程序员姓名和程序主旨)。然后我们设计一些macros,再撰写一小段代码(其中用到那些macros),把这一小段代码加到每一个 .CPP 和 .H 檔的最前面。大功告成。

本例不需要我们动手写directives。

我想我遗漏了一个重要的东西。Macros 如何定义?放在什么地方?我曾经在本书第8章介绍Scribble 的数据结构时,谈到collection classes。其中有一种数据结构名为 Map(也就是Dictionary)。Macros 正是被定义并储存在一个Map之中,并以macro 名称做为键值(key)。

让我们一步一步来。

利用资源编辑器修改IDD_CUSTOM1对话窗画面

请参考第4章和第10章,修改IDD_CUSTOM1 对话窗画面如下:

两个edit控制组件的ID如图15-4所示。

利用ClassWizard修改IDD_CUSTOM1对话窗的对应类CCustom1Dlg

图15-4列出每一个控制组件的类型、识别代码及其对应的变量名称等数据。变量将做为DDX 所用。修改动作如图15-5。

control ID           名称        种类      变量类型
IDC_EDIT_AUTHOR   m_szAuthor    Value      CString
IDC_EDIT_COMMENT  m_szComment   Value      CString

图15-4 IDD_CUSTOM1对话窗控制组件的类型、ID、对应的变量名称

图15-5 利用ClassWizard 为IDD_CUSTOM1对话窗的两个edit

控制组件加上两个对应的变量m_szAuthor和m_szComment,以为DDX 所用

Custom AppWizard 为我们做出来的这个CCustom1Dlg必定派生自 CAppWizStepDlg。你不会在 MFC 类架构文件中发现 CAppWizStepDlg,它是 Visual C++ 的 mfcapwz.dll所提供的一个类。此类有一个虚函数 OnDismiss,当AppWizard 的使用者选按【Back】或【Next】或【Finish】钮时就会被唤起。如果它传回TRUE,AppWizard 就可以切换对话窗;如果传回的是FALSE,就不能。我们可以在这个函数中做数值检验的工作,更重要的是做 macros的设定工作。

改写OnDismiss虚函数,在其中定义macros

前面我已经说过,macros 的定义储存在一个Map 结构中。它在哪里?

整个Top Studio AppWizard(以及其它所有的 custom AppWizard)的主类系派生自系统提供的CCustomAppWiz:

// in Top StudioAw.h
class CTopStudioAppWiz : public CCustomAppWiz
{
    ....
};
// in "Top StudioAw.cpp"
CTopStudioAppWiz TopStudioaw;  // 类似 application object。
// 对象命名规则是 "项目名称" + "aw"。

你不会在MFC类架构文件中发现CCustomAppWiz,它是Visual C++的 mfcapwz.dll所提供的一个类。此类拥有一个CMapStringToString 对象,名为m_Dictionary,所以TopStudioaw 自然就继承了m_Dictionary。这便是储存macros 定义的地方。我们可以利用TopStudioaw.m_Dictionary[xxx] = xxx 的方式来加入一个个的macros。

现在,改写OnDismiss 虚函数如下:

这么一来我们就定义了三个 macros:

macro名称             macro内容
PROJ_AUTHOR           m_szAuthor
PROJ_DATE             szDate
PROJ_COMMENT          m_szComment

修改text template

现在,为Top Studio AppWizard 的template 子目录中的每一个 .H 檔和 .CPP 檔增加一小段代码,放在文件最前端:

/*
This project was created using the Top Studio AppWizard
$$PROJ_COMMENT$$
Project: $$Root$$
Author : $$PROJ_AUTHOR$$
Date : $$PROJ_DATE$$
*/

Top Studio AppWizard 执行结果

重新编译链接,然后使用 Top Studio AppWizard 产生一个项目。第7个步骤的画面如下:

由Top Studio AppWizard 产生出来的程序代码中,每一个 .CPP 和 .H 檔最前面果然有下面数行文字,大功告成。

更多的信息

我在本章中只是简单示范了一下「继承自原有之 Wizard,再添加新功能」的作法。这该算是半自助吧。全自助的作法就复杂许多。Walter Oney 有一篇 "Pay No Attention to the Man Behind the Curtain! Write Your Own C++ AppWizards" 文章,发表于Microsoft Systems Journal的1997 三月号,里面详细描述了全自助的作法。请注意,他是以Visual C++ 4.2 为演练对象。不过,除了画面不同,技术上完全适用于 Visual C++ 5.0。

Dino Esposito 有一篇文章"a new assistant",发表于Windows Tech Journal的1997 三月号,也值得参考。1997 年五月份的Dr. Dobb's Journal也有一篇名为"Extending Visual C++ : Custom AppWizards make it possible" 的文章,作者是John Roberts。