首页 > 代码库 > InstallShield集成安装MSDE2000最小版本(三) fishout特许授权发布

InstallShield集成安装MSDE2000最小版本(三) fishout特许授权发布

原文:InstallShield集成安装MSDE2000最小版本(三) fishout特许授权发布

原帖地址:http://blog.csdn.net/fishout/archive/2009/11/09/4790564.aspx

 

在这一节里,我们讨论如何在安装目录的任意指定基础上实现实例名及sa密码的更改,这样,就继承了原有微软80多兆安装包的功能,但是,现有的安装包体积小多了,仅有原来的1/6,当然,这仅是全新安装而言,对于升级安装是不适应的,不过,实际上我们大多数时候都是使用全新安装,何况,MSDE2000还支持多实例安装,所以,这种方法还是非常有实际意义的。
  我们先看看默认实例和命名实例在安装上有何区别:
  1、安装目录的区别;
  默认安装的子目录名称是:C:\Program Files\Microsoft SQL Server\MSSQL\。
  命名实例的子目录名称是:C:\Program Files\Microsoft SQL Server\实例名\。
  2、注册表中的区别:
  默认实例的注册表位置:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSSQLServer及Microsoft SQL Server子键。
  命名实例的注册表位置:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\实例名。
  看来默认实例和命名实例在安装上无论是目录名还是注册表都还是有很大区别。
  主要设计思路:
  首先,安装子目录的区别可以在Components中的Destination统一设定为<TARGETDIR>,通过自定义窗口根据用户选择用脚本进行控制。
  然而,注册表的区别就没有可以直接设置的地方了,因为,我们先前采用的是注册表导入的方式并与相关Components进行关联这种方式进行的,如果采用脚本写注册表入口的方式实现,固然可行,可是,这个工作量太大了,有没有更为简便的办法来实现呢?通过查询联机帮助,发现,CreateRegistrySet这个注册表函数就可以实现注册表入口与特定component的关联。
  好了,关键问题的解决思路明确了,让我们来具体实现吧!
  目标:实现实例名及sa密码的安装时指定。
  一、注册表准备
  1、如前所述,在运行中浏览至c:\SQL2KSP4\MSDE,让我们以这个命令行安装使用混合模式身份验证的默认实例:setup.exe SECURITYMODE=SQL SAPWD="fishout@TOM.COM" /L*v C:\MSDELog.log,等待安装完毕。
  2、打开注册表编辑器,导航至:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSSQLServer,ProtocolList属性中增加tcp/np键值以加快连接速度,删除多余的子健,将MSSQLServer子键下的内容导出,导航至HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server,同样也删除多余的子健并导出,合并两个注册表文件,编辑修改其中的安装路径,使其如下:
  (1)、“默认实例”注册表设置:
view plaincopy to clipboardprint?
Windows Registry Editor Version 5.00  
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server]  
"InstalledInstances"=hex(7):4d,00,53,00,53,00,51,00,4c,00,53,00,45,00,52,00,56,\  
  00,45,00,52,00,00,00,00,00  
"SsrpActiveServer"="" 
"SqlMdacRegRefCount"=dword:00000001  
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSSQLServer\MSSQLServer]  
"AuditLevel"=dword:00000000  
"DefaultLogin"="guest" 
"ListenOn"=hex(7):53,00,53,00,4d,00,53,00,53,00,48,00,37,00,30,00,00,00,53,00,\  
  53,00,4e,00,45,00,54,00,4c,00,49,00,42,00,00,00,00,00  
"LoginMode"=dword:00000000  
"Map_"="\\" 
"Map#"="-" 
"Map$"="" 
"SetHostName"=dword:00000000  
"Tapeloadwaittime"=dword:ffffffff  
"uptime_pid"=dword:00000e30  
"uptime_time_utc"=hex:de,57,29,6f,c2,5e,ca,01  
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSSQLServer\MSSQLServer\CurrentVersion]  
"CurrentVersion"="8.00.194" 
"RegisteredOwner"="" 
"SerialNumber"="" 
"CSDVersionNumber"=dword:00000400  
"CSDVersion"="8.00.1100" 
"Language"=dword:00000804  
"checksum"=hex:37,38,32,32,63,31,35,38,61,65,37,64,34,63,64,37,35,30,64,61,30,\  
  33,34,62,37,64,63,33,37,66,33,36,33,62,37,36,35,65,35,39,66,37,31,30,32,36,\  
  62,34,37,64,66,36,33,30,63,37,63,64,62,38,64,65,34,37,65,36,30,62,37,32,36,\  
  39,63,36,34,64,31,65,65,38,33,62,33,62,35,35,31,33,61,36,63,64,61,36,66,63,\  
  66,32,65,64,64,39,31,36,62,63,62,39,36,66,34,63,32,61,36,34,34,63,64,64,35,\  
  35,33,34,31,37,36,31,66,62,30,61,64,61,33,32,64,63,64,32,34,39,64,34,31,31,\  
  31,33,38,63,36,38,61,62,31,64,39,63,00  
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSSQLServer\MSSQLServer\Parameters]  
"SQLArg0"="-d<TARGETDIR>\\Data\\master.mdf" 
"SQLArg1"="-e<TARGETDIR>\\LOG\\ERRORLOG" 
"SQLArg2"="-l<TARGETDIR>\\Data\\mastlog.ldf" 
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSSQLServer\MSSQLServer\SuperSocketNetLib]  
"ProtocolList"=hex(7):74,00,63,00,70,00,00,00,6e,00,70,00,00,00,00,00  
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSSQLServer\MSSQLServer\SuperSocketNetLib\Np]  
"PipeName"="\\\\.\\pipe\\\\sql\\query" 
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSSQLServer\MSSQLServer\SuperSocketNetLib\Tcp]  
"TcpHideFlag"=dword:00000000  
"TcpDynamicPorts"="0" 
"TcpPort"="1433" 
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSSQLServer\Setup]  
"FeatureName"="SqlRun" 
"FirstStart"=dword:00000000  
"ProductCode"="{E09B48B5-E141-427A-AB0C-D3605127224A}" 
"SQLDataRoot"="<TARGETDIR>" 
"SQLPath"="<TARGETDIR>" 
"Edition"="Desktop Engine" 
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server]
"InstalledInstances"=hex(7):4d,00,53,00,53,00,51,00,4c,00,53,00,45,00,52,00,56,\
  00,45,00,52,00,00,00,00,00
"SsrpActiveServer"=""
"SqlMdacRegRefCount"=dword:00000001

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSSQLServer\MSSQLServer]
"AuditLevel"=dword:00000000
"DefaultLogin"="guest"
"ListenOn"=hex(7):53,00,53,00,4d,00,53,00,53,00,48,00,37,00,30,00,00,00,53,00,\
  53,00,4e,00,45,00,54,00,4c,00,49,00,42,00,00,00,00,00
"LoginMode"=dword:00000000
"Map_"="\\"
"Map#"="-"
"Map$"=""
"SetHostName"=dword:00000000
"Tapeloadwaittime"=dword:ffffffff
"uptime_pid"=dword:00000e30
"uptime_time_utc"=hex:de,57,29,6f,c2,5e,ca,01

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSSQLServer\MSSQLServer\CurrentVersion]
"CurrentVersion"="8.00.194"
"RegisteredOwner"=""
"SerialNumber"=""
"CSDVersionNumber"=dword:00000400
"CSDVersion"="8.00.1100"
"Language"=dword:00000804
"checksum"=hex:37,38,32,32,63,31,35,38,61,65,37,64,34,63,64,37,35,30,64,61,30,\
  33,34,62,37,64,63,33,37,66,33,36,33,62,37,36,35,65,35,39,66,37,31,30,32,36,\
  62,34,37,64,66,36,33,30,63,37,63,64,62,38,64,65,34,37,65,36,30,62,37,32,36,\
  39,63,36,34,64,31,65,65,38,33,62,33,62,35,35,31,33,61,36,63,64,61,36,66,63,\
  66,32,65,64,64,39,31,36,62,63,62,39,36,66,34,63,32,61,36,34,34,63,64,64,35,\
  35,33,34,31,37,36,31,66,62,30,61,64,61,33,32,64,63,64,32,34,39,64,34,31,31,\
  31,33,38,63,36,38,61,62,31,64,39,63,00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSSQLServer\MSSQLServer\Parameters]
"SQLArg0"="-d<TARGETDIR>\\Data\\master.mdf"
"SQLArg1"="-e<TARGETDIR>\\LOG\\ERRORLOG"
"SQLArg2"="-l<TARGETDIR>\\Data\\mastlog.ldf"

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSSQLServer\MSSQLServer\SuperSocketNetLib]
"ProtocolList"=hex(7):74,00,63,00,70,00,00,00,6e,00,70,00,00,00,00,00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSSQLServer\MSSQLServer\SuperSocketNetLib\Np]
"PipeName"="\\\\.\\pipe\\\\sql\\query"

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSSQLServer\MSSQLServer\SuperSocketNetLib\Tcp]
"TcpHideFlag"=dword:00000000
"TcpDynamicPorts"="0"
"TcpPort"="1433"

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSSQLServer\Setup]
"FeatureName"="SqlRun"
"FirstStart"=dword:00000000
"ProductCode"="{E09B48B5-E141-427A-AB0C-D3605127224A}"
"SQLDataRoot"="<TARGETDIR>"
"SQLPath"="<TARGETDIR>"
"Edition"="Desktop Engine"

  (2)、“命名实例”注册表设置:
view plaincopy to clipboardprint?
Windows Registry Editor Version 5.00  
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server]  
"InstalledInstances"=hex(7):46,00,49,00,53,00,48,00,4f,00,55,00,54,00,00,00,00,\  
  00  
"SsrpActiveServer"="" 
"SqlMdacRegRefCount"=dword:00000001  
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\<ID_INSTANCENAME>]  
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\<ID_INSTANCENAME>\MSSQLServer]  
"AuditLevel"=dword:00000000  
"DefaultLogin"="guest" 
"ListenOn"=hex(7):53,00,53,00,4d,00,53,00,53,00,48,00,37,00,30,00,00,00,53,00,\  
  53,00,4e,00,45,00,54,00,4c,00,49,00,42,00,00,00,00,00  
"LoginMode"=dword:00000000  
"Map_"="\\" 
"Map#"="-" 
"Map$"="" 
"SetHostName"=dword:00000000  
"Tapeloadwaittime"=dword:ffffffff  
"uptime_pid"=dword:00000e10  
"uptime_time_utc"=hex:23,ac,f1,ec,70,5f,ca,01  
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\<ID_INSTANCENAME>\MSSQLServer\CurrentVersion]  
"CurrentVersion"="8.00.194" 
"RegisteredOwner"="" 
"SerialNumber"="" 
"CSDVersionNumber"=dword:00000400  
"CSDVersion"="8.00.1100" 
"Language"=dword:00000804  
"checksum"=hex:37,38,32,32,63,31,35,38,61,65,37,64,34,63,64,37,35,30,64,61,30,\  
  33,34,62,36,30,31,34,32,64,30,61,34,32,61,65,31,37,31,34,33,37,32,63,63,37,\  
  65,30,61,64,31,66,30,31,32,32,33,64,31,66,36,66,30,32,65,31,32,37,63,38,35,\  
  38,64,62,39,35,30,34,31,63,32,66,66,38,30,63,38,37,37,66,34,36,66,61,34,34,\  
  36,32,35,33,33,39,61,38,63,34,62,63,38,36,30,65,31,32,37,66,39,64,34,34,32,\  
  37,36,32,37,39,36,66,31,38,33,30,64,32,64,32,32,30,63,62,66,63,61,64,30,34,\  
  32,30,64,35,63,30,30,33,34,33,65,34,00  
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\<ID_INSTANCENAME>\MSSQLServer\Parameters]  
"SQLArg0"="-d<TARGETDIR>\\Data\\master.mdf" 
"SQLArg1"="-e<TARGETDIR>\\LOG\\ERRORLOG" 
"SQLArg2"="-l<TARGETDIR>\\Data\\mastlog.ldf" 
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\<ID_INSTANCENAME>\MSSQLServer\SuperSocketNetLib]  
"ProtocolList"=hex(7):74,00,63,00,70,00,00,00,6e,00,70,00,00,00,00,00  
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\<ID_INSTANCENAME>\MSSQLServer\SuperSocketNetLib\Np]  
"PipeName"="\\\\.\\pipe\\MSSQL$FISHOUT\\sql\\query" 
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\<ID_INSTANCENAME>\MSSQLServer\SuperSocketNetLib\Tcp]  
"TcpHideFlag"=dword:00000000  
"TcpDynamicPorts"="0" 
"TcpPort"="0" 
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\<ID_INSTANCENAME>\Setup]  
"FeatureName"="SqlRun" 
"FirstStart"=dword:00000000  
"ProductCode"="{E09B48B5-E141-427A-AB0C-D3605127224A}" 
"SQLDataRoot"="<TARGETDIR>" 
"SQLPath"="<TARGETDIR>" 
"Edition"="Desktop Engine" 
"PatchLevel"="8.4.2039" 
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server]
"InstalledInstances"=hex(7):46,00,49,00,53,00,48,00,4f,00,55,00,54,00,00,00,00,\
  00
"SsrpActiveServer"=""
"SqlMdacRegRefCount"=dword:00000001

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\<ID_INSTANCENAME>]

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\<ID_INSTANCENAME>\MSSQLServer]
"AuditLevel"=dword:00000000
"DefaultLogin"="guest"
"ListenOn"=hex(7):53,00,53,00,4d,00,53,00,53,00,48,00,37,00,30,00,00,00,53,00,\
  53,00,4e,00,45,00,54,00,4c,00,49,00,42,00,00,00,00,00
"LoginMode"=dword:00000000
"Map_"="\\"
"Map#"="-"
"Map$"=""
"SetHostName"=dword:00000000
"Tapeloadwaittime"=dword:ffffffff
"uptime_pid"=dword:00000e10
"uptime_time_utc"=hex:23,ac,f1,ec,70,5f,ca,01

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\<ID_INSTANCENAME>\MSSQLServer\CurrentVersion]
"CurrentVersion"="8.00.194"
"RegisteredOwner"=""
"SerialNumber"=""
"CSDVersionNumber"=dword:00000400
"CSDVersion"="8.00.1100"
"Language"=dword:00000804
"checksum"=hex:37,38,32,32,63,31,35,38,61,65,37,64,34,63,64,37,35,30,64,61,30,\
  33,34,62,36,30,31,34,32,64,30,61,34,32,61,65,31,37,31,34,33,37,32,63,63,37,\
  65,30,61,64,31,66,30,31,32,32,33,64,31,66,36,66,30,32,65,31,32,37,63,38,35,\
  38,64,62,39,35,30,34,31,63,32,66,66,38,30,63,38,37,37,66,34,36,66,61,34,34,\
  36,32,35,33,33,39,61,38,63,34,62,63,38,36,30,65,31,32,37,66,39,64,34,34,32,\
  37,36,32,37,39,36,66,31,38,33,30,64,32,64,32,32,30,63,62,66,63,61,64,30,34,\
  32,30,64,35,63,30,30,33,34,33,65,34,00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\<ID_INSTANCENAME>\MSSQLServer\Parameters]
"SQLArg0"="-d<TARGETDIR>\\Data\\master.mdf"
"SQLArg1"="-e<TARGETDIR>\\LOG\\ERRORLOG"
"SQLArg2"="-l<TARGETDIR>\\Data\\mastlog.ldf"

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\<ID_INSTANCENAME>\MSSQLServer\SuperSocketNetLib]
"ProtocolList"=hex(7):74,00,63,00,70,00,00,00,6e,00,70,00,00,00,00,00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\<ID_INSTANCENAME>\MSSQLServer\SuperSocketNetLib\Np]
"PipeName"="\\\\.\\pipe\\MSSQL$FISHOUT\\sql\\query"

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\<ID_INSTANCENAME>\MSSQLServer\SuperSocketNetLib\Tcp]
"TcpHideFlag"=dword:00000000
"TcpDynamicPorts"="0"
"TcpPort"="0"

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\<ID_INSTANCENAME>\Setup]
"FeatureName"="SqlRun"
"FirstStart"=dword:00000000
"ProductCode"="{E09B48B5-E141-427A-AB0C-D3605127224A}"
"SQLDataRoot"="<TARGETDIR>"
"SQLPath"="<TARGETDIR>"
"Edition"="Desktop Engine"
"PatchLevel"="8.4.2039"
  3、在安装工程中,新建注册表入口,重命名为:MDSE2000_Default,右击它,点击:Import REG File...,导入刚才已修改好的“默认实例”注册表设置,再次新建注册表入口,重命名为:MSDE2000_Custom,导入为“命名实例”注册表设置,注意:不要在右边勾选与此关联的安装文件组。
  二、实例名的变更
  实例名的变更通过两个地方进行设置,一是Components的安装路径仅设置为<TARGETDIR>,通过自定义窗口的脚本根据实例名的选择不同而设置不同的路径,二是注册表设置,根据选择的实例名使用CreateRegistrySet函数关联不同的注册表入口。
  三、SA密码的修改方法
  在“MSDE2000.sql”脚本内容中增加一行:exec sp_password NULL, ‘<SAPASSWORD>‘, ‘sa‘,使其看上去如下:
view plaincopy to clipboardprint?
if not exists (select name from master.dbo.sysdatabases where name = ‘model‘)  
begin  
    exec master..sp_attach_db ‘model‘,‘C:\Program Files\Microsoft SQL Server\MSSQL$FISHOUT\Data\model.mdf‘,‘C:\Program Files\Microsoft SQL Server\MSSQL$FISHOUT\Data\modellog.ldf‘ 
    exec master..sp_attach_db ‘msdb‘,‘C:\Program Files\Microsoft SQL Server\MSSQL$FISHOUT\Data\msdbdata.mdf‘,‘C:\Program Files\Microsoft SQL Server\MSSQL$FISHOUT\Data\msdblog.ldf‘ 
    exec master..sp_resetstatus tempdb  
    Alter database tempdb modify file (name = tempdev, filename = ‘C:\Program Files\Microsoft SQL Server\MSSQL$FISHOUT\Data\tempdb.mdf‘)  
    Alter database tempdb modify file (name = templog, filename = ‘C:\Program Files\Microsoft SQL Server\MSSQL$FISHOUT\Data\templog.ldf‘)  
    exec master..sp_password ‘fishout@TOM.COM‘, ‘new_password‘, ‘sa‘ 
    use master  
    exec sp_configure ‘allow updates‘,1  
    reconfigure with override 
    update sysdatabases set [filename]=‘C:\Program Files\Microsoft SQL Server\MSSQL$FISHOUT\Data\master.mdf‘ where [name]=‘master‘ 
    exec sp_configure ‘allow updates‘,0  
    reconfigure with override 
end 
if not exists (select name from master.dbo.sysdatabases where name = ‘model‘)
begin
    exec master..sp_attach_db ‘model‘,‘C:\Program Files\Microsoft SQL Server\MSSQL$FISHOUT\Data\model.mdf‘,‘C:\Program Files\Microsoft SQL Server\MSSQL$FISHOUT\Data\modellog.ldf‘
    exec master..sp_attach_db ‘msdb‘,‘C:\Program Files\Microsoft SQL Server\MSSQL$FISHOUT\Data\msdbdata.mdf‘,‘C:\Program Files\Microsoft SQL Server\MSSQL$FISHOUT\Data\msdblog.ldf‘
    exec master..sp_resetstatus tempdb
    Alter database tempdb modify file (name = tempdev, filename = ‘C:\Program Files\Microsoft SQL Server\MSSQL$FISHOUT\Data\tempdb.mdf‘)
    Alter database tempdb modify file (name = templog, filename = ‘C:\Program Files\Microsoft SQL Server\MSSQL$FISHOUT\Data\templog.ldf‘)
    exec master..sp_password ‘fishout@TOM.COM‘, ‘new_password‘, ‘sa‘
    use master
    exec sp_configure ‘allow updates‘,1
    reconfigure with override
    update sysdatabases set [filename]=‘C:\Program Files\Microsoft SQL Server\MSSQL$FISHOUT\Data\master.mdf‘ where [name]=‘master‘
    exec sp_configure ‘allow updates‘,0
    reconfigure with override
end

  四、自定义对话框用户接口的实现
  编辑修改原有的SdAskDestPath2对话框,使其如图所示:

  将以下自定义对话框脚本另存为:SdAskDestPath2DlgEx.rul,在工程中导入,实现安装路径、实例名和SA密码的设置。
view plaincopy to clipboardprint?
//////////////////////////////////////////////////////////////////////////////////////////  
//  
//  File Name:    SdAskDestPath2DlgEx.rul  
//  
//  Description:  This file contains the InstallShield script for the SdAskDestPath2  
//          dialog function.  
//  
///////////////////////////////////////////////////////////////////////////////////////// 
#include "Dialogs.h" 
#include "CustomDlg.h" 
#include "Winapi.h" 
#include "ISRTDefs.h" 
#include "Silent.h" 
#include "sdrc.h" 
#include "sdint.h" 
#include "DialogsPriv.h"  
// 对话框ID定义 
#define SD_Radio1             1322 
#define SD_Radio2             1323 
#define SD_Text1              1324 
#define SD_Text2              1325 
#define SD_EditInstanceName   1326 
#define SD_EditSaPassword     1327  
prototype SdAskDestPath2Ex(STRING, STRING, BYREF STRING, BYREF STRING, BYREF STRING);  
function SdAskDestPath2Ex(szTitle, szMsg, svDir, svInstanceName, svSaPassword)  
    STRING  szDlg, svDirLoc, szTemp;  
    INT     nId, nTemp, nSdDialog, nStyle;  
    HWND    hwndDlg;  
    BOOL    bDone;  
    STRING  svRadioChoice;  
    begin  
    szDlg     = SD_DLG_ASKDESTPATH2;  
    nSdDialog = SD_NDLG_ASKDESTPATH2;  
    svDirLoc = svDir;  
    // 读静默安装参数  
    if (MODE=SILENTMODE) then  
        SdMakeName( szAppKey, szDlg, szTitle, nSdAskDestPath2 );  
        SilentReadData( szAppKey, "Result", DATA_NUMBER, szTemp, nId );  
        if ((nId != BACK) && (nId != CANCEL)) then  
            SilentReadData( szAppKey, "szDir", DATA_STRING, svDir, nTemp );  
            SilentReadData( szAppKey, "szInstanceName", DATA_STRING, svInstanceName, nTemp );  
            SilentReadData( szAppKey, "szSaPassword", DATA_STRING, svSaPassword, nTemp );  
        endif;  
        return nId;  
    endif;  
    // 确保初始化完成  
    if (!bSdInit) then  
        SdInit();  
    endif;  
    if (EzDefineDialog( szDlg, "", "", SD_NDLG_ASKDESTPATH2 ) = DLG_ERR) then  
        return -1;  
    endif;  
    // 循环开始直至用户选择标准按钮  
    bDone = FALSE;  
    while (!bDone)  
       nId = WaitOnDialog( szDlg );  
       switch (nId)  
       case DLG_INIT:  
            hwndDlg = CmdGetHwndDlg( szDlg );  
            TextSubGetValue ( "<ID_RADIOCHOICE>", svRadioChoice, TRUE, TRUE );  
            if (svRadioChoice = "") then  
          _WinSubEnableControl (hwndDlg, SD_EditInstanceName, 0); // 禁止控件修改  
          CtrlSetState(szDlg, SD_Radio1, BUTTON_CHECKED); // 设置初始状态  
            else 
          CtrlSetState(szDlg, SD_Radio2, BUTTON_CHECKED);  
            endif;  
            TextSubGetValue ( "<ID_INSTANCENAME>", svInstanceName, TRUE, TRUE );  
            TextSubGetValue ( "<ID_SAPASSWORD>", svSaPassword, TRUE, TRUE );  
            if (svSaPassword = "") then  
          svSaPassword = "fishout@TOM.COM";  
            endif;  
            CtrlSetText(szDlg, SD_EditInstanceName, svInstanceName); // 设置初始值  
            CtrlSetText(szDlg, SD_EditSaPassword, svSaPassword);  
            CtrlSetText( szDlg, 0x80000000 | SD_STA_DESTDIR, svDirLoc );  
            if(szMsg != "") then  
          SdSetStatic( szDlg, SD_STA_CHANGEDIRMSG, szMsg );  
            endif;  
            SdGeneralInit( szDlg, hwndDlg, nStyle, szSdProduct );  
            SdSetDlgTitle(szDlg, hwndDlg, szTitle);  
       case SD_Radio1:  
            svRadioChoice = "";  
            svInstanceName = "";  
            svSaPassword = "fishout@TOM.COM";  
            CtrlSetText(szDlg, SD_EditInstanceName, svInstanceName);  
            CtrlSetText(szDlg, SD_EditSaPassword, svSaPassword);  
            _WinSubEnableControl (hwndDlg, SD_EditInstanceName, 0); // 禁止修改  
       case SD_Radio2:  
            svRadioChoice = "CustomInstanceName";  
            svInstanceName = "fishout";  
            CtrlSetText(szDlg, SD_EditInstanceName, svInstanceName);  
            _WinSubEnableControl (hwndDlg, SD_EditInstanceName, 1); // 允许修改  
       case SD_PBUT_CHANGEDIR:  
            SelectDirNoLog( "", "", svDirLoc, TRUE );  
            CtrlSetText( szDlg, 0x80000000 | SD_STA_DESTDIR, svDirLoc );  
       case SD_PBUT_CONTINUE:  
            svDir = svDirLoc;  
            CtrlGetText(szDlg, SD_EditInstanceName, svInstanceName);  
            CtrlGetText(szDlg, SD_EditSaPassword, svSaPassword);  
            StrToUpper(svInstanceName, svInstanceName);  
            TextSubSetValue ( "<ID_INSTANCENAME>", svInstanceName, TRUE );  
            TextSubSetValue ( "<ID_SAPASSWORD>", svSaPassword, TRUE );  
            TextSubSetValue ( "<ID_RADIOCHOICE>", svRadioChoice, TRUE );  
            // 根据实例名的不同,确定不同的安装路径  
            if (svRadioChoice = "") then  
          svDir = svDir + "\\mssql";  
            else 
          svDir = svDir + "\\mssql$" + svInstanceName;  
            endif;  
            nId   = NEXT;  
            bDone = TRUE;  
       case BACK:  
            CtrlGetText(szDlg, SD_EditInstanceName, svInstanceName);  
            CtrlGetText(szDlg, SD_EditSaPassword, svSaPassword);  
            TextSubSetValue ( "<ID_INSTANCENAME>", svInstanceName, TRUE );  
            TextSubSetValue ( "<ID_SAPASSWORD>", svSaPassword, TRUE );  
            TextSubSetValue ( "<ID_RADIOCHOICE>", svRadioChoice, TRUE );  
            nId    = BACK;  
            bDone  = TRUE;  
       case DLG_ERR:  
            SdError( -1, "SdAskDestPath2" );  
            nId   = -1;  
            bDone = TRUE;  
       case DLG_CLOSE:  
            SdCloseDlg( hwndDlg, nId, bDone );  
       default:  
            // 检查标准按钮句柄  
            if (SdIsStdButton( nId ) && SdDoStdButton( nId )) then  
          bDone = TRUE;  
            endif;  
       endswitch;  
    endwhile;  
    EndDialog( szDlg );  
    ReleaseDialog( szDlg );  
    SdUnInit( );  
    // 保存静默安装参数  
    if (MODE=RECORDMODE) then  
        SdMakeName( szAppKey, szDlg, szTitle, nSdAskDestPath2 );  
        SilentWriteData( szAppKey, "szDir", DATA_STRING, svDir, 0 );  
        SilentWriteData( szAppKey, "szInstanceName", DATA_STRING, svInstanceName, 0 );  
        SilentWriteData( szAppKey, "szSaPassword", DATA_STRING, svSaPassword, 0 );  
        SilentWriteData( szAppKey, "Result", DATA_NUMBER, "", nId );  
    endif;  
    return nId;  
    end; 
//////////////////////////////////////////////////////////////////////////////////////////
//
//  File Name:    SdAskDestPath2DlgEx.rul
//
//  Description:  This file contains the InstallShield script for the SdAskDestPath2
//          dialog function.
//
/////////////////////////////////////////////////////////////////////////////////////////

#include "Dialogs.h"
#include "CustomDlg.h"
#include "Winapi.h"
#include "ISRTDefs.h"
#include "Silent.h"
#include "sdrc.h"
#include "sdint.h"
#include "DialogsPriv.h"
// 对话框ID定义
#define SD_Radio1              1322
#define SD_Radio2              1323
#define SD_Text1              1324
#define SD_Text2              1325
#define SD_EditInstanceName      1326
#define SD_EditSaPassword      1327
prototype SdAskDestPath2Ex(STRING, STRING, BYREF STRING, BYREF STRING, BYREF STRING);
function SdAskDestPath2Ex(szTitle, szMsg, svDir, svInstanceName, svSaPassword)
    STRING  szDlg, svDirLoc, szTemp;
    INT     nId, nTemp, nSdDialog, nStyle;
    HWND    hwndDlg;
    BOOL    bDone;
    STRING  svRadioChoice;
    begin
    szDlg     = SD_DLG_ASKDESTPATH2;
    nSdDialog = SD_NDLG_ASKDESTPATH2;
    svDirLoc = svDir;
    // 读静默安装参数
    if (MODE=SILENTMODE) then
        SdMakeName( szAppKey, szDlg, szTitle, nSdAskDestPath2 );
        SilentReadData( szAppKey, "Result", DATA_NUMBER, szTemp, nId );
        if ((nId != BACK) && (nId != CANCEL)) then
            SilentReadData( szAppKey, "szDir", DATA_STRING, svDir, nTemp );
            SilentReadData( szAppKey, "szInstanceName", DATA_STRING, svInstanceName, nTemp );
            SilentReadData( szAppKey, "szSaPassword", DATA_STRING, svSaPassword, nTemp );
        endif;
        return nId;
    endif;
    // 确保初始化完成
    if (!bSdInit) then
        SdInit();
    endif;
    if (EzDefineDialog( szDlg, "", "", SD_NDLG_ASKDESTPATH2 ) = DLG_ERR) then
        return -1;
    endif;
    // 循环开始直至用户选择标准按钮
    bDone = FALSE;
    while (!bDone)
       nId = WaitOnDialog( szDlg );
       switch (nId)
       case DLG_INIT:
            hwndDlg = CmdGetHwndDlg( szDlg );
            TextSubGetValue ( "<ID_RADIOCHOICE>", svRadioChoice, TRUE, TRUE );
            if (svRadioChoice = "") then
          _WinSubEnableControl (hwndDlg, SD_EditInstanceName, 0); // 禁止控件修改
          CtrlSetState(szDlg, SD_Radio1, BUTTON_CHECKED); // 设置初始状态
            else
          CtrlSetState(szDlg, SD_Radio2, BUTTON_CHECKED);
            endif;
            TextSubGetValue ( "<ID_INSTANCENAME>", svInstanceName, TRUE, TRUE );
            TextSubGetValue ( "<ID_SAPASSWORD>", svSaPassword, TRUE, TRUE );
            if (svSaPassword = "") then
          svSaPassword = "fishout@TOM.COM";
            endif;
            CtrlSetText(szDlg, SD_EditInstanceName, svInstanceName); // 设置初始值
            CtrlSetText(szDlg, SD_EditSaPassword, svSaPassword);
            CtrlSetText( szDlg, 0x80000000 | SD_STA_DESTDIR, svDirLoc );
            if(szMsg != "") then
          SdSetStatic( szDlg, SD_STA_CHANGEDIRMSG, szMsg );
            endif;
            SdGeneralInit( szDlg, hwndDlg, nStyle, szSdProduct );
            SdSetDlgTitle(szDlg, hwndDlg, szTitle);

       case SD_Radio1:
            svRadioChoice = "";
            svInstanceName = "";
            svSaPassword = "fishout@TOM.COM";
            CtrlSetText(szDlg, SD_EditInstanceName, svInstanceName);
            CtrlSetText(szDlg, SD_EditSaPassword, svSaPassword);
            _WinSubEnableControl (hwndDlg, SD_EditInstanceName, 0); // 禁止修改

       case SD_Radio2:
            svRadioChoice = "CustomInstanceName";
            svInstanceName = "fishout";
            CtrlSetText(szDlg, SD_EditInstanceName, svInstanceName);
            _WinSubEnableControl (hwndDlg, SD_EditInstanceName, 1); // 允许修改

       case SD_PBUT_CHANGEDIR:
            SelectDirNoLog( "", "", svDirLoc, TRUE );
            CtrlSetText( szDlg, 0x80000000 | SD_STA_DESTDIR, svDirLoc );

       case SD_PBUT_CONTINUE:
            svDir = svDirLoc;
            CtrlGetText(szDlg, SD_EditInstanceName, svInstanceName);
            CtrlGetText(szDlg, SD_EditSaPassword, svSaPassword);
            StrToUpper(svInstanceName, svInstanceName);
            TextSubSetValue ( "<ID_INSTANCENAME>", svInstanceName, TRUE );
            TextSubSetValue ( "<ID_SAPASSWORD>", svSaPassword, TRUE );
            TextSubSetValue ( "<ID_RADIOCHOICE>", svRadioChoice, TRUE );
            // 根据实例名的不同,确定不同的安装路径
            if (svRadioChoice = "") then
          svDir = svDir + "\\mssql";
            else
          svDir = svDir + "\\mssql$" + svInstanceName;
            endif;
            nId   = NEXT;
            bDone = TRUE;

       case BACK:
            CtrlGetText(szDlg, SD_EditInstanceName, svInstanceName);
            CtrlGetText(szDlg, SD_EditSaPassword, svSaPassword);
            TextSubSetValue ( "<ID_INSTANCENAME>", svInstanceName, TRUE );
            TextSubSetValue ( "<ID_SAPASSWORD>", svSaPassword, TRUE );
            TextSubSetValue ( "<ID_RADIOCHOICE>", svRadioChoice, TRUE );
            nId    = BACK;
            bDone  = TRUE;

       case DLG_ERR:
            SdError( -1, "SdAskDestPath2" );
            nId   = -1;
            bDone = TRUE;

       case DLG_CLOSE:
            SdCloseDlg( hwndDlg, nId, bDone );

       default:
            // 检查标准按钮句柄
            if (SdIsStdButton( nId ) && SdDoStdButton( nId )) then
          bDone = TRUE;
            endif;
       endswitch;
    endwhile;
    EndDialog( szDlg );
    ReleaseDialog( szDlg );
    SdUnInit( );
    // 保存静默安装参数
    if (MODE=RECORDMODE) then
        SdMakeName( szAppKey, szDlg, szTitle, nSdAskDestPath2 );
        SilentWriteData( szAppKey, "szDir", DATA_STRING, svDir, 0 );
        SilentWriteData( szAppKey, "szInstanceName", DATA_STRING, svInstanceName, 0 );
        SilentWriteData( szAppKey, "szSaPassword", DATA_STRING, svSaPassword, 0 );
        SilentWriteData( szAppKey, "Result", DATA_NUMBER, "", nId );
    endif;
    return nId;
    end;
  五、修改脚本文件
  1、将Setup.rul中Dlg_SdAskDestPath2标号处的内容修改为以下代码:
view plaincopy to clipboardprint?
Dlg_SdAskDestPath2:  
    if ((nResult = BACK) && (nSetupType != CUSTOM)) goto Dlg_SetupType2;  
    szTitle = "";  
    szMsg = "";  
    // 还原保存的路径,用于自定义对话框  
    szDir = szCustomDir;  
    if (nSetupType = CUSTOM) then  
                //{{IS_SCRIPT_TAG(Dlg_SdAskDestPath2)     
        nResult = SdAskDestPath2Ex( szTitle, szMsg, szDir, svInstanceName, svSaPassword );  
                //}}IS_SCRIPT_TAG(Dlg_SdAskDestPath2)  
        TARGETDIR = szDir;  
    else 
        TARGETDIR = szDir + "\\mssql";  
        svInstanceName = "";  
        svSaPassword = "fishout@TOM.COM";  
    endif;  
    if (nResult = BACK) goto Dlg_SetupType2; 
Dlg_SdAskDestPath2:
    if ((nResult = BACK) && (nSetupType != CUSTOM)) goto Dlg_SetupType2;
    szTitle = "";
    szMsg = "";
    // 还原保存的路径,用于自定义对话框
    szDir = szCustomDir;
    if (nSetupType = CUSTOM) then
                //{{IS_SCRIPT_TAG(Dlg_SdAskDestPath2)   
        nResult = SdAskDestPath2Ex( szTitle, szMsg, szDir, svInstanceName, svSaPassword );
                //}}IS_SCRIPT_TAG(Dlg_SdAskDestPath2)
        TARGETDIR = szDir;
    else
        TARGETDIR = szDir + "\\mssql";
        svInstanceName = "";
        svSaPassword = "fishout@TOM.COM";
    endif;
    if (nResult = BACK) goto Dlg_SetupType2;
  2、修改“featureevents.rul”中的内容,实现注册表入口关联及SQL脚本的执行,代码如下:
view plaincopy to clipboardprint?
//---------------------------------------------------------------------------  
// The Installed event is sent after the feature DefaultFeature  
// is installed.  
//--------------------------------------------------------------------------- 
#include "ShutDownRunningApp.rul"  
export prototype DefaultFeature_Installed();  
function DefaultFeature_Installed()  
    number nvServiceState, nResult;  
    string szServiceName, szServiceDisplayName, szServiceDescription, szServicePathFile, szStartServiceArgs;  
    BOOL bStartService;  
    LIST listConnections;  
    string szMsg, szKey, szConnection, szCmdLine, szServer, svInstanceName;  
begin  
    szCmdLine = TARGETDIR ^ "Binn\\sqlservr.exe";  
    // 转换为短路经  
    LongPathToShortPath(szCmdLine);  
    // 实例名  
    TextSubGetValue ( "<ID_INSTANCENAME>", svInstanceName, TRUE, TRUE );  
    if (svInstanceName = "") then  
        szServer = "(local)";  
        szServiceName = "MSSQLSERVER";  
        szServiceDisplayName = "MSSQLSERVER";  
        szServicePathFile = TARGETDIR ^ "Binn\\sqlservr.exe -sMSSQLSERVER";  
        szCmdLine = "/c " + szCmdLine + " -sMSSQLSERVER -c -f -T3608 -T4022";  
        CreateRegistrySet("MSDE2000_Default"); // 写注册表入口  
    else 
        szServer = "(local)\\" + svInstanceName;  
        szServiceName = "MSSQL$" + svInstanceName;  
        szServiceDisplayName = szServiceName;  
        szServicePathFile = TARGETDIR ^ "Binn\\sqlservr.exe -s" + svInstanceName;  
        szCmdLine = "/c " + szCmdLine + " -s" + svInstanceName + " -c -f -T3608 -T4022";  
        CreateRegistrySet("MSDE2000_Custom");  
    endif;  
    // SQL Server 以单用户模式启动,允许对系统目录进行更新,在新目录附加数据库  
    LaunchApplication(WINSYSDIR ^ "cmd.exe", szCmdLine, "", SW_HIDE, LAAW_OPTION_WAIT, LAAW_OPTION_FIXUP_PROGRAM);  
    // SQL运行时初始化  
    SQLRTInitialize2();  
    // 获取连接信息  
    listConnections = SQLRTGetConnections();  
    ListGetFirstString (listConnections, szConnection);  
    // Windows认证方式  
    SQLRTPutConnectionAuthentication( szConnection, TRUE );  
    // 打开连接  
    nResult = SQLRTConnect( szConnection, szServer, TRUE, "", "" );  
    if( nResult < ISERR_SUCCESS ) then  
        // 获取错误信息  
        SQLRTGetErrorMessage( szMsg );  
        // 显示错误信息  
        MessageBox( szMsg, MB_OK );  
    else 
        // SQL Server登录成功,保存连接信息  
        szKey = "";  
        Sprintf( szKey, SQL_FORMATSTRING_CONNECTION_SERVER, szConnection );  
        LogWriteCustomString( szKey, szServer );  
        Sprintf( szKey, SQL_FORMATSTRING_CONNECTION_USER, szConnection );  
        LogWriteCustomString( szKey, "" );  
        Sprintf( szKey, SQL_FORMATSTRING_CONNECTION_AUTH, szConnection );  
        LogWriteCustomNumber( szKey, SQL_AUTH_WINDOWS );  
        Sprintf( szKey, SQL_FORMATSTRING_CONNECTION_DB, szConnection );  
        LogWriteCustomString( szKey, "" );  
    endif;  
    // 执行SQL脚本  
    SQLRTComponentInstall("MSDE2000.sql_SQLComponent");  
    // 中止SQL Server单用户、跟踪进程  
    if ProcessRunning("sqlservr") then  
        ProcessEnd("sqlservr");  
    endif;  
    // 安装SQL Server服务  
    szMsg = "正在启动 MSDE2000 SP4服务......";  
    SdShowMsg(szMsg, TRUE);  
    // 安装服务  
    szServiceDescription = "";  
    bStartService = TRUE;  
    szStartServiceArgs = "";  
    if (ServiceGetServiceState (szServiceName, nvServiceState ) >= ISERR_SUCCESS) then  
        // 停止并卸载原来的服务  
        ServiceStopService ( szServiceName );  
        ServiceRemoveService ( szServiceName );  
    endif;  
    ServiceAddService ( szServiceName, szServiceDisplayName, szServiceDescription, szServicePathFile, bStartService, szStartServiceArgs );  
    SdShowMsg(szMsg, FALSE);  
end; 
//---------------------------------------------------------------------------
// The Installed event is sent after the feature DefaultFeature
// is installed.
//---------------------------------------------------------------------------
#include "ShutDownRunningApp.rul"
export prototype DefaultFeature_Installed();
function DefaultFeature_Installed()
    number nvServiceState, nResult;
    string szServiceName, szServiceDisplayName, szServiceDescription, szServicePathFile, szStartServiceArgs;
    BOOL bStartService;
    LIST listConnections;
    string szMsg, szKey, szConnection, szCmdLine, szServer, svInstanceName;
begin
    szCmdLine = TARGETDIR ^ "Binn\\sqlservr.exe";
    // 转换为短路经
    LongPathToShortPath(szCmdLine);
    // 实例名
    TextSubGetValue ( "<ID_INSTANCENAME>", svInstanceName, TRUE, TRUE );
    if (svInstanceName = "") then
        szServer = "(local)";
        szServiceName = "MSSQLSERVER";
        szServiceDisplayName = "MSSQLSERVER";
        szServicePathFile = TARGETDIR ^ "Binn\\sqlservr.exe -sMSSQLSERVER";
        szCmdLine = "/c " + szCmdLine + " -sMSSQLSERVER -c -f -T3608 -T4022";
        CreateRegistrySet("MSDE2000_Default"); // 写注册表入口
    else
        szServer = "(local)\\" + svInstanceName;
        szServiceName = "MSSQL$" + svInstanceName;
        szServiceDisplayName = szServiceName;
        szServicePathFile = TARGETDIR ^ "Binn\\sqlservr.exe -s" + svInstanceName;
        szCmdLine = "/c " + szCmdLine + " -s" + svInstanceName + " -c -f -T3608 -T4022";
        CreateRegistrySet("MSDE2000_Custom");
    endif;
    // SQL Server 以单用户模式启动,允许对系统目录进行更新,在新目录附加数据库
    LaunchApplication(WINSYSDIR ^ "cmd.exe", szCmdLine, "", SW_HIDE, LAAW_OPTION_WAIT, LAAW_OPTION_FIXUP_PROGRAM);
    // SQL运行时初始化
    SQLRTInitialize2();
    // 获取连接信息
    listConnections = SQLRTGetConnections();
    ListGetFirstString (listConnections, szConnection);
    // Windows认证方式
    SQLRTPutConnectionAuthentication( szConnection, TRUE );
    // 打开连接
    nResult = SQLRTConnect( szConnection, szServer, TRUE, "", "" );
    if( nResult < ISERR_SUCCESS ) then
        // 获取错误信息
        SQLRTGetErrorMessage( szMsg );
        // 显示错误信息
        MessageBox( szMsg, MB_OK );
    else
        // SQL Server登录成功,保存连接信息
        szKey = "";
        Sprintf( szKey, SQL_FORMATSTRING_CONNECTION_SERVER, szConnection );
        LogWriteCustomString( szKey, szServer );
        Sprintf( szKey, SQL_FORMATSTRING_CONNECTION_USER, szConnection );
        LogWriteCustomString( szKey, "" );
        Sprintf( szKey, SQL_FORMATSTRING_CONNECTION_AUTH, szConnection );
        LogWriteCustomNumber( szKey, SQL_AUTH_WINDOWS );
        Sprintf( szKey, SQL_FORMATSTRING_CONNECTION_DB, szConnection );
        LogWriteCustomString( szKey, "" );
    endif;
    // 执行SQL脚本
    SQLRTComponentInstall("MSDE2000.sql_SQLComponent");
    // 中止SQL Server单用户、跟踪进程
    if ProcessRunning("sqlservr") then
        ProcessEnd("sqlservr");
    endif;
    // 安装SQL Server服务
    szMsg = "正在启动 MSDE2000 SP4服务......";
    SdShowMsg(szMsg, TRUE);
    // 安装服务
    szServiceDescription = "";
    bStartService = TRUE;
    szStartServiceArgs = "";
    if (ServiceGetServiceState (szServiceName, nvServiceState ) >= ISERR_SUCCESS) then
        // 停止并卸载原来的服务
        ServiceStopService ( szServiceName );
        ServiceRemoveService ( szServiceName );
    endif;
    ServiceAddService ( szServiceName, szServiceDisplayName, szServiceDescription, szServicePathFile, bStartService, szStartServiceArgs );
    SdShowMsg(szMsg, FALSE);
end;

  好了!一个相对比较完美的MSDE2000最小版本就实现了,这下可以将其集成在自己的应用程序中,以下问题全部解决:
    1、无法与应用程序的安装一并卸载;
    2、无法实现MSDE2000的最小安装;
    3、如果用户中途取消安装,程序不能完全回滚;
    4、无法与应用程序安装到同一个目录。

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/fishout/archive/2009/11/09/4790564.aspx