无标题文档

InstallShield简明使用教程

本电子书由ndugu制作，http://www.nethovel.com

[作者简介]： 丁有和，男，69年8月出生，江苏南京人，讲师，硕士，现执教于南京师范大学电气与电子工程学院。长期以年，作者一直尝试CAD、CAM、CAPP及CAI的研究和开发，在校学报及国内其它刊物上发表多篇文章，其中《UCDOS矢量汉字库的使用技术》发表在《微计算机应用》1997年第3期，《VC++控件的汉字及颜色的编程控制》发表在《计算机应用》1999年第2期。

前言

第 1 章 InstallShield集成开发环境

    1.1 集成开发环境概述
    1.2 菜单栏和工具栏
    1.3 项目工作区窗口
    1.4 集成开发环境的初步实践

第 2 章 InstallScript脚本语言基础

   2.1 InstallScript程序结构
    2.2 数据类型、运算符与表达式
    2.3 逻辑运算和判断选取控制
    2.4 循环语句
    2.5 函 数
    2.6 字符串操作
    2.7 结构体类型和指针
    2.8 链 表
    2.9 编译预处理
    2.10 文件及文件夹操作
    2.11 常用对话框操作

第 3 章 基本安装程序的建立

    3.1 创建安装项目
    3.2 理解程序框架
    3.3 添加和修改
    3.4 选择媒介发布

第 4 章 安装界面的设计

    4.1 概 述
    4.2 应用多媒体
    4.3 界面的效果设计
    4.4 对话框界面的定制
    4.5 界面的汉化

第 5 章 深入安装程序制作

    5.1 反安装
    5.2 安装类型和组件控制
    5.3 制作数据库应用程序的安装
    5.4 多个安装程序
    5.5 安装程序的调试

    附录A 使用InstallShield系统变量的缺省值
    附录B 使用InstallShield for Visual C++6.0
    附录C 最新InstallShield产品使用简介


前言

    在DOS时代，人们不会忘记，想要制作一个应用程序的安装往往是通过批处理文件来实现的，这种风格的安装程序常常令人们乐此不疲。直到UCDOS图形安装界面的出现，才让人感到一些新意，并为之一振。然而不久，具有易学易用的图形用户界面、多任务功能的Windows系统出现了，并大有取代DOS的趋势。直到Windows 95的问世，才使得DOS真正变为过去，成为PC机上最流行的操作环境，并且随着Windows 98与Internet Explorer集成的新特性的引入，越来越多的程序员已开始致力于Windows应用程序的研究与开发。

    同样，安装程序的运行环境也由原来的DOS变成了现在的Windows系统。安装一个软件或应用程序已不再仅仅是将相关的文件复制到硬盘中，而是必须允许用户按自己的愿望选择安装类型和安装路径，并且在不需要的时候，还要能够将安装的相关内容全部删除掉。这种Windows下的安装程序给人们留下了很深的印象，人们在惊叹Office 2000强大的安装功能和悦目的安装界面之后，已无法维持对DOS安装程序的留恋。

    InstallShield恰恰是在这种操作系统不断发展的潮流中应运而生的，从InstallShield3.0到5.5，从普通的安装程序制作到最新的Windows安装界面，InstallShield公司已开发出基本各种操作平台和软件开发环境的InstallShield产品。InstallShield for Microsoft Visual C++6(简称InstallShield VC版)和InstallShield Express Custom Edition for C++ Builder或Delphi(简称InstallShield Express C++ Builder或Delphi版)就是其中用户较为熟悉的产品。

    由于InstallShield功能强大、灵活性好、完全可扩展以及具有强有力的网络支持，在各种安装程序开发工具中脱颖而出，成为目前最为流行的制作安装程序的工具软件。而且，它所内建的脚本语言InstallScript使得用户可以像其他高级语言那样灵活地构造出自己的安装脚本程序来。正是因为这一点，InstallShield已经成为目前制作安装程序的工业标准。用户所熟悉的Visual Studio 98、Borland C++ Builder(Delphi)以及目前绝大多数的软件安装程序均是用InstallShield来制作的。

    由于InstallShield 5.5还支持Visual Basic 6.0，故本教程以InstallShield 5.5专业版为主，并从应用出发，深入详实地讨论制作一般应用类和数据库类安装程序的方法、技巧以及InstallScript语言基础，且在InstallShield用户界面函数的基础上，挖掘其运用能力，最大地发挥InstallShield的定制和扩展潜能，构造出颇具创意、功能完善的安装界面来。

    本教程主要讲述InstallShield开发环境、InstallScript脚本语言基础、基本安装程序的建立、安装界面的设计以及深入安装程序制作等共五章内容。各章内容并不停留在初学者的水平上，而且在制作安装程序的每个方面，都给出了更高、更深层次的方法，例如安装对话框的定制、使用自己的DLL库、操作注册表、安装界面的汉化、使用多媒体、反安装以及安装程序的调试等。

    为了帮助读者充分掌握InstallShield特性，本书给出大量有用的实例，这些实例均以[Ex_xxxx]命名，并在Windows 98、InstallShield 5.5专业版本调试通过。

    由于在软件开发环境和操作系统未来发展的几年里，Windows 98/2000以及VC++、C++ Builder、VB等还仍将是主流。在这种契机下，适时地将InstallShield 5.5的使用方法和技巧奉献给大家，必将对广大的程序员、软件开发者和爱好者有所帮助。

    由于时间仓促，加之作者水平有限，不当之处在所难免，恳请读者批评指正。
    感谢天极网及赵家雄、方舟先生，没有他们此教程不可能在网上出版。感谢一直关心和支持此项工作的家人和朋友们，尤其是我的妻子。


集成开发环境概述
    在Windows 95/98/NT操作系统中正确安装了InstallShield 5.5专业版后，就可以单击任务栏的"开始"，选择"程序"中的"InstallShield 5.5 Professional Edition"，再选择该程序文件夹下的"InstallShield 5.5 Professional Edition"就能运行InstallShield。第一次运行时，弹出如图1.1所示的界面。

    但当利用Project Wizard(项目向导)或其他工具创建一个安装项目后，就会出现InstallShield 5.5的完整界面，如图1.2所示。

它是由标题栏、菜单栏、工具栏、项目工作区窗口、文档窗口、输出窗口以及状态栏等组成的。

    标题栏是用来显示出当前窗口中的文件名，而且一般还有[最小化]、[最大化]或[还原]以及[关闭]按钮，单击[关闭]按钮将退出集成开发环境。

    菜单栏包含了集成开发环境中几乎所有的命令，它为用户提供了文档操作、安装脚本程序的编译、调试、窗口操作等一系列的功能。由于工具栏比菜单操作更为便捷，故常常将一些常用菜单命令也同时安排在工具栏上。

    项目工作区窗口包含用户安装项目的一些信息，包括文件组、组件、脚本文件、资源等。在项目工作区窗口中的目录项或图标处单击鼠标右键，有时还会弹出相应的快捷菜单，它包含当前状态下的一些常用操作。

    文档窗口位于集成开发环境中的右边，脚本文件、资源文件以及安装程序所需要的各种资源等都可以通过该窗口显示出来。

    输出窗口出现在集成开发环境窗口的底部，它包含了Build和Compile两个页面，分别用来显示建立和编译过程中的相关信息。

    状态栏位于集成开发环境的最底部，它用来显示当前操作状态、说明、文本光标所在的行列号等信息。


菜单栏和工具

    1.2.1 菜单栏

    在集成开发环境界面中，用户可以看到在它的上方排列着一系列的菜单，而每一个菜单下都有各自的菜单命令。在进一步与集成开发环境打交道之前，先了解各项菜单命令的基本功能是很有必要的，因为大部分的操作都是通过菜单来完成的。

    InstallShield 5.5的菜单栏中包含了File(文件)、Edit(编辑)、View(查看)、Insert(插入)、Project(项目)、Build(编译)、Tools(工具)、Windows(窗口)以及Help(帮助)等菜单。其中File、Edit、View、Windows和Help均与一般Windows应用程序的菜单用法相同。这里仅对Insert、Project、Build和Tools菜单作简单说明。

    Insert菜单

    Insert菜单中的命令主要用于项目及资源的创建和添加，它有三个菜单项：File into Script Files...、File into Setup Files...和Links into File Groups...，分别表示将某个文件插入脚本文件和安装文件中以及链接到文件组中。

    一般情况，这三个菜单项是被禁用的。当切换到项目工作区窗口的"Scripts"页面并选定其中的脚本文件目录项时，菜单项"File into Scripts Files"被激活；当切换到项目工作区窗口的"Setup Files"页面并选定其中的目录项时，菜单项"Files into Setup Files"被激活；而当切换到项目工作区窗口的"File Groups"页面并选中其中的"Links"目录项时，菜单项"Links into File Groups"被激活。

    Project菜单

    Project菜单中的命令主要用于项目的创建和相关内容的设置，它有三个菜单项：Project Wizard…、Visual Basic Project Wizard…和Setting…，分别用来创建一般安装项目、VB应用程序安装项目以及项目相关内容的设置。

    Build菜单

    Build菜单中的命令主要用来进行安装程序的编译、连接、调试、运行等操作，它包括这样的菜单命令：Compile(编译)、Run Setup(运行)、Debug Setup(调试)、Media(媒介)、Settings...( 设置调试和编译时的参数)。

    Tools菜单

    Tools菜单中的命令主要是一些用于运行或定制开发环境中的实用工具，如图1.3所示。

    值得一提的是，随着集成开发环境当前状态的改变，有些菜单中的菜单命令项还会随之变化。例如，当文档窗口没有任何脚本程序时，许多菜单项都是灰色的，用户不能使用它们。此外，InstallShiled 5.5与其他Windows应用程序一样，其菜单系统一般都遵循下列一些相同的规则：

    (1) 打开InstallShiled 5.5的"File"菜单，会看到"Open"菜单项文本后有"…"，若选择该菜单命令，则弹出通用文件"打开"对话框。因此，菜单文本后有"…"就表示其执行结果是将弹出相应的对话框。

    (2) 在"File"菜单文本中，其中"F"字母带下划线。它表示该菜单项的助记符是"F"，当按住"Alt"键不放，再敲击该字母时，对应的菜单项就会被选中。

    (3) 在"Open"菜单文本后有"Ctrl+O"字样，任何时候，先按下"Ctrl"健不放，然后再按"O"键就执行"Open"菜单项命令，弹出通用文件"打开"对话框。这表明"Ctrl+O"和该菜单项命令是一致的，"Ctrl+O"称为该菜单项的快捷键。

    1.2.2 工具栏

    尽管菜单命令可以完成各种操作，其相应的快捷键也可提高操作的效率。但是，菜单命令的操作相对繁琐，快捷键又需要用户加以记忆，所以，有时候用起来还略嫌不便。而工具栏是一种图形化的操作界面，具有直观和快捷的特点，熟练掌握工具栏的使用后，工作效率将大有提高。

    工具栏是一系列工具按钮的组合。当鼠标指针停留在工具栏按钮的上面时，按钮凸起，主窗口底端的状态栏上显示出该按钮的一些提示信息，并且如果指针停留时间长一些，就会出现一个小的弹出式的"工具提示"窗口，显示出按钮的名称。工具栏上的按钮通常和一些菜单命令相对应，提供了一种执行经常使用的命令的快捷方法。

    同菜单中的菜单命令项相似，当菜单命令项禁止使用时，相应的工具按钮也是灰色的，用户不能使用它们。

    InstallShield 5.5的工具栏如图1.4所示。


项目工作区窗口

    1.3.1 Scripts页面

    项目工作区窗口的Scripts页面用来管理安装程序的脚本文件，在该页面中各项脚本文件均以树状列表的型式显示出来的。每个目录项前都有一个图标，且顶层目录项前还有一个套在方框中的符号"+"。双击顶层目录项或单击最前面的"+"，则直接打开并显示该目录项的所有子项，此时顶层目录项前的"+"变成"-"号；再双击顶层目录项或单击最前面的"-"，则该项目的所有子项被收缩，只显示顶层目录项内容，此时顶层项目前的"-"又变成了原来的"+"号。

    展开顶层目录项的所有子项，双击以.rul为扩展名的脚本文件项，则在开发环境的右边的文档窗口中显示出该脚本文件的内容。为了增强安装程序代码的可读性，脚本文件的源代码内容往往是以不同颜色来显示的，各种颜色所代表的含义如下所示：

    白底黑字 一般文本

    黑底白字 被选定的文本

    青色底黑色字 文本的行标记

    白底红字 InstallShield的函数

    白底蓝字 InstallScript脚本语言的关键字

    白色底品红色字 常数

    白色底紫红色字 用""符号括起来的文本内容

    白底绿字 注释

    1.3.2 Components页面

    项目工作区窗口的Components页面用来管理安装程序的各项组件。缺省时，一个安装项目通常有程序文件(Program Files)、示例文件(Example Files)、帮助文件(Help Files)以及共享的DLL文件(Shared DLLs)共四个组件。双击某个组件目录项，则在主界面的右边窗口中显示出该组件相关属性的完整列表，这些属性都可以方便地进行相应的修改。

    1.3.3 Setup Types页面

    项目工作区窗口的Setup Types页面用来管理提供给用户的安装类型。缺省时，一个安装项目通常有Typical(典型)、 Compact(紧凑)和Custom(定制)共三种安装类型。双击某种安装类型，则在主界面的右边窗口中显示出该安装类型相关的组件。

    在组件中，凡是文件组前面有一个带钩号(√)的图标，表示该文件组已被选入相应的安装类型中。反复双击文件组前面的图标可在"选入"和"不选"之间进行切换。

    1.3.4 Setup Files页面

    项目工作区窗口的Setup Files页面用来管理在安装过程中所需要的安装文件，它通常有含有下面一些内容。

(1) Splash Screen(启动画面)

    InstallShield用Setup.bmp作为安装程序的启动画面。若在不同的语系(中文、English)中放置各自的位图文件，则安装程序将根据安装环境的不同语系选用相应的Setup.bmp；若将该文件放置在Language Independent(与语系无关)目录项中，则不管操作系统是何种语系，都将以此位图文件作为程序安装的启动画面。

(2) Language Independent(与语系无关)

    它允许用户为各种操作系统或专门为Windows 95/98/NT操作系统指定相应的安装文件，这样不管怎样的语系，安装程序都会根据相应的操作系统来拣选相应的文件。

(3) 其他语系相关的文件

    它允许用户为各种操作系统或专门为Windows 95/98/NT操作系统指定相应的安装文件，这样安装程序会根据相应的语系和语系下的操作系统来拣选相应的文件。

(4) Advanced Files(高级文件)

    在用具体的媒介发布时，允许用户在相应的媒介中放置一些非压缩文件。

    1.3.5 File Groups页面

    项目工作区窗口的File Groups页面用来管理安装项目所需要的文件组。缺省时，安装程序项目通常有Example Files(示例文件组)、Help Files(帮助文件组)、Program DLLs(应用程序所需的DLL文件组)、Program Executable Files(应用程序文件组)以及Shared DLLs(共享DLL文件组)共五个文件组。

    双击某个文件组，则在主界面的右边窗口中显示出该文件组的相关属性，用户可以方便地进行修改。

    1.3.6 Resources页面

    项目工作区窗口的Resources页面用来管理安装项目所需要的安装资源。缺省时，一个安装项目通常有String Table(字符串表)、Registry Entries(注册项)和Shell Objects(外壳对象)共三种资源，其中Shell Objects资源是在Windows 95/98和Windows NT 4.0及其以后操作系统中创建被安装应用程序的程序文件夹(Folder命令)、桌面图标或相应的快捷方式(Shortcut命令)。

    1.3.7 Media页面

    项目工作区窗口的Media页面用来管理程序发布时的媒介。在该页面中包含Media Build Wizard(媒介创建向导)、缺省的媒介以及用户新创建的媒介等项，单击"Media Build Wizard"将开始媒介创建向导，用来创建新的媒介。

    需要说明的是：在各个页面中，右击鼠标时都会弹出相应的快捷菜单，它包含当前状态下的一些常用操作。


集成开发环境的初步实践

    前面介绍了关于集成开发环境的一些基本情况，这里以空类型的安装项目为例，进一步说明集成开发环境的使用过程。

    1.4.1 创建一个空的安装项目

    在InstallShield 5.5中，利用Project Wizard(安装项目向导)和安装项目模板可以创建Windows应用程序、数据库应用程序以及其他类型程序的安装项目。这里，我们首先对空类型的安装项目作简单说明，其他安装项目类型将在以后的章节中陆续介绍。

    一个空类型的安装项目包含了一般安装项目的基本框架，只是安装脚本文件中没有相应的安装程序代码，因而不执行任何操作。创建一个空类型的安装项目的最大好处是可以帮助用户熟悉和掌握InstallScript语言的用法，并由此编制出简繁随意的安装程序来。

    在InstallShield 5.5中，想要创建一个空类型的安装项目，只需选择"File"菜单->"New"菜单命令，在"New"对话框中选定"Blank Setup"，并单击[确定]按钮即可，如图1.5所示。

    此时，InstallShield 5.5自动为该安装项目命名为"Blank Setup"，并定位到项目工作区窗口的Scripts页面，而且还在集成开发环境的右边窗口中打开相应的脚本文件。

    1.4.2 添加代码

    在空类型安装项目的脚本文件中，一开始是没有相应的安装程序内容的，它需要用户添加一些代码，例如：

...

program // 每个安装脚本程序都是以program开始

SprintfBox(INFORMATION, "问候", "%s", "您好！" ); // 消息对话框

endprogram // 每个安装脚本程序都是以endprogram结束

    1.4.3 编译并运行

    打开Build菜单，选择Compile菜单项或按快捷键Ctrl+F7，系统开始对"Blank Setup"进行编译，同时在输出窗口中在线地显示出编译过程的情况，当出现

Done - 0 error(s), 0 warning(s)

字样时，表示"Blank Setup"安装程序可以被运行了。

    在"Build"菜单中选取"Run Setup"菜单命令或按快捷键Ctrl+F5，就可以运行"Blank Setup"安装项目。

    运行刚开始，出现"Setup"对话框，用来显示准备安装向导的进展情况，如图1.6。

    然后才执行前面添加的程序代码，其结果如图1.7所示。

    本章着重介绍了InstallShield 5.5的集成开发环境，并以空类型的安装项目为例简单地说明了安装脚本程序的添加、编译、运行的过程。下一章将讨论InstallScript脚本语言的基础内容。


第 2 章 InstallScript脚本语言基础

    InstallScript是专门用来编写InstallShield安装程序的脚本语言。由于InstallScript和C语言极为相似，因而使得Visual C++用户编写安装脚本程序颇为得心应手。即使对于没有任何语言基础的用户来说，编写InstallScript程序也不会觉得无从下手，因为InstallScript程序结构是非常简单的。并且，
InstallScript为用户提供了超过250个的内部函数，从而使得用户不需要太多的代码就能编写出具有专业水准的安装程序来。

    2.1 InstallScript程序结构

    同其他程序设计语言一样，InstallScript脚本语言也有自己的程序结构。

    2.1.1 几个InstallScript程序

    下面先来看看几个比较简单的InstallScript程序。

    [例Ex_Hello] 一个简单的InstallScript程序，用来弹出"问候"对话框。
      STRING szTitle;
program
szTitle = "问候";
SetDialogTitle (DLG_MSG_INFORMATION, szTitle);
MessageBox("您好！", INFORMATION );
endprogram

     程序中，program...endprogram构成主程序体，每一个InstallScript程序中都必须包含一个且只有一个这样的主程序体。在主程序体外，只能是变量定义、用户函数定义以及预处理指令等，而程序体内可以包括若干条语句，每一条语句都由分号"；"结束。本例中，SetDialogTitle和MessageBox都是InstallScript的内部函数，它们分别用来设置对话框的标题和显示指定的信息文本，INFORMATION是一个系统预定义的常量，szTitle变量是在程序体外定义的字符串变量。

    [例Ex_Func] 自已定义一个函数，用来显示消息对话框。
      STRING szTitle; // 定义一个字符串变量
prototype MyMessage(STRING, STRING); // 自定义函数的声明
        program
szTitle = "问候";
MyMessage(szTitle, "您好！");
endprogram
      function MyMessage(szTitle, szMessage) /* MyMessage函数体 */
begin
SetDialogTitle (DLG_MSG_INFORMATION, szTitle);
MessageBox(szMessage, INFORMATION );
end;

      尽管本例的结果和Ex_Hello示例相同，但它使用了自定义函数MyMessage。InstallScript语言规定，一个自定义函数名必须在program关键字前面声明，而函数体代码的实现代码必须在endprogram后进行，且每个自定义的函数体都必须以begin开始end结束(注意end后要有分号"；")。程序中的"/*...*/"之间的内容或"//"开始一直到行尾的内容是用来注释的，它的目的只是为了提高程序的可读性，对编译和运行并不起作用。正是因为这一点，注释的内容可以用汉字来表示，也可以用英文来说明，只要便于理解就行。

    [例Ex_Include] 使用包含文件。
      #include "Sddialog.h";
      STRING szTitle, szMsg, svDir;
        program
          szTitle = "SdAskDestPath Example";
svDir = "C:\\EXAMPLE\\TARGET";
szMsg = "";
         // 获取用户指定的安装路径
if (SdAskDestPath(szTitle, szMsg, svDir, 0) = NEXT) then
TARGETDIR = svDir;
endif;
         // 显示用户指定的安装路径
SprintfBox(INFORMATION, "SdAskDestPath", "Successful.\n\nThe Target " +
"directory is: " + TARGETDIR);
        endprogram
#include "Sddialog.rul"

     该程序是使用Sd(Script Dialog，脚本对话框)对话框函数的一个示例。InstallScript语言规定，在调用Sd对话框函数时，需要在program前加上#include "Sddialog.h"语句，而在endprogram后加#include "Sddialog.rul"。与C语言相似，#include "Sddialog.h"和#include "Sddialog.rul"是InstallScript的编译指令，称为预处理指令。InstallScript编译系统会根据预处理指令#include中的文件名，把该文件的内容包含进来。也就是说，实际程序的代码长度是在原来长度的基础上增加了Sddialog.h和Sddialog.rul文件的长度。程序中，SprintfBox参数内容中的"\n"是换行符，即在"Successful."文本后回车换行。

    2.1.2 InstallScript程序的基本组成

     从上面的几个示例可以看出，一个InstallScript程序往往由预处理命令、函数、语句、变量以及注释等几个基本部分组成的。

    (1) 预处理命令

    在InstallScript程序的一开始经常出现含有以"#"开头的命令，它们是预处理命令。InstallScript提供了三类预处理命令：宏定义命令、文件包含命令和条件编译命令。

    (2) 函数

    一个InstallScript程序是由若干个函数组成的。这些函数中，有的是InstallScript系统中所提供的内部函数，有的是用户根据自己需要自己编制设计的函数(如例Ex_Func中的MyMessage)。

    (3) 语句

    语句是组成程序的基本单元，它可以是用来判断的条件语句，也可以是用来反复运行的循环语句等。这些语句是组成InstallScript程序中的最重要部分之一。

    (4) 变量

    大多数程序离不开变量。InstallScript变量的类型比较简单，主要有数值型(NUMBER)、字符串型(STRING)以及链表型(LIST)等，例如例Ex_Hello中的szTitle是一个STRING型变量。

    (5) 注释

    程序的目的不仅在于实现某种功能、解决某个问题，而且还在于数据结构和算法的交流。因此在程序中添加必要的注释是非常重要的，它能提高程序的可读性，帮助用户对程序的理解。

    需要说明的是，InstallScript不支持控制台的输入和输出，数据的输入和输出是通过对话框进行的。

    2.1.3 InstallScript程序的书写风格

     尽管InstallScript语言比C或C++语言容易理解，但对于初学者来说，一开始就养成良好的编程习惯仍然是非常重要和必要的。

    1. 标识符命名

     标识符是用来标识变量名、函数名、结构名、文件名等的有效字符序列。标识符命名的好坏直接影响程序的可读性，例如a1b1、c1d虽然是合法的标识符，但却是不好的标识符，因为它不能让人理解它们所代表的含义。下面几个原则是命名时所必须注意的：

    (1) 合法性

    InstallScript规定标识符由大小写字母、数字字符(0―9)和下划线组成，且第一个字符必须为字母或下划线。任何标识符中都不能有空格、标点符号及其他字符，例如下面的标识符是不合法的：

      93Salary，Youhe.Ding，$178，#5f68，r
注意，InstallScript中标识符的大小写是有区别的。例如，data、Data、DaTa、DATA等都是不同的标识符。
用户定义的标识符不能和系统的关键字同名。以下是43个InstallScript关键字：

        abort begin BOOL BYREF
        case CHAR default downto
        else end elseif endfor
        endif endprogram endswitch endwhile
        exit for function GDI
        goto HWND if INT
        KERNEL LIST LONG NUMBER
        POINTER program prototype repeat
        return SHORT step STRING
        switch then to typedef
        until USER while

    需要注意的是，用户定义的标识符还不能和InstallShield的函数名、系统变量名以及预定义的常量名相同。

    (2) 有效性

虽然，标识符的长度(组成标识符的字符个数)是任意的，但最好不能超过32个，因为InstallShield的编译系统只能识别前32个字符，也就是说前32个字符相同的两个不同标识符被有的系统认为是同一个标识符。

    (3) 易读性

    在定义标识符时，若能做到"见名知意"就可以达到易读性的目的。为了达到这个目的，许多Visual Basic、Visual C++及Delphi等程序员广泛使用匈牙利的命名规则来定义标识符，InstallScript也使用这个<
命名规则。

    匈牙利的命名规则是将标识符的类型(小写)来作为标识符的前缀。例如前面的szTitle表示一个字符串变量，其中的sz表示STRING变量类型。表2.1列出了常用变量的前缀。除此之外，对于函数名的命名往往使用多个单词来组成，每个单词的第一字母都是大写，例如前面的SdAskDestPath函数名。

    2. 缩进和注释

     缩进是指程序在书写时不要将程序的每一行都由第一列开始，而且在适当的地方加进一些空行或空格。它同注释一样，也是为了提高程序的可读性。

    注释的重要性已在前面论及过，这里不再重复。但要注意的是：

    (1) 注释应在编程的过程中同时进行，不要指望程序开发完成后再补写注释。那样只会多花好几倍的时间，更为严重的是，时间长了以后甚至会读不懂自己写的程序。 必要的注释内容应包含：脚本程序的总体注释(文件名、作用、创建时间、版本、作者及引用的手册、运行环境等)、函数注释(目的、算法、使用的参数和返回值的含义、对环境的一些假设等)及其他的少量注释。千万不要陈述那些一目了然的内容，否则会使注释的效果适得其反』些空行或空格。它同注释一样，也是为了提高程序的可读性。

    注释的重要性已在前面论及过，这里不再重复。但要注意的是：

    (1) 注释应在编程的过程中同时进行，不要指望程序开发完成后再补写注释。那样只会多花好几倍的时间，更为严重的是，时间长了以后甚至会读不懂自己写的程序。 必要的注释内容应包含：脚本程序的总体注释(文件名、作用、创建时间、版本、作者及引用的手册、运行环境等)、函数注释(目的、算法、使用的参数和返回值的含义、对环境的一些假设等)及其他的少量注释。千万不要陈述那些一目了然的内容，否则会使注释的效果适得其反。


2.2 数据类型

    程序可以看成是由数据结构和算法组成的。数据结构体现对数据的描述，而算法反映了对数据的操作及处理。任何一门计算机语言都必须包括数据类型、运算符与表达式等内容来定义和实现程序中的数据结构和算法。

    2.2.1 基本数据类型

    InstallScript的数据类型比其他任何高级语言的数据类型要简单易用，它只有基本类型和结构类型两类。这里先讨论InstallScript的基本数据类型。

    基本数据类型是InstallScript的内部数据类型，包括CHAR(字符型)、NUMBER(数值整型)等，表2.2列出各种基本数据的类型。

    需要说明的是：在InstallScript的数据类型中，除了BOOL、HWND及LIST类型不能使用小写外，其余的数据类型还有其小写形式，例如int、number、string等，用来提供一种方便。但是，InstallScript没有无符号数值类型以及浮点数值类型。

    2.2.2 常量与变量

    根据程序中数据的可变性，数据可以分为常量和变量两大类。

    1. 常量

    在程序运行过程中，其值不能被改变的量称为常量。常量可分为不同的类型，如1、20、0、-6为整型常量，‘a’、‘b’为字符常量。常量一般从其字面形式即可判别。

    InstallScript的常量有整型常量、字符常量和字符串常量等类型。这些常量的含义和C语言基本一致，故这里不再重复。

    需要说明的是，在InstallScript中还可以用一个标识符表示一个常量。

[例Ex_Define] 用#define定义符号常量。
#define TITLE "问候"
program
SprintfBox(INFORMATION,TITLE,"%s","您好！");
endprogram

    程序中用#define命令行定义TITLE，使其代表字符串常量"问候"，此后凡是在程序中出现的TITLE都代表"问候"。

    这种代替常量本身的标识符称为符号常量。在程序中使用符号常量不仅可以提高程序的可读性(标识符总比常量本身更具意义)，而且修改也极为方便。

    2. 变量

    变量是指在程序执行中其值可以改变的量。变量的作用是存储程序中需要处理的数据，它可以放在程序中的任何位置上。但无论如何，在使用一个变量前必须先定义这个变量。

    变量是用下面的格式语句进行定义的：

    类型 变量名表;

例如：
    NUMBER nNum1;
    NUMBER nNum2;
    NUMBER nNum3;
    BOOL bValidEntry;

    其中，nNum1、nNum2、nNum3被定义成整型变量，而bValidEntry被定义成布尔型变量。有时，还可以将同类型的变量定义在一行语句中，不过变量名要用逗号(,)分隔。例如上面的变量可这样定义：
    NUMBER nNum1, nNum2, nNum3;
      BOOL bValidEntry;

    在定义字符串常量时，可以指定字符串的长度，例如：

    STRING szUserName[128]; // 指定字符串的长度为128个字符

    若不指定其长度，则InstallScript自动指定。16位操作系统中，字符串的长度被指定为512个字符，而32位操作系统中，字符串的长度被指定为1024个字符。

    在定义变量时，需要注意：

    (1) 不能在主程序体(program...endprogram之间)或函数体内部(begin...end之间)定义变量，变量必须定义在程序体外或函数名与begin关键字之间。例如：

    function SdAskDestPath(szTitle, szMsg, svDir, nStyle)
    STRING szDlg, svDirLoc, szTemp;
      INT nId, nTemp;
      HWND hwndDlg;
      BOOL bDone;
    begin
      ...
    end;
(2) 在同一个主程序或同一个函数体中不能有同时出现两个相同的变量名。
(3) 不能在变量定义的同时，给变量赋初值。

    2.2.3 InstallScript运算符简介

    和其他的程序设计语言一样，InstallScript记述运算的符号称为运算符，运算符的运算对象称为操作数。一个操作数可以是变量、常量或是具体的数值等。对一个操作数运算的运算符称为单目运算符，如-a；对二个操作数运算的运算符称为双目运算符，如3+5等。

    InstallScript的运算符分为以下几类：

    算术运算符 ( +, -, *, /)
    关系运算符 ( <, >, =, <=, >=, != )
    逻辑运算符 ( &&, ||, ! )
      位运算符 ( &, |, ~, ^, <<, >> )
    赋值运算符 ( = )
    指针运算符 ( *, & )
    分量运算符 ( ., -> )
    下标运算符 ( [ ] )
  字符串运算符 ( ^, +, % )
          其它 ( 如BYREF运算符 )

    2.2.4 算术运算符

    算术运算符包括常用的加减乘除四则运算符以及单目正负运算符，如下所示：

    + (正号运算符，如+4等)
    - (负号运算符，如-4等)
    * (乘法运算符，如6*8等)
    / (除法运算符，如6/8等)
    + (加法运算符，如6+8等)

    - (减法运算符，如6-8等)
InstallScript中算术运算符和数学运算的概念及运算方法是一致的，但要注意以下几点：

(1) 除法运算
    两个整数相除，结果为整数，如7/5的结果为1，它是将小数部分去掉，而不是四舍五入。但InstallScript不支持浮点运算。
(2) 优先级和结合性
    在一个包含多种算术运算的混合运算中，先乘除后加减的运算规则是由运算符的优先级来保证的。InstallScript将表达式求值中多种运算之间的先后关系(即运算符之间的优先关系)用运算符的优先级表示。在算术运算符中，单目运算符的优先级最高，其次是乘、除，最后是加减。

    优先级相同的运算符，则按它们的结合性进行处理。所谓运算符的结合性是指运算符和操作数的结合方式，它有"从左至右"和"从右至左"两种。"从左至右的结合"又称"左结合"，是指运算符左边的操作数先与运算符相结合，再与运算符右边的操作数进行运算，如3*5/4的次序是先乘后除；而自右至左的"右结合"的次序刚好相反，它是将运算符右边的操作数先与运算符相结合，如-i+6相当于(-i)+ 6。

    在算术运算符中，除单目运算符外，其余运算符的结合性都是从左至右的。
(3) 关于书写格式
    在使用运算符进行数值运算时，若书写时没有在双目运算符两边加上空格，则有时编译系统会做出与自己想象中不同的理解。例如：
    -5*-6-7
和
    -5 * -6 - -7 // 注意空格
结果是不一样，前者发生编译错误，而后果的结果是37。

    为了避免上述情况的发生，在书写时，有时应有意识地加上一些括号。这样不仅增强程序的可读性，而且，尤其当对优先关系犹豫时，加上括号是保证正确结果的最好方法。

    2.2.5 赋值运算符和赋值表达式

    在InstallScript脚本语言中，赋值符"="是一个双目运算符，结合性从右至左，其作用是将赋值符右边操作数的值赋给左边的操作数。每一个合法的表达式在求值后都有一个确定的值和类型。赋值表达式的值是赋值符右边操作数的值，赋值表达式的类型是赋值符右边操作数的类型。例如下面语句：

    STRING szName;
      LONG nValue;
      BOOL bDone;
      HWND hInstance;
      INT iStyle;
      LIST LISTINFO;
    program
      szName = "InstallShield";
      nValue = 15;
      bDone = FALSE;
      hInstance = 0;
      iStyle = DLG_MSG_STANDARD|DLG_CENTERED;
      LISTINFO = ListCreate(STRINGLIST);
      ...

    但是，InstallScript不支持多重赋值运算，例如a = b = c 相当于C++的a = b ==c。也就是说，若 b 不等于c，表达式为a=0，若b和c相等，则表达式为a=1。


2.3 逻辑运算和判断选取控制

    InstallScript往往利用逻辑运算后的结果对程序进行判断、选取等控制。

    2.3.1 关系运算符

    关系运算是逻辑运算中比较简单的一种。所谓"关系运算"实际上是比较两个操作数是否符合给定的条件。若符合条件，则关系表达式的值为"真"，否则为"假"。在InstallScript编译系统中，往往将"真"表示为TRUE，将"假"表示为FALSE。而任何不为0的数被认为是"真"，0被认为是"假"。

    由于关系运算需要两个操作数，所以关系运算符都是双目运算符。 InstallScript提供了下列6种关系运算符：

    <(小于)，<=(小于等于)，>(大于)，>=(大于等于)，= (相等于)，!=(不等于)

    其中，前4种的优先级相同且高于后面的两种，但关系运算符的优先级低于算术运算符。

需要说明的是：

    (1) InstallScript赋值运算符和等于的关系运算符使用同一个"="符号。
    (2) InstallScript不支持赋值和关系运算同在一个表达式中的情形。例如，下面的语句是不允许的：
    if ((listID = ListCreate (NUMBERLIST)) = LIST_NULL)
    then
        . . .
    endif;

    2.3.2 逻辑运算符

    逻辑运算符是用于将多个关系表达式或逻辑量("真"或"假")组成一个逻辑表达式。InstallScript提供了下列3种逻辑运算符：

    ! 逻辑非(单目)
    && 逻辑与(双目)
    || 逻辑或(双目)
    "逻辑非"是指将"真"变"假"，"假"变"真"。
    "逻辑与"是指当两个操作数都是"真"时，结果才为"真"，否则为"假"。
    "逻辑或"是指当两个操作数中有一个是"真"时，结果就为"真"，而只有当它们都为"假"时，结果才为"假"。
    "逻辑非"、"逻辑与"和"逻辑或"的优先级依次从高到低，且"逻辑非"的优先级还比关系运算符高，而"逻辑与"和"逻辑或"的优先级却比关系运算符低。
无标题文档

    和C不一样，InstallScript对逻辑表达式的值非常敏感，例如下面代码：

    if (iVar = 10) && (MyFunction( ) = 0)
    then
        MessageBox("That is so true.", INFORMATION);
    endif;

    只有当&&运算符左边的结果为TRUE时，右边的函数MyFunction才会被执行。为了不引起误解，最好将上述代码改写成：

    if (iVar = 10) then
      if (MyFunction( ) = 0) then
        MessageBox("That is so true.", INFORMATION);
      endif;
    endif;

    2.3.3 if语句

    if语句是用来判定所给定的条件是否满足，并根据判定的结果("真"或"假")决定执行给出的两种操作之一。

    InstallScript提供了下列5种形式的if语句。

    (1) if-then结构

      if-then结构具有下列形式：
      if (条件表达式) then
          语句
      endif;

    当"条件表达式"表达为"真"时，then后面的语句才会被执行。一个"条件表达式"可以是一个布尔或整型常量、变量、产生布尔或整型结果的表达式以及能返回整型结果的函数。

    例如：
    if (szStringA = "exit") then
        AskYesNo ( "Are you sure you want to exit?" , NO );
    endif;
    当字符串szStringA和"exit"相等时，函数AskYesNo才会被执行，否则跳过if-then结构，执行后面的语句。

条件表达式两边的圆括号"( )"是可选的，但最好能使用，因为它能提高程序代码的可读性。
无标题文档

    (2) if-then-else结构

    if-then-else结构具有下列形式：

        if (条件表达式) then
            语句1
        else
            语句2
        endif;

    当"条件表达式"表达为"真"时，执行then后面的语句1，而当"条件表达式"表达为"假"时，执行语句2。例如：
      if szStringA = "exit" then
        AskYesNo ("Are you sure you want to exit?" , NO );
        // szStringA等于"exit"时
    else
        MessageBox ("Please wait... ", INFORMATION ); // szStringA不等于"exit"时
      endif;

    (3) if-then-else的嵌套结构

    在if-then-else语句中以包含一个或多个if-then-else语句称为if-then-else语句的嵌套。其一般形式如下：

        if (条件表达式1) then
          if (条件表达式2) then
              语句1 // 当条件表达式2为TRUE时执行
          else
              语句2 // 当条件表达式2为FALSE时执行
          endif;
        else
        if (条件表达式3) then
              语句3 // 当条件表达式3为TRUE时执行
          else
              语句4 // 当条件表达式3为FALSE时执行
        endif;
        endif;

    例如：

    if szStringA = "exit" then
      AskYesNo ("Are you sure you want to exit?" , NO );
    else
      if szStringA = "continue" then
        MessageBox ("Please wait...", INFORMATION );
      else
        UserErrorHandler;
      endif;
    endif;

    当字符串szStringA和"exit"相等时，执行函数AskYesNo；当szStringA和"continue"相等时，执行MessageBox，而当szStringA等于其他值时，用户自定义的UserErrorHandler函数被执行。
无标题文档

    (4) elseif结构

    elseif结构具有下列形式：

      if (条件表达式1) then
          语句1
      elseif (条件表达式2) then
          语句2
      elseif (条件表达式3) then
          语句3
          ...
      endif;
    例如：

      if szStringA = "exit" then
          AskYesNo ("Are you sure you want to exit?" , NO );
      elseif szStringA = "continue" then
          MessageBox ("Please wait...", INFORMATION );
      elseif szStringA = "reboot" then
          goto StartHere;
      endif;
          ...
      StartHere:
          ...

      代码中，StartHere是一个语句标号。当字符串szStringA和"exit"相等时，执行函数AskYesNo；若不相时等，则将szStringA和"continue" 比较，相等时执行函数MessageBox；若不相等，再将szStringA和"reboot"比较，...。

    (5) if 和goto结构

      if和goto的结构具有下列形式：
      if (条件表达式) goto 标号;
        当条件表达式为"真"时，将流程转到"标号"所在的位置。这种形式最简单，例如：
        Name:
        AskText("Company name:", "", szSrc);
      if (szSrc = "") goto Name;

    一旦szSrc为空字符串时，就不停地要求用户输入相关内容。
无标题文档

    2.3.4 switch...endswitch语句

    当程序有多个条件判断时，若使用if语句则可能使嵌套太多，降低了程序的可读性。开关语句switch能很好地解决这个问题，它具有下列形式：

    switch ( 表达式 )
          case 常量1 ：语句1
          case 常量2 ：语句2
              ...
               case 常量n ：语句n
              default ：语句n+1
    endswitch;

    当表达式的值与case中某个值相等时，就执行该case中"："号后面的所有语句。若case中所有的值都不等于表达式的值，则执行default:后面的语句，若default不存在，则跳出switch 结构。

    这里的表达式可以是一个常量、变量、算术表达式、逻辑表达式或一个有返回值的函数，但必须用圆括号"( )"括起来。case后面只能是一个常数或常量名，不能为变量名、有返回值的函数名、字符串表涉及的字符串名以及其他类型的表达式等。

    例如：

    STRING szMsg, svResult;
    NUMBER nvResult;
    program
      GetSystemInfo(VIDEO, nvResult, svResult); // 获得系统显卡类型
      switch (nvResult)
            case IS_UNKNOWN: szMsg = "用户显卡类型未知";
            case IS_EGA : szMsg = "EGA显卡";
            case IS_VGA : szMsg = "VGA显卡";
            case IS_SVGA : szMsg = "Super VGA (800 x 600) 显卡";
            case IS_XVGA : szMsg = "XVGA (1024 x 768) 显卡";
            case IS_UVGA : szMsg = "分辨率大于1024 x 768的显卡";
                  default : szMsg = "错误";
      endswitch;
        MessageBox(szMsg, INFORMATION);
    endprogram

    每次只有一个case语句块被执行，执行后，将跳出switch结构，执行endswitch后面的语句，这一点与C语言不同，InstallScrip的case语句后不需要break。


2.4 循环语句

    在InstallScript语言中，可以用以下几种形式的语句来实现循环：

    (1) 用goto语句和if语句构成循环
    (2) 用while..endwhile语句
    (3) 用repeat..until语句
    (4) 用for..endfor语句

    2.4.1 用goto语句和if语句构成循环

    goto语句为无条件转向语句，它的一般形式为：

        goto 语句标号；

    语句标号用标识符表示，它的命名规则与变量名相同，不能用整数来作为标号。结构化程序设计方法主张限制使用goto语句，因为滥用goto语句将使程序流程无规律、可读性差。但也不是绝对禁止使用goto语句。一般来说，可以有两种用途：一是从循环体中跳到循环体外，另一是与if语句一起构成循环结构。

    例如：

      Name:
          AskText("Company name:", "", szSrc);
          if (szSrc = "") then
            MessageBox("Please enter the company name.", SEVERE);
            goto Name;
          endif;

    2.4.2 while..endwhile语句

    while..endwhile循环语句具有下列形式：

      while (表达式)
        语句
      endwhile;
无标题文档

    “语句”是此循环的循环体，它可以是一条语句，也可以是多条的复合语句。当"表达式"为"真"时便开始执行while循环体中的语句，然后反复执行，每次执行都会判断"表达式"是否为"真"，若为"假"，则终止循环。
    [例Ex_While] 一个简单的while..endwhile循环程序。
        NUMBER nCount;
      program
        nCount = 1;
        while (nCount < 5)
          MessageBox ("This is still true.", INFORMATION);
          nCount = nCount + 1;
        endwhile;
      endprogram

    由于nCount的初始值为1，因此while的表达式为"真"，循环体第一次被执行，同时nCount被增加1，当循环4次后，nCount被增加到5，此时while的表达式为"假"，结束循环，执行endwhile后面的语句。

    需要说明的是，while..endwhile循环可以使用嵌套，但每一个while循环体都必须以endwhile来结束。在while循环体中不能定义语句"标号"。

    2.4.3 repeat..until语句

    repeat ..until循环语句具有下列形式：

      repeat
        语句
      until (表达式) ;

    "语句"是此循环的循环体，它可以是一条语句，也可以是多条的复合语句。当语句执行到until时，将判断"表达式"是否为"真"，若是，则继续执行循环体，直到下一次表达式等于"假"为止。

    [例Ex_Repeat] repeat ..until循环程序。
      NUMBER nCount;
      program
        nCount = 1;
        repeat
          MessageBox("Count is less than 5", INFORMATION);
          nCount = nCount + 1;
        until (nCount = 5);
        endprogram

    同while一样，在repeat循环体中也不能定义语句"标号"。
无标题文档

    2.4.4 for..endfor语句

    for..endfor循环语句具有下列形式：
      for X=A to B step C
        语句
      endfor;

    其中，X为循环控制变量(或称循环变量)，可以用任一简单变量来表示；A为循环变量初值；B为循环变量终值；C为循环变量的增量或步长。若省略step关键字，则增量自动为1。关键字to也可用downto代替，表示从数值从高到低自动减去C的量。

    [例Ex_For] for..endfor循环程序。
      NUMBER iCount;
      program
        for iCount = 1 to 5
          MessageBox ("You will see this 5 times", INFORMATION);
        endfor;
      endprogram
    [例Ex_DownFor] 使用downto的for..endfor循环程序。
      NUMBER j;
      program
        for j = 20 downto 10 step 5
          MessageBox ("You will see this 3 times", INFORMATION);
        endfor;
      endprogram

    同repeat一样，在for循环体中也不能定义语句"标号"。

    2.4.5 abort和exit

    abort(异常中断)和exit(退出)是InstallScript的两个关键字。在安装程序执行过程中，当用户按下Esc键、F3键或单击安装对话框中的[Cancel]按钮时，系统将自动执行abort，其目的是将已安装的内容从计算机系统中清除掉。任何时候，只要安装程序遇到abort，都会进行上述的处理。而exit只是中断安装程序的执行，因此若用户在未安装完之前需要程序中断，则应在主程序体中使用abort来代替exit。但也应注意，虽然exit还可代替程序中的endprogram，与program一起构成一个主程序体，但最好不要这样。


2.5 函 数

    在结构化程序设计中，通常需要若干个模块实现较复杂的功能，而每一个模块自成结构，用来解决一些子问题。这种模块化的结构设计思想能很好地发挥"团队"力量，在代码修改和重用上，极为方便和快捷。函数正是结构化设计程序的基本结构。

    2.5.1 概述

    InstallScript允许用户在安装程序中使用下列三种类型的函数：

    (1) InstallShield内部函数

    InstallShield中定义了250多个的内部函数，包括字符串处理、文件、路径以及文件夹等操作的函数。除了Sd对话框函数外，其他大多数内部函数可以直接在程序中进行调用。

    (2) 用户自定义的函数

    自定义的函数是用户自己构造的函数，它必须在主程序体program关键字前声明，而在endprogram后进行定义，才能在程序中进行调用。

    (3) DLL函数

    Windows动态链接库(Dynamic Linking Library，简称DLL)提供了一些特定结构的函数，能被应用程序在运行过程中装入和连接，且多个程序可以共享同一个动态链接库，从而可以大大节省内存和磁盘空间。从编程角度来说，动态链接库可以提高程序模块的灵活性，因为它本身是可以单独设计、编译和调试的。同大多数编程语言一样，在InstallShield 5.5中也可方便调用DLL函数。

    2.5.2 自定义函数的声明和定义

    在InstallScript脚本语言中，使用用户自定义函数必须先进行函数的声明，然后进行函数的定义。

    (1) 自定义函数的声明

    自定义的函数必须在主程序体program关键字前按下列形式进行声明：

      prototype 函数名(形参类型1，形参类型2，...);

    其中，prototype是InstallScript的关键字，它通知编译器该行语句是用来声明一个自定义函数。"形参类型"是指InstallScript的基本数据类型，如INT、STRING或SHORT等。例如下面的函数声明都是合法的：

      prototype FunctionName (INT, STRING, SHORT); // 声明有三个形参的函数
      prototype CopyBitmapExample (); // 声明一个没有形参的函数
      prototype FileTransfer (LONG, LONG, LONG, STRING, STRING);// 声明有五个形参的函数

    函数名是一个有效的InstallScript标识符(注意命名规则)，函数名后面必须跟一对圆括号"( )"，以区别于变量名及其他用户定义的标识名。函数的形式参数写在括号内，参数表中参数个数可以是0，表示没有参数，但圆括号不能省略，也可以是一个或多个参数，但多个参数间要用逗号分隔。
无标题文档

    (2) 函数的定义

    函数声明后，必须在主程序体endprogram后按照下列的形式进行相应的函数定义：

     function 函数名(形参1，形参2，...)
       定义函数内部使用的变量；
        begin
          语句
        end;

    函数的函数体由begin和end之间的若干条语句组成，用于实现这个函数执行的动作。"函数名"必须和声明时的函数相同，"形参"应与声明时的形参类型一一对应，但"形参名"可以任意的有效InstallScript标识符。如果需要在函数中使用其他变量，则这些变量应在begin前进行定义。

    [例Ex_MyFunc]使用自定义函数SetupScreen。
      prototype SetupScreen(NUMBER); // 声明一个函数，只有一个形参
         program
          SetupScreen(WHITE); // 自定义函数在程序体中的调用
        endprogram
      function SetupScreen (nColor) // 函数的定义，注意nColor与NUMBER是一一对应的
        number nDx, nDy; // 函数内部变量的定义
        begin
          GetExtents( nDx, nDy );
          Enable (FULLWINDOWMODE);
          Enable (INDVFILESTATUS);
          Enable (BITMAP256COLORS);
          Enable (DIALOGCACHE);

          SetTitle ("Installing " , 24, nColor);
          SetColor (BACKGROUND, BK_BLUE);

          SetColor (STATUSBAR, BLUE);

          SetTitle ("Setup", 0, BACKGROUNDCAPTION);
          Enable (BACKGROUND);
          Delay (1);
        end;

      和C语言一样，不允许在一个函数体中再定义函数。

    (3) 函数的调用

    函数调用的一般形式为：

      函数名( 实际参数表 );

    所谓"实际参数"(简称"实参")，它与"形参"相对应，是实际调用函数时所给定的常量、变量或表达式，且必须有确定的值。需要注意的是：实参与形参的个数应相等，类型应一致，且按顺序对应，一一传递数据。
    InstallScript中，调用一个函数的方式可以有很多，例如：

    RectangleArea (nLong, nWide);
    // 作为一个语句，不使用返回值，只要求函数完成一定的操作
    nArea = RectangleArea (nLong, nWide);
    // 将函数的返回值赋予一个变量
    nTotal= RectangleArea (nLong1, nWide1) + RectangleArea (nLong2, nWide2);
    // 将函数的返回值参与运算
无标题文档

    2.5.3 BYREF参数和函数的返回值

    在InstallScript的大多数函数中，函数的参数传递方式是"按值传递"的。所谓"按值传递"，是指当一个函数被调用时，系统根据实参和形参的对应关系将实际参数的值一一传递给形参，供函数执行时使用。函数本身不对实参进行操作，也就是说，即使形参的值在函数中发生了变化，实参的值也不会受到影响。
当然，由于某种原因，用户有时想要让形参的改变影响实参，这里就需要使用BYREF(引用)关键字。所谓"引用"，简单地说，它实际上是给一个已知变量起个别名，对引用的操作也就是对被它引用的变量的操作。一个函数能使用引用传递的方式是在函数声明时将形参类型前加上引用关键字"BYREF"。例如：

    prototype StrInvert(BYREF STRING );

    若形参列表中不止一个形参定义成引用形式，则必须在每个引用形参类型前加上引用关键字"BYREF"。例如：

    prototype StrChangeChar( BYREF STRING, CHAR, BYREF BOOL);
    // 注意，第二个形参没有被变成引用形式。

    下面来看看函数参数的引用传递的示例。

      [例Ex_ByRef] 函数参数的引用传递。

        prototype SwapString( BYREF STRING, BYREF STRING );

        STRING myStr1,myStr2;
          program

            myStr1 = "这是第一个字符串";
            myStr2 = "这是第二个字符串";

            MessageBox(myStr1,INFORMATION); // 结果显示"这是第一个字符串"
            SwapString(myStr1,myStr2);
            MessageBox(myStr1,INFORMATION); // 结果显示"这是第二个字符串"
          endprogram

        function SwapString(svString1, svString2)
          STRING sTemp;
          begin
            sTemp = svString1;
            svString1 = svString2;
            svString2 = sTemp;
          end;

    实际上，如果用户需要函数改变的数值个数只有一个的话，那么使用函数的返回值就显得非常随意。例如：

        function RectangleArea (nLength, nWidth)
          INT nVal;
            begin
              nVal = nLength * nWidth;
              return nVal;
            end;

    其中，关键字return负责将后面的值作为函数的返回值，并将流程返回到调用此函数的位置处。由于return的后面可以是常量、变量或任何合法的表达式，因此函数RectangleArea可以改成下列形式：

        function RectangleArea (nLength, nWidth)
          begin
            return nLength * nWidth;
          end;

    需要注意的是，一旦执行return语句后，在函数体内return后面的语句不再被执行。
无标题文档

    2.5.4 局部变量和全局变量

    InstallScript中每一个变量必须先定义后使用，若变量是在函数定义时函数名和begin之间定义的变量，则此变量就是一个局部变量，它只能在函数体内使用，而在函数体外则不能使用它。若变量是在主程序体program前定义，则该变量就是一个全部变量，它能被后面的所有函数或语句引用。

    在一个程序中，不能有两个同名的全局变量，但局部变量在其作用范围外可以同名，甚至可以和全局变量同名。

    [例Ex_Variable] 函数的局部变量。

      STRING szVal; // 定义的全局变量
      prototype AFunction();
        program
          szVal = "YES"; // 给全局变量赋值，结果为"YES"
          AFunction();
          MessageBox(szVal, INFORMATION); // szVal结果仍为"YES"
        endprogram

      function AFunction()
        STRING szVal; // 函数内部的局部变量，和全局变量同名
        begin
          szVal = "NO"; // 给局部变量赋值，结果为"NO"
        end;

    在InstallScript中，函数的形参变量被认为是局部变量。
无标题文档

    2.5.5 使用DLL函数

    出于对InstallShield函数功能的扩展，InstallShield专业版还允许用户使用外部DLL中的函数，其使用步骤如下：

    (1) 首先使用下列形式在主程序体program前声明所要使用的DLL函数：

        prototype .<函数名> ( 形参类型列表 );

    (2) 然后，使用UseDLL函数将DLL文件调入内存；
    (3) 接着，用下列形式调用已声明过的DLL函数；
        函数名 ( 实参 );
    (4) 最后，使用 UnUseDLL将DLL文件从内存中释放出来。

        [例Ex_DLL] 使用DLL函数。

            #define DLL_FILE "C:\\EXAMPLE\\DLLS\\MSVC\\MSC\\EXAMPLE\\WINDEBUG\\MYDLL.DLL"
              // 声明在MYDLL.DLL文件中的MydllReturn函数
            prototype MYDLL.MydllReturn( INT, POINTER );
            STRING szDLL, svString;
            INT nValue;
            POINTER psvString;
            NUMBER nResult;
            BOOL bDone;
            program

              szDLL = DLL_FILE;

        /*--------------------------------------------------------------------------*\
            * 将MYDLL.DLL文件调入内存。
        \*--------------------------------------------------------------------------*/
              nResult = UseDLL (szDLL);

              if (nResult = 0) then
                  MessageBox ("UseDLL successful \n\n.DLL file loaded.", INFORMATION);
              else
                  MessageBox ("UseDLL failed.\n\nCouldn’t load .DLL file.", INFORMATION);
                  abort;
              endif;
              bDone = FALSE;

              while (bDone != TRUE)
                Disable(BACKBUTTON);
                AskText("Enter an example string.", "Example string.", svString);
                psvString = &svString;
                nValue = StrLength(svString);

                // 调用DLL函数
                MydllReturn(nValue, psvString);

                SprintfBox(INFORMATION, "UseDLL", "MydllReturn() changed the string " +
"to: %s", svString);

                // 由用户控制while循环的终止
                if (AskYesNo("Do another example?", YES) = NO) then
                    bDone = TRUE;
                endif;
              endwhile;
/*--------------------------------------------------------------------------*\
* 将MYDLL.DLL文件从内存中释放出来。
\*--------------------------------------------------------------------------*/
              if (UnUseDLL (szDLL) < 0) then
                MessageBox("UnUseDLL failed.\n\nDLL still in memory.", SEVERE);
              else
                MessageBox("UnUseDLL successful.\n\n.DLL file removed from memory.",
                  INFORMATION);
              endif;

            endprogram


2.6 字符串操作

    同C语言一样，InstallScript也有许多字符串操作的运算符及其内部函数。

    2.6.1 字符和字符串

    虽然InstallScript支持数组类型，但它只支持一维数组。且这里的字符数组和字符串的概念很不一样。例如：

    CHAR str[10];

    program

      str="ABCDE"; // 产生编译错误

    endprogram

    但是，STRING类型的字符串概念与C语言一样。它是一个以＇\0＇为终止符的一维字符数组，使用数组下标可以获得相应的字符。例如：

    [例Ex_String] 使用字符串。

      prototype BlankLeadingZeros(BYREF STRING);

        STRING szString;

        program

        szString = "00001234";

        BlankLeadingZeros(szString);

        MessageBox(szString, INFORMATION);

      endprogram

      function BlankLeadingZeros(szString) // 将字符串的前导字符＇0＇变为空格。

        INT iVal, iLength;

        begin

          iVal = 0; // 字符数组的下标从0开始

          iLength = StrLength (szString);

          while (szString[iVal] = "0") && (iVal <= iLength)

            szString[iVal] = " ";

            iVal = iVal + 1;

          endwhile;

        end;

结果是将"00001234"字符串变为" 1234"。
无标题文档

    2.6.2 字符串的运算符

    在InstallScript脚本程序中，用户打交道最多的是字符串的操作。为此，InstallScript提供了下列的字符串运算符：

      ^ (在一个路径或文件名后添加另一个路径)

      + (在一个字符串后添加另一个字符串)

      % (在一个字符串中查找一个子串)

    例如：

      szStringVar = "C:\\MYPATH\\" ^ "YOURPATH\\FILENAME";

    的结果为

      "C:\MYPATH\YOURPATH\FILENAME"

    但是，如果用户忘记在C:\\MYPATH后加上反斜杠，即：

      szStringVar = "C:\\MYPATH" ^ "YOURPATH\\FILENAME";

    其结果仍然是上述结果。因为InstallScript的"^"运算符自动添加相应的反斜杠。

    用字符串的"+"可以将上述表达式改为下列形式：

      szStringVar = "C:\\MYPATH\\" + "YOURPATH\\FILENAME";

    但是，在使用字符串运算符中，不能在运算符的两边使用圆括号"( )"。例如，下面的表达式是错误的：

      szPath = szTestPath ^ (AUTOFILE + ".BAT");

    而应该用下列形式：

      szFile = AUTOFILE + ".BAT";

      szPath = szTestPath ^ szFile;

    需要特别注意的是，字符串运算符"%"是在一个字符串中查找一个子串，而不是C语言的求余运算符。例如：

      szStringVarA = "This is a sample string.";

      if (szStringVarA % "sample") then

        MessageBox("Operation complete",INFORMATION);

      endif;

    其结果是MessageBox被执行。若在查找时不要求大小写敏感，则上述代码可改为：

      szStringVarA = "This is a sample string.";

      if (szStringVarA % "SAMPLE") then

        MessageBox("Operation complete", INFORMATION);

      endif;

    需要说明的是，字符串运算符%与内部函数StrFind相似，但StrFind还返回被查找的字符串首字符在字符串中的位置。
无标题文档

    2.6.3 字符串和数值转换

    使用InstallShield的内部函数可以很容易地实现字符串和数值之间的转换。

    1. StrToNum函数

        StrToNum函数是将字符串转换成数值。函数原型如下：

          StrToNum (nvVar, szString);

    其中，nvVar是转换后返回的NUMBER数值，而szString用来指定源字符串。当转换成功后，函数返回0，否则返回负值。

需要说明的是，在将字符串转换成数值过程中，可能有下列几种情况：

      (1) 若字符串中的字符都是由"0"~"9"组成的，则将其转换成相应的数值。

      (2) 若字符串前面的一个或多个字符由"0"~"9"组成，则只转换其前面的字符。例如"-123ABC456"被转换成-123。

      (3) 若字符串第一个字符不是"0"~"9"或不是正负号，则不能转换。

      (4) 若字符串第一个字符是正负号，而第二个字符不是"0"~"9"，则不能转换。

    2. NumToStr函数

      与StrToNum相对应的NumToStr函数是将数值转换成字符串，其函数原型如下：

         NumToStr (svString, nValue);

      其中，svString是转换后返回的字符串，而nValue用来指定要转换的NUMBER数值。

        [例Ex_NumToStr] 将数值转换成字符串。

          #define DISK_DRIVE "C:\\"

            STRING szDrive, svString;

            NUMBER nSpace, nResult;

            program

              szDrive = DISK_DRIVE;

              nSpace = GetDiskSpace(szDrive);

              nResult = NumToStr(svString, nSpace);

              if (nResult < 0) then

                MessageBox("NumToStr failed.", SEVERE);

                abort;

              endif;

              SprintfBox(INFORMATION, "NumToStr", "Disk Space: %s", svString);

            endprogram

3. Sprintf函数

    Sprintf函数是将字符、字符串、数值按一定的格式转换成另外一个字符串，它和MFC的CString类中的Format函数极为相似。Sprintf函数原型如下：

      Sprintf (svResult, szFormat [,arg] [,...]);

    其中，svResult是返回的字符串，szFormat用来指定格式控制的字符串，arg是变量或常量列表，它的个数不能超过10个。函数成功调用后，返回得到的字符串长度。

    需要说明的是，szFormat字符串所包含的格式字符必须用百分号"%"来引导，由于它的格式与Format函数或C的printf等函数的含义相同，故这里不再重复。例如：

    Sprintf ( svResult , "%s:%d*%d" ,"屏幕分辨率为", 640, 480);

    则svResult的值为：“屏幕分辨率为：640*480”。
无标题文档

    2.6.4 字符串操作函数

    和C语言一样，InstallShield也提供了许多字符串函数，如表2.3所示。


2.7 结构体类型和指针

    至此，我们所使用的数据类型都是基本的类型，如INT、NUMBER、STRING等。但InstallScript还允许用户按一定的规则进行数据结构体类型的构造。同时提供指针的概念，方便用户对变量的地址进行操作。

    2.7.1 用typedef定义一个结构体

     一个结构体是由多种类型的数据组成的整体。组成结构的各个分量称为结构体的数据成员(简称为成员)。结构体是InstallScript提供的构造复杂数据类型的唯一手段。

     1. 定义结构体

         结构体定义的格式为：
           typedef 结构体名
            begin
              成员定义1；
              成员定义2；
              ...
              成员定义n；
            end;

     结构体定义是以关键字typedef开始的，结构体名应是一个有效合法的标识符。在结构体中的每个成员都必须通过"成员定义"来确定成员名及其类型。例如：

     typedef EMPLOYEE
      begin
        STRING szName[50]; // 姓名
        STRING szDepartment[50]; // 部门
        NUMBER nExtension; // 电话分机号码
      end;

    其中，EMPLOYEE是自己定义的结构体名，该结构有3个成员变量。一旦结构体类型定义后，就可以定义其结构体变量。例如：

      EMPLOYEE structEmployee;
    使用结构体类型时要注意：

      (1) 不能用赋值运算符将一个结构内容赋予另一个结构，如newstruct = struct1；
      (2) 成员变量类型若是STRING，则必须指定其大小；
      (3) 不能在函数体内部定义一个结构体类型；
      (4) 成员变量类型或结构体本身不能使用BYREF关键字，数据的传递用指针来进行。
无标题文档

    2. 结构体变量的引用

    当一个结构体变量定义之后，就可引用这个变量。使用时，遵循下列规则：
    (1) 只能引用结构体变量中的成员变量，并使用下列格式：
        结构体变量名.成员变量名
     例如：
        structEmployee.nExtension = 3057;
     "."是成员运算符，它的优先级是最高的，因而可以把structEmployee.nExtension作为一个整体来看待，它可以像普通变量那样进行赋值或各种运算。

    (2) 若成员本身又是一个结构体变量，引用时需要多个成员运算符一级一级地找到最低一级的成员。例如：
       typedef POINT
         begin
          SHORT nX;
          SHORT nY;
        end;
      typedef RECT
        begin
          POINT ptUpperLeft;
          POINT ptLowerRight;
        end;
      RECT rc;
     则有：
       rc.ptUpperLeft.nX = 0;
无标题文档

    2.7.2 指针

     如果在程序中定义了一个变量，在编译时系统就会给这个变量分配内存单元，并根据程序中定义的变量类型，分配一定长度的内存空间，每个内存单元中存放着变量的值。为了便于内存单元的存取，系统为每一个内存单元分配一个地址。这个"地址"称为"指针"。

    1. 定义和引用指针变量

    指针变量(pointer)是存放内存地址的变量，一般情况下该地址是另一个变量存储在内存中的首地址，这时又称该指针变量"指向"这个变量。例如：

    INT i;
    POINTER p;
    ...
    i = 5;
    p = &i;

    其中，i是一个值为5的整型变量，p是一个指针变量，&i是取变量i在内存中的地址；于是p的数值就等于变量i在内存中的地址值，因此p是一个指向变量i的指针。

    需要注意的是：一旦一个变量定义后，该变量在内存中的地址也就确定下来，不管以后对该变量如何赋值，在程序运行期间，其内存地址总是固定不变的。

    指针变量和所有变量一样，遵循先定义后使用的原则。InstallScript中定义一个指针变量是在变量名前加上关键字POINTER，若是结构体类型，则按下列格式：

      结构体类型名 POINTER 指针变量名1，[指针变量名2,...];
     例如：
        EMPLOYEE POINTER pPointerName；
    这时，结构体成员变量的引用就可以像下列形式：
        pPointerName->nExtension = 3057;
     程序中，"->"称为指向运算符，它的左边必须是一个指针变量，它等效于指针变量所指向的结构体类型变量。

    在定义一个指针后，系统也会给指针分配一个内存单元，但分配的空间大小都是相同的，因为指针变量的数值是某个变量的地址，而地址值的长度是一样的。
无标题文档

    InstallScript中有两个专门用于指针的运算符：

      &(取地址运算符)、*(取值运算符)

    运算符"&"只能对变量操作，作用是取该变量的地址；运算符"*"用于指针类型的变量操作，作用是取该指针所指内存单元中存储的内容。例如：

     EMPLOYEE MyStructure;
      POINTER pNum;
      STRING svString;
      ...
      pPointerName = &MyStructure; // 将结构体变量地址赋予结构体指针
      pNum = &nvNumber; // 将NUMBER变量地址赋予指针变量
      SprintfBox ( INFORMATION , "", "%d" ,*pNum );

    2. 函数的指针传递

    指针也可作为函数的形参类型，这样的形参改变后将影响实参的值。

    [例Ex_Pointer] 函数的指针传递。
     typedef RECT // 定义一个结构体
      begin
        SHORT sX;
        SHORT sY;
      end;
      RECT Rectangle; // 定义一个结构体变量
      RECT POINTER pRect; // 定义一个结构体指针变量
      prototype SizeRectangle(RECT POINTER); // 将函数的形参定义成指针
      program
        pRect = &Rectangle;
        SizeRectangle(pRect); // 结果Rectangle的sX和sY分别为10和5
        . . .
      endprogram
      function SizeRectangle(pRectangle)
        begin
          pRectangle->sX = 10;
          pRectangle->sY = 5;
        end;


2.8 链 表

    众所周知，在使用数组存放数据前，必须事先定义好数组的长度。而且，相邻的数组元素的位置和距离都是固定的，也就是说任何一个数组元素的地址都可以用一个简单的公式计算出来，因此这种结构可以有效地对数组元素进行随机访问。但数组元素的插入和删除会引起大量数据的移动，从而使简单的数据处理变得非常复杂、低效。为了能有效地解决这些问题，一种称为"链表"的结构类型得到了广泛的应用。

    2.8.1 概述

    链表是一种动态数据结构，它的特点是用一组任意的存储单元(可以是连续的，也可以是不连续的)存放数据元素。链表中每一个元素称为"结点"，每一个结点都是由数据域和指针域组成的，每个结点中的指针域指向下一个结点。

    实际上，链表中的每个结点可以有若干个数据和若干个指针。结点中只有一个指针的链表称之为单链表，是最简单的链表结构。

    但在InstallScript中，使用的是和C语言相似的单链表结构，并将指针域的操作变成InstallScript内部的工作方式，这使得用户只需通过相应的函数就可以简单地向链表添加字符串或整型数据。这些函数如表2.4所示。

    从表2.4中可以看出，InstallScript的单链表结构分为两种类型：一类是由字符串组成的链表；另一类是由整型数据组成的链表。InstallScript规定：在同一个链表中，结点的数据域要么由字符串组成，要么由整型数据组成，而不能同时存在这两种数据类型。
无标题文档

    2.8.2 建立链表

    在InstallScript中，使用ListCreate函数就可以建立一个单链表。这一点要比C语言的链表操作简单许多。例如下面的示例。

    [例Ex_CreateList] 创建链表。
        LIST listID; // 定义一个链表变量
        NUMBER nItem, nvItem;
        program
          listID = ListCreate(NUMBERLIST); // 创建链表
          // 检查链表是否被成功创建。
          if (listID = LIST_NULL) then
            MessageBox("不能创建链表。", SEVERE);
            abort;
          endif;
          // 向链表添加数据
          nItem = 1078;
          ListAddItem(listID, nItem, AFTER);
          nItem = 304;
          ListAddItem(listID, nItem, AFTER);
          // 返回链表当前结点的数据，结果为304
          ListCurrentItem(listID, nvItem);
          SprintfBox(INFORMATION, "ListCreate", "Current item in list: %d", nvItem);
          // 返回链表中第一个结点的数据，结果为1078。
          ListGetFirstItem(listID, nvItem);
          SprintfBox(INFORMATION, "ListCreate", "First item in list: %d", nvItem);
          ListDestroy(listID); // 删除链表并将链表从内存中释放出来。
        endprogram
       需要注意的是，当链表使用完之后，必须调用ListDestroy来删除链表，以释放被链表所占用的内存空间。
无标题文档

    2.8.3 链表元素的基本操作

    InstallScript中的ListAddItem、ListDeleteItem、ListFindItem及ListSetCurrentItem或ListAddString、ListDeleteString、ListFindString及ListSetCurrentString函数分别对链表中的字符串或整型数据的结点进行添加、删除、查找及修改等操作。

    [例Ex_AddAndSet] 向链表添加结点并进行修改。
      #include "Sddialog.h"
        STRING szTitle, szMsg;
        LIST listID;
        NUMBER nItem;
        program
          listID = ListCreate(NUMBERLIST);
          if (listID = LIST_NULL) then
              MessageBox("Unable to create list.", SEVERE);
              abort;
          endif;
          // 添加结点数据
          nItem = 1078;
          ListAddItem(listID, nItem, AFTER);
          nItem = 1304;
          ListAddItem(listID, nItem, AFTER);
          szTitle = "ListSetCurrentItem Example";
          szMsg = "Elements in listID:";
          // 显示链表内容
          SdShowInfoList(szTitle, szMsg, listID);
          nItem = 305;
/*--------------------------------------------------------------------------*\
          * 将当前结点的数据由1304更改成305。
\*--------------------------------------------------------------------------*/
          if (ListSetCurrentItem(listID, nItem) < 0) then
              MessageBox("ListSetCurrentItem failed.", SEVERE);
          endif;
          // 显示修改后的链表内容
          SdShowInfoList(szTitle, szMsg, listID);
          // 删除链表
          ListDestroy(listID);
        endprogram
        #define SD_SINGLE_DIALOGS 1
        #define SD_SHOWINFOLIST 1
        #include "Sddialog.rul"

    当然，用户还可通过ListGetFirstItem和ListGetNextItem函数来遍历链表的结点内容。

      [例Ex_SearchAll] 遍历链表的结点内容。
        STRING szTitle, szMsg;
        LIST listID;
        NUMBER nItem, nvItem, nResult;
        program
        // 创建一个整型数据链表并添加一些结点数据
          listID = ListCreate(NUMBERLIST);
          if (listID = LIST_NULL) then
              MessageBox("Unable to create list.", SEVERE);
              abort;
          endif;
        // 添加结点数据
        nItem = 1078;
        ListAddItem(listID, nItem, AFTER);
        nItem = 304;
        ListAddItem(listID, nItem, AFTER);
        // 以下是链表的遍历过程
        nResult = ListGetFirstItem(listID, nvItem);
        while (nResult != END_OF_LIST)
              szTitle = "ListGetFirstItem & ListGetNextItem";
            // 显示nvItem.
            SprintfBox(INFORMATION, szTitle, "%i", nvItem);
            nResult = ListGetNextItem(listID, nvItem);
        endwhile;
        // 删除链表
        ListDestroy(listID);
      endprogram


2.9 编译预处理

    InstallScript程序的源代码中可包含各种编译指令，这些指令称为预处理命令。虽然它们实际上不是。

    InstallScript语言的一部分，但却扩展了InstallScript程序设计的环境。

    InstallScript提供的预处理命令主要有宏定义命令、文件包含命令和条件编译命令。这些命令在程序中都是以"#"来引导，每一条预处理命令必须单独占用一行。由于它不是InstallScript的语句，因此一般在结尾没有分号"；"。

    2.9.1 宏定义

    在以前的程序中，曾用#define定义这样的一个符号常量：

    #define TITLE "问候"

    其中，#define是宏定义命令，它的作用是将字符串"问候"用TITLE来代替；TITLE称为宏名。再如：

    #define MAX_SIZE 145
    这样，将数值145用宏MAX_SIZE来代替。

需要注意的是：

    (1) #define、MAX_SIZE和145之间一定要有空格，且一般将宏名定义成大写，以与普通标识符相区别。
    (2) 宏被定义后，一般不能再重新定义，而只有当使用下列命令才可以：

        #undef 宏名

    (3) 一个定义过的宏名可以用来定义其它新的宏，但要注意其中的括号，例如：

      #define WIDTH 80
      #define LENGTH ( WIDTH + 10 )
        宏LENGTH等价于：
      #define LENGTH ( 80 + 10 )
        但其中的括号不能省略，因为当
        var = LENGTH * 20;
      若宏LENGTH定义中有括号，则预处理后变成：
        var = ( 80 + 10 ) * 20;
      若宏LENGTH定义中没有括号，则预处理后变成：
        var = 80 + 10 * 20;
      显然，两者的结果是不一样的。
    (4) InstallScript的宏定义不能带参数，仅限于使用简单的宏定义。
无标题文档

    2.9.2 "文件包含"处理

    所谓"文件包含"是指将另一个源文件的内容合并到源程序中。InstallScript语言提供了#include命令用来实现文件包含的操作，它的格式如下：

    #include "文件名"
    例如：
    // 将系统缺省路径中的文件包含进来
    #include "SUPPORT.RUL"
    // 包含一个含有变量或自定义函数声明的文件
    #include "DECLARE.RUL"
    // 从LIBRARY文件夹中包含一个文件
    #include "..\LIBRARY\WINSUB.H"
    #include "..\LIBRARY\SYSCHK.H"
    // 从DIALOGS文件夹中包含一个文件
    #include "..\DIALOGS\WELCOME\WELCOME.H"
    #include "..\DIALOGS\REGINS\REGINS.H"
    #include "..\DIALOGS\ICONS\ICONS.H"

     "文件包含"命令是很有用的，它可以节省程序设计人员的重复劳动。但在使用#include命令需要注意：

    (1) 一条#include命令只能包含一个文件，若想包含多个文件须用多条文件包含命令。
    (2) 包含的文件所指明的路径全名(含文件名)不能越过260个字符。
    (3) 当在文件中使用路径时，应该用单反斜杠"\"代替双反斜杠"\\"。
    (4) 若文件中不指明具体的路径，则编译器将在安装程序项目文件夹中或InstallShield的include文件夹中查找。
    (5) 不能将C语言的.H文件包含进来，因为编译器不能识别C语言结构。

    2.9.3 条件编译

    一般情况下，脚本源程序中所有的语句都参加编译，但有时也希望根据一定的条件去编译脚本源文件的不同部分，这就是"条件编译"。条件编译使得同一脚本源程序在不同的编译条件下得到不同的目标代码。
InstallScript提供的条件编译命令有几种常用的形式，现分别介绍如下：

    (1) 第一种形式
      #ifdef 标识符1
        程序段1
      [#ifndef 标识符1
        程序段2]
      #endif

    其中，#ifdef、#ifndef和#endif都是关键字，"程序段"是由若干条预处理命令或语句组成的。这种形式的含义是：如果标识符已被#define命令定义过，则编译"程序段1"，如果标识符未被#define命令定义过，则编译"程序段2"。
     (2) 第二种形式
      #ifdef 标识符
        程序段1
      [#else
        程序段2]
      #endif

    这种形式是第一种形式的替代，它的含义是：如果标识符已被#define命令定义过，则编译"程序段1"，否则编译"程序段2"。
无标题文档

    (3) 第三种形式
      #ifndef 标识符
        程序段1
      [#else
        程序段2]
      #endif
    这与前一种形式的区别仅在于，如果标识符没有被#define命令定义过，则编译"程序段1"，否则就编译"程序段2"。
    (4) 第四种形式
        #if 表达式1
          程序段1
        [#elif 表达式2
          程序段2
          ...]
        [#else
          程序段n]
        #endif

    其中，#if 、#elif、#else和#endif是关键字。它的含义是，如果"表达式1"为"真"就编译"程序段1"，否则如果"表达式2"为"真"就编译"程序段2"，...，如果各表达式都不为"真"就编译"程序段n"。

    使用InstallScript预处理命令需要注意：

    (1) 预处理命令只能放在主程序体外部。
    (2) 在#ifdef等预处理命令的同一行上不能添加注释。
    (3) 在#ifdef等预处理命令中不能使用像if、while及switch等流控制语句。
    (4) 在#ifdef或#ifndef 语句中只能测试整型常量。
    (5) 用户还可以#error预处理命令来定义自己的错误信息。例如：

    #define PRODUCTID 1
      #if (PRODUCTID = 1)
        #define PRODUCTNAME "Lite"
      #elif (PRODUCTID = 2)
        #define PRODUCTNAME "Professional"
      #endif
      #ifndef PRODUCTNAME
        #error PRODUCTID out of range.
      #endif
      若PRODUCTNAME没有用#define定义过，则用户定义的错误消息"3 out of range."被显示(若PRODUCTID为3)。


2.10 文件及文件夹操作

    InstallShield提供了许多函数用来对文件进行基本操作以及对文件夹进行创建、删除和查找等操作。

    2.10.1 文件基本操作

    文件基本操作包括复制、删除、查找、重新命名以及获取或设置文件的属性等。

(1) 文件的复制

    InstallShield提供的CopyFile与XCopyFile函数用来复制文件的，它们的原型如下：

      CopyFile (szSrcFile, szTargetFile);
      XCopyFile (szSrcFile, szTargetFile, nOp);

    这两个函数都是用来将文件从源文件夹复制到目标文件夹中。虽然，CopyFile函数不像XCopyFile可以复制源文件夹下所有的子文件夹中的文件，但它可以将复制到目标文件夹中的文件重新命名。显然，XCopyFile函数对成批复制文件非常有效，并可根据由nOp指定的方式来操作。nOp可以是下列的预定义值：

      COMP_NORMAL 正常方式，复制时覆盖相同的文件
      COMP_UPDATE_SAME 它和COMP_UPDATE_DATE或COMP_UPDATE_VERSION组合
      COMP_UPDATE_DATE 只有当源文件的日期和时间比相同的目标文件新时才会覆盖
      COMP_UPDATE_VERSION 只有当源文件的版本比相同的目标文件新时才会覆盖
      SELFREGISTER 复制文件时还进行自我注册操作
      SHAREDFILE 把复制的所有文件当作是共享的
      LOCKEDFILE 记录锁定的.dll和.exe文件以便Windows重启后更新
      EXCLUDE_SUBDIR 复制时不含有子文件夹
      INCLUDE_SUBDIR 连同子文件夹中的文件一起复制

    当然，这两个函数都有可能返回下列的值：

    0 成功复制
    COPY_ERR_CREATEDIR 目标文件夹不能创建
    COPY_ERR_MEMORY 不能为复制文件进程分配必要的内存
    COPY_ERR_NODISKSPACE 目标磁盘中没有足够的可用空间
    COPY_ERR_OPENINPUT 找不到源文件夹
    COPY_ERR_OPENOUTPUT 不能复制函数中指定的文件
    COPY_ERR_TARGETREADONLY 目标文件夹写保护
    -51 自我注册文件没有注册成功(只用于XCopyFile函数)
    其他负数 产生未知的错误

    需要说明的是，InstallShield使用系统变量SRCDIR和TARGETDIR表示源文件夹和目标文件夹的路径。由于安装程序中其他函数也会使用这些变量，因此在调用CopyFile与XCopyFile函数前，必须先用VarSave函数将SRCDIR和TARGETDIR的当前值保存，然后设置相应的路径，最后用VarRestore函数恢复SRCDIR和TARGETDIR的原来值。

    [例Ex_CopyFile] 文件复制示例。
      #define SOURCE_DIR "C:\\Windows"
      #define TARGET_DIR "D:\\Temp"
      NUMBER nResult;
      program
        VarSave (SRCTARGETDIR); // 将缺省的源文件夹和目标文件夹路径保存
        SRCDIR = SOURCE_DIR; // 设定源文件夹路径
        TARGETDIR = TARGET_DIR; // 设定目标文件夹路径
        // 复制文件，它等效于XCopyFile("*.TXT", "", COMP_NORMAL)
        nResult = CopyFile("*.TXT", "*.*");
        if ( nResult < 0) then
          MessageBox ("不能复制文件！", SEVERE);
        else
          MessageBox ("文件复制完毕。", INFORMATION);
        endif;
        VarRestore (SRCTARGETDIR); // 恢复缺省的源文件夹和目标文件夹路径
      endprogram
无标题文档

    (2) 文件的删除与重新命名

    InstallShield提供的DeleteFile与RenameFile函数分别用来文件的删除与重新命名，它们的原型如下：

      DeleteFile (szFile);
      RenameFile (szFileOld, szFileNew);

    需要说明的是：DeleteFile函数不能删除系统文件、只读文件、隐含文件以及网络上没有删除权限的文件。并且该函数使用TARGETDIR作为其工作路径。而RenameFile函数是将由SRCDIR指定的源文件夹下的文件重新命名并移至由TARGETDIR指定的目标文件夹中。

    [例Ex_Rename] 文件重新命名示例。
      #define TARGET_DIR "D:\\Temp"
      program
        VarSave (SRCTARGETDIR); // 将缺省的源文件夹和目标文件夹路径保存
        SRCDIR = TARGET_DIR; // 设定源文件夹路径
        TARGETDIR = TARGET_DIR; // 设定目标文件夹路径
        // 将My.TXT文件重新命名为MyNew.TXT
        if ( RenameFile("My.TXT", "MyNew.TXT") < 0 ) then
          MessageBox ("文件不能重新命名！", SEVERE);
        else
          MessageBox ("文件已重新命名。", INFORMATION);
        endif;
        VarRestore (SRCTARGETDIR); // 恢复缺省的源文件夹和目标文件夹路径

    (3) 文件的查找

    InstallShield提供两个函数用来查找文件，它们是：

      FindFile (szPath, szFileName, svResult);
      FindAllFiles (szDir, szFileName, svResult, nOp);

    其中，szDir和szPath用来指定要查找的路径，szFileName表示要查找的文件名，它可以使用通配符，svResult用来返回查找到的第一个文件。对于FindAllFiles函数来说，还可使用nOp指定查找的方式，当nOp为CONTINUE时表示从上一次查找停止的位置处开始查找，当nOp为RESET时表示在szDir中从头开始查找。

    [例Ex_FindFiles] 查找C:\Windows下的所有.INI文件。
      #define TARGET_DIR "C:\\Windows"
      NUMBER nResult;
      STRING svFileName;
      program
        nResult = FindAllFiles(TARGET_DIR, "*.ini", svFileName, RESET);
        while (nResult = 0)
          MessageBox(svFileName, INFORMATION);
          nResult = FindAllFiles(TARGET_DIR, "*.ini", svFileName, CONTINUE);
        endwhile;
        endprogram
      endprogram
无标题文档

    (4) 文件属性的获取和设置

    InstallShield提供的SetFileInfo与GetFileInfo函数分别用来设置与获取文件的属性。SetFileInfo函数的原型如下：

    SetFileInfo (szPathFile, nType, nAttribute, szValue);

    该函数是用来设置由szPathFile指定文件的由szValue指定的时间、日期或用来更改文件的属性。想要文件的时间和日期同时被改变，用户必须两次调用该函数，但若改变文件的其他多个属性，则只要在nAttribute中使用"|"组合进行一次调用就可实现。其中nType用来指定要更改的文件特征，它可以是下列值之一：

    FILE_ATTRIBUTE 表示一个或多个属性将被更改
    FILE_DATE 表示文件的日期将被更改
    FILE_TIME 表示文件的时间将被更改

    若当nType为FILE_ATTRIBUTE时指定的文件属性(nType为其它值时，该参数为0)，则szValue为""，而nAttribute可以是下列值之一或"|"组合：

    FILE_ATTR_ARCHIVED 设置"存档"属性
    FILE_ATTR_HIDDEN 设置"隐含"属性
    FILE_ATTR_READONLY 设置"只读"属性
    FILE_ATTR_SYSTEM 设置"系统"属性
    FILE_ATTR_NORMAL 当此值单独指定时，清除所有的文件属性，
    另一个函数GetFileInfo是用来获取一个已存在文件szPathName的属性、时间、日期和大小。它的原型如下：

    GetFileInfo (szPathName, nType, nvResult, svResult);
其中，nType用来指定获取文件属性的特征，它可以是下列值之一：
    FILE_ATTRIBUTE 获取文件属性，结果在nvResult中
    FILE_DATE 获取文件日期,格式为YYYY\MM\DD,结果在svResult中
    FILE_SIZE 获取文件大小，结果在nvResult中
    FILE_TIME 获取文件时间,格式为HH:MM:SS,结果在svResult中

    需要说明的是，用GetFileInfo获取文件属性时最好先将nType指定为FILE_ATTRIBUTE，若返回的nvResult值等于FILE_ATTR_NORMAL，则没有任何属性；但若返回的nvResult不等于FILE_ATTR_NORMAL，则需要与系统预定义的文件属性值进行"与"(&)操作以确定文件的具体属性，例如下面的代码片断：

    if (nvResult = FILE_ATTR_NORMAL) then
      // 正常文件
    else
    if (FILE_ATTR_HIDDEN & nvResult) then
      // 该文件有"隐含"属性
    endif;
    if (FILE_ATTR_READONLY & nvResult) then
      // 该文件有"只读"属性
    endif;
  endif;

    其中，返回的nvResult值可能是：

      FILE_ATTR_ARCHIVED 存档文件
      FILE_ATTR_DIRECTORY 目录文件
      FILE_ATTR_HIDDEN 隐含文件
      FILE_ATTR_READONLY 只读文件
      FILE_ATTR_SYSTEM 系统文件
      FILE_ATTR_NORMAL 正常文件
      endprogram
无标题文档

    2.10.2 文件夹与路径操作

    用于文件夹与路径操作的InstallShield函数有很多，表2.5列出所有的相关函数。

    需要说明的是：

    1. 关于DeleteDir函数

    DeleteDir函数能够删除由szDir指定的文件夹或文件夹中的所有内容，具体的删除操作还取决于nFlag的值。若nFlag为ALLCONTENTS时，则删除该文件夹所有的内容；若nFlag为ONLYDIR时，则仅当文件夹中的文件为空时才会删除该文件夹。需要注意的是：

    (1) DeleteDir函数不会将删除的文件夹放入Windows系统的"回收站"，因而一旦文件夹删除后不能再恢复。
    (2) 该函数不能删除当前文件夹以及网络系统中没有删除权限的文件。
    (3) 当指定的文件夹下有"只读"、"隐含"或"系统"文件时，该函数不会删除该文件夹。
    (4) szDir指定的文件夹路径中的"\"应是"\\"，例如：
        DeleteDir("D:\\Temp", ALLCONTENTS);
      endprogram
无标题文档

    2. 关于FindAllDirs

    FindAllDirs函数用来查找szDir指定的文件夹或该文件夹下的所有子文件夹，并将查找的结果保存在字符串链表listDirs中。函数中的nOp用来指定是(为INCLUDE_SUBDIR)否(为EXCLUDE_SUBDIR)查找所有子文件夹。
    [例Ex_FindFolders] 查找C盘下的所有子文件夹。
      LIST listDirs;
      program
        listDirs = ListCreate (STRINGLIST);
        FindAllDirs ("C:\\", INCLUDE_SUBDIR, listDirs);
        ListDestroy ( listDirs );
      endprogram

    需要说明的是，FindAllDirs函数支持Windows 95/98的长文件夹名。

    3. 关于GetValidDrivesList

    GetValidDrivesList函数用来获取系统中有效的驱动器列表，返回的结果取决于下面的nDriveType值：

      FIXED_DRIVE 查找硬盘驱动器
      REMOTE_DRIVE 查找网络驱动器
      REMOVEABLE_DRIVE 查找软盘驱动器
      CDROM_DRIVE 查找CD-ROM

    例如，下面的代码将查找所有的本地驱动器，并将结果存放在字符串列表中：

      listDirs = ListCreate (STRINGLIST);
      GetValidDrivesList ( listDirs , REMOVEABLE_DRIVE , -1 );
      GetValidDrivesList ( listDirs , FIXED_DRIVE , 10000 );
      GetValidDrivesList ( listDirs , CDROM_DRIVE , -1 );
      ListDestroy ( listDirs );

    代码中，GetValidDrivesList函数最后一个参数用来指定要查找的驱动器所含有的最少的磁盘空间，若此参数小于0，则查找时不会检测磁盘空间，从而大大加快查找速度，这对查找软驱和CD-ROM特别有用。


2.11 常用对话框操作(上)

    作为与用户交互的重要手段，对话框是InstallShield安装程序中最重要也是最主要的用户界面。在安程序程序运行过程中，对话框通过各种控件(如按钮、编辑框、列表框、组合框等)来捕捉用户的输入信息或数据。

    基于对话框的这种重要性，InstallShield为用户提供了大量的对话框函数，这其中包括内建对话框和Sd对话框函数。当然，用户也可以在安装程序代码中定义自己的对话框(在4.4节中，将讨论如何定制对话框)。

    2.11.1 内建对话框和Sd对话框

    InstallShield在系统内部定义了一些对话框，这些对话框称之为内建对话框，其相应的对话框函数可以在所有版本中使用。但为了提高对话框的功能和应用能力，作为补充和拓展，InstallShield还提供了一些高效、适应能力强的脚本对话框(Script Dialog)，由于它们是用InstallScript脚本语言代码来写的，因此又称之为Sd对话框。

    1. 内建对话框

    InstallShield的内建对话框函数中，除了AskYesNo、MessageBox、SprintfBox等少量对话框是调用Windows API函数外，其余的均是InstallShield自己构造的。表2.6列出了InstallShield全部的内建对话框函数。

    例如下面的代码，其结果如图2.1所示。

    program
      Welcome ( "欢迎" , 0 );
    endprogram

无标题文档

2. Sd对话框

    InstallSheld提供了大量的脚本对话框供用户使用，脚本对话框是按InstallScript特定的机制来创建的，并提供对话框的脚本语言代码供用户揣摩。为了与内建对话框函数相区别，脚本对话框的函数名都是以Sd为开头。表2.7列出了InstallShield全部的Sd对话框函数，

    但使用脚本对话框时一定要注意：

    (1) 与内建对话框函数相比，调用Sd对话框时还需要在安装脚本程序中添加另外两条语句，如下所示：

    #include "sddialog.h" // 添加Sd对话框的头文件
    program
      SdWelcome ( "欢迎" , "" ); // 调用Sd对话框
    endprogram
    #include "sddialog.rul" // 添加Sd对话框的脚本文件

其结果如图2.2所示。
无标题文档

    (2) 若调用Sd对话框函数时，其字符串实参为NULL字符串("")时，Sd对话框将该参数使用缺省的字符串文本。这一点，也适用于InstallShield的内建对话框函数。
    (3) 用户可以通过调用DialogSetInfo改变某些对话框中的控件。
    (4) 在Sd对话框函数中，用户可以将%P插入到字符串中，用于放置产品名称。缺省时，产品名称%P表示空字符串，但用户可以使用SdProductName函数来设定其具体的值。
    (5) 为了提高系统对Setup.rul的编译速度以及减小编译文件Setup.ins的字节数，在需要包含Sd对话框脚本文件前，先对SD_SINGLE_DIALOGS进行宏定义，然后再对具体的对话框标识(参见表2.7)进行定义。例如若调用SdShowInfoList、SdLicense对话框时，则可有如下定义：
    ...
    endprogram
    #define SD_SINGLE_DIALOGS 1
    #define SD_SHOWINFOLIST 1
    #define SD_LICENSE 1
    #include "Sddialog.rul"

    (6) 在所有InstallShield对话框中，若对话框含有[Next]、[Back]以及[Cancel]按钮。则单击[Next]、[Back]按钮，对话框函数相应地返回NEXT、BACK之值，而单击[Cancel]按钮，则将弹出"Exit Setup"对话框，用于确认是否真的退出安装。

    2.11.2 信息显示对话框

    对话框函数MessageBox、SprintfBox、SdLicense、SdShowInfoList、SdShowMsg和SdStartCopy分别用来显示用户资料、安装进程、错误代码等信息。

    1. MessageBox和SprintfBox

    MessageBox和SprintfBox都是调用Windows的API函数的消息对话框，且SprintfBox还能使用如Sprintf函数的格式控制符。它们的原型如下：

    MessageBox (szMsg, nType);
    SprintfBox (nType, szTitle, szFormat [,arg] [,...]);
    其中，nType用来指定在对话框中显示的图标类型，其预定义值的含义如图2.3所示。

    参数szMsg指定要显示的消息内容，如果消息太长，则可在消息文本中添加"\n"转义符来使其换行，szTitle指定对话框的标题。

    [例Ex_MessageBox] 使用MessageBox。
    STRING szTitle;
    STRING szMyComp;
    NUMBER nvDx,nvDy;
    program
      szTitle = "我的电脑";
      SetDialogTitle (DLG_MSG_INFORMATION, szTitle); // 设置对话框的标题为"我的电脑"
      GetExtents( nvDx, nvDy );
      Sprintf(szMyComp,"%s:%d x %d","屏幕分辨率为",nvDx,nvDy);
      MessageBox(szMyComp, INFORMATION );
    endprogram
    [例Ex_SprintfBox] 使用SprintfBox。
    NUMBER nvDx,nvDy;
    program
      GetExtents( nvDx, nvDy );
      SprintfBox ( INFORMATION , "我的电脑" , "%s:%d x %d" , "屏幕分辨率为",nvDx,nvDy);
    endprogram
无标题文档

    这两个示例的结果都是一样的，如图2.4所示。显然，SprintfBox要比MessageBox功能强大得多。

    需要说明的是：缺省时，这两个对话框函数显示的对话框中总有一个[确定]按钮。为了能发挥Windows API的MessageBox的功能，用户可以在上例的基础上作如一些修改。

    [例Ex_SprintfBoxEx] 扩展使用SprintfBox或MessageBox。
    #define MB_OK 0x0000 // 对话框中含有[确定]按钮
    #define MB_OKCANCEL 0x0001 // 对话框中含有[确定]、[取消]按钮
    #define MB_ABORTRETRYIGNORE 0x0002 // 对话框中含有[终止]、[重试]、[忽略]按钮
    #define MB_YESNOCANCEL 0x0003 // 对话框中含有[是]、[否]、[取消]按钮
    #define MB_YESNO 0x0004 // 对话框中含有[是]、[否]按钮
    #define MB_RETRYCANCEL 0x0005 // 对话框中含有[重试]、[取消]按钮
    #define MB_ICONHAND 0x0010 // 对话框中含有X形图标
    #define MB_ICONQUESTION 0x0020 // 对话框中含有？形图标
    #define MB_ICONEXCLAMATION 0x0030 // 对话框中含有！形图标
    #define MB_ICONASTERISK 0x0040 // 对话框中含有i形图标
    NUMBER nvDx,nvDy;
    program
      GetExtents( nvDx, nvDy );
      SprintfBox ( MB_YESNO|MB_ICONQUESTION ,"我的电脑" ,"%s:%d x %d" , "屏幕分辨率　　　　　　　　　　　　　　　为",nvDx,nvDy);
    endprogram

    结果在对话框中显示出[是]和[否]按钮，且图标变成了带问号的图标，如图2.5所示，其中MB_OK等符号常量的值必须按上述代码中的值进行定义。

    同样，若将MessageBox函数中的nType设置成MB_YESNO|MB_ICONQUESTION，也会有类似的结果。
    2. SdShowInfoList和SdStartCopy函数
    SdShowInfoList和SdStartCopy是用来显示多条信息的对话框。它们的原型如下：
    SdShowInfoList (szTitle, szMsg, listID);
    SdStartCopy (szTitle, szMsg, listData);
无标题文档

    其中，szTitle为对话框的标题，szMsg为对话框中最前面显示的消息。ListID和ListData是LIST数据类型的链表变量。

    [例Ex_ShowInfo] 使用SdShowInfoList和SdStartCopy。
    #include "Sddialog.h"
    STRING szTitle, szMsg;
    STRING svReturn, szInfo;
    NUMBER nvReturn;
    LIST listInfo;
    program
      listInfo = ListCreate(STRINGLIST); // 创建链表
      // 检测系统是否有CD-ROM
      GetSystemInfo (CDROM, nvReturn, svReturn);
      if (nvReturn = TRUE) then
        szInfo = "您的计算机中有一个 CD-ROM 驱动器。";
      else
        szInfo = "您的计算机中没有 CD-ROM 驱动器。";
      endif;
      // 将上述信息插入链表中
      ListAddString(listInfo, szInfo, AFTER);
      // 检测当前系统时间
      GetSystemInfo (TIME, nvReturn, svReturn);
      Sprintf(szInfo, "现在的时间是： %s.", svReturn);
      // 将上述结果插入链表中
      ListAddString(listInfo, szInfo, AFTER);
      // 检测系统基本内存大小
      GetSystemInfo (BASEMEMORY, nvReturn, svReturn);
      Sprintf(szInfo, "您的计算机的基本内存大小为：%ld k ", nvReturn);
      ListAddString(listInfo, szInfo, AFTER);
      szTitle = "SdShowInfoList示例";
      szMsg = "下面的信息和您的计算机有关：";
      SdShowInfoList (szTitle, szMsg, listInfo);
      szTitle = "SdStartCopy示例";
      SdStartCopy(szTitle, szMsg, listInfo);
    endprogram
    #define SD_SINGLE_DIALOGS 1
    #define SD_SHOWINFOLIST 1
    #define SD_STARTCOPY 1
    #include "Sddialog.rul"

结果如图2.6、2.7所示。


第 3 章 基本安装程序的建立

    上一章介绍了InstallScript脚本语言的一些基础内容，其目的是帮助用户能够编制出具有较高水平的安装程序。当然，建立一个安装程序无需用户从头开始，因为installshild的Project Wizard能快速有效地生成安装项目所需的程序框架。需要用户所做的，就是在该框架的基础上添加或修改一些内容以完善安装程序功能。

    3.1 创建安装项目

    在installshild 5.5中，Project Wizard用来制作一般应用程序的安装项目，Visual Basic Project Wizard则还专门为VB6.0应用程序进行定制。本节着重讨论Project Wizard的使用方法。

    3.1.1 使用Project Wizard

    运行installshild 5.5后，双击Project窗口中的ProjectWizard就可开始安装项目向导。

    (1) 首先，出现如图3.1所示的"Welcome"对话框，要求用户输入应用程序名称、公司名称、选择应用程序所使用的开发环境、应用程序的类型、版本号以及应用程序的可执行文件名，单击Browse按钮("..."符号的按钮)可将磁盘中已有的应用程序的可执行文件名调入。单击[帮助]按钮，弹出该对话框的帮助说明。

    当然，用户不一定现在就在"Welcome"对话框中输入相应的内容，因为在installshild的项目工作区窗口中也可以进行上述内容的修改和添加。
无标题文档

    (2) 保留缺省值，单击[下一步]按钮，出现如图3.2所示的"Choose Dialogs"对话框。该对话框用来让用户从列表中选定安装过程中所出现的对话框。在对话框列表项前面的方框中有钩号(√)的表示被选中，单击小方框可以在选中和未选中之间进行切换。每次选定对话框列表项时，"Choose Dialogs"对话框的左下角将会显示相应的对话框模型，单击[Preview]按钮还可按正常比例显示该模型。

    (3) 保留缺省值，单击[下一步]按钮，出现如图3.3所示的"Choose Target Platforms"对话框。该对话框用来让用户选择一个或多个操作系统类型，以决定可以在哪些操作系统中进行安装。

    (4) 保留缺省值，单击[下一步]按钮，出现如图3.4所示的"Specify Languages"对话框。该对话框用来让用户选择一个或多个语系，以决定创建的安装项目可以支持哪些语系。需要说明的是，installshild 5.5英文专业版只支持English语系的安装项目。
无标题文档

    (5) 保留缺省值，单击[下一步]按钮，出现如图3.5所示的"Specify Setup Types"对话框。该对话框用来让用户选择一个或多个应用程序的安装类型，以决定程序安装时供用户选择的安装类型。

需要说明的是，多个安装类型列表项的选定操作是通过鼠标来进行的。当用户按住Shift键不放，再单击鼠标可选定多个连续的列表项，若单击鼠标前按住的键是Ctrl，则可选定多个不连续的列表项。这种操作方式在Windows应用程序中几乎是一致的。

    (6) 保留缺省值，单击[下一步]按钮，出现如图3.6所示的"Specify Components"对话框。该对话框用来让用户添加或删除安装项目中的组件，单击[Add]按钮将在Components列表中添加一个组件，单击[Delete]删除选定的组件。
无标题文档

    (7) 保留缺省值，单击[下一步]按钮，出现如图3.7所示的"Specify File Groups"对话框。该对话框用来让用户添加或删除安装项目中的文件组，单击[Add]按钮将在File Groups列表框中添加一个文件组，单击[Delete]删除选定的文件组。

    (8) 保留缺省值，单击[下一步]按钮，出现如图3.8所示的"Summary"对话框。该对话框显示用户向导中选定的信息摘要，若用户对其中某项设置不满意，可单击[上一步]按钮进行重新选择。任何时候，单击[取消]按钮都会终止创建安装项目。

    (9) 单击[完成]按钮，installshield开始创建。稍等片刻，一个名为"Your Application Name"的安装项目的框架就创建好了，同时该项目的所有内容都被放在缺省的C:\My Installations\ Your Application Name文件夹中。当然，用户不必当心创建的安装项目名是否和以前创建的安装项目名重名，因为installshild会自动在重名的项目名后面依次加上"-1"、"-2"...。
无标题文档

    3.1.2 修改安装项目属性

    想要改变上述安装项目的缺省属性，可选择"Project"菜单下的"Settings..."命令。弹出相应的"Project Settings"对话框，如图3.9所示。

    对话框中含有"General"(一般信息)、"Information"(项目信息)、"Owner"(制造商信息)、"Instructions"(说明)、"Language"(语系)、"Operating Systems"(操作系统)等页面。

    其中，"Information"页面可供用户进行开发环境、应用程序的类型以及应用程序适用的行业的重新选择。而"Owner"页面用来指定产品名称、作者、开发商、Email地址、主页、公司名称、版权说明以及版本等信息。

    这里，我们着重讨论"Language"和"Operating Systems"两个页面。任何时候，选择这两个标签总可以进行语系和操作平台的添加。

    添加语系的操作步骤如下：

      (1) 将"Project Settings"对话框切换到"Language"页面(参见图3.9)；
      (2) 单击[Add...]按钮可向语系列表中增加新的语系，如图3.10所示；
      (3) 取消"Show only available languages"选中标记，则语系列表中显示出所有的语系；
      (4) 选定"Chinese (PRC)"，单击[OK]按钮，将给此安装项目添加一个新的语系"Chinese (PRC)"。

    添加操作平台的操作步骤如下：

      (1) 将"Project Settings"对话框切换到"Operating Systems"页面；
      (2) 单击[Add...]按钮可向操作系统列表中增加新的操作系统，如图3.11所示；
      (3) 取消"Show only available platforms "选中标记，操作系统列表中显示出其他一些操作系统；
      (4) 选定某操作系统列表项，单击[OK]按钮，将给此安装项目添加一个新的操作平台。


3.2 理解程序框架

    前面创建的安装脚本程序Setup.rul是用来进行安装初始化、显示安装对话框、安装所有的数据以及创建程序菜单项和桌面图标等操作的。

    3.2.1 安装初始化

    下面先来看看安装脚本程序Setup.rul的部分源代码：

    #include "sdlang.h" // 预定义语言标识的头文件
    #include "sddialog.h" // Sd对话框的头文件
    #define UNINST_LOGFILE_NAME "Uninst.isu"

    // 以下是安装过程所需要的自定义函数声明
    prototype ShowDialogs();
    prototype ...
    ...

    // 以下是全局变量的定义
    BOOL bWinNT, bIsShellExplorer, bInstallAborted, bIs32BitSetup;
    STRING svDir;
    STRING svName, svCompany, svSerial;
    STRING svDefGroup;
    STRING szAppPath;
    STRING svSetupType;

    program
      Disable( BACKGROUND ); // 安装界面不使用背景色
      // 安装初始化
      CheckRequirements();
      SetupInstall();
      SetupScreen();
      // 显示安装过程的对话框
      if (ShowDialogs()<0) goto end_install;
        // 安装所有的文件
      if (ProcessBeforeDataMove()<0) goto end_install;
      if (MoveFileData()<0) goto end_install;
      if (ProcessAfterDataMove()<0) goto end_install;
          // 安装注册
      if (SetupRegistry()<0) goto end_install;
          // 安装程序菜单项和桌面图标
      if (SetupFolders()<0) goto end_install;
          end_install:
          CleanUpInstall();
          // 若一个不可恢复的错误产生后，则清除已安装的部分内容
      if (bInstallAborted) then
          abort;
      endif;
    endprogram
    // 以下是自定义函数的代码
    // 略

    #include "sddialog.rul" // Sd对话框所必须的包含文件
    从上述代码过程可以看出，在程序框架中进行安装初始化的代码语句为：
    CheckRequirements();
    SetupInstall();
    SetupScreen();
无标题文档

    它们都是自定义函数的调用，其相应的函数代码如下：

    function CheckRequirements() // 检测安装所需要的环境
      NUMBER nvDx, nvDy, nvResult;
      STRING svResult;

      begin
        bIsShellExplorer = FALSE;
        bIsWindowsNT4 = FALSE;
        bIsWindowsNT351 = FALSE;
        bIsWindows95 = FALSE;
        bIsWindows98 = FALSE;
        // 测量屏幕分辨率，最小要求为640 x 480
        GetExtents( nvDx, nvDy );
        if (nvDy < 480) then
          MessageBox( @ERROR_VGARESOLUTION, WARNING );
          abort;
        endif;
        // 设置"安装"操作模式
        bIs32BitSetup = TRUE;
        GetSystemInfo( ISTYPE, nvResult, svResult ); // 获得操作系统的类型信息
        if (nvResult = 16) then
          bIs32BitSetup = FALSE;// 行16位安装程序
          return 0;
        endif;
// 检测目标操作系统
        GetSystemInfo( OS, nvResult, svResult );
      if (nvResult = IS_WINDOWSNT) then
          // 判定操作系统是Windows NT 4.0还是Windows NT 3.51,
        if (GetSystemInfo( WINMAJOR, nvResult, svResult ) = 0) then
          if (nvResult >= 4) then
            bIsShellExplorer = TRUE;
            bIsWindowsNT4 = TRUE;
          else
            bIsWindowsNT351 = TRUE;
          endif;
        endif;
      elseif (nvResult = IS_WINDOWS9X) then
        bIsShellExplorer = TRUE;
        // 判定操作系统是Windows 95还是Windows 98
        GetSystemInfo (WINMINOR, nvResult, svResult);
        if (nvResult < 10) then
          bIsWindows95 = TRUE;
        else
          bIsWindows98 = TRUE;
        endif;
      endif;
    end;
    function SetupInstall() // 设置安装时所需要的参数
      begin
        Enable( CORECOMPONENTHANDLING ); // 可以处理核心组件
        bInstallAborted = FALSE;
        if (bIs32BitSetup) then // 若是32位安装，将使用长路径名
          svDir = PROGRAMFILES ^ @COMPANY_NAME ^ @PRODUCT_NAME;
        else
          svDir = PROGRAMFILES ^ @COMPANY_NAME16 ^ @PRODUCT_NAME16;
        endif;
        TARGETDIR = svDir; // 设置目标文件路径
        SdProductName( @PRODUCT_NAME ); // 设置Sd对话框中产品的名称
        Enable( DIALOGCACHE ); // 将对话框使用高速缓冲机制，避免闪烁
        return 0;
      end;
    function SetupScreen() // 设置安装主界面的外观
      begin
        Enable( FULLWINDOWMODE ); // 安装主界面全屏显示
        SetTitle( @TITLE_MAIN, 24, WHITE ); // 主标题的颜色为白色、字体大小为24
        SetTitle( @TITLE_CAPTIONBAR, 0, BACKGROUNDCAPTION ); // 设置标题栏的标题
        Enable( BACKGROUND ); // 可以使用背景颜色
        Delay( 1 ); // 延时1秒
      end;

    代码中，凡是在标识符前有"@"符号的表示取安装项目中字符串资源中的相关内容。例如@TITLE_MAIN表示取字符串资源中的TITLE_MAIN字段内容。
无标题文档

    3.2.2 显示安装过程对话框

    显示安装过程对话框的函数是自定义函数ShowDialogs，其代码如下：
    function ShowDialogs()
      NUMBER nResult;
      begin
        Dlg_Start: // 开始的语句标号
        Dlg_SdWelcome:
        nResult = DialogShowSdWelcome(); // 显示欢迎对话框
        if (nResult = BACK) goto Dlg_Start; // 按[Back]按钮，转到一开始
          Dlg_SdLicense:
          nResult = DialogShowSdLicense(); // 显示许可协议对话框
        if (nResult = BACK) goto Dlg_SdWelcome; // 按[Back]按钮，转到标号Dlg_SdWelcome
        Dlg_SdRegisterUserEx:
        nResult = DialogShowSdRegisterUserEx(); // 显示用户注册信息对话框
        if (nResult = BACK) goto Dlg_SdLicense;
        Dlg_SdAskDestPath:
        nResult = DialogShowSdAskDestPath(); // 显示选择目的位置对话框
        if (nResult = BACK) goto Dlg_SdRegisterUserEx;
        Dlg_SdSetupType:
        nResult = DialogShowSdSetupType(); // 显示安装类型对话框
        if (nResult = BACK) goto Dlg_SdAskDestPath;
        Dlg_SdComponentDialog2:
        if ((nResult = BACK) && (svSetupType != "Custom") && (svSetupType != "")) then
          goto Dlg_SdSetupType;
        endif;
        nResult = DialogShowSdComponentDialog2();// 显示选择组件对话框
        if (nResult = BACK) goto Dlg_SdSetupType;
        Dlg_SdSelectFolder:
        nResult = DialogShowSdSelectFolder(); // 显示选择程序菜单项对话框
        if (nResult = BACK) goto Dlg_SdComponentDialog2;
        return 0;
      end;

    当然，上述的DialogShowSdSelectFolder等显示对话框的函数也都是自定义函数，用户可自行查看其代码。
无标题文档

    3.2.3 安装文件数据

    安装文件数据是安装程序所完成的最主要的任务，它不仅将应用程序所需要的全部文件正确地安装到用户指定的目的文件夹中，而且还要为以后反安装时注册一些内容。Setup.rul程序中的自定义函数
ProcessBeforeDataMove、MoveFileData和ProcessAfterDataMove就是实现上述过程，其代码如下：

    function ProcessBeforeDataMove() // 在真正数据安装前所做的必须准备
      STRING svLogFile;
      NUMBER nResult;
      begin
        InstallationInfo( @COMPANY_NAME, @PRODUCT_NAME, @PRODUCT_VERSION, @PRODUCT_KEY );
        // 注册安装信息
        svLogFile = UNINST_LOGFILE_NAME; // 反安装注册文件
        nResult = DeinstallStart( svDir, svLogFile, @UNINST_KEY, 0 );// 开始注册反安装信息
        if (nResult < 0) then
          MessageBox( @ERROR_UNINSTSETUP, WARNING );
        endif;
        szAppPath = TARGETDIR; // 用户指定的安装路径保存在 TARGETDIR变量中
        if ((bIs32BitSetup) && (bIsShellExplorer)) then // 32位安装时

        // 以下是反安装信息的注册
          RegDBSetItem( REGDB_APPPATH, szAppPath );
          RegDBSetItem( REGDB_APPPATH_DEFAULT, szAppPath ^ @PRODUCT_KEY );
          RegDBSetItem( REGDB_UNINSTALL_NAME, @UNINST_DISPLAY_NAME );
        endif;
        return 0;
      end;
    function MoveFileData() // 安装数据
      NUMBER nResult, nDisk;
      begin
        nDisk = 1;
        SetStatusWindow( 0, "" ); // 设置安装时的状态窗口参数
        Disable( DIALOGCACHE );
        Enable( STATUS ); // 显示进展指示器
        StatusUpdate( ON, 100 ); // 跟踪显示安装的进程
        nResult = ComponentMoveData( MEDIA, nDisk, 0 ); // 安装数据
        HandleMoveDataError( nResult ); // 处理安装时产生的错误
        Disable( STATUS ); // 关闭进展指示器
        return nResult;
    end;
    function ProcessAfterDataMove()
      STRING szReferenceFile, szMsg;
      begin
        // 在反安装之前，DeinstallSetReference用来检测相应的文件，
        // 若该文件正在使用，反安装将不会进行。
        szReferenceFile = svDir ^ @PRODUCT_KEY;
        DeinstallSetReference( szReferenceFile );
        return 0;
      end;
无标题文档

    3.2.4 创建程序菜单项和桌面图标

    Setup.rul代码中的自定义函数SetupFolders负责创建程序菜单项和桌面图标，其代码为：
    function SetupFolders()
    NUMBER nResult;
    begin
      nResult = CreateShellObjects( "" ); // 创建Shell对象
      return nResult;
    end;

    由于安装项目Resources页面的"Shell Objects"目录项中还没有程序菜单项和图标相关的内容，因而上述代码实际上并没有真的起作用。想要创建程序菜单项和图标，除了在Shell Objects目录项中添加相关内容外，还可使用CreateProgramFolder和AddFolderIcon等函数。如下面的代码过程：

    function SetupFolders()
      NUMBER nResult;
      STRING svPath;
      begin
        svPath = TARGETDIR ^ "MySDI.exe"; // 应用程序的路径全名
        LongPathToQuote ( svPath , TRUE ); // 使用长路径名
        // 创建程序菜单
        AddFolderIcon ( "" , "我的单文档应用程序" , svPath , "" , "" , 0 , "" , REPLACE );
        // 创建桌面图标
        AddFolderIcon ( FOLDER_DESKTOP , "我的单文档应用程序" , svPath ,
                        "" , "" , 0 , "" , REPLACE );
        file://nResult = CreateShellObjects( "" ); // 创建Shell对象
        return 0; file://nResult;
      end;

    同样，若只在"开始"->"程序"中创建一个程序文件夹，内有"我的单文档应用程序"和"ReadMe"程序项，则有下列代码：

    function SetupFolders()
      NUMBER nResult;
      STRING svPath;
      begin
         // 创建程序文件夹
        CreateProgramFolder ( SHELL_OBJECT_FOLDER );
        svPath = TARGETDIR ^ "MySDI.exe"; // 应用程序的路径全名
        LongPathToQuote ( svPath , TRUE ); // 使用长路径名
        // 在程序文件夹下创建程序菜单项
        AddFolderIcon ( SHELL_OBJECT_FOLDER , "我的单文档应用程序" , svPath ,
                        "" , "" , 0 , "" , REPLACE );
        svPath = "C:\\Windows\\NotePad.exe" + TARGETDIR ^ "ReadMe.txt";
        LongPathToQuote ( svPath , TRUE ); // 将路径名两边插入引号
        AddFolderIcon ( SHELL_OBJECT_FOLDER , "ReadMe" , svPath ,
                      "" , "" , 0 , "" , REPLACE );
        file://nResult = CreateShellObjects( "" );
        return 0; file://nResult;
      end;


3.3 添加和修改

    上述程序框架在编译运行后，不会有任何文件数据被复制，因为该安装项目还只是一个空的框架。所以，它还需要用户通过InstallShield的开发环境对相关文件、文件组、组件、程序菜单项和图标进行添加或修改等操作，并且还需在理解程序框架的基础上，添加一定的代码来完善安装程序的功能，如使序列号有效等。

为叙述方便起见，这里以前面用Project Wizard创建的"Your Application Name"安装项目为例。

    3.3.1 文件、文件组和组件的修改

    在InstallShield中，一个组件包含若干个文件组，一个文件组包含若干个文件。并且，用户可以通过InstallShield的开发环境中的相关属性使它们互相关联起来，如图3.12所示。

    根据图3.12所示的关联属性，我们可以作下列一些修改。

    1. 删除不要的组件

    打开"Your Application Name"安装项目，将项目工作区窗口切换到Components页面，选定组件项"Example Files"，右击鼠标，从弹出相应的快捷菜单中选择"Delete"菜单命令，删除该组件。按类似的操作，删除"Help Files"组件。这样，安装项目中只剩下"Program Files"和"Shared DLLs"组件。

    2. 删除不要的文件组

    将项目工作区窗口切换到File Groups页面，选定文件组项"Example Files"，右击鼠标，从弹出相应的快捷菜单中选择"Delete"菜单命令，删除该文件组。按类似的操作，删除"Help Files"和"Program DLLs"文件组。这样，安装项目中只剩下"Program Executable Files"和"Shared DLLs"文件组。

    3. 向文件组中添加文件

    将项目工作区窗口切换到File Groups页面，展开所有的文件组项。选定"Program Executable Files"的"Links"项，右击鼠标，从弹出相应的快捷菜单中选择"Insert Files"菜单命令。这里以Visual C++创建的单文档应用程序的MySDI.exe为例，将MySDI.exe(Release版本)文件调入。还有，需要在"Shared DLLs"文件组中将Windows\System文件夹下的mfc42.dll和msvcrt.dll调入。
无标题文档

    4. 向组件中添加文件组

    将项目工作区窗口切换到Components页面，选定组件项"Program Files"，双击右边窗口中的"Include File Groups"字段，弹出如图3.13所示的属性对话框。

    在该对话框中单击[Add]按钮，又弹出一个对话框，从该对话框的文件组列表中选定"Program Executable Files"，单击[OK]按钮。然后再单击属性对话框的[确定]按钮，文件组"Program Executable Files"就和组件"Program Files"关联起来。类似的，将"Shared DLLs"文件组与"Shared DLLs"组件相关联。

    需要说明的是，组件中的字段"Overwrite"属性起着非常关键的作用，因为它决定了在文件复制时的覆盖特征。缺省时是"ALWAYSOVERWRITE"，它的意思是：总是将源文件覆盖机器中已有的文件。这样就有可能将一些根本不能工作的老版本文件覆盖机器中已有新版本文件。为此，我们需要将组件的"Overwrite"值进行更改，尤其是"Shared DLLs"组件。

    双击"Shared DLLs"组件的"Overwrite"字段，弹出如图3.14的属性对话框。在该对话框的组合框中选择要覆盖的特性类型，通常我们选择如图3.14所示的类型，并进行如图3.14所示的设置。这样，只有源文件比用户机器中的相同文件具有新的时间和高的版本时才覆盖原文件。
无标题文档

    3.3.2 添加Shell对象

    为了能通过自定义函数SetupFolders中的CreateShellObjects函数创建程序菜单项或桌面图标，我们需要在工作区窗口Resources页面的Shell Objects目录项中添加相关内容。

    需要说明的是：在Windows系统中，Shell通常指资源管理器和程序管理器的外壳。展开Shell objects下的所有目录项，当用户右击某个目录项时，还将弹出相应的快捷菜单。通过它们用户可以在资源管理器外壳(用于Windows 95/98和Windows NT 4.0及其以后操作系统中)下创建程序文件夹(Folder命令)或快捷方式(Shortcut命令)，在程序管理器外壳(用于Windows 3.1和Windows NT 3.51操作系统中)下创建程序组(Group命令)或图标(Icon命令)。

    1. 只在"程序"菜单中添加"我的单文档应用程序"菜单项

其步骤如下：

    (1) 在InstallShield集成开发环境中，将工作区窗口切换到Resources页面，并展开Shell Objects目录项中所有的子项，如图3.15所示。

    (2) 选定"Start Menu"下的"Programs"子项，右击鼠标，从弹出的快捷菜单中选择"New"->"Shortcut"(快捷方式)菜单命令，此时会在"Programs"目录项下添加一个名为"New Shortcut-1"。
    (3) 选定刚才的"New Shortcut-1"，右击鼠标，从弹出的快捷菜单中选择"Rename"，将"New Shortcut-1"改为"App"。单击"App"，将在右边窗口中显示其属性内容，如图3.16所示。
    (4) 双击右边窗口中的字段名，如"Shortcut Text"，则弹出如图3.17所示的属性对话框。单击[>>]按钮将从字符串资源中选择一个字符串作为快捷方式的名称。
(　　5) 在这个对话框中，用户可以更改所有"Shortcut"属性，其含义和结果如表3.1所示。


3.4 选择媒介发布

    当安装项目的所有的内容被编译后，若没有任何错误，我们就可以通过Media Build Wizard创建适当的媒介来发布安装程序。

    3.4.1 使用Media Build向导

    一般来说，对于大型应用程序往往选择CD_ROM媒介来发布，而对于小型应用程序则往往选择3.5英寸软盘(1.44MB)来发布。创建媒介的过程如下：

    (1) 用下面的三种方式之一启动Media Build Wizard：

      选择"Build"菜单"->"Media Build Wizard"命令;

      单击InstallShield开发环境工具栏上的"Media Build"工具按钮。

      将InstallShield项目工作区窗口切换到"Media"页面，单击"Media Build Wizard"项，或右击鼠标，从快捷菜单中选择"Media Build Wizard..."命令。

    (2) Media Build Wizard启动后，出现"Media Name"对话框。用户在"Media Name"编辑框中可指定一个新的媒介名称。

    (3) 保留缺省值，单击[下一步]按钮，出现"Disk Type"对话框。在磁盘类型列表中，用户可以选择不同的媒介类型。其中，"single disk image"用于将文件打包在同一张磁盘中，可进行二次压缩。

    (4) 保留缺省值，单击[下一步]按钮，出现"Build Type"对话框。选中"Full Build"项将全部创建所需要的文件数据，其中包括压缩应用程序的所有文件，创建CAB文件及Setup.exe文件等。选中"Quick Build"项，将测试和检查安装程序是否按预期的方式进行。单击[Advanced...]按钮，则弹出相应的高级属性对话框，允许用户设置口令、文件的时间/日期、媒介创建的目标位置等属性。

    (5) 保留缺省值，单击[下一步]按钮，出现"Tag File"对话框。它用来输入公司名称及其他与应用程序相关的信息，这些信息将保存在Data.tag文件中。

    (6) 保留缺省值，单击[下一步]按钮，出现"Platforms"(操作平台)对话框。

    (7) 保留缺省值，单击[下一步]按钮，出现"Summary"(摘要)对话框。这里列出前面用户指定的选项，若用户不满意还可单击[上一步]按钮重新进行选择。

    (8) 单击[完成]按钮，InstallShield将根据用户的选择创建相应的媒介。这时用户还可看到一个"Building Media"对话框。一旦媒介创建完毕，用户可按[Finish]按钮结束Media Build Wizard。
无标题文档

    3.4.2 发送到实际的媒介中

    当一个媒介创建完成，并且安装项目被正确编译后，就可将其发送到实际的媒介中。这里以刚才创建的"New Media"为例，说明发送的过程。

    (1) 将InstallShield项目工作区窗口切换到"Media"页面，将会看到刚才的"New Media"，选定此项并右击鼠标，从弹出的快捷菜单中选择"Send Media To ..."命令。

    (2) 弹出"Select Type"对话框。它用来指定将媒介文件复制到硬盘还是软盘中。

    (3) 选中"Copy each ’disk’ in the media to a removable disk"(复制到软盘)项，单击[下一步]按钮，弹出"Select Removable Drive"对话框。若有一个以上的软驱，用户可从组合框进行选择。

    (4) 单击[下一步]按钮，弹出"Status"(状态信息)对话框。单击[Start]按钮，系统将根据需要提示用户插入一张格式化过的空白软盘，并开始复制相关文件数据。

    (5) 一旦文件数据复制完成后，出现"Status"对话框。单击[Close]按钮关闭对话框，完成媒介发送过程。

    若用户觉得上述过程比较麻烦，可直接将C:\My Installations\Your Application Name\Media\New Media\Disk Images\disk1文件夹中的内容复制到软盘中。需要注意的是，若创建的媒介数据不止一张软盘时，系统会将数据依次存入disk1、disk2、disk3...等文件夹中。

    本章从应用角度对基本安装程序的建立过程作深入细致的讨论，使用户不需要太多的脚本程序编写就可对一般应用程序进行包装发布。但是，若仅仅掌握上述技巧则远远不够，因为一个出色的安装软件还应有其独到之处，如果没有创意，那只能是一种重复劳动。因此，从下一章开始起，我们将就安装界面的定制话题作进一步探讨。


第 4 章 安装界面的设计

    前面讨论的对话框在安装程序中起着非常重要的作用，它们不仅可以获得用户选定的安装路径、需要安装的组件及程序菜单项，而且还可以用于密码和序列号的检测等。但这些对话框是为最后的文件数据安装作准备的，因此从另一个角度来说，用户的最终评价除了取决于对话框的界面效果外，在很大程度上还取决于最后的安装主界面的好坏。当然，为了使安装界面具有一定的"个性"，用户还需要在InstallScript语言的基础上，挖掘其运用能力，最大地发挥InstallShield的潜能。

    4.1 概 述

    与一般Windows应用程序界面相比，安装程序的界面(尤其是安装主界面)要简洁得多。正是因为这个原因，我们在设计安装界面时，尤其要考虑美学方面的要求。

    4.1.1 主界面元素

    InstallShield 5.5提供的图形界面元素包括：弹出窗口、消息文本、对话框、图标、位图、声音以及影像等。同样，组成安装主界面的元素一般也不会超出这些类型。图4.1是一个常见的安装主界面：
无标题文档

    它含有这样的一些元素：

    (1) 主标题

    在安装主界面中，主标题往往是安装的应用程序或软件的名称。一般情况下，主标题的字体是最大的。

    (2) 背景

    安装主界面的背景往往决定了界面视觉效果的基调，它可以是用户指定的渐变颜色或是纯色。当然也可以用一张图片作为其背景。

    (3) 广告牌

    InstallShield允许用户在文件复制过程中发布广告牌。所谓广告牌，是指这样的一些位图或元文件图像，它们是用来显示一切用于沟通、广告、培训、激发兴趣以及引起注意的文字或图片。

    (4) 进展指示器

    进展指示器，顾名思义，它是用来显示文件安装的进度。它有三种类型，一种是没有[取消]按钮的旧式指示器；另一种是只有[取消]按钮，但没有边框和标题栏的指示器；还有一种是带有[取消]按钮的进展对话框。图4.1的进展指示器是最后一种的进展对话框。

    (5) 信息指示器

    信息指示器用来反映安装过程中三个方面的信息，一是文件已复制到目的位置的百分比，另一是每张安装盘的进展情况，最后是目的驱动器中所含有的可用空间大小。如图4.2所示，当目的驱动器中可用的空间大小低于总容量的5%，最右下角的"Low"将变成红色。

    4.1.2 主界面操作函数

    InstallShield为用户提供了一些函数用来设置安装主界面的特性以及将界面元素在主界面窗口中的放置位置。表4.1列出这些函数的形式及其功能说:

    其中Enable函数是最经常使用的，它是用来激活并显示某个界面元素。其原型如下：

    Enable (nConstant);
无标题文档

    其中，nConstant表示要激活的界面元素，它可以是下列值之一：

    BACKBUTTON 表示[上一步](Back)按钮，缺省时该按钮是被激活。
    BACKGROUND 当安装处在窗口模式下时，显示安装界面的背景窗口。
    BITMAP256COLORS 激活位图的256色调色板
    DEFWINDOWMODE 设置安装界面的背景窗口是一个常规的、可调整大小及带标题栏的窗口。当　　　　 　BACKGROUND被Enable后，设置的窗口立即有效。
    FEEDBACK_FULL 表示信息指示器。
    FULLWINDOWMODE 将安装界面的背景窗口最大化。
    HOURGLASS 将鼠标指针变成"沙漏型"的等待光标。
    INDVFILESTATUS 在进展指示器中显示被传送的文件全名。
    NEXTBUTTON 表示[下一步](Next)按钮，缺省时该按钮是被激活。
    STATUS 激活标准进展指示器，信息指示器(若没有被禁用)也会被激活。
    STATUSDLG 激活对话框形式的进展指示器，信息指示器(若没有被禁用)也会被激活。
    STATUSOLD 激活旧式的、不带[取消]按钮的进展指示器，信息指示器(若没有被禁用)也会被激活。

    例如：若要指定安装主界面的背景是一个最大化的窗口，则有下列代码：

      Enable ( BACKGROUND );
      Enable ( DEFWINDOWMODE );

    需要说明的是，在与Enable相对的Disable函数中，形参nConstant的取值和Enable函数中的nConstant是基本相同的。

    4.1.3 界面设计的常用原则

    界面设计中常遵循的原则有：对比原则、协调原则、平衡原则、乐趣原则等。运用这些原则可以加强界面的气氛、增加吸引力、突出重心、提高美感。其中，乐趣原则的重要性非同小可。所谓"乐趣"原则是指对用户的愉悦心情的考虑，通俗来说就是提供以用户为中心，提高其舒适感。为了遵循这个原则，用户需要在比例、强调、凝聚、扩散以及形态等多个方面加以考虑。

    1. 比例

    黄金分割点(0.618)，也称黄金比例，是界面设计中非常有效的一种方法。在设计物体的长度、宽度、高度及其型式和位置时，如果能参照黄金比例来处理，就能产生特有的稳定和美感。

    2. 强调

    在单一风格的界面中，加入适当的变化，就会产生强调的效果。强调可打破界面的单调感，使界面变得有朝气，例如，界面皆为文字编排，看起来索然无味，如果加上插图或照片，就如一颗石子丢进平静的水面，产生一波一波的涟漪。
无标题文档

    3. 凝聚与扩散

    人们的注意力总会特别集中到事物的中心部分，这就构成了视觉的凝聚。一般而言，凝聚型看似温柔，也是许多人所喜欢采用的方式，但容易流于平凡。离心型的布局，可以称为扩散型是具有现代感的编排型式。

    4. 形态的意象

    由于计算机屏幕的限制，一般的编排形式总是以四边形为标准形，其他各种形式都属于它的变形。四角皆成直角，给人以很规律、表情少的感觉，其他的变形则呈现出形形色色的感觉，譬如成为锐角的三角形有锐利、鲜明感，近于圆的形状有温和柔弱之感。相同的曲线也有不同的表情，例如用仪器画出来的圆，有硬质感，而徒手画出来的圆就有柔和的圆形曲线之美。

    5. 变化率

    在界面设计中，必须根据内容来决定标题的大小。标题和正文大小的比率就称为变化率。变化率越大，界面越活泼，变化率越小，界面格调越高。依照这种尺度来衡量，就很容易判断界面的效果。

    6. 规律感

    具有共同印象的形状反复排列时，就会产生规律感。不一定要用同一形状的东西，只要具有强烈印象就可以了。三四次的出现就能产生轻的规律感。有时候只反复使用两次特定的形状，也会产生规律感。规律感在设计一个软件时，可以使用户很快地熟悉系统，掌握操作方法。这一点，相信用户从微软的Windows软件中可以得到启发。

    7. 导向

    依眼睛所视或物体所指的方向，使界面产生一种引导路线，称为导向。设计者在设计界面时，常利用导向使整体画面更引人注目。一般来说，用户的眼光会不知不觉地锁定在移动的物体上，即使物体是在屏幕的角落，画面的移动都会让目光跟它移动的方向。了解了这一点，设计者就可以有意识地将用户的目光导向到希望用户注意的信息对象上。

    8. 空白区

    速度很快的说话方式适合体育新闻的播报，但不适合做典礼的节目主持人，原因是每一句话当中的空白量太少。界面设计的空白量问题也很重要，无论排版的平衡感有多好，读者一看界面的空白量就已给它打好分数了。所以，千万不要在一个界面上放置太多的信息对象，以至界面拥挤不堪。没有空白区，就没有界面的美。空白的多寡对界面的印象有决定性的影响。空白部分加多，会使格调提高并且稳定界面；空白较少，会使人产生活泼的感觉。设计信息量很丰富的杂块界面时，用较多的空白显然就不适合。


4.2 应用多媒体

     为了不使安装主界面过于单调，许多优秀软件(如Windows 98、Visual Studio 98等)的安装界面中还用广告牌的形式来发布相关信息，让用户更多地了解自己的产品；甚至还播放背景音乐或影像来消除用户在安装过程中长时间等待的不满情绪。这种以多媒体的形式来丰富安装主界面的特色，是InstallShield 5.5中的显著特点。

    4.2.1 字体

    字体是文字显示的外观形式，它包括了文字的字样、风格和尺寸等多方面的属性。适当地选用不同的字体，可以大大地丰富文字的外在表现力。例如，把文字中某些重要的字句用较粗的字体显示，能够体现出突出、强调的意图。

     字体的字样是字符书写和显示时表现出的特定模式，例如，对于汉字，通常有宋体、楷体、仿宋、黑体、隶书以及幼圆等多种字样。字体风格主要表现为字体的粗细和是否倾斜等特性。字体的尺寸是用来指定字符所占区域的大小，通常用字符高度来描述。字体尺寸可以取毫米或英寸作为单位，但为了直观起见，也常常采用一种称为"点"的单位，一点约折合为1/72英寸。对于汉字，还常用"号"数来表示字体尺寸，初号字最大，以下依次为小初、一号、小一、二号、小二??，如此类推，字号越大，字体尺寸越小。

     在InstallShield中，字体的应用范围非常有限，用户只能通过SetFont和SetTitle设定安装主界面中主标题的字体类型和大小。例如：
    SetFont ( FONT_TITLE , STYLE_BOLD|STYLE_ITALIC|STYLE_SHADOW , "华文新魏" );
    SetTitle ( "InstallShield汉化补丁" , 28 , WHITE);

    // 设定主标题的内容为"InstallShield汉化补丁"、大小为28点、颜色为白色

    其中SetFont是用来设置要显示字符串的文本字体，它的原型如下：

    SetFont (nItemID, nFontStyle, szFontName);

    参数nItemID用来指定要设置字体的项目，目前只能为FONT_TITLE，它表示主标题。nFontStyle用来指定字体的风格，它可以是下列的值之一：

    STYLE_NORMAL 正常字体风格，它不能与其他值进行"|"组合
    STYLE_BOLD 粗体
    STYLE_ITALIC 斜体
    STYLE_SHADOW 文字带阴影
    STYLE_UNDERLINE 文字带下划线

    szFontName用来指定字体的字样名。若指定的字样名没有找到，则使用缺省的"Arial"字样。当函数返回0时表示字体设置成功，否则字体设置失败。

    需要说明的是，在安装Word 2000后，Windows可使用的字体类型增加了很多，尤其是中文字体。但是，用户最好能使用基本的中文字体，如宋体、楷体、黑体以及仿宋体，以适应不同的安装环境。
无标题文档

    4.2.2 颜色

    Windows提供了一种"设备无关"的颜色接口，使得应用程序只需提供"绝对的"颜色值，系统就会将该代码以合适的颜色或颜色组合映射到计算机的显示器上。同样，也可以在InstallShield安装脚本程序中使用RGB函数来指定某一个颜色值：

    NUMBER nColor;
    ...

     nColor = RGB(128, 0, 255);
其中，RGB函数用来创建一个用于SetColor和SetTitle函数中的颜色值。它的原型如下：

      RGB (constRed, constGreen, constBlue);

    参数constRed、constGreen和constBlue用来指定RGB中红色、绿色和蓝色分量值，取值范围都是为0-255，函数成功调用后返回相应的颜色值。

    但是，这一颜色值在系统不同的颜色模式下，其实际的颜色会有所不同。例如，在256色颜色模式下，由于实际显示的颜色不会超过256色，因此系统会用最接近的颜色和用户指定的颜色相匹配，从而造成了颜色的失真现象。为了有效地解决这一问题，下面的建议值得参考：

    (1) 尽量使用Windows的标准色。例如对于256色模式，有下列20种标准色：
    RGB( 0, 0, 0) 黑色
    RGB( 0, 0, 255) 蓝色
    RGB( 0, 255, 0) 绿色
    RGB( 0, 255, 255) 青色
    RGB( 255, 0, 0) 红色
    RGB( 255, 0, 255) 品红色
    RGB( 255, 255, 0) 黄色
    RGB( 255, 255, 255) 白色
    RGB( 0, 0, 128) 暗蓝色
    RGB( 0, 128, 0) 暗绿色
    RGB( 0, 128, 128) 暗青色
    RGB( 128, 0, 0) 暗红色
    RGB( 128, 0, 128) 暗紫色
    RGB( 128, 128, 0) 橄榄色
    RGB( 128, 128, 128) 暗灰色
    RGB( 192, 192, 192) 亮灰色
    RGB( 192, 220, 192) 淡绿色
    RGB( 166, 202, 240) 天蓝色
    RGB( 255, 251, 240) 乳白色
    RGB( 160, 160, 164) 中灰色

    (2) 尽量使用纯色，尤其是用于安装主界面的背景颜色。这些纯色是InstallShield中预先定义的一些值，并可从SetColor函数得到这些纯色的应用：

      SetColor (nObject, nColor);

    该函数用来为指定的对象设定颜色。其中nObject用来指定要设定颜色的对象，它只能是下列值之一：
    BACKGROUND 表示安装主界面的背景
    STATUSBAR 表示进展指示器中的进展条

    参数nColor用来指定要设定的颜色。它除了可以是用RGB定义的颜色外，还可使用下面的颜色：

    BK_BLUE 渐变的蓝背景色
    BK_GREEN 渐变的绿背景色
    BK_MAGENTA 渐变的紫背景色
    BK_RED 渐变的红背景色
    BK_YELLOW 渐变的黄背景色
    BK_SOLIDBLUE 用于背景的纯蓝
    BK_SOLIDGREEN 用于背景的纯绿
    BK_SOLIDMAGENTA 用于背景的纯紫
    BK_SOLIDRED 用于背景的纯红
    BK_SOLIDYELLOW 用于背景的纯黄
    BK_SMOOTH 与RGB定义的颜色进行"|"组合，用于产生渐变的背景色。
    GREEN 产生绿色的进展条
    RED 产生红色的进展条
    BLUE 产生蓝色的进展条
    MAGENTA 产生紫色的进展条
    YELLOW 产生黄色的进展条

    (3) 如果安装界面一定要使用256色以上的颜色模式，那么可通过GetSystemInfo (COLORS, 0, "")获得当前系统使用的颜色数来确定是否继续安装。
无标题文档

    4.2.3 位图

    InstallShield 5.5支持的图像格式文件有两种类型。一种是BMP，它是一些和显示像素相对应的位阵列；另一种是WMF，它是一种标准型的图元文件，也是一种设备无关的图像文件，它将图像以图形对象(线、圆弧、多边形)而不是用像素的形式来存储的。

    在InstallShield 5.5中，位图不仅可以作为广告牌，而且可以作为组件项前面的图标以及对话框左边的位图标签。

    1. 组件项前面的图标

    下面的过程将使一个位图成为组件项前面的图标：

    (1) 用图像编辑软件创建一个位图，如图4.3所示。

  该位图实际上是由多个连续的16 x 16的图像组成的，其背景色一定要设置成紫色(RGB值为255,0,255)。

    (2) 将该位图保存在View.bmp文件中。
    (3) 启动InstallShield 5.5，用Project Wizard创建一个安装项目。
    (4) 切换到项目工作区窗口的"Setup Files"页面，选定"Language Independent\Windows 95/98 & NT 3.51/4.0"项。
    (5) 在右边的属性窗口中，右击鼠标，从弹出的快捷菜单中选择"Insert Files..."命令。
    (6) 通过弹出的对话框将刚才保存的View.bmp文件调入。
    (7) 将项目工作区窗口切换到Media页面。单击Media Build Wizard项，创建新的媒介。
    (8) 编译并运行。在"Select Type"对话框中选择"Custom"安装类型，按[Next>]按钮弹出如图4.4所示的"Select Components"对话框界面。

需要说明的是，组件图标可应用于ComponentDialog、SdComponentDialog、SdComponentDialog2、SdComponentDialogAdv和SdComponentMult对话框中。

    2. 一般位图的显示

    当然，若用户想要将位图显示在屏幕的任何位置，则可使用PlaceBitmap函数，其原型如下：

      PlaceBitmap (szName, nID_BITMAP, nDx, nDy, nDrawOp);

    该函数是将一张图像插入到安装界面中。这个图像来自于由szName指定的BMP文件、WMF文件或DLL文件。若szName指定的文件是DLL，则还需要用nID_BITMAP指定相应的位图ID号。参数nDx和nDy用来指定与安装界面窗口边框的水平和垂直偏移量，参数nDrawOp用来表示以下的操作方式：

    BITMAPICON 表示位图有透明部分。用户可以将此方式与其他操作进行"|"组合，但除TILED、FULLSCREEN和FULLSCREENSIZE外。当szName指定一个图元文件或是一个24位位图，则BITMAPICON不起作用。

    TILED 平铺位图
    FULLSCREENSIZE 将图像放大至整个安装窗口
    CENTERED 居中位图
    LOWER_LEFT 将位图放置在窗口的左下角
    LOWER_RIGHT 将位图放置在窗口的右下角
    UPPER_LEFT 将位图放置在窗口的左上角
    UPPER_RIGHT 将位图放置在窗口的右上角
    REMOVE 清除以前放置在窗口中的图像
无标题文档

    需要说明的是：

    (1) 应明确图像文件路径的影响。用于显示在安装界面的图像文件的路径，用户可以在上述函数的szName中指定，例如D盘中存在MyImage.bmp文件，则szName应为"D:\\MyImage.bmp"。虽然此时运行一般不会有问题，但将安装程序发布后问题就来了，因为用户的机器上不会有MyImage.bmp文件。为此我们需要将要使用的所有文件(包括图像文件)添加在InstallShield集成开发环境的"Setup Files"页面中，此时szName应为：

      szName = SUPPORTDIR ^ "MyImage.bmp";

    (2) szName中也可使用分号";"，用来设置相应的透明色。例如：
      szName = SUPPORTDIR ^ "TransImage.bmp;255, 128, 64";
其中，255, 128, 64分别表示RGB颜色中的红、绿、蓝颜色分量值，用来指定这种颜色是透明的。
    (3) 若图像保存在DLL文件中时，用户不但用szName指定DLL文件全名，而且还要在nID_BITMAP指定相应的位图ID号，这里的ID号是DLL中已定义的。例如，若创建的DLL文件是MyImage.DLL，文件中包含一个位图(ID号为1000)，当该文件调入安装项目后，则有下列代码：
    PlaceBitmap ( SUPPORTDIR ^"MyImage.Dll" , 1000 , 0, 0, CENTERED );
图像保存在DLL文件的方法有很多，例如用户可以用Visual C++ 6.0修改_IsRes.DLL或利用InstallShield专业版提供的定制对话框的项目来构造编译。但不管是怎样的方法，其最后的DLL文件名一定不能是_IsRes.DLL或_IsUser.DLL，否则相应的图像不会显示。
    (4) 当在PlaceBitmap函数中使用REMOVE操作方式之前，可以用nID_BITMAP指定一个位图ID号，这样就不需要再指定相应的图像文件名。例如：
    PlaceBitmap ( "D:\\CIBA2Ks.bmp" , 10 , 0, 0, CENTERED );

    elay(1);

    PlaceBitmap ( "" , 10 , 0, 0, REMOVE );
    (5) 为了使显示的图像更具特色，用户可以在PlaceBitmap函数调用前，用SetDisplayEffect函数设置图像显示效果，它的原型如下：
    SetDisplayEffect (nEffect);

    其中，nEffect可以下列值之一(但这些效果对PlaceBitmap操作方式BITMAPICON、BITMAPFULLSCREEN和BITMAPTILE不起作用)：

    EFF_FADE 淡入淡出效果
    EFF_REVEAL 中心展开效果
    EFF_HORZREVEAL 水平展开效果
    EFF_HORZSTRIPE 水平条纹显示效果
    EFF_VERTSTRIPE 垂直条纹显示效果
    EFF_BOXSTRIPE 盒式条纹显示效果
    EFF_NONE 取消任何效果

    3. 对话框的位图标签

     更改对话框的位图标签有两种方法，一是使用Visual C++修改_IsRes.Dll中的对话框资源，另一是使用DialogSetInfo函数。由于第一种方法在本章的最后一节中还将具体介绍，故这里仅讨论第二种方法。

     函数DialogSetInfo是用来更改一些Sd对话框中的指定的元素，例如对话框中的位图标签、复选框的风格、硬盘空间大小的单位等。该函数往往应用于SdComponentDialogAdv、SdComponentDialog2和 SdComponentMult对话框函数中。它的原型如下：
    DialogSetInfo (nInfoType, szInfoString, nParameter);

    其中参数nInfoType用来指定要更改的元素，它可以是下列值之一：

    DLG_INFO_USEDECIMAL 显示的内存大小使用十进制
    DLG_INFO_KUNITS 空间大小的单位使用KB
    DLG_INFO_ALTIMAGE 更改对话框中的位图标签
    DLG_INFO_CHECKSELECTION 更改复选框的风格

    参数szInfoString用来当nInfoType为DLG_INFO_ALTIMAGE时指定的位图文件名，该位图的大小最好为120 x 260像素，否则超出该范围的位图被裁剪，小于该范围的位图被缺省的背景色(RGB(0,128,028 ))填充。

    参数nParameter用来当nInfoType为DLG_INFO_CHECKSELECTION时，指定下列的值：

    CHECKBOX Windows 3.1的复选框
    CHECKBOX95 Windows 95的复选框
    CHECKLINE 选中项
    CHECKMARK 选中标记
无标题文档

    若当nInfoType为DLG_INFO_ALTIMAGE时，则该参数可以是：

      -1 将位图填入_isres.dll相关的资源中
      TRUE szInfoString必须指定显示的位图

    若当nInfoType为DLG_INFO_KUNITS或DLG_INFO_USEDECIMAL时，则该参数可以是：

      TRUE 表示nInfoType设置有效
      FALSE 表示nInfoType设置无效，使用缺省的风格

    例如，下面的过程将使所有的对话框的位图标签改为类似如图4.5所示的外观：

    (1) 用图像编辑软件创建一个大小为120 x 260的位图。将该位图保存在Bmp.bmp文件(也可是其他文件名)中。
    (2) 启动InstallShield 5.5，用Project Wizard创建一个安装项目。
    (3) 切换到项目工作区窗口的"Setup Files"页面，选定"Language Independent\Windows 95/98 & NT 3.51/4.0"项。
    (4) 在右边的属性窗口中，右击鼠标，从弹出的快捷菜单中选择"Insert Files..."命令。
    (5) 通过弹出的对话框将刚才保存的Bmp.bmp文件调入。
    (6) 打开Setup.rul文件，在主程序体的最前面添上下列语句：
        DialogSetInfo ( DLG_INFO_ALTIMAGE , SUPPORTDIR ^"Bmp.bmp" , TRUE );
    (7) 将项目工作区窗口切换到Media页面。单击Media Build Wizard项，创建新的媒介。
    (8) 编译并运行。

    4.2.4 声音和影像

    为了丰富产品展示的表现力，提高安装界面的专业水准，InstallShield还提供了PlayMMedia函数来播放MIDI、WAVE声音和AVI影像，其函数原型如下：

    PlayMMedia (nType, szFileName, nOperation, nReserved);

    其中，nType表示播放的媒体类型，它可以是MMEDIA_WAVE(MIDI音乐)、MMEDIA_MIDI(WAVE音乐)和MMEDIA_AVI(AVI影像)；szFileName指定要播放的声音或影像文件全名，若媒体文件调入安装项目后，则可使用系统变量SUPPORTDIR来指明相应的路径；nReserved目前保留，只能为0；nOperation表示媒体播放的方式，它可以是下列值：

    MMEDIA_PLAYSYNCH 同步播放。它意味着只有当媒体播放完毕后，才执行下一步操作。
    MMEDIA_PLAYASYNCH 异步播放。它意味着媒体是在后台播放的，但为了确保媒体播放结束，还必须指定MMEDIA_STOP操作。
    MMEDIA_PLAYCONTINUOUS 循环播放。它只能和MMEDIA_PLAYASYNCH进行"|"组合。
    MMEDIA_STOP 停止播放。

    显然，若要在安装进行过程中进行媒体的后台连续播放，则可有下列代码：

PlayMMedia ( MMEDIA_WAVE , "C:\\Windows\\Media\\The Microsoft Sound.wav" ,
            MMEDIA_PLAYASYNCH|MMEDIA_PLAYCONTINUOUS , 0 );

      ...
PlayMMedia ( MMEDIA_WAVE , "C:\\Windows\\Media\\The Microsoft Sound.wav" ,
            MMEDIA_STOP , 0 );


4.3 界面的效果设计

     通过上面的介绍，相信用户对安装界面的设计有一定的了解，这里再对一些常用的界面效果的设计方法和技巧作进一步的讨论。

    4.3.1 设置主界面的背景效果

    安装主界面的背景可以有三种效果：一般背景色、渐变背景色以及位图背景。

    1. 背景色

    在设置安装主界面的背景色之前首先要调用Enable函数显示安装界面的背景，然后调用SetColor函数设置相应的背景色，若在该函数中指定BK_SMOOTH特性时，则产生垂直的颜色渐变效果，其中SetColor指定的颜色是界面最上边的颜色，而最下面的颜色是黑色RGB(0, 0, 0)。例如：

    program
      Enable ( BACKGROUND );
      Enable ( FULLWINDOWMODE );
      SetColor ( BACKGROUND , BK_RED|BK_SMOOTH );
      Delay( 3 );
    endprogram

    其结果如图4.6所示。需要说明的是，若不指定任何背景色，则安装界面使用缺省的深青色RGB (0, 128, 128)。
无标题文档

    2. 位图背景

    如果想要用位图作为安装界面的背景，用户首先要创建一个BMP或WMF图片，然后调用PlaceBitmap函数将图片显示在安装界面上。但为了图片能覆盖整个背景，用户还必须在该函数中指定FULLSCREENSIZE、FULLSCREEN(若图片大小超过背景)或TILED特性。例如下面的代码：

    program
      Enable ( BACKGROUND );
      Enable ( FULLWINDOWMODE );
      PlaceBitmap ( "C:\\Windows\\安装程序.bmp" , 0 , 0 , 0 , FULLSCREENSIZE );
      Delay( 3 );
    endprogram

其结果如图4.7所示。

    4.3.2 使用进展指示器

    在说明进展指示器的使用方法之前，我们先来看看Project Wizard产生的一段代码：

    function MoveFileData()
      NUMBER nResult, nDisk;
      begin
       nDisk = 1;
       SetStatusWindow( 0, "" );
       Disable( DIALOGCACHE );
       Enable( STATUS );
       StatusUpdate( ON, 100 );
       nResult = ComponentMoveData( MEDIA, nDisk, 0 );
      HandleMoveDataError( nResult );
       Disable( STATUS );
      return nResult;
  end;

    显然，上述代码中的SetStatusWindow、Enable以及StatusUpdate函数与进展指示器密切相关。其中，Enable用于激活和显示不同类型的进展指示器，相关的参数值可以是STATUSDLG(标准类型的进展对话框)、STATUS(只有[取消]按钮的进展条)、STATUSOLD(没有[取消]按钮的进展条)以及INDIVFILESTATUS(在进展指示器中显示被传送的文件全名)；而SetStatusWindow和StatusUpdate分别用于设置进展指示器的相关信息及确定是否跟踪文件复制进程，它们的原型如下：

      SetStatusWindow (nPercent, szString);

    该函数是为进展指示器指定开始或当前的百分比以及在进展条上方显示的当前文本信息。参数nPercent 指定进展指示器的百分比大小(从0到100)。若不改变当前的百分比，则将此参数设定为-1。参数szString指定在进展条上方显示的当前文本信息。若指定组件的"Status Text"属性后，则当调用ComponentMoveData函数时，这个信息内容还会自动被更改，

      StatusUpdate (bLink, nFinalPercent);

    该函数使用或禁止跟踪文件传送进程。一旦bLink设为ON，当文件传送时，进展条自动显示出相应的百分比，直到nFinalPercent设定的最后百分比值为止。参数bLink用来指定是使用(ON)或禁止(OFF)跟踪文件复制进程。nFinalPercent用来指定跟踪文件复制进程后要达到的最终百分比。若设定的最终百分比比当前的值要小，则进展条不会被更新。
无标题文档

    需要说明的是：

    (1) StatusUpdate函数只对文件传送函数CopyFile、XCopyFile以及ComponentMoveData有效。
    (2) 用户可以用SetStatusWindow函数来代替StatusUpdate进行文件传送进程的人工跟踪，例如下面的代码：

    // 假设安装项目中只有一个名为"Component"的组件，该组件下包含一个文件组"File Group"
    NUMBER nTotalSize, nFileSize, nPercent;
    NUMBER nFileResult, nvResult;
    LIST listFile;
    STRING svFile, svStatus, svResult;
    program
      nPercent = 0;
      Enable ( BACKGROUND );
      Enable ( FULLWINDOWMODE );
      PlaceWindow ( BILLBOARD , 0 , 0 , CENTERED );
      Enable( STATUSDLG );
      Enable( INDVFILESTATUS );
      PlaceWindow ( FEEDBACK , 50 , 50 , LOWER_LEFT );
      PlaceWindow ( STATUSDLG , 50 , 50 , LOWER_RIGHT );
      SetStatusWindow ( nPercent , "准备复制文件..." );
      VarSave (SRCTARGETDIR);
      TARGETDIR = "D:\\Temp";

      // 获取所有组件中的文件字节总数
      nTotalSize = ComponentTotalSize ( MEDIA , "" , TRUE , TRUE );
      listFile = ListCreate( STRINGLIST );
      // 获取与"Component"组件相联的文件组中的所有文件
      ComponentFileEnum ( MEDIA , "Component" , "File Group\\*.*" , listFile , INCLUDE_SUBDIR );
      // 获取"Component"组件的状态文本信息
      ComponentGetData ( MEDIA , "Component" , COMPONENT_FIELD_STATUS , nvResult , svStatus );
      nFileResult = ListGetFirstString( listFile, svFile );
      while ( nFileResult != END_OF_LIST )
        // 获取每个文件的字节大小
        ComponentFileInfo ( MEDIA , "Component" , svFile ,COMPONENT_INFO_ORIGSIZE , nFileSize , 　　　　　　　　　　　　　　svResult );
        // 计算当前的百分比
        nPercent = nPercent + 100 * nFileSize / nTotalSize;
        // 设置进展提示器的相关信息
        SetStatusWindow ( nPercent , svStatus );
        nFileResult = ListGetNextString( listFile, svFile );
      endwhile;
      ListDestroy( listFile );
      VarRestore (SRCTARGETDIR);
      SetStatusWindow ( 100 , " 文件复制完毕..." );
      Delay ( 3 );
    endprogram

    如果用户执意人工处理组件的相关数据，虽然不失为一种方法，但总没有函数ComponentMoveData来得简单，且该函数是唯一能自动更新信息批示器的函数。

    (3) 另外，用户还可使用SetDialogTitle和SetColor函数来改变进展对话框的标题和进展条的颜色。例如：

    Enable( STATUSDLG );
    SetDialogTitle ( DLG_STATUS , "复制文件" );
    SetColor ( STATUSBAR , BK_GREEN );

    需要说明的是，在调用Enable(STATUSDLG)前，使用Disable(FEEDBACK_FULL)函数可以使信息指示器隐藏。
无标题文档

    4.3.3 展示广告牌

    广告牌是在文件传送过程中显示的一系列图片(图片的格式可以是位图BMP或图元WMF)，它能起到沟通、广告、训练、促进、激发、娱乐以及其他引入注目的作用。

    在InstallShield 5.5中，广告牌是在调用ComponentMoveData函数进行文件传送时自动展示的，用户既不需要在文件传送过程中激活广告牌，也不需要指定广告牌显示时间的长短以及广告牌与广告牌的时间间隔。用户需要做的仅仅是构造用于广告牌的一系列图片，然后将这一系列图片正确命名，最后将其放入项目工作区窗口的"Setup Files"页面下的相应的语系和操作平台中。

    显然，正确命名用于广告牌的图片文件名是保证InstallShield自动展示广告牌的关键。InstallShield对BMP和WMF格式的图片给出了不尽相同的命令规则。

    对于BMP格式的广告牌而言，InstallShield允许用户使用两套图片，其中一套用于低屏幕分辨率(640 x 480)，此时的图片必须以Bbrdln.bmp命名，其中n是1到99的连续数字，另一套适用于任何屏幕分辨率，这时的图片必须以Bbrdn.bmp命名，其中n是1到99的连续数字。用户可以只指定Bbrdn.bmp系统的广告牌，但当这两套图片同时放入"Setup Files"页面中时，安装程序会自动根据用户机器的屏幕分辨率自动拣选。

    对于WMF格式的广告牌而言，InstallShield不支持低分辨率的广告牌，也就是说，用户只能使用一套适用于所有分辨率的广告牌，这时的图片必须以Bbrdn.wmf命名，其中n是1到99的连续数字。尽管如此，用户还可在文件传送前使用SizeWindow函数调整WMF图片显示的大小，以满足低分辨率屏幕的需求。

    使用广告牌时，需要注意：

    (1) 在同一个安装项目中，不能同时存在BMP和WMF格式的广告牌。

    (2) 尽管可以指定多达99张广告牌图片，但是用户也应该充分考虑到文件传送所需要的时间，因为文件传送完后广告牌不再自动显示。也就是说，用户准备了25张图片，但当文件传送过程只持续了20秒时，则可能只有前面的10张图片被显示。

    (3) 在文件传送过程中，由于InstallShield不会自动关闭MWF广告牌的显示，这时的广告牌的隐藏必须通过后一张的覆盖才能达到，因此在制作MWF广告牌时，应将所有图片设置成一样的大小。

    4.3.4 改变窗口状态

    我们有时需要对某个窗口的大小和位置进行改变，例如信息指示器和进展提示器的缺省位置不能令人满意，尤其是在屏幕分辨率为800 x 600以上的情况下。这时，就要使用PlaceWindow和SizeWindow函数，它们的原型如下：

      PlaceWindow (nObject, nDx, nDy, nCorner);

    该函数用来改变包括广告牌在内的窗口的位置。其中参数nObject用来指定要改变位置的窗口对象，它可以是下列值之一：

    ASKOPTIONS AskOptions对话框
    ASKPATH AskPath对话框
    ASKTEXT AskText对话框
    BACKGROUND 背景窗口
    BILLBOARD 广告牌
    ENTERDISK EnterDisk对话框
    FEEDBACK 信息指示器
    MMEDIA_AVI 播放下一个AVI文件的窗口
    STATUS 进展指示器
    TATUSDLG 对话框风格的进展指示器

    参数nDx表示与nCorner指定边框的水平距离(以像素为单位)。例如，若nObject被指定为FEEDBACK，则可使用CENTERED表示水平居中。所有窗口对象的缺省水平位置都为居中。nDy表示与nCorner指定边框的垂直距离(以像素为单位)。例如，若nObject被指定为FEEDBACK，则可使用CENTERED表示水平居中。所有窗口对象的缺省垂直位置都为居中。nCorner用来指定被nDx 和nDy参考的边角，它可以是下列值之一：

    LOWER_LEFT 表示左下角
    LOWER_RIGHT 表示右下角
    UPPER_LEFT 表示左上角
    UPPER_RIGHT 表示右上角
    CENTERED 居中
    SizeWindow (nObject, nDx, nDy);

    该函数用来改变指定窗口的大小。参数nObject用来指定要改变大小的窗口对象，它可以是下列值之一：
    BACKGROUND 背景窗口
    METAFILE WMF格式的广告牌
    MMEDIA_AVI 播放下一个AVI文件的窗口
    参数nDx和nDy用来指定窗口的水平和垂直像素大小。

    需要说明的是：

    (1) 由于上述两个函数都跟屏幕的分辨率有关，因此在使用这两个函数前最好调用GetExtents函数测试一下屏幕的分辨率。其函数原型如下：

    GetExtents (nvDx, nvDy);

    (2) 在改变窗口位置时，居中可以使屏幕分辨率的影响减小到最低程度，但在设置时，要注意PlaceWindow函数的最后三个参数都要使用CENTERED，例如：

    PlaceWindow ( FEEDBACK , CENTERED , CENTERED , CENTERED );
    PlaceWindow ( STATUSDLG , CENTERED , CENTERED , CENTERED);

    (3) PlaceWindow函数中的nDx、nDy两个参数和nCorner紧密相关，且nCorner表示背景窗口的边角。例如，若将信息指示器置于背景窗口的左下角，且距左下角的水平距离为50像素，垂直距离为20像素，则有下列代码：

    PlaceWindow ( FEEDBACK , 50 , 20 , LOWER_LEFT );

    (4) 对于有些窗口，需要用Enable函数来激活。例如，虽然Enable和PlaceWindow函数出现的先后次序对结果没有影响，但最好将Enable函数放在前面。
无标题文档

    4.3.5 启动画面

    许多大型应用程序的安装程序都有启动画面，如VC++等软件。通过启动画面，除了可以显示安装的应用程序名称和版权等提示信息外，还丰富应用程序安装画面，给安装程序增添了许多动态特性和专业规范。

    在InstallShield中，安装程序的启动画面是自动显示的。用户只需将名为Setup.bmp位图文件放置到安装项目"Setup Files"页面中的"Splash Screen\Language Independent"目录下，然后创建新的媒介即可。此时安装程序运行后，用户指定的启动画面将自动居中显示。但当用户使用Project Wizard创建一个安装项目时，InstallShield将使用缺省的启动画面，如图4.8所示。

    需要说明的是:

     (1) InstallShield允许用户构造的启动画面的颜色数最多为256色，但同时也带来另外一个问题。若安装时系统的颜色数为16色，则256色的启动画面将无法显示。为了能有效解决这一问题，InstallShield允许还允许用户构造另一个16色的安装启动画面，并以Setup16.bmp命名，InstallShield在安装时将自动根据系统的颜色数进行拣选。当然，若用户不想这么做，那么只要构造一个名为Setup.bmp，颜色不超过16的安装启动画面就可以了。

    (2) 在InstallShield专业版中，若在发布给用户的安装初始化文件Setup.ini中的[Startup]下指定AppName=My Application值，则在开始的安装进程消息对话框中显示出用户的产品名"My Application"。


4.4 对话框界面的定制

    由于对话框是安装界面中最重要的组成部分，因而InstallShield提供非常多的对话框函数来满足了用户制作安装程序的需要。但这些对话框比较通用，没有个性，且对话框中的字体、文本语言对普通用户来说，并不尽人意，因为我们总希望对话框上的文字是"简体中文，宋体，9号"。好在InstallShield的专业版中提供了相应的定制接口，这使得我们创建具有个性化界面的对话框成为可能。

    4.4.1 使用对话框定制模板

    InstallShield 5.5正确安装后，会在"\Examples\Custom Dialog\VC++ 4 Project"文件夹中复制一些用于对话框定制的文件，它们主要有resource.h(资源头文件)、Sdrc.h(Sd对话框资源头文件)、_Isuser.RC(资源文件)和_Isuser.mdp(项目文件)，这些文件构成了用Visual C++定制对话框的模板。当用Visual C++对对话框资源更改后，我们就可以在Visual C++进行编译，产生_Isuser.dll；再将_Isuser.dll调入InstallShield集成开发环境Setup Files页面中的指定的language(s)和operating system(s)项目中，就可以用脚本语言进行编程了。

    具体步骤如下：

    (1) 新建一个文件夹(如MyDialog)，将"\Custom Dialog\VC++ 4 Project"内容复制到该文件夹中。
    (2) 打开Visual C++ 6.0，将MyDialog中的项目文件_Isuser.mdp调入。注意，_Isuser.mdp是低版本的项目文件，Visual C++ 6.0会将其自动转换。
    (3) 将项目工作区窗口切换到Resource页面，展开Dialog资源，双击Dialog项目下的DLG_TEMPLATE，结果如图4.9所示。
无标题文档

    (4) 利用Visual C++的对话框编辑器作如下修改，结果如图4.10所示：

    将对话框的标题改为"对话框定制"，字体设置为"宋体，9号"，ID号值为13029；并作如下修改：

将按钮[Back]的标题改为"< 上一步(&B)"，将按钮[Next]的标题改为"下一步(&X) >"，将按钮[Cancel]的标题改为"取消"；

    向对话框添加"静态文本"控件，标题为"拨号方式："；
    向对话框添加两个单选按钮，标题分别为"音频"和"脉冲"，ID号值分别为110，100；
    向对话框添加"静态文本"控件，标题为"通信串行口："；
    向对话框添加一个列表框，ID号值设为330。

    需要说明的是，用Visual C++向对话框添加控件时，它会自动给控件定义一个ID标识符。若在其属性对话框中将此ID标识符后面添上"=XXX"，就能将该标识符赋予相应的数值，例如图4.11是将上述添加的列表框的ID号值设为330。

    而且，为了能在对话框中显示InstallShield内部定义的位图，用户必须在对话框中添加一个静态文本控件，并使其标题内容为"@10550,10551;1;1;0,128,128;0,128,128"。另外，对话框的ID值一般应大于13000(至少应大于12033)，这是因为InstallShield定义的对话框ID值最大为12033。而控件的ID值虽没有什么限制，不过若查看一下Sdrc.h文件，也许会给您带来一点启发和帮助。

    (6) 编译，结果在MyDialog\Release文件夹中产生_Isuser.dll。
    (7) 关闭Visual C++，打开InstallShield 5.5。
    (8) 创建一个Blank Setup类型的安装项目(设其缺省的项目名为Blank Setup-1)。
    (9) 将项目工作区窗口切换到Setup Files页面，将刚才编译产生的_Isuser.dll调入到Language Independent项下的Operating System Independent中。
    (10) 至此，对话框的模板创建并调入完毕。
无标题文档

    4.4.2 定制对话框的脚本程序过程

    一旦对话框模板创建完毕，用户就可以在脚本安装程序中进行测试。我们按下列步骤进行：

    (1) 将InstallShield项目工作区窗口切换到Scripts页面。选定最上一层的"’Blank Setup-1’ Script Files"项，右击鼠标，从弹出的快捷菜单中选择"Insert Files..."命令，将MyDialog文件夹中的resource.h调入。
    (2) 单击"resource.h"，在InstallShield右边的文档窗口中出现该文件内容。将resource.h文件的内容改写：
    #define DLG_TEMPLATE 13029 // 定义的对话框ID，与创建的对话框模板ID相同
    #define ID_PULSE 100 // 定义的控件ID，与"脉冲"单选按钮ID相同
    #define ID_TONE 110 // 定义的控件ID，与"音频"单选按钮ID相同
    #define ID_COMPORT 330 // 定义的控件ID，与列表框ID相同
    #ifdef APSTUDIO_INVOKED
    #ifndef APSTUDIO_READONLY_SYMBOLS
    #define _APS_NO_MFC 1
    #define _APS_NEXT_RESOURCE_VALUE 101
    #define _APS_NEXT_COMMAND_VALUE 40001
    #define _APS_NEXT_CONTROL_VALUE 1000
    #define _APS_NEXT_SYMED_VALUE 101
    #endif
    #endif

    (3) 单击"Setup.rul"项，打开Setup.rul文件，添加下列内容：

      #include "Resource.h"

      // 定义[Back]、[Next]和[Cancel]按钮的标识值，这些值应与对话框模板中相应的标识值相同
      #define SD_PBUT_BACK 12
      #define SD_PBUT_CONTINUE 1
      #define SD_PBUT_EXITSETUP 9
      BOOL bDone;
      STRING svComPort;
      NUMBER nCmdValue, nPulseState, nToneState, nDial9State;
      LIST listID;
      program

      // 创建显示在列表框中的链表
      listID = ListCreate(STRINGLIST);
      ListAddString(listID, "COMM1:", AFTER);
      ListAddString(listID, "COMM2:", AFTER);
      ListAddString(listID, "COMM3:", AFTER);
      ListAddString(listID, "COMM4:", AFTER);
      // 注册对话框
      EzDefineDialog( "MYCOMDIALOG", "", "", DLG_TEMPLATE);
      // 显示对话框
      bDone = FALSE;
      while (bDone=FALSE)
      nCmdValue = WaitOnDialog("MYCOMDIALOG");
      switch (nCmdValue)
      case DLG_INIT:
      CtrlSetState("MYCOMDIALOG", ID_TONE, BUTTON_CHECKED);
      CtrlSetList("MYCOMDIALOG", ID_COMPORT, listID);

      // 处理用户单击[Next]按钮的消息
      case SD_PBUT_CONTINUE:
      CtrlGetCurSel("MYCOMDIALOG", ID_COMPORT, svComPort);
      nPulseState = CtrlGetState("MYCOMDIALOG", ID_PULSE);
      nToneState = CtrlGetState("MYCOMDIALOG", ID_TONE);
      bDone = TRUE;
      // 处理用户单击[Cancel]按钮的消息
      case SD_PBUT_EXITSETUP:
      bDone = TRUE;
      // 处理用户关闭对话框的消息
      case DLG_CLOSE:
          bDone = TRUE;
          // 处理用户选中单选按钮的消息
      case ID_PULSE:
         nPulseState = CtrlGetState("MYCOMDIALOG", ID_PULSE);
          if (nPulseState = BUTTON_CHECKED) then
            CtrlSetState("MYCOMDIALOG", ID_TONE, BUTTON_UNCHECKED);
            CtrlSetState("MYCOMDIALOG", ID_PULSE, BUTTON_CHECKED);
          else
            CtrlSetState("MYCOMDIALOG", ID_TONE, BUTTON_CHECKED);
            CtrlSetState("MYCOMDIALOG", ID_PULSE, BUTTON_UNCHECKED);
          endif;
      case ID_TONE:
          nToneState = CtrlGetState("MYCOMDIALOG", ID_TONE);
          if (nToneState = BUTTON_CHECKED) then
              CtrlSetState("MYCOMDIALOG", ID_PULSE, BUTTON_UNCHECKED);
              trlSetState("MYCOMDIALOG", ID_TONE, BUTTON_CHECKED);
          else
              CtrlSetState("MYCOMDIALOG", ID_TONE, BUTTON_UNCHECKED);
              CtrlSetState("MYCOMDIALOG", ID_PULSE, BUTTON_CHECKED);
          endif;
无标题文档

        // 处理对话框错误消息
        case DLG_ERR:
          MessageBox("内部的对话框错误！", SEVERE);
          bDone = TRUE;
        ndswitch;
      endwhile;
      // 结束对话框
      EndDialog("MYCOMDIALOG");
      // 将对话框和链表从内存中释放出来
      ReleaseDialog("MYCOMDIALOG");
      ListDestroy(listID);
    endprogram

    (4) 将项目工作区窗口切换到Media页面。单击Media Build Wizard项，创建新的媒介。
    (5) 编译并运行，结果如图4.12所示。

    从以上的脚本程序，我们可以总结出其一般的代码过程：

    (1) 定义对话框和控件的ID号，其值应与创建的对话框模板中相应的ID一致；
    (2) 使用EzDefineDialog函数来注册对话框，以便InstallShield能识别它。当然，我们也可以使用DefineDialog函数，但EzDefineDialog最简单，其原型如下：

      EzDefineDialog (szDialogName, szDLL, szID, nID);

    其中，参数szDialogName用来指定一个唯一的对话框名称；szDLL用来指定含有注册对话框的DLL文件名。若此参数为NULL，InstallShield将在_isuser.dll和_isres.dll寻找相应的对话框。szID用来指定对话框的标识符名称，若对话框的标识是一个数值，则此参数为NULL。nID用来指定对话框的标识符数值，若对话框的标识是一个字符串，则此参数为0。函数返回0时表示注册成功，返回DLG_ERR_ALREADY_EXISTS表示用户在脚本程序试图注册一个已经存在的对话框，返回DLG_ERR表示一个不确定的错误产生了。

    (3) 用循环语句不断显示对话框，并处理对话框和控件的消息，其中WaitOnDialog用于定制对话框的显示，其原型如下：

      WaitOnDialog (szDlgName);

    其中，参数szDialogName用来指定要显示的对话框名称。当对话框返回：

      dialog control ID 表示接收到WM_COMMAND消息的控件ID号
      DLG_CLOSE 表示对话框即将关闭
      DLG_ERR 表示产生错误
      DLG_INIT 表示对话框初始化消息

    (4) 用EndDialog函数结束对话框的显示。
    (5) 用ReleaseDialog函数释放对话框(以及链表)所占用的内存空间


4.5 界面的汉化

     前面的对话框定制方法让我们真正领略了InstallShield的可扩展的能力，但这种方法只能用于InstallShield的专业版，而对于其InstallShield的Visual C++6版本(若其他版本)的用户来说，则只好忍痛割爱了。然而对原有的对话框界面进行定制却是常常需要的，虽然，我们不一定非要改得面目全非，但至少需要将对话框中的英文变成中文。这就是我们常说的"汉化"。

     软件汉化的方法和工具有很多，但这不是本节所要讨论的内容。本节所要阐述的是如何用Visual C++使InstallShield所创建的安装程序的所有界面变成中文，并就汉化后的相关文件如何制作成补丁安装程序作一些探讨。

    4.5.1 概述

    软件中文化是一项由来已久的传统，由于自身的语言文化环境的影响，使得绝大数用户都倾向于使用中文软件；对于英文软件来说，即便是非常优秀，用户仍然感觉不愉快，原因是不习惯。为了使优秀的英文软件中文化，许多程序员及爱好者想出许多快速有效的、令人称奇的汉化方法。但我们不提倡在无授权协议的情况下直接对软件本身进行汉化，这是因为汉化的本身是对软件进行修改，有时不经意地就会违反了《知识产权保护条例》、《版权、著作权法》以及《专利法》等法规。

    4.5.2 用Visual C++汉化

    从定制对话框的过程得到启发，我们可以用Visual C++打开InstallShield 5.5的对话框资源进行修改，这一点，用户可以从InstallShield 5.5的帮助说明中得到证实。

    InstallShield的对话框和Sd对话框资源都是存放在_IsRes.Dll文件中(位于"\ Redistributable \Compressed Files\0009-English\Intel 32"文件夹)，只要利用Visual C++对话框编辑器对资源中的文本内容进行修改即可起到汉化的目的。但是，用Visual C++汉化后的DLL(及EXE)文件只能在Windows NT中才能存盘，而在Windows 95/98中却不能保存。因此，下面的步骤是在Windows NT中进行的：

    (1) 首先将原来的_IsRes.Dll复制到另外一个文件夹中，这里假定为D:\MyDLL。
    (2) 在Windows NT中，启动Visual C++ 6.0，选择"File"菜单->"Open"命令，弹出通用"打开"对话框，打开D:\MyDLL中的_IsRes.Dll文件，注意一定要将"Open as"属性设为"Resource"，如图4.15所示。
无标题文档

    (3) 这时，_IsRes.Dll的资源就被打开了，如图4.16所示。其中的对话框资源Dialog是我们最关心的。

    (4) 展开"Dialog"项，可以看到InstallShield定义的所有对话框。

    选定所有的对话框，如图4.17所示，并右击鼠标，从弹出的快捷菜单中选择"Properties..."命令，在弹出的属性对话框中将所有的对话框语系由"English[U.S.]"改为"Chinese[P.R.C]"。如图4.18所示。

    (5) 双击某个对话框，则显示其对话框模板。分别将对话框字体改为"宋体，9号"，对话框和所有控件的标题改成汉字。
    (6) 同样，用户可以将所有的对话框模板作上述修改。
    (7) 其他的资源如Bitmap、String Table、Version等也可作类似的修改，这里不再阐述。
    (8) 保存修改，退出Visual C++。至此，_IsRes.Dll汉化完毕。
无标题文档

    4.5.3 汉化补丁安装程序制作

    类似的，用户还可以用上述方法再对位于"\ Redistributable \Compressed Files\0009-English\Intel 32"文件夹中的IsUninst.Exe以及"\ Redistributable \Uncompressed Files \0009-English \OS Independent"文件夹中的_setup.dll这两个文件进行汉化。这样，就保证了InstallShield制作的安装程序界面是中文的。

    为了让您的朋友们分享您的成果，用户可以将这三个文件(_IsRes.Dll、IsUninst.Exe及_setup.dll)做成补丁安装程序的形式发布给您的朋友们。当然，在制作此类安装程序时还应掌握一些技巧。

    1．检测InstallShield的存在性

    检测硬盘中是否存在InstallShield，只需判断是否存在InstallShield的运行文件Ide.exe即可。当然，我们还应该考虑下列一些情况：

    (1) 查找到的Ide.exe的版本可能不是InstallShield 5.5版。这种情况必须调用VerGetFileVersion来检测其版本号是否为"5.50.136.0"。
    (2) 在硬盘中可能安装多个不同版本的InstallShield，即Ide.exe不止一个。这种情况需要将所有查找到的Ide.exe文件用链表的形式保存下来，然后一个一个地进行版本的检测。

    具体代码如下：

    // 查找到的InstallShield安装路径由svPath参数返回
    function ExistsInstallShield(svPath)
    IST listDrive, listIDE;
    UMBER nResult, nFind, nStrLen;
    STRING svString, svSubStr, szFileName, svResult, svVersionNumber;
    BOOL bFind;
    begin

    bFind = FALSE;
    szFileName = "Ide.exe";
    listDrive = ListCreate ( STRINGLIST ); // 创建存放硬盘驱动器号的链表
    listIDE = ListCreate ( STRINGLIST ); // 创建存放Ide.exe不同版本的路径链表
    GetValidDrivesList ( listDrive , FIXED_DRIVE , 10000000 );
    nResult = ListGetFirstString ( listDrive , svString );

    // 查找硬盘中存在的所有Ide.exe
    while (nResult != END_OF_LIST)
      svString = svString + ":";
      nFind = FindAllFiles ( svString , szFileName , svResult , RESET );
      while (nFind = 0)
        ListAddString ( listIDE , svResult , AFTER );
        nFind = FindAllFiles ( svString , szFileName , svResult , CONTINUE );
      ndwhile;
      nResult = ListGetNextString ( listDrive , svString );
    endwhile;
    nResult = ListGetFirstString ( listIDE , svString );

    // 判断所有Ide.exe中是否存在版本号"5.50.136.0"的文件
    while (nResult != END_OF_LIST)
      VerGetFileVersion ( svString , svVersionNumber );
      if (svVersionNumber = "5.50.136.0") then
        bFind = TRUE;
        nStrLen = StrLength(svString);
        StrSub(svSubStr, svString, 0, nStrLen - 15);
        StrRemoveLastSlash ( svSubStr );
        svPath = svSubStr;
        goto EndFind;
      endif;
      nResult = ListGetNextString ( listIDE , svString );
    endwhile;

    EndFind:
    ListDestroy(listDrive);
    ListDestroy(listIDE);
    return bFind;

    end;
无标题文档

    如果用户觉得上述查找的方法所花费的时间太长，可以试试下面的方法，它是检测Windows系统的"开始"->"程序"菜单项中是否存在InstallShield程序项。具体代码如下：

    function ExistsInstallShieldFast(svPath)
      STRING szFolderName, szItemName, svCmdLine, svWrkDir, svIconPath;
      STRING svShortCutKey;
      NUMBER nvIconIndex, nvMinimizeFlag, nResult, nStrLen;
      BOOL bFind;
      begin

        bFind = FALSE;
        szItemName = "InstallShield 5.5 Professional Edition";
        szFolderName = FOLDER_PROGRAMS^szItemName;
        nResult = QueryProgItem ( szFolderName , szItemName , svCmdLine , svWrkDir ,
                                svIconPath , nvIconIndex , svShortCutKey , nvMinimizeFlag );

        if (nResult = IS_ITEM) then
            bFind = TRUE;
            nStrLen = StrLength(svCmdLine);
            StrSub(svPath, svCmdLine, 0, nStrLen - 15);
            StrRemoveLastSlash ( svPath );
        endif;

      return bFind;

    end;

    代码中，函数QueryProgItem就是用来查找某个程序项或文件夹的，它的原型如下：

        QueryProgItem (szFolderName, szItemName, svCmdLine, svWrkDir, svIconPath,
        nvIconIndex, svShortCutKey, nvMinimizeFlag);

    其中，szFolderName用来指定包含程序项或目录的文件夹名称。它也可以是下列值之一：

      FOLDER_DESKTOP 桌面文件夹
      OLDER_STARTMENU "开始"的文件夹
      FOLDER_STARTUP "启动"菜单文件夹

    而szItemName用来指定要检测的程序项或目录名称。svCmdLine用来返回程序项的可执行文件的命令行内容，或者是快捷方式的目标内容，或者是程序目录的路径全名。svWrkDir用来返回程序项的工作目录的路径全名。svIconPath用来返回.ico或.exe文件的路径全名。nvIconIndex用来返回用于程序项的图标索引。svShortCutKey用来返回程序项的快捷键。nvMinimizeFlag用来返回程序项的运行方式，它可能是NULL或RUN_MINIMIZED，分别表示常规窗口和窗口最小化。函数返回IS_ITEM表示szItemName是一个程序项或快捷方式，而返回IS_FOLDER则表示szItemName是一个程序文件夹，返回<0的值则表示不能查找。

    当然，最好的方法是通过注册表来查找，在下一章将讨论这方面的内容。

    2．复制并备份原来的文件

     安装汉化补丁最终目的是将相关文件复制到系统中，为此我们需要注意以下几个方面：

    (1) 检测汉化补丁是否经安装过；
    (2) 将原来的文件备份；
    (3) 在创建新的媒介后，需要将汉化相关补丁文件复制到发布的安装媒介中，为避免汉化后的_IsRes.Dll、IsUninst.Exe及_setup.dll文件与安装程序本身的冲突，最好将这三个文件的文件名作适当的更改，如在文件名前加"P"等。
无标题文档

    具体代码如下：

    function InstallChineseFiles( szPath )
      STRING szSrcDir1,szSrcDir2;
      NUMBER nResult;
      begin

      szSrcDir1 = szPath ^ "Redistributable\\Compressed Files\\0009-English\\Intel 32";
      szSrcDir2 = szPath ^ "Redistributable\\Uncompressed Files\\0009-English" + "\\OS 　　　　　　　　　　　　　　　　　　Independent";
      VarSave (SRCTARGETDIR); // 将缺省的源文件夹和目标文件夹位置保存
      SRCDIR = szSrcDir1; // 设定缺省的源文件夹位置
      TARGETDIR = szSrcDir1; // 设定缺省的目标文件夹位置

      // 重新命名相关文件，以作为备份
      nResult = RenameFile ( "_IsRes.Dll" , "_IsRes.bak" );
      if (nResult< 0 ) goto EndInstall;
      RenameFile ( "IsUninst.Exe" , "IsUninst.bak" );

      SRCDIR = szSrcDir2;
      TARGETDIR = szSrcDir2;
      RenameFile ( "_setup.dll" , "_setup.bak" );
      VarRestore (SRCTARGETDIR); // 恢复缺省的源文件夹和目标文件夹位置
      VarSave (SRCTARGETDIR);

      TARGETDIR = szSrcDir1;

      // 复制相关文件，注意必须要XCopyFile中指定COMP_UPDATE_DATE参数，
      // 这样只有当复制文件比原来的文件的日期和时间新时，才会复制，避免了重复安装。
      nResult = XCopyFile ( "P_IsRes.Dll" , "" , COMP_UPDATE_DATE );
      nResult = XCopyFile ( "PIsUninst.Exe" , "" , COMP_UPDATE_DATE );
      if (nResult = 0) then
      SRCDIR = TARGETDIR;
      RenameFile ( "P_IsRes.Dll" , "_IsRes.Dll" );
      RenameFile ( "PIsUninst.Exe" , "IsUninst.Dll" );
      VarRestore (SRCTARGETDIR);
    endif;

    TARGETDIR = szSrcDir2;

    nResult = XCopyFile ( "P_setup.dll" , "" , COMP_UPDATE_DATE );
    if (nResult = 0) then
        SRCDIR = TARGETDIR;
        RenameFile ( "P_setup.dll" , "_setup.dll" );
    endif;

    VarRestore (SRCTARGETDIR);

    EndInstall:
      if (nResult < 0) then
          MessageBox ( "已经安装过InstallShield的汉化补丁！" , SEVERE );
      endif;
     return nResult;

    end;
无标题文档

    3．完整的安装程序

    完整的安装程序如下：

    prototype ExistsInstallShieldFast(BYREF STRING);

    prototype InstallChineseFiles( STRING );
    STRING svPath;
    BOOL nvSetup, nvDelete, bHasSetup;
    program

    SetDialogTitle ( DLG_MSG_SEVERE , "安装失败" );
    SetDialogTitle ( DLG_MSG_INFORMATION , "安装信息" );
    if (ExistsInstallShieldFast(svPath) = FALSE) then
        MessageBox ( "安装InstallShield 5.5汉化补丁之前，应先安装InstallShield！" , SEVERE );
        exit;
    endif;
    if (InstallChineseFiles(svPath) != 0) then
        MessageBox ( "安装InstallShield 5.5的汉化补丁失败！" , SEVERE );
    else
        MessageBox ( "已成功安装InstallShield 5.5的汉化补丁!" , INFORMATION );
    endif;

    endprogram

    function ExistsInstallShieldFast(svPath)
       ...
    begin
      // 同前
    end;

    function InstallChineseFiles( szPath )
         ...
      begin
         // 同前
       end;

    至此为止，用户可以为一个Windows一般应用程序建立一个颇具创意的安装程序。但是，我们还没有涉及到数据库应用程序、Visual Basic应用程序以及其他复杂的应用程序的包装，下一章将讨论这一方面的内容。


第 5 章 深入安装程序制作

    前面的几章内容基本满足了制作一般安装程序的需求，但对于稍稍复杂的应用软件来说，其安装程序的构造有时变得异常困难，例如一个ODBC数据库应用程序安装后，其数据源若由安装程序自动定义，则成为一个技术难题。当然，InstallShield 5.5的相关模板为用户的安装程序提供了必要的结构框架，提高了制作安装程序的效率，并且InstallShield 5.5中的某些机制使得用户很容易地对安装组件和安装类型进行控制。除此之外，安装中还常常涉及到反安装、多个安装程序及调试等一些深入话题。

    5.1 反安装

    当安装在计算机中的应用程序或软件不再需要时，用户很希望能将它们完全删除掉，这个过程称为反安装。随InstallShield 5.5一起发行的UnInstallShield是一个非常不错的反安装工具应用程序，它虽然很小巧，但其功能一点不逊色；而且，当用InstallShield项目向导创建的安装程序进行应用程序安装后，UnInstallShield将自动跟踪并记录安装内容，从而使得反安装完全彻底地删除应用程序。

    5.1.1 概述

    在Windows 98中，对一个应用程序进行反安装，可按下列步骤进行：

    (1) 选择桌面的"开始"菜单->"设置"->"控制面板"命令，打开"控制面板"窗口。
    (2) 找到"添加/删除程序"项，双击鼠标左键(传统方式)。
    (3) 弹出"添加/删除程序 属性"对话框，在应用程序列表中选择指定的应用程序名，如"Your Application Name"。
    (4) 弹出"Confirm File Deletion"询问对话框，单击[是]按钮进行下一步。
    (5) 单击[是]按钮，系统将自动进行该应用程序的删除，结果如图5.1所示。
无标题文档

    (6) 单击[OK]按钮，关闭对话框。

    上述删除应用程序的过程是自动执行的，但事实上，UnInstallShield运行时还需要一个可执行文件和一个反安装注册文件。一般地，IsUninst.exe是UnInstallShield的可执行文件。当发布用InstallShield项目向导创建的安装程序时，IsUninst.exe被自动压缩到_sys1.cab文件中。而当程序安装后，IsUninst.exe还会被自动复制到Windows文件夹中。

    UnInstallShield的反安装注册文件是在每次进行应用程序安装时由InstallShield自动创建的，用来记录安装过程中与反安装相关的事件。表5.1列出了可以被UnInstallShield记录的函数名。

    实际上，只有运行安装脚本程序中的DeinstallStart函数后，反安装注册才会真正开始。一般来说，缺省的注册文件名为DeisLxx.isu，其中"xx"是从1开始的顺序号，若当前应用程序文件夹中没有任何反安装注册文件，这时的注册文件名为DeisL1.isu。

    5.1.2 IsUninst.exe命令行参数

    UnInstallShield的可执行文件IsUninst.exe可以使用一些命令参数，如表5.2所示。

    需要说明的是，IsUninst.exe和指定的命令行参数之间一定要有空格，并且若文件的路径名中有空格，则必须在文件路径名的两边用双引号括起来。
无标题文档

    5.1.3 建立反安装

    在安装脚本程序中建立一个完整的反安装功能，一般要进行下列步骤：

    (1) 先调用InstallationInfo函数(必需的)为DeinstallStart函数作准备；
    (2) 调用DeinstallStart(必需的)创建反安装注册文件并在注册表中进行注册；
    (3) 调用RegDBSetItem(必需的)在控制面板中"添加/删除"对话框中的应用程序列表中添加反安装的应用程序名，并在注册表中进行注册；
    (4) 为了便于用户的反安装操作，还应将应用程序的反安装命令放入程序文件夹中。

    上述过程中的InstallationInfo、DeinstallStart以及RegDBSetItem函数的原型如下：

      InstallationInfo (szCompany, szProduct, szVersion, szProductKey);

    该函数是用来为DeinstallStart函数指定相应的公司名称、产品名称、版本号以及应用程序的可执行文件名。参数szCompany指定公司的名称，InstallShield使用szCompany参数在注册表中的[HKEY_LOCAL_MACHINE]\Software下创建一个\键。szProduct指定产品的名称，InstallShield使用szProduct参数在注册表中的[HKEY_LOCAL_ MACHINE]\Software\ 下创建一个\键。szVersion用来指定版本号，InstallShield使用szVersion参数在注册表中的[HKEY_LOCAL_MACHINE]\Software\\ 下创建一个\ 键。szProductKey用来指定应用程序的可执行文件名，InstallShield使用szProductKey参数在注册表中的[HKEY_LOCAL_MACHINE]\Software\Microsoft\Windows\CurrentVersion\App Paths下创建相应的应用程序路径，但这个应用程序路径键只有通过调用函数RegDBSetItem才会真正被创建。

      DeinstallStart (szDefLogPath, svLogFile, szKey, lStyle);

    该函数是用来在注册表中创建相关的应用程序反安装键以及对反安装注册文件进行初始化。参数szDefLogPath用来指定反安装注册文件放置的路径全名，不要包含文件名。svLogFile用来指定反安装注册文件的文件名。若此参数为""，则使用缺省的反安装注册文件名。szKey用来指定要创建的应用程序反安装的键名。lStyle参数必须为0。

      RegDBSetItem (nItem, szValue);
无标题文档

    该函数是用来在应用程序路径键名或应用程序反安装键名下设置相关的键值。参数szValue用来指定相应的键值。nItem用来指定要设置的键名，它可以是下列值之一：

    REGDB_APPPATH 在应用程序路径键名下设置 [Path] 键值。
    REGDB_APPPATH_DEFAULT 在应用程序路径键名下设置 [DefaultPath] 键值。
    REGDB_UNINSTALL_NAME 在反安装键名下设置 [DisplayName] 键值。

按照上述的步骤，我们可以用下列的简单示例来实现应用程序的反安装功能。

    [例Ex_UnInstall] 实现应用程序的反安装功能。

    // 定义所需要的符号常量
    #define APPBASE_PATH "我的目录"
    #define COMPANY_NAME "我的公司"
    #define PRODUCT_NAME "我的应用程序"
    #define PRODUCT_VERSION "6.0"
    #define PRODUCT_KEY "test.exe"
    #define DEINSTALL_KEY "Test_DeinstKey"
    #define UNINSTALL_NAME "我的应用程序 6.0"
    #define PROGRAM_FOLDER_NAME "我的应用程序"

    STRING szMsg, svTarget, svResult, svUninstLogFile, svFolder, szProgram;

    program

    start:

      // 选择安装目的位置
      szMsg = "选择一个目的位置.";
      svTarget = TARGETDISK ^ APPBASE_PATH;

      if (AskDestPath("", szMsg, svTarget, 0) = BACK) then
        goto start;
      endif;

      InstallationInfo(COMPANY_NAME, PRODUCT_NAME, PRODUCT_VERSION, PRODUCT_KEY);
      DeinstallStart(svTarget, svUninstLogFile, DEINSTALL_KEY, 0);
      RegDBSetItem(REGDB_UNINSTALL_NAME, UNINSTALL_NAME);

      // 在程序菜单中添加反安装菜单项
      svFolder = PROGRAM_FOLDER_NAME;

      CreateProgramFolder(svFolder);

      szProgram = UNINST;
      szProgram = szProgram + " -f" + svUninstLogFile;

      AddFolderIcon(svFolder, "unInstallShield", szProgram, WINDIR,"", 0, "", REPLACE);
  ndprogram

    该程序在Windows 98中运行后，结果为：
    (1) 在"开始"->"程序"菜单中，创建了"我的应用程序"菜单，该菜单中包含了"UnInstallShield"命令，其链接的内容为"C:\WINDOWS\IsUninst.exe -fC:\我的目录\DeIsL1.isu"。
    (2) 控制面板的"添加/删除程序 属性"对话框的应用程序列表中含有"我的应用程序 6.0"；
    (3) 缺省安装后，应用程序的工作文件夹为"C:\我的目录"，该文件夹中存在一个反安装注册文件DeIsL1.isu。


5.2 安装类型和组件控制

  缺省的安装程序项目一般都包括Compact(紧凑)、Typical(典型)和Custom(定制)三个安装类型，以及每个安装类型都有"Program Files"、"Example Files"、"Help Files"和"Shared DLLs"等组件。这些安装类型和组件的控件既可以在集成开发环境中直接操作，也可以通过编程来控制。

    5.2.1 安装类型控制

    在InstallShield 5.5中，只有SdSetupTypeEx可以使用除Compact、Typical和Custom之外的多个安装类型，并且允许用户对安装类型进行定制。例如下面的过程：

    (1) 创建一个Blank Setup类型的安装项目(设其缺省的项目名为Blank Setup-1)。
    (2) 将项目工作区窗口切换到Setup Types页面。
    (3) 在Setup Types页面中，选定最上一层的"’Blank Setup-1’ Setup Types"项，并右击鼠标，从弹出的快捷菜单中选择"New Setup Type"命令。
    (4) 这时，安装类型中添加了一个"New Setup Type 1"类型。
    (5) 选定新添加的安装类型项，右击鼠标，从弹出的快捷菜单中选择"Rename"命令。并将"New Setup Type 1"改为"Minimum"。
    (6) 依次右击各个安装类型，从弹出的快捷菜单中选择"Properties"命令。分别将它们的属性改成下列结果：

      安装类型 Description Display Name
      Minimum 最小安装，大约需要50M硬盘空间。 最小
      Compact 紧凑安装，大约需要80M硬盘空间。 紧凑
      Typical 典型安装，大约需要120M硬盘空间(一般用户的安装方式)。 典型
      Custom 定制安装，最多需要150M硬盘空间(高级用户的安装方式)。 定制

    (7) 将项目工作区窗口切换到Media页面。单击Media Build Wizard项，创建新的媒介。
    (8) 打开脚本安装程序文件Setup.rul，添加下列代码：

        #include "sddialog.h"
        STRING szTitle,szMsg,svSetupType;
        program
          Disable ( BACKBUTTON );
          szTitle = "安装类型";
          szMsg = "点取您希望的安装类型，然后按[Next]按钮";
          SdSetupTypeEx ( szTitle , szMsg , "" , svSetupType , 0 );
        endprogram
        #include "sddialog.rul"

    (9) 编译并运行。结果如图5.2所示。
无标题文档

    5.2.2 组件控制

    对组件的一般操作过程如下：

    (1) 创建一个Blank Setup类型的安装项目(设其缺省的项目名为Blank Setup-2)。
    (2) 将项目工作区窗口切换到Components页面。
    (3) 这时，安装类型中已添加了一个"New Component 1"类型，选中并删除它。
    (4) 在Components页面中，选定最上一层的"’Blank Setup-2’ Components"项，并右击鼠标，从弹出的快捷菜单中选择"Component Wizard ..."命令。
    (5) 这时，弹出如图5.3所示的对话框，在推荐的组件名列表框中选定"Shared Files"项，并单击[下一步]按钮。

    (6) 弹出如图5.4所示的对话框，保留缺省值，单击[完成]按钮后，系统就会添加一个名为"Shared Files"组件。
无标题文档

    (7) 重复上述操作，再为安装项目添加三个组件。这样，连同原来缺省的Component组件在内一共有四个组件，名称分别是"Shared Files"、"Help Files"、"Sample Files"和"New Component 0"。
    (8) 选定"Sample Files"，并右击鼠标，从弹出的快捷菜单中选择"Rename"命令。并将"Sample Files"改为"Examples"。类似的，将"New Component 0"改为"Executable"。
    (9) 依次单击各个安装组件，分别将它们的属性改为如表5.3所示的结果。表中，New File Group 1、New File Group 2和New File Group 3是在安装项目的File Groups页面中创建的新的文件组。

    (10) 将项目工作区窗口切换到Media页面。单击Media Build Wizard项，创建新的媒介。
    (11) 打开脚本安装程序文件Setup.rul，添加下列代码：

      #include "sddialog.h"
      STRING szTitle, szMsg, szDir;
      program
        szTitle = "选择组件";
        szMsg = "选择您需要安装的组件，清除不需要的组件。";
        szDir = "C:\\Program Files\\Temp";
        SdComponentDialog2 ( szTitle , szMsg , szDir , "" );
      endprogram
      #include "sddialog.rul"

    (12) 编译并运行。结果如图5.5所示。
无标题文档

    从上面的示例过程可以看出： 通过InstallShield的开发环境对组件的操作，我们可以容易地实现组件的选择以及相关文件的传送，这是一种最稳妥的方法。当然，我们也可在安装脚本程序中直接对组件进行添加、设定等操作。例如下面的代码过程，其结果和图5.5是一样的。

    [例Ex_ScriptComponent] 直接用脚本程序对组件进行操作。
    #include "sddialog.h"
    #define COMP1 "必须的可执行文件"
    #define COMP1SIZE 7005184
    #define COMP1DESC "该组件包含该程序所必须的可执行文件"
    #define COMP2 "共享文件"
    #define COMP2SIZE 123904
    #define COMP2DESC "选中此组件，将安装该程序的共享文件"
    #define COMP3 "帮助文件"
    #define COMP3SIZE 134144
    #define COMP3DESC "选中此组件，将安装该程序的相应的帮助文件"
    #define COMP4 "示例文件"
    #define COMP4SIZE 82944
    #define COMP4DESC "选中此组件，将安装该程序的Visual C++ 6.0的示例文件"
    #define DESTDIR "C:\\Program Files\\Temp"
    #define SDCOMPTITLE "选择组件"
    #define SDCOMPMSG "选择您需要安装的组件，清除不需要的组件。"
    STRING szDir;
    NUMBER nData;
    program

      // 定义一个新的media名称
      MEDIA = "MyComponent";
      // 添加第一个组件
      ComponentAddItem( MEDIA, COMP1, COMP1SIZE, TRUE );

      // 设置第一个组件的相关属性
      ComponentSetData ( MEDIA, COMP1, COMPONENT_FIELD_DESCRIPTION, nData, COMP1DESC );
      ComponentAddItem( MEDIA, COMP2, COMP2SIZE, TRUE );
      ComponentSetData ( MEDIA, COMP2, COMPONENT_FIELD_DESCRIPTION, nData, COMP2DESC );
      ComponentAddItem( MEDIA, COMP3, COMP3SIZE, TRUE );
      ComponentSetData ( MEDIA, COMP3, COMPONENT_FIELD_DESCRIPTION, nData, COMP3DESC );
      ComponentAddItem( MEDIA, COMP4, COMP4SIZE, TRUE );
      ComponentSetData ( MEDIA, COMP4, COMPONENT_FIELD_DESCRIPTION, nData, COMP4DESC );
      szDir = DESTDIR; // 设置目的文件夹
      SdComponentDialog2 ( SDCOMPTITLE, SDCOMPMSG, szDir, "" );

      // 恢复原来的media
      MEDIA = "DATA";
      endprogram
      #include "sddialog.rul"
无标题文档

    上述程序中，ComponentAddItem和ComponentSetData是用于组件添加和设定的两个主要函数，它们的原型如下：

      ComponentAddItem (szMedia, szComponent, nDataSize, bSelected);

    该函数用来创建一个只适用于script-created组件系列的组件。参数szMedia用来指定script-created组件系列的媒介名。若指定的媒介名不存在，则将自动创建。szComponent用来指定要添加的组件，不能为NULL。nDataSize用来指定该组件所包含的文件字节数。bSelected用来指明该组件的缺省选定状态。为TRUE时，表示选中；为FALSE时，表示未选中。

      ComponentSetData (szMedia, szComponent, nInfo, nData, szData);

    该函数用来设置某个组件的相关属性，这些属性和InstallShield开发环境中的组件属性基本一一对应。参数szMedia用来指定script-created组件系列的媒介名。szComponent用来指定要设置的组件。nData用来指定设置的数值。szData用来指定设置的文本。nInfo用来指定要设置的属性类型，它可以是下列值之一：

    COMPONENT_FIELD_DESCRIPTION 组件的描述
    OMPONENT_FIELD_FTPLOCATION 组件的FTP地址
    COMPONENT_FIELD_HTTPLOCATION 组件的HTTP地址
    COMPONENT_FIELD_VISIBLE 组件是否在选择组件对话框中显示出来。相应的nData为TRUE(显示)或FALSE(不显示)。
    COMPONENT_FIELD_SELECTED 组件的选中状态。相应的nData为TRUE(选中)或FALSE(未选中)。
    COMPONENT_FIELD_MISC 组件的其他文本
    COMPONENT_FIELD_DISPLAYNAME 组件的显示名称

    需要说明的是，在安装初始化时，系统变量MEDIA的缺省值被设置成"DATA"，若用户用script-created机制创建组件，并更改了MEDIA的值，则在调用ComponentMoveData、 CreateShellObjects或CreateRegistrySet函数之前，必须将 MEDIA的值重新设置成原来的"DATA"。


5.3 制作数据库应用程序的安装

    在制作安装程序时，如果一般应用程序的运行环境只是需要一些相应的DLL文件，那么这种类型的安装程序是非常容易制作的。例如，一般的Visaul Basic 6.0应用程序程序只需要在Windows操作系统的System目录中安装Msvbvm60.dll和ComCat.dll文件就可以运行了。但是，若是一个数据库应用程序，安装程序的构造就变得复杂了，因此我们不仅需要考虑所使用的数据库技术和方式，而且还要考虑采用的数据库开发软件环境。好在InstallShield提供了ODBC-DAO-RDO、OLE DB、Access、PowerBuilder等安装程序模板，使得问题变得简单许多。

    5.3.1 概述

    用数据库方式来管理人们日常生活中大量的信息已变得越来越重要，并涌现出许多数据库管理系统，如PowerBuilder、Micosoft Access、Micosoft SQL Server、Oracle Server、Sybase SQL Server和Micosoft Visual Foxpro等。尽管这些系统能出色地胜任数据库的管理，但却不能开发出其它功能强大的Windows应用程序。而许多开始工具如Visual C++就能将关系数据库与面向对象的编程方法有机地结合起来，使得数据库处理和应用程序开发都能很好地进行。

    一般地，这些开发工具通常为用户提供了ODBC(Open Database Connectivity，开放数据库连接) 、DAO(Data Access Objects，数据访问对象)及OLE DB(OLE Data Base，OLE数据库)三种数据库方式，使用户的应用程序从特定的数据管理系统(DBMS)脱离出来。

    ODBC提供了应用程序接口(API)，使得任何一个数据库都可以通过ODBC驱动器与指定的DBMS相联。用户的程序就可以通过调用ODBC驱动管理器中相应的驱动程序达到管理数据库的目的。作为Microsoft Windows Open Standards Architecture (WOSA，Windows开放式服务体系结构)的主要组成部分，ODBC一直沿用至今。

    DAO类似于用Microsoft Access或Microsoft Visual Basic编写的数据库应用程序，它使用Jet数据库引擎形成一系列的数据访问对象：数据库对象、表和查询对象、记录集对象等。它可以打开一个Access数据库文件(MDB文件)，也可直接打开一个ODBC数据源以及使用Jet引擎打开一个ISAM(被索引的顺序访问方法)类型的数据源(dBASE、FoxPro、Paradox、Excel或文本文件)。

    OLE DB试图提供一种统一的数据访问接口，并能处理除了标准的关系型数据库中的数据之外，还能处理包括邮件数据、Web上的文本或图形、目录服务(Directory Services),以及主机系统中的IMS和VSAM数据。OLE DB提供一个数据库编程COM(组件对象模型)接口，使得数据的使用者(应用程序)可以使用同样的方法访问各种数据，而不用考虑数据的具体存储地点、格式或类型。这个COM接口与ODBC相比，其健壮性和灵活性要高得多。但是，由于OLE DB的程序比较复杂，因而对于一般用户来说，应使用基于OLE DB的高层访问接口ADO(ActiveX Data Object)，它对OLE DB的接口作为封装，从而使程序开发得到简化。
无标题文档

    5.3.2 使用InstallShield安装模板

    InstallShield提供一定数量的安装程序模板，这些模板是一些完整的、可运行的安装项目，用户可以将其直接复制或在此基础上作进一步的修改。

    InstallShield 5.5版本提供了如下的安装程序模板：

    1. Template One

     该模板是用于Windows 95、98或Windows NT 4.0的最基本的安装程序模板，用户通过少量的修改可满足一般安装程序的需要。以此模板创建的安装程序执行下列过程：

    (1) 显示缺省的安装启动画面，用户通过修改Setup.bmp来改变显示的内容；
    (2) 调用SdWelcome函数，显示相关欢迎信息；
    (3) 调用SdAskDestPath函数获取用户指定的安装路径；
    (4) 进行反安装与安装相关操作；
    (5) 在系统"开始"->"程序"文件夹中创建程序文件夹及其快捷方式，并调用ShowProgramFolder函数显示相关的内容；
    (6) 调用MessageBox函数提示安装结束。

    2. Template Two

    该模板比Template One更完善，它的功能和"Project Wizard"非常相似。

    3. MFC Template

     该模板用于基于MFC的Windows 95、98或Windows NT 4.0的基本应用程序模板。以此模板创建的安装程序执行与Template One相似的过程，不同的是它使用自带的MFC运行库Mfc42.dll和Msvcrt.dll，当然用户可以将本地最新的运行库复制到\TemplateData\MFC Template Data相关目录下来更新。

    4. Visual Basic Template

     该模板用于基于VB5.0/6.0的Windows 95、98或Windows NT 4.0的基本应用程序的完整模板，安装程序运行过程和"Project Wizard"是一致的。若用户想要用此模板安装VB6.0应用程序，则需要将"Windows System Self-reg Shared Files"文件组中的Msvbvm50.dll更换成Msvbvm60.dll即可。

    除了上述应用程序基本模板之外，InstallShield5.5还对最新的数据库方式提供了许多模板，它们是Access Template(支持Microsoft Access97)、ADO Template(全面支持ADO，安装ADO、OLE DB提供者以及OLE DB)、BDE 4.51 Template(支持Borland Database Engine 4.51)、BDE 5 Template(支持Borland Database Engine 5)、ODBC-DAO-RDO Template(全面支持DAO、RDO以及ODBC，包括桌面数据库驱动程序、SQL和Oracle驱动程序以及相应的DSN)、OLE DB Template(支持OLE DB 1.5)、PowerBuilder Template(全面支持PowerBuilder 6.0，包括运行库文件、ODBC3.5等)。

    尽管InstallShield5.5有很多上述这样的模板，但它们的使用方法却是基本一样的。例如下面的过程是用ODBC-DAO-RDO Template创建VC6.0数据库应用程序的安装项目，已知：Access 2000制作的数据库student.mdb，库中包含一张名为xs的数据表，要求ODBC数据源名为"我的数据库"。
无标题文档

    (1) 启动InstallShield 5.5；
    (2) 单击"New"工具按钮或选择"File"->"New"菜单命令，弹出相应的对话框，切换到"Template"页面，如图5.6所示；

    (3) 在左边的模板列表框中选定"ODBC-DAO-RDO Template"，单击[Properties]按钮可以查看该模板有相关属性信息；
    (4) 单击[确定]按钮后，IntallShield就会自动为用户创建一个名为"ODBC-DAO-RDO Template"的安装项目；
    (5) 切换到"File Groups" 页面，展开"Program Files"项，选定"Static File Links"，删除右边窗口中的所有文件，并右击鼠标，从弹出的快捷菜单中选择"Insert Files"命令，将数据库文件"student.mdb"和VC6.0应用程序(Release版)"Ex_ODBC.exe"调入；
    (6) 按相同的方法，将"Windows System Self-reg Files"下的链接文件改为"Msvcrt.dll"，并将其"Self-Registered"属性改为"No"，将"Windows System Self-reg Share Files"下的链接文件改为"Mfc42.dll"；
    (7) 切换到"Resource"页面，展开"Explorer Shell"的所有子项，选定"App"，将其"Target"属性改为"\Ex_ODBC.exe"。
    (8) 切换到"Setup Files" 页面，展开"English"项，选定"Windows 95/98 & NT 3.51/4.0"，双击右边窗口的"_Drivers.ini"文件，这样该文件就会被打开。
    (9) 将"_Drivers.ini"内容移到最后，找到[Sample Access Data Source]部分，其中关键字：

    Description 显示在ODBC管理器中的DSN描述
    Driver 与DSN相关的驱动文件名
    DSN 数据源名
    DBQ 与DSN相关的数据源的完整路径和数据库文件
    ranslationDLL 与DSN相关的形实转换DLL
    TranslationName 与TranslationDLL相关的注册表键名
    ISODBCDriverDesc 与驱动相关的注册表键名
    ISODBCComponentName 与DSN相关的安装组件名
    ISODBCDSNType 要安装的数据源类型，当为user时表示缺省的用户数据源类型，当为system时为系统数据源类型。

    在以上的关键字中，Description=, Driver=, DSN=, 和 DBQ= 是必须的。

    (10) 将[Sample Access Data Source]内容改变如下：

        Description=用于VC++的数据源
        ISODBCDriverDesc=Microsoft Access Driver (*.mdb)
        Driver=\odbcjt32.dll
        DSN=我的数据库
        DefaultDir=
        DBQ=\student.mdb
        UID=
        SODBCUninstDSN=TRUE
        ISODBCComponentName=ISODBC Drivers\Microsoft Access Driver

    (11)将项目工作区窗口切换到Media页面，单击Media Build Wizard项，创建新的媒介。
    (12)编译并运行。

    需要说明的是，这些数据库模板的目的是将数据库应用程序及其所有的运行环境全部包装，这是一个最完美的做法；但同时也带来一个新的问题，那就是由于数据库运行环境的版本变化，原有的安装模板不再适用。为此需要用户及时访问http:// www.installshield.com/support/is5以获得最新的安装模板。
无标题文档

    5.3.3 使用注册表

    InstallShield5.5为用户许多操作注册表的函数，如RegDBSetDefaultRoot、RegDBCreateKeyEx、RegDBSetKeyValueEx和RegDBDeleteKey等，它们的原型如下：

      RegDBSetDefaultRoot (nRootKey);

    该函数用来设置InstallShield操作的缺省根键名。参数nRootKey用来指定要设置的根键名，它可以是HKEY_CLASSES_ROOT、HKEY_LOCAL_MACHINE(Windows NT 4.0除外)、HKEY_CURRENT_USER或HKEY_USERS。

      RegDBCreateKeyEx (szKey, szClass);

    该函数用来在注册表中创建一个主键。参数szKey用来在注册表的根键下，指定要创建的键名，可以使用两个反斜杠"\\"来表示子键的层次。szClass用来指定一个与该键相关的类名(高级用户使用)。

      RegDBSetKeyValueEx (szKey, szName, nType, szValue, nSize);

    该函数用来在注册表中设置指定键名下的键值。参数szKey用来指定要设置键值的键名，可以使用两个反斜杠"\\"来表示子键的层次；szName用来指定在szKey下与设置的值相关联的值名；若要设置缺省的值，将szName置为NULL；szValue用来指定要设置的值；nSize用来指定要设置值的字节大小；nType表示设置的数值类型，它可以是下列值之一：

      REGDB_STRING 单行字符串文本
      REGDB_STRING_EXPAND 含有类似"%MYPATH%"的字符串
      REGDB_STRING_MULTI 多行字符串文本
      REGDB_NUMBER 数字
      REGDB_BINARY 二进制数值

    RegDBSetKeyValueEx相对应的函数是RegDBGetKeyValueEx，它是用来在注册表中获取指定键名下的键值。
    RegDBDeleteKey (szSubKey);
    该函数在注册表中删除由szSubKey指定的一个键。

    1. 通过注册表获取应用程序安装信息

    在第四章第五节中，介绍了两种方法来检测InstallShield5.5专业版的安装路径。但如果用注册表操作函数就显得非常容易，如下面的代码：

    #define TITLE "RegDBGetAppInfo"
    STRING szStrName, svStrValue, szMsg;
    NUMBER nvSize, nvType;
    program
      // Set the root key.
      RegDBSetDefaultRoot (HKEY_LOCAL_MACHINE);
      szStrName = "Software\\InstallShield\\InstallShield Professional\\5.5\\Main";
      if (RegDBGetKeyValueEx (szStrName, "Path", nvType, svStrValue, nvSize) < 0) then
           MessageBox ("Failed to get application information value.", SEVERE);
           abort;
      else
        if (nvType = REGDB_STRING) then
          szMsg = "The InstallShield Installed Path is : %s ";
          SprintfBox (INFORMATION, TITLE, szMsg, svStrValue);
        endif;
      endif;
    endprogram
无标题文档

    2. 通过注册表配置ODBC

    除了前面ODBC-DAO-RDO的安装模板可以进行ODBC数据源的设置外，直接操作注册表也能达到同样效果。

    例如下面的过程是利用MFC Template制作VC6.0数据库应用程序的安装项目(条件同前):

    (1) 启动InstallShield 5.5；
    (2) 单击"New"工具按钮或选择"File"->"New"菜单命令，弹出相应的对话框，切换到"Template"页面，选定"MFC Template"，单击[确定]按钮后，IntallShield就会自动为用户创建一个名为"MFC Template"的安装项目；
    (3) 切换到"File Groups" 页面，展开"Program Files"项，选定"Static File Links"，删除右边窗口中的所有文件，并右击鼠标，从弹出的快捷菜单中选择"Insert Files"命令，将数据库文件"student.mdb"和VC6.0应用程序(Release版)"Ex_ODBC.exe"调入；
    (4) 切换到"Resource"页面，展开"Explorer Shell"的所有子项，选定"App"，将其"Target"属性改为"\Ex_ODBC.exe"。
    (5) 切换到"Scripts"页面，打开"setup.rul"脚本文件，在
      prototype CheckRequirements ();
      语句的下一行添加下列语句：
      prototype SetupRegistry();
    (6) 在脚本文件的后面添加该自定义函数代码：

    function SetupRegistry()
      NUMBER nResult;
      STRING szPath, szKeyName;
      begin
        RegDBSetDefaultRoot ( HKEY_CURRENT_USER );
        szKeyName = "Software\\ODBC\\ODBC.INI\\我的数据库";
        if(RegDBKeyExist(szKeyName)<0) then
          RegDBCreateKeyEx (szKeyName,"");
        else
          RegDBDeleteKey (szKeyName);
          RegDBCreateKeyEx (szKeyName,"");
        endif;
      szKeyName = "Software\\ODBC\\ODBC.INI\\ODBC Data Sources";
      if(RegDBKeyExist(szKeyName)<0) then
        RegDBCreateKeyEx (szKeyName,"");
      endif;
      RegDBSetKeyValueEx ( szKeyName, "我的数据库" , REGDB_STRING ,"Microsoft Access Driver (*.mdb)" , -1);
    szPath = TARGETDIR^"student.mdb";
    szKeyName = "Software\\ODBC\\ODBC.INI\\我的数据库";
    RegDBSetKeyValueEx ( szKeyName, "DBQ" , REGDB_STRING , szPath , -1);
    RegDBSetKeyValueEx ( szKeyName, "Description" , REGDB_STRING , "用于VC++的数据源" , -1);
    RegDBSetKeyValueEx ( szKeyName, "Driver" , REGDB_STRING , WINSYSDIR+"\odbcjt32.DLL" , -1);
    RegDBSetKeyValueEx ( szKeyName, "DriverID" , REGDB_NUMBER , "25" , -1);
    RegDBSetKeyValueEx ( szKeyName, "SafeTransactions" , REGDB_NUMBER ,"0" , -1);
    RegDBSetKeyValueEx ( szKeyName, "UID" , REGDB_STRING ,"" , -1);
    RegDBCreateKeyEx ("Software\\ODBC\\ODBC.INI\\我的数据库\\Engines","");
    RegDBCreateKeyEx ("Software\\ODBC\\ODBC.INI\\我的数据库\\Engines\\Jet","");
    szKeyName = "Software\\ODBC\\ODBC.INI\\我的数据库\\Engines\\Jet";
    RegDBSetKeyValueEx ( szKeyName, "Driver" , REGDB_STRING , WINSYSDIR+"\odbcjt32.DLL" , -1);
    RegDBSetKeyValueEx ( szKeyName, "ImplicitCommitSync" , REGDB_STRING , "" , -1);
    RegDBSetKeyValueEx ( szKeyName, "Threads" , REGDB_NUMBER , "3" , -1);
    RegDBSetKeyValueEx ( szKeyName, "UserCommitSync" , REGDB_STRING , "Yes" , -1);
  end;

    (7)将项目工作区窗口切换到Media页面，单击Media Build Wizard项，创建新的媒介。
    (8)编译并运行。


5.4 多个安装程序

    需要多个安装程序的情况是比较多的，如"金山词霸"的安装。启动多个安装程序可就下面两种情况而有不同的方法。

    1. 由InstallShield构造的两个安装项目

    在这种情况下，用户首先指定其中一个为主安装项目，另一个为次安装项目，并且次安装项目已被编译过，能完全正确运行。这时，我们按下列步骤进行：

    (1) 启动InstallShield 5.5，将主安装项目调入。
    (2) 切换到项目工作区窗口的"Setup Files"页面，将次安装项目要发布的安装媒介\Dsik1、\Disk2...等文件夹下的文件全部调入用户指定的\目录项中。
    (3) 在主安装项目的脚本程序中，添加下列语句：

          DoInstall( SUPPORTDIR ^ "Setup.ins", "", WAIT );

    其中，DoInstall就是用来启动另一个安装程序的，其函数原型如下：

          DoInstall (szInsFile, szCmdLine, lWait);

    该函数是用来运行另一个安装程序。参数szInsFile用来指定用户要运行的被正确编译过的脚本文件.ins的文件全名；szCmdLine用来指定InstallShield命令行内容；lWait表示运行的操作方式，它可以是下列值之一：

      NOWAIT 两个安装程序同时运行
      WAIT 在次安装程序运行完成后，才进行主安装项目的下一步操作

    该函数返回1时表示成功调用了次安装程序，并将流程返回到调用DoInstall的下一个语句中。若InstallShield找到了要调用的安装脚本文件，但却不能启动它，那么流程仍就被返回，并且此函数返回1。返回-2时表示InstallShield没有找到要调用的安装脚本文件。返回其他负数则表示产生不可预知的错误。

    (4) 将项目工作区窗口切换到Media页面，单击Media Build Wizard项，创建新的媒介。
    (5) 编译并运行。

    需要说明的是，如果被调用的次安装程序中也有DoInstall的调用，那么就形成了嵌套，从而可以启动许许多多的安装程序。
无标题文档

    2. 由第三方提供的安装程序包

    如果要执行的另一个安装程序是第三方提供的安装程序，它可能只有一个可执行的文件。这时就要使用LaunchAppAndWait和LaunchApp函数来启动，它们的原型如下：

      LaunchAppAndWait (szProgram, szCmdLine, lWait);
      LaunchApp (szCommand, szCmdLine);

    这两个函数都是用来启动一个应用程序，它们唯一的区别是LaunchApp函数直到已启动的应用程序被关闭或中断后，流程才会被返回，而LaunchAppAndWait可以立即返回流程。参数szProgram和szCommand都是用来指定要启动的应用程序文件全名，但对于szProgram来说，若应用程序没有指定路径和文件扩展名，LaunchAppAndWait不会启动该应用程序，而对于szCommand来说，若没有指定应用程序的路径，LaunchApp将在当前目录、Windows目录、Windows系统目录以及其他PATH环境变量指定的目录中进行查找。szCmdLine用来指定应用程序执行时的命令行参数，若没有，则将此参数设为NULL。lWait用来指定流程的返回方式，它可以是NOWAIT(立即返回)或WAIT(直到应用程序关闭或中断才将流程返回)。

     如果需要启动的应用程序需要的DLL文件不在Windows系统目录中，则需要调用ChangeDirectory函数来改变调用DLL的目录，例如下面的过程是通过LaunchAppAndWait函数执行另一个安装程序：

      (1) 启动InstallShield 5.5，将主安装项目调入。
      (2) 切换到项目工作区窗口的"Setup Files"页面，将次安装项目要发布的安装媒介下的文件全部调入用户指定的\目录下。
      (3) 在主安装项目的脚本程序中，添加下列语句：

    NUMBER nResult;
    STRING szDir;
    program
      ...
      szDir = SUPPORTDIR;
      StrRemoveLastSlash (szDir);
      ChangeDirectory (szDir);
      LaunchAppAndWait ( SUPPORTDIR ^"Setup.exe " , "" ,WAIT);
    endprogram

    (4) 将项目工作区窗口切换到Media页面，单击Media Build Wizard项，创建新的媒介。
    (5) 编译并运行。


5.5 安装程序的调试

    InstallShield 5.5开发环境中集成了功能强大的调试工具，利用它们可以调试InstallScript安装程序，并且能设置和管理断点、查看全局和局部变量的值等。

    5.5.1 调用前的准备

    在InstallShield中，调试一个安装程序一般分为两步。首先，修正在编译时产生的不正确的语法和拼错的关键词等错误，直至编译通过为止；然后，再用调试器检测和修正逻辑错误以及在循环、判断等运行时产生的错误。

    为了能有效地进行安装程序的调试，下面的内容应该要掌握。

    1. 调试器能检测出的错误类型

    InstallShield开发环境集成的调试器能调试出下列三种类型的错误：

      1) 语法错误

     这是最简单的一种错误类型，通常包括拼写错误、标点符号错误、语句结构及其用法错误，如if语句中忘记了endif。这类错误一般都能被编译器发现并加以标记。

      2) 运行错误

     顾名思义，这类错误是在安装程序运行时产生的错误，它通常有几种表现形式。例如，安装程序试图在一个不存在的文件夹中复制文件，内存不够、目标磁盘已满或损坏等。

      3) 逻辑错误

    这类错误要比语法错误和运行错误更具隐蔽性。例如，假如用户在脚本中有一条创建路径的语句，该语句涉及到两个或以上的变量。一旦创建的路径不正确，您能确定是哪个变量产生错误呢？

    2. 在不同的机器中调试错误

    为了能在不同的操作系统中(如Windows 3.1或Windows NT 3.51)保证安装程序能正确运行，我们需要在不同的机器中进行调试。通常按下列步骤进行：

    (1) 在安装有InstallShield 5.5的机器中编译并建立用户的安装程序。
    (2) 选择"Build"->"Debug Setup"菜单命令，在调试器被启动前，系统会产生Setup.dbg，一时调试器被启动，立即退出它。
    (3) 在另外一个机器上，复制下列文件：

    媒介文件 在 \My Installations\\Media\\Disk Images文件中。
    Setup.dbg 在\My Installations\\Script Files文件中。
    Isdbgi51.dll(32位Intel)　或
    Isdbga51.dll (32位Alpha)　或
    Isdbg51.dll (16位) 从Windows系统文件夹中复制到另一个机器的相同文件夹中

    (4) 在另一个机器中，用DOS模式运行setup -d 。

    3. 启动调试器

    一旦一个安装程序被编译通过后，就可以用下面两种方式启动调试器：

      (1) 选择"Build"->"Debug Setup"菜单命令；
      (2) 单击工具栏上的按钮"Debug Setup"按钮。
无标题文档

    5.5.2 使用调用器

    调试器启动后，它还再次地编译安装程序，并将断点停留在安装脚本程序体中的第一条语句上，如图5.7所示。

    1. 调试器的界面

    图5.7是InstallShield调试器的全部界面，它通常含有查看窗口、变量窗口、代码窗口以及相关控制按钮。

      1) 查看窗口

    查看窗口中往往显示出一些变量及相应的值，当用户在该窗口中选定某个变量，按下[Del]键将删除该项，若用户在变量窗口中选定一个变量后，单击"查看"按钮就会将该变量及其内容添加在查看窗口中。

      2) 变量窗口

    变量窗口有两组控件，分别用于查看全局变量和局部变量的值。开始时，查看全局变量控件组被激活，用户可以通过Variable组合框选定某个全局变量，控件组右边的Value编辑框显出相应的内容。

      3) 代码窗口

    代码窗口可兼作编辑器，用来编辑代码、设置断点位置以及显示程序执行的流程等。

      4) 控制按钮

    控制按钮用来进行程序调试，各按钮的功能如表5.4所示。
无标题文档

    2. 设置并使用断点

     调试器启动后，单击"Break"按钮，将弹出如图5.8所示的"Set Breakpoints"对话框，用来设置断点。

    在对话框中，"Functions"组合框用来选定安装脚本程序中可能出现的函数作为断点的位置，单击[Set]按钮将此函数所在的位置设置为断点。单击[Clear]将在"Current Breakpoints"列表中选定的断点删除，单击[Clear All]按钮将所有的断点清除，单击[Close]按钮关闭"Set Breakpoints"对话框。

    实际上，断点也可通过鼠标来设置，这种方法最为直观、随意，其操作步骤如下：

      (1) 用鼠标将代码窗口移动至用户满意的地方，并使代码窗口变大些。
      (2) 此时的代码窗口就像是一个编辑器窗口。通过代码窗口的滚动条，用户可浏览安装脚本程序代码，如图5.9所示。

    (3) 在想要设置断点的地方双击鼠标，此时断点所在的行变成了红色，参见图5.9。
    (4) 若想清除刚才设置的断点，只需将鼠标指针移至断点所在行，然后双击鼠标即可。
    (5) 再将代码窗口移走，使"控制按钮"所在的窗口不被代码窗口所遮挡。

    3. 查看变量

    查看安装脚本程序中的全局和局部变量比较简单容易，只需通过"变量窗口"的上下两组控件进行相关操作即可。由于前面已论述过，这里不再重复。但需要注意的是，内建函数的返回值是通过查看全局变量LAST_RESULT来得到的。
无标题文档

    5.5.3 减少错误产生

    对于一般的代码错误，用户可以通过编译器和调试器就可解决。但是，由于InstallShield集成开发环境在运行过程中还会产生一些代码以外的错误，因此下面的一些建议值得参考。

    1. 使用系统的磁盘扫描程序

     Windows系统中的"磁盘扫描程序"不仅可以检查硬盘的逻辑和物理错误，而且还可以修复已损坏的区域。为了保证InstallShield运行稳定性，最后在安装或启动InstallShield前先进行磁盘扫描。在Windows 98中，启动"磁盘扫描程序"是通过选择"开始"->"程序"->"附件"->"系统工具"->"磁盘扫描程序"菜单命令而进行的。

    2. 关闭反病毒程序

    一个反病毒程序往往对用户的程序安装有一定的影响，大多数反病毒程序通常会不让程序向硬盘根扇区写数据以及保护某些EXE、DLL或COM文件不被修改。由于反病毒程序不能判断哪些修改是有用的以及哪些修改是有害的，因而在安装程序前最好关闭反病毒程序。

    关闭时，可采用下列步骤：

    (1) 若只有病毒监控程序，则只要关闭它即可进行程序安装。但若反病毒程序是驻留在内存中，则需要按下面步骤进行。
    (2) 用文件编辑器打开根目录下的"Autoexec.bat"。
    (3) 将所有与反病毒程序相关的命令行前面加入"REM "。
    (4) 保存Autoexec.bat。
    (5) 重复前面的操作，将Config.sys文件作同样修改。
    (6) 重新启动计算机。

    3. 错误分析

    下面几个措施可帮助用户查找错误产生的根源：

    (1) InstallShield是否只安装过一次？若是，再重新安装一次，如果问题仍然存在，有条件的话，再在其他机器中进行多次安装。若问题仍然存在，可排除是InstallShield开发环境产生错误的可能性。
    (2) 问题是否和发布的媒介有关？若是，试试在3.5"软盘和CD-ROM两个媒介上同时发布安装程序，这样可帮助用户缩小错误产生的范围。
    (3) 只要有可能就使用品牌好的媒介，这样就可排除媒介本身产生错误的可能性。
    (4) 试试在多个操作系统环境中进行调试，若错误只发生在其中某个系统中，只有能找出该系统和其他系统的差别，就可找出错误产生的原因。
    (5) 尽可能将系统占用的资源释放出来，并保证内存足够多，这样可排除由于资源限制所产生的错误的可能性。InstallShield最小需要2MB的内存，若还要调用其他脚本程序以及大有位图文件，则还需要更多的内存。

    事实上，安装的深入话题还有很多，例如网络安装、其他应用程序的安装等。需要说明的是，最新的InstallShield软件包中还含有Express、InstallFromTheWeb、PackageForTheWeb、JavaEdition、ApplicationRepackager等软件，在附录中将对这些内容作简单介绍。


附录A 使用InstallShield系统变量的缺省值
2001-01-29· adding ·yesky
    若InstallShield安装在D盘，Windows系统安装在C盘，测试的安装项目是第一次创建的Blank Setup，则InstallShield系统变量的缺省值如下：

BATCH_INSTALL 0*

COMMONFILES C:\Program Files\Common Files\
ERRORFILENAME --
FOLDER_DESKTOP C:\WINDOWS\Desktop\
FOLDER_PROGRAMS C:\WINDOWS\Start Menu\Programs\
FOLDER_STARTMENU C:\WINDOWS\Start Menu\
FOLDER_STARTUP C:\WINDOWS\Start Menu\Programs\启动\
HINST_INSTALL 0*
INFOFILENAME --
ISRES C:\WINDOWS\TEMP\_ISTMP0.DIR\B3F9AB.DLL*
ISUSER C:\WINDOWS\TEMP\_ISTMP0.DIR\_ISUSER.DLL*
ISVERSION 5.50.136.0
LAST_RESULT 0*
LOGHANDLE 0*
MEDIA DATA
PROGRAMFILES C:\Program Files\
SELECTED_LANGUAGE 9
SRCDIR C:\My Installations\Blank Setup\Media\Default\DiskImages\disk1\
SRCDISK C:
SUPPORTDIR C:\WINDOWS\TEMP\_ISTMP0.DIR\*
TARGETDIR C:\WINDOWS\*
TARGETDISK C:*
UNINST C:\WINDOWS\ISUNINST.EXE
WINDIR C:\WINDOWS\
WINDISK C:
WINSYSDIR C:\WINDOWS\SYSTEM\
WINSYSDISK C:

    注：凡是后面有星号(*)的值在不同的安装项目或安装进程中可能会有不同。


附录B 使用InstallShield for Visual C++6.0

    InstallShield for Microsoft Visual C++6(简称InstallShield的VC版)是随Microsoft Visual Studio 98一起发行的，虽是一个免费版本，但它的功能仍比其他安装工具软件略胜一筹，因为它是InstallShield专业版5.1的子集，具备了InstallShield的基本功能及相同的集成开发环境；而且令人惊喜的是，InstallShield的脚本语言InstallScript使得用户可以像其他高级语言那样灵活地构造出自己的安装脚本程序。但它与相应的InstallShield5.5专业版有许多不同，主要表现在以下几个方面。

    1．开发环境

      (1) VC版不能创建16位Windows系统的安装程序。
      (2) VC版不能创建自释放的EXE安装程序包。
      (3) VC版创建的组件不能超过5个，且不能创建子组件，而专业版不受限制。
      (4) VC版没有组件向导。
      (5) VC版创建的文件组不能超过8个，而专业版不受限制。
      (6) VC版使用的文件压缩方式同InstallShield3.0相同。
      (7) VC版不允许定义DLL函数。
      (8) VC版在创建的安装程序中，启动消息对话框不能更改也不能隐藏。
      (9) VC版不会报告媒介创建的结果。
      (10) VC版不能更改用户对话框左侧的位图。

    2．InstallScrip功能

    nstallShield的VC版的脚本语言在函数功能上有一些限制，如表1所示。
无标题文档

    3. 使用InstallShield的VC版

     InstallShield的VC版的开发环境和InstallShield专业版5.1是一样的，这里不再重复。由于该InstallShield是专门为Microsoft Visual C++6定制的，所以从Visual C++6开发环境中直接运行InstallShield更快捷方便。

    当用户用Visual C++6调试好应用程序后并编译成Release版的EXE文件，就可选择"Tools"菜单中"InstallShield Wizard"命令，执行下列过程(以Visual C++单文档应用程序MySDI为例)：

    (1) 首先，出现如图1所示的"Welcome"对话框，要求用户选定一个Visual C++6.0的应用程序项目(以.dsw为扩展名)。单击[Browse...]按钮可在磁盘中进行查找。

    (2) 单击[下一步]按钮，出现如图2所示的"Application Information"对话框，要求用户输入应用程序名称、公司名称、应用程序的类型、版本号以及应用程序的可执行文件。单击Browse按钮(有"..."符号的按钮)可将磁盘中已有的应用程序的可执行文件名调入。
无标题文档

    (3) 单击[下一步]按钮，出现如图3所示的"Summary"对话框，显示该安装项目中的文件及文件组信息。

    (4) 单击[完成]按钮，安装项目MySDI就创建好了，并自动启动InstallShield for Microsoft Visual C++6 。
    (5) 利用InstallShield开发环境进行更深的安装项目的操作。

    需要说明的是，由于InstallShield for Microsoft Visual C++6正确安装后，会自动在Visual C++6.0(必须先安装)的"Tool"菜单下添加一个名为"InstallShield Wizard"菜单命令，该命令是用来为一个Visual C++6.0的应用程序创建安装项目的。若InstallShield安装前没有安装Visual C++6.0，则当IntallShield安装后，进行下列设置以便能在Visual C++6.0的"Tool"菜单中使用"InstallShield Wizard"菜单命令：

    (1) Visual C++6.0正确安装后，启动Visual C++6.0，并选择"Tools"菜单->"Customize"命令。
    (2) 在弹出的"Customize"对话框中，切换到"Tools"页面，如图4所示。

    (3) 将菜单列表项滚动到最后一个空行，并双击鼠标，键入"&InstallShield Wizard"，并按Enter键。
    (4) 选定刚才键入的菜单列表项，单击Browse按钮(有"..."符号的按钮)将InstallShield所在的Program文件夹下的IsVcWiz.exe调入。
    (5) 将"Initial directory"的路径设为IsVcWiz.exe所在的路径。


附录C 最新InstallShield产品简介

    在InstallShield 5.5产品系列中，除了本身的InstallShield专业版外，还有express(简明版)、JavaEdition(用于跨平台的Java安装)、Application Repackager(用于应用程序的包装，它自动检测与应用程序相关的DLL等内容)、Packagefortheweb(用于将安装媒介打包成一个自运行文件)以及Installfromtheweb(创建用于WEB的安装项目)等。

    当然，InstallShield产品目前已发展到跨平台、支持更多操作系统的程度，其中InstallShield Professional - Standard Edition 6.2 和InstallShield Professional - Windows Installer Edition 2.0 就是两个功能强大、界面新颖的最新产品。

    Standard Edition 6.2是从InstallShield专业版5.5发展而来，它在原有的基础上增加许多功能，尤其是在Internet安装上更是如此，这主要体现在：

    (1) 耳目一新的"Project Wizard"使用得开发者更快捷地创建最新应用程序的安装项目，其功能非常强大，如图5所示。
无标题文档

    (2) "One-Click Install"解决方案，它使得用户只需在网页上简单地点击一下，就可进行安装，其下载和安装是同时进行的；安装过程中显示出安装进度、当前下载速度以及所需要的时间，如图6所示。

    (3) 在原有的安装模板上增加了新多第三方的技术，并用"Object Gallery"(对象库)的形式来管理，如图7所示。

    Windows Installer Edition在安装技术上是最新的，它试图让用户接受最新的Windows安装程序标准，类似于Office 2000的安装，它的高效、易用以及可控性已经赢来不少客户。最新的特性除了支持Windows 2000外，还体现在：
无标题文档

    (1) 动态调试功能可以使用户创建的安装程序错误减少到最小，并检测是否满足最新的Windows安装标准，如图8所示。

    (2) 为用户提供更多、更新的内建模块，提高用户开发效率，它同时也支持Access, MDAC和DCOM等，如图9所示。