创建安装程序的两种方法
● 彭 进 赵 昕
创 建 安 装 程 序 是 程 序 员 经 常 遇 到 的 问 题 之 一。 本 文 仅 探 讨 在Windows 平 台 上 创 建 安 装(Setup) 程 序 的 两 种 方 法。
一、 使 用Visual C++ 编 程 生 成Setup 程 序
生 成Setup 程 序 最 直 接 的 方 法 当 然 是 通 过 编 程 来 实 现。 对 于Windows 平 台 来 说, 没 有 比Visual C++ 更 好 的 开 发 工 具 了( 原 因 很 简 单, 有 谁 能 比Microsoft 更 了 解Windows 平 台 呢 ?)。 下 面 的 例 程 就 是 使 用Visual C++ 5.0 编 译 完 成 的。
Setup 程 序 主 要 处 理 两 个 方 面 的 问 题 :
(1) 用 户 界 面。 评 价 一 个Setup 程 序 的 优 劣 时, 用 户 界 面 是 否 美 观 是 其 中 的 一 个 重 要 因 素。 此 外, 通 过 交 互 式 界 面 还 应 能 够 获 得 用 户 的 相 关 信 息( 比 如 目 标 目 录)。
(2) 文 件 拷 贝 与 程 序 组 的 生 成。 也 就 是 按 照 用 户 输 入 的 信 息, 生 成 相 应 的 目 录 并 完 成 文 件 拷 贝 功 能( 这 要 涉 及 到 解 压 缩 问 题)。 一 般 来 说, 还 应 包 括 将 可 执 行 文 件 的 图 标 添 加 到 指 定 的 程 序 组 中。
1、 为Setup 程 序 设 置 背 景
Setup 程 序 的 用 户 界 面 以 对 话 框 为 主, 不 过 若 有 美 丽 的 背 景 则 能 为 你 的 程 序 增 色 不 少。 你 可 以 选 择 一 个 合 适 的BMP 文 件, 将 它 插 入 到 工 程 文 件(project) 中, 并 通 过 重 载 主 窗 口 类 的OnPaint() 函 数 显 示 出 来。 值 得 注 意 的 是, 背 景 图 片 不 应 过 于 眩 目, 否 则 会 有 喧 宾 夺 主 之 感。 例 如, 要 加 入 的BMP 文 件 的ID 号 是IDB_BIT。 下 面 给 出 应 加 在OnPaint() 中 的 函 数。
void Background(CDC *pDC)
{ CDC * pmem;
CBitmap * pback;
CBitmap * pold;
BITMAP ff;
pmem=new CDC;
pbit=new CBitmap;
pbit->LoadBitmap(IDB_BIT);
pmem->CreateCompatibleDC(pDC);
pold=(CBitmap *) pmem->SelectObject(pbit);
pbit->GetObject(sizeof(ff),&ff);
pDC->BitBlt(0,0,bm.bmWidth,bm.bmHeight,pmem,0,0, MERGECOPY );
delete pmem->SelectObject(pold);
delete pmem;
return;
}
2、 显 示 全 屏 效 果
一 般 的 主 窗 口 都 有 边 界(border), 如 果 你 更 欣 赏DOS 界 面 中 的 全 屏 效 果, 则 最 好 在 重 载CWnd:: PreCreateWindow(CREATESTRUCT&cs) 时 保 持cs.style 的 缺 省 值, 并 且 在 创 建 主 窗 口 时 使 用CreateEx(WS_EX_TO PMOST,AfxRegisterWndClass(CS_VREDRAW), NULL,WS_VISIBLE|WS_POPUP,0,0,(GetSystemMetrics (SM_CXSCREEN)),(GetSystemMetrics(SM_CYSCREEN)), HWND_DESKTOP,0);。
3、 保 存 公 用 参 量
通 过 对 话 框, 可 与 使 用 者 交 换 信 息。 那 么, 如 何 将 程 序 运 行 时 必 需 的 参 量( 比 如 说 安 装 目 录) 保 存 起 来 呢 ? 当 然, 可 以 生 成 一 个 配 置 文 件, 不 过 更 为 专 业 的 作 法 是 将 相 关 信 息 存 入 到 系 统 的INI 文 件 中。 如 果 开 发 平 台 是Windows 95, 则 一 切 都 将 变 得 很 简 单, 因 为 你 面 对 的 就 是win.ini 文 件, 该 文 件 在Windows 目 录 下。Visual C++ 提 供 了 如 下 的 一 组 函 数 来 操 作 该 文 件。
(1)CWinApp::GetProfileString
CString GetProfileString( LPCTSTR lpszSection, LPCTSTR lpszEntry, LPCTSTR lpszDefault = NULL );
用 来 读 取lpszSection 区 域 内 的lpszEntry 参 数, 其 缺 省 值 为lpszDefault。
(2)CWinApp::WriteProfileString
BOOL WriteProfileString( LPCTSTR lpszSection, LPCTSTR lpszEntry, LPCTSTR lpszValue );
用 来 写 入lpszSection 区 域 内 的lpszEntry 参 数, 其 值 为lpszValue。
如 果 你 的 开 发 平 台 是Winnt, 则 要 麻 烦 一 点。 假 如 你 的 变 量 保 存 在e:\\winnt\sdi.ini 中, 则 在 使 用 以 上 函 数 之 前 必 须 调 用 :
free((void*)m_pszProfileName);
m_pszProfileName=_tcsdup(_T("e:\\winnt\\sdi.ini"));
如 果 要 存 取 的 变 量 并 不 多, 则 也 可 将 它 们 保 存 在 注 册 关 键 字(registry key) 中。 假 如 你 的 变 量 保 存 在Moon 注 册 关 键 字( 一 般 是 你 公 司 的 名 字) 下, 则 在 使 用 以 上 函 数 之 前 必 须 调 用 :
free((void*)m_pszRegistryKey);
m_pszRegistryKey=_tcsdup(_T("HKEY_CURRENT_ USER\\Software\\moon"));
4、 文 件 的 拷 贝 与 解 压 缩
如 果 你 的 安 装 程 序 是 针 对 软 盘 的, 则 存 储 空 间 就 成 了 必 须 考 虑 的 问 题。 原 封 不 动 地 把 应 用 程 序 拷 贝 上 去 未 免 太 傻 了, 较 为 合 适 的 方 法 是 将 应 用 程 序 压 缩 在 软 盘 中, 安 装 时 再 解 压 缩 到 相 应 的 目 标 目 录 中。Visual C++ 提 供 了 一 套 以LZ 开 头 的 函 数, 用 来 操 作 用Compress 命 令 压 缩 的 文 件 : 你 可 以 用LzOpenFile() 打 开 原 文 件 和 目 标 文 件, 然 后 用LzCopy() 解 压 缩 拷 贝。 该 组 函 数 调 用Windows 系 统 的lzexpand.dll 动 态 链 接 库, 所 以 编 译 链 接 时 一 定 要 注 意 加 入 头 文 件lzexpand.h, 并 插 入 接 口 库lz32.lib。
还 有 个 更 为 简 单 的 办 法, 即 先 用 像arj.exe 这 类 的 共 享 压 缩 程 序 压 缩 原 程 序, 然 后 调 用system("arj ...") 来 完 成 解 压 缩, 整 个 解 压 缩 过 程 在 后 台 进 行, 用 户 不 会 知 道 你 到 底 用 的 是 什 么 方 法。
5、 显 示 拷 贝 进 度
很 多 专 业 安 装 程 序 在 进 行 文 件 拷 贝 时 都 会 显 示 进 度 栏, 甚 至 还 带 有 动 画。 如 何 实 现 这 种 效 果 呢 ? 办 法 是 在 拷 贝 的 同 时 创 建 一 个modeless 对 话 框。 这 种 对 话 框 用create() 函 数 创 建, 类 似 于 创 建 了 一 个 与 主 线 程 独 立 的 新 线 程。 这 样 就 可 在 拷 贝 文 件 的 同 时 在 对 话 框 中 显 示 出 拷 贝 进 度。Visual C++ 提 供 了 两 个 很 有 用 的 控 件, 即CAnimateCtrl 和 CProgressCtrl。 可 在 对 话 框 中 画 出 这 两 个 控 件, 并 用ClassWizard 加 入 相 应 的 类。 其 中,CAnimateCtrl 是 用 来 显 示.avi 文 件 的, 使 用 它 即 可 在 每 个 文 件 拷 贝 成 功 后 启 动 一 个“ 小 纸 片 飞 过” 的 文 件 ;CProgressCtrl 用 来 显 示 拷 贝 进 度, 当 各 个 拷 贝 成 功 后 使 表 示 进 度 的 蓝 条 前 进 到 适 当 的 位 置。
6、 程 序 组 和 程 序 项 的 生 成
程 序 组 的 生 成 实 际 上 是 与 负 责 管 理 程 序 组 的Program Manager 进 行DDE 对 话, 你 可 以 通 过 该 对 话 来 添 加 相 应 的 程 序 组 和 应 用 程 序 的 图 标。 同 时 由 于 该 对 话 是 单 向 的, 所 以 负 责 处 理 反 馈 消 息 的callback() 函 数 不 进 行 任 何 处 理。 下 面 给 出 其 源 程 序 及 其 用 法。
HDDEDATA CALLBACK DDECallback( UINT uType, // transaction type
UINT uFmt, // clipboard data format
HCONV hconv, // handle to the conversation
HSZ hsz1, // handle to a string
HSZ hsz2,
// handle to a string
HDDEDATA hdata, // handle to a global memory object
DWORD dwData1,
// transaction-specific data
DWORD dwData2
// transaction-specific data
)
{return NULL;}
SendDdeCmd(LPSTR cmd)
{
LPDWORD dwDDEInst=0L;
UINT ui;
HSZ hszService,hszTopic,hszItem;
HCONV hConv;
HDDEDATA hexecData;
ui=DdeInitialize((unsigned long *)&dwDDEInst, DDECallback,CBF_FAIL_ALLSVRXACTIONS,0L);
if(ui!=DMLERR_NO_ERROR)
{return FALSE;}
hszService=DdeCreateStringHandle( (DWORD) dwDDEInst,"PROGMAN",CP_WINANSI);
hszTopic=DdeCreateStringHandle((DWORD)dwDDEInst, "PROGMAN",CP_WINANSI);
hszItem=DdeCreateStringHandle((DWORD)dwDDEInst,"GROUP", CP_WINANSI);
hConv=DdeConnect((DWORD)dwDDEInst,hszService, hszTopic,NULL);
DdeFreeStringHandle((unsigned long)dwDDEInst, hszService);
DdeFreeStringHandle((unsigned long)dwDDEInst, hszTopic);
DdeFreeStringHandle((unsigned long)dwDDEInst, hszItem);
if(!hConv)
return FALSE;
hexecData=DdeCreateDataHandle((DWORD)dwDDEInst,(unsigned char *)cmd,lstrlen(cmd)+1,0,NULL,0,0);
DdeClientTransaction((unsigned char *)hexecData, (DWORD)-1,hConv,NULL,0,XTYP_EXECUTE,1000,NULL);
DdeDisconnect(hConv);
DdeUninitialize((unsigned long)dwDDEInst);
return TRUE;
}
如 果 你 想 加 入 一 个 名 叫“demo” 的 程 序 组, 其 中 包 含 有 名 称 为“test” 的 程 序 项, 并 执 行c:\demo\test.exe, 使 用 的 是“test.ico” 图 标, 则 可 按 如 下 方 式 调 用 该 函 数 :
SendDdeCmd("[CreateGroup (demo)] [AddItem (c:\demo\test.exe, test,c:\demo\test.ico)]")
二、 利 用 工 具 软 件 创 建 安 装 程 序
使 用Visual C++ 编 程 能 够 生 成 功 能 强 大 的Setup 程 序。 不 过, 如 果 你 更 喜 欢 利 用 现 成 的 工 具, 则 不 妨 试 一 试 用 于 创 建 安 装 程 序 的 工 具 软 件InstallShield5 Free Edition。 该 软 件 提 供 了 一 种 类 似 于Basic 的 编 程 语 言, 清 晰 易 懂。
创 建 一 个 新 的 安 装 程 序 时, 可 从Project Wizard 开 始, 首 先 启 动 的 是 一 个Welcome 对 话 框, 它 包 括 以 下 几 项 内 容 :
(1)Application Name (edit box)
在 这 个 对 话 框 中 输 入 你 想 要 安 装 的 应 用 程 序 的 名 字, InstallShield 将 用 该 名 字 注 册 登 记 并 生 成 你 的 程 序 组。
(2)Development Environment (list box)
选 择 开 发 该 应 用 程 序 的 环 境( 如Visual C++)。
(3)Application Type (list box)
选 择 你 的 应 用 程 序 的 类 别。
(4)Application Version (edit box)
选 择 你 的 应 用 程 序 的 版 本。
(5)Application Executable (edit box)
输 入 应 用 程 序 的 主 运 行 文 件, 或 单 击 ... 寻 找 该 文 件。
(6) 单 击“ 下 一 步”,select dialogs
选 择 你 的 安 装 程 序 所 需 要 的 对 话 框, 如 果 你 暂 时 还 不 能 确 定 的 话, 则 可 按Preview 键 预 览 一 下。
(7) 单 击“ 下 一 步”, 选 择 适 合 于 该 应 用 程 序 的 操 作 系 统 平 台。 你 可 以 选 择Win 95、Winnt 3.x 或Winnt4.0。
(8) 单 击“ 下 一 步”, 选 择 安 装 程 序 使 用 的 语 言。 在 这 种free 版 本 中, 你 所 能 作 的 唯 一 选 择 就 是“ 英 语”。
(9) 如 果 你 想 在 你 的 安 装 程 序 中 包 括 多 种 安 装 类 型 选 择, 则 单 击“ 下 一 步”。 启 动 的 对 话 框 中 提 供 了 上 述 功 能, 从 中 你 可 选 择Compact、 Typical、 Custom 及Network 等 多 种 安 装 类 型。
(10) 单 击“ 下 一 步”, 选 择 你 想 要 安 装 的 组 件, 这 些 组 件 的 不 同 组 合 即 是 你 在 上 面 选 择 的 不 同 安 装 类 型。
(11) 单 击“ 下 一 步”, 选 择 你 想 要 创 建 的 文 件 组, 该 文 件 组 是 你 的 应 用 程 序 所 包 括 文 件 的 逻 辑 分 组, 这 种 逻 辑 分 组 对 应 于 上 面 的 组 件。
(12) 单 击“ 下 一 步”, 概 括( 即“Summary” ) 你 所 输 入 的 信 息。 如 果 不 满 意, 则 还 可 以 修 改 ; 如 果 满 意 的 话, 则 按“ 完 成” 键, 一 个 安 装 程 序 的 框 架 就 大 功 告 成 了。
下 面 介 绍 制 作 供 安 装 程 序 使 用 的 工 程 文 件。
实 际 上,Project Wizard 已 经 为 你 作 好 了 大 部 分 的 工 作。 为 了 创 建 一 个 完 整 的 安 装 程 序, 首 先 要 弄 清 楚 工 程 文 件 中 各 个 项 目 间 的 关 系。 这 要 从File Group 说 起。File Group 是 应 用 程 序 文 件 的 逻 辑 分 组, 在 每 个 文 件 夹 下 都 有link 项, 用 右 键 单 击 该 位 置, 选 择 插 入 项, 则 可 将 你 的 应 用 文 件 进 行 适 当 分 组。 每 个File Group 中 含 有 若 干 属 性 值, 单 击 该 属 性 则 可 对 其 进 行 修 改。 比 如, 对 于Compress 项, 你 可 以 选 择 是 否 对 该File Group 下 的 文 件 进 行 压 缩。 几 个File Group 可 以 同 属 于 一 个Component, 通 过 划 分Component 你 可 以 区 分 不 同 的 安 装 方 式。 每 个Component 也 有 若 干 属 性 : 其 中,Destination 项 供 你 选 择 该Component 将 要 安 装 到 的 目 标 目 录 ;Installation 项 供 你 选 择 遇 到 相 同 文 件 名 时 的 处 理 方 式 ;SetupType 项 供 你 选 择 不 同 的 安 装 类 型 所 安 装 的 组 件(Component)。 下 面, 你 将 进 入Script Files 项, 它 是 你 的 安 装 程 序 的 脚 本 程 序, 如 果 你 只 想 创 建 一 个 标 准 的 安 装 程 序, 则 只 需 对 其 作 很 小 的 改 动 即 可。 由 于 系 统 没 有 为 你 生 成 创 建 程 序 组 的 部 分, 所 以 必 须 手 工 修 改function SetupFolders() 函 数, 在DialogShowSdSelectFolder() 中, 安 装 脚 本 已 经 将 要 生 成 的 目 标 程 序 组 存 入 到 变 量svDefGroup 中, 所 以 你 只 需 在SetupFolders() 中 加 入 以 下 几 行 内 容( 假 设 你 想 加 入 一 个 名 为“demo” 的 项, 执 行c:\demo.exe 文 件, 使 用 的 图 标 是“c:\demo.ico”, 工 作 目 录 为“c:\test”, 用“Ctrl + Alt + 1” 作 为 热 键, 并 且 该 程 序 项 将 作 为 该 程 序 组 的 第 一 项, 该 程 序 执 行 时 将 被 最 大 化)。
STRING szProgram ="c:\demo.exe"
STRING szParam = " "
LongPathToQuote (szProgram, TRUE);
LongPathToShortPath (szParam);
STRING szCommandLine = szProgram + " " + szParam;
AddFolderIcon(svDefGroup, "demo",szCommandLine, "c:\test","c:\demo.ico",0, "Ctrl +Alt + 1", REPLACE |RUN_MAXIMIZED);
现 在 修 改Resource 项, 其 中String Table 存 储 的 是 显 示 在 对 话 框 里 的 各 种 字 符 变 量。 例 如,FOLDER_NAME 是 你 的 程 序 组 的 缺 省 名 字,PRODUCT_KEY 是 你 的 程 序 项 的 运 行 文 件,PRODUCT_NAME 是 你 的 程 序 项 的 名 字,TITLE_CAPTIONBAR 将 显 示 在 你 的 安 装 程 序 背 景 窗 口 的 上 边 框 内,TITLE_MAIN 将 显 示 在 你 的 安 装 程 序 背 景 窗 口 上。 对 于Win 95、Winnt 4.0 的 用 户,UNINST_DISPLAY_NAME 将 显 示 在 控 制 面 板 的“ 添 加/ 删 除 程 序” 的 列 表 中。
在Setup file 这 一 项 中, 你 可 以 修 改 安 装 程 序 开 始 时 显 示 的 图 片, 只 需 将 编 辑 好 的 文 件 插 入 到Splash Screen 的Language Independent 下, 并 改 名 为Setup.bmp 即 可。
如 果 你 已 经 正 确 地 添 如 了 上 述 各 项 内 容, 则 下 面 所 要 做 的 事 情 就 是 打 开Media 项 单 击 右 键 选 择 其 中 的Media Build Wizard 以 便 创 建 应 用 程 序 的 物 理 存 储 结 构。 如 果 你 选 择 的 是 软 盘 方 式, 则 自 动 生 成 按 照Disk 划 分 的 目 标 程 序。 至 此, 利 用 工 具 软 件InstallShield 创 建 安 装 程 序 的 过 程 宣 告 结 束。