简介:InstallShield for VC++ 6.0 是专为 Microsoft Visual C++ 6 定制的安装包制作工具,面向使用 VC++6 开发 Windows 应用程序、需要将 Release 版 EXE 打包发布的开发者。它可直接从 VC++6 的 Tools 菜单调用 InstallShield Wizard,省去单独配置工程与脚本的麻烦,适合刚接触安装包制作、希望快速生成标准安装向导的初中级开发者。资源包共 27 个文件,约 10.04MB,包含 setup.exe、_isdel.exe 等可执行程序,_sys1.cab、_user1.cab、data1.cab 等安装数据压缩包,setup.ins、setup.lid、setup.ini 等安装脚本与配置,以及 dll、dat、htm、gif、bmp 等运行库、说明文档与界面素材,覆盖安装向导运行所需的完整组件。目前已有 1174 人学习下载。借助该工具,读者可把调试完成的程序快速封装为带界面、带卸载与注册表写入的安装包,直接用于项目交付或软件分发,省去手工编写安装脚本的环节。
1. Installshield for VC++ 6.0 安装包制作工具:老 IDE 的最后一公里
手里维护着一套 VC++ 6.0 编译出来的 MFC 程序,代码能跑、功能稳定,客户现场却卡在“怎么装”这一步——这是很多工控、医疗设备、老产线软件维护者共同的处境。Installshield for VC++ 6.0 安装包制作工具,说的就是当年随 Visual C++ 6.0 一起分发的那套 InstallShield 5.x/6.x 版本,它能把编译出来的 exe、dll、ocx、ini 打包成一个带向导界面、能写注册表、能注册 COM 组件、能判断系统环境的 setup.exe。今天还在用它的人,多半不是怀旧,而是新工具链装不进那台只能跑 XP 或 Win7 的现场机器。这篇笔记按“它到底能干什么 → 工程怎么建 → 脚本怎么改 → 坑在哪 → 怎么验证”的顺序讲,目标是让你照着能把一个 VC++ 6.0 的 MFC 工程打成可交付的安装包。
2. InstallShield 5.x 工程结构与 VC++ 6.0 的衔接方式
2.1 为什么老项目还在用这套组合
VC++ 6.0 生成的程序有个绕不开的特征:它依赖 mfc42.dll、msvcrt.dll、msvcp60.dll 这一批运行库,而且很多老工程用的是 MFC 静态链接之外的动态链接方式,目标机器上没装对应运行库就直接报“找不到 mfc42.dll”。InstallShield 5.x 的价值在于它自带一套运行库检测和分发机制,能在安装阶段把 VC 运行库一起铺到系统目录,还能通过Dependency Walker式的扫描把工程依赖的 dll 自动列进安装清单。
另一个现实原因是 COM 组件注册。VC++ 6.0 时代大量功能靠 ocx 和 dll 实现,这些组件必须在安装时用regsvr32注册,否则程序启动就报“类未注册”。InstallShield 的Self-Registration机制能在文件拷贝完成后自动调用注册逻辑,比手写批处理可靠得多。常见做法是在工程里把 ocx 标记为 self-registered,安装脚本里不用额外写注册命令。
还有一点是安装界面。老项目的交付对象往往是产线工人或医院设备科,他们对“解压到某个目录再双击 exe”这种交付方式接受度低,需要一个带“下一步”的向导。InstallShield 5.x 的默认模板就能生成这种界面,改改公司名和 logo 就能用,不需要额外写 UI 代码。
2.2 工程目录里每个文件管什么
一个典型的 InstallShield 5.x 工程目录长这样,理解每个文件的职责是改脚本的前提:
| 文件/目录 | 作用 | 改它的场景 |
|---|---|---|
Setup.rul | 主安装脚本,InstallScript 语言 | 加自定义检测、改安装流程 |
*.ipr | 工程文件,记录文件分组和快捷方式 | 增删要打包的文件 |
File Groups | 文件分组定义,如AppExecutables | 把新 dll 归到对应组 |
Components | 组件定义,决定哪些文件一起装 | 拆分主程序和帮助文档 |
Setup.ini | 安装初始化参数 | 改产品名、版本号、默认路径 |
Language | 多语言资源 | 加中文界面字符串 |
Setup.rul是核心,它用的是 InstallScript 语言,语法接近 C,但有自己的事件函数:OnBegin、OnFirstUIBefore、OnMoving、OnEnd。VC++ 6.0 工程编译出来的文件不会自动进安装包,需要在File Groups里手动添加或通过Link方式关联。我一般会把主 exe 和核心 dll 放一个组,把 ocx 单独放一个组并勾选 self-registered,把配置 ini 放第三个组,安装时按组控制覆盖策略。
2.3 从 VC++ 6.0 工程到安装包的最小操作链
假设你有一个编译好的 MFC 工程,输出目录是Release,里面有MainApp.exe、Core.dll、Grid.ocx、config.ini。下面是从零建一个安装工程的步骤。
第一步,打开 InstallShield 5.x 的Project Wizard,选Standard Setup,产品名填你的软件名,公司名填客户单位,版本号按实际填。向导会问是否允许用户改安装路径,老项目一般选“允许”,因为现场机器盘符不统一。
第二步,在File Groups面板右键新建三个组:AppBin、AppOcx、AppConfig。把MainApp.exe和Core.dll拖进AppBin,Grid.ocx拖进AppOcx,config.ini拖进AppConfig。
第三步,选中Grid.ocx,在属性面板勾选Self-Registered。这一步等价于告诉安装程序:拷贝完这个文件后自动调regsvr32。
第四步,在Components面板新建一个组件MainComponent,把三个文件组都挂进去。如果帮助文档要单独装,就再建一个组件。
第五步,打开Setup.rul,在OnFirstUIBefore里加一段检测:如果目标机器已经装了更高版本,提示用户先卸载。InstallScript 里用RegDBGetKeyValueEx读注册表版本号,用MessageBox弹提示。
// Setup.rul 片段:检测已安装版本 function OnFirstUIBefore() NUMBER nResult; STRING szVersion; begin // 读取注册表中已安装版本 RegDBSetDefaultRoot(HKEY_LOCAL_MACHINE); nResult = RegDBGetKeyValueEx( "SOFTWARE\\MyCompany\\MainApp", "Version", nType, szVersion, nSize ); if (nResult = 0) then // 已安装过,比较版本 if (szVersion >= "2.0.0") then MessageBox("检测到已安装更高版本,请先卸载。", SEVERE); abort; endif; endif; // 继续默认安装界面 return 0; end;这段脚本的逻辑是:先设注册表根为HKEY_LOCAL_MACHINE,读SOFTWARE\MyCompany\MainApp下的Version值。如果读到了且大于等于当前要装的版本,就弹框并中止安装。参数说明:RegDBGetKeyValueEx的第三个参数是类型,第四个是接收值的字符串变量,第五个是缓冲区大小。注意 InstallScript 的字符串比较是按字典序,版本号最好用固定位数如2.0.0和1.9.9,避免10.0被当成小于9.0。
第六步,编译安装工程。InstallShield 会生成setup.exe和一堆.cab文件。如果选了Single Executable,就只出一个 exe,但体积会大一些。老项目交付一般选单文件,方便拷贝。
3. 脚本里必须改的四个地方和对应参数
3.1 安装路径默认值与 Program Files 判断
InstallShield 默认把路径设成C:\Program Files\公司名\产品名,但 XP 和 Win7 的 Program Files 路径可能不同,32 位和 64 位系统也不一样。常见做法是在OnFirstUIBefore里用GetSystemInfo判断系统类型,再拼路径。
// 根据系统类型设置默认安装路径 function OnFirstUIBefore() STRING szDefaultPath; NUMBER nType; begin GetSystemInfo(OS_TYPE, nType); if (nType = OS_64BIT) then szDefaultPath = "C:\\Program Files (x86)\\MyCompany\\MainApp"; else szDefaultPath = "C:\\Program Files\\MyCompany\\MainApp"; endif; // 把默认路径写入安装界面 SetInstallDir(szDefaultPath); return 0; end;GetSystemInfo的第一个参数OS_TYPE返回系统类型,OS_64BIT是常量。SetInstallDir把路径传给安装向导的路径选择页。注意 InstallScript 里反斜杠要写两个,字符串拼接用+。如果客户现场有绿色版需求,可以把默认路径改成D:\MainApp,但要在文档里写清楚。
3.2 注册表写入与卸载信息登记
安装程序不写卸载信息,控制面板里就看不到你的软件,客户想卸载只能手动删目录。InstallShield 5.x 在OnEnd里可以写注册表,把卸载串登记到Uninstall键下。
// 写入卸载信息 function OnEnd() STRING szUninstall; STRING szInstallDir; begin // 获取安装目录 szInstallDir = TARGETDIR; // 拼卸载命令 szUninstall = szInstallDir ^ "setup.exe /uninstall"; RegDBSetDefaultRoot(HKEY_LOCAL_MACHINE); RegDBSetKeyValueEx( "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MainApp", "DisplayName", REGDB_STRING, "MainApp 2.0", -1 ); RegDBSetKeyValueEx( "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\MainApp", "UninstallString", REGDB_STRING, szUninstall, -1 ); return 0; end;TARGETDIR是 InstallScript 的内置变量,保存用户选的安装目录。^是字符串拼接符,和 C 的+不同。RegDBSetKeyValueEx的最后一个参数-1表示字符串长度自动计算。注意UninstallString指向的setup.exe必须在安装目录里存在,所以打包时要把setup.exe本身也放进安装文件,否则卸载会找不到程序。
3.3 运行库检测与 VC++ 6.0 依赖分发
VC++ 6.0 程序依赖的mfc42.dll、msvcrt.dll、msvcp60.dll在目标机器上不一定有。InstallShield 5.x 自带一个Redistributable机制,但老版本的清单可能不全。我一般手动把这三个 dll 放进AppBin组,然后在OnMoving里检测系统目录是否已有更高版本。
// 检测系统目录运行库版本 function OnMoving() STRING szSysDir; STRING szDllPath; NUMBER nResult; begin szSysDir = GetSystemDir(); szDllPath = szSysDir ^ "\\mfc42.dll"; nResult = FileExists(szDllPath); if (nResult = 0) then // 系统没有,从安装包拷贝 CopyFile(TARGETDIR ^ "\\mfc42.dll", szSysDir); endif; return 0; end;GetSystemDir返回系统目录,XP 下是C:\Windows\System32。FileExists返回 0 表示不存在。CopyFile把安装目录里的 dll 拷到系统目录。注意这个操作需要管理员权限,如果客户是普通用户账户,拷贝会失败。替代方案是把 dll 放在程序同目录,但 VC++ 6.0 的 MFC 程序默认从系统目录加载,同目录加载需要改 manifest 或注册表,老项目一般不折腾,直接要求管理员安装。
3.4 快捷方式与开始菜单项
安装完不建快捷方式,用户找不到程序。InstallShield 5.x 在OnEnd里用CreateShortCut建桌面和开始菜单快捷方式。
// 创建快捷方式 function OnEnd() STRING szExePath; STRING szShortcut; begin szExePath = TARGETDIR ^ "\\MainApp.exe"; // 桌面快捷方式 szShortcut = "C:\\Documents and Settings\\All Users\\Desktop\\MainApp.lnk"; CreateShortCut(szExePath, szShortcut, "", "", "", 0); // 开始菜单 szShortcut = "C:\\Documents and Settings\\All Users\\Start Menu\\Programs\\MainApp.lnk"; CreateShortCut(szExePath, szShortcut, "", "", "", 0); return 0; end;CreateShortCut的参数依次是:目标 exe、快捷方式路径、参数、工作目录、图标路径、图标索引。All Users目录在 XP 下是C:\Documents and Settings\All Users,Win7 下是C:\ProgramData,所以这段脚本在 Win7 上会失败。更稳的做法是用GetFolderPath获取系统文件夹路径,但 InstallShield 5.x 的GetFolderPath支持有限,常见做法是判断系统版本后拼路径。
4. 避坑:InstallShield 5.x 在 VC++ 6.0 项目里的五个翻车点
4.1 安装包在 Win7 上提示“不是有效的 Win32 应用程序”
现象:在 XP 上编译好的setup.exe,拷到 Win7 上双击报错。原因:InstallShield 5.x 生成的安装引擎是 16 位或早期 32 位,Win7 64 位不兼容。解决:在 InstallShield 工程设置里把Setup Launcher改成 32 位模式,或者用 InstallShield 6.x 以上版本重新编译。如果客户只有 Win7 64 位,建议直接换 InstallShield 2010 之后的版本,但那样就不叫“for VC++ 6.0”了,需要重新做工程。
4.2 ocx 注册失败,程序启动报“类未注册”
现象:安装过程没报错,但程序启动时弹“Class not registered”。原因:Self-Registered勾选了,但 ocx 依赖的其他 dll 没一起注册,或者注册时权限不够。解决:先用regsvr32手动注册一次,看报什么错。如果是依赖缺失,用Dependency Walker打开 ocx 看缺哪个 dll,把缺失的 dll 也放进安装包。如果是权限问题,在OnEnd里用LaunchAppAndWait显式调regsvr32,并加/s静默参数。
4.3 安装路径含中文或空格导致脚本失败
现象:用户把程序装到D:\我的软件,安装完成但快捷方式指向错误。原因:InstallScript 对中文路径处理不好,TARGETDIR变量里的中文可能变成乱码。解决:在OnFirstUIBefore里检测路径是否含非 ASCII 字符,如果有就提示用户换路径。或者干脆把默认路径设成纯英文,并在文档里写明“不要装到中文目录”。
4.4 卸载时残留注册表和文件
现象:控制面板卸载后,注册表里还有SOFTWARE\MyCompany\MainApp,安装目录里还有config.ini。原因:InstallShield 只删除自己记录的文件,用户后来生成的配置文件不在清单里。解决:在OnUninstall事件里手动删注册表键和残留目录。InstallScript 用RegDBDeleteKey删键,用DeleteDir删目录,但DeleteDir只能删空目录,需要先DeleteFile删文件。
4.5 编译出的 setup.exe 体积过大
现象:一个 5MB 的程序,安装包 50MB。原因:InstallShield 默认把运行库、多语言资源全打进去了。解决:在File Groups里去掉不需要的语言包,在Setup.ini里把Compression设成High,在Media设置里选Single Executable并勾选Compress。如果还是大,检查是不是把Debug目录的文件也打进去了,只打Release目录。
5. 验证安装包是否可交付的三个硬指标
5.1 在干净虚拟机上跑一遍完整安装卸载
装完系统后不打任何补丁,直接跑setup.exe,看能不能装完、程序能不能启动、卸载后目录和注册表是否干净。这一步能暴露 80% 的依赖问题。我一般用 VMware 建一个 XP SP3 快照,每次改完安装脚本就恢复快照重跑一遍。注意虚拟机要设成和客户现场一样的系统版本和位数,XP 32 位和 Win7 32 位的表现可能不同。
5.2 用 Process Monitor 看安装过程写了哪些注册表
安装时开着 Process Monitor,过滤RegSetValue和CreateFile,看安装程序到底写了哪些键、拷了哪些文件。如果发现写了不该写的键,比如覆盖了系统运行库的版本号,就要在脚本里加判断。这个工具比看 InstallShield 日志直观,适合排查“装完系统变慢”这类玄学问题。
5.3 检查安装日志里的错误码
InstallShield 会在%TEMP%下生成Setup.log,里面记录了每一步的结果。安装失败时先看这个日志,搜Error和Fail。常见的错误码-1是文件拷贝失败,-2是注册表写入失败,-3是权限不足。日志里还会记录TARGETDIR的实际值,如果路径不对,说明SetInstallDir没生效。
5.4 一个我常用的版本号自增技巧
每次发版手动改Setup.ini里的版本号容易忘,我习惯在 VC++ 6.0 工程里加一个version.h,用宏定义版本号,然后在 InstallShield 的Setup.rul里用#include引入这个头文件。InstallScript 支持#include,但路径要用相对路径或绝对路径。这样编译安装包时版本号自动和程序一致,减少“装完发现版本不对”的返工。
// version.h 放在 VC++ 工程和 InstallShield 工程都能找到的目录 #define APP_VERSION "2.0.3" #define APP_NAME "MainApp"// Setup.rul 里引用 #include "..\\..\\version.h" function OnBegin() STRING szMsg; begin szMsg = APP_NAME ^ " " ^ APP_VERSION ^ " 安装程序"; SetTitle(szMsg, 0); return 0; end;SetTitle设置安装向导的标题栏文字。#include的路径是相对于Setup.rul所在目录的,如果两个工程不在同一目录树,就用绝对路径,但绝对路径换机器会失效,所以最好把version.h放在两个工程都能访问的公共目录,比如D:\Project\Common。
这套组合我用了很多年,最大的体会是:InstallShield 5.x 的脚本能力足够应付 VC++ 6.0 项目的交付,但它的坑集中在系统兼容性和权限上,每次换客户环境都要重新验证一遍。别信“一次做好到处能装”,老 IDE 的安装包没有银弹,只有快照和日志。希望帮到你。
本文还有配套的精品资源,点击获取