大家一定有过这样的经历,每当我们殚精竭虑为用户开发完一个系统之后往往都是派一名技术员到用户那里进行应用软件的安装,数据库的配置,这些看似简单的配置对一般用户来说可不是件容易做的事。这不仅给那些喜欢力求完美的程序设计师带来一点遗憾外,而且也使我们设计出来的软件缺少一种安全可靠感。如果我们在交给用户软件的时候能给他们一个象一些专业软件的安装程序,这不仅给用户留下了一个良好的第一印象,同时也节省了开发维护成本。 |
要说起制作安装程序的软件有许多,像InstallShield,CreateInstall,Easy Install,EasySetup,SetupBuilder等等,可以举出一大堆来。但其中最为著名的当属InstallShield。目前世界上绝大多数的商业软件的安装盘都是用她来完成,比如我们可能天天在使用的Norton AntiVirus 2000。 |
InstallShield之所以很流行也许是因为程序员可以在通过编写脚本语言方便的进行Windows应用程序的注册表修改、ODBC配置等复杂的操作,以及她本身提供了一些相当智能化的功能。下面,就本人在实际开发中的一些所得和大家一起探讨,文中如有任何不妥之处还请同行们给予指教。 |
在讲述到数据库配置的一系列问题是以目前最常用的ODBC接口为基础,文中用到的实例是在WIN98系统中,InstallShield用的是Professional 6.21下开发和调试的,其中的应用程序实例用的PowerBuilder 7.0下开发的演示程序。 |
第一步:快速体验 |
首先,我们可以来快速体验一下用InstallShield制作的安装程序是什么样的。 |
当然,我不可能详细到怎样建立一个项目之类的问题,有关这方面的内容还请看InstallShield的入门教材。 |
第二步:;InstallShield初探 |
首先,用Project Wizard建立一个新的项目,取名Demo,同时在对话框中选择所有的选项,并且在语言选择中选择Chinese(PRC)和English,即简体中文和英文支持。当然你也可以选择其它语言,为了便于讨论我只选择了Chinese(PRC)和English。 |
运行一下试一试,一切都正常,只是一个空壳罢了,没有任何东西。 |
好了,战斗的号角已经吹响了,让我们集中精力个个歼灭。 |
一、 语言选择 |
由于我们在生成项目的同时在语言选择中选择了Chinese(PRC)和English,因此,在图01中的下拉列表框中会自动出现中文,英文的选项。这一步由模板自动完成,我们不要做任何事情。 |
二、 设置启动画面 |
在我们生成项目的同时,InstallShield会为我们缺省的放上一张启动画面。见图16。 |
从该图片上我们可以看出,一般情况下是放置一张能很好的代表你公司的图片,同时上面很清楚地标明你的应用程序的名称,版本,公司名称,版权归属等基本信息。毫无疑问,我们必须用我们自己的图片来替换它。替换的方法是,在Setup File面板中,打开Splast Screen,找到Language Independent,这时我们会发现在它右边的列表中有一个文件Setup.bmp,它就是图18的那一张,我们必须首先把它删除,然后再插入我们想要的那一张,在列表中任何一个位置点击鼠标右键,选择Insert Files,用一个BMP文件替换它就行了。注意,我们替换的图片的文件名必须是Setup.bmp。 |
另外,顺便提一下,在Splash Screen下除了Language Independent分支外,还有Chinese(PRC)和English分支,如果在它们中放置不同的Setup.bmp文件,那么系统会根据用户在安装前选择了不同的语言而启动不同的背景,这样可以有效地避免多国语言的烦恼。同样,在设置法律声明(许可协议)和自述文件的时候也是一样的。 |
三、 开始安装 |
这一步由系统自动给出,我们不要做任何事情。但是我们可以在这里进行软件安装前的准备工作。比如,操作系统判断,机器可用内存容量的检测等,这些往往被忽视。 |
当我们用项目向导生成的新项目时,InstallShield只为我们生成两个事件,分别是OnFirstUIBefore和OnMoving,它们的意义是, |
OnFirstUIBefore:在应用程序第一次安装时为了搜集用户信息而弹出的对话框之前,应用程序所响应的事件。 |
OnMoving:该事件在当所选择的安装组件正在安装或反安装时响应。 |
显然,准备工作可以在事件OnFirstUIBefore中完成,但InstallShield同时还为我们提供了事件OnBegin,该事件在是在Setup脚本中第一个被触发。还记得图片02,上面有个进度条,显示了“正准备InstallShield(R)向导...”,当该进度条结束后就首先调用OnBegin,然后才是OnFirstUIBefore。因此,我们可以将一些前期的准备工作放到这里来完成。下面是如何具体实现。 |
1、首先,如何添加OnBegin事件? |
InstallShield将事件分成三类:全局事件,组件事件和杂项事件。其中,全局事件的添加最为方便,只要将当前行光标移到Serup脚本的最后,然后写上 |
function OnBegin() |
begin |
... // 函数体 |
end; |
就行了。 |
2、实现操作系统的识别 |
像其它编程工具一样,InstallShield也为我们提供了一些系统变量,利用这些变量我们可以轻而易举的得到一些像系统目录,语言代码,CUP类型等实用数据。 |
具体实现代码如下: |
/*****************程序代码*******************/ |
Function OnBegin() |
Begin |
if ( !SYSINFO.bIntel) then |
szMsg = "提示:该软件只能运行在Intel系列的处理器上!\n\n安装程序将终止"; |
MessageBox(szMsg, SEVERE); |
abort; |
endif; |
if (SYSINFO.WIN9X.bWinMe || |
SYSINFO.WINNT.bWinNT || |
SYSINFO.WINNT.bWinNT4 || |
SYSINFO.WINNT.bWin2000) then |
szMsg = "提示:该软件只能运行在WIN9X系统上才能确保程序的正常工作!\n\n是否继续安装?"; |
if ( !AskYesNo (szMsg, NO)) then |
abort; |
endif; |
endif; |
end |
/******************结束*********************/ |
相关变量:SYSINFO |
存放本机的一些系统变量,如操作系统,CUP类型等。有关系统变量的具体使用请参阅编程手册。 |
相关函数:AskYesNo |
该函数弹出一消息窗口,用户通过按是或非来回答该窗口显示的问题。 |
3、实现内存容量的检测 |
在查阅了InstallShield系统变量后并没有找到有关内存容量方面的变量,因此只有通过调用函数来实现。 |
相关函数:GetSystemInfo() |
该函数返回有关目标系统的诸如CUP类型,磁盘容量,当前日期,当前操作系统,内存容量等信息,返回的内存容量是以千字节为单位。有关函数的具体使用请参阅函数手册。 |
具体实现代码如下: |
/*****************程序代码*******************/ |
if (GetSystemInfo (EXTENDEDMEMORY, nvFreeMem, svResult) < 0) then |
MessageBox ("内存检测失败,安装程序将终止!.", SEVERE); |
abort; |
endif; |
if ( nvFreeMem < 16384 ) then |
szMsg = "该软件只能安装在16M以上内存的机器中。\n很遗憾,本机器可用内存不足16M!\n\n安装程序将终止!"; |
MessageBox (szMsg, SEVERE); |
abort; |
endif; |
// end. |
/******************结束*********************/ |
四、 许可协议 |
选择Setup File面板,打开Language Independent,选择Operating System Independent ,在右边列表中找到License.txt文件,双击它输入自己的许可协议就行了。 |
五、 Readme文件 |
同上,找到Infolist.txt文件,双击它输入需要说明的自述文件。 |
六、客户信息 |
看一看图片,不难发现当安装程序运行这一步时上面已经填上了缺省的用户名和客户名称,甚至连序列号都有。这是怎么回事呢?原来在前面我们介绍的事件OnFirstUIBefore的开始有一段代码,如下: |
nSetupType = TYPICAL; |
TARGETDIR = PROGRAMFILES ^@COMPANY_NAME ^@PRODUCT_NAME; |
szDir = TARGETDIR; |
SHELL_OBJECT_FOLDER = @FOLDER_NAME; |
svName = ""; |
svCompany = ""; |
svSerial = ""; |
如果我们没有给svName和svCompany赋新值的话缺省的用户名和客户名称就是我们在安装Windows时注册的用户和单位。当然,如果没有给svSerial赋值的话,图片上的序列号就为空。 |
相关系统变量: |
PROGRAMFILES:存放Windows程序文件夹(Program files)的完整路径。不能更改。 |
七、 序列号判断 |
用InstallShield安装模板制作出的安装程序在运行过程如果没有序列号是无法再继续下去的。但如果我们在序列号上不加任何检验的话,InstallShield会默认为任何字符都是有效的而不管它是什么,此时序列号也就毫无意义,除非你想让你的应用程序无限制的发布。因此,还是建议你在安装过程中对序列号进行一下有效性检验。方法很简单,在事件OnFirstUIBefore中找到Dlg_SdRegisterUserEx分支,见下列代码: |
Dlg_SdRegisterUserEx:: |
szMsg = ""; |
szTitle = ""; |
nResult = SdRegisterUserEx( szTitle, szMsg, svName, svCompany, svSerial ); |
if (nResult = BACK) goto Dlg_SdShowInfoList; |
在语句 |
if (nResult = BACK) goto Dlg_SdShowInfoList; |
前添上相应的序列号校验程序 |
//*******增加序列号的检验 |
if ( svSerial != '199721') then |
MessageBox("警告:输入序列号错误,请确认后重输!",SEVERE); |
goto Dlg_SdRegisterUserEx; |
endif; |
//end. |
就这么简单。声明一下,这里我只是给出的最简单的判别模式,较为安全的模式是在判别时对序列号进行必要的加解密转换,有兴趣的朋友可以自己去实现。 |
八、 安装文件夹 |
在一般情况下,InstallShield默认的安装文件夹是存放Windows程序文件夹(Program files)的完整路径,即C:\ Program files(如果Windows安装在C盘的话)。但是,在绝大多数情况下我们都希望客户将应用程序安装在单独的数据盘上,即不与操作系统安装在同一个硬盘分区里。一方面便于管理,另一方面也为了防止我们的程序因为系统盘格式化而受到连累。因此,我们常常会建议用户在安装应用程序的时候选择另一个硬盘,如D盘。 |
另外,我们有时希望我们所开发出来的应用程序安装在一个带有自己公司名称和应用程序名称的目录下。InstallShield默认的目录就做的非常好,她会建立一个诸如“C:\Program Files\ABCD软件工作室\Demo”的目录(在这里我虚构了一个公司“ABCD软件工作室”)。但,问题是,一旦让用户选择了其它的路径,或者硬盘,他们几乎都不会在目录上加上开发商和应用程序目录,“\ABCD软件工作室\Demo”。如果用户仅仅选择了磁盘而忘了选择任何目录的话,InstallShield就会将所有的程序和数据文件复制到磁盘根目录下,用户和我们当然都不希望这样。因此,为了避免这一切的发生我们只能寄希望于我们的安装程序能够有足够的智能来应付这一切,这样就不管用户是否选择了目录。实现方法如下。 |
在事件OnFirstUIBefore中找到Dlg_SdAskDestPath分支,如下 |
Dlg_SdAskDestPath: |
szTitle = ""; |
szMsg = ""; |
nResult = SdAskDestPath( szTitle, szMsg, szDir, 0 ); |
TARGETDIR = szDir; |
if (nResult = BACK) goto Dlg_SdRegisterUserEx; |
我们首先需要定义一个临时字符串变量svTemp |
STRING svTemp; |
然后,将语句 |
nResult = SdAskDestPath( szTitle, szMsg, szDir, 0 ); |
TARGETDIR = szDir; |
修改成如下: |
svTemp = szDir; |
nResult = SdAskDestPath( szTitle, szMsg, szDir, 0 ); |
if ( svTemp != szDir) then |
szDir = szDir^@COMPANY_NAME ^@PRODUCT_NAME; |
endif; |
TARGETDIR = szDir; |
该语句的功能是在用户选择的路径后加上单位和应用程序名称作为安装的目录(注:这里我并没有考虑到用户在选择了默认外的路径后加上单位和应用程序名称作为安装目录的情况)。其中,变量COMPANY_NAME 和PRODUCT_NAME中存放的是单位名和产品名,我们可以随时在资源面板中修改它的值。 |
变量TARGETDIR是指向在硬盘上目的文件夹的完整路径。当调用函数SdAskDestPath时,该函数会创建一个对话框来让用户选择应用程序要存放的目的路径。 |
不要以为加上了单位和程序的目录就万事大吉了,可别忘了既然让用户来选择安装的目的盘就很有可能出现磁盘空间不够大,从而导致程序的安装失败或安装后的剩余空间不够程序将来数据的存放等等。因此,必须在用户选择之后,程序进入下一步之前对目的盘的可用空间进行判断。在这里我假设我们的程序需要10兆的空间来存放目前的文件和未来几年可能的数据。 |
为了达到上述目的就必须知道: |
1、 用户最终选择的是哪个硬盘 |
2、 该硬盘当前的剩余空间是多少 |
那么,如何来实现呢?幸好InstallShield为我们提供了足够多的函数来应付这一切。 |
函数一:GetDisk |
该函数从指定的路径或带有路径的文件名当中提取该路径或文件所在的磁盘驱动器号。调用成功返回0,否则返回小于0的任何数。 |
函数二:GetDiskSpace |
该函数返回指定磁盘或指定路径所在磁盘的剩余空间,用字节表示。 |
具体实现代码如下: |
//*******加入磁盘剩余空间判断,目前判断剩余空间是否少于10M |
//******* 10M * 1024千字节 * 1024字节 = 10485760 字节 |
GetDisk (szDir, svDrive); |
lFreeSpace = GetDiskSpace (svDrive); |
if (lFreeSpace < 10485760) then |
MessageBox("警告:安装路径所在的磁盘空间不足10M!请重新选择!",SEVERE); |
goto Dlg_SdAskDestPath; |
endif; |
// end. |
我们只要将这段代码放入语句 |
if (nResult = BACK) goto Dlg_SdRegisterUserEx; |
之前就行了。 |
九、 安装类型 |
该步骤是由InstallShield自动给出的,一般情况下我们不必做任何事。三个安装选项:典型安装、压缩安装和自定义安装是根据我们在安装类型(Setup Type)的面板中预先设置好的值来进行的。如果用户选择了自定义安装,系统就会弹出如下界面,见图。 |
这是由系统自动给出的,我们用不着写一行代码,这是不是很棒? |
不知道你有没有注意到窗口的左面有一个说明框,它是用来提示用户目前所选择的组件是干什么的。在缺省的状态下它是空的,需要我们来添加。添加的方法是在组件(Components)的面板中,每当我们点中一个组件在左边的列表中就会有一个描述栏(Description),它的内容就是上面提到的帮助信息,你只要将相对用户说的话写上去就行了。 |
十、 程序文件夹 |
这一步由InstallShield自动给出,一般不要做任何修改。 |
十一、 最后确认 |
我们一定还记得每当安装一个像Office等专业化软件时,在软件正式安装前系统都会给出一个列表框,其中列出了你所做的一切选择,包括软件的安装路径,需要安装的组件等,同时还包括了注册的用户名和单位等信息。在我们用InstallShield制作安装盘的时候也会有这一个列表框,但遗憾的是在缺省状况下InstallShield提供的这个列表框是空的,什么内容也没有,这需要我们给它加入。好在有函数ListAddString来帮我们的忙。 |
相关函数:ListAddString |
该函数在字符串列表框的当前对象前后新增加一个字符串。调用成功返回0,否则返回小于0的任何数。 |
在脚本Setup.rul的事件OnFirstUIBefore中找到Dlg_SdStartCopy分支,如下: |
Dlg_SdStartCopy: |
szTitle = ""; |
szMsg = ""; |
listStartCopy = ListCreate( STRINGLIST ); |
//The following is an example of how to add a string(svName) to a list(listStartCopy). |
//eg. ListAddString(listStartCopy,svName,AFTER); |
nResult = SdStartCopy( szTitle, szMsg, listStartCopy ); |
ListDestroy(listStartCopy); |
if (nResult = BACK) goto Dlg_SdSelectFolder; |
// setup default status |
SetStatusWindow(0, ""); |
Enable(STATUSEX); |
StatusUpdate(ON, 100); |
return 0; |
在语句 |
nResult = SdStartCopy( szTitle, szMsg, listStartCopy ); |
前加上如下代码 |
//*******加入安装过程中用户所选择的主要步骤 |
// 如果不手动加入的话将会什么也不列出 |
ListAddString(listStartCopy,"客户信息:",AFTER); |
ListAddString(listStartCopy,"用户名:" + svName,AFTER); |
ListAddString(listStartCopy,"用户单位:" + svCompany,AFTER); |
ListAddString(listStartCopy,"",AFTER); |
ListAddString(listStartCopy,"程序安装路径:" + szDir,AFTER); |
ListAddString(listStartCopy,"",AFTER); |
ListAddString(listStartCopy,"程序文件夹:" + szfolder,AFTER); |
ListAddString(listStartCopy,"",AFTER); |
switch (nSetupType) |
case TYPICAL : ListAddString(listStartCopy,"安装类型:典型安装",AFTER); |
case COMPACT: ListAddString(listStartCopy,"安装类型:压缩安装",AFTER); |
case CUSTOM: ListAddString(listStartCopy,"安装类型:自定义安装",AFTER); |
endswitch; |
//end. |
其中svName、svCompany、szDir等变量我们可以在事件OnFirstUIBefore开始的变量定义中找到。从变量的名称我们很清楚的知道该变量存放的是用户名,单位,安装目录等。 |
十二、 第一幅背景,第二幅背景――图片12,图片13 |
细心的朋友一定会注意到软件在安装过程中会出现两副不同的背景图片,图12和图13,这就是InstallShield的显示区界面,我们可以称它为布告板。这看上去是不是很像在播放幻灯片?呵呵,我们可以和微软的比一比了(说句笑话)。言归正传,还是来说说是如何实现的吧。 |
布告板,它只有在文件被传输时才被激活。也就是说,当你调用ComponentTransferData函数来解压并拷贝时布告板才被显示,该函数是有系统自动调用,用不着我们来操心。 |
但是,你不能为一个布告板指定显示时间,InstallShield会根据整个程序的安装时间(指文件拷贝时间)自动的为每一个文件平均分配,但至少是2秒。如果你的安装程序仅持续20秒,而你却放置了25副图片,很显然系统只会显示前10副图片。 |
当你需要显示布告板之前还必须确保以下两个条件: |
1、 在你的脚本里,首先要确保在文件被传输前调用Enable(BACKGROUND) 和 Enable(FULLWINDOWMODE),我们可以加在事件OnMoving中。形式如下: |
function OnMoving() |
STRING szAppPath; |
begin |
Enable(BACKGROUND); |
Enable(FULLWINDOWMODE); |
Enable(STATUSDLG); |
PlaceWindow(STATUSDLG, 400, 10, LOWER_LEFT); |
szAppPath = TARGETDIR; |
RegDBSetItem(REGDB_APPPATH, szAppPath); |
RegDBSetItem(REGDB_APPPATH_DEFAULT, szAppPath ^ @PRODUCT_KEY); |
end; |
相关函数: |
Enable(BACKGROUND) :显示安装主背景窗口 |
Enable(FULLWINDOWMODE):设置主背景窗口为最大化。 |
2、 在设置文件(Setup Files)面板中,在合适的目标语言和平台下放置具有特殊后缀名的布告板文件。默认的命名是:“Bbrd”,然后再跟一个数字,最后再加上后缀“.bmp”或“.wmf”。例如,我们现在要加三副BMP图片,则这三副图片的名称分别应该是Bbrd1.bmp、Bbrd2.bmp、Bbrd3.bmp。 |
好了,运行一下,很不错。我们可以利用这项技术在安装过程中播放类似幻灯片效果,就像安装Windows一样。 |
十三、 复制完成 |
这一步由InstallShield自动给出。 |
十四、 重新启动 |
在很多情况下,当我们修改了注册表或是在系统中安装了其它驱动程序的时候,为了确保应用程序能够顺利运行,我们还是希望用户在安装结束之后能够重新启动一下系统。这时函数SdFinishReboot就显得十分有用。 |
相关函数:SdFinishReboot |
该函数显示一个重新启动的对话框,它提供给用户两种选择:系统自动启动和用户自己启动。当函数返回WILL_REBOOT表明用户选择了重新启动;当返回NEXT(或1)表明用户选择了自己启动;当返回小于0表明当用户选择了重新启动,但重新启动失败。在缺省的情况下是用户自己启动。 |
一般情况下,我们是在事件OnEnd中调用SdFinishReboot函数。OnEnd顾名思义是在Setup脚本里最后被执行的事件。程序代码如下: |
/*****************重启动代码*******************/ |
function OnEnd() |
STRING szTitle, szMsg1, szMsg2; |
NUMBER nOption, nReserved; |
begin |
szTitle = "已经完成全部的拷贝工作,"; |
szMsg1 = "在使用本软件前必须重新启动。"; |
nOption = 0; |
szMsg2 = '请选择一种启动方式,然后单击"确定"完成安装。'; |
nReserved = 0; |
if (SdFinishReboot (szTitle, szMsg1, nOption, szMsg2, nReserved) < 0) then |
MessageBox ("重启动失败,请重新启动系统后再使用本软件!", SEVERE); |
endif; |
end; |
/**************** 结束 *******************/ |
其中,szTitle、szMsg1、szMsg2不设置也罢,只是系统会以缺省的方式出现。 |
第三步:深度探索 |
如果说我们完成了图1到图15全部功能后就可以马上动手制作安装盘的话那我们所做的也只是给应用程序加了一个花边,并没有实际功效。它只是把原先由人工的复制工作交给系统自动去执行,更说不上文章开头所说的实现系统的自动配置。我们还是得在应用程序发行时去用户现场安装、配置一些接口程序。 |
大家都知道,我们开发的程序有百分之九十以上是和数据库打交道。目前,数据库种类的繁多,各软件厂商又各自推出自己的数据库接口程序,这就使得我们要确保开发出来的应用程序最终能够安全顺利的运行就必须携带上自己的数据库接口程序,虽然有一些接口标准,但这远远不够。因此,如何将这些工作交给安装程序去完成就成为能否制作出具有专业水准的安装程序的关键,也是为什么我会把它作为一个标题单独分开。 |
那么如何在制作安装程序的时候实现数据库驱动程序的安装和配置呢?要实现它我们还是先从注册表和ODBC两个基本概念入手,下面进行分别讨论。 |
一、注册表 |
在早期的Windows系列应用程序中用后缀为.ini的文件保存Windows系统和其他该应用程序的配置信息。后来,Windows95的出现,系统就用一个称做注册表的来代替保存应用程序配置信息的.ini文件,尽管Windows95仍然支持使用.ini文件。用注册表来保留这些信息可以为我们提供一个配置信息的单一源,这不同于那些分布在磁盘里的各种.ini文件的杂烩。同时,注册表也提供了一个简单机制来保存当前用户或当前配置的信息。注册表甚至允许你通过网络管理其他机器的注册表。另外,在NT下,注册表能提供安全级别,这在先前的配置存储机制中是没有的。因此,使得能否合理的使用注册表成为WIN32应用程序的特征之一。 |
注册表使用的是树型体系结构,树中的每个结点称键。每个键也可以包含其他的键或子键。它允许进一步的分支,也即为值,它用来存储有效的数据。在注册表中,注册表用键来组织数据,一个键中的值用它们的名来识别,键名由可以打印的ASCII字符组成的简单字符串。另外,以英文句号(.)开始的键名被系统所保留。键中的值可以用不同的数据来表示,可以是从一个简单的整数到用户定义的二进制对象。在注册表中只有四个基本键会受到应用程序安装的影响,这四个键分别是HKEY_CLASSES_ROOT、HKEY_CURRENT_USER、HKEY_LOCAL_MACHINE和HKEY_USERS。而其中对我们配置ODBC有用的只有HKEY_LOCAL_MACHINE和HKEY_CURRENT_USER。 |
1、 HKEY_LOCAL_MACHINE |
在HKEY_LOCAL_MACHINE中保存的是关于本地机器的配置信息,独立于任何特殊的用户都存储在其中。此配置信息又进一步分成几个不同的子键,它们是Config,Enum,Hardware,Network,Security,Software和System。 |
其中,Software子键用于包含用户应用程序的配置信息,这是最有可能被我们的应用程序使用的地方。由于我们的应用程序所要的信息一般来说对其他程序并不需要,同时也为了更好的管理这些信息,因此我们应该在HKEY_LOCAL_MACHINE\SOFTWARE下增加一个子键,键名当然是能代表我们的应用程序。 |
2、 HKEY_CURRENT_USER |
键HKEY_CURRENT_USER通常为我们的应用程序存储和恢复特殊用户相应的特殊配置提供了方便。同样,在HKEY_CURRENT_USER中也存在一些子键,最常用的键是Software,打开Software键我们会发现有一个子键ODBC,顾名思义里面存放的当前机器上的ODBC.INI文件的内容, |
好了,有关注册表的一些知识就简单的说到这里,在开始开始配置我们的ODBC之前,为了便于读者能够顺利的读懂本文所附带的程序代码,现将有关InstallShell注册表函数作一个简单的介绍。 |
1、 RegDBKyeExist |
该函数用来查看某个指定的键是否在注册表中。 |
2、 RegDBSetDefaultRoot |
该函数为其他的注册表函数设置一个不同的根键。大多数InstallShield注册表函数工作在缺省的以键HKEY_CLASSES_ROOT为根的注册键树上,用这个函数你就可以指定一个特殊的键为根,像键HKEY_LOCAL_MACHINE或 HKEY_CURRENT_USER或HKEY_USERS。 |
3、 RegDBSetKeyValueEx |
该函数为某个在注册表中的键设置键值。如果该键不存在,该函数将为你创建该键。但是,新创建的键不会被安装软件卸载程序所登记,除非它是一个已登记过的键的子键。要想让安装软件卸载程序所登记可以先用RegDBCreateKeyEx创建然后再设置该键值。 |
4、 RegDBCreateKeyEx |
该函数用来在注册表中创建一个键。当使用该函数来创建一个键或子键时,为了保证能被正确地安装软件卸载程序登记要首先确保它的父键首先已经被成功创建。 |
二、ODBC |
1、ODBC介绍 |
ODBC,即开放式数据库连接,它是由Microsoft公司提供的应用程序接口(API),一个单独的应用程序通过它可以访问许多个不同类型的数据库及不同格式的文件。 |
虽然继ODBC之后业界已推出了一些像OLE DB,ADO等之类的新技术,但目前,开放式数据库连接API也许是在Windows应用程序中用得最广泛的数据库接口。在每种数据库所用到的专用接口中,除了加入特殊代码外,还需要为ODBC API译码。在ODBC API和用来与数据库交换信息的专用接口之间,特殊的ODBC驱动程序提供了任何一种必需的译码。 |
2、ODBC 4.0驱动程序 |
以下的表列出了 ODBC 桌面数据库驱动程序 4.0 的每一个组件所要求的文件。这些文件安装在 Windows的系统目录\Windows\System或 Windows NT的系统目录\Windows\System32下。如果 ODBC 文件以前被安装在一个不同的目录中,请确定你使用的是在 \Windows\System (或 System32) 目录下的新文件。 |
某些文件是多个部件共同需要的。如果你想要和你的应用程序一起重新分配任一个 ODBC 桌面数据库驱动程序,这些文件也必须被重新分配。建议在分发软件时在你的应用程序目录下建立一个 “\ODBC\”目录,将这些重新拷贝到该目录底下,但一定要确保该路径是个PATH搜索路径。 |
以下的文件对于每一个 ODBC 桌面数据库驱动程序 4.0 是共同的: |
Ds16gt.dll |
Ds32gt.dll |
Expsrv.dll |
Msjint40.dll |
Msjet40.dll |
Msjter40.dll |
Msjtes40.dll |
Msvcrt40.dll |
Odbc16gt.dll |
Odbc32.dll |
Odbc32gt.dll |
Odbccp32.cpl |
Odbccp32.dll |
Odbccr32.dll |
Odbcinst.cnt |
Odbcinst.hlp |
Odbcint.dll |
Odbcjet.hlp |
Odbcjet.cnt |
Odbcji32.dll |
Odbcjt32.dll |
Odbcjtnw.hlp |
Odbcjtnw.cnt |
Odbctl32.dll |
Vbajet32.dll |
Odbctrac.dll |
下列文件是安装某一个驱动程序类型的软件所必须的: |
驱动程序 |
文件 |
Microsoft Access |
Msrd2x40.dll,Msrd3x40.dll |
DBASE |
Msxbse40.dll,Oddbse32.dll |
Microsoft Excel |
Msexcl40.dll,Odexl32.dll |
Paradox |
Mspdox40.dll,Odpdx32.dll |
Text |
Mstext40.dll,Odtext32.dll |
Adaptive Server Anywhere |
见下面说明 |
注意: |
1、 ODBC 桌面数据库驱动程序4.0版本至少需要 16 MB 的随机访问内存 (RAM) |
2、 有关与这些驱动程序一起使用的ODBC版本的信息,参考ODBC程序员参考手册。 |
3、Adaptive Server Anywehre ODBC驱动程序 |
下表列出的是工作在Adaptive Server Anywehre环境下的驱动程序,必须将这些文件拷贝到一个独立的目录下,并且使系统能够搜索到。 |
描述 |
32位Windows |
ODBC驱动程序 |
Dbodbc6.dll |
ODBC翻译程序 |
Dbodtr6.dll |
特殊语言库文件 |
Dblgen6.dll |
网络接口文件 |
Dbport6.dll |
连接对话框程序文件 |
Dbcon6.dll |
注意: |
1、 ODBC翻译程序仅仅在你的应用程序是依赖于ANSI标准的字符串转换。 |
2、 网络接口程序库是专门用于网络通信用,它仅仅是在客户端的程序访问网络服务器上才必须存在。 |
3、 连接对话框程序文件在以下的几种情况下才需要加入: |
a. 你的最终用户需要创建自己的数据源 |
b. 当用户连接数据库时需要输入用户标识和口令 |
c. 不管是出于什么目的用户需要显示连接对话框 |
4、配置ODBC驱动程序 |
为了使用ODBC驱动程序安装程序不仅仅只是将这些驱动程序文件拷贝到硬盘上,它还必须在注册表中设置一组ODBC驱动程序的属性。 |
Adaptive Server Anywhere安装程序会自动在NT和WIN9X的系统注册表中标识和配置ODBC驱动程序。因此,如果你为你的最终用户制作应用程序的安装程序时也必须进行相同的设置。 |
通过查看Windows注册表工具,我们发现Adaptive Server Anywhere的ODBC驱动程序是在下列的键中被系统所标识的。 |
HKEY_LOCAL_MACHINE\Software\ODBC\ODBCINST.INI\Adaptive Server Anywhere 6.0,其中需要设置的下键有 |
键名 |
类型 |
键值 |
Driver |
String |
Path\dbodbc6.dll |
Setup |
String |
Path\dbodbc6.dll |
除此之外,你还必须在键HKEY_LOCAL_MACHINE\Software\ODBC\ODBCINST.INI\ODBC Drivers中为Adaptive Server Anywhere注册 |
在该键中增加如下键 |
键名 |
类型 |
键值 |
Adaptive Server Anywhere 6.0 |
String |
Installed |
5、数据源注册 |
除了进行ODBC驱动程序注册外,还必须为用户的数据源进行注册。这是因为每一个用户数据源必须被注册表登记才能被系统所识别,这样用户才能使用它。因此,必须在键HKEY_CURRENT_USER\Software\ODBC\ODBC.INI\中进行如下注册。 |
键名 |
值类型 |
键值 |
AutoStop |
String |
Yes |
DatabaseFile |
String |
Path\demo.db |
Description |
String |
Adaptive Server Anywhere Sample Database |
Driver |
String |
Path\win32\dbodbc6.dll |
PWD |
String |
Sql |
Start |
String |
Path\win32\dbeng6.exe -q |
UID |
String |
Dba |
注: |
1、 上面的path除了DatabaseFile指的是客户数据库所在的路径,其他的都是指Adaptive Server Anywhere的安装路径。我们在给客户分发应用软件的时候一般不再另外安装Adaptive Server Anywhere,所以可以将Adaptive Server Anywhere路径下的文件Dbeng6.exe,Dbodbc6.dll,Dblgon6.dll,Dbport6.dll,Dbcon6.dll,Dbodtr6.dll一起分发给客户,并且拷贝至一个单独目录下,然后将路径指向它就可以了。 |
2、 在dbeng6.exe后加上“-q”是为了隐藏任务栏上的SQLAnywhere窗口。 |
另外,必须将数据源加到注册表中的数据源列表中,加入到如下键中 |
HKEY_CURRENT_USER\Software\ODBC\ODBC.INI\ODBC Data Sources\ |
该键中存放的是每一个数据源和ODBC驱动程序的关联。键名是指数据源名字;键值是指ODBC驱动程序的名字。由于本文所叙述的是基于PowerBuilder7.0开发环境下的应用程序的发布,所以键值都是"Adaptive Server Anywhere 6.0"(因为,当我们安装PowerBuilder 7.0时,系统默认的ODBC驱动程序的名字为Adaptive Server Anywhere 6.0),当然,如果你愿意话也可以改变它。 |
这里有必要提示一下,由于用户数据源的配置可能包含一些敏感的数据库设置,比如说连接数据库的用户ID和口令。这些设置都会以无格式的文本形式存储在注册表中,可以很容易的被Windows注册表编辑程序regedit.exe或regedt32.exe查看,只要有点编程经验的人都能通过获这些实用工具取该ID和口令来修改数据库中数据。因此,你在处理这些问题的时候还要三思一下,你可以选择加密口令或让用户在连接数据库时录入。 |
既然我们知道了如何在注册表中为应用程序进行ODBC配置,那么剩下的问题是,把他们放在什么地方?不用说,大家也猜到了,最好的地方就是当所有文件都已经复制完毕,在我们要求用户重新启动系统之前。事件OnMoved可以做到这一切。在做这些事的同时我们最好能够显示给用户一个消息框,告诉用户我们在干什么,做完这一切之后再关闭它。这就要用到函数SdShowMsg和Delay。见图。 |
|
相关事件:OnMoved |
该事件是在当所有在目标机器上的组件都被安装或反安装时响应,在该事件中的代码总是会被执行。 |
相关函数: |
SdShowMsg:该函数打开或关闭一个非模态的小窗口,该窗口显示指定的消息。 |
Delay:该函数可以用指定的时间(秒)来使安装程序的执行时间延迟。 |
下面是完整的ODBC注册表配置的代码程序。 |
/*********************程序代码********************/ |
//*******当所有数据拷贝完毕后在这里配置ODBC |
function OnMoved() |
STRING szMsg; |
STRING svDB; |
STRING svASAOdbcDll; |
STRING svASA; |
STRING szKey; |
STRING szClass; |
STRING svResult; |
STRING szNumName, szNumValue, svNumValue, szTitle; |
NUMBER nType, nSize, nvType, nvSize; |
begin |
szMsg = "正在进行系统配置,请等待..."; |
SdShowMsg (szMsg, TRUE); |
svDB = TARGETDIR + "\\db\\Demo.db"; |
svASAOdbcDll = TARGETDIR + "\\ASA\\dbodbc6.dll"; |
svASA = TARGETDIR + "\\ASA\\dbeng6.exe"; |
RegDBSetDefaultRoot (HKEY_LOCAL_MACHINE); |
// 建立MyDemo ASA主键 |
szKey = "Software\\ODBC\\ODBCINST.INI\\MyDemo ASA"; |
szClass = ""; |
if (RegDBCreateKeyEx(szKey, szClass) < 0) then |
MessageBox ("注册表操作失败,安装程序将终止!", SEVERE); |
abort; |
endif; |
// 建立MyDemo ASA下的键值 |
szNumName = "Driver"; |
szNumValue = svASAOdbcDll; |
nType = REGDB_STRING; |
nSize = -1; |
if (RegDBSetKeyValueEx (szKey, szNumName, nType, szNumValue, |
nSize) < 0) then |
MessageBox ("注册表操作失败,安装程序将终止!", SEVERE); |
abort; |
endif; |
szNumName = "Setup"; |
if (RegDBSetKeyValueEx (szKey, szNumName, nType, szNumValue, |
nSize) < 0) then |
MessageBox ("注册表操作失败,安装程序将终止!", SEVERE); |
abort; |
endif; |
szKey = "Software\\ODBC\\ODBCINST.INI\\ODBC Drivers"; |
szNumName = "MyDemo ASA"; |
szNumValue = "Installed"; |
if (RegDBSetKeyValueEx (szKey, szNumName, nType, szNumValue, |
nSize) < 0) then |
MessageBox ("注册表操作失败,安装程序将终止!", SEVERE); |
abort; |
endif; |
//*************配置ODBC |
RegDBSetDefaultRoot (HKEY_CURRENT_USER); |
// 建立Mydemo |
szKey = "Software\\ODBC\\ODBC.INI\\MyDemo"; |
if (RegDBCreateKeyEx(szKey, szClass) < 0) then |
MessageBox ("注册表操作失败,安装程序将终止!", SEVERE); |
abort; |
endif; |
szNumName = "AutoStop"; |
szNumValue = "Yes"; |
nType = REGDB_STRING; |
nSize = -1; |
if (RegDBSetKeyValueEx (szKey, szNumName, nType, szNumValue, |
nSize) < 0) then |
MessageBox ("注册表操作失败,安装程序将终止!", SEVERE); |
abort; |
endif; |
szNumName = "DatabaseFile"; |
szNumValue = svDB; |
if (RegDBSetKeyValueEx (szKey, szNumName, nType, szNumValue, |
nSize) < 0) then |
MessageBox ("注册表操作失败,安装程序将终止!", SEVERE); |
abort; |
endif; |
szNumName = "Description"; |
szNumValue = "My Paper's Sample"; |
if (RegDBSetKeyValueEx (szKey, szNumName, nType, szNumValue, |
nSize) < 0) then |
MessageBox ("注册表操作失败,安装程序将终止!", SEVERE); |
abort; |
endif; |
szNumName = "Driver"; |
szNumValue = svASAOdbcDll; |
if (RegDBSetKeyValueEx (szKey, szNumName, nType, szNumValue, |
nSize) < 0) then |
MessageBox ("注册表操作失败,安装程序将终止!", SEVERE); |
abort; |
endif; |
szNumName = "PWD"; |
szNumValue = "SQL"; |
if (RegDBSetKeyValueEx (szKey, szNumName, nType, szNumValue, |
nSize) < 0) then |
MessageBox ("注册表操作失败,安装程序将终止!", SEVERE); |
abort; |
endif; |
szNumName = "Start"; |
szNumValue = svASA + " -d -c8m"; |
if (RegDBSetKeyValueEx (szKey, szNumName, nType, szNumValue, |
nSize) < 0) then |
MessageBox ("注册表操作失败,安装程序将终止!", SEVERE); |
abort; |
endif; |
szNumName = "UID"; |
szNumValue = "DBA"; |
if (RegDBSetKeyValueEx (szKey, szNumName, nType, szNumValue, |
nSize) < 0) then |
MessageBox ("注册表操作失败,安装程序将终止!", SEVERE); |
abort; |
endif; |
//***************注册数据源 |
szKey = "Software\\ODBC\\ODBC.INI\\ODBC Data Sources"; |
szNumName = "MyDemo"; |
szNumValue = "MyDemo ASA"; |
if (RegDBSetKeyValueEx (szKey, szNumName, nType, szNumValue, |
nSize) < 0) then |
MessageBox ("注册表操作失败,安装程序将终止!", SEVERE); |
abort; |
endif; |
// UInstalled -- 反安装后删除新建的键 |
if ( FindFile(TARGETDIR + "\\db\\", "Demo.db", svResult) < 0 ) then |
RegDBSetDefaultRoot (HKEY_LOCAL_MACHINE); |
szKey = "Software\\ODBC\\ODBCINST.INI\\MyDemo ASA"; |
if (RegDBDeleteKey (szKey) < 0) then |
MessageBox ("删除注册表数据失败!", SEVERE); |
endif; |
szKey = "\\Software\\ODBC\\ODBCINST.INI\\ODBC Drivers"; |
if (RegDBDeleteValue (szKey, "MyDemo ASA") < 0) then |
MessageBox ("删除注册表数据失败!", SEVERE); |
endif; |
RegDBSetDefaultRoot (HKEY_CURRENT_USER); |
szKey = "Software\\ODBC\\ODBC.INI\\MyDemo"; |
if (RegDBDeleteKey (szKey) < 0) then |
MessageBox ("删除注册表数据失败!", SEVERE); |
endif; |
szKey = "Software\\ODBC\\ODBC.INI\\ODBC Data Sources"; |
if (RegDBDeleteValue (szKey, "MyDemo") < 0) then |
MessageBox ("删除注册表数据失败!", SEVERE); |
endif; |
endif; |
// UInstalled -- end |
Delay (1); |
SdShowMsg (szMsg, FALSE); |
end; |
/********************* 结束 ********************/ |
在进行完成注册表的添加之后还必须做的最后一件事就是为我们的应用程序指定一个搜索路径,因为我们在安装的时候拷贝了一些DLL库文件。显儿易见这就要求我们在Autoexec.bat中文件添加一个搜索路径。 |
涉及到的函数: |
* FindFile:在指定的路径下查找指定的文件,当函数返回0时表示文件找到,当返回小于0的任何数时表示没有找到。 |
* CreateFile:创建一个新的文件,如果该文件已经存在,那么CreateFile将覆盖掉原先的。同样,在创建之前要先用OpenFileMode设置文件模式。 |
* OpenFileMode:在你想要打开一个已存在的文件或建立一个新文件设置文件的模式。文件的模式根据文件类型的不同会有如下几种: |
文本文件:添加模式,只读模式 |
二进制文件:只读模式,读写模式 |
* OpenFile:打开一个已经存在的文本文件或二进制文件。但打开之前必须先要用OpenFileMode设置文件打开的模式。 |
* WriteLine:该函数在一个以添加模式(append mode)打开或建立的文本文件中写上一行文本。 |
* CloseFile:当你完成用GetLine读文件或用WriteLine写文件的操作后必须用CloseFile函数将使用的文件关闭。 |
* LongPathToQuote:在长文件名上放置或去掉双引号。因为如果是没有加引号的长文件名PAHT命令是不会认的。 |
具体的实现代码如下: |
/**********************程序代码******************/ |
// 在Autoexec.bat中加入搜索路径 |
function OnMoved() |
STRING svResult; |
NUMBER nvFileHandle; |
STRING svDir; |
Begin |
... ... |
OpenFileMode(FILE_MODE_APPEND); |
if (FindFile("c:\\", "autoexec.bat", svResult) < 0) then //没有,需要create一个 |
CreateFile(nvFileHandle, "c:\\","Autoexec.bat" ); |
else |
OpenFile ( nvFileHandle , "C:\\" , "Autoexec.bat" ); |
endif; |
svDir = TARGETDIR + "\\DLL"; // 将路径指向DLL目录 |
LongPathToQuote (svDir , TRUE ); |
WriteLine( nvFileHandle," "); |
WriteLine( nvFileHandle ,"Set PATH=" +svDir +";%PATH%"); |
WriteLine( nvFileHandle," "); |
CloseFile(nvFileHandle); |
end; |
/********************** 结束 ******************/ |
到目前为止,我们已经完成了制作一个应用程序的安装实例所需要的全部工作。剩下的也只是一些锦上添花的事了。让我们最后再看看还有什么要做的?对了,还应该像其他专业软件那样在开始菜单中或者是在桌面上或者是在程序文件夹中添加快捷方式,这样用户在安装完毕之后可以很方便的找到它。在这里我们将会用到函数AddFolderIcon。 |
相关函数:AddFolderIcon |
该函数在指定的程序文件中插入或替换图标。 |
下面是具体的实现代码: |
/******************程序代码******************/ |
function OnMoved() |
STRING szProgramFolder, szItemName, szCommandLine, szWorkingDir, szIconPath; |
STRING szShortCutKey, szProgram, szParam; |
NUMBER nIcon; |
begin |
//SzProgramFolder = FOLDER_STARTMENU; |
// Set up parameters for call to AddFolderIcon. |
szProgram = FOLDER_STARTMENU; |
szParam = TARGETDIR + "\\Demo.exe"; |
LongPathToQuote (szProgram, TRUE); |
LongPathToShortPath (szParam); |
szCommandLine = szParam; |
szWorkingDir = TARGETDIR + "\\DLL"; // 加上应用程序的工作路径 |
szIconPath = ""; |
nIcon = 0; |
szShortCutKey = ""; |
// 在开始菜单中添加快捷方式 |
szProgramFolder = FOLDER_STARTMENU; |
szItemName = "My Demo in StartMenu"; |
if (AddFolderIcon (szProgramFolder, szItemName, szCommandLine, szWorkingDir, |
szIconPath, nIcon, szShortCutKey, REPLACE) < 0) then |
MessageBox ("安装程序在进行开始菜单中添加快捷方式失败,稍侯请自己添加!.", SEVERE); |
endif; |
// 在程序文件夹中添加快捷方式 |
szProgramFolder = FOLDER_PROGRAMS; |
szItemName = "My Demo in Programs"; |
if (AddFolderIcon (szProgramFolder, szItemName, szCommandLine, szWorkingDir, |
szIconPath, nIcon, szShortCutKey, REPLACE) < 0) then |
MessageBox ("安装程序在进行程序文件中添加快捷方式失败,稍侯请自己添加!.", SEVERE); |
endif; |
// 在桌面上添加快捷方式 |
szProgramFolder = FOLDER_DESKTOP; |
szItemName = "My Demo in Desktop"; |
if (AddFolderIcon (szProgramFolder, szItemName, szCommandLine, szWorkingDir, |
szIconPath, nIcon, szShortCutKey, REPLACE) < 0) then |
MessageBox ("安装程序在进行桌面上添加快捷方式失败,稍侯请自己添加!.", SEVERE); |
endif; |
end; |
/****************** 结束 ******************/ |
当然了,我们还得在卸载程序中加上删除这些快捷方式的代码,否则的话当用户删除了我们的应用程序后会对我们的程序怒发冲冠的,我当然不希望这样。 |
/***************程序代码***************/ |
// 删除桌面上的快捷方式 |
if (DeleteFolderIcon (FOLDER_DESKTOP, "My Demo in Desktop") < 0) then |
MessageBox ("安装程序在删除桌面上的快捷方式的时候出错,稍侯请自己删除!.", SEVERE); |
endif; |
// 删除开始菜单中的快捷方式 |
if (DeleteFolderIcon (FOLDER_STARTMENU, "My Demo in StartMenu") < 0) then |
MessageBox ("安装程序在删除开始菜单中的快捷方式的时候出错,稍侯请自己删除!.", SEVERE); |
endif; |
// 删除程序文件夹中的快捷方式 |
if (DeleteFolderIcon (FOLDER_PROGRAMS, "My Demo in Programs") < 0) then |
MessageBox ("安装程序在删除程序文件夹中的快捷方式的时候出错,稍侯请自己删除!.", SEVERE); |
endif; |
/*************** 结束 ***************/ |
好了,我们可以拿着这个安装程序向老板领赏了。还不赶快制作一个具有专业级水平的应用程序安装盘来分发给我们的客户。相信他们一定会瞪大眼睛惊叹我们的软件开发水平是一流的。废话不说了,现在就开始行动! |
总结 |
至此,所有图例的有关技术已经全部讲述完毕。总之,InstallShield博大精深,我所叙述的只是一些皮毛而已,如果大家对她想更深入的了解还是请看她自带的帮助文档。 |
不过,最后我还是要多说一句,千万不要忘了编写一个详细,完整的用户手册。这可是用户最需要的,但同时也是我们最懒得去做的一件事。 |