第 四 部 分 A ctiveX 控 件
第 8 章 使 用 ActiveX 控 件
ActiveX 控 件 是 可 执 行 组 件 , 设 计 它 们 的 目 的 是 将 其 嵌 入 窗 口 或 Web 页中,来实现 一 些 完 备 的 功 能 。 对 于 用 户 来 讲 , 它 们 与 我 们 在 前 一 章 中 遇 到 的 普 通 的W indows 控 件 十 分 相 像 , 这 些 普 通 控 件 是 通 过 对 话 编 辑 器 或 Gallery 添 加 到 程 序的 。 但 与 普 通 控 件 不 同 的 是 , ActiveX 控 件 在 Web 页 上 或 在 对 话 框 中 是 相 同 的 , 它 使 开 发 者 可 以 同 时 接 触 两 个 明 显 不 同 的 市 场 。
如 果 你 对 ActiveX 控 件 感 兴 趣 ( 作 为 一 个 开 发 者 , 你 理 应 如 此 ), 那 么 , 你 可 能 就是 因 为 想 使 用 它 们 或 想 编 写 它 们 。 Visual C++ 可 以 帮 助 你 实 现 这 两 个 目 的 。 本 章所 涉 及 的 是 这 一 主 题 的 前 半 部 分 ,介 绍 如 何 在 叫 做 包 容 器 的 客 户 应 用 程 序 中 使 用ActiveX 控 件 。第 9 章 和 第 10 章 将 涉 及 后 半 部 分 ,介绍编写 ActiveX 控 件 的 两 种不 同 方 法 。 本 章 将 展 现 有 关 ActiveX 控 件 的 介 绍 性 信 息 , 因 此 , 如 果 你 愿 意 了 解有 关 这 一 主 题 的 入 门 知 识 的 话 , 应 该 首 先 阅 读 这 一 章 。
为 了 使 讨 论 的 长 度 适 中 , 这 些 章 中 的 范 例 程 序 使 用 了 MFC 。 MFC 框 架 管 理 了 几百 个 ActiveX 编 程 的 详 细 资 料 , 它 使 得 编 写 包 容 器 或 ActiveX 控 件 不 再 比W indows 中 其 他 编 程 项 目 更 为 困 难 。 编 写 使 用 现 有 的 ActiveX 控 件 的 MFC 包 容器 应 用 程 序 通 常 需 要 很 少 的 或 者 根 本 就 不 需 要 什 么 基 础 知 识 。 尽 管 在 编 写
ActiveX 控 件 时 使 用 MFC 有 一 些 争 论 ( 完 全 是 因 为 大 小 的 问 题 , 我 们 将 在 下 一 章讨 论 ), 但 是 , 当 应 用 到 包 容 器 的 时 候 , 这 些 争 论 就 不 那 么 有 效 了 。 MFC 将 客 户
/服 务 器 之 间 的 交 互 过 程 包 装 得 非 常 完 全 ,以 致 于 如 果 没 有 MFC 类 库 或 类 似 支 持的 帮 助 ,就 难 以 编 写 一 个 包 容 器 应 用 程 序 。不 过 ,如 果 你 不 愿 意 使 用 MFC ,Active Template Library(ATL )提 供 了 另 一 种 方 案 。 Visual C++ 包 括 一 个 叫 做 AtlCon 的 范例 项 目 , 它 演 示 了 如 何 使 用 ATL 编 写 包 容 器 应 用 程 序 。 源 文 件 位 于 文 件 夹MSDN\Samples\Vc98\ATL\AtlCon 之 中 。第 10 章 为 包 容 器 应 用 程 序 的 开 发 仔 细 讨论 了 ATL 所 提 供 的 类 型 支 持 。
尽 管 本 章 和 随 后 两 章 所 讨 论 的 是 ActiveX 控 件 的 需 求 和 内 部 操 作 ,对 于 这 样 一 个大 主 题 , 它 们 所 介 绍 的 也 仅 仅 是 基 础 知 识 。 本 章 重 点 介 绍 一 些 方 法 , 应 用 这 些 方法 , Visual C++ 将 使 程 序 员 在 处 理 ActiveX 控 件 时 更 加 得 心 应 手 , 无 论 你 正 在 编写 的 是 一 个 控 件 ,还 是 使 用 该 控 件 的 包 容 器 应 用 程 序 。如 果 想 了 解 本 领 域 内 可 能对 Internet 编 程 来 说 十 分 重 要 的 内 容 , 请 参 考 专 业 资 料 ,例如 Kraig Brockschmidt 的 Inside OLE ( OLE 内 幕 ) 第 二 版 。 你 可 以 在 M S D N 在 线 帮 助 中 找 到 完 整 的 文本 。
背 景 知 识 简 介
ActiveX 这 个 名 字 是 新 的 ,但 是 ,它 的 技 术 是 成 熟 的 。ActiveX 控 件 形 成 了 M icrosoft
的 ActiveX 技 术 的 唯 一 组 成 部 分 ,它 们 是 建 立 在 Component Object Model( 组 件 对
象 模 型 ,C O M )和 OLE 的 基 础 之 上 。OLE 用 于 代 表 Object Linking and Embedding
( 对 象 链 接 与 嵌 入 ), 但 是 , 因 为 对 象 嵌 入 很 早 就 是 OLE 能 力 中 的 很 小 的 一 部分 , 所 以 M icrosoft 已 经 抛 弃 了 这 个 缩 略 语 。 目 前 , OLE 已 经 有 了 新 的 含 义 , 不再 具 有 版 本 号 了 。它 已 经 从 为 特 定 目 的 而 创 建 的 一 门 技 术 ,转 化 到 了 一 种 作 为 其他 技 术 ( 其 中 包 括 ActiveX )基 础 的 一 般 结 构 。 OLE 定 义 了 为 创 建 和 连 接 包 括 叫 做OLE 自 定 义 控 件 在 内 的 多 种 程 序 组 件 定 义 了 一 个 标 准 的 蓝 图 。 至 少 , 它 们 过 去被 称 作 OLE 控 件 , M icrosoft 现 在 叫 它 们 为 ActiveX 控 件 。
那 么 , 什 么 是 OLE/ActiveX 控 件 呢 ? 简 单 的 答 案 是 OLE/ActiveX 控 件 是 一 个 动 态链 接 库 , 它 是 作 为 基 于 COM 的 服 务 器 进 行 操 作 的 , 并 且 可 以 嵌 入 在 包 容 器 宿 主应 用 程 序 之 中 。 详 细 的 答 案 就 是 本 章 的 内 容 。 在 深 入 介 绍 它 的 工 作 方 式 之 前 , 让我 们 来 回 顾 一 下 OLE 控 件 的 历 史 。
吸 引 W indows 开 发 者 注 意 的 第 一 种 类 型 的 组 件 软 件 可 能 是 Visual Basic Extension 模 型 的 自 定 义 控 件 。 自 定 义 控 件 就 是 人 们 所 熟 悉 的 VBX , 它 是 为 添 加 到 控 件 名之 后 的 三 字 母 扩 展 名 而 命 名 的 。VBX 结 构 允 许 开 发 者 向 Visual Basic 程 序 创 建 有效 的 和 可 重 使 用 的 附 加 物 , 它 们 可 以 作 为 自 己 组 件 放 置 在 窗 口 之 中 , 在 Visual Basic 中 叫 做 表 单 。 VBX 控 件 的 优 点 有 三 方 面 :
-
VBX 能 够 实 现 虚 拟 显 示 并 与 用 户 交 互 。
-
Viusal Basic 应 用 程 序 能 够 通 过 叫 做 方 法 的 由 VBX 导 出 的
函 数 来 编 程
VBX 。
- 作 为 一 个 动 态 链 接 库 ,VBX 控 件 可 以 以 二 进 制 的 形 式 代
替 源 代 码 重 新使 用 。
正 如 我 们 将 要 看 到 的 , ActiveX 控 件 可 以 提 供 相 同 的 优 点 。
VBX 控 件 还 允 许 程 序 员 对 从 Visual Basic 中 继 承 下 来 的 一 些 局 限 性 进 行 补 偿 。例如 ,既然 VBX 控 件 通 常 是 用 C 或 汇 编 语 言 编 写 的 ,那 么 ,它 们 就 可 以 使 用 Visual Basic 中 没 有 的 指 针 来 帮 助 应 用 程 序 进 行 大 量 的 指 针 操 作 ,例 如 散 列 和 分 类 。VBX 模 型 的 一 个 问 题 是 , 它 不 是 为 向 其 他 语 言 和 平 台 完 美 地 进 行 移 植 而 设 计 的 。 例如 , 一 个 C++ 程 序 员 不 能 很 容 易 地 创 建 一 个 VBX 派 生 物 , 因 为 VBX 不 是 由 类表 示 的 。更 进 一 步 来 说 , VBX 模 型 是 一 个 捆 绑 在 Intel 处 理 器 分 段 结 构 之 上 的 16 位 标 准 。 不 过 , VBX 控 件 活 跃 的 市 场 已 经 证 明 了 , 组 件 软 件 可 以 在 W indows 开发 中 充 当 很 重 要 的 角 色 。
OLE 控 件 标 准 是 设 计 用 来 满 足 下 一 层 次 的 要 求 的 , 它 将 VBX 类 型 控 件 的 优 点 带给 了 具 有 W in32 编 程 功 能 的 所 有 语 言 。 这 些 语 言 包 括 Access Basic; Visual Basic for Applications(VBA), WordBasic 和 Visual Basic Scripting(VBScript) 。
考 虑 到 VBX 取 名 的 方 法 , OLE 控 件 通 常 被 叫 作 OCX , 这 是 因 为 OCX 扩 展 名 称通 常 添 加 到 文 件 名 的 后 面 。 有 了 OCX 和 VBX 共 有 的 约 定 , 就 足 以 说 明 其 中 一个 是 如 何 从 另 一 种 中 演 化 的 。 例 如 , M icrosoft 从 VBX 术 语 中 借 用 了 三 个 接 口 类型 , 它 们 定 义 了 OLE 控 件 和 它 的 客 户 、 包 容 器 应 用 程 序 之 间 的 通 信 。
-
M ethod s( 方 法 ): OLE 控 件 展 现 给 包 容 器 应 用 程 序 的 函
数 , 允 许 客 户调 用 该 控 件 。
-
Properties( 属 性 ): 控 件 和 包 容 器 内 部 的 公 共 数 据 , 它
可 以 用 来 向 对 方描 述 自 己 。 在 启 动 期 间 , 一 个 控 件 可 以 读 取 该 包 容 器 的 属 性 , 并 且 调整 它 的 初 始 化 过 程 , 以 便 它 与 包 容 器 的 外 观 和 特 性 相 配 。 当 控 件 处 于活 动 状 态 的 时 候 , 包 容 器 可 以 读 取 该 控 件 的 属 性 , 来 了 解 它 当 前 的 状态 , 如 果 控 件 允 许 的 话 , 还 可 以 重 写 属 性 , 以 改 变 控 件 的 行 为 。
-
Events (事 件 ): 控 件 发 送 给 包 容 器 的 通 知 , 本 章 稍 后 将
详 细 介 绍 这 个 问题 。 一 个 事 件 通 知 是 通 过 调 用 包 容 器 中 的 一 个 函 数 来 发 生 的 , 也 就 是众 所 周 知 的 “ 点 燃 ” 事 件 。
就 在 人 们 已 经 注 意 到 16 位 VBX 的 局 限 的 时 候 , OLE (那 些 日 子 是 叫 OLE2) 已 逐渐 趋 于 成 熟 , 从 逻 辑 上 来 说 , 它 已 成 为 以 OLE 控 件 形 式 存 在 的 VBX 的 接 任 者 , 现 在 叫 做 ActiveX 控 件 。
控 件 包 容 器
ActiveX 控 件 是 服 务 器 , 包 容 器 应 用 程 序 是 客 户 。 ActiveX 控 件 最 好 从 客 户 端 实现 , 因 此 本 节 一 开 始 是 讨 论 包 容 器 是 如 何 通 过 现 有 的 ActiveX 控 件 扩 展 其 能 力 ,
我 们 通 过 几 个 例 子 和 实 验 来 进 行 演 示 。 幸 运 的 是 , 现 成 的 样 本 非 常 多 , 我 们 可 以从 中 进 行 选 择 。Visual C++ 和 Internet Explorer 随 带 一 个 免 费 特 许 的 ActiveX 控 件集 , 其 中 的 一 部 分 在 图 8-1 的 Gallery 对 话 中 列 出 。 如 果 想 打 开 Gallery 对 话 , 请从 Project( 项 目 ) 菜 单 中 选 择 Add To Project ( 添 加 到 项 目 ) 命 令 , 并 单 击 次 级菜 单 上 的 Components And Controls ( 组 件 和 控 件 )。 然 后 双 击 Registered ActiveX Controls 文 件 夹 来 显 示 控 件 列 表 。
图 8-1 Gallery 中 的 ActiveX 控 件
表 8-1 列 出 了 M icrosoft 提 供 的 免 费 特 许 的 ActiveX 控 件 。 如 果 在 Gallery 中 选 中了 一 个 控 件 图 标 , More Inf o( 其 他 信 息 ) 按 钮 是 启 用 的 , 那 么 , 这 就 意 味 着 该 控件 可 以 通 过 在 线 帮 助 来 描 述 自 己 。 单 击 More Info 按 钮 可 以 查 看 该 控 件 的 资 料 。
表 8-1 一 些 可 以 从 M icrosoft 中 提 供 的 ActiveX 控 件文 件 名 说 明
AniBtn32.ocx Animated button ( 动 画 按 钮 ): 使 用 位 图 或 元 文 件来 创 建 一 个 带 有 变 化 的 图 像 的 按 钮
BtnMenu.ocx M enu ( 菜 单 ): 显 示 一 个 按 钮 和 一 个 弹 出 式 菜 单 , 如 图 8-2 所 示 。
DBGrid32.ocx Grid ( 网 格 ): 以 标 准 网 格 样 式 显 示 单 元 格 的 电 子表 控 件 。 用 户 可 以 选 定 单 元 格 ( 与 老 的 G rid32 控 件不 同 ), 并 直 接 将 数 据 输 入 单 元 格 。 还 可 以 由 包 容器 通 过 编 程 来 填 充 单 元 格 ,或 为 了 自 动 更 新 而 捆 绑到 记 录 集
IELabel.ocx label( 标 签 ): 以 一 定 角 度 或 沿 指 定 的 曲 线 显 示 文字
续 表
IEMenu.ocx Pop-up menu ( 弹 出 式 菜 单 ): 显 示 一 个 弹 出 式 菜单 , 如 图 8-2 所 示
IEPopWnd.ocx IEPrld.ocx
Pop-up windo w( 弹 出 窗 口 ): 在 弹 出 式 窗 口 中 显 示
M T M L 文 档
Preloade r( 预 加 载 器 ): 下 载 指 定 URL 的内容,并将 它 存 储 在 高 速 缓 存 中 。完 成 下 载 之 后 ,该 控 件 将触 发 一 个 事 件
IEStock.ocx stock ticker( 股 市 自 动 接 收 机 ): 以 指 定 的 固 定 周期 下 载 和 显 示 URL 的 内 容 。 正 如 它 的 名 字 所 暗 示的 , 该 控 件 对 于 显 示 连 续 变 化 的 数 据 时 特 别 有 用 , 如 图 8-2 所示
IETimer.ocx Time r( 计 时 器 ): 一 个 不 可 见 的 控 件 , 以 一 定 的 周期 触 发 一 个 事 件
keySta32.ocx Key ( 键 ) 状 态 : 显 示 并 有 选 择 地 修 改 Cap Lock, Num Lock,Insert 和 Scroll Lock 键 的 状 态
续 表
M arquee.ocx M arquee ( 大 屏 幕 ): 水 平 或 垂 直 方 向 滚 动 H T M L 文 件 中 的 文 字 ,可 以 被 配 置 成 改 变 滚 动 的 数 量 和 延迟 时 间
MCI32.ocx Multimedia ( 多 媒 体 ): 管 理 M edia Control Interface(MC I,媒 体 控 制 接 口 ) 设 备 的 多 媒 体 文 件 的录 音 和 播 放 。 该 控 件 可 以 显 示 一 套 用 于 将 M C I 命令 传 向 设 备 的 推 压 式 按 钮 , 这 些 设 备 包 括 音 频 板 、M IDI 序 列 发 生 器 、 CD-ROM 驱 动 器 、 音 频 CD 播放 器 、视 频 光 盘 播 放 器 以 及 视 频 磁 带 录 音 机 和 播 放器 。 该 控 件 还 支 持 Video for Windows AVI 文 件 的播 放
MSCal.ocx Calenda r( 日 历 ): 在 屏 幕 上 显 示 的 日 历 , 用 户 可 以从 中 选 择 日 期 。
续 表
MSChart.ocx Char t( 图 表 ): 一 个 高 级 的 图 表 控 件 , 它 接 收 数 字数 据 , 然 后 显 示 几 种 图 表 类 型 之 一 , 包 括 线 、 条 和栏 形 图 表 。 该 控 件 的 绘 制 以 两 维 或 三 维 形 式 显 示 , 见 图 8-2 和 图 8-3
MSComm32.oc x
Comm ( 通 信 ): 为 串 行 通 信 提 供 支 持 , 处 理 发 送到 串 行 口 和 从 串 行 口 接 收 的 数 据 传 输
MSMask32.ocx M asked edit( 掩 码 编 辑 ): 一 个 增 强 的 编 辑 控 件 , 它 可 以 确 保 输 入 符 合 预 先 定 义 好 的 格 式 。例 如 ,一个 “ ##:##?? ” 的 约 束 将 输 入 限 制 为 时 间 格 式 。 例如 “ 11:18 AM ”
PicCLP32.ocx Picture cli p( 图 片 剪 辑 ):显 示 位 图 中 剪 下 的 矩 形 区域 ,可 以 根 据 指 定 的 行 数 和 列 数 将 位 图 划 分 为 网 格
在 表 8-1 中 , 一 些 文 件 名 的“ IE ”前缀代表 Internet Explorer, 用 来 表 明 该 控 件 是 那个 程 序 所 包 括 的 。 OCX 文 件 可 以 位 于 系 统 之 上 的 任 何 地 方 , 但 通 常 都 放 在W indows\OCCache 和 W indows\System 子 文 件 夹 中 。 如 果 由 于 某 种 原 因 , 你 没 有了 这 些 文 件 , 并 且 想 沿 着 本 章 的 演 示 继 续 下 去 , 请 从 本 书 配 套 光 盘 的 OCX 文 件
夹 中 将 这 些 文 件 复 制 到 你 的 OCCache,System 或 System32 文 件 夹 中 。不 过 , 不 要假 定 这 个 小 小 的 样 本 就 代 表 了 ActiveX 控 件 中 的 最 新 词 汇 。市 场 上 每 天 都 有 新 控件 出 现 , 你 可 以 在 自 己 的 应 用 程 序 中 免 费 使 用 它 们 的 演 示 版 。 如 果 你 想 浏 览Internet 寻 找 一 些 控 件 , 如 下 两 个 地 址 可 以 提 供 免 费 下 载 , 并 且 提 供 了 到 应 用 程序 开 发 者 感 兴 趣 的 Wes 站 点 的 链 接 :
http://www.mcirosoft.com/com/ http://www.actrvex.com
图 8-2 几 个 在 包 容 器 上 出 现 的 M icrosoft ActiveX 控件
当 你 从 配 套 光 盘 或 另 一 个 来 源 向 自 己 的 硬 盘 复 制 控 件 文 件 的 时 候 , 请 使 用 在vc98\Bin 于 文 件 夹 中 找 到 的 RegSvr32.exe 实 用 程 序 对 其 进 行 注 册 。 RegSvr32 调用 该 控 件 的 自 注 册 函 数 , 该 函 数 将 有 关 这 个 控 件 的 信 息 写 入 系 统 注 册 表 。在 注 册控 件 之 前 , 包 容 器 应 用 程 序 一 般 是 没 有 办 法 来 为 嵌 入 而 定 位 它 的 。 单 击 Start 按钮 , 并 从 Run 对 话 中 执 行 Regsvr32 ,在 命 令 行 中 指 定 一 个 ocx 文 件 :
regsvr32\windows\occache\anibtm32.ocx
如 果 你 的 系 统 PATH 语 句 没 有 包 括 VC98\Bin 文 件 夹 , 那 么 , 请 在 键 入 regsvr32 的 时 候 指 定 正 确 的 路 径 。 如 果 想 解 除 对 ActiveX 控 件 的 注 册 ( 也 就 是 说 , 从 注 册表 中 删 除 它 的 条 目 ),请 按 照 相 同 的 方 式 再 运 行 一 遍 RegSvr32 ,但 是 要 在 文 件 名 前包 括 开 关 “ /u ”。 解 除 对 控 件 的 注 册 并 不 从 磁 盘 上 删 除 它 的 ocx 文 件 。
你 还 可 以 通 过 单 击 Tools ( 工 具 ) 菜 单 上 的 Register Contro l( 注 册 表 控 件 ) 命 令来 从 Visual C++ 中 运 行 Regsvr32 。 不 过 , 在 默 认 状 态 下 , 该 命 令 假 定 你 想 在 结 构之 下 注 册 一 个 控 件 , 因 此 , 它 仅 仅 注 册 项 目 目 标 文 件 。 第 13 章 将 解 释 如 何 修 改像 Register Contro l( 注 册 表 控 件 ) 这 样 的 工 具 , 以 便 你 可 以 将 任 何 文 件 指 定 为 命令 行 参 数 , 而 不 仅 仅 是 当 前 项 目 中 一 个 文 件 。
向 Web 页 添 加 ActiveX 控 件
在 向 项 目 插 入 ActiveX 控 件 之 前 , 你 可 能 想 先 看 一 看 控 件 。 你 只 需 一 个 文 本 编 辑
器 和 一 个 支 持 ActiveX 的 浏 览 器 即 可 , 一 个 用 于 创 建 H T M L 文 档 , 另 一 个 用 于查 看 它 。 H T M L 代 表 Hypertext Markup Language( 超 文 本 标 记 语 言 ), 它 定 义 了 一个 简 单 的 约 定 , 以 创 建 在 各 种 书 和 文 章 中 说 明 良 好 的 Web 页 。 仅 仅 通 过 几 分 钟的 学 习 , 你 就 可 以 了 解 到 有 关 H T M L 的 许 多 知 识 。Visual C++ 文 本 编 辑 器 可 以 编辑 H T M L 文 件 , 在 这 方 面 , 它 的 能 力 有 限 , 但 可 以 在 显 示 窗 口 中 为 标 记 和 其 他文 档 元 素 加 上 颜 色 。
为 了 在 H T M L 文 档 中 使 用 ActiveX 控 件 , 你 必 须 首 先 找 到 该 控 件 的 32 个 数 字 的类 标 符 号 码 。我 们 将 在 下 一 章 中 更 加 详 细 地 介 绍 CLSID ,现 在 , 你 只 需 知 道 如 何查 看 该 号 码 即 可 。注 册 表 编 辑 器 为 找 到 控 件 的 CLSID 提 供 了 一 种 方 法 。单 击 Start 按 钮 , 并 在 Run 对 话 中 键 入 regedit 或 regedit32 ,究 竟 键 入 哪 个 , 需 取 决 于 系 统 是W indows 95 还 是 W indows NT 。 单 击 注 册 表 编 辑 器 Edit 菜 单 上 的 Find 命 令 , 然后 键 入 该 控 件 的 文 件 名 。
例 如 , 在 注 册 表 编 辑 器 中 , 搜 索 ietimer.ocx , 将 找 到 注 册 表 中 的 如 下 层 次 :
位 于 窗 口 底 部 的 32 位 数 字 的 号 码 代 表 Timer ActiveX 控 件 的 CLSID 。 用 相 同 的方 式 搜 索 ielabel.ocx 将 显 示 代 表 Label ActiveX 控 件 的 CLSID :
99b42120-6ec7-11cf-abc7-00aa00a47dd2
应 用 手 头 的 这 两 个 号 码 ,你 可 以 编 写 一 个 简 单 的 H T M L 文 档 ,使用 Timer 和 Label
控 件 来 显 示 文 本 , 它 们 不 停 地 翻 滚 , 永 无 休 止 地 从 彩 色 框 底 部 弹 出 :
如 果 想 看 动 画 , 请 使 用 Internet Explorer 这 样 的 浏 览 器 , 或 任 何 可 察 知 ActiveX
的 创 作 工 具 , 来 打 开 本 书 配 套 光 盘 Code\chapter.08 文 件 夹 的 Tumble.htm 文 档 。在 Internet Explorer 3.0 及 以 后 版 本 中 , 单 击 Open 命 令 , 并 找 到 该 文 档 , 然 后 双击 打 开 它 。 列 表 8-1 显 示 了 Tumble.htm 文 档 的 内 容 。
列 表 8-1 Tumble.htm 文 档
classid="clsid:99b42120-6ec7-11cf-a6c7-00aa00a47dd2"
id=label
width=150
height=150
>
<PARAM NAME="Angle" value="0">
<PARAM NAME="Alignment" value="7">
<PARAM NAME="BackStyle" value="1">
<PARAM NAME="BackColor" value="255">
<PARAM NAME="FontItalic" value="-1">
<PARAM NAME="FontUnderline" value="-1">
<PARAM NAME="Caption" value="Tumbling text!">
Test Containe r实 用 程 序
Visual C++ 提 供 了 一 个 名 为 Test Container 的 工 具 , 正 如 它 的 名 字 所 暗 示 的 那 样 , 利 用 它 , 你 不 用 创 建 自 己 的 包 容 器 应 用 程 序 , 便 可 加 载 和 试 验 已 注 册 的 ActiveX 控 件 。 单 击 Tool s( 工 具 ) 菜 单 上 的 ActiveX Control Test Containe r( AvtiveX 控 件
测 试 包 容 器 ) 命 令 , 来 启 动 Test Container, 如 图 8-3 所 示 , 它 带 有 两 个 典 型 的AciveX 控 件 ,分 别 叫 做 Button Menu 和 Microsoft Chart ,在 配 套 光 盘 中 均 有 提 供 。该 程 序 的 可 执 行 文 件 是 Tstcon32.exe ,在 Common\Tools 子 文 件 夹 中 。
如 果 想 在 Test Container 中 加 载 控 件 , 请 从 Edit( 编 辑 ) 菜 单 中 选 择 Insert New Contro l( 插 入 新 控 件 ) 命 令 , 或 者 单 击 工 具 栏 上 的 New Contro l( 新 建 控 件 ) 按钮 , 然 后 从 Insert Contro l( 新 建 控 件 ) 对 话 中 显 示 的 列 表 中 选 择 所 需 要 的 控 件 , 在 Text Containe r( 文 本 包 容 器 ) 窗 口 中 , 一 个 控 件 可 以 首 次 仅 仅 作 为 小 框 出 现 ; 如 果 是 这 样 的 话 ,请 通 过 拖 动 一 角 来 调 整 控 件 。控 件 的 初 始 尺 寸 取 决 于 该 控 件 从包 容 器 中 申 请 的 启 动 尺 寸 ( 如 果 有 的 话 ) 。下 一 章 中 的 一 个 范 例 项 目 演 示 了 用 MFC 编 写 的 ActiveX 控 件 是 如 何 调 用 ColeControl::SetInctialSize 函 数 来 建 立 它 的 启 动尺 寸 的 。 如 图 8-3 所 演 示 的 帮 样 , 几 个 控 件 可 以 在 Test Container 中 同 时 运 行 ; 在 每 个 控 件 窗 口 所 圈 定 的 矩 形 框 内 单 击 ,可 以 从 活 动 控 件 中 进 行 选 择 。当 一 个 控件 被 选 中 之 后 , 尺 寸 控 制 柄 将 出 现 在 它 的 边 框 之 上 , Test Container 工 具 栏 上Invoke Methods and Properties ( 调 用 方 法 和 属 性 ) 工 具 成 为 可 以 启 用 的 。
图 8-3 通过 Tools (工具)菜单调用的 Test Container 实用程序
被 选 中 的 ActiveX 控 件 可 以 通 过 它 的 方 法 函 数 编 程 。 单 击 Invoke Method s( 调 用方 法 ), 或 从 Control 菜 单 中 选 择 相 应 的 命 令 , 来 打 开 如 图 8-4 所 示 的 Invoke M ethods 对 话 。 M ethod Nam e( 方 法 名 ) 组 合 框 的 下 拉 式 列 表 将 所 有 控 件 方 法 归类 , 大 致 可 分 为 普 通 方 法 和 属 性 方 法 两 类 。 普 通 方 法 在 下 拉 式 列 表 中 被 标 为M ethod 。 属 性 方 法 被 标 为 PropGet 或 PropPut , 究 竟 标 记 为 哪 一 个 , 主 要 取 决 于它 们 所 对 应 的 是 检 索 属 性 值 的“ 获 取 ” 方 法 , 还 是 写 属 性 值 的“ 放 置 ” 方 法 。 例如 图 8-4 显 示 了 Button Menu 控 件 导 出 了 两 种 方 法 类 型 , 允 许 包 容 器 通 过 获 取 和放 置 方 法 读 或 写 该 控 件 的 Caption 属 性 ( 就 是 出 现 在 按 钮 上 的 文 字 )。
图 8-4 通过 Test Container 的 Invoke Methods( 调 用 方 法 ) 对 话 对 控 件 编 程
为 了 向 Button Menu 控 件 的 弹 出 式 菜 单 中 添 加 一 个 项 目 ,包 容 器 调 用 AddItem 方法 。 我 们 可 以 使 用 Invoke Method s( 调 用 方 法 ) 对 话 框 做 相 同 的 事 情 。 在 Method Name 框 中 选 择 AddItem ,然 后 在 标 签 为 Parameter Value( 参 数 值 ) 的 编 辑 框 中 键入 所 需 求 的 文 字 。 当 你 单 击 Invoke 按 钮 的 时 候 , Test Container 将 调 用 AddItem 方 法 ,将 文 字 添 加 到 控 件 的 菜 单 命 令 列 表 之 中 。为 了 看 到 新 命 令 ,可 以 关 闭 Invoke M ethods( 调 用 方 法 ) 对 话 , 并 单 击 Button Menu 控 件 。
当 为 一 个 整 数 类 型 的 属 性 选 中 放 置 方 法 的 时 候 , 你 必 须 还 在 Parameter Type( 参数 类 型 ) 框 中 做 出 像 VT_14 这 样 的 选 择 。 通 过 先 调 用 相 应 的 获 取 方 法 , 并 记 下返 回 值 , 或 者 在 Parameter Type( 参 数 类 型 ) 框 中 选 择 VT_UNKNOWN , 可 以 确定 正 确 的 参 数 类 型 。如 果 放 置 方 法 有 多 个 参 数 , 请 依 次 在 Parameters 列 表 框 中 选中 每 个 变 量 , 在 键 入 值 后 单 击 Set Value( 设 置 值 ) 按 钮 。 当 所 有 的 值 都 在 Value 列 中 正 确 出 现 之 后 , 单 击 Invoke 来 将 参 数 传 递 给 方 法 。
像 BackColor 这 样 的 颜 色 属 性 是 24 位 COLORREF 值 , 它 可 以 表 示 为 VT_14 整数 类 型 。 COLORREF 值 的 三 个 字 节 对 应 于 整 个 颜 色 的 红 、 绿 和 蓝 组 件 , 请 参 阅第 5 章 中 Color 范 例 项 目 。 尽 管 COLORREF 值 作 为 像 OXFF 这 样 的 十 六 进 制 数来 表 达 更 为 容 易 , Invoke Method 对 话 仅 仅 能 识 别 以 十 进 制 格 式 键 入 的 值 。 如 果想 在 对 话 中 输 入 一 个 新 颜 色 值 , 可 键 入 16 , 711 , 680 来 代 表 深 蓝 , 65, 280 代 表深 绿 , 255 代 表 深 红 。 在 Parameter Type( 参 数 类 型 ) 框 中 选 择 VT_COLO R , 将启 用 标 签 为 Choose Colo r( 选 择 颜 色 ) 的 按 钮 显 示 范 例 颜 色 的 种 类 。 不 过 , 该 选项 目 前 不 能 将 选 择 的 颜 色 正 确 转 换 成 有 效 的 方 法 参 数 。
许 多 控 件 提 供 它 们 自 己 的 属 性 表 ,Test Contain e(r 测 试 包 容 器 )可 以 通 过 Properties
工 具 按 钮 访 问 它 们 。 单 击 工 具 将 导 致 Test Container 向 控 件 发 布 一 条OLEIVERB_PROPERTIES 命 令 , 告 诉 它 显 示 属 性 表 ( 如 果 有 的 话 )。 双 击 控 件 的边 框 还 可 以 调 用 该 命 令 , 其 功 能 与 从 Text Container 的 Edit ( 编 辑 ) 菜 单 中 选 择Properties 一 样 。
通 过 可 移 动 的 分 隔 条 , 可 以 把 Test Container 窗 口 分 成 两 个 水 平 的 窗 格 。 底 部 视窗 一 般 显 示 所 选 中 控 件 触 发 事 件 的 实 时 记 录 。记 录 也 叫 做 事 件 日 志 ,可 以 重 新 确定 路 线 , 方 法 是 从 Test Container 的 Option s( 选 项 ) 菜 单 中 选 择 Loggin g( 记 录 ) 命令。在 ActiveX 控 件 开 发 期 间 , 利 用 事 件 日 志 , 可 以 节 省 开 发 人 员 的 时 间 , 让你 快 速 测 试 控 件 事 件 是 否 已 被 正 确 触 发 。 在 下 一 章 , 当 我 们 测 试 范 例 ActiveX 控件 时 , 我 们 还 会 看 到 事 件 日 志 的 功 能 。
向 对 话 框 添 加 ActiveX 控 件
尽 管 从 CWnd 中 派 生 出 来 的 任 何 类 都 可 以 嵌 入 一 个 ActiveX 控 件 , MFC 还 需 为对 话 包 容 器 进 行 优 化 。 这 是 很 好 的 , 因 为 ActiveX 控 件 与 普 通 控 件 一 样 , 通 常 也是 在 对 话 框 中 出 现 的 。 这 种 优 化 是 在 Visual C++ 中 反 映 出 来 的 , 它 提 供 了 许 多 特征 , 可 以 帮 助 你 为 第 6 章 介 绍 的 基 于 对 话 的 类 ( 就 是 CDialog, CPropertyPage, CFormView, CRecordView 或 CDaoRecordView )使 用 的 ActiveX 控 件 创 建 包 容 器 应用 程 序 。 向 对 话 添 加 已 经 注 册 的 类 仅 需 单 击 几 下 鼠 标 即 可 , 先 是 在 Gallery 中 , 然 后 是 在 Visual C++ 对 话 编 辑 器 中 。
在 某 些 方 面 , 对 话 编 辑 器 为 ActiveX 控 件 比 Test Container 实 用 程 序 提 供 了 更 为方 便 的 测 试 区 。 其 中 之 一 便 是 , 即 使 控 件 没 有 提 供 它 自 己 的 属 性 表 , 对 话 编 辑 器也 将 显 示 一 个 Properties ( 属 性 ) 框 。 例 如 , 如 果 你 双 击 了 Test Container 窗 口 中Button Menu 控 件 的 边 框 , 将 会 出 现 一 个 消 息 框 , 说 “ Property Page are not supporte d( 属 性 页 未 支 持 )”。 但 是 , 如 果 你 在 对 话 编 辑 器 中 为 相 同 的 命 令 调 用 了Properties ( 属 性 ) 命 令 , 那 么 编 辑 器 将 向 它 的 一 般 的 Properties( 属 性 ) 框 添 加一 个 标 签 为 A ll( 所 有 ) 的 颜 色 选 项 卡 , 它 列 出 了 该 控 件 的 属 性 , 并 允 许 你 进 行编 辑 。A ll 选 项 卡 比 在 Test Container 实 用 程 序 中 引 用 方 法 所 包 含 的 过 程 提 供 了 到控 件 属 性 的 更 为 方 便 的 访 问 。
下 面 查 看 对 话 框 中 正 在 工 作 的 ActiveX 控 件 的 方 法 : 无 需 任 何 编 程 。 对 话 编 辑 器自 己 作 为 控 件 客 户 ,在 这 里 ,我 们 以 Animate Button ActiveX 控 件 为 例 进 行 演 示 。
如 果 你 愿 意 在 这 些 步 骤 中 用 完 整 的 项 目 练 习 , 请 打 开 本 书 配 套 光 盘 上 的Code\Chapter.08\AniButtn 文 件 夹 中 的 Dewo.dsw 文 件 。 运 行 Demo.exe , 并 单 击Help 菜 单 上 的 About 命 令 。
步 骤 1 : 创 建 一 个 空 项目
使 用 AppWizard 创 建 一 个 空 项 目 ,为 它 取 一 个 喜 欢 的 名 字 ,并 接 受 所 有 的 默 认 设置 。 在 默 认 状 态 下 , AppWizard 通 过 在 步 骤 3 中 打 开 ActiveX Controls 复 选 框 , 来 使 得 每 个 应 用 程 序 都 成 为 ActiveX 控 件 包 容 器 :
选 择 ActiveX Controls 选 项 , 将 运 行 大 量 的 其 他 代 码 , 来 支 持 控 件 包 容 方 式 , 但是 , 由 框 架 来 管 理 每 件 东 西 。 在 表 面 上 , 该 选 项 只 是 把 这 行 添 加 到 应 用 程 序 类InitInstance 函 数 :
AfxEnableControlContainer();
将 这 行 添 加 到 StdAfx.h 文 件 :
#indude<afxdisp.h> //MFC OLE automation classes.
如 果 你 有 一 个 MFC 项 目 想 转 化 为 控 件 包 容 器 , 请 使 用 文 本 编 辑 器 来 手 工 实 现 上述 改 变 。如 果 想 得 到 相 同 的 结 果 ,还 可 以 将 位 于 Gallery 的 Visual C++ Components 文 件 夹 的 ActiveX Control Containment 组 件 添 加 到 项 目 中 。
步 骤 2 : 插 入 ActiveX 控 件
当 AppWizard 完 成 创 建 项 目 之 后 ,请从 Proje c (t 项 目 )菜 单 中 选 择 Add To Project
( 添 加 到 项 目 ) 命 令 , 然 后 单 击 次 级 菜 单 上 的 Components And Controls 命 令 来显 示 Gallery 对 话 。 在 Registry ActiveX Controls 文 件 夹 中 选 择 Anibutton Control 图 标 , 并 单 击 Insert 按 钮 。 接 受 Confirm Classes( 确 认 类 ) 对 话 中 的 默 认 设 置 , 然 后 退 出 Gallery 。
因 为 你 还 可 以 从 对 话 编 辑 器 中 向 项 目 添 加 ActiveX 控 件 , 所 以 , 进 入 Gallery 不
是 必 需 的 。 当 对 话 编 辑 器 的 工 作 区 域 出 现 ( 在 下 一 步 中 介 绍 ) 的 时 候 , 请 右 击 工 作区 中 的 任 何 地 方 , 并 从 上 下 文 菜 单 中 选 择 Insert ActiveX Contro l。 该 操 作 将 打 开与 Gallery 对 话 中 显 示 的 相 同 的 注 册 控 件 列 表 。 只 需 单 击 列 表 中 的 一 个 控 件 , 便可 以 将 它 添 加 到 对 话 中 去 。
步 骤 3 : 向 对 话 添 加 控 件 并 初 始 化
从 技 术 上 讲 ,对 话 包 容 器 不 是 ActiveX 控 件 的 父 窗 口 ,而 仅 仅 提 供 一 个 站 点( C O M 这 样 称 呼 它 ), 这 个 词 不 应 该 从 字 面 上 来 理 解 。 站 点 的 作 用 是 嵌 入 式 对 象 及 其 包容 器 之 间 的 中 间 人 ,在 这 种 情 况 下 ,是 处 理 ActiveX 控 件 和 对 话 窗 口 之 间 的 通 信 。任 何 对 话 都 可 以 为 实 现 演 示 的 目 的 而 做 到 过 一 点 , 甚 至 是 项 目 的 About ( 关 于 ) 框 也 是 如 此 。我 们 甚 至 不 需 要 为 使 用 新 控 件 而 创 建 项 目 。 我 们 所 需 要 的 只 是 一 个站 点 , 以 及 对 话 编 辑 器 中 模 拟 的 Abou t( 关 于 ) 框 。
双 击 Workspac e( 工 作 空 间 ) 窗 口 ResourceView 窗 格 中 的 IDD_ABOUTBOX ,来启 动 对 话 编 辑 器 , 并 加 载 Abou t( 关 于 ) 框 。 当 编 辑 器 的 窗 口 出 现 的 时 候 , 它 的控 件 窗 口 就 具 有 了 一 个 新 按 钮 , 用 来 代 表 插 入 的 Anibutton 控 件 。
这 个 工 具 并 不 是 在 工 具 栏 上 永 久 存 在 ,它 是 仅 仅 为 这 个 项 目 而 使 用 的 。如 果 想 向另 一 个 项 目 添 加 Anibutton 组 件 , 你 必 须 再 返 回 步 骤 2 去 插 入 一 个 控 件 。 至 于 使控 件 进 入 对 话 工 作 区 , 没 有 特 殊 的 处 理 要 求 。 只 需 像 你 对 任 何 控 件 工 具 操 作 的 那样 , 把 它 拖 入 对 话 框 , 然 后 右 击 对 话 中 选 中 的 控 件 , 并 且 单 击 上 下 文 菜 单 上 的
Properties ,来 调 用 该 控 件 的 Properti e(s 属 性 )对 话 。图 8-5 显 示 了 Anibutton Control
Properties 对 话 的 Control 选 项 卡 , 这 就 是 我 们 将 开 始 初 始 化 控 件 的 地 方 。
图 8-5 Anibuton Control Properties 对 话 的 Control 选项卡
注 意 :如 果 注 册 表 中 的 某 个 条 目 丢 失 了 ,Visual C++ 将 显 示 一 条 消 息 ,说 Animated Button 控 件 需 要 一 个 设 计 时 间 许 可 证 。 如 果 你 按 照 这 里 列 出 的 步 骤 操 作 时 出 现了 这 条 消 息 , 这 就 表 明 , 你 要 么 装 了 只 具 有 USER 权 限 的 Visual C++, 或 者 是 注
册 表 已 经 崩 溃 了 。 重 新 安 装 Visual C++ 好 象 是 唯 一 的 解 决 方 案 。 如 果 想 了 解 这个 问 题 , 以 及 其 他 ActiveX 控 件 的 列 表 , 请 访 问 这 个 Knowedge Base 站 点 :
http://support.microsoft.com/support/kb/articles/Q155/0/59.asp
如 下 的 列 表 将 引 导 你 完 成 演 示 程 序 所 需 要 的 初 始 化 设 置 。 这 些 设 置 是 在
Properties ( 属 性 ) 对 话 的 五 个 选 项 卡 中 完 成 的 。
-
Contro l( 控 件 ) 选 项 卡 : 在 Control 选 项 卡 中 单 击 组
合 框 , 并 选 中 如图 8-5 所 示 的 条 目 。
-
General 2 ( 常 规 2 ) 选 项 卡 : 如 果 想 指 定 在 控 件 窗
口 中 显 示 的 数 字 , 请 在 Captain ( 标 题 ) 框 中 键 入 Click Here! 。 将 复 选 框 设 置 为H ideFocusBox , 这 可 以 防 止 在 控 件 具 有 焦 点 的 时 候 点 线 矩 形 出 现 在 标
题 文 字 的 周 围 。
- Frame Settin g( 帧 设 置 ) 选 项 卡 : Anibuton 控 件 可 以 保
持 大 量 充 当 按钮 图 像 的 位 图 。 对 于 我 们 的 演 示 , 任 何 位 图 均 可 行 , 其 中 也 包 括W indows 文 件 夹 中 的 系 统 墙 纸 图 像 文 件 。 单 击 Load 按 钮 , 并 找 到
W indows 文 件 夹 来 显 示 B M P 文 件 列 表 ,这 些 文 件 具 有 像 Black Thatch, BlueRivets, Sandstone 以 及 Triangles 这 样 的 名 字 。从 列 表 中 选 中 一 个 文件 , 并 单 击 Insert 按 钮 。 再 次 单 击 Load 按 钮 , 并 重 复 上 述 过 程 , 直 至已 经 向 控 件 添 加 了 五 六 个 不 同 的 位 图 。 完 成 之 后 , 你 可 以 通 过 移 动 滚
动 条 来 检 查 每 个 位 图 条 目 。
-
Fonts ( 字 体 ) 选 项 卡 : 这 是 你 为 按 钮 上 出 现 的 标
题 选 中 字 体 的 地 方 。图 8-6 中 的 字 体 是 Times New Roman, 斜 体 , 磅 值 为 32 。
-
Colors ( 颜 色 ) 选 项 卡 : 因 为 位 图 图 像 是 填 充 控 件
窗 口 的 , 所 以 , 背景 颜 色 无 关 紧 要 。 通 过 从 Property Name 文 本 框 中 选 定 ForeColor, 并单 击 白 色 块 来 设 置 标 题 文 字 的 前 景 颜 色 。
在 本 章 前 面 的 章 节 中 , 已 经 提 到 了 , 并 不 是 所 有 的 ActiveX 控 件 都 提 供 自 己 的 属性 表 , 但 Anibuton 控 件 就 是 这 样 。 上 面 列 出 的 属 性 页 是 在 AniBtn32.ocx 可 执 行文 件 中 包 含 的 资 源 , 对 话 编 辑 器 选 取 并 添 加 自 己 的 General 和 All 选 项 卡 , 以 形成 如 图 8-5 所 示 的 完 整 的 Propertie s( 属 性 ) 对 话 。 这 就 意 味 着 , 你 无 需 与 两 个 对话 进 行 交 互 , 这 两 个 对 话 一 个 由 控 件 提 供 , 而 另 一 个 由 对 话 编 辑 器 提 供 。
步 骤 4 : 测 试 控 件
通 过 拖 动 尺 寸 控 制 柄 在 对 话 工 作 区 内 扩 大 控 件 , 然 后 , 在 对 话 框 的 中 心 重 新 定 位控 件 窗 口 。 打 开 D ialog ( 对 话 ) 工 具 栏 上 的 编 辑 器 测 试 模 式 开 关 :
在 新 ActiveX 控 件 窗 口 内 部 单 击 几 次 , 循 环 显 示 位 图 图 像 , 其 中 一 个 如 图 8-6 所示 。 单 击 对 话 的 O K 按 钮 来 返 回 到 编 辑 模 式 。
图 8-6 典 型 对 话 中 的 Anibutton ActiveX 控件
既 然 我 们 对 ActiveX 控 件 可 以 实 现 的 许 多 形 式 已 经 有 了 一 定 的 了 解 , 那 么 , 让 我
们 来 分 析 其 中 的 一 个 , 看 看 它 是 如 何 工 作 的 。
在 包 容 器 和 ActiveX 控 件 之 间 进 行 通 信
ActiveX 控 件 服 务 器 可 以 非 常 有 效 地 与 一 个 客 户 进 程 关 联 起 来 。 一 个 ActiveX 控件 通 常 在 作 为 动 态 链 接 库 工 作 , 这 就 意 味 着 该 控 件 在 客 户 进 程 的 地 址 空 间 执 行 , 但 这 并 非 一 定 如 此 。 由 于 这 个 原 因 , ActiveX 控 件 通 常 称 为 进 行 中 的 服 务 器 。 包容 器 程 序 在 加 载 普 通 DLL 的 时 候 , 并 不 是 通 过 调 用 LoadLibrary API 函 数 来 加 载这 个 ActiveX 控 件 。相 反 ,它 调 用 CoCreateInstance 来 申 请 Component Object Model 框 架 的 运 行 时 间 服 务 来 加 载 库 ,并 在 客 户 和 控 件 服 务 器 之 间 建 立 初 始 通 信 点 。这个 通 信 点 叫 做 接 口 。 包 容 器 所 调 用 的 一 个 接 口 , 一 般 在 图 中 用 一 个 小 圈 来 表 示 , 如 图 8-7 所 示 。 在 服 务 器 中 , 实 现 对 正 确 函 数 的 调 用 。 请 注 意 该 图 , 一 旦 初 始 接口 就 位 之 后 , 所 有 对 象 就 将 开 始 彼 此 之 间 的 对 话 , 图 中 没 有 包 括 COM 。
图 8-7 将 Activex 控 件 连 向 包 容 器
每 个 接 口 都 是 一 个 指 向 Activex 控 件 导 出 的 函 数 的 指 针 数 组 。 这 个 数 组 一 般 叫 做
V 表 ,因 为 它 十 分 像 指 向 虚 拟 函 数 的 指 针 的 C++ 表 。因 为 仅 有 接 口 的 单 一 间 接 步
骤 处 于 客 户 和 运 行 的 服 务 器 之 间 , 所 以 , 调 用 ActiveX 控 件 实 际 上 和 调 用 一 个 普通 的 动 态 链 接 库 一 样 迅 速 。
并 不 是 所 有 的 C O M 服 务 器 都 处 于 运 行 状 态 , 服 务 器 EXE 应 用 程 序 在 它 自 己 的地 址 空 间 中 运 行 ,或 者 是 与 客 户 相 同 的 机 器 , 或 者 是 通 过 网 络 附 加 到 另 一 台 机 器上 。 在 这 种 情 况 下 , 客 户 和 服 务 器 都 是 通 过 处 理 边 界 进 行 分 隔 的 , 并 不 能 直 接 进行 通 信 。 对 于 进 程 之 外 的 服 务 器 , C O M 为 了 处 理 通 信 而 加 载 两 个 动 态 链 接 库 。第 一 个 库 叫 做 代 理 , 被 映 射 到 服 务 器 空 间 。 当 客 户 调 用 代 理 的 接 口 的 时 候 , 代 理将 函 数 参 数 打 包 , 通 过 远 程 过 程 调 用 (RPC )将 它 们 发 送 到 Stub 。 Stub 将 包 中 的 信息 转 换 回 参 数 列 表 ,并 调 用 服 务 器 中 的 目 标 函 数 。从 服 务 器 回 到 客 户 的 通 信 的 是同 一 个 路 径 。通 过 代 理 和 存 根 库 连 接 客 户 和 服 务 器 的 过 程 叫 做 编 组 。正 如 你 所 希望 的 那 样 , 编 组 比 客 户 和 ActiveX 控 件 之 间 更 加 直 接 的 交 互 更 慢 , 因 为 运 行 的 服务 器 并 不 依 赖 远 程 过 程 调 用 来 与 客 户 进 行 通 信 , 除 非 通 信 是 线 程 之 间 的 , 否 则 也不 需 要 编 组 。 第 10 章 将 详 细 讨 论 线 程 内 的 编 组 。
通 信 在 ActiveX 控 件 和 它 的 包 容 器 间 的 两 个 方 向 运 行 , 因 此 , 包 容 器 应 用 程 序 必须 提 供 它 自 己 的 一 套 接 口 , 才 能 从 控 件 处 接 收 呼 叫 。 M icrosoft 发 布 了 指 定 包 容器 应 该 支 持 的 最 小 接 口 集 的 指 导 方 针 。该 指 导 方 针 在 在 线 帮 助 中 有 说 明 , 可 以 通过 MSDN Library 窗 口 的 Index 选 项 卡 进 行 访 问 。 从 Visual C++ 的 Help 菜 单 选 择Index 命 令 , 然 后 键 入 required interface 来 定 位 该 标 题 的 文 章 。
通 过 支 持 这 些 接 口 , 包 容 器 应 用 程 序 可 以 确 保 与 遵 守 该 指 导 方 针 的 任 何 ActiveX
控 件 融 在 一 起 。表 8-2 介 绍 了 包 容 器 为 了 遵 守 OLE/ActiveX 技 术 规 范 而 应 该 支 持的 八 个 接 口 。
表 8-2 中 的 头 三 个 接 口 提 供 复 合 文 档 包 容 器 , 而 不 是 控 件 包 容 器 。 用 MFC 编 写包 容 器 程 序 , 可 以 使 你 不 必 担 心 接 口 支 持 的 详 细 情 况 。 正 如 本 章 以 前 曾 经 介 绍的,在 AppWizard 中 , 为 一 个 包 容 器 项 目 选 择 ActiveX 控 件 支 持 , 将 向 源 代 码 添加 一 个 对 框 架 的 AfxEnableControlContainer 函 数 的 调 用 。 该 函 数 用 于 建 立 表 8-2 中 列 出 的 所 有 接 口 。 一 旦 这 些 接 口 就 位 之 后 , ActiveX 控 件 和 它 的 包 容 器 之 间 的通 信 就 可 以 通 过 事 件 、 方 法 和 属 性 来 发 生 了 。
事 件
尽 管 ActiveX 控 件 是 完 备 的 ,它 仍 然 可 以 通 过 触 发 事 件 使 包 容 器 应 用 程 序 了 解 到控 件 内 部 的 行 为 。对 于 某 个 控 件 触 发 的 事 件 , 控 件 开 发 者 便 认 为 包 容 器 应 用 程 序想 知 道 有 关 控 件 的 事 情 。 例 如 , 控 件 可 以 为 了 响 应 控 件 窗 口 内 的 鼠 标 单 击 , 或 者在 控 件 有 焦 点 的 时 候 , 向 包 容 器 传 递 任 何 收 集 到 的 键 盘 输 入 来 触 发 一 个 事 件 。一个 触 发 事 件 可 能 发 送 信 号 , 来 表 明 类 似 于 定 位 URL 、 下 载 数 据 或 分 类 列 表 这 样的 任 务 已 经 完 成 。 我 们 可 以 把 事 件 触 发 和 普 通 控 件 向 它 的 父 窗 口 发 送 诸 如CBN_DROPDROWN 或 BN_DOUBLECLICKED 之 类 的 通 知 消 息 做 一 个 类 比 ,我们 可 以 看 到 , 除 了 ActiveX 控 件 是 通 过 调 用 包 容 器 中 的 一 个 函 数 , 而 不 是 通 过 发送 消 息 来 触 发 事 件 的 之 外 , 两 者 是 很 相 似 的 。
表 8-2 包 容 器 应 该 支 持 的 遵 守 OLE/ActiveX 技 术 规 范 的 接 口接 口 说 明
IOle ClientSite 嵌 入 式 对 象 用 来 查 询 包 容 器 有 关 客 户 站 点 的 大小 以 及 用 户 接 口 的 特 性 。IOleClientSite 接 口 还 提供 了 诸 如 RequestNewObject Layout 函 数 之 类 的服 务 , 控 件 可 以 通 过 这 个 函 数 来 为 它 的 站 点 申 请新 的 大 小
IAdviseSink 由 对 象 使 用 , 用 来 将 对 象 数 据 中 的 改 变 通 知 给 包容 器
IOleInPlaceSite 用 于 管 理 包 容 器 和 对 象 站 点 之 间 的 交 互
IOleControlSite 为 嵌 入 式 ActiveX 控 件 提 供 各 种 服 务 。 例 如 , Translate Accelerator 函 数 用 来 请 求 包 容 器 处 理 指定 的 键 击 , OnFocus 函 数 用 来 告 诉 控 件 它 是 否 具有 输 入 焦 点
IOleInPlaceFram e
续 表
ActiveX 控 件 用 来 统 治 像 组 成 菜 单 这 样 的 资 源 的显 示
IOleContainer 允 许 控 件 强 制 它 的 包 容 器 保 持 在 运 行 状 态 , 或 访问 其 他 嵌 入 在 同 一 文 档 或 窗 口 中 的 其 他 控 件
IErrorInfo 支 持 双 重 接 口 包 容 器 所 需 要 的 ( 请 参 阅 第 10 章 )
IDispatch 控 件 用 来 访 问 包 容 器 的 外 部 层 性 , 并 调 用 该 包 容器 的 事 件 处 理 程 序 函 数 。该 包 容 器 为 属 性 和 事 件实 观 独 立 的 Idispatch 接口
接 收 触 发 事 件 的 包 容 器 中 的 函 数 是 回 调 类 型 的 函 数 。如 果 包 容 器 应 用 程 序 想 被 通知 一 个 特 殊 的 控 件 事 件 , 它 必 须 提 供 一 个 函 数 ( 也 就 是 众 所 周 知 的 事 件 处 理 函 数或 事 件 实 现 函 数 ) 来 接 收 调 用 。 包 容 器 在 一 个 IDispatch V 表 ( 也 就 是 事 件 接 收 端 ) 中 存 储 到 它 的 事 件 处 理 程 序 的 指 针 列 表 。这 个 事 件 接 收 端 将 每 个 事 件 与 它 自 己 的处 理 程 序 函 数 连 接 起 来 。这 个 包 容 器 应 用 程 序 不 必 要 为 该 控 件 触 发 的 每 个 事 件 提供 处 理 程 序 函 数 , 也 不 是 每 个 ActiveX 控 件 都 触 发 事 件 。
OLE/ActiveX 标 准 预 定 义 了 大 量 的 库 存 事 件 ,它 们 将 控 件 窗 口 中 所 发 生 的 事 情 通
知 给 包 容 器 。 例 如 , 当 鼠 标 在 控 件 窗 口 内 部 单 击 的 时 候 , 为 了 将 这 个 库 存 事 件 通知 给 包 容 器 , 一 个 使 用 MFC 的 控 件 可 以 通 过 EVENT_STOCK_CLICK 宏 建 立Click 事 件 :
BEGIN_EVENT_MAP(CDemoCtrl, COleControl)
//{{AFX_EVENT_MAP(CDemoCtrl) EVENT_STOCK_CLICK()
//}}AFX_EVENT_MAP
END_EVENT_MAP()
因 为 由 框 架 来 负 责 感 知 鼠 标 移 动 和 触 发 事 件 , 所 以 , 该 控 件 不 需 要 其 他 代 码 。 如果 包 容 器 想 知 道 鼠 标 单 击 在 控 件 窗 口 中 是 何 时 发 生 的 , 它 可 以 为 click 事 件 提 供一 个 处 理 程 序 函 数 , 可 以 在 匹 配 事 件 接 收 端 映 射 中 引 用 它 。
BEGIN_EVENTSINK_MAP(CDemoContainer,Cdialog)
//{{AFX_EVENTSINK_MAP(CDemoContainer) ON_EVENT(CDemoContainer, IDC_CTRL,DISPID_CLICK,
OnClick,VTS_NONE)
//}}AFX_EVENTSINK_MAP END_EVENTSINK_MAP()
在 上 面 的 代 码 段 中 , ON_EVENT 宏 的 参 数 可 能 需 要 解 释 一 下 。 CDemoContainer 是 该 包 容 器 的 类 , 它 是 从 CDialog 中 派 生 的 。 常 数 IDC_CTRL 用 来 标 识 类 对 话窗 口 中 的 控 件 。DISPID_CLICK 是 Click 事 件 的 调 度 标 识 符 ( 简 写 为 dispid)。用 于库 存 事 件 的 调 度 标 识 符 在 O leCtl.h 文 件 中 定 义 ,每 个 标 识 符 都 具 有 一 个 DISPID_ 前 缀 。 所 有 的 非 库 存 事 件 都 叫 自 定 义 事 件 , OLE 可 以 为 它 分 配 一 个 正 的 调 度 标识 符 ,而 为 库 存 事 件 保 留 负 的 标 识 符 。该 宏 的 第 四 个 参 数 指 向 用 于 处 理 事 件 的 包容 器 成 员 函 数 。 本 例 中 , 该 成 员 函 数 的 名 字 是 OnClick 。VTS_NONE 参 数 用 于 指定 Click 事 件 没 有 参 数 。
表 8-3 列 出 了 由 OLE/ActiveX 技 术 规 范 定 义 的 库 存 事 件 的 函 数 原 型 。 除 了 Error 之 外 , 所 有 库 存 事 件 者 仅 仅 在 ActiveX 控 件 具 有 输 入 焦 点 时 发 生 。 事 件 原 型 好 像可 以 暗 示 单 一 函 数 的 存 在 , 事 实 上 , 通 常 至 少 包 括 三 个 函 数 , 如 图 8-8 所 示 。 在较 低 层 次 上 , 一 个 控 件 通 过 调 用 包 容 器 的 IDispatch:Invoke 方 法 , 并 传 递 给 它 适于 该 事 件 的 参 数 来 触 发 一 个 事 件 。 但 是 , 在 更 高 的 层 次 上 , 存 在 着 两 个 其 他 的 函数 , 一 个 位 于 ActiveX 控 件 中 用 包 装 对 IDispatch::Invoke 的 调 用 , 另 一 个 位 于 包容 器 中 用 于 处 理 调 用 。 两 个 函 数 共 享 同 一 个 参 数 表 , 而 在 效 果 上 是 单 一 的 函 数 , 隐 藏 了 发 生 在 它 们 之 间 的 低 级 的 IDispatch 行 为 。 函 数 名 是 任 意 的 。 MFC 通 过 向
事 件 名 前 添 加 前 缀 Fire , 来 形 成 触 发 函 数 的 名 字 , 例 如 FireClick 函 数 触 发 的 是
Click 事 件 。
图 8-8 触发一个典型的事件
表 8-3 由 OLE/ActiveX 定 义 的 库 存 事 件事 件 原 型 当 时 触 发 事 件
void FireClick() 在 控 件 窗 口 中 单 击 了 任 何 鼠 标 按钮 ( 左 , 中 或 右 ), MouseDown 和MouseUp 库 存 事 件 在 Click 之 前触 发
void FireDblClick() 在 控 件 窗 口 中 双 击 了 任 何 鼠 标 按钮
void FireError(SCODE scode, LPCSTR lpszDescription, UNIT nHelpID=0)
控 件 检 测 到 错 误
void FireKeyDown(short* pnChar, short nShiftState)
控 件 接 收 到
WM_SYSKEYDOWN 或
W M _ K E Y D O W N 消 息
void FireKeyPress(short* pnChar) 控 件 接 收 到 W M _ C H A R 消 息
void FireKeyUp(short* pcChar, Short nShiftState )
void FireMouseDown(short nButton,
short nShiftState, float x, float y)
void FireMouseMove(short nButton,
续 表
控 件 接 收 到 WM_SYSKEYUP 或
MW_KEYUP 消 息
按 下 任 何 鼠 标 按 钮 ( 左 、 中 或右 ) , 生 成 一 条WM_xBUTTONDOWN 消 息
控 件 接 收 到 W M _ M O U S E M O V E
消 息
short nShiftState, float x, float
y)
void FireMouseUp(short nButton,
short nShiftState, float x, float
任 何 鼠 标 按 钮 被 释 放 , 生 成 一 个
WM_xBUTTONUP 消 息
y)
方 法
方 法 与 事 件 处 理 函 数 刚 好 相 反 。 事 件 处 理 函 数 位 于 包 容 器 之 中 , 并 由 控 件 调 用 , 方 法 则 位 于 控 件 之 中 , 由 包 容 器 调 用 。 包 容 器 可 以 调 用 方 法 来 了 解 条 件 , 或 申 请控 件 完 成 一 些 操 作 。
OLE/ActiveX 预 定 义 了 三 个 库 存 方 法 , 分 别 叫 做 DoClick , Refresh 和 ActiveX , 它 们 中 没 有 一 个 具 有 参 数 或 返 回 值 。Doclick 导 致 控 件 触 发 它 的 Click 库 存 事 件 ( 如果 支 持 它 的 话 ), Refresh 方 法 告 诉 控 件 使 它 的 窗 口 失 效 , 并 自 己 重 绘 , AboutBox 告 诉 该 控 件 去 显 示 一 个 信 息 丰 富 的 对 话 框 。 ActiveX 控 件 导 入 的 任 何 其 他 方 法 都叫 做 自 定 义 方 法 , 它 们 是 由 控 件 的 作 者 设 计 的 。 对 于 包 容 器 而 言 , 方 法 是 作 为 由动 态 链 接 库 导 出 的 普 通 函 数 出 现 的 , 带 有 一 个 可 选 的 参 数 表 , 最 多 可 带 有 15 个参 数 , 并 返 回 任 何 类 型 的 值 。
属 性
属 性 是 包 含 在 包 容 器 和 控 件 内 部 并 可 以 互 相 展 示 的 公 共 数 据 。OLE/ActiveX 定 义了 四 类 属 性 , 分 别 叫 做 库 存 的 、 自 定 义 的 、 环 境 的 和 扩 展 的 。 库 存 和 自 定 义 属 性属 于 控 件 , 环 境 和 扩 展 属 性 属 于 包 容 器 。
库 存 和 自 定 义 属 性
库 存 属 性 用 于 指 定 由 ActiveX 标 准 定 义 的 典 型 控 件 特 性 ,例 如 控 件 的 前 景 和 背 景颜 色 , 在 窗 口 中 显 示 的 文 字 , 以 及 用 于 文 字 的 字 体 。 自 定 义 属 性 是 控 件 设 计 者 想展 现 给 包 容 器 的 所 有 其 他 数 据 。 包 容 器 通 过 调 用 MFC 中 作 为 Get 和 Set 方 法 的函 数 来 读 写 控 件 的 属 性 , 这 些 方 法 是 由 该 控 件 导 出 的 。 在 使 用 Test Container 的Invoke Methods 对 话 的 时 候 ,我 们 曾 经 遇 到 过 相 同 的 get/put 属 性 方 法 。不 同 之 处是 名 称 方 面 的 差 异 , WFC 以 前 缀 Get 和 Set 作 为 方 法 名 的 开 头 ,而 COM 术 语 使用 get 和 put( 小 写 形 式 )。 一 般 说 来 , 每 个 属 性 都 有 一 个 对 应 的 Get/Set 方 法 对 , 但 控 件 可 以 不 为 它 导 出 Set 方 法 , 来 阻 止 包 容 器 改 变 控 件 属 性 。 第 6 章 将 演 示 这是 如 何 实 现 的 。
表 8-4 显 示 了 控 件 中 的 库 存 属 性 和 包 容 器 为 读 取 该 属 性 而 调 用 的 函 数 之 间 的 链接 。 对 于 在 表 中 第 三 列 列 出 的 每 个 获 取 方 法 , 都 存 在 一 个 具 有 与 其 相 匹 配 的 名 字对 应 的 Set 方 法 。 Set 方 法 没 有 返 回 值 , 只 有 一 个 类 型 与 Get 方 法 的 返 回 值 相 同的 参 数 。 GetAppearance 和 SetAppearance 的 原 型 说 明 了 Get/set 函 数 的 样 式 :
short GetAppearance( ) //Returns a property of type short void SetAppearance( short n ) //Passes a property of type short
表 8-4 由 OLE/ActiveX 定 义 的 库 存 控 件 属 性
属 性 控 件 中 调 度 映 射 项 由 包 容 器 调 用 的 Get
函 数
Appearance DISP_STOCKPROP_APPEAR
ANCE
short GetAppearance()
BackColor DISP_STOCKPROP_BACKCO
LOR
OLE_COLOR
GetBackColor()
BorderStyle DISP_STOCKPROP_BORDER
STYLE
short GetBorderStyle()
Caption DISP_STOCKPROP_CAPTION BSTR GetText()
Enabled DISP_STOCKPROP_ENABLE
D
BOOL GetEnabled()
Font DISP_STOCKPROP_FONT LPFONTDISP
GetFont()
续 表
ForeColor DISP_STOCKPROP_FORECO
LOR
OLE_COLOR
GetForeColor()
hWnd DISP_STOCKPROP_HWND OLE_HANDLE
GetHwnd()
Text DISP_STOCKPROP_TEXT BSTR GetText()
ReadyState DISP_STOCKPROP_READYS
TATE
long GetReadyState()
环 境 和 扩 展 属 性
环 境 和 扩 展 属 性 属 于 客 户 站 点 ,不 能 被 控 件 改 变 。扩 展 属 性 是 与 嵌 入 的 控 件 相 关的 数 据 , 但 由 包 容 器 进 行 实 现 和 管 理 。 环 境 属 性 用 于 描 述 包 容 器 本 身 。 例 如 , 它的 当 前 背 景 颜 色 或 字 体 。通 过 阅 读 它 的 包 容 器 的 环 境 属 性 ,控 件 可 以 对 它 的 外 观和 行 为 进 行 剪 裁 ,以 与 包 容 器 相 匹 配 。控 件 通 过 调 用 带 有 用 于 所 要 求 属 性 的 调 度标 识 符 的 COleControl:GetAmbientProperty 函 数 来 查 询 环 境 属 性 :
LPFONTDISP fontdisp;
GetAmbientProperty(DISPID_AMBIENT_FONT,VT_FONT,&fontdisp);
对 于 由 OLE/ActiveX 技 术 规 范 预 定 义 的 标 准 环 境 属 性 ,一 个 控 件 可 以 更 加 方 便 地调 用 由 COleControl 提 供 的 相 关 帮 助 器 函 数 , 例 如 AmbientFont:
LPFONTDISP fontdisp=AmbientFont();
表 8-5 列 出 了 包 容 器 所 支 持 的 标 准 环 境 属 性 。 ActiveX 控 件 通 过 调 用 使 用 表 的 第二 列 中 列 出 的 调 度 标 识 符 之 一 的 GetAmbientProperty , 或 者 通 过 调 用 第 三 列 中 等价 的 帮 助 器 函 数 ,来 确 定 环 境 属 性 的 值 。如 果 你 使 用 AppWizard 来 创 建 包 容 器 应用 程 序 , 那 么 , 对 标 准 环 境 属 性 的 支 持 就 已 经 在 内 部 创 建 了 , 而 不 需 进 行 特 殊 的操 作 。 调 用 SetFont 或 SetTextColor 在 包 容 器 中 设 置 字 体 或 前 景 颜 色 , 将 自 动 在对 话 中 为 站 点 设 置 环 境 属 性 。 当 ActiveX 控 件 调 用 AmbientFont 或AmbientForeColor 函 数 的 时 候 , 它 接 收 到 用 于 当 前 对 话 的 环 境 数 据 。
编 写 包 容 器 应 用 程 序
包 容 器 开 发 者 是 如 何 事 先 知 道 一 个 ActiveX 控 件 所 提 供 的 事 件 、 方 法 和 属 性 , 以及 你 的 包 容 器 应 用 程 序 应 包 含 哪 些 处 理 程 序 函 数 呢 ? Gallery 和 ClassWizard 可 以为 你 完 成 这 项 工 作 。在 你 的 包 容 器 程 序 中 , 使 用 现 有 的 控 件 取 决 于 单 个 的 许 可 证协 议 ( 这 是 下 一 章 将 要 涉 及 的 主 题 ), 但 是 , 你 可 以 从 Gallery 中 选 择 一 个 ActiveX
控 件 ,并 将 它 作 为 任 何 其 他 组 件 添 加 到 你 的 项 目 中 。Visual C++ 自 动 扫 描 注 册 表 , 来 定 位 在 系 统 上 注 册 的 所 有 控 件 , 因 此 , 向 Gallery 添 加 ActiveX 控 件 仅 仅 是 注册 它 的 事 情 。
表 8-5 标 准 的 包 容 器 环 境 属 性
属 性 调 度 标 识 符 控 件 调 用 的 函 数
BackColor DISPID_AMBIENT_BACKC
OLOR
OLE_COLOR
AmbientBackColor()
D isplayName DISPID_AMBIENT_DISPLA
Y N A M E
CString AmbientDisplayName()
Font DISPID_AMBIENT_FONT LPFONTDISP
AmbientFont()
ForeColor DISPID_AMBIENT_FORECO
LOR
OLE_COLOR
AmbientForeColor()
LocaleID DISPID_AMBIENT_LOCALE
ID
LCID AmbientLocaleID()
ScaleUnits DISPID_AMBIENT_SCALEU
NITS
CString AmbientScaleUnits()
续表
ShowGrabHa ndles
DISPID_AMBIENT_SHOWG RABHAN-DLES
BOOL
AmbientShowGrabHandl es()
ShowHatchin g
DISPID_AMBIENT_SHOWH ATCHING
BOOL
AmbientShowHatching()
TextAlign DISPID_AMBIENT_TEXTAL
IGN
short AmbientTextAlign()
UIDead DISPID_AMBIENT_UIDEAD BOOL AmbientUIDead()
UserMode DISPID_AMBIENT_USERM
ODE
BOOL
AmbientUserMode()
当 Gallery 在 你 的 包 容 器 项 目 中 放 置 ActiveX 控 件 的 时 候 , 它 将 检 查 该 控 件 的 可执 行 图 像 中 包 含 的 类 型 库 , 以 获 得 由 该 控 件 所 导 出 的 事 件 、方 法 和 属 类 的 一 个 列表 。 从 这 个 信 息 中 , Gallery 可 以 创 建 一 个 完 整 的 包 装 类 , 该 类 中 包 含 有 Get/Set 属 性 函 数 和 方 法 调 用 , 包 容 器 可 以 通 过 它 们 获 得 到 控 件 数 据 的 访 问 。如 果 想 在 控件 中 获 得 或 设 置 属 性 ( 例 如 背 景 颜 色 ), 包 容 器 将 调 用 包 装 类 中 的 一 个 函 数 :
OLE_COLOR CDemoCtrl::GetBackColor()
{
OLE_COLOR result; GetProperty(DISPID_BACKCOLOR,VT_I4, (void*)&result); return result;
}
void CDemoCtrl::SetBackColor(OLE_COLOR propVal)
{
SetProperty(DISPID_BACKCOLOR,VT_I4,propVal);
}
既 然 事 件 处 理 程 序 属 于 包 容 器 的 类 ,通 常 由 CDialog 这 样 的 类 中 派 生 出 来 ,那 么 ,
Gallery 就 不 为 事 件 处 理 程 序 函 数 添 加 源 代 码 。 在 控 件 被 添 加 到 对 话 之 中 , 这 项
工 作 留 给 ClassWizard 完 成 。
最 后 ,举 例 来 说 明 这 个 过 程 。本 节 创 建 了 一 个 叫 做 Hour 的 简 单 包 容 器 应 用 程 序 , 它 使 用 本 书 配 套 光 盘 中 包 括 的 一 个 ActiveX 控 件 。 该 控 件 与 以 前 在 Tumber.htm 文 档 中 使 用 的 是 同 一 个 IETimer.ocx 定 时 器 控 件 。在 Gallery 的 Registered ActiveX Control 文 件 夹 中 名 称 Timer Object 下 列 出 的 控 件 之 中 , 你 可 以 找 到 这 个 IETimer 控 件 。 这 个 文 件 夹 中 的 控 件 列 表 可 能 还 包 括 另 一 个 定 时 器 ActiveX 控 件 , 它 是 由名 为 Time Control 的 样 本 项 目 中 创 建 的 (Time Control 的 源 文 件 位 于 文 件 夹MSDN\Samples\VC98\MFC\Controls\Time 之 中 ) 。 两 个 定 时 器 控 件 导 出 相 同 的 方法 , 并 实 现 相 同 的 功 能 , 因 此 , Hour 项 目 使 用 哪 一 个 都 无 所 谓 。
与 Anibutton 和 Calendar 这 样 的 ActiveX 控 件 不 同 的 是 ,Timer Object 不 是 一 个 可见 的 控 件 。在 包 容 器 内 ,它 不 作 为 窗 口 显 示 ,而 只 是 以 指 定 的 周 期 触 发 一 次 事 件 , 为 包 含 程 序 起 到 定 时 器 的 作 用 。Hour 程 序 使 用 定 时 器 事 件 来 管 理 如 图 8-9 所 示 的三 个 进 程 指 示 器 。 这 个 进 程 控 件 以 分 秒 和 十 分 之 一 秒 的 形 式 来 显 示 剩 余 时 间 。Hour 程 序 的 名 称 来 源 于 这 种 事 实 : 当 M inutes 进 程 控 件 满 60 分 钟 之 后 , 三 个 控件 便 会 复 位 。
图 8-9 Hour 程 序
从 开 始 到 结 束 创 建 Hour 项 目 只 需 五 步 。
步 骤 1 : 用 AppWizard 创 建 Hour 项 目
从 环 境 的 File 菜 单 选 择 New , 在 Projects 选 项 卡 中 选 中 MFC AppWizard(exe) 图标 ,键 入 Hour 作 为 项 目 名 。Hour 是 基 于 对 话 的 应 用 程 序 ,因 此 ,单 击 AppWizard 步 骤 1 中的 D ialog Based ( 基 于 的 对 话 ) 单 选 按 钮 , 并 确 保 步 骤 2 中 的 ActiveX Controls 复 选 框 已 经 打 开 :
单 击 Finish 按 钮 创 建 该 项 目 。
步 骤 2 : 将 Time r Objec t控 件 插 入 项 目
现 在 , 你 应 该 很 熟 悉 这 一 步 骤 了 。 使 用 Project 菜 单 上 的 Add To Project 命 令 来 打
开 Gallery 。 并 显 示 如 图 8-1 所 示 的 ActiveX 控 件 列 表 。 水 平 滚 动 并 选 择 Timer Object 或 Timer Control 图 标 , 然 后 单 击 Insert 按 钮 。 该 操 作 将 把 CIeTimer 或CTimerCtrl 类 的 源 代 码 添 加 到 Hour 项 目 ,究 竟 是 哪 一 个 类 取 决 于 所 选 定 的 控 件 。当 Confirm Classes 对 话 出 现 的 时 候 单 击 OK , 然 后 关 闭 Gallery 对 话 。
如 果 Timer Object 没 有 在 对 话 显 示 中 列 出 , 那 么 , 该 控 件 就 还 没 有 注 册 。 如 果 想注 册 Timer Object 控 件 , 请 将 IETimer.ocx 文 件 从 配 套 光 盘 中 复 制 到W indows\OCCache 文 件 夹 , 并 运 行 RegSvr32 实 用 程 序 。 当 控 件 成 功 注 册 了 自 己之 后 , 当 你 下 次 打 开 对 话 时 , 它 将 在 Gallery 列 表 中 出 现 。
步 骤 3 : 在 Hour 对 话 中 放 置 Time r Ob jec t控 件
在 Visual C++ 的 早 期 版 本 中 , 你 必 须 双 击 ResourceView 窗 格 中 的IDD_HOUR_DIALOG 标 识 符 , 来 启 动 对 话 编 辑 器 , 并 加 载 主 对 话 。 通 过 选 中 对话 工 作 区 中 的 “ to do ” 静 态 文 字 控 件 和 Cancel 按 钮 , 并 按 Del 键 来 删 除 它 们 。将 Progress, Static Text 和 Timer Object 工 具 从 Controls 工 具 栏 拖 到 工 作 区 之 上 ,
并 对 其 进 行 排 列 , 使 它 看 上 去 与 下 图 类 似 :
由 于 Timer Object ActiveX 控 件 在 程 序 运 行 的 时 候 没 有 创 建 自 己 的 窗 口 , 所 以 , 你 把 它 放 在 对 话 中 的 什 么 地 方 都 可 以 。 展 现 每 个 控 件 的 Properties 框 , 并 键 入 屏幕 图 像 上 显 示 的 标 题 , 以 及 在 表 8-6 中 第 二 列 列 出 的 标 识 符 。
表 8-6 Hour 程 序 中 的 控 件 标 识 符
控 件 |
标 识 符 |
变 量 名 |
---|---|---|
分 钟 进 度 指 示 器 |
IDC_PROGRESS_MIN |
progMin |
秒 钟 进 度 指 示 器 |
IDC_PROGRESS_SEC |
progSec |
十 分 之 一 秒 进 度 指 示 器 |
IDC_PROGRESS_MIN |
progTen |
顶 端 “ x ” 静 态 控 件 |
IDC_MINUTES |
strMin |
中 间 “ x ” 静 态 控 件 |
IDC_SECONDS |
strSec |
底 部 “ x ” 静 态 控 件 |
IDC_TENTHS |
strTen |
时 间 控 件 |
IDC_TIMER1 |
time |
应 用 程 序 类 CHourDlg 需 要 代 表 每 个 对 话 控 件 的 成 员 变 量 , 你 可 以 通 过ClassWizard 来 添 加 它 。在 对 话 编 辑 器 仍 处 于 活 动 状 态 的 时 候 , 单 击 View 菜 单 上的 ClassWizard 命 令 来 调 用 第 6 章 中 介 绍 的 MFC ClassWizard 。在 Member Variables
( 成 员 变 量 ) 选 项 卡 中 , 选 中 Control IDs 框 中 的 每 个 新 类 , 并 单 击 Add Variable
( 添 加 变 量 ) 按 钮 , 来 显 示 Add Member Variable ( 添 加 成 员 变 量 ) 对 话 。 在 标
签 为 M ember Variable Name( 成 员 变 量 名 ) 的 文 本 框 中 , 键 入 表 8-6 中 第 三 列 列出 的 控 件 变 量 。 图 8-10 显 示 了 最 后 的 结 果 。
我 们 还 需 要 一 个 函 数 来 处 理 由 Timer Object 控 件 触 发 的 事 件 。 在 ClassWizard 的M essage Map s( 消 息 映 射 ) 选 项 卡 中 , 选 中 IDC_TIMER1 , 并 从 M essages 框 中选 中 Timer, 然 后 单 击 从 Object IDs 框 中 的 Add Function ( 添 加 函 数 ) 按 钮 。ClassWizard 添 为 名 为 OnTimerTimer1 的 事 件 处 理 程 序 函 数 添 加 存 根 代 码 。
“ E ” 前 缀 用 来 将 OnTimeTimer1 指 定 为 一 个 事 件 处 理 程 序 函 数 。 单 击 O K 来 关闭 ClassWizard 对 话 。
图 8-10 向 CHourDlg 类 添 加 成 员 函 数
步 骤 4 : 向 Hou r.cpp 和 Hou r.h 文 件 添 加 代 码
如 果 想 查 看 一 下 ClassWizard 已 经 添 加 到 HourDlg.h 头 文 件 的 变 量 和 函 数 声 明 , 请 单 击 位 于 WizardBar 最 右 端 的 箭 头 按 钮 :
并 从 下 拉 式 菜 单 中 选 择 Go To Class Definition ( 转 向 类 定 义 )。 Visual C++ 将 在 文本 编 辑 器 中 自 动 打 开 HourDlg.h , 并 将 插 字 符 定 位 在 CHourDlg 声 明 的 开 始 处 , 在 这 里 添 加 新 控 件 变 量 。
// Dialog Data
//{{AFX_DATA(CHourDlg)
enum { IDD = IDD_HOUR_DIALOG };
CProgressCtrl progTen;
CProgressCtrl progSec;
CProgressCtrl progMin;
CString strMin;
CString strSec;
CString strTen;
CIeTimer time;
//}}AFX_DATA
ClassWizard 还 可 以 为 OnTimerTimer1 事 件 处 理 程 序 函 数 添 加 原 型 :
afx_msg void OnTimerTimer1(); DECLARE_EVENTSINK_MAP()
CHourDlg 类 声 明 仅 需 两 行 :
class CHourDlg : public CDialog
{
像 以 前 一 样 , 阴 影 部 分 表 示 你 必 须 在 文 本 编 辑 器 中 键 入 的 附 加 代 码 。
变 量 iM in 和 iSec 用 于 保 持 已 经 过 去 的 分 和 秒 的 计 数 情 况 , 它 们 被 写 到 对 话 框 中与 进 程 指 示 器 相 邻 的 静 态 控 件 之 中 。 对 于 消 逝 的 十 分 之 一 秒 ,不 需 要 类 似 的 计 数器 ,因 为 IDC_PROGRESS_TEN 进 程 指 示 器 的 位 置 在 由 Timer Object 控 件 触 发 的每 个 事 件 之 前 。 过 一 会 , 当 我 们 向 事 件 处 理 程 序 添 加 代 码 的 时 候 , 这 一 切 就 会 变得 很 清 楚 。
对 源 代 码 的 最 后 修 改 是 在 CHourDlg::OnInitDialog 函 数 中 进 行 的 。 在 W izardBar 的 M embers 框 内 任 何 地 方 单 击 , 来 显 示 成 员 函 数 的 下 拉 列 表 , 并 从 如 图 所 示 列表 中 选 择 OnInitDialog。
Visual C++ 在 文 本 编 辑 器 中 打 开 HourDlg.cpp 源 文 件 , 并 且 自 动 将 插 字 符 放 在
OnInitDialog 定 义 的 开 始 处 。 在 该 函 数 的 “ to do ” 行 添 加 阴 影 文 字 , 如 下 所 示 :
// TODO: Add extra initialization here
这 些 指 令 用 于 初 始 化 进 程 指 示 器 控 件 。 指 令 :
time.SetInterval(100); //Set timer interval to 1/10 second
调 用 Time Object 中 的 一 个 方 法 , 告 诉 控 件 开 始 每 隔 100 毫 秒 触 发 一 次 事 件 。使 用 W izardBar 向 下 到 CHourDlg::OnTimerTimer1 函 数 , 并 添 加 如 下 阴 影 行 :
void CHourDlg::OnTimerTimer1()
{
// TODO: Add your control notification handler code here
progMin.SetPos( 0 );
}
else
progMin.StepIt ();
iSec = 0;
progSec.SetPos( 0 );
strMin.Format( "%d", iMin );
SetDlgItemText( IDC_MINUTES, strMin );
}
else
progSec.StepIt ();
i = 0;
progTen.SetPos( 0 );
strSec.Format( "%d", iSec );
SetDlgItemText( IDC_SECONDS, strSec );
}
strTen.Format( "%d", i );
SetDlgItemText( IDC_TENTHS, strTen );
}
每 隔 十 分 之 一 秒 , OnTimerTimer1 实 现 函 数 接 收 到 该 控 件 的 触 发 事 件 , 并 将IDC_PROGRESS_TEN 进 程 指 示 器 提 前 一 步 。当 Tenths 进 程 指 示 器 达 到 最 大 值 之后 , 该 指 示 器 被 复 位 到 零 , Seconds 指 示 器 加 1 。 按 照 同 样 的 方 式 , 在 60 秒 过 去之 后 , Seconds 指 示 器 被 复 位 到 零 , M inutes 指 示 器 加 1 。 当 一 个 小 时 过 去 之 后 ,
整 个 过 程 再 重 新 开 始 。
步 骤 5 : 创 建 和 测 试 项 目
在 Build 工 具 栏 上 选 中 W in32 Release 配 置 ,然 后 创 建 Hour.exe 程 序 的 发 行 版 本 。单 击 Build 菜 单 上 的 Execute 命 令 来 运 行 完 成 后 的 程 序 。 请 注 意 , Hour 运 行 得 有点 儿 慢 , 它 是 依 靠 系 统 定 时 器 资 源 的 一 个 典 型 的 W in32 程 序 。 尽 管 你 可 以 把 它作 为 一 个 煮 蛋 定 时 器 使 用 ,但 Timer Object 控 件 并 不 适 合 于 需 要 精 确 定 时 的 应 用程 序 。
下 一 章 将 介 绍 名 为 Game 的 另 一 个 包 容 器 项 目 , 它 与 Hour 十 分 相 似 。 不 同 之 处是 Game 使 用 你 自 己 编 写 的 而 不 是 由 M icrosoft 提 供 的 自 定 义 ActiveX 控 件 。
在 没 有 对 话 编 辑 器 的 情 况 下 工 作
利 用 Visual C++ 对 话 编 辑 器 , 就 可 以 很 容 易 地 向 对 话 框 添 加 控 件 。 但 是 , 当 想 在窗 口 而 不 是 对 话 中 放 置 一 个 控 件 时 , 可 能 就 要 花 些 时 间 了 。不 存 在 任 何 技 术 上 的障 碍 ( 只 是 因 为 常 规 控 件 可 以 在 框 架 化 的 窗 口 中 出 现 , 因 此 ActiveX 控 件 也 能 ), 但 你 必 须 走 在 对 话 编 辑 器 服 务 的 前 面 。 本 节 将 解 释 如 何 向 非 对 话 窗 口 添 加ActiveX 控 件 , 通 过 将 Button Menu 控 件 放 入 应 用 程 序 的 客 户 区 来 演 示 这 点 。
这 里 所 介 绍 的 Button 项 目 仅 需 要 键 入 一 些 东 西 即 可 ; 否 则 , 你 可 以 在 配 套 光 盘上 找 到 所 有 的 源 文 件 。 请 注 意 , 该 项 目 没 有 使 用 对 话 编 辑 器 , 因 此 , 在 开 发 期 间从 来 都 不 用 例 子 来 说 明 Button Menu 控 件 。 本 节 中 介 绍 的 技 术 可 以 克 服 一 些 控 件在 设 计 阶 段 所 存 在 的 创 建 错 误 。任 何 ActiveX 控 件 都 可 以 按 照 这 些 步 骤 放 在 窗 口之 中 , 应 用 程 序 将 正 确 地 进 行 编 译 。 但 是 , 这 里 可 以 进 行 真 正 的 许 可 保 护 , 因 为未 经 许 可 的 应 用 程 序 不 能 在 运 行 时 实 例 化 一 个 受 保 护 的 控 件 。下 一 章 当 我 们 讨 论有 关 许 可 的 问 题 时 , 就 会 清 楚 这 一 点 了 。
步 骤 1 : 创 建 Button 项 目
运 行 AppWizard 开 始 项 目 , 在 AppWizard 的 第 一 屏 中 选 中 Single Documen t( 单文 档 )选 项 。在 AppWizard 完 成 之 后 ,打 开 Gallery 中 的 Registered ActiveX Controls 文 件 夹 ,并 双 击 标 签 为 BtnMenu Object 的 图 标 。接 受 提 出 的 新 控 件 类 名 Cpmenu , 然 后 关 闭 Gallery 。
步 骤 2 : 向 CButtonView 类 添 加 控 件
我 们 的 目 的 是 在 应 用 程 序 的 主 窗 口 视 图 内 放 置 Button Menu 控 件 , 因 此 , 开 始 在Button View 源 文 件 中 进 行 编 码 。首 先 ,向 位 于 ButtonView.h 头 文 件 的 CButtonView 类 声 明 中 添 加 Cpmenu 对 象 :
class CButtonView : public CView
{
.
.
.
}
下 一 步 是 编 写 初 始 化 btnmenu 对 象 的 代 码 。 这 项 工 作 最 好 在CButtonView::OnInitialUpdate 函 数 中 完 成 , 以 确 保 该 应 用 程 序 仅 仅 在 视 图 刚 出 现的 时 候 创 建 控 件 。 ClassWiard 可 以 生 成 该 函 数 的 初 始 代 码 ; 只 需 在 ClassWizard
的 M essage Maps 选 项 卡 中 的 Class Name 框 中 选 中 CButtonView , 然 后 双 击M essages 框 中 的 OnInitialUpdate 即 可 。通 过 Edit Code 按 钮 退 出 ClassWizard ,该操 作 将 在 文 本 编 辑 器 中 自 动 打 开 ButtonView.cpp 源 文 档 , 并 将 插 字 符 定 位 在 新OnInitialUpdate 函 数 。 向 该 函 数 添 加 如 下 所 示 的 初 始 化 代 码 :
void CButtonView::OnInitialUpdate()
{
CView::OnInitialUpdate();
// TODO: Add your specialized code here and/or call the base class
v = 2L; btnmenu.AddItem( "Menu item #2", v |
); |
|
---|---|---|
v = 3L; |
||
btnmenu.AddItem( "Menu item #3", v v = 4L; |
); |
|
} |
btnmenu.AddItem( "Menu item #4", v |
); |
Cpmenu 是 从 Cwnd 派 生 出 来 的 , 它 提 供 了 Create 函 数 的 两 个 版 本 , 这 两 个 版 本在 Pmenu.h 文 件 中 都 有 原 型 。出 于 简 化 的 考 虑 ,这 里 所 列 出 的 代 码 段 使 用 了 CRect 对 象 将 该 控 件 在 主 窗 口 中 的 尺 寸 和 位 置 固 定 下 来 。 Cpmenu::AddItem 函 数 用 于 向该 按 钮 的 弹 出 式 菜 单 添 加 命 令 字 符 串 , 根 据 函 数 的 第 二 个 参 数 所 给 定 的VARIANT 值 , 来 组 织 菜 单 中 的 命 令 。 本 例 中 的 OnInitialUpdate 函 数 , 只 是 为 了保 持 VARINT 值 而 创 建 一 个 ColeVariant 对 象 , 以 及 为 了 插 入 一 个 有 代 表 性 的 菜单 命 令 列 表 而 四 次 调 用 AddItem s。
如 果 你 这 时 编 译 并 运 行 Button 应 用 程 序 , 那 么 , 它 将 在 主 窗 口 中 正 确 地 显 示
Button Menu 控 件 。 尽 管 单 击 该 控 件 可 以 调 用 它 的 弹 出 式 菜 单 , 但 是 , 当 选 中 并单 击 菜 单 中 的 命 令 的 时 候 , 应 用 程 序 本 身 并 不 能 做 出 响 应 。这 是 因 为 我 们 还 没 有添 加 函 数 来 处 理 Button Menu 控 件 在 用 户 交 互 期 间 触 发 的 事 件 。那 是 下 一 步 的 工作 。
步 骤 3 : 处 理 事 件
我 们 现 在 仍 然 不 知 道 Button Menu 控 件 触 发 的 是 什 么 事 件 。该 信 息 被 作 为 类 型 库资 源 的 一 部 分 存 储 在 控 件 的 OCX 文 件 之 中 。 但 是 , ClassWizard 却 不 能 读 取 这 个数 据 , 原 因 是 它 对 控 件 或 Cpmenu 类 一 无 所 知 。Visual C++ 提 供 了 一 个 实 用 程 序 , 你 可 以 应 用 它 来 探 究 一 个 控 件 的 类 型 库 ,来 了 解 该 控 件 可 以 触 发 的 事 件 , 以 及 处理 程 序 函 数 所 需 要 的 参 数 列 表 。 该 程 序 的 名 字 叫 OleView.exe, 是 通 过 单 击 Tools 菜 单 上 的 OLE/COM Object Viewer 调 用 的 。 利 用 Object Viewer 的 File 菜 单 上 的View TypeLib 命 令 , 你 可 以 打 开 BtnMenu.ocx 文 件 , 并 显 示 它 的 类 型 库 。 它 表 明Button Menu 仅 能 触 发 两 个 事 件 , 分 别 叫 做 Click 和 Select。 库 脚 本 显 示 了 包 容 器应 用 程 序 如 何 必 须 声 明 处 理 程 序 函 数 , 以 便 正 确 地 接 收 事 件 触 发 :
这 是 我 们 为 事 件 编 写 处 理 程 序 函 数 所 需 要 的 所 有 信 息 。但 是 对 于 提 供 了 许 多 不 同事 件 的 控 件 来 说 ,浏 览 类 型 库 还 有 更 容 易 的 方 法 ,即 将 该 控 件 的 存 在 简 单 地 通 知给 ClassWizard 。 然 后 , ClassWizard 可 以 承 担 从 控 件 读 取 类 型 信 息 的 所 有 工 作 , 为 存 根 处 理 程 序 函 数 生 成 代 码 ,并 向 事 件 接 收 端 映 射 添 加 它 们 的 条 目 。所 需 要 的仅 仅 是 项 目 类 数 据 库 的 一 点 消 息 , 以 及 向 CButtonView 类 声 明 添 加 一 点 儿 东 西 。步 骤 如 下 :
首 先 , 在 文 本 编 辑 器 中 打 开 Button.clw 文 件 , 并 为 一 个 对 话 框 添 加 新 的 资 源 条目 :
ResourceCount=3
Resource1=IDR_MAINFRAME Resource2=IDD_ABOUTBOX
Resource3=IDD_FAKEDL G
在 第 一 行 中 一 定 要 把 资 源 数 目 增 加 到 3 。 在 文 件 的 底 部 添 加 新 对 话 资 源 的 一 个 声明 。
[DLG:IDD_FAKEDLG]
在 项 目 中 没 有 这 样 的 对 话 ( 及 标 识 符 名 ), 但 是 , ClassWizard 并 不 需 要 知 道 这 一点 。 该 条 目 将 告 诉 ClassWizard , 一 个 标 识 为 IDD_FAKEDLG 的 对 话 资 源 属 于CButtonView 类 ; 并 且 包 含 有 由 CLSID 号 进 行 标 识 的 Button Menu 控 件 。
下 一 步 , 返 回 到 ButtonView.h 文 件 , 并 修 正 我 们 以 前 添 加 的 代 码 , 为IDD_FAKEDLG 标 识 符 添 上 #define 语 句 ,并 在 CButtonView 类 声 明 中 声 明 该 值 。新 条 目 是 用 特 殊 的 AFX_DATA 注 释 进 行 标 记 的 , 使 得 对 话 框 标 识 符 对 于 第 6 章中 解 释 的 标 识 符 来 说 是 可 以 识 别 的 。 结 果 应 该 与 下 面 类 似 :
#include "pmenu.h"
#define IDC_BTNMENU 1001
class CButtonView : public CView
{
private:
Cpmenu btnmenu;
//}}AFX_DAT A
保 存 该 文 件 。现 在 ,当 你 调 用 ClassWizard 的 时 候 ,如 果 ClassName 中 CButtonView 类 被 选 中 的 话 ,IDC_BTNMENU 标 识 符 将 在 Message Maps 选 项 卡 中 对 象 列 表 的底 部 显 示 出 来 。选中 IDC_BTNMENU 条 目 来 显 示 M essages 框 中 的 两 个 事 件 ,然后 双 击 Select 事 件 来 添 加 名 为 OnSelectBtnmenu 的 处 理 程 序 函 数 。 无 论 何 时 用 户单 击 了 控 件 弹 出 式 菜 单 中 的 一 个 命 令 , 该 处 理 程 序 函 数 前 将 赢 得 控 制 权 。
在 Member Functio n(s 成 员 函 数 )框 中 选 定 了 新 处 理 程 序 函 数 之 后 ,单 击 Edit Code
( 编 辑 代 码 ) 按 钮 , 来 重 新 打 开 ButtonView.cpp 文 件 。 将 代 码 添 加 到OnSelectBtnmenu 函 数 , 无 论 何 时 , 该 控 件 触 发 了 它 的 Select 事 件 , 该 函 数 都 将做 出 反 应 :
void CButtonView::OnSelectBtnmenu(long item)
{
// TODO: Add your control notification handler code here
CStrin g str;
创 建 Button 程 序 并 运 行 它 。每 次 你 单 击 如 图 8-11 所 示 控 件 菜 单 中 的 一 个 项 目 时 , 你 都 应 该 能 看 到 一 个 消 息 框 。
图 8-11 在框架窗口中放置的 ActiveX 控 件
第 9 章 使 用 MFC 编 写 Ac tiveX 控 件
第 8 章 演 示 了 在 使 用 MFC 的 情 况 下 , 创 建 ActiveX 控 件 并 不 特 别 需 要 理 解 OLE 和 COM 基 础 结 构 。 很 显 然 , 在 编 写 ActiveX 控 件 时 亦 是 如 此 。MFC 可 以 处 理 诸多 的 细 节 , 你 不 必 关 心 它 太 多 复 杂 的 基 础 信 息 , 便 可 以 编 写 控 件 。 如 果 你 决 定 不用 MFC 来 编 写 自 己 的 ActiveX 控 件 ( 这 个 想 法 的 理 由 很 充 足 ), 那 么 , 你 对 项 目的 实 现 就 更 加 有 信 心 了 。根 据 你 的 方 法 和 控 件 的 复 杂 性 , 你 可 能 需 要 仔 细 地 了 解ActiveX 和 Component Object Model 的 原 理 。
本 章 继 续 前 一 章 的 内 容 。 它 从 服 务 器 而 不 是 客 户 的 角 度 来 检 查 ActiveX 控件,介绍 了 Visual C++ 帮 助 想 编 写 而 不 只 是 使 用 ActiveX 控 件 的 开 发 者 的 一 些 方 法 。Visual C++ 提 供 了 三 个 不 同 的 工 具 来 帮 助 建 立 ActiveX 控 件 项 目 :
-
对 ActiveX 控 件 的 MFC 支 持 。
-
BaseCtl 框 架 。
-
Active Template Library 。
用 于 创 建 ActiveX 控 件 的 Visual C++ 工 具
MFC 提 供 了 创 建 ActiveX 控 件 的 最 容 易 的 方 法 。 以 前 在 M icrosoft Developer Network 上 提 供 的 Control Development kit 已 经 作 为 一 套 工 具 并 入 Visual C++ 中 , 该 套 工 具 包 括 已 经 重 命 名 了 的 MFC Active ControlWizard : Test Container 和ControlWizard 。正 如 本 章 稍 后 用 例 子 所 演 示 的 那 样 ,ControlWizard 将 生 成 包 含 有原 始 代 码 的 源 文 件 , 该 代 码 使 用 MFC 来 管 理 几 乎 所 有 的 C O M 详 细 资 料 。 生 成的 源 代 码 可 以 处 理 串 行 方 式 ,显 示 控 件 的 属 性 表 ,并 为 程 序 员 和 用 户 提 供 其 他 许多 方 便 之 处 。你 仅 仅 需 要 添 加 必 要 的 代 码 来 画 出 该 控 件 , 对 用 户 的 输 入 做 出 反 应和 触 发 事 件 。
使 用 MFC 编 写 ActiveX 控 件 可 以 使 OCX 文 件 相 当 小 , 但 是 , 这 是 一 种 误 导 ,因为 该 控 件 永 远 依 靠 两 个 大 文 件 的 存 在 而 存 在 。这 是 MFC 的 缺 点 。如 果 控 件 的 OCX 文 件 在 用 户 的 计 算 机 上 不 可 用 ,该 用 户 的 浏 览 器 程 序 必 须 下 载 这 个 文 件 , 才 能 显示 它 , 因 此 , 小 的 可 执 行 文 件 尺 寸 对 于 用 于 Web 页 的 控 件 来 说 就 特 别 重 要 。 一个 ActiveX 控 件 不 能 与 MFC 静 态 链 接 起 来 , 这 就 意 味 着 , 如 果 用 户 硬 盘 上 不 存在 MFC 库 DLL 文 件 的 正 确 版 本 ,那 么 该 文 件 必 须 随 控 件 一 起 传 输 。更 为 糟 糕 的是 , MFC 需 要 C 运 行 时 间 库 , 因 此 Msrcrt.dll 文 件 也 可 能 需 要 下 载 。 当 用 户 首 先遇 到 一 个 显 示 依 靠 MFC 的 ActiveX 控 件 的 Web 页 的 时 候 , 将 自 动 传 输 库 文 件 。尽 管 对 于 用 户 通 过 快 速 网 络 连 向 的 内 部 Web 站 点 来 说 , 这 是 很 合 理 的 , 但 是 对于 Internet 来 说 却 是 不 现 实 的 。 既 然 MFC 的 大 小 大 约 为 1 兆 字 节 , 因 此 , 使 通
过 快 速 快 速 调 制 解 调 器 下 载 该 文 件 , 也 要 花 上 几 分 钟 的 时 间 。
BaseCtrl 框 架 也 就 是 人 们 所 熟 知 的 ActiveX Controls 框 架 ,是 MFC 的 一 个 简 便 的替 代 方 案 。 尽 管 BaseCtrl 为 开 发 者 提 供 的 支 持 比 MFC 少 得 多 , 但 它 却 提 供 了 更大 的 灵 活 性 。 从 BaseCtrl 中 创 建 的 ActiveX 控 件 既 不 需 要 MFC , 也 不 需 要 C 运行 时 间 库 。 因 为 框 架 仅 仅 提 供 了 最 少 的 代 码 , 因 此 该 控 件 比 它 的 MFC 同 伴 占 用更 少 的 内 存 。但 是 BaseCtrl 的 优 点 是 要 付 出 代 价 的 , 使 用 BaseCtrl 需 要 对 你 的 组件 做 更 多 的 工 作 , 并 且 要 对 C O M 和 ActiveX 的 原 理 有 更 深 入 的 理 解 。 例 如 , 你必 须 十 分 适 应 持 久 的 接 口 , 例 如 Istream , IPersistPropertyBag 和 IPersistStream 。框 架 通 过 三 个 主 类 提 供 了 核 心 功 能 , 这 三 个 主 类 的 名 字 分 别 为CAutomationObject, ColeControl 和 CPropertyPage 。
BaseCtrl 过 去 常 常 需 要 Visual Basic 4.0 来 开 始 一 个 项 目 , 但 现 在 不 需 要 了 。 你 现在 可 以 运 行 一 个 由 Visual C++ 提 供 的 NMake 实 用 程 序 来 开 始 一 个 项 目 , 第 一 次生 成 库 文 件 , 再 次 为 新 类 生 成 存 根 源 文 件 。 在 BaseCtrl 提 供 的 RendMe.txt 文 件中 解 释 了 这 个 过 程 。 在 安 装 Visual C++ 时 , 如 果 你 申 请 包 括 范 例 代 码 , 那 么ReadMe.txt 文 件 和 几 个 范 例 BaseCtl 项 目 的 源 代 码 将 位 于 文 件 夹MSDN\Samples\VC98\SDK\COM\AativeXC\BaseCtrl 之 中 。 如 果 想 得 到 BaseCtl 的 最 近 更 新 版 本 , 请 从 如 下 站 点 下 载 ActiveX 开 发 工 具 箱 :
http:www.microsoft.com/intdev/sdk
目 前 并 不 常 使 用 BaseCtl ,原 因 是 Active Template Library(ATL )提 供 了 一 种 创 建 小
ActiveX 控 件 的 高 级 工 具 。 尽 管 结 果 OCX 文 件 比 依 赖 于 MFC 的 类 似 控 件 要 大 , 但 通 过 ATL 创 建 的 控 件 通 常 占 用 更 少 的 内 存 , 原 因 是 它 不 需 要 其 他 辅 助 文 件 的存 在 。除 了 为 你 实 现 许 多 标 准 接 口 的 智 能 模 板 代 码 库 之 外 ,ATL 还 提 供 了 一 个 可以 生 成 初 始 源 文 件 的 向 导 , 这 就 大 大 简 化 了 ActiveX 控 件 项 目 的 早 期 阶 段 。 与 用ControlWizard 和 MFC 创 建 ActiveX 控 件 相 比 ,用 ATL 创 建 相 同 的 控 件 需 要 更 多的 工 作 , 但 对 于 创 建 用 Internet 上 使 用 的 ActiveX 控 件 来 讲 , ATL 是 一 个 很 好 的选 择 。
你 可 能 听 说 过 ATL 不 适 合 于 编 写 ActiveX 控 件 , 但 自 从 该 库 的 2.0 发 行 之 后 ,这种 说 法 就 不 再 正 确 了 。 不 过 , ATL 不 是 一 个 简 单 的 主 题 , 直 到 下 一 章 才 能 涉 及 到更 加 详 细 的 讨 论 。 本 章 将 介 绍 创 建 ActiveX 控 件 的 MFC 方 法 。 MFC 提 供 了 试 验ActiveX 编 程 的 最 好 方 法 , 并 提 供 了 充 足 的 信 息 。 除 了 可 以 为 你 节 省 大 量 的 编 码之外,使用 MFC 编 写 ActiveX 控 件 , 还 可 以 提 供 许 多 有 帮 助 的 Visual C++ 特 征 , 例 如 ClassWizard 和 ControlWizard 。 正 如 我 们 将 要 在 下 面 看 到 的 那 样 , ControlWizard 为 ActiveX 控 件 项 目 提 供 了 一 个 很 好 的 起 点 。
ControlWizard
应 用 与 AppWizard 为 MFC 应 用 程 序 创 建 项 目 相 同 的 方 法 ,ControlWizard 可 以 为ActiveX 控 件 创 建 一 个 项 目 。 在 ControlWizard 帮 助 下 创 建 的 控 件 使 用 MFC , 因此 也 就 具 有 前 一 节 所 介 绍 的 优 点 和 缺 点 。 ControlWizard 是 AppWizard 的 自 定 义的 形 式 , ControlWizard 项 目 以 和 普 通 AppWizard 项 目 相 同 的 方 式 开 始 。 从 File
菜 单 中 选 择 New 命 令 打 开 New 对 话 ,在 Projects 选 项 卡 中 单 击 代 表 MFC ActiveX ControlWizard 的 图 标 。 图 9-1 说 明 了 这 些 步 骤 。
图 9-1 用 ControlWizard 开 始 一 个 ActiveX 控件项目
在 创 建 项 目 之 前 , ControlWizard 将 引 导 完 成 两 个 步 骤 。 本 节 将 检 查 这 个 向 导 所提 供 的 选 项 , 并 讨 论 一 个 选 项 何 时 对 于 你 的 ActiveX 控 件 来 说 是 合 适 的 , 以 及 它为 什 么 合 适 。
图 9-2 显 示 了 ControlWizard 的 打 开 屏 幕 , 它 首 先 询 问 你 项 目 中 控 件 的 数 量 。 与VBX 自 定 义 的 控 件 类 似 , 一 个 OCX 文 件 可 以 包 含 不 止 一 个 ActiveX 控 件 组 件 。请 在 对 话 顶 部 的 文 本 框 中 指 定 你 想 要 的 控 件 数 量 。 以 后 , 在 项 目 开 发 期 间 , 你 还可 以 添 加 控 件 。 使 用 ControlWizard 屏 上 的 下 一 个 选 项 , 可 以 通 过 许 可 来 限 制 控件 的 使 用 。 尽 管 许 可 支 持 选 项 在 默 认 状 态 下 是 关 闭 的 , 对 于 一 般 用 途 的 许 多 控件 , 应 该 具 有 简 短 描 述 的 许 可 保 护 。
图 9-2 ControlWizard 的 步 骤 1
步 骤 1 中 的 最 后 一 个 选 项 用 于 指 导 ControlWizard 为 控 件 生 成 帮 助 文 件 。 对 项 目帮 助 文 件 的 申 请 ,所 添 加 的 帮 助 支 持 种 类 与 从 AppWizard 中 得 到 的 相 同 。如 果 想获 得 所 生 成 的 帮 助 文 件 的 说 明 , 请 参 阅 第 2 章 开 始 部 分 的 讨 论 。
ControlWizard 的 第 二 屏 ( 图 9-3) 展 现 了 决 定 控 件 如 何 与 包 容 器 进 行 交 互 的 选 项 。第 二 步 中 的 选 项 需 要 解 释 一 下 , 因 此 , 下 面 详 细 对 它 们 说 明 。
图 9-3 ControlWizard 的 步 骤 2
- Activates when Visible ( 可 见 时 激 活 ): 决 定 当 控 件 可
见 时 , 包 容 器 是否 应 自 动 激 活 它 。 对 于 ActiveX 控 件 , 通 常 需 要 立 即 激 活 , 尽 管 选 中
Activates When Visible 选 项 仅 仅 作 为 包 容 器 应 用 程 序 的 线 索 , 它 可 能忽 略 申 请 。 如 下 的 讨 论 有 很 多 是 与 此 选 项 有 关 的 。
- Invisible At Run-Time ( 运 行 时 不 可 见 ): 如 果 选 中 了
该 选 项 , ControlWizard 将 不 把 OnDraw 函 数 添 向 控 件 类 。 可 以 为 那 些 不 需 要 与用 户 进 行 交 互 的 控 件 使 用 该 选 项 , 例 如 第 8 章 中 介 绍 的 Timer Object
控 件 。
- **Available In Insert Object Dialo g( 在 插 入 对 象 对 话 中 可 用
)**: 与 从 Test Container 工 具 到 M icrosoft Office 应 用 程 序 的 许 多 包 容 器 应 用 程 序 提 供的 Insert Object 对 话 ( 或 等 价 物 ) 相 关 。 不 选 中 该 选 项 , 将 通 知 包 容 器 ,
它 不 应 该 在 包 容 器 的 Insert Object 对 话 中 包 括 这 个 新 控 件 。
-
Has An About Box ( 有 一 个 About 框 ): ControlWizard 将 为
About 方法 生 成 源 代 码 , 为 常 用 About 框 生 成 资 源 。
-
**Acts As A Simple Frame Contro l( 作 为 一 个 简 单 的 框 架 控 件
)**: 为IsimpleFrame- Site 接 口 添 加 支 持 。 该 选 项 将 设 置 控 件 , 来 充 当 框 架 , 用 来 包 围 包 容 器 窗 口 中 其 他 ActiveX 控 件 , 将 控 件 分 组 , 并 允 许 它 们
一 起 移 动 。 并 不 是 所 有 的 包 容 器 都 支 持 简 单 的 框 架 。
- W indow Subclassin g( 窗 口 细 分 类 ):通 过 对 诸 如 编 辑
框 或 进 程 指 示 器之 类 的 普 通 W indows 控 件 进 行 细 分 类 , 来 建 立 控 件 项 目 。
在 默 认 状 态 下 , ControlWizard 仅 仅 打 开 上 面 列 出 的 两 个 选 项 Activates when Visible 和 Has An About Box 。 取 消 选 中 Activate when Visible 复 选 框 , 将 清 除 掉该 控 件 的 OLEMISC_ACTIVATEWHENVISIBLE 状 态 标 志 , 该 标 志 是 控 件 通 过MFC 的 AfxOLeRegisterControlClass 全 局 函 数 放 置 在 它 的 注 册 表 数 据 之 中 的 。 嵌入 控 件 的 包 容 器 可 以 通 过 调 用 控 件 的 IOleObject::GetMiscStatus 方 法 来 确 定 该 标志 的 状 态 。 清 楚 的 标 志 将 向 包 容 器 传 递 信 号 , 当 控 件 可 见 时 , 它 应 该 保 持 非 激 活状 态 , 这 样 , 将 延 迟 对 控 件 窗 口 的 创 建 , 直 至 用 户 需 要 它 。 对 于 ActiveX 控 件 , 这 将 节 省 创 建 窗 口 这 一 不 必 要 的 操 作 。 第 10 章 将 详 细 讨 论 ActiveX 控 件 状 态 标志 , 例 如 OLEMISC_ACTIVATEWHUENVISIBLE 。
你 可 能 还 想 考 虑 关 闭 About Box 选 项 ,因 为 对 About 框 添 加 的 支 持 将 增 大 完 成 之后 的 ActiveX 控 件 的 容 量 。 例 如 由 ControlWizard 生 成 的 标 准 About 框 将 向 完 成之 后 的 OCX 文 件 添 加 大 约 2KB 的 额 外 代 码 和 资 源 文 件 。如 果 想 了 解 有 关 最 小 化资 源 数 据 的 其 他 方 法 , 请 参 考 第 4 章 的 最 后 一 节 , 这 对 ActiveX 控 件 来 说 是 特 别重 要 的 。
如 果 选 中 了 Available In Insert Object Dialog 复 选 框 , 那 么 , 该 控 件 的 自 我 注 册 过程 将 向 控 件 的 CLSID 注 册 表 层 次 添 加 名 为 Insertable 的 Registry 键 。Insertable 键将 通 知 包 容 器 ActiveX 控 件 可 以 充 当 被 动 嵌 入 的 对 象 。 这 样 , 包 容 器 就 能 够 通 过
OLE Documents 接 口 创 建 ActiveX 控 件 的 对 象 。这 些 用 IOle 前 缀 标 识 的 接 口 包 括IOleCache, IOleClientSite, IOleContainer, IOleInplaceObject 和 IOleInPlaceSite 。 可以 在 包 容 器 文 档 中 嵌 入 对 象 的 应 用 程 序 为 寻 找 具 有 Insertable 键 的 对 象 而 扫 描 注册 表 , 并 在 标 准 的 Object 对 话 中 列 出 了 一 个 对 象 列 表 。 例 如 , 为 了 在 M icrosoft Word 中 看 到 这 个 列 表 , 请 单 击 Word 的 Insert 菜 单 上 的 Object 命 令 。 仅 仅 当Available In Insert Object Dialog 复 选 框 打 开 的 时 候 , 使 用 ControlWizard 创 建 的ActiveX 控 件 才 能 出 现 在 列 表 之 中 。 不 支 持 包 容 器 文 档 的 应 用 程 序 将 忽 略Insertable 键 。 例 如 , 我 们 在 第 8 章 中 所 遇 到 的 Test Container 实 用 程 序 中 的 New Contro l, 将 显 示 一 个 所 有 注 册 控 件 的 列 表 , 而 不 管 它 是 否 标 记 为 可 插 入 的 。
如 果 你 想 让 ActiveX 控 件 对 标 准 或 普 通 W indows 控 件 进 行 细 分 , 那 么 请 单 击 图9-3 中 屏 幕 底 部 显 示 的 框 。 该 下 拉 窗 口 将 显 示 一 个 列 表 , 其 范 围 从 按 钮 到 树 状 视图 的 16 个 W indows 控 件 。 从 该 列 表 中 选 中 一 条 目 , 将 导 致 ControlWizard 为 对选 中 的 Windows 控 件 进 行 细 分 的 ActiveX 控 件 生 成 源 代 码 。 使 用 该 选 项 可 以 生成 只 有 某 个 W indows 控 件 的 特 性 的 ActiveX 控 件 , 但 是 , 为 了 添 加 所 需 要 的 效果 , 你 可 能 还 要 进 行 修 改 。
在 ControlWizard 的 步 骤 2 中 单 击 Advanced 按 钮 ,来 打 开 如 图 9-4 所 示 的 Advanced ActiveX Features 对 话 。该 对 话 提 供 了 用 来 设 置 或 清 除 由 ColeControl::ControlFlags 枚 举 集 定 义 的 位 标 志 , 该 枚 举 集 描 述 了 控 件 被 激 活 时 的 行 为 特 性 。 设 置 任 何 复 选框 都 将 导 致 ControlWizard 添 加 超 越 ColeControl::GetControlFlags 方 法 的 代 码 ,该方 法 的 作 用 是 将 ControlFlags 设 置 通 知 给 包 容 器 。 为 了 在 现 有 的 控 件 项 目 中 添 加
超 越 代 码 , 请 设 置 特 定 的 位 标 志 , 例 如 下 面 的 W indowlessActivate:
DWORD CDemoCtrl::GetControlFlags()
{
return COleControl::GetControlFlags() | windowlessActivate;
}
在 Advanced ActiveX Features 对 话 中 的 复 选 框 标 签 看 上 去 很 简 洁 , 但 是 , 略 加 解释 之 后 是 可 以 很 容 易 理 解 它 的 。下 面 的 列 表 说 明 了 标 志 的 含 义 。如 果 想 了 解 有 关ControlFlags 设 置 , 以 及 它 们 是 如 何 影 响 到 控 件 动 作 的 更 为 详 细 的 信 息 , 请 查 阅题 目 为 “ ActiveX Controls::Optimization ” 的 帮 助 文 章 , 通 过 索 引 条 目 Optimizing ActiveX Controls 可 以 找 到 它 。
图 9-4 通过单击 ControlWizard 的 Advanced 按钮调用 Advanced ActiveX Features 对 话
-
W indowless Activation (无 窗 口 激 活 ) :通 知 包 容 器 ,控
件 在 激 活 时 不 创建 自 己 的 窗 口 。 下 面 的 讨 论 有 许 多 是 关 于 无 窗 口 激 活 的 。
-
Upclipped Device Context( 不 剪 辑 设 备 上 下 文 ): 申 请 不
要 剪 辑 控 件
的 显 示 , 这 样 处 理 会 更 快 。 不 过 , 控 件 必 须 确 保 不 在 站 点 边 界 之 外 显示 。
- Flicker-Free Activatio n( 激 活 时 不 闪 烁 ):要 求 包 容 器
要 在 控 件 切 换 状态 时 不 要 使 控 件 窗 口 无 效 。 该 操 作 将 阻 止 控 件 在 成 为 活 动 或 不 活 动 的时 候 重 绘 自 己 , 这 样 也 就 清 除 了 可 能 发 生 的 轻 微 的 闪 烁 。 该 选 项 仅 仅
适 合 于 不 管 状 态 如 何 而 以 同 一 种 方 式 绘 制 自 己 的 控 件 。
- Mouse Pointer Notification s( 鼠 标 指 针 通 知 ):要 求 包 容
器 在 控 件 不 处于 活 动 状 态 对 ActiveX 控 件 发 送 鼠 标 消 息 。 如 果 包 容 器 满 足 了 要 求 , 那 么 , 非 活 动 的 控 件 将 继 续 接 收 与 控 件 窗 口 上 鼠 标 活 动 有 关 的
WM_SETCURSOR 和 W M _ M O U S E M O V E 消 息 。 选 中 该 选 项 将 启 用IPointerInactive 接 口 , 包 容 器 将 把 这 些 属 于 控 件 的 消 息 委 托 给 这 个 接口 。 IPointerInactive 接 口 为 控 件 窗 口 调 整 每 条 消 息 的 鼠 标 坐 标 , 并 且通 过 控 件 的 消 息 映 射 分 派 消 息 。 通 过 这 项 功 能 , 控 件 甚 至 可 以 在 非 活动 状 态 实 现 拖 放 目 标 的 功 能 。
- Optimized Drawing ( 优 化 的 绘 制 ): 通 过 允 许 控 件 的
OnDraw 方 法 在返 回 时 不 恢 复 用 于 设 备 上 下 文 的 原 始 GDI 对 象 , 来 提 高 绘 制 的 速 度 。仅 仅 当 包 容 器 支 持 优 化 的 绘 制 时 , 该 选 项 才 起 作 用 , 而 包 容 器 是 否 支
持 优 化 的 绘 制 ,由 控 件 通 过 调 用 COleControl::IsOptimizedDraw 函 数 来确 定 。 返 回 值 TRUE 意 味 着 该 控 件 不 必 在 完 成 绘 制 之 后 不 必 选 中 诸 如
钢 笔 和 刷 子 之 类 的 原 始 G D I 对 象 , 便 可 回 到 设 备 上 下 文 之 中 。
- Load Properties Asynchronously ( 异 步 加 载 属 性 ): 该 选
项 可 以 提 高ActiveX 控 件 的 响 应 性 ,而 且 这 种 控 件 需 要 大 量 属 性 数 据 。异 步 加 载 将允 许 控 件 在 Web 页 上 尽 可 能 地 成 为 活 动 状 态 , 即 使 在 浏 览 器 在 后 台 连
续 不 断 地 通 过 调 制 解 调 器 加 载 该 控 件 的 数 据 时 也 是 如 此 。 例 如 , 该 控件 这 样 便 可 以 立 即 播 放 音 频 或 视 频 片 断 , 而 无 需 等 待 完 整 的 数 据 集 。不 过 , 控 件 必 须 不 对 尚 未 到 达 的 数 据 进 行 操 作 。 异 步 加 载 为 控 件 增 添了 负 担 , 因 此 , 应 该 仅 仅 为 可 以 从 中 受 益 的 控 件 使 用 该 选 项 。
W indowless Activation 选 项 与 前 面 介 绍 的 Invisible At Run-Time 标 记 不 是 同 一 件事 情 。 无 窗 口 激 活 仅 仅 意 味 着 控 件 不 提 供 它 自 己 的 窗 口 。 通 过 不 创 建 窗 口 , 控 件优 化 了 创 建 的 速 度 ,同 时 稍 微 降 低 了 一 点 儿 可 执 行 文 件 的 大 小 。如 果 包 容 器 支 持无 窗 口 对 象 的 话 , 该 控 件 可 以 自 由 使 用 包 容 器 的 窗 口 服 务 。 这 种 支 持 需 要IOleInPlaceObjectWindowless 接 口 来 将 用 户 输 入 消 息 反 应 到 无 窗 口 控 件 。 通 过 覆盖 包 容 器 的 窗 口 ,无 窗 口 控 件 可 以 带 有 真 正 透 明 的 背 景 , 对 于 显 示 自 己 矩 形 窗 口的 普 通 ActiveX 控 件 来 说 , 达 到 这 种 效 果 是 不 可 能 的 。 不 过 , 使 用 第 4 章 中 介 绍的 使 背 景 透 明 的 想 法 ,一 个 窗 口 控 件 通 常 可 以 将 它 自 己 的 窗 口 颜 色 与 包 容 器 的 背景 颜 色 相 匹 配 , 来 模 拟 透 明 背 景 。 正 如 在 前 一 章 中 所 提 到 的 那 样 , 控 件 可 以 通 过调 用 ColeControl::AmbientBackColor 来 确 定 包 容 器 周 围 的 当 前 颜 色 :
OLE_COLOR ContainerBkGrnd = AmbientBackColor();
并 非 所 有 的 包 容 器 都 支 持 ColeControl 的 Ambient 函 数 。 在 调 用 诸 如
AmbientBackColor 之 类 的 函 数 之 后 。 一 个 ActiveX 控 件 应 该 检 查 有 效 返 回 值 。
许 可
放 置 在 流 行 Web 上 的 Active 控 件 不 久 便 可 以 在 全 世 界 的 计 算 机 上 出 现 , 在 数 以千 计 的 浏 览 器 上 查 看 。这 种 简 单 重 复 使 用 ActiveX 控 件 的 能 力 可 能 是 该 项 技 术 最吸 引 人 的 特 征 和 最 大 的 优 点 。 不 过 , 这 样 的 话 , 便 引 起 了 有 关 程 序 员 的 知 识 产 权的 潜 在 问 题 。 为 了 更 清 楚 地 认 识 这 一 问 题 , 请 考 虑 一 下 ActiveX 控 件 是 如 何 传 过Author、 Webmaster 和 User 这 迥 然 不 同 的 三 方 面 的 。
收 取 一 定 的 费 用 ,Author 便 允 许 Webmaster 在 Web 页 上 安 装 ActiveX 控 件 。User 通 过 Internet 访 问 Webmaster 的 站 点 ,结 果 ,ActiveX 控 件 将 从 Webmaster 的 计 算机 复 制 到 User 的 计 算 机 ; 在 User 的 浏 览 器 程 序 中 出 现 。 到 此 为 止 , 每 件 事 情 都如 期 进 行 , 控 件 的 使 用 像 Author 所 期 望 的 那 样 。 但 是 , 如 果 不 采 取 一 些 安 全 措施 的 话 , 拥 有 这 个 ActiveX 控 件 的 其 他 程 序 员 就 可 以 在 自 己 的 应 用 程 序 中 使 用它 。 许 多 开 发 者 不 愿 意 在 未 经 授 权 的 情 况 下 , 他 们 的 产 品 用 这 种 方 式 被 重 用 , 特别 是 在 可 以 得 益 的 商 品 应 用 程 序 之 中 使 用 。
要 防 止 未 经 授 权 使 用 ActiveX 控 件 , 最 常 用 的 措 施 是 包 含 一 个 许 可 证 。 许 可 证 不仅 将 我 们 的 范 例 中 的 Author 标 识 为 作 为 控 件 版 权 的 所 有 者 , 而 且 还 可 以 阻 止 没
有 从 Author 处 收 到 许 可 证 的 开 发 者 随 便 重 用 控 件 。 OLE/ActiveX 控 件 标 准 的 设计 考 虑 到 了 许 可 证 。 该 标 准 定 义 了 IClassFactory2 接 口 , 包 容 器 可 以 通 过 该 接 口来 创 建 控 件 对 象 的 一 个 实 例 ,并 证 明 它 自 己 已 获 得 了 使 用 该 控 件 的 许 可 证 。仅 仅当 包 容 器 满 足 了 有 效 许 可 证 存 在 的 控 件 的 时 候 , 才 能 完 成 控 件 对 象 的 创 建 。
许 可 证 已 成 为 ActiveX 控 件 普 遍 采 用 的 方 法 ,因 此 ,详 细 检 查 一 下 ControlWizard 的 许 可 证 方 案 还 是 十 分 值 得 的 。 在 这 个 主 题 上 花 费 时 间 的 另 一 个 原 因 是 , Visual C++ 在 线 文 档 中 的 许 可 证 说 明 不 十 分 清 楚 , 这 主 要 是 因 为 当 可 能 涉 及 到 几 个 包 容器 的 时 候 , 这 个 文 档 将 说 “ the container( 这 个 包 容 器 )”。 记 住 , 任 何 程 序 都 是可 以 创 建 ActiveX 控 件 的 一 个 实 例 , 并 为 之 提 供 站 点 的 包 容 器 , 这 一 点 是 很 重 要的 。 第 8 章 演 示 了 可 以 在 不 同 环 境 下 嵌 入 ActiveX 控 件 的 几 个 不 同 的 包 容 器 :
- Internet Explorer 或 另 一 个 可 察 知 Active 的 浏 览 器 , 它 通
过 由 H T M L
文 档 中 的 OBJECT 标 记 指 定 的 类 标 识 符 来 定 位 控 件 。
-
Visual C++ 对 话 编 辑 器 ,当 它 被 放 入 开 发 环 境 下 的 一 个
对 话 中 时 ,创 建控 件 的 一 个 实 例 。
-
Hour 程 序 这 样 的 包 容 器 应 用 程 序 , 它 可 以 在 运 行 时 嵌
入 一 个 ActiveX
控 件 。
许 可 证 方 案 可 以 阻 止 包 容 器 应 用 程 序 未 经 授 权 便 使 用 控 件 ,但是,阻止哪一个包容 器 呢 ? 在 我 们 的 方 案 中 , 当 User 连 同 显 示 Author 的 控 件 的 H T M L 指 令 一 起 下
载 该 控 件 的 时 候 , User 的 浏 览 器 必 须 能 够 自 由 运 行 该 控 件 , 而 不 必 使 用 许 可 证 。访 问 应 该 仅 仅 限 制 到 列 表 中 的 其 他 两 种 类 型 的 包 容 器 :也 就 是 开 发 程 序 ( 像 Visual C++ ) 和 以 及 它 们 所 创 建 的 包 容 器 应 用 程 序 ( 像 Hour) 。
当 Webmaster 决 定 开 发 使 用 Author 的 ActiveX 控 件 的 时 候 。 应 该 考 虑 一 连 串 的事 件 。 为 了 创 建 应 用 程 序 , Webmaster 运 行 一 个 Windows 开 发 程 序 , 例 如 Visual C++ 或 Visual Basic 。 这 个 程 序 设 计 要 求 应 用 程 序 在 对 话 框 中 显 示 控 件 , 因 此 , Webmaster 使 用 对 话 编 辑 器 本 身 就 是 包 容 器 创 建 控 件 的 一 个 实 例 , 并 在 该 对 话 中显 示 它 。 这 个 时 候 , 叫 做 设 计 阶 段 , 许 可 证 问 题 便 出 现 了 。 在 创 建 控 件 实 例 的 过程 中 , 开 发 程 序 将 调 用 该 控 件 的 IClassFactory2::CreateInstanceLic 方 法 , 其 中 的参 数 为 NULL ,控 件 通 过 仅 仅 在 确 认 许 可 证 存 在 之 后 返 回 到 接 口 的 一 个 指 针 来 响应 该 函 数 ( 过 一 会 儿 , 我 们 将 看 到 是 如 何 响 应 的 )。
既 然 Webmaster 被 授 权 可 以 在 应 用 程 序 中 使 用 该 控 件 ,那 么 许 可 证 验 证 将 获 得 成功 , 对 话 编 辑 器 可 以 创 建 该 控 件 的 一 个 实 例 。 Webmaster 完 成 应 用 程 序 的 开 发 , 并 将 可 执 行 文 件 的 副 本 出 售 给 User。 作 为 软 件 包 的 一 部 分 , Webmaster 提 供 一 个安 装 程 序 ,用 来 在 User 的 硬 盘 上 放 置 Author 的 ActiveX 控 件 的 副 本 ,并 注 册 它 。尽 管 User 从 来 没 有 与 Author 一 起 签 订 过 许 可 证 协 议 , 新 的 包 容 器 应 用 程 序 将 在运 行 时 成 功 地 创 建 控 件 对 象 的 一 个 实 例 ( 这 个 处 理 过 程 过 一 会 再 解 释 )。 这 叫 做 许可 证 验 证 的 运 行 阶 段 。
现 在 ,让 我 们 来 考 虑 一 下 当 User( 恰 好 又 是 程 序 员 ) 试 图 创 建 嵌 有 Author 控 件 的 另
一 个 包 容 器 应 用 程 序 的 时 候 ,会 有 什 么 事 情 像 以 前 一 样 发 生 。User 的 开 发 程 序 调用 控 件 的 IClassFactory2::CreateInstanceLic 方 法 , 但 这 次 , 控 件 检 测 到 User 没 有拥 有 许 可 证 , 因 此 也 就 不 允 许 这 一 创 建 企 图 。 有 决 心 的 User 可 以 在 没 有 对 话 编辑 器 的 帮 助 下 开 发 应 用 程 序 ,但 是 ,完 成 之 后 的 应 用 程 序 不 再 能 够 创 建 该 控 件 的一 个 实 例 。未 经 授 权 的 控 件 中 的 许 可 证 验 证 代 码 既 在 设 计 时 使 用 , 又 在 运 行 时 使用 。
ControlWizard 添 向 控 件 项 目 的 代 码 可 以 实 现 类 似 刚 才 介 绍 的 许 可 证 方 案 。 下 一节 将 解 释 该 方 案 是 如 何 工 作 的 。
Control W izard 许 可 证 支 持
如 图 9-2 所 示 ,ControlWizard 的 初 始 屏 幕 提 供 了 对 许 可 证 协 议 的 支 持 。如 果 你 为新 控 件 要 求 运 行 时 间 许 可 证 , 那 么 , ControlWizard 将 生 成 额 外 的 源 代 码 和 文 本文 件 ,它 们 一 起 可 以 保 证 使 你 的 控 件 将 只 被 经 过 授 权 的 人 使 用 。文 本 文 件 是 带 有LIC 扩 展 名 的 文 档 , 包 含 如 下 文 字 。
Copyright (c) 1998 author
Warning: This product is licensed to you pursuant to the terms of the
license agreement included with the original software, and is protected by copyright law and international treaties. Unauthorized reproduction or distribution may result in severe civil and criminal
penalties, and will be prosecuted to the maximum extent possible under the law.
在 许 可 证 的 第 一 行 中 , 单 词 author 代 表 你 的 用 户 名 或 公 司 名 。 像 这 样 一 个 由
ControlWizard 实 现 的 基 于 文 件 的 许 可 证 方 案 , 当 包 容 器 应 用 程 序 在 设 计 时 添 加该 控 件 的 时 候 , 需 要 LIC 文 档 与 控 件 OCX 文 件 存 在 于 同 一 目 录 之 中 。 由 于 这 个原 因 ,Author 必 须 向 Webmaster 分 发 这 个 LIC 文 本 文 件 ,以 便 Webmaster 可 以 创建 使 用 该 控 件 的 包 容 器 应 用 程 序 。但 许 可 证 的 期 限 阻 止 了 Webmaster 将 该 文 档 重新 分 发 给 别 人 。用 户 不 需 要 LIC 文 件 便 可 运 行 Webmaster 的 应 用 程 序 ,并 或 在 浏览 ControlWizard 在 主 项 目 文 件 夹 中 放 置 LIC 文 件 的 主 副 本 。 当 Visual C++ 创 建这 个 OCX 文 件 的 时 候 , 它 将 这 个 LIC 文 件 从 主 文 件 夹 复 制 到 OCX 文 件 驻 留 的Release 或 Debug 文 件 夹 ,因 此 , 最 终 一 个 项 目 可 能 有 两 三 个 许 可 证 文 件 的 副 本 。在 创 建 OCX 文 件 之 前 , 应 当 制 订 文 件 主 副 本 的 更 改 方 案 , 以 便 所 有 的 副 本 保 持最 新 。
ControlWizard 添 加 到 项 目 的 源 代 码 由 两 个 函 数 组 成 , 一 个 叫 做 GetLicenseKey,
它 可 以 从 该 控 件 的 OCX 文 件 中 检 索 到 一 个 独 一 无 二 的 密 码 或 关 键 字 , 另 一 个 函数 叫 做 VerifyUserLicense , 它 为 许 可 证 文 本 文 件 的 存 在 检 查 用 户 磁 盘 的 指 定 位置 。一 个 名 为 License 什 么 都 不 做 的 项 目 ,演 示 了 ControlWizard 是 如 何 把 这 两 个函 数 添 加 到 控 件 的 类 源 代 码 之 中 的 。 你 没 有 必 要 自 己 创 建 License 项 目 , 因 为 不在 这 里 开 发 控 件 。 它 的 作 用 仅 仅 是 使 得 如 下 讨 论 更 加 容 易 , 同 时 用 来 阐 述ControlWizard 的 许 可 证 方 案 。
下 面 是 这 两 个 函 数 的 源 代 码 清 单 ,它 取 自 该 项 目 的 LicenseCtl.cpp 实 现 文 件 ( 第 三行 上 的 _s2LicString 文 字 根 据 系 统 不 同 而 有 所 不 同 )。
static const TCHAR BASED_CODE _szLicFileName[] = _T("License.lic");
static const WCHAR BASED_CODE _szLicString[] = L"Copyright (c) 1998 Witzend Software";
/////////////////////////////////////////////////////////////////////////////
// CLicenseCtrl::CLicenseCtrlFactory::VerifyUserLicense -
// Checks for existence of a user license
BOOL CLicenseCtrl::CLicenseCtrlFactory::VerifyUserLicense()
{
return AfxVerifyLicFile(AfxGetInstanceHandle(), _szLicFileName,
_szLicString);
}
/////////////////////////////////////////////////////////////////////////////
// CLicenseCtrl::CLicenseCtrlFactory::GetLicenseKey -
// Returns a runtime licensing key
BOOL CLicenseCtrl::CLicenseCtrlFactory::GetLicenseKey(DWORD dwReserved, BSTR FAR* pbstrKey)
{
if (pbstrKey == NULL) return FALSE;
*pbstrKey = SysAllocString(_szLicString);
return (*pbstrKey != NULL);
}
在 设 计 时 , 当 开 发 程 序 企 图 将 一 个 ActiveX 控 件 插 入 到 包 容 器 项 目 时 , 也 就 是 当对 话 编 辑 器 向 正 在 开 发 中 的 对 话 添 加 控 件 的 时 候 , VerifyUserLicense 和GetLicenseKey 函 数 均 被 调 用 。 当 开 发 完 成 , 而 且 包 容 器 应 用 程 序 已 经 建 立 并 执行 的 时 候 , 它 在 运 行 时 创 建 控 件 实 例 的 企 图 , 会 导 致 了 再 调 用 该 控 件 的GetLicenseKey 函 数 。 让 我 们 分 别 考 查 一 下 这 两 种 情 形 , 首 先 看 一 看 在 设 计 时 控件 是 如 何 为 开 发 程 序 验 证 许 可 证 的 存 在 的 。
设 计 时 间 许 可 证 验 证
通 过 调 用 控 件 的 IClassFactory2::CreateInstanceLic 方 法 , 开 发 程 序 事 实 上 是 在 说 : “ 如 果 有 效 许 可 证 存 在 , 就 创 建 控 件 的 一 个 新 实 例 , 并 返 回 一 个 指 向 该 实 例 接 口的 指 针 ”。 该 控 件 的 工 作 便 是 验 证 许 可 证 的 存 在 。 当 CreateInstanceLic 被 调 用 的时 候 ,框 架 将 调 用 路 由 到 控 件 的 VerifyUserLicense 函 数 ,该 函 数 用 来 确 认 LIC 许可 证 文 件 与 控 件 OCX 文 件 存 在 于 同 一 目 录 之 中 , 而 且 , 该 文 件 的 第 一 行 与
_szLicString 参 数 的 内 容 相 匹 配 。_szLicString 参 数 就 是 人 们 所 熟 知 的 许 可 证 关 键字 。
如 果 LIC 文 件 存 在 , 并 且 包 含 正 确 的 许 可 证 关 键 字 , 那 么 , VerifyUserLicense 将返 回 值 TRU E , 允 许 开 发 程 序 为 正 在 开 发 之 中 的 包 容 器 应 用 程 序 创 建 ActiveX 控件 的 实 例 。开 发 程 序 下 一 步 调 用 控 件 的 IclassFactory2::RelestLickey 方 法 。这 个 调用 以 GetLicenseKey 结 束 ,该 函 数 是 ControlWizard 向 控 件 源 代 码 添 加 的 两 个 函 数中 的 第 二 个 。 GetLicenseKey 将 返 回 控 件 的 _szLicenseKey 关 键 字 的 一 个 副 本 , 开发 程 序 将 它 嵌 入 在 包 容 器 应 用 程 序 的 可 执 行 文 件 之 中 。当 我 们 谈 到 运 行 时 间 许 可证 的 时 候 , 我 们 还 将 看 到 为 什 么 包 容 器 需 要 它 自 己 的 关 键 字 副 本 。
如 果 控 件 没 有 在 与 OCX 文 件 相 同 的 目 录 中 找 到 LIC 文 件 , 或 者 , 如 果 该 许 可 证文 件 的 第 一 行 已 经 被 改 变 了 , 那 么 VerifyUserLicense 将 返 回 FALSE , 在 这 种 情况 下 , 开 发 程 序 将 显 示 一 条 解 释 该 问 题 的 错 误 信 息 。 例 如 , 下 面 是 当 LicenseLic 文 件 被 改 变 或 重 命 名 之 后 ,Visual C++ 是 如 何 处 理 这 种 情 形 的 ( 如 果 你 想 自 己 试 一试 , 一 定 要 在 包 含 OCX 文 件 的 子 文 件 夹 中 改 变 LicenseLic 文 件 , 因 为 在 控 件 创建 之 后 , 改 变 项 目 文 件 夹 中 的 主 副 本 是 没 有 任 何 效 果 的 )。 假 定 你 正 在 开 发 一 个叫 做 DemoContainer 的 包 容 器 应 用 程 序 ,并 且 希 望 把 License ActiveX 控 件 添 加 到DemoContainer 的 About 框 , 请 在 对 话 编 辑 器 中 加 载 About 框 资 源 , 并 在 工 作 区内 右 击 来 显 示 该 编 辑 器 的 上 下 文 菜 单 。 从 上 下 文 菜 单 中 选 择 Insert ActiveX Control 命 令 , 然 后 从 图 9-5 所 示 的 列 表 中 选 择 License Contro l。
图 9-5 向 包 容 器 项 目 插 入 License 控 件
对 话 编 辑 器 企 图 创 建 License 控 件 的 一 个 实 例 , 这 导 致 了 一 系 列 的 函 数 调 用 。 编辑 器 首 先 调 用 控 件 的 IClassFactory2::CreateInstanceLic 方 法 , 该 方 法 依 次 调 用 控件 的 VerifyUserLicense 函 数 ,该 函 数 调 用 框 架 的 A fxVerifyLicFile 函 数 。这 个 MFC 函 数 读 取 由 szLicenseName 字 符 串 ( 这 里 是 LicenseLic) 来 标 识 的 文 件 ,如 果 该 文 件存 在 的 话 , 将 它 的 第 一 行 与 包 含 在 _szLicString 中 的 许 可 证 关 键 字 进 行 比 较 。 如果 文 件 不 存 在 , 或 第 一 行 与 许 可 证 关 键 字 不 匹 配 , A fxVerifyLicFile 将 返 回 值FALSE , 来 禁 止 对 象 创 建 。 结 果 会 出 现 Visual C++ 的 一 条 错 误 信 息 , 来 解 释 为 何失 败 :
运 行 时 间 许 可 证
如 果 许 可 证 文 件 存 在 , 并 且 , VerifyUserLicense 函 数 返 回 了 值 TRUE , 那 么 开 发程 序 将 成 功 地 创 建 控 件 实 例 ,并 将 控 件 类 的 源 文 件 插 入 到 包 容 器 项 目 之 中 。直 到包 容 器 应 用 程 序 被 创 建 并 成 为 可 执 行 程 序 之 后 , 才 发 生 下 一 次 有 效 许 可 证 的 验证 。
当 包 容 器 应 用 程 序 运 行 , 并 试 图 创 建 ActiveX 控 件 的 一 个 实 例 的 时 候 , 它 还 调 用该 控 件 的 IClassFactory2:: CreateInstancelic 方 法 。 但 是 与 开 发 程 序 在 设 计 时 间 为第 四 个 参 数 传 递 的 NULL 值 不 同 , 应 用 程 序 提 供 了 一 个 指 向 它 的 许 可 证 关 键 字副 本 的 指 针 。 这 个 字 符 串 与 开 发 程 序 从 控 件 的 GetLicensekey 函 数 获 得 并 放 在 应用 程 序 的 数 据 之 中 的 字 符 中 是 相 同 的 。 对 CreateInstanceLic 的 调 用 即 :“ 创 建 控
件 的 一 个 新 实 例 , 这 里 是 证 明 我 是 一 个 经 过 授 权 的 包 容 器 的 证 据 。” 控 件 将
_szlicString 中 它 自 己 的 许 可 证 关 键 字 副 本 与 包 容 器 递 交 的 副 本 进 行 比 较 ,验 证 匹配 , 并 允 许 创 建 操 作 。 在 这 种 情 况 下 , 框 架 不 调 用 VerifyUserLicense 函 数 , 这 就是 当 应 用 程 序 在 User 的 机 器 上 运 行 时 , 为 什 么 不 需 要 LIC 文 件 的 原 因 。 仅 仅 是Webmaster 机 器 上 的 开 发 程 序 而 不 是 完 成 后 的 包 容 器 应 用 程 序 过 需 要 许 可 证 。
为 了 获 得 更 多 的 保 护 , 改 变 许 可 证 LIC 文 件 的 第 一 行 , 使 文 字 更 具 体 些 。但 是 要对 源 代 码 中 的 _szLicString 字 符 数 组 做 出 相 同 的 改 变 , 否 则 , VerifyUserLicense 将 识 别 不 了 这 个 文 本 文 件 。你 还 可 能 考 虑 修 改 这 里 介 绍 的 基 于 文 件 的 许 可 证 方 案来 依 靠 系 统 注 册 表 中 而 不 是 LIC 文 本 文 件 的 关 键 字 。这 将 需 要 为 搜 索 注 册 表 简 单地 重 写 一 下 VerifyUserLicense 函 数 , 而 且 , 还 要 假 定 已 经 安 装 了 程 序 , 来 为 授 权的 许 可 证 注 册 关 键 字 。
例 1 : 什 么 都 不 做 的 ActiveX 控 件
在 开 始 ActiveX 控 件 的 开 发 之 前 , 我 们 应 该 清 楚 地 了 解 ControlWizard 对 项 目 的巨 大 贡 献 。 最 好 的 办 法 是 运 行 ControlWizard , 并 从 生 成 的 源 文 件 中 创 建 一 个 什么 都 不 做 的 控 件 。 正 如 我 们 将 要 在 下 一 节 中 所 看 到 的 , 创 建 一 个 有 用 的 ActiveX 控 件 需 要 做 一 些 工 作 , 并 且 需 要 进 行 一 定 数 量 的 讨 论 , 所 有 这 些 都 使 得 在 开 始 编写 代 码 之 前 , ControlWizard 已 经 为 工 作 控 件 生 成 了 所 有 必 要 的 源 文 件 这 一 事 实不 那 么 明 朗 了 。 留 给 开 发 者 的 任 务 通 常 只 是 装 饰 一 下 现 有 的 控 件 , 其 工 作 已 不 如
创 建 ActiveX 控 件 那 样 多 了 。 从 一 开 始 , ControlWizard 创 建 就 可 以 在 一 个 包 容 器中 运 行 , 显 示 它 自 己 的 窗 口 , 当 它 的 窗 口 被 移 动 或 调 整 之 后 , 做 出 合 适 的 反 应 , 显 示 About 框 , 并 显 示 它 的 属 性 表 的 模 型 。 不 过 , 该 控 件 所 执 行 的 没 有 一 件 是 有用 的 工 作 , 因 为 它 没 有 触 发 事 件 , 导 出 自 定 义 方 法 或 包 含 属 性 。
图 9-6 由 ControlWizard 创建的默认 ActiveX 控件
创 建 如 图 9-6 所 示 的 ActiveX 控 件 不 需 要 任 何 编 程 。 如 果 你 愿 意 进 行 实 验 , 可 以像 以 前 解 释 的 那 样 通 过 运 行 MFC ControlWizard 来 创 建 一 个 空 项 目 , 将 项 目 的 配置 改 为 W in32 Release , 从 所 生 成 的 源 文 件 中 创 建 控 件 。 像 任 何 ActiveX 控 件 一样 , 结 果 可 能 只 通 过 像 第 8 章 中 介 绍 的 Test Container 实 用 程 序 这 样 的 执 行 包 容器 来 加 载 。 从 Tools 菜 单 中 启 动 Test Container, 调 用 它 的 New Control 工 具 , 为新 的 ActiveX 控 件 搜 索 列 表 。 该 控 件 与 项 目 具 有 相 同 的 名 字 , 作 为 Demo 控 件 或某 些 类 似 的 东 西 出 现 在 列 表 之 中 。将 该 控 件 插 入 Test Containe r,然 后 通 过 双 击 该控 件 的 窗 口 边 框 或 通 过 单 击 Test Container 的 Properties 按 钮 , 来 打 开 如 图 9-6 所示 的 默 认 属 性 表 。 如 果 想 显 示 控 件 的 About 框 , 请 选 中 Invoke Method 工 具 , 并单 击 Invoke 按 钮 。 完 成 实 验 之 后 , 退 出 Test Container 并 关 闭 项 目 。 注 册 表 中 的控 件 条 目 可 以 永 远 保 持 ,而 不 受 损 害 ,但 在 从 系 统 中 删 除 一 个 ActiveX 控 件 之 前 , 最 好 是 清 理 一 下 注 册 表 。 运 行 第 8 章 中 介 绍 的 Regsrv32 实 用 程 序 , 并 包 括 /u 开关 来 取 消 对 该 控 件 的 注 册 :
regsvr32/u \demo\release\demo.ocx
该 实 用 程 序 通 过 显 示 一 个 消 息 框 来 指 示 成 功 :
当 控 件 被 成 功 地 取 消 注 册 之 后 , 就 可 以 删 除 项 目 文 件 了 。
一 个 名 为 ColeControl::OnDraw 的 过 载 成 员 函 数 用 于 绘 制 如 图 9-6 所 示 的 控 件 窗口 。与由 ControlWizard 所 生 成 的 一 样 ,OnDraw 函 数 在 白 色 矩 形 内 部 显 示 一 个 椭圆 :
void CDemoCtrl::OnDraw(
CDC* pdc, const Crect& rcBounds, const CRect& rcInvalid)
{
pdc->FillRect(rcBounds, CBrush::FromHandle(HBRUSH)GetStockObject(WHITE_BRUSH)));
pdc->Ellipse(rcBounds);
}
当 你 开 发 使 用 Control Wizard 创 建 的 ActiveX 控 件 的 时 候 , 这 个 函 数 是 你 开 始 的首 选 位 置 之 一 。 我 们 使 用 一 个 范 例 函 数 , 演 示 了 如 何 重 写 O rDraw 函 数 , 来 显 示
一 个 含 义 更 加 丰 富 的 控 件 窗 口 。
例 2 : Tower ActiveX 控 件
本 节 将 对 前 一 节 的 内 容 进 行 扩 展 , 展 示 了 一 个 简 单 的 项 目 , 用 来 说 明 如 何 从ControlWizard 开 始 ,来 开 发 一 个 有 用 的 ActiveX 控 件 。我 已 经 将 其 命 名 为 Towe r, 因 为 它 是 汉 诺 塔 ( Tower of Hanoi ) 难 题 的 变 种 , 这 个 难 题 由 十 九 世 纪 法 国 数 学家 Edouard Lucas 提 出 。 图 9-7 显 示 了 Tower 控 件 出 现 在 对 话 中 的 情 形 , 它 显 示为 分 为 三 层 的 矩 形 窗 口 。游 戏 的 目 的 是 一 个 接 一 个 地 从 第 一 个 窗 格 中 拖 动 几 个 新块 , 并 将 其 重 新 安 装 在 第 三 个 窗 格 之 中 。你 可 以 从 任 何 一 个 窗 格 向 另 一 个 窗 格 移动 单 个 新 块 , 但 不 能 把 大 块 放 在 小 块 之 上 。
尽 管 仅 仅 是 个 游 戏 , 但 是 Tower 控 件 显 示 了 一 个 典 型 ActiveX 控 件 的 所 有 标 志 。Tower 包 含 库 存 和 自 定 义 属 性 , 可 以 导 出 方 法 , 以 及 触 发 事 件 , 来 让 包 容 器 应 用程 序 知 道 游 戏 的 当 前 状 态 。 在 本 章 稍 后 , 我 们 将 使 用 Text Container 实 用 程 序 来在 控 件 发 生 时 监 视 它 们 的 状 态 。
图 9-7 嵌 入 典 型 的 对 话 中 的 Tower ActiveX 控件
如 果 你 愿 意 自 己 创 建 Tower ActiveX 控 件 ,可 按 如 下 步 骤 进 行 ,其 中 解 释 了 操 作 。不 过 , 讨 论 不 是 专 门 针 对 Tower 项 目 的 , 而 且 同 时 也 搜 索 了 你 在 建 立 自 己 的ActiveX 控 件 项 目 时 可 能 想 考 虑 的 一 些 替 代 方 法 。 第 一 步 是 运 行 ControlWizard 创 建 项 目 , 后 四 步 是 使 用 ClassWizard 向 Tower 项 目 添 加 属 性 、 方 法 、 事 件 和 消息 处 理 程 序 函 数 。 第 六 步 是 为 控 件 创 建 一 个 简 单 的 属 性 页 。 在 ClassWizard 的 存
根 代 码 就 位 之 后 ,第 七 步 是 展 示 如 何 为 了 运 行 第 八 步 中 创 建 和 测 试 的 控 件 而 用 其他 代 码 来 充 实 程 序 。 图 9-7 中 显 示 的 Game 程 序 是 一 个 简 单 的 包 容 器 , 编 写 它 的目 的 只 是 为 了 演 示 Tower。它 是 在 AppWizard 的 帮 助 下 创 建 的 基 于 对 话 的 应 用 程序 , 与 前 一 章 中 介 绍 的 Hour 程 序 有 些 类 似 。 因 为 我 们 已 经 研 究 过 了 这 种 类 型 的程 序 , 所 以 , 本 节 仅 仅 简 单 提 及 一 下 Game 。 你 将 在 本 书 配 套 光 盘 的Code\Chapter.oq 文 件 夹 中 找 到 Tower 和 Game 包 容 器 程 序 的 源 代 码 。
步 骤 1 : 创 建 Towe r项 目
Tower 项 目 是 通 过 ActiveX ControlWizard 诞 生 的 。 通 过 单 击 File 菜 单 上 的 New 命 令 激 活 ControlWizard ,然 后 单 击 Projects 选 项 卡 ,并 选 择 如 图 9-1 所 示 的 MFC ActiveX ControlWizard 图 标 。 键 入 Tower 作 为 项 目 名 , 并 接 受 ControlWizard 第二 步 中 的 默 认 设 置 。
步 骤 2 : 添 加 属 性
Tower 控 件 有 五 个 属 性 , 名 字 分 别 为 Caption , Font, ForeColor, BackColor 和CurnentBlock 。 头 四 个 用 于 确 定 在 Tower 窗 口 的 顶 部 显 示 的 标 题 文 字 和 外 观 的 库存 属 性 。 在 启 动 的 时 候 , 控 件 将 标 题 文 字 初 始 化 为 “ Tower ”。 但 是 , 如 果 需 要 的话 , 包 容 器 可 以 在 Caption 属 性 中 指 定 新 文 字 。 CurrentBlock 是 包 含 有 代 表 被 拖动 块 的 整 数 值 的 Cursor 属 性 。 CurrentBlock 中 整 数 值 的 范 围 是 从 代 表 最 小 块 的 0
到 代 表 最 大 块 的 6 。因 为 包 容 器 没 有 理 由 改 变 这 个 值 ,所 以 Tower 将 Currentblack
作 为 只 读 属 性 来 保 持 。
在 像 Tower 这 样 的 MFC ActiveX 控 件 中 指 定 属 性 , 需 要 各 种 宏 的 精 确 放 置 和 取名 , 因 此 , 这 项 工 作 最 好 留 给 ClassWizard 去 做 。 在 ClassWizard 的 Automation 选 项 卡 中 ,单 击 Add Propert y( 添 加 属 性 )按 钮 来 打 开 Add Property 对 话 ( 图 9-8), 然 后 单 击 External Name( 外 部 名 称 ) 框 中 的 箭 头 来 显 示 一 个 库 存 属 性 列 表 。 通过 从 列 表 中 选 中 它 , 来 向 项 目 添 加 库 存 属 性 。 如 果 想 指 定 Custom 属 性 , 请 键 入库 存 名 字 列 表 之 中 没 有 的 任 何 外 部 名 字 。
图 9-8 从 Automation 选 项 卡 中 调 用 的 ClassWizard 的 Add Property 对话
利 用 Implementation ( 实 现 ) 组 框 中 的 Stock 单 选 按 钮 , 可 以 明 确 地 指 定 一 个 属
性 是 库 存 的 , 还 是 自 定 义 的 。 但 是 , 当 你 选 中 库 存 属 性 的 时 候 , 该 按 钮 是 默 认 设置 的 。 标 签 为 Member Variable 和 Get/Set Methods 的 单 选 按 钮 仅 用 于 自 定 义 属性 , 为 控 件 将 自 定 义 属 性 展 现 给 包 容 器 应 用 程 序 提 供 了 两 个 选 择 。 如 果 你 想 使 客户 到 该 属 性 的 访 问 不 受 限 制 , 请 在 Member Variable 单 选 按 钮 处 进 行 设 置 。ClassWizard 将 为 客 户 可 以 通 过 属 性 页 改 变 的 属 性 创 建 一 个 变 量 , 并 且 还 可 以 在包 容 器 改 变 了 属 性 的 时 候 , 产 生 一 个 简 单 的 通 知 来 让 控 件 知 道 。如 果 你 的 控 件 不需 要 这 个 通 知 , 可 以 清 除 文 本 框 , 以 阻 止 函 数 的 生 成 , 这 样 可 以 减 少 开 销 量 。
为 了 对 包 容 器 如 何 ( 或 何 时 ) 可 以 读 或 写 自 定 义 属 性 进 行 最 大 限 度 的 控 制 , 请 单 击Get/Set Methods 单 选 按 钮 。 该 操 作 将 指 示 ClassWizard 为 一 对 包 容 器 可 以 为 了 读或 写 属 性 而 调 用 的 方 法 生 成 源 代 码 。 如 果 想 检 索 属 性 的 当 前 值 , 包 容 器 可 以 调 用相 应 的 获 取 方 法 ; 如 果 想 设 置 新 值 , 包 容 器 可 以 调 用 Set 方 法 。 你 可 以 通 过 省 略Set 方 法 将 属 性 设 为 只 读 , 或 通 过 省 略 Get 方 法 , 而 将 属 性 设 为 只 读 。 不 过 , 打开 Get/Set Methods 单 选 按 钮 至 少 需 要 定 义 一 种 方 法 , 来 使 得 该 属 性 对 于 包 容 器来 说 是 可 见 的 。
我 们 可 以 接 受 ClassWizard 为 Tower 的 库 存 属 性 所 做 的 默 认 设 置 。如 图 9-8 所 示 。选 中 Caption 库 存 属 性 将 自 动 打 开 Stock 单 选 按 钮 , 并 导 出 函 数 GetText 和SetText , 以 允 许 包 容 器 读 和 写 Tower 的 当 前 标 题 。 这 是 MFC 帮 助 进 行 ActiveX 控 件 轻 松 编 程 的 几 种 方 法 之 一 : GetText 和 SetText 方 法 是 框 架 的 COleControl 对象 的 成 员 , 因 此 , 我 们 只 需 要 单 击 它 , 随 后 , 便 可 以 忽 略 它 们 。 框 架 承 担 了 维 护Caption 属 性 , 并 通 过 GetText 和 SetText 函 数 导 出 到 容 器 的 所 有 工 作 。 这 两 个 函
数 从 Text 库 存 属 性 中 获 取 它 们 的 名 字 , 在 该 属 性 中 , Caption 仅 仅 是 一 个 别 名 )。单 击 O K 按 钮 将 Caption 属 性 添 加 到 Tower 控 件 , 然 后 , 重 复 相 同 的 步 骤 来 添 加Fon t, ForeFont 和 BackColor 库 存 属 性 。
如 果 仅 仅 添 加 Tower 的 自 定 义 属 性 , 请 第 五 次 进 入 Add Property 对 话 框 , 并 且 键入 CurrentBlock 作 为 外 部 名 字 , 为 它 分 配 short 类 型 。 不 是 接 受 建 议 的 通 知 函 数OnCurrentBlockChanged , 而 是 单 击 Get/Set Methods 单 选 按 钮 。 ClassWizard 将 自动 分 配 名 为 GetCurrentBlock 和 SetCurrentBlock 的 函 数 。 但 是 , 既 然CurrentBlockCustom 属 性 对 于 包 容 器 来 说 应 呈 现 为 只 读 ,因 此 应 清 除 Set Function 框 , 以 便 不 再 将 SetCurrentBlock 添 加 到 控 件 类 。 嵌 入 了 Tower 的 包 容 器 应 用 程序 现 在 已 没 有 办 法 改 变 CurrentBlock , 尽 管 它 可 以 在 任 何 时 候 调 用GetCurrentBlock , 查 询 控 件 , 找 出 属 性 的 当 前 值 。
单 击 Add Property 对 话 中 的 O K 按 钮 , 将 导 致 ClassWizard 在 TowerCtl.cpp 文 件中 为 GetCurrentBlock 函 数 编 写 存 根 代 码 。 图 9-9 显 示 了 在 指 定 了 五 个 属 性 之 后的 ClassWizard 对 话 的 样 子 。 靠 近 外 部 名 字 的 “ S ”和 “ C ” 代 码 用 来 指 示 该 属 性是 库 存 的 , 还 是 自 定 义 的 。
图 9-9 向 Tower ActiveX 控件添加属性
步 骤 3 : 添 加 方 法
从 技 术 上 讲 , 我 们 已 经 向 Tower 项 目 添 加 了 一 种 方 法 。 在 前 一 节 中 生 成 的GetCurrentBlock 函 数 就 是 一 种 方 法 。也 就 是 说 ,它 是 包 容 器 可 以 通 过 接 口 调 用 的导 出 函 数 。 在 这 里 , 我 们 将 添 加 另 一 个 名 为 Reset 的 方 法 。 Reset 函 数 为 包 容 器指 示 控 件 去 重 启 动 游 戏 提 供 了 一 种 方 法 。 图 9-7 中 的 Game 程 序 演 示 了 应 用 程 序是 如 何 使 用 该 特 征 的 , 当 用 户 单 击 对 话 的 Reset 按 钮 时 , 将 调 用 Tower 的 Reset 方 法 。
还 是 在 ClassWizard 的 Automation 选 项 卡 中 ,单 击 Add Method 按 钮 ,并 键 入 Reset 作 为 外 部 名 字 。 选 择 Void 为 返 回 类 型 , 因 为 Reset 不 返 回 值 。Reset 也 没 有 参 数 , 但 如 果 有 的 话 , 将 通 过 双 击 的 标 签 为 Parameter List( 参 数 列 表 ) 的 框 的 Name 列 , 并 键 入 函 数 参 数 来 指 定 ,每 行 一 个 参 数 。在 下 节 中 ,当 我 们 添 加 Tower 的 事 件 时 , 还 要 做 类 似 的 工 作 。
单 击 O K 返 回 ClassWizard 对 话 , 该 对 话 现 在 在 外 部 名 字 列 表 中 列 出 了 Reset 方法 。“ M ” 前 缀 将 Reset 作 为 一 种 方 法 来 进 行 标 识 。
步 骤 4 : 添 加 事 件
下 面 介 绍 ClassWizard 对 话 的 ActiveX Events ( ActiveX 事 件 ) 选 项 卡 , 并 单 击
Add Event 按 钮 , 来 打 开 如 图 9-10 所 示 的 Add Event 对 话 框 。 我 们 还 要 向 Tower 控 件 添 加 一 个 库 存 事 件 和 四 个 Custom 事 件 , 它 们 共 同 使 包 容 器 应 用 程 序 能 够 了解 Tower 控 件 中 所 发 生 的 事 情 。库 存 事 件 是 Click ,可 以 从 Add Event 对 话 External Name 框 中 的 下 拉 式 列 表 中 选 中 它 。Click 事 件 用 于 通 知 包 容 器 Tower 窗 口 什 么 时候 以 及 在 哪 里 发 生 了 鼠 标 单 击 。单 击 O K 按 钮 ,然 后 第 二 次 打 开 Add Event 对 话 , 并 为 该 控 件 的 第 一 个 Custom 事 件 键 入 外 部 名 字 FromPanel。 无 论 何 时 用 户 选 中面 板 中 的 一 块 , Tower 控 件 都 将 通 过 调 用 FireFromPanel 函 数 来 触 发 FromPanel 事 件 。 FireFromPanel 调 用 客 户 中 的 事 件 处 理 程 序 函 数 , 向 它 传 递 一 个 从 0 到 2 的 单 一 参 数 ,这 一 参 数 是 用 来 标 识 从 中 拖 出 块 的 面 板 。通 过 双 击 标 签 为 Parameter List 的 列 表 框 中 的 蓝 色 区 域 展 现 新 条 目 框 来 指 定 函 数 的 参 数 。 在 新 条 目 框 中 , 键入 代 表 参 数 名 的 nPanel, 并 接 受 默 认 的 类 型 short, 如 图 9-10 所 示 。
下 一 个 Custom 事 件 叫 做 ToPanel , 它 与 FromPanel 类 似 , 有 一 个 类 型 为 short 的单 一 参 数 nPane l。 当 用 户 放 下 块 的 时 候 , Tower 触发 ToPanel 事 件 , 通 过 nPanel 参 数 表 明 三 个 面 板 中 哪 一 个 接 受 了 放 置 操 作 。 第 三 个 自 定 义 事 件 叫 做 Error, 当用 户 企 图 在 小 块 上 面 放 置 大 块 的 时 候 , Tower 将 触 发 它 , 来 通 知 包 容 器 这 是 无 效的 移 动 。 Error 没 有 参 数 表 , 因 此 , 只 需 在 External Name 框 中 键 入 它 , 并 单 击O K , 返 回 到 主 ClassWizard 对 话 即 可 。
图 9-10 为 Tower ActiveX 控 件 指 定 自 定 义 事 件 及 其 参 数
Error 是 包 括 在 外 部 名 字 下 拉 式 列 表 之 中 的 库 存 事 件 的 名 字 。在 External Name 框中 键 入 它 , 而 不 是 从 列 中 选 中 它 , 表 明 classWizard 应 该 把 这 个 事 件 视 为 自 定 义的 。 这 就 演 示 了 如 何 可 以 使 用 库 存 事 件 名 以 及 它 的 调 度 标 识 符 用 于 自 定 义 事 件 。因 为 Error 库 存 事 件 至 少 需 要 七 个 参 数 , 因 此 , 通 过 使 用 自 定 义 事 件 , 而 不 是 库存 事 件 , 可 以 简 化 用 于 控 件 及 其 包 容 器 的 代 码 。
第 四 个 也 就 是 最 后 一 个 自 定 义 事 件 是 W inner, 它 用 来 通 知 包 容 器 , 用 户 已 成 功地 将 最 后 一 块 移 入 了 控 件 的 第 三 个 面 板 , 从 而 赢 得 了 这 场 游 戏 。 与 自 定 义 Error 事 件 类 似 , W inner 没 有 参 数 表 。
图 9-11 显 示 了 在 添 加 了 Tower 的 五 个 事 件 之 后 对 话 的 ActiveX Events 选 项 卡 的样 子 。
正 如 在 前 一 章 中 所 提 及 的 ,包 容 器 不 需 要 为 ActiveX 控 件 触 发 的 所 有 事 件 提 供 处理 程 序 函 数 。 例 如 , Game 包 容 器 程 序 将 忽 略 Tower 的 Click 库 存 事 件 , 而 仅 仅处 理 自 定 义 事 件 ,来 在 事 件 发 生 的 时 候 更 新 状 态 窗 口 。控 件 设 计 者 必 须 预 料 到 包容 器 可 能 需 要 的 信 息 类 型 , 并 且 提 供 事 件 来 传 达 该 信 息 , 同 时 允 许 同 一 个 包 容 器忽 略 某 些 事 件 。
图 9-11 向 Tower ActiveX 控件添加事件
步 骤 5 : 添 加 消 息 处 理 程 序 函 数
在 Tower 中 , 用 户 使 用 拖 放 方 式 在 面 板 中 移 动 块 。 当 用 户 按 下 左 鼠 标 按 钮 的 时候 , 箭 头 将 变 为 十 字 形 状 , 从 而 提 供 反 馈 , 来 指 示 拖 动 操 作 起 作 用 了 。 当 鼠 标 按钮 被 释 放 的 时 候 ,系 统 将 把 鼠 标 恢 复 为 原 来 的 箭 头 形 状 。 在 本 练 习 的 步 骤 6 中 编写 代 码 的 时 候 , 我 们 将 详 细 说 明 处 理 的 过 程 , 但 目 前 , 我 们 仅 仅 需 要 使 用ClassWizard 来 为 鼠 标 消 息 创 建 存 根 处 理 程 序 函 数 。
在 ClassWizard 的 M essage Map 选 项 卡 中 , 从 Messages 框 中 选 中W M _ L B U T T O N D O W N 消 息 , 并 单 击 Add Function 按 钮 来 创 建 On_ButtonDown 处 理 程 序 函 数 。 为 W M _LBUTTONUP 消 息 做 相 同 的 操 作 , 接 受 默 认 的 函 数 名OnButtonUp 。 对 于 第 三 个 消 息 处 理 程 序 函 数 , 从 M essages 框 选 中PreCreateWindow , 并 单 击 Add Function 按 钮 。 这 将 生 成 CWnd 虚 拟 函 数 的 超 越存 根 , 为 Tower 最 后 几 分 钟 的 初 始 化 提 供 一 个 方 便 的 位 置 。
图 9-12 显 示 了 在 添 加 需 要 的 函 数 之 后 ClassWizard 的 M essage Maps 选 项 卡 的 外观 (Tower Ctrl 类 的 其 他 成 员 函 数 , 例 如 OnDraw 和 OnResetState , 已 在 前 面 由ControlWizard 生 成 )。 完 成 之 后 , 单 击 O K 按 钮 退 出 ClassWizard 。 我 们 还 必 须 编写 代 码 来 填 充 ControlWizard 和 ClassWizard 已 经 添 加 到 项 目 中 的 所 有 存 根 代 码 , 但 是 , 还 有 一 个 更 艰 巨 的 任 务 , 需 要 Visual C++ 对 话 编 辑 器 的 服 务 来 完 成 。
图 9-12 向 Tower ActiveX 控 件 添 加 消 息 处 理 程 序 函 数
步 骤 6 : 创 建 属 性 表
我 们 以 前 曾 经 看 到 , ControlWizard 向 项 目 添 加 一 个 普 通 的 属 性 页 资 源 ( 见 图 9- 6)。 本 节 将 解 释 如 何 修 正 该 资 源 , 它 最 终 将 扩 展 进 入 一 个 可 以 使 用 的 属 性 表 , 该属 性 表 允 许 用 户 在 设 计 时 查 看 和 改 变 Tower 的 属 性 。第 一 步 是 在 对 话 编 辑 器 中 修改 普 通 的 属 性 页 。 通 过 双 击 Workspace 窗 口 ResourceView 窗 格 中 的IDD_PROPPAGE_TOWER 标 识 符 来 加 载 资 源 。 选 中 对 话 工 作 区 中 的 “ to do ” 静态 文 字 控 件 , 并 删 除 它 , 用 如 下 所 示 的 静 态 标 签 和 编 辑 框 来 代 替 它 。
选 中 对 话 工 作 区 中 的 编 辑 框 , 单 击 View 菜 单 上 的 Properties, 给 该 框 一 个 标 识 符
值 IDC_EDIT_CAPTION 。 属 性 页 本 身 的 大 小 无 关 紧 要 。 我 们 一 会 儿 将 要 看 到 , MFC 为 属 性 页 提 供 颜 色 和 字 体 库 存 属 性 , 它 们 决 定 了 完 成 之 后 的 属 性 表 对 话 的 大小 。
在 编 辑 框 中 输 入 的 文 字 成 为 Caption 属 性 的 新 值 , 它 被 存 储 在 一 个 字 符 串 变 量 之中 。 通 过 最 后 一 次 进 入 ClassWizard , 并 单 击 ClassWizard 的 M ember Variables 选项 卡 , 来 创 建 该 字 符 串 变 量 。 在 Class Name 框 中 , 确 定 CTowerPropPage 是 当 前类 ,并 且 在 Control IDs 框 中 已 经 选 中 了 IDC_EDIT_CAPTION 。单击 Add Variable 按 钮 , 并 将 新 成 员 变 量 命 名 为 strCaption 。 类 别 应 该 是 Value, 变 量 类 型 应 该 是“ CString ”。单 击 O K 按 钮 退 出 Add Member Variable 对 话 。最 好 是 通 过 对 话 数 据验 证 ( 请 参 阅 第 6 章 ), 来 限 制 基 于 文 本 的 属 性 ( 例 如 strCaption) 的 长 度 。 通 过 在M ember Variable 选 项 卡 的 顶 部 文 本 框 中 键 入 一 个 值 , 可 以 设 置 字 符 串 限 制 :
单 击 O K 按 钮 退 出 ClassWizard 。 用 户 现 在 已 经 可 以 在 设 计 时 通 过 调 用 属 性 页 并键 入 新 字 符 串 来 改 变 Tower 的 标 题 了 。在 下 一 节 中 ,我 们 将 看 到 这 些 工 作 是 如 何完 成 的 。
步 骤 7 : 添 加 源 代 码
从 ControlWizard 和 ClassWizard 之 间 , 已 经 生 成 了 500 多 行 源 代 码 。 但 是 , 如 果我 们 现 在 就 创 建 Tower 控 件 的 话 ,它 看 上 去 还 是 有 点 像 前 面 所 介 绍 的 什 么 都 没 做的 Demo 控 件 。该 练 习 的 第 七 步 将 向 TowerCtl.cpp 和 TowerCtl.h 文 件 添 加 源 代 码 , 充 实 在 前 面 步 骤 中 添 加 的 存 根 函 数 。本 节 最 后 将 对 TowerPpg.cpp 文 件 和 Tower.rc 脚 本 文 件 进 行 微 小 的 修 改 。
TowerCtl.h
TowerCtl.h 头 文 件 只 需 稍 微 改 动 一 下 。 在 文 本 编 辑 器 中 加 载 该 文 件 , 并 像 下 面 那样 对 其 进 行 修 正 , 添 加 阴 影 行 。
// TowerCtl.h : Declaration of the CTowerCtrl ActiveX Control class.
/////////////////////////////////////////////////////////////////////////////
// CTowerCtrl : See TowerCtl.cpp for implementation. class CTowerCtrl : public COleControl
{
DECLARE_DYNCREATE(CTowerCtrl)
private: |
||
---|---|---|
short |
nPanel[3][NUM_BLOCKS]; |
// Panel contents |
short |
nBlockNdx, nFromPanel; |
// nPanel index of moved block |
BOOL |
bMoving; |
// Flag is set when dragging |
COLORREF |
color[NUM_BLOCKS]; |
// Block colors |
// Constructor public:
CTowerCtrl();
.
.
.
以 后 , 当 我 们 讨 论 实 现 代 码 的 时 候 , 将 遇 到 CTowerCtrl 类 的 六 个 成 员 变 量 。 表
- 提 供 了 这 些 变 量 的 简 要 说 明 。
表 9-1 CTowerCtrl 类 的 成 员 变 量
变 量 说 明
nPanel 一 个 3 × 7 的 数 值 ,其 作 用 是 反 映 任 何 时 刻 三 个 面 板 的内 容 。 数 组 元 素 值 0 到 6 意 味 着 该 位 置 被 一 个 彩 色 块所 占 据 , 0 代 表 最 小 的 块 , 6 代 表 最 大 的 块 。 元 素 值 为
7 意 味 着 该 位 置 是 空 闲 的 。 例 如 , 当 块 整 齐 地 堆 在 第一 个 面 板 的 时 候 , nPanel 数 组 应 该 是 这 样 的
nPanel[0][]={0,1,2,3,4,5,6,}; //Panel 1
nPanel[1][]={7,7,7,7,7,7,7,}; //Panel 2
nPanel[]2[]={7,7,7,7,7,7,7,}; //Panel 3
nBlockNdx 正 在 拖 动 的 块 的 辅 助 数 组 索 引
nFormPanel 正 在 拖 动 的 块 的 主 数 组 索 引
bMoving 当 用 户 拖 动 块 时 被 设 置 为 TRUE 的 Boolean 值
续 表
Color 包 含 有 块 的 颜 色 的 COLORREF 值 的 数 组 。 颜 色 是 按照 块 尺 寸 递 增 的 顺 序 安 排 的 , 最 小 块 的 颜 色 放 在 数 组的 第 一 个 值 中
hCrossHairs 系 统 十 字 箭 头 的 一 个 句 柄 。 当 用 户 拖 块 一 个 块 时 , 该光 标 应 为 十 字 形 状
TowerCtl.cpp
下 一 个 是 TowerCtl.cpp 类 实 现 文 件 。通 过 单 击 W izardBar 上 的 棍 棒 图 标 打 开 这 个文 件 , 并 滚 动 到 在 这 里 显 示 的 属 性 页 映 射 。 按 照 阴 影 行 中 指 示 的 那 样 做 出 修 改 , 为 颜 色 和 字 体 属 性 添 加 由 MFC 框 架 提 供 的 属 性 页 :
/////////////////////////////////////////////////////////////////////////////
// Property pages
BEGIN_PROPPAGEIDS(CTowerCtrl, 3)
PROPPAGEID(CTowerPropPage::guid)
END_PROPPAGEIDS(CTowerCtrl)
在 映 射 第 一 行 的 BEGIN_PROPPAGEIDS 宏 中 , 将 页 数 设 置 为 3 , 这 是 十 分 重 要的 。 如 果 你 以 后 在 属 性 表 中 添 加 或 删 除 了 页 , 应 该 对 页 数 进 行 调 整 , 以 反 映 出 改变 。 通 过 使 用 对 话 编 辑 器 创 建 其 他 的 资 源 , 并 且 将 每 个 页 的 新 条 目 插 入 属 性 页 映射 , 可 以 向 控 件 的 属 性 表 添 加 更 多 的 自 定 义 页 面 。 这 个 过 程 很 少 会 用 到 , 因 此 , 直 到 本 章 最 后 一 节 , 才 讨 论 附 加 属 性 页 的 问 题 。
图 9-13 显 示 了 完 成 之 后 的 Tower 属 性 表 的 样 子 。 该 表 的 顺 序 是 前 三 页 ( 标 签 为Caption;Colors 和 Fonts) 对 应 于 属 性 页 映 射 中 三 个 条 目 的 顺 序 。 剩 下 的 修 改 影 响到 后 半 个 TowerCtl.cpp 实 现 文 件 , 这 里 列 出 的 文 件 以 类 构 造 器 开 头 。该 清 单 被 分为 几 部 分 , 每 部 分 之 后 跟 着 一 段 注 释 , 用 来 解 释 阴 影 行 中 所 添 加 的 代 码 的 目 的 。
/////////////////////////////////////////////////////////////////////////////
// CTowerCtrl::CTowerCtrl - Constructor
CTowerCtrl::CTowerCtrl()
{
InitializeIIDs(&IID_DTower, &IID_DTowerEvents);
}
/////////////////////////////////////////////////////////////////////////////
// CTowerCtrl::~CTowerCtrl - Destructor
CTowerCtrl::~CTowerCtrl()
{
}
类 构 造 器 用 块 颜 色 初 始 化 color 数 组 , 并 调 用 Reset 方 法 来 初 始 化 nPanel 数 组 。对 COleControl::SetInitialSize 函 数 的 调 用 为 Tower 控 件 分 配 了 一 个 200 × 75 像 素的 默 认 窗 口 尺 寸 。 在 创 建 站 点 时 , 许 多 包 容 器 程 序 超 越 控 件 的 初 始 尺 寸 , 因 此 , 对 ActiveX 控 件 来 说 , 调 用 SetInitialSize 通 常 是 没 有 用 的 。 当 你 在 Test Container 实 用 程 序 中 打 开 控 件 的 时 候 ,该 函 数 的 目 的 就 很 明 显 了 , 它 接 受 控 件 为 自 己 所 建立 的 任 何 一 个 初 始 尺 寸 。 正 如 我 们 在 第 8 章 中 所 看 到 的 那 样 , 有 些 ActiveX 控 件
( 例 如 Button Menu 控件 ) 在 Test Container 中 是 作 为 小 方 框 出 现 的 , 为 了 展 现 控 件窗 口 , 用 户 必 须 对 其 进 行 调 整 。 对 Tower 并 不 发 生 这 种 情 形 , 因 为 它 通 过SetInitialSize 为 自 己 设 置 默 认 窗 口 尺 寸 。
// CTowerCtrl::OnDraw - Drawing function void CTowerCtrl::OnDraw(
CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid)
{
COLORREF colorBack = TranslateColor( GetBackColor() );
int i, j, k, yCaption, iPanelWidth, iPanelHeight;
// Paint control background
brush.CreateSolidBrush( colorBack );
pdc->FillRect( rcBounds, &brush );
pdc->SetBkMode( TRANSPARENT );
pdc->SetTextColor( TranslateColor( GetForeColor() ) );
SelectStockFont( pdc );
// Display caption
::CopyRect( &rect, rcBounds );
pdc->DrawText( InternalGetText(), -1, &rect, DT_CENTER | DT_TOP );
pdc->GetTextMetrics( &tm ); // Compute height
yCaption = tm.tmHeight + tm.tmExternalLeading; // of Caption area,
iPanelWidth = rcBounds.Width()/3; // width and height
iPanelHeight = rcBounds.Height() - yCaption; // of a panel
// Draw column dividers
pen.CreatePen( PS_SOLID, 1, TranslateColor( GetForeColor() ) );
pOldPen = pdc->SelectObject( &pen );
pdc->MoveTo( rcBounds.left+iPanelWidth, rcBounds.top+yCaption );
pdc->LineTo( rcBounds.left+iPanelWidth, rcBounds.bottom );
pdc->MoveTo( rcBounds.left+iPanelWidth*2, rcBounds.top+yCaption );
pdc->LineTo( rcBounds.left+iPanelWidth*2, rcBounds.bottom );
// Save current brush
pOldBrush = (CBrush*) pdc->SelectStockObject( NULL_BRUSH );
// Outer loop: for each panel...
for (i=0; i < 3; i++)
{
rect.top = rcBounds.top + yCaption;
rect.bottom = rect.top + iPanelHeight/NUM_BLOCKS;
// Inner loop: for each colored block in panel...
for (j=0; j < NUM_BLOCKS; j++)
{
if (nPanel[i][j] != EMPTY)
{
// Determine left and right edges of colored block
k = NUM_BLOCKS - 1 - nPanel[i][j];
rect.left = rcBounds.left + iPanelWidth*i +
(iPanelWidth*k)/(2*NUM_BLOCKS) + 1;
rect.right = rect.left +
iPanelWidth*(nPanel[i][j]+1)/NUM_BLOCKS - 1;
// Fill rectangle with block's color
brush.CreateSolidBrush( color[nPanel[i][j]] );
pdc->SelectObject( &brush );
pdc->FillRect( &rect, &brush );
}
rect.top = rect.bottom;
rect.bottom += iPanelHeight/NUM_BLOCKS;
}
}
}
下 一 步 , 需 在 该 类 的 OnDraw 函 数 中 修 改 源 代 码 , 无 论 何 时 , Tower 的 窗 口 失 效时 都 将 执 行 这 个 函 数 。 我 们 以 前 曾 经 看 到 ControlWizard 编 写 OnDraw 的 简 单 版本 。在 白 色 矩 形 中 显 示 普 通 的 椭 圆 。在 这 里 ,我 们 来 修 改 一 下 这 个 函 数 ,在 Tower 的 三 个 面 板 中 绘 制 彩 块 的 当 前 排 列 情 况 。无 论 何 时 用 户 移 动 了 一 个 块 ,窗 口 都 将重 画 自 己 , 以 反 映 出 改 变 。
OnDraw 首 先 用 控 件 的 BackColor 属 性 的 当 前 值 来 画 窗 口 背 景 。 该 函 数 从ColeControl::GetBackColor 函 数 中 检 索 到 属 性 , 通 过 COleControl::TranslateColor 将 它 转 换 为 COLORREF 值 , 并 创 建 一 个 刷 子 , 用 它 来 填 充 窗 口 矩 形 (rcBounds 参 数 提 供 了 Tower 窗 口 相 对 于 包 容 器 窗 口 原 点 的 坐 标 ) 。 类 似 地 , 该 函 数 使 用ForeColor 属 性 的 当 前 值 来 设 置 设 备 上 下 文 的 文 字 颜 色 , 并 使 用 Font 属 性 设 置 当前 的 字 体 。OnPraw 然 后 编 写 包 含 在 Caption 属 性 之 中 的 字 符 串 ,将文字在 Power 窗 口 的 顶 部 居 中 显 示 :
pdc → DrawText(InternalGetText(),-1,&rect,DT_CENTER | DT_TDP);
从 Tower 窗 口 的 高 度 中 减 去 标 题 文 字 的 高 度 便 是 面 板 的 高 度 , 这 个 值 被 存 储 在
iPanelHeight 之 中 。每 个 彩 块 的 高 度 ( 厚 度 )是 面 板 高 度 的 七 分 之 一 , 因 此 , 七 个 块堆 起 来 正 好 从 面 板 底 部 堆 到 顶 部 。 每 个 面 板 的 宽 度 是 整 个 窗 口 宽 度 的 三 分 之 一 。有 了 这 些 尺 寸 之 后 , OnDraw 就 开 始 准 备 在 面 板 中 显 示 彩 块 了 。
每 个 块 的 当 前 位 置 被 存 储 在 一 个 3 × 7 的 nPanel 数 组 之 中 。 使 用 为 每 个 面 板 重 复的 外 循 环 和 为 每 个 块 重 复 的 内 循 环 , 该 函 数 可 以 遍 历 所 出 现 的 21 个 槽 中 的 每 一个 , 并 在 每 一 步 读 取 nPanel 数 组 的 一 个 元 素 。EMPTY 元 素 值 意 味 着 该 槽 没 有 包含 一 个 块 。 如 果 一 个 元 素 具 有 从 0 到 6 的 值 , OnDraw 将 使 用 Color 数 组 中 相 应的 颜 色 为 槽 中 的 块 上 色 。 一 个 名 为 rect 的 RECT 结 构 用 于 保 持 当 前 块 的 坐 标 。
/////////////////////////////////////////////////////////////////////////////
// CTowerCtrl::DoPropExchange - Persistence support
void CTowerCtrl::DoPropExchange(CPropExchange* pPX)
{
ExchangeVersion(pPX, MAKELONG(_wVerMinor, _wVerMajor)); COleControl::DoPropExchange(pPX);
}
/////////////////////////////////////////////////////////////////////////////
// CTowerCtrl::OnResetState - Reset control to default state
void CTowerCtrl::OnResetState()
{
COleControl::OnResetState(); // Resets defaults found in DoPropExchange
}
/////////////////////////////////////////////////////////////////////////////
// CTowerCtrl::AboutBox - Display an "About" box to the user
void CTowerCtrl::AboutBox()
{
CDialog dlgAbout(IDD_ABOUTBOX_TOWER); dlgAbout.DoModal();
}
尽 管 Tower 没 有 改 变 类 的 DoPropExchange 函 数 , 但 是 , 简 单 考 虑 一 下 该 函 数 还是 值 得 的 。 属 性 交 换 允 许 ActiveX 控 件 保 存 嵌 入 物 之 间 的 自 定 义 属 性 。 例 如 , 每当 它 启 动 的 时 候 , Tower 控 件 总 是 初 始 化 游 戏 , 并 在 第 一 个 面 板 中 安 装 彩 块 堆 。通 过 属 性 交 换 , 我 们 可 以 增 强 Tower, 以 便 在 关 闭 的 时 候 保 存 被 中 断 了 的 游 戏 , 并 且 在 下 次 包 容 器 嵌 入 该 控 件 的 时 候 ,重 新 创 建 同 一 个 块 位 置 。在 运 行 之 间 保 存和 恢 复 的 属 性 具 有 持 久 性 。
由 框 架 管 理 的 库 存 属 性 自 动 具 有 持 久 性 。 为 了 使 自 定 义 属 性 具 有 持 久 性 , 应 向DoPropExchange 函 数 添 加 合 适 的 属 性 交 换 函 数 , 当 该 控 件 被 加 载 以 及 当 它 再 次中 止 时 ,该 函 数 便 可 执 行 。属 性 交 换 函 数 是 用 PX_ 前 缀 后 面 跟 着 由 函 数 系 列 化 的数 据 类 型 来 进 行 标 识 的 。 例 如 , PX_Bool, PX_Font 和 PX_String 函 数 使 得Boolean 、字体和 CString 属 性 具 有 持 久 性 。 如 果 想 获 得 这 些 及 其 他 属 性 交 换 函 数的 说 明 , 请 参 考 在 线 帮 助 。
/////////////////////////////////////////////////////////////////////////////
// CTowerCtrl message handlers
short CTowerCtrl::GetCurrentBlock()
{
retur n nPanel[nFromPanel][nBlockNdx] ;
}
GetCurrentBlock 函 数 是 在 第 二 步 中 建 立 的 方 法 ,在 此 ,包 容 器 想 了 解 哪 一 个 块 正在 移 动 ( 尽管 ClassWizard 向 代 码 中 添 加 标 题 , 但 GetCurrentBlock 不 是 消 息 处 理函 数 )。 可 以 在 任 何 时 候 调 用 这 个 函 数 , 但 是 , 如 果 包 容 器 对 GetCurrentBlock 所提 供 的 信 息 感 兴 趣 的 话 , 它 可 能 将 调 用 该 函 数 来 响 应 FromPanel 事 件 , 这 个 事 件宣 布 一 个 块 正 在 被 移 动 。GetCurrentBlock 的 EMPTY 的 返 回 值 意 味 着 用 户 当 前 没有 拖 动 块 。可 以 回 想 一 下 第 二 步 过 程 ,因 为 包 容 器 没 有 理 由 改 变 属 性 ,所 以 ,Tower 不 为 CurrentBlock 属 性 导 进 相 应 的 Set 方 法 。
void CTowerCtrl::Reset()
{
int i;
for (i=0; i < NUM_BLOCKS; i++) // Initialize panel array
{ |
nPanel[0][i] = |
i; |
// Panel 0 | = |
0,1,2,3,4,5,6 |
---|---|---|---|---|---|
nPanel[1][i] = |
EMPTY; |
// Panel 1 | = |
7,7,7,7,7,7,7 |
|
} |
nPanel[2][i] = |
EMPTY; |
// Panel 2 | = |
7,7,7,7,7,7,7 |
nBlockNdx = 0; // Ndx of block being moved
nFromPanel = 0;
InvalidateControl();
}
利 用 Reset 方 法 ,包 容 器 应 用 程 序 可 以 重 新 开 始 游 戏 。例 如 ,当 用 户 单 击 如 图 9-7 所 示 的 Reset 按 钮 的 时 候 , Game 程 序 将 调 用 Reset 方 法 。 TowerCtrl 类 构 造 器 还调 用 Reset, 以 在 启 动 时 初 始 化 nPanel 数 组 , 在 第 一 个 面 板 中 堆 起 七 个 块 , 并 将其 他 两 个 面 板 中 的 所 有 位 置 标 记 为 空 。 Reset 调 用 COleControl::InvalidateControl 函 数 来 触 发 对 OnDraw 的 一 次 调 用 , 刷 新 控 件 窗 口 。 为 了 使 自 己 失 效 , 基 于COleControl 的 ActiveX 控 件 应 调 用 InValidateControl 函 数 , 而 不 是 InValidate API 函 数 。
BOOL CTowerCtrl::PreCreateWindow(CREATESTRUCT& cs)
{
return COleControl::PreCreateWindow(cs);
}
Tower 通 过 监 视 左 鼠 标 按 钮 来 模 拟 拖 和 放 操 作 。当 用 户 在 Tower 窗 口 之 中 按 下 鼠
标 按 钮 的 时 候 , 控 件 将 把 箭 头 改 为 系 统 十 字 形 状 ,以 为 用 户 提 供 简 单 而 形 象 的 反馈 , 表 明 拖 操 作 起 作 用 了 。 当 包 容 器 第 一 次 嵌 入 Tower 控 件 时 调 用 的PreCreateWindow 函 数 将 加 载 十 字 箭 头 , 并 将 该 句 柄 存 储 在 hCrosHairs 之 中 。 该函 数 还 调 用 ColeControl::SetText 来 初 始 化 Caption 属 性 。
void CTowerCtrl::OnLButtonDown(UINT nFlags, CPoint point)
{
COleControl::OnLButtonDown(nFlags, point);
}
当 用 户 在 Tower 窗 口 中 的 某 处 按 下 鼠 标 左 按 钮 的 时 候 , OnLButtonDown 函 数 将处 理 产 生 的 WM_LBUTTONDOWN 消 息 。该 函 数 首 先 检 查 point 参 数 中 的 单 击 坐标 , 然 后 确 定 单 击 发 生 在 哪 个 面 板 之 中 。 如 果 面 板 为 空 , 单 击 将 被 忽 略 。 否 则 , OnLButtonDown 将 把 箭 头 改 为 十 字 形 状 ,并 触 发 FromPanel 事 件 ,来 将 正 在 移 动块 的 消 息 通 知 包 容 器 。
因 为 仅 有 面 板 中 最 小 的 块 才 能 被 移 动 , 因 此 , OnLButtonDown 只 需 确 定 单 击 发生 在 哪 个 面 板 之 中 , 而 不 必 确 定 发 生 在 哪 个 块 上 。 尽 管 这 可 以 大 大 简 化 帮 助 器GetPanel 函 数 中 测 试 的 次 数 , 它 同 时 还 具 有 为 一 个 块 启 动 拖 动 操 作 的 作 用 , 即 使单 击 没 有 精 确 地 落 在 该 块 上 面 。
void CTowerCtrl::OnLButtonUp(UINT nFlags, CPoint point)
{
short i=0, nToPanel;
nToPanel = GetPanel( point.x ); // Panel in which block is dropped
if (bMoving && nToPanel != nFromPanel)
{
while (nPanel[nToPanel][i] == EMPTY && i < NUM_BLOCKS-1)
i++; // i=ndx of panel's smallest block
// Is dragged block smaller than smallest block in panel?
if (nPanel[nFromPanel][nBlockNdx] < nPanel[nToPanel][i])
{
if (nPanel[nToPanel][i] != EMPTY)
--i;
nPanel[nToPanel][i] = nPanel[nFromPanel][nBlockNdx];
nPanel[nFromPanel][nBlockNdx] = EMPTY;
FireToPanel( nToPanel ); // Tell container
if (i == 0 && nToPanel == 2 // If we've filled
{ // the third panel,
FireWinner (); // fire Winner event
Reset (); // and reset game
}
InvalidateControl();
}
else // If invalid drop,
COleControl::OnLButtonUp(nFlags, point);
}
当 用 户 在 面 板 中 释 放 左 鼠 标 按 钮 来 放 下 块 时 , OnButtonUp 函 数 接 收 控 制 。 该 函数 比 它 的 同 伴 OnLButton Down 有 更 多 的 工 作 要 做 。 除 了 确 定 放 置 操 作 发 生 哪 一个 面 板 中 之 外 ,OnLButtonUp 还 必 须 确 定 该 面 板 中 的 块 均 不 小 于 所 放 置 的 块 。如果 有 的 话 ,OnButtonUp 将 触 发 Error 事 件 通 知 包 容 器 ,说 用 户 企 图 进 行 非 法 放 置 。如 果 放 操 作 是 合 法 的 ,OnLButtonUp 将 触 发 ToPanel 事 件 ,来 宣 布 放 操 作 的 结 束 。如 果 块 正 在 被 拖 入 第 三 个 面 板 最 上 面 的 槽 中 ,那么游戏结束 ,OnLButtonUp 将 触发 Winner 事 件 。
释 放 鼠 标 按 钮 很 方 便 , 但 也 有 副 作 用 , 即 在 OnLButtonUp 没 有 采 取 任 何 动 作 的情 况 下 ,光 标 便 恢 复 到 以 前 的 形 状 。因 为 Tower 不 处 理 WM_SETCURSOR 消 息 , 所 以 , 当 用 户 释 放 鼠 标 按 钮 的 时 候 , 系 统 将 自 动 恢 复 原 来 的 窗 口 光 标 。
TowerPpg.cpp
在 步 骤 6 中 , 我 们 通 过 添 加 一 个 允 许 用 户 重 写 Caption 属 性 的 文 本 框 , 来 修 改Tower 的 普 通 属 性 。 Tower 在 StrCaption 变 量 中 存 储 该 文 本 框 的 内 容 , 该 变 量 是一 个 用 ClassWizard 创 建 的 CString 对 象 。 在 setCaption 字 符 串 和 Caption 库 存 属性 之 间 需 要 一 个 链 接 , 以 便 当 用 户 在 Tower 的 属 性 表 中 改 变 strCaption 的 时 候 , 控 件 可 以 把 改 变 转 发 给 埋 藏 在 框 架 之 中 的 库 存 属 性 。
可 以 通 过 DDP_ 前 缀 来 识 别 MFC 的 属 性 数 据 传 输 函 数 的 目 的 。 Tower 所 需 要 的函 数 是 DPP_Text 函 数 , 它 可 以 将 文 字 以 字 符 串 变 量 (strCaption) 中 复 制 到 字 符 属性 (Caption)。 如 果 想 添 加 DDP_Text 调 用 , 请 在 文 本 编 辑 器 中 打 开 TowerPpg.cpp 实 现 文 件 , 并 插 入 如 下 所 示 的 阴 影 行 :
void CTowerPropPage::DoDataExchange(CDataExchange* pDX)
{
//{{AFX_DATA_MAP(CTowerPropPage)
DDP_Text(pDX , IDC_EDIT_CAPTION , strCaption , _T("Caption" ) ) ; DDX_Text(pDX, IDC_EDIT_CAPTION, strCaption);
DDV_MaxChars(pDX, strCaption, 25);
//}}AFX_DATA_MAP
DDP_PostProcessing(pDX);
}
Tower.rc
在 默 认 状 态 下 , ControlWizard 将 普 通 属 性 页 标 为 General , 将 该 标 签 作 为 字 符 串资 源 存 储 在 Tower.rc 脚 本 文 件 之 中 。如 果 想 改 变 该 选 项 卡 标 签 ,请 在 文 本 编 辑 器中 打 开 Tower.rc 文 件 , 并 在 如 下 所 示 的 阴 影 行 中 将 “ Genera l” 改 为 “ Caption ”:
STRINGTABLE DISCARDABLE BEGIN
IDS_TOWER "Tower Control"
IDS_TOWER_PPG "Tower Property Page"
END
你 还 可 以 使 用 第 4 章 中 介 绍 的 Visual C++ 字 符 串 编 辑 器 实 现 相 同 的 修 改 。图 9-13
显 示 了 修 改 后 的 结 果 。
步 骤 8 : 创 建 和 测 试 Tower ActiveX 控 件
如 果 你 正 在 从 源 代 码 中 创 建 Tower 控 件 ,首 先 应 将 文 件 Tower.ico 和 TowerCtl.bmp 从 Code\chapter.o9\Tower 文 件 夹 复 制 到 项 目 文 件 夹 。第 一 个 文 件 提 供 了 在 控 件 的About 框 中 出 现 的 独 一 无 二 的 图 标 资 源 。 第 二 个 文 件 包 含 有 一 个 位 图 , 包 容 器 可以 在 嵌 入 控 件 时 从 这 个 位 图 中 创 建 一 个 个 性 化 的 工 具 按 钮 。正 如 我 们 在 前 一 章 中所 看 到 的 那 样 , Visual C++ 自 己 利 用 该 控 件 的 位 图 。 当 一 个 ActiveX 控 件 通 过Gallery 添 加 到 项 目 的 时 候 , 对 话 编 辑 器 将 从 该 控 件 的 资 源 文 件 中 展 开 位 图 , 并使 用 这 个 图 像 在 Controls 工 具 栏 上 画 新 的 工 具 按 钮 :
对 于 这 种 目 的 , 一 个 图 标 一 般 来 说 太 大 了 , 因 此 , ActiveX 标 准 建 议 , 在 控 件 的资 源 数 据 中 包 括 16 × 16 位 图 图 像 。 提 供 这 种 资 源 的 控 件 通 过 类 的 注 册 表 数 据 中的 ToolboxBitmap32 键 来 宣 告 自 己 。 在 下 一 章 中 , 我 们 还 要 遇 到 这 个 键 。
将 项 目 配 置 设 为 W in32 Release , 并 且 从 Build 菜 单 选 择 Build 命 令 。 当 源 码 成 功编 译 和 链 接 之 后 , Visual C++ 将 自 动 注 册 这 个 控 件 。 如 果 你 想 不 将 Tower 作 为 项目 创 建 便 拿 它 做 试 验 , 那 么 , 在 使 用 它 之 前 你 必 须 注 册 该 控 件 。 如 果 想 注 册Tower ,首 先 应 将 Tower.ocx 文 件 复 制 到 硬 盘( 如 果 必 要 的 话 ),然 后 运 行 RegSvr32 实 用 程 序 :
regsvr32 path\tower.ocx
其 中 , path 代 表 Tower.ocx 文 件 在 硬 盘 上 的 位 置 。
本 书 配 套 光 盘 上 的 Game 程 序 为 试 验 新 的 Tower 控 件 提 供 了 最 方 便 的 方 法 。Game 具 有 用 于 显 示 Tower 的 About 框 、复 位 游 戏 以 及 列 出 游 戏 规 则 的 按 钮 。程 序 还 可以 连 续 不 断 地 显 示 游 戏 状 态 , 这 是 通 过 监 视 Tower 的 FromPanel, ToPanel, Error 和 W inner 事 件 来 实 现 的 。 Test Container 实 用 程 序 提 供 了 另 一 种 方 法 来 用 Tower 控 件 做 试 验 , 并 且 能 够 比 Game 程 序 展 现 该 控 件 更 多 的 内 部 工 作 过 程 。 正 确 注 册了 控 件 之 后 , 运 行 Test Containe r, 单 击 New Control 按 钮 , 从 如 下 的 列 表 中 选 中Tower 控 件 :
Insert Control 对 话 响 应 键 盘 条 目 , 让 你 通 过 按 控 件 名 的 第 一 个 字 母 来 快 速 滚 动 列表 。 例 如 , 按 下 T 可 以 立 即 把 选 择 条 放 在 Tower 控 件 条 目 上 。
通 过 双 击 控 件 边 框 , 可 以 试 一 试 Tower 的 属 性 表 。 MFC 框 架 已 经 添 加 了 属 性 页 , 你 可 以 在 里 面 修 改 用 于 在 Tower 窗 口 中 显 示 标 题 的 控 件 和 字 体 。 图 9-13 显 示 了在 控 件 的 属 性 表 中 展 现 的 Font 页 。 在 对 话 的 其 他 三 个 选 项 卡 中 , Caption 选 项 卡
用 于 显 示 在 步 骤 6 中 修 改 的 属 性 页 。 当 你 为 标 题 键 入 新 文 字 的 时 候 ,变 化 立 即 在控 件 的 窗 口 中 反 映 出 来 。
图 9-13 Tower Control Properties 属 性 表
Test Container 可 以 显 示 Tower 事 件 的 实 时 记 录 ,当 你 调 试 一 个 ActiveX 控 件 的 时候 , 这 是 非 常 宝 贵 的 。 向 上 移 动 分 隔 栏 , 以 为 事 件 日 志 展 现 更 多 的 空 间 , 然 后 从Tower 的 第 一 个 面 板 中 拖 动 一 个 块 , 并 把 它 放 到 另 一 个 面 板 之 中 。 作 为 对 拖 放 操作 的 响 应 , Tower 触发 ClickFromPanel 和 ToPanel 事 件 , 当 它 们 发 生 的 时 候 , 在事 件 日 志 中 均 有 记 录 。 如 图 9-14 所 示 , 日 志 中 的 每 个 条 目 均 以 下 一 个 用 来 指 示事 件 在 哪 个 控 件 发 生 的 标 签 作 为 开 头 。 既 然 Test Container 可 以 嵌 入 多 个 控 件 , 那 么 , 利 用 标 签 , 在 事 件 很 多 时 , 日 志 条 目 仍 可 以 保 持 直 观 。
图 9-14 在 Test Container 中监视事件
向 ActiveX 控 件 项 目 添 加 属 性 页
正 如 我 们 在 开 发 Tower 控 件 时 所 看 到 的 ,ControlWizard 可 以 为 ActiveX 控 件 生 成一 个 单 一 属 性 页 ,来补充 MFC 提 供 的 库 存 页 。对 于 Tower 项 目 , 一 页 就 足 够 了 , 原 因 是 Caption 是 需 要 自 定 义 属 性 页 的 唯 一 一 个 可 修 改 的 属 性 。 不 过 , 带 有 多 个属 性 数 据 的 ActiveX 控 件 可 能 需 要 其 他 的 页 来 将 数 据 展 现 给 用 户 。每 个 附 加 页 都需 要 在 属 性 页 映 射 中 具 有 它 自 己 的 对 话 资 源 类 和 条 目 。 最 后 一 节 列 出 了 向ActiveX 控 件 项 目 添 加 新 属 性 页 的 必 要 步 骤 , 使 用 Tower 控 件 作 为 例 子 。
- 在 项 目 处 于 打 开 状 态 的 时 候 , 单 击 Insert 菜 单 上 的
Resource 命 令 , 并双 击 列 表 中 的 D ialog 来 启 动 对 话 编 辑 器 。 你 还 可 以 展 开 对 话 资 源 列表 , 并 选 中 IDD_OLE_PROPPAGE_LARG E。 和 以 前 一 样 , 新 属 性 页的 大 小 并 不 重 要 。 按 照 你 所 希 望 的 那 样 设 计 新 属 性 页 , 然 后 在 工 作 区中 选 中 对 话 窗 口 ,并 单 击 View 菜 单 上 的 Properties 命 令 ,来 展 现 D ialog Properties 框 。 在 Styles 选 项 卡 中 , 关 闭 Titlebar 复 选 框 , 将 对 话 样 式设 为 Child ,禁 用 边 框 。如 果 你 选 中 了 IDD_OLE_PROPPAGE_LARGE , 来 启 动 对 话 编 辑 器 ,这 些 设 置 就 已 经 为 你 做 好 了 。Styles 选 项 卡 应 该 是下 图 的 样 子 :
- 按 Ctrl+S 来 保 存 新 对 话 资 源 , 然 后 单击 View 菜 单 中 的
ClassWizard 命 令 。 当 询 问 你是 否 愿 意 为 对 话 资 源 创 建 一 个 新 类 的 时 候 , 单 击 O K 按 钮 表示 接 受 。 为 新 类 键 入 一 个 名 字 , 选 择 COlePropertyPage 作 为 基类 :
- 为 新 页 添 加 所 需 要 的 所 有 成 员 变 量 , 然 后 退 出
ClassWizard 。 在TowerCtl.cpp 实 现 文 件 中 , 为 ClassWizard 刚 刚 创 建 的 属 性 类 头 文 件 添加 一 条 #include 语 句 , 如 下 :
# includ e "TowerPPG2.h" .
正 确 的 文 件 名 出 现 在 New Class 对 话 的 File Name 框中,在 TowerCtl.cpp 文 件 中 , 还 要 为 新 页 的 属 性 页 映 射 添 加 一 个 条 目 。 对 于 Tower 控 件 , 添 加 部 分 应 该 是 这 个 样 子 :
BEGIN_PROPPAGEIDS(CTowerCtrl, 4)
PROPPAGEID(CTowerPropPage::guid)
PROPPAGEID(CTowerPropPage2::guid ) PROPPAGEID( CLSID_CColorPropPage )
PROPPAGEID( CLSID_CFontPropPage )
END_PROPPAGEIDS(CTowerCtrl)
记 住 ,要 在 映 射 第 一 行 中 的 BEGIN_PROPPAGEIDS 宏 中 增 加 页 数 。新页 数 是 4 。
- 在 文 本 编 辑 器 或 字 符 串 编 辑 器 中 打 开 项 目 的 RC 文 件 ,
并 添 加 两 个 字符 串 资 源 。 第 一 个 字 符 串 用 来 标 识 系 统 注 册 表 中 已 注 册 的 属 性 页 , 第二 个 字 符 串 用 来 保 持 在 属 性 表 对 话 中 出 现 的 选 项 卡 标 签 :
STRINGTABLE DISCARDABLE BEGIN
IDS_TOWER "Tower Control"
IDS_TOWER_PPG "Tower Property Page"
IDS_TOWER_PPG_CAPTION "Caption"
END
- 如 果 你 在 上 一 步 中 使 用 文 本 编 辑 器 来 创 建 新 字 符 串 资
源 , 那 么 请 向Resource.h 文 件 为 IDS_TOWER_PPG2 和IDS_TOWER_PPG_NEWPAGE 常 数 添 加 定 义 :
如 果 你 在 上 一 步 中 使 用 的 是 字 符 串 编 辑 器 , 那 么 添 加 这 些 行 就 不 是 必要的了,因为 Visual C++ 在 你 保 存 字 符 串 资 源 的 时 候 会 自 动 写 下 定 义 。
- 在 文 本 编 辑 器 中 , 打 开 ClassWizard 为 新 属 性 页 类 创 建 的
实 现 CPP 文件 。 搜 索 这 里 的 源 代 码 , 在 每 个 阴 影 行 中 , 用 字 符 串 标 识 符 代 替 0 参数 。
BOOL CTowerPropPage2::CTowerPropPageFactory
::UpdateRegistry(BOOL bRegister)
{
if (bRegister)
return AfxOleRegisterPropertyPageClass( AfxGetInstanceHandle(), m_clsid, IDS_TOWER_PPG2);
else
return AfxOleUnregisterClass(m_clsid, NULL);
}
.
.
.
CTowerPropPage2::CTowerPropPage2(): COlePropertyPage(IDD,IDS_TOWER_PPG_NEWPage)
{
.
.
.
在 做 出 这 些 改 变 之 后 , 重 建 Tower ActiveX 控 件 可 以 向 控 件 资 源 添 加 新属 性 页 。 这 里 是 新 属 性 页 的 一 个 例 子 , 这 主 要 取 决 于 你 在 本 练 习 的 第 一
步 中 的 设 计 。
第 10 章 用 ATL 编 写 ActiveX 控 件
第 9 章 演 示 了 使 用 MFC 轻 松 地 编 写 ActiveX 控 件 , 但 需 要 指 出 的 是 , 其 方 便 性的 代 价 是 , 可 执 行 部 分 过 于 庞 大 。 对 于 ActiveX 控 件 来 说 , 它 适 于 小 文 件 , 特 别是 在 万 维 网 上 应 用 程 序 时 , 这 点 非 常 重 要 。 幸 运 的 是 , Visual C++ 在 MFC 之 外还 提 供 了 其 他 工 具 。 这 一 章 就 是 要 讨 论 一 个 非 常 流 行 的 MFC 替 代 工 具 , 叫 做Active Template Library ( 活 动 模 板 库 )。
Active Template Library 通 常 简 称 为 ATL , 它 提 供 了 一 套 范 围 很 广 的 C++ 类 模 板
( 注 : C++ 类 模 板 也 叫 做 参 数 化 的 类 型 , 它 是 一 种 高 级 的 宏 格 式 , 编 译 器 根 据 传递 到 模 板 的 参 数 , 扩 展 为 正 常 的 类 定 义 。 模 板 与 使 用 #define 语 句 创 建 的 宏 不 同 , 它 扩 展 为 普 通 的 类 型 , 使 它 们 成 为 类 型 安 全 的 类 。编 译 器 可 以 监 视 程 序 模 板 化 的类 的 使 用 , 就 像 它 对 普 通 的 类 一 样 , 而 且 , 可 以 正 确 识 别 出 任 何 不 一 致 类 型 的 情况 ), 用 于 开 发 可 嵌 入 在 C O M 应 用 程 序 中 的 服 务 器 目 标 。 在 ATL 的 第 3 版 中 , ATL 辅 助 编 写 各 种 类 型 的 COM 目 标 ,甚 至 可 帮 助 生 成 包 容 器 程 序 ;在 这 一 章 里 ,
主 要 讨 论 怎 样 使 用 |
ATL 开 发 ActiveX 控 件 。 这 里 的 目 的 是 继 续 说 明 第 |
9 |
章 所 讨 |
---|---|---|---|
论 的 问 题 , 说 明 用 |
MFC 之 外 的 方 法 来 开 发 制 作 ActiveX 控 件 。 |
MFC 简 化 了 ActiveX 控 件 的 开 发 ,但 不 可 避 免 地 会 出 现 大 量 的 MFC 库 DLL ,这
对 于 制 作 网 页 是 不 合 适 的 。 为 了 解 决 这 个 问 题 , Microsoft 已 经 增 强 了 ATL , 其方 法 是 为 ActiveX 控 件 添 加 特 殊 的 支 持 。 当 前 , 选 择 开 发 ActiveX 控 件 项 目 所 使用 的 工 具 一 般 要 遵 守 的 原 则 是 :
-
在 开 发 如 第 9 章 所 述 的 Game 程 序 的 一 般 包 容 器 应 用 程
序 时 , 考 虑 使用 MFC 。 当 包 容 器 本 身 同 MFC 有 较 强 的 联 系 时 , MFC 作 为 开 发 工 具是 很 有 吸 引 力 的 , 因 为 控 件 不 负 责 加 载 库 。 也 就 是 说 , 它 早 已 做 出 了补 偿 。
-
应 用 在 Internet 的 ActiveX 控 件 则 使 用 ATL 。
这 一 章 将 比 前 两 章 更 详 细 地 讨 论 COM , 还 详 细 地 介 绍 使 用 ATL 编 写 复 杂 的ActiveX 控 件 , 而 不 把 更 多 的 精 力 放 在 C O M 上。 ATL 不 像 MFC , 它 让 开 发 者 离C O M 的 表 面 更 近 , 而 不 是 在 它 下 面 。
ATL 和 包 容 器 应 用 程 序
尽 管 ATL 不 是 本 章 的 主 题 , 但 它 对 包 容 器 编 程 的 支 持 值 得 说 两 句 。ATL 在 C O M 编 程 中 的 作 用 比 较 突 出 , 但 它 倾 向 于 对 服 务 器 的 开 发 , 而 不 是 客 户 。 库 提 供 两 类模 板 , 分 别 叫 做 CComPtr 和 CComQIPtr , 这 两 类 模 板 证 明 对 编 写 客 户 端 代 码 非常 有 用 。 这 些 模 板 生 成 灵 活 的 接 口 指 针 , 可 设 计 来 保 证 客 户 正 确 地 释 放 控 制 接
口 , 即 使 有 异 常 错 误 打 断 了 正 常 的 运 行 流 程 。
首 先 看 问 题 , 再 看 解 决 方 案 。 包 容 器 用 控 件 的 QueryInterface 方 法 去 调 用 控 件 的QueryInterface 方 法 , 来 请 求 控 件 提 供 的 接 口 指 针 。 唯 一 的 限 制 是 当 完 成 使 用 接口 时 , 包 容 器 就 需 要 调 用 接 口 的 Release 方 法 , 通 知 控 件 这 个 接 口 已 不 需 要 了 。不 能 执 行 COM 这 一 基 本 规 则 , 将 导 致 控 件 目 标 在 系 统 的 内 存 池 中 保 存 , 甚 至 在包 容 器 终 止 之 后 仍 然 存 在 。使 用 一 个 代 码 段 可 以 演 示 包 容 器 如 何 需 要 指 向 控 件 接口 的 指 针 , 以 IOleObject 为 例 :
void Function1(Iunknown* pUnk)
{
IOieObject *pOleObj;
pUnk->QueryInterface(IID_OleObject,(PVOID*) &pOleObj );
.
. // Use the IOleObject interface
.
pOleObj->Release(); //When finished,release it
}
如 果 在 最 后 一 行 调 用 IO leObject::Release 让 该 接 口 终 止 前 , 有 一 个 异 常 错 误 让 程序 结 束 的 话 , 这 段 编 码 就 会 出 现 麻 烦 。 如 果 把 pOleObj 当 作 智 能 指 针 就 会 解 决 这一 潜 在 问 题 ,因 为 这 一 指 针 的 破 坏 程 序 调 用 该 接 口 的 Release 方 法 ,当 IOleObject 在 使 用 中 时 , 即 使 程 序 突 然 中 止 , 它 仍 能 够 运 行 。
void Function1(Iunknown* pUnk)
{
CComPtr<IOleObject> pOleObj; pUnk-> QueryInterface(&pOleObj );
.
. // Use the IOleObject interface
.
} // Release called automatically
两 个 程 序 段 的 明 显 区 别 是 , 修 改 过 的 版 本 在 停 止 使 用 IOleObject 时 , 没 有 明 确 调用 IO leObject::Release 。当 pOleObj 脱 离 作 用 域 时 , 因 为 Function1 正 常 返 回 或 出
现 错 误 , 指 针 的 破 坏 程 序 就 承 担 起 释 放 接 口 的 任 务 。
把 pOleObj 当 作 智 能 指 针 还 有 另 外 的 优 点 。现 在 ,检 索 接 口 指 针 pUnk 将 更 简 单 , 原 因 是 CComPtr 提 供 其 本 身 的 QueryInterface 函数,从 pOleObj 中 推 出 接 口 的 标识 符 。 通 过 确 保 标 识 符 (IID_OleObject) 和 目 标 指 针 (pOleObj) 指 向 同 一 接 口(IOleObject) ,它 使 编 码 更 方 便 ,也 增 加 了 安 全 性 。可 避 免 犯 以 下 这 样 可 笑 的 错 误 :
pUnk-> QueryInterface( IID_ThisObject, (PVOID*) &pThatObject);
CcomPtr::QueryInterface 能 够 通 过 应 用 程 序 __uuidof 运 算 符 让 接 口 标 识 符 不 指 向指 针 (UUID 为 universally unique identifier( 通 用 唯 一 标 识 符 ) 的 缩 写 )。 在 编 译 时 确定 标 识 符 , __uuidof 取 消 了 多 余 的 编 码 同 定 义 标 识 符 的 程 序 间 的 链 接 。 总 的 效 果是 减 少 了 运 行 大 小 。 例 如 , 在 前 面 Function1 的 第 一 个 版 本 中 , 可 以 用 以 下 两 行的 任 意 一 行 __uuidof 代 替 调 用 QueryInterface 。
pUnk-> QueryInterface(__uuidof(IOlebject),(PVOID*)&pOleObj); pUnk-> QueryInterface(__uuidof(pOleObj), (PVOID*)&pOleObj);
Iunknown 是 客 户 最 终 很 少 需 要 的 接 口 , 但 它 是 客 户 能 肯 定 ActiveX 控 件 可 支 持的 唯 一 接 口 。 获 得 一 个 接 口 可 分 两 个 步 骤 : 首 先 调 用 程 序 得 到 一 个 Iunknown 指针( 在 分 段 代 码 中 的 pUnk ),然 后 调 用 Iunknown::QueryInterface 检 索 实 际 需 要 的接 口 指 针 。 ATL 提 供 另 一 个 智 能 指 针 把 这 两 个 步 骤 合 二 为 一 。 CComQIPtr 模 板
在 构 造 器 包 含 对 Iunknown::QueryInterface 的 调 用 。以 下 是 一 个 ATL 的 CComQIPtr 模 板 的 简 要 列 表 ,显 示 构 造 器 怎 样 得 到 所 要 的 接 口 指 针 和 破 坏 程 序 在 以 后 如 何 释放 它 。
template <class T, const IID* piid = &_unidof(T)> class CComQIPtr
{
public:
T* P;
.
.
.
CComQIPtr(Iunknown* lp)
{
p = NULL;
if (lp != NULL)
lP-> QueryInterface( * piid, (void ** )&p );
}
~ CComQIPtr()
{
if (p)
p->Release();
}
.
.
.
}
CComQIPtr 模 板 为 调 用 程 序 提 供 了 一 个 更 为 简 洁 的 方 法 从 控 件 得 到 一 个 接 口 指
针 。 如 果 控 件 不 支 持 所 需 接 口 , 指 针 类 的 p 成 员 为 NULL 。
CComQIPtr <IOleObject> pOleObj; pOleObj = pUnk;
if (pOleObj.p)
{
.
. // Use the IOleObject interface
.
}
调 用 程 序 可 使 用 CComQIPtr 为 除 了 IUnknown 之 外 的 任 何 接 口 生 成 指 针 。 要 为
Iunknown 接 口 生 成 指 针 可 使 用 CComPtr 。
CComQIPtr 和 CcomPtr 模 板 受 到 Standard Template Library ( 标 准 模 板 库 ) 中 的auto_ptr 智 能 指 针 类 的 启 发 , 它 确 保 了 通 过 new 分 配 的 目 标 返 回 到 程 序 的 空 闲 空间 。 尽 管 模 板 主 要 对 使 用 ActiveX 控 件 的 客 户 应 用 程 序 有 好 处 , 它 们 也 可 很 容 易地 为 控 件 本 身 服 务 ,特 别 是 控 件 聚 集 或 包 含 另 一 个 控 件 时 。本 章 稍 后 会 介 绍 一 个
例 子 , 来 说 明 ActiveX 控 件 如 何 利 用 智 能 指 针 。
ATL 对 客 户 编 程 的 支 持 是 非 常 被 动 的 ,它 只 包 括 项 目 中 的 源 代 码 文 件 。这 些 文 件可 通 过 其 A tl 前 缀 识 别 , 存 储 在 VC98\ATL\Include 文 件 夹 中 。 例 如 , 一 个 使 用CComQIPtr 和 CcomPtr 的 包 容 器 项 目 访 问 MSDN 的 Samples\VC98\ATL 文 件 夹中 的 A tlCon 项 目 。ATL 对 开 发 类 似 ActiveX 控 件 的 服 务 程 序 的 帮 助 远 不 止 这 些 , 本 章 其 余 部 分 将 集 中 介 绍 ATL 的 这 一 方 面 。
ATL 和 ActiveX 控 件
从 技 术 上 说 , ActiveX 标 准 对 控 件 的 能 力 要 求 不 高 。 作 为 ActiveX 控 件 运 行 , 一个 组 件 只 需 要 实 现 Iunknow n, 可 嵌 入 , 而 且 可 以 自 我 注 册 。 其 他 诸 如 属 性 、 方法 和 事 件 均 是 可 选 的 。 由 于 过 于 微 小 , 而 且 是 隐 性 的 , 一 个 C O M 目 标 在 学 术 之外 没 有 讨 论 的 价 值 。 实 际 上 , 一 个 ActiveX 控 件 维 持 一 组 数 据 ; 激 发 事 件 ; 并 支持 足 够 的 接 口 , 让 用 户 应 用 程 序 和 它 交 互 。 ActiveX Test Container( ActiveX 测 试包 容 器 ) 功 能 对 于 ActiveX 控 件 来 说 就 像 是 石 蕊 试 纸 。 本 章 开 发 的 两 个 控 件 项 目能 够 成 功 地 在 Test Container 中 运 行 , 因 为 它 们 插 入 了 在 表 10-1 描 述 的 接 口 。 所列 的 接 口 是 ActiveX 控 件 为 同 公 布 的 M icrosoft 指 南 相 兼 容 所 必 须 提 供 的 支 持 。比 较 表 10-1 和 表 8-2 , 其 中 列 出 了 要 嵌 入 ActiveX 控 件 , 一 个 包 容 器 必 须 支 持 的接 口 。
如 果 希 望 ATL 很 容 易 地 创 建 一 个 只 有 几 K 大 小 的 ActiveX 控 件 ,一 定 会 很 失 望 。库 提 供 了 几 种 方 法 来 减 少 运 行 量 ,但实现表 10-1 所 列 的 接 口 会 加 入 大 量 的 编 码 。创 建 短 小 编 码 的 唯 一 方 法 就 是 直 接 使 用 C O M 编 程 ,而 不 借 助 于 ATL 和 其 他 支 持库 ( Visual C++ 的 Samples\VC98\ATL 文 件 夹 中 有 一 个 叫 做 M inimal 的 ATL 项 目范 例 , 创 建 了 一 个 小 C O M 服 务 器 程 序 只 有 5600 个 字 节 。M inimal 服 务 器 缺 少 几个 基 本 特 征 , 并 不 是 为 了 创 建 真 正 的 ActiveX 控 件 )。与 MFC 一 样 , ATL 带 来 了方 便 , 也 带 来 了 大 量 冗 长 无 用 的 编 码 。 做 一 些 额 外 的 工 作 , 就 可 以 得 到 一 个 简 单的 无 窗 口 的 ActiveX 控 件 , 例 如 只 有 40KB 大 小 或 更 小 。 本 章 稍 后 会 介 绍 几 种 帮助 减 少 用 ATL 控 件 大 小 的 方 法 。
表 10-1 ActiveX 控 件 必 须 支 持 的 接 口 , 以 同 指 南 相 兼 容接 口 说 明
IOleObject 与 控 件 的 客 户 站 点 通 信 所 需 要 的 , 事 件 除 外 。
事 件 通 过 IConnectionPointContainer 接 口 进 行处 理 , 在 下 面 将 说 明
IOleInPlaceObject 通 过 在 适 当 位 置 激 活 的 控 件 来 实 现 ,并 提 供 它
们 自 己 的 用 户 接 口 。 需 要 IOleObject 的 支 持
IOleInPlaceActiveObj ect
只 有 在 控 件 提 供 用 户 接 口 , 而 且 支 持
IOleInPlaceObject 的 情 况 下 才 需 要
IDataObject 以 某 种 方 式 传 送 数 据 到 包 容 器 的 控 件 需 要
它 ,如 共 享 内 存 或 文 件 。IDataObject 提供 COM 的 Uniform Data Transfe r( 统 一 数 据 传 输 ) 的手 段 , 这 是 一 种 协 议 , 用 于 为 任 何 数 据 类 型 的交 换 建 立 规 则
IViewObject2 通 过 显 示 窗 口 的 可 见 控 件 来 实 现
续 表
IDisPatch 具 有 定 制 方 法 或 客 户 可 以 通 过
Idispatch::Invoke 来 访 问 的 控 件 需 要 它
IConnectionPointCont ainer
启 动 事 件 的 控 件 需 要 它 。这 个 接 口 为 客 户 枚 举控 件 目 标 可 以 启 动 的 事 件
IConnectionPoint 支 持 IconnectionPointContainer 的 控 件 需 要
IProvideClassInfo 由 包 含 类 型 库 信 息 的 控 件 实 现 ,这 意 味 着 大 多
数 ActiveX 控 件 。 通 过 其 GetClassInfo 方 法 , 接 口 提 供 了 到 ITypeInfo 实 现
的 指 针 , 从 中 , 客 户 可 以 抽 取 控 件 的 类 型 信息 。 类 似 的
续 表
IProvideClassInf
接 口 是 一 个 扩 展 接 口 , 它 扩 充 了 GetGUID 方法 , 这 样 , 客 户 便 可 以 为 控 件 的 默 认 事 件 来 检索 到 标 识 符 的 指 针
IPersistStorage 可 以 保 存 和 从 包 容 器 提 供 的 Istorage 实 例 加 载
的 控 件 需 要 它
IClassFactory 实 例 化 一 个 要 求 的 类 目 标 ,将 返 回 给 它 一 个 指
针 。此 目 标 是 通 过 在 系 统 注 册 表 中 注 册 的 类 标识 符 来 进 行 标 识
IClassFactory2 与 IClassFactory 相 同 , 但 是 , 它 还 增 加 了 许 可
证 的 支 持 ( 参 见 第 9 章 的 “ 许 可 证 ” 一 节 )
另 一 方 面 , 当 考 虑 到 与 控 件 有 关 的 大 量 MFC 库 DLL 时,用 ATL 编 写 的 ActiveX 控 件 比 用 MFC 编 写 的 相 应 的 ActiveX 控 件 要 小 得 多 。 用 ATL 库 构 造 的 ActiveX 控 件 可 使 用 MFC , 但 这 样 用 ATL 就 没 有 意 义 。 如 果 计 划 用 MFC 编 写 控 件 , 可以 首 选 MFC ControlWizard ,只 有 在 创 建 智 能 指 针 类 CComQIPtr 和 CcomPtr 才 会使 用 ATL 。 使 用 ATL 的 主 要 好 处 是 , 它 所 创 建 的 组 件 不 需 要 MFC 和 C 运 行 库 。
这 样 的 组 件 可 放 在 Internet 上 独 立 存 在 , 而 不 用 依 靠 用 户 机 器 上 的 支 持 文 件 。
与 MFC 一 样 , ATL 为 控 件 包 含 的 每 一 个 目 标 创 建 单 一 的 代 表 性 的 类 。 这 些 类 来源 于 目 标 支 持 的 所 有 接 口 , 这 是 MFC 通 过 嵌 套 类 玩 的 一 个 小 把 戏 。 ATL 可 通 过使 用 多 样 继 承 更 灵 活 地 达 到 同 样 的 效 果 , 这 些 继 承 是 几 个 基 类 发 展 出 来 新 的 类 , 新 类 从 每 个 类 继 承 了 成 员 数 据 和 函 数 。基 类 列 表 叫 做 继 承 列 表 , 看 起 来 像 以 下 的类 声 明 :
class CMyClass: public CClass1, public CClass2, public IInterface1,
public IInterface2,
.
.
.
{
ATL 为 开 发 ActiveX 控 件 提 供 最 重 要 的 服 务 是 许 多 库 的 实 现 代 码 ,这 些 代 码 用 于控 件 通 常 支 持 的 许 多 COM 接 口 。 以 类 模 板 的 形 式 , 有 了 库 代 码 , 就 不 用 再 编 写程 序 来 支 持 通 用 的 接 口 了 。 ATL 为 列 在 表 10-1 中 的 每 一 个 接 口 提 供 了 模 板 化 的实 现 代 码 , 并 以 接 口 名 加 上 Impl 后 缀 来 命 名 该 模 板 。 例 如 , IQuickActiveImpl 模 板 为 IQuickActive 方 法 提 供 如 SetExtent 和 GetExtent 这 样 的 代 码 。 与 COM 要求 的 那 样 , 一 个 支 持 接 口 的 所 有 方 法 都 列 出 来 , 但 不 一 定 使 用 。 许 多 方 法 只 叫 做ATLTRACENOTIMPL 宏,为 Visual C++ Output( 输 出 ) 窗 口 的 Debug 选 项 卡 编写 很 少 的 记 录 信 息 , 并 把 E_NOTIMPL 返 回 给 调 用 程 序 。如 果 想 让 ActiveX 控 件提 供 这 样 的 方 法 , 则 必 须 自 己 添 加 代 码 。
除 了 代 码 库 外 , ATL 还 提 供 AppWizard , 以 开 始 创 建 一 个 服 务 器 项 目 , 还 有 另 一个 向 导 , 用 来 帮 助 生 成 ActiveX 控 件 所 需 的 类 代 码 。 你 会 感 觉 到 , 使 用 ATL 并不 比 使 用 MFC 困 难 , ATL 需 要 编 程 人 员 比 使 用 MFC 时 多 注 意 项 目 细 节 , 并 处理 更 多 的 COM 问 题 。在 展 示 ATL 的 演 示 项 目 前 , 先 停 顿 一 下 , 了 解 一 下 后 面 会遇 到 的 有 关 ATL3 个 方 面 的 背 景 信 息 : 接 口 映 射 , 目 标 映 射 和 线 程 模 型 。
接 口 映 射
接 口 映 射 是 大 多 数 QueryInterface 函 数 的 通 用 模 式 。C O M 目 标 的 所 有 接 口 都 支 持这 一 函 数 , 让 包 容 器 应 用 程 序 在 任 何 接 口 中 调 用 QueryInterface , 并 接 收 其 他 任何 接 口 的 指 针 。 如 果 控 件 不 支 持 所 需 的 接 口 , 它 返 回 的 值 为 E_NOINTERFACE 。
以 下 程 序 段 演 示 了 一 个 典 型 的 QueryInterface 函 数 在 不 使 用 ATL 时 看 起 来 会 是 什么 样 。 参 数 riid 指 调 用 程 序 所 需 的 接 口 标 识 符 , 参 数 ppvObject 接 收 接 口 指 针 :
STDMETHODIMP CMyClass::QueryInterface( REFIID riid,PVOID *ppvObject )
{
switch( riid)
{
case IID_UNKNOWN:
case IID_IInterface1:
*ppvObject = (IInterfacel*) this; break;
case IID_IInterface2:
*ppvObject = (IInterface2* this;
break;
case IID_IInterface3:
*ppvObject = (IInterface3*) this; break;
default:
*ppvObject = 0;
return E_NOINTERFACE;
}
(IUnknown*) *ppvObject->AddRef(); return S_OK;
}
这 类 重 复 的 模 式 很 容 易 用 宏 来 实 现 , 了 解 宏 , 便 可 对 接 口 映 射 的 物 理 分 布 有 清 楚的 认 识 。 当 用 户 调 用 类 目 标 的 QueryInterface 方 法 时 , 接 口 映 射 通 过 ATL 的CComObjectRootBase::InternalQueryInterface 函 数 来 确 定 调 用 的 路 线 ,在 AtlCom.h 文 件 中 , 如 下 处 理 :
static HRESULT WINAPI InternalQueryInterface(PVOID pThis, const_ATL_INTMAP_ENTRY *pEntries,REFIID riid, PVOID *ppvObject)
同 以 前 一 样 , riid 确 认 接 口 , ppvObject 接 收 它 的 指 针 。 该 函 数 的 第 二 个 参 数pEntries 指 向 接 口 映 射 的 开 始 , 它 包 含 _ATL_INTMAP_ENTRY 数 组 结 构 , 数 组中 的 每 一 个 结 构 包 含 一 个 接 口 标 识 符 , 一 个 DWORD 变 量 , 还 有 一 个 函 数 指 针 :
struct _ALT_INTMAP_ENTRY
{
const IID* |
piid; |
//Interface iedntifier |
---|---|---|
DWORD |
dw; |
//Offset |
_ATL_CREATORARGFUNC* |
pFunc; |
Function pointer |
};
pFunc 的 值 决 定 InternalQueryInterface 如 何 解 释 dw 的 值 。 如 果 pFunc 是ATL_SIMPLEMAPENTRY( 定 义 为 1) , dw 则 包 含 类 目 标 的 偏 移 值 , 允 许InternalQueryInterface 完 成 类 似 以 下 的 调 用 :
*ppvObject=pThis+pEntries->dw;
*ppvObject->AddRef();
PEntries 已 经 增 加 值 , 以 指 向 数 组 中 接 口 _ATL_INTMAP_ENTRY 结 构 。 如 果
pFunc 的 值 大 于 1 ,则 InternalQueryInterface 调 用 函 数 , 把 dw 作 为 参 数 。
Pentries->pFunc( pThis, riid, ppvObject, pEntries->dw);
所 调 用 的 函 数 负 责 编 写 所 需 的 指 向 *ppvObject 的 接 口 指 针 。
Pfunc 成 员 也 可 为 NULL 。 这 个 特 殊 值 为 数 组 中 最 后 的 _ATL_INTMAP_ENTRY 结 构 所 保 留 , 作 为 接 口 映 射 的 结 尾 标 记 。 你 没 有 必 要 过 分 担 心 这 一 部 分 , ATL 通 过 特 殊 的 宏 来 确 保 最 后 的 结 构 。 另 外 , 在 安 排 映 射 数 组 的
_ATL_INTMAP_ENTRY 结 构 方 面 没 有 限 制 。 以 下 是 前 面 所 引 述 的 CmyClass 的接 口 映 射 。 注 意 , 一 个 空 的 _ATL_INTMAP_ENTRY 结 构 是 如 何 结 束 数 组 的 :
{&IID_Interface1, 0, 1}
{&IID_Interface 2, 4, 1}
{&IID_Interface 3, 8, 1}
{0, 0, 0 }
在 使 用 ATL 的 代 码 中 ,接 口 映 射 由 一 系 列 的 COM_INTERFACE 宏 构 成 ,每 个 宏扩 展 为 一 个 ATL_INTMAP_ENTRY 结 构 。 ATL 提 供 17 种 不 同 的COM_INTERFACE 宏 , 通 过 其 后 缀 _ENTRY 和 _TEAR_OFF 来 表 明 宏 要 处 理 的接 口 类 型 。宏的名称由 COM_INTERFACE 加 上 后 缀 字 符 串 构 成 。表 10-2 将 描 述COM_INTERFACE 宏 , 并 解 释 什 么 时 候 使 用 它 们 。 为 简 明 起 见 , 第 一 列 只 列 宏后 缀 名 。 要 想 详 细 了 解 宏 , 请 参 阅 在 线 帮 助 “ COM_INTERFACE_ENTRY M acros ”。
表 10-2 ATL 的 COM_INTERFACE 宏
COM_INTERFACE 说 明
宏
_ENTRY 暴 露 衍 生 类 的 接 口
_ENTRY_IID 与 COM_INTERFACE_ENTRY 相 同 , 但 是 ,
压 指 定 接 口 的 标 识 符
_ENTRY2 对 于 从 两 个 或 多 个 双 接 口 衍 生 的 类 ,要 解 析 其
接 口 的 不 明 确 性 ,应 当 提 供 到 Idispatch 接 口 的指 针 。 双 接 口 在 本 章 的 后 面 介 绍
_ENTRY2_IID 与 COM_INTERFACE_ENTRY2 相 同 , 但 是 ,
它 指 定 接 口 的 标 识 符
_ENTRY_IMPL 是 COM_INTERFACE_ENTRY 的 另 一 种 方 法
续 表
_ENTRY_IMPL_IID 与 COM_INTERFACE_ENTRY_IMPL 相 同 ,
但 是 , 它 指 定 接 口 的 标 识 符 。 此 宏 和
COM_INTERFACE_ENTRY_IMPL 在 ATL 版
本 3 中 是 过 时 的 , 要 使 用
COM_INTERFACE_ENTRY
_ENTRY_FUNC 在 ATL 处理 QueryInterface 时 , 指 定 获 得 控 件
的 挂 钩 函 数 。 挂 钩 函 数 可 以 忽 略 此 进 程 , 方 法是 返 回
E_NOINTERFACE , 这 样 , 便 可 以 隐 藏 ATL
要 返 回 的 接 口
_ENTRY_FUNC_BLI N D
与 COM_INTERFACE_ENTRY_FUNC 相 同 ,
只 是 要 查 询 引 起 挂 钩 函 数 调 用 的 所 有 接 口
_ENTRY_TEAR_OF F
_ENTRY_CACHED
_TEAR_OFF
_ENTRY_AGGREGA TE
续 表
为 快 用 ( tear-off ) 接 口 声 明 C O M 映 射 项 , 这种 接 口 只 有 在 客 户 通 过 QueryInterface 请 求时 , 才 进 行 实 例 化 。 这 种 接 口 只 在 需 要 时 才 占用 内 存 , 这 使 它 更 适 合 于 ISupportErrorInfo 这样 的 接 口 , 因 为 在 控 件 的 生 存 期 , 这 样 的 接 口也 可 以 不 使 用 。 其 缺 点 是 , 它 比 常 规 接 口 的 开销 要 稍 大 些
实 现 这 种 接 口 的 类 必 须 从CComTerzaOffObjectBase 衍 生 出 来 ,而 且 具 有自 己 的 C O M 映射
与 COM_INTERFACE_ENTRY_TEAR_OFF 宏
相 同 , 不 同 的 是 , 在 第 一 个 实 例 后 , 保 存 此 接口 数 据 。 如 果 快 用 接 口 是 实 例 化 的 , 则 有 效 地缓 存 可 以 将 它 转 换 为 常 规 接 口
为 聚 合 目 标 提 供 的 接 口 声 明 COM 映 射 。 此 宏查 询 转 发 到 聚 合 目 标 的 IUnknown 接 口 的 接 口标 识 符 。 本 章 的 后 面 将 讨 论 有 关 聚 合 的 问 题
_ENTRY_AGGREGA TE_BLIND
_ENTRY_AUTO- AGGREGATE
续 表
与 COM_INTERFACE_ENTRY_AGGREGATE
相 同 , 不 同 的 是 , 所 有 查 询 都 转 发 到 指 定 的
Iunknown 接口
如 果 提 供 IUnknown 指 针 , 则 与
COM_INTERFACE
_ENTRY_AUTOAGGREGATE 相 同 ,否 则 ,此宏 自 动 插 件 由 给 定 的 类 标 识 符 指 出 的 聚 合
__ENTRY_AUTO-
AGGREGATE_BLIN D
与
COM_INTERFACE_ENTRY_AUTOAGGREG
ATE 相 同 , 除 非 提 供 了 IUnknown 指 针 , 在 这样 的 情 况 下 , 所 有 查 询 都 转 发 到 Iunknown 接口 , 如 果 Iunknown 指 针 没 有 提 供 , 则 宏 创 建由 给 定 的 类 标 识 符 指 出 的 聚 合
续 表
_ENTRY_CHAIN 允 许 继 续 处 理 指 定 的 基 本 类 的 COM 映 射 。 基
本 类 必 须 在 当 前 类 的 继 承 性 列 表 中 出 现 。也 就是 说 , 它 必 须 是 当 前 类 的 基 础 。COM_INTERFACE_ENTRY_CHAIN 不 能 是
C O M 映 射 中 的 第 一 项
_ENTRY_BREAK 在 请 求 指 定 的 接 口 时 ,调 用 DebugBreak。使 用
此 宏 来 触 发 调 试 器 断 点 ( 调 试 器 在 第 11 章 中介 绍 )
_ENTRY_NOINTER- FACE
在 查 询 指 定 的 接 口 时 , 返 回E_NOINTERFACE , 并 结 束 C O M 映 射 处 理 。此 宏 禁 用 此 接 口 , 防 止 它 被 COM 映 射 后 的 任何 COM_INTERFACE 宏 进 行 处 理
ATL 指 作 为 C O M 映 射 的 源 代 码 的 宏 系 列 ,这 一 名 称 常 常 和 宏 所 创 建 的 接 口 映 射混 用 。 在 ATL 中 , C O M 映 射 以 调 用 BEGIN_COM_MAP 宏 开 始 , 以END_COM_MAP 宏 结 束 。 在 它 们 之 间 有 一 系 列 的 COM_INTERFACE 宏 , 都 有类 支 持 的 接 口 。
BEGIN_COM_MAP(CMyClass) COM_INTERFACE_ENTRY(IMyClass) COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY2(IPersist, IpersistStreamInit)
.
.
.
END_COM_MAP()
映 射 从 MFC 的 标 准 消 息 映 射 中 借 鉴 了 许 多 方 法( MFC 提 供 自 己 的 宏 来 创 建 接 口映 射 , 这 一 点 在 第 9 章 没 有 叙 述 。 在 MFC 中 , 接 口 映 射 以 BEGIN_ INTERFACE_MAP 宏 开 始 , 以 END_INTERFACE_MAP 宏 结 束 ) 。 COM 映 射 的结 构 顺 序 并 不 重 要 , 列 表 中 的 第 一 个 接 口 必 须 使 用 简 单 的 映 射 项 , 例 如COM_INTERFACE_ENTRY 或 其 他 COM_INTERFACE 宏 , 用
ATL_SIMPLE_MAPENTRY 的 pFunc 值 扩 展 至 ATL_INTMAP_ENTRY 结 构 。 这个 需 求 阻 止 了 ATL 使 用 映 射 中 的 第 一 个 接 口 来 响 应 目 标 的 IUnknown 接 口 的 请求 。
目 标 映 射
- 个 ActiveX 控 件 可 能 包 含 几 个 目 标 , 每 个 目 标 都 通 过 一
个 类 来 表 示 , 提 供 自 己的 接 口 映 射 。 图 10-1 显 示 了 控 件 、 控 件 所 包 含 的 目 标 和 目 标 实 现 的 接 口 之 间 的层
图 10-1 ActiveX 控件的元素
次 关 系 。同 接 口 映 射 的 概 念 一 样 , 目 标 映 射 记 录 了 一 个 控 件 的 目 标 和 目 标 相 关 的类 标 识 符 ( CLSID )。 ATL 把 目 标 映 射 安 排 成 一 个 _ATL_OBJMAP_ENTRY 结 构数 组 , 每 一 个 定 义 一 系 列 求 助 功 能 。 以 下 是 一 个 简 单 的 _ATL_OBJMAP_ENTRY 结 构 数 组 :
struct _ATL_OBJMAP_ENTRY
{
HRESULT UpdateRegistry( BOOL bRegister );
HRESULT GetClassObject( void* pv, REFIID riid,PVOID* ppv); HRESULT CreateInstance( void* pv, REFIID riid,PVOID* ppv); LPCTSTR GetObjectDescription();
HRESULT RevokeClassObject();
HRESULT RegisterClassObject( DWORD dwClsContext,DWORD dwFlags);
};
当 客 户 第 一 次 请 求 一 个 类 目 标 时 , 便 调 用 GetClassObject 函 数 , 生 成 一 个 目 标 实例 ,向 调 用 程 序 提 供 了 一 个 指 针 ,来 指 向 所 需 要 的 IClassFactory 或 IClassFactory2 接 口 。 函 数 在 目 标 映 射 结 构 中 存 储 类 工 厂( class factory ) 指 针 , 让 下 一 个 调 用 类目 标 新 实 例 的 需 求 尽 快 完 成 。因 为 类 目 标 在 栈 和 堆 上 进 行 实 例 化 , 而 不 是 在 控 件的静态数据中,所以,用 ATL 构 造 的 ActiveX 控 件 不 需 要 同 C 运 行 库 进 行 链 接 。不 使 用 C 运 行 库 和 静 态 构 造 器 , 可 以 保 证 最 终 控 件 的 可 执 行 尺 寸 很 少 , 不 用 对链 接 的 C 库 进 行 额 外 的 初 始 化 。
线 程 模 型
ATL 支 持 四 种 线 程 模 型 , 分 别 名 为 单 一( single )、公 寓( apartmen t)、自 由( free )和 两 种 ( both )。 线 程 模 型 描 述 了 一 个 控 件 实 现 的 线 程 安 全 类 型 和 程 度 , 尽 管 对于 任 何 客 户 应 用 程 序 , 无 论 线 程 安 排 是 怎 么 样 的 ,都 可 安 全 地 进 入 任 何 线 程 模 型生 成 的 控 件 。 如 果 客 户 的 线 程 与 任 何 线 程 模 型 都 不 兼 容 , 则 C O M 便 会 在 二 者 之间 实 现 自 身 , 以 保 证 线 程 的 安 全 通 信 。 有 了 这 个 保 证 , 为 自 己 的 控 件 选 择 哪 种 线程 模 型 就 会 很 简 单 了 。 对 于 每 一 种 选 择 , 都 有 其 各 自 的 优 缺 点 , 通 常 , 性 能 与 代码 的 大 小 是 相 对 的 ,效 率 与 简 化 是 相 对 的 。ATL 添 加 到 项 目 中 的 代 码 支 持 用 户 选择 的 线 程 , 所 以 , 你 只 需 要 保 证 自 己 编 写 的 代 码 也 与 所 选 择 的 模 型 相 符 。 在 本 节中 , 将 解 释 4 种 模 型 之 间 的 不 同 之 处 , 以 帮 助 你 决 定 哪 一 种 最 适 合 你 的 项 目 。
从 客 户 的 角 度 来 看 , 很 容 易 想 象 线 程 是 怎 样 的 。 开 始 , 我 们 来 看 看 应 用 程 序 于 包容 器 的 线 程 模 型 , 然 后 , 检 查 每 种 线 程 模 型 对 应 用 程 序 嵌 入 的 ActiveX 控 件 的 影响 如 何 。 线 程 模 型 并 不 难 以 理 解 , 但 是 , 其 中 的 规 则 却 有 些 不 好 理 解 。 在 本 章 的后 面 , 有 一 个 示 例 项 目 , 它 应 用 这 里 讨 论 的 一 些 理 论 , 并 说 明 一 种 控 件 在 不 同 的线 程 模 型 下 是 如 何 起 作 用 的 。
单 一 线 程
单 一 线 程 模 型 是 四 种 线 程 中 最 简 单 的 ,因 为 它 不 需 要 任 何 控 件 来 防 卫 其 数 据 的 模拟 使 用 ,即 使 是 静 态 数 据 。这 种 模 型 允 许 客 户 创 建 一 个 控 件 项 目 的 任 何 数 目 的 实
例 , 但 是 , 它 限 制 在 一 个 线 程 中 , 所 有 客 户 都 访 问 这 些 实 例 。 单 一 线 程 不 限 制 客户 可 以 运 行 的 线 程 数 目 , 而 且 , 确 实 , ActiveX 控 件 应 当 假 定 其 许 多 客 户 都 是 多线 程 的 。这 种 模 型 只 是 指 示 对 目 标 接 口 的 所 有 调 用 都 从 客 户 的 线 程 中 产 生 , 这 个线 程 是 第 一 次 调 用 CoInitializeEx 来 初 始 化 C O M 框 架 的 线 程 。由于 ActiveX 控 件不 需 要 耗 费 其 他 精 力 来 保 证 线 程 对 其 接 口 或 其 他 数 据 的 访 问 是 安 全 的 ,所 以 ,单一 的 线 程 是 在 四 种 线 程 模 型 中 尺 寸 最 小 的 。只 要 客 户 符 合 此 模 型 的 限 制 条 件 ,在客 户 和 服 务 器 之 间 进 行 通 信 就 是 直 接 而 快 速 的 。
但 是 , 考 虑 一 下 , 在 客 户 使 用 相 同 的 AvtiveX 控 件 时 , 在 两 个 线 程 之 间 会 发 生 什么 。 线 程 A , 它 不 是 客 户 的 主 线 程 , 通 过 CoInitializeEX 来 初 始 化 C O M , 然 后 调用 CoCreateInstance , 来 创 建 控 件 目 标 的 一 个 实 例 。 线 程 B 随 后 需 要 此 控 件 的 服务 , 与 线 程 A 一 样 , 它 创 建 一 个 新 的 线 程 实 例 。 但 是 , 在 这 一 次 , COM 不 像 线程 A 调用 CoCreateInstance 那 样 , 返 回 直 接 指 向 控 件 代 码 的 接 口 指 针 。 而 是 , 返回 的 指 针 引 用 一 个 在 线 程 B 上 运 行 的 不 可 见 的 代 理 服 务 目 标 。 在 客 户 使 用 指 针从
线 程 B 来 调 用 一 种 方 法 时 , 代 理 服 务 便 会 发 送 一 条 消 息 给 线 程 A 上 运 行 的 存 根目 标 。 存 根 依 次 直 接 调 用 到 相 同 线 程 上 的 ActiveX 控 件 , 然 后 , 通 过 另 一 条 消 息传 递 返 回 值 给 代 理 服 务 。 消 息 的 交 换 在 线 程 之 间 进 行 , 而 且 保 证 ActiveX 控 件 总是 在 线 程 A 的 环 境 中 执 行 , 也 就 是 第 一 次 初 始 化 COM 的 线 程 。
从 一 个 线 程 中 重 新 确 定 一 个 调 用 的 路 线 ,并 在 另 一 个 线 程 中 完 成 它 ,这 便 是 线 程间 调 度 , 从 概 念 上 来 说 , 它 与 第 8 章 讨 论 的 进 程 间 调 度 类 似 。 主 要 的 不 同 是 代 理服 务 和 执 行 线 程 间 调 度 的 存 根 目 标 是 由 C O M 设 置 的 隐 藏 目 标 , 而 不 是 在 单 独 的进 程 中 安 装 的 动 态 链 接 库 。 窗 口 之 间 的 消 息 传 送 与 远 程 过 程 调 用 类 似 ,它 在 客 户进 程 内 的 代 理 服 务 和 进 程 外 的 存 根 之 间 来 回 传 递 。线 程 之 间 的 调 度 通 常 要 比 进 程之 间 的 要 快 , 但 是 , 与 直 接 调 用 目 标 相 比 , 交 换 消 息 和 切 换 线 程 的 环 境 还 会 大 大减 慢 对 目 标 实 例 的 访 问 速 度 。 每 次 客 户 从 线 程 B 调 用 控 件 的 一 种 方 法 时 , 为 了调 度 到 对 线 程 A 的 调 用 , 还 要 重 复 相 同 的 繁 琐 过 程 。 最 糟 糕 的 是 , 线 程 A 可 能会 非 常 忙 , 没 有 在 其 消 息 循 环 中 , 这 样 , 代 理 服 务 提 出 的 消 息 就 必 须 进 行 等 待 , 直 到 其 抽 取 和 路 由 到 存 根 。 对 线 程 A 生 成 的 客 户 的 所 有 方 法 调 用 , 都 直 接 到 控件 中 , 而 且 不 进 行 调 度 , 这 使 得 线 程 A 具 有 了 特 殊 的 权 限 , 而 其 他 使 用 此 控 件的 线 程 都 没 有 授 予 此 权 限 。只 有 在 客 户 从 不 是 第 一 个 通 过 CoInitializeEx 用 C O M 注 册 自 身 的 线 程 调 用 控 件 时 , 单 一 线 程 模 型 才 会 得 到 性 能 上 的 损 失 。
公 寓 线 程
公 寓 线 程 模 型 为 了 消 除 线 程 间 的 调 度 , 做 了 很 多 的 工 作 。 在 这 种 安 排 方 法 下 , 所有 的 客 户 线 程 都 享 有 特 权 , 而 且 能 够 直 接 与 ActiveX 控 件 的 实 例 交 互 , 而 不 需 要首 先 通 过 代 理 服 务 和 存 根 服 务 。换 句 话 说 , 每 个 公 寓 线 程 都 像 是 前 面 讨 论 的 线 程A , 没 有 一 个 线 程 像 线 程 B 那 样 。 需 要 控 件 服 务 的 每 个 线 程 都 首 先 调 用CoInitializeEx, 与 以 前 一 样 , 然 后 调 用 CoCreateInstance , 来 创 建 一 个 控 件 实 例 。在 此 公 寓 线 程 中 , 返 回 的 接 口 指 针 直 接 指 向 接 口 实 例 化 目 标 中 的 V 表 , 而 且 不
进 入 代 理 服 务 。 每 个 线 程 都 具 有 自 己 的 目 标 实 例 , 只 要 线 程 只 访 问 它 创 建 的 实例 , 就 不 会 有 调 度 发 生 。
有 时 , 人 们 会 产 生 混 淆 , 这 是 因 为 有 两 类 公 寓 线 程 。 ATL 所 说 的 “ 公 寓 ” 模 型 更像 是 单 一 线 程 的 公 寓 模 型 , 通 常 缩 写 为 STA 。 这 种 模 型 在 前 面 已 经 介 绍 了 : 每 个线 程 都 有 一 个 目 标 实 例 , 每 个 线 程 都 调 用 自 己 的 实 例 。 多 线 程 的 公 寓 模 型 , 即M TA , 就 是 ATL 称 之 为 自 由 模 型 的 公 寓 模 型 , 稍 候 我 们 将 介 绍 这 种 模 型 。 公 寓是 一 个 抽 象 的 概 念 , 它 与 现 实 世 界 进 程 和 线 程 的 处 理 方 法 的 关 系 不 大 , 所 以 , 不要 浪 费 很 多 时 间 来 试 图 将 它 可 视 化 。 可 以 这 样 来 看 , 一 个 进 程 就 像 是 一 个 建 筑物 , 其 中 , 线 程 表 示 单 独 的 房 间 。 这 种 模 型 将 一 个 目 标 的 单 一 实 例 与 可 以 直 接 安全 调 用 到 那 个 实 例 的 一 个 或 多 个 线 程 进 行 折 衷 。
尽 管 我 们 通 常 要 讲 到 STA 或 M TA 客 户 应 用 程 序 ,模 型 的 指 定 可 以 更 精 确 地 应 用到 应 用 程 序 中 的 线 程 , 因 为 进 程 可 以 包 含 STA 和 M TA 线 程 。 在 客 户 线 程 调 用CoInitializeEx 时 ,它 将 传 递 一 个 值 ,这 个 值 指 定 此 线 程 是 为 哪 种 公 寓 模 型 而 设 计的 。 COINIT_APARTMENTTHREADED 的 值 在 STA 模 型 下 注 册 线 程 , 也 就 是 单一 线 程 公 寓 的 方 法 。 COINIT_MULTITHREADED 的 值 注 册 线 程 作 为 多 线 程 公 寓的 一 部 分 。一 个 进 程 可 以 包 含 任 何 数 目 的 单 一 线 程 公 寓 , 但 只 能 包 含 一 个 多 线 程的 公 寓 , 在 多 线 程 公 寓 中 , 任 意 数 目 的 线 程 都 可 以 属 于 它 。 在 公 寓 内 部 调 用 线 程时 , 从 目 标 检 索 的 接 口 指 针 不 进 行 调 度 , 但 是 , 在 使 用 其 他 公 寓 中 的 线 程 时 , 总是 要 进 行 调 度 。 在 谈 到 单 一 线 程 公 寓 模 型 时 , 公 寓 和 线 程 这 两 个 词 经 常 互 换 使用 , 不 会 引 起 混 乱 , 但 是 , 在 实 际 应 用 程 序 中 , 在 应 用 到 多 线 程 公 寓 模 型 中 时 ,
这 就 不 对 了 , 下 面 将 讨 论 这 个 问 题 。自 由 线 程
自 由 线 程 是 多 线 程 公 寓 模 型 的 另 一 个 名 称 。在 多 线 程 公 寓 中 的 线 程 可 以 安 全 地 共享 目 标 的 单 一 实 例 所 提 供 的 接 口 指 针 , 而 不 管 创 建 实 例 的 线 程 是 什 么 。 COM 在此 方 法 中 不 起 作 用 , 而 且 , 在 公 寓 的 限 制 内 , 不 发 生 调 度 调 用 。 然 而 , 与 在 单 一线 程 模 型 中 一 样 的 是 , 在 线 程 调 用 到 在 另 一 个 公 寓 中 实 例 化 的 目 标 时 ,还 需 要 进行 调 度 。
这 似 乎 是 足 够 了 , 但 是 , 自 由 线 程 将 重 大 的 责 任 都 放 到 了 开 发 者 身 上 。 选 择 支 持自 由 线 程 模 型 意 味 着 , 在 任 何 时 间 , 要 编 写 任 意 数 目 的 线 程 安 全 访 问 的 代 码 。 下一 节 将 介 绍 每 个 线 程 对 ActiveX 控 件 施 加 影 响 的 一 些 需 求 。
为 ActiveX 控 件 选 择 线 程 模 型
理 解 C O M 的 客 户 端 口 的 线 程 模 型 , 便 可 以 选 择 最 适 于 ActiveX 控 件 的 线 程 模型 。 幸 运 的 是 , 服 务 器 中 的 线 程 涉 及 到 更 到 的 代 码 , 而 且 , 理 论 概 念 更 少 。 你 只需 要 为 自 己 的 控 件 选 择 一 种 线 程 模 型 , 并 根 据 情 况 来 编 写 代 码 , 保 证 C O M 将 通过 调 度 来 解 决 任 何 不 匹 配 的 问 题 。客 户 线 程 与 STA 或 M TA 模 型 是 一 致 的 ,但 是 , ActiveX 控 件 采 用 了 前 面 列 出 的 四 种 模 型 之 一 : 单 一 ( 非 线 程 ), 公 寓 ( STA ), 自 由 ( MTA ) 或 两 种 。 两 种 线 程 模 型 意 味 着 STA 和 M TA , 也 就 是 说 , 必 须 写 控件 来 适 应 客 户 线 程 的 两 种 可 能 模 型 , 而 不 需 要 C O M 来 调 度 其 交 互 作 用 。
我 们 已 经 看 到 了 , 在 调 用 CoInitializeEx 时 , 客 户 线 程 如 何 对 COM 识 别 其 模 型 , 但 是 , ActiveX 控 件 的 主 线 程 是 接 收 客 户 调 用 的 线 程 , 它 并 不 需 要 注 册 自 身 。 这是 很 有 意 义 的 , 因 为 毕 竟 我 们 讲 过 , 只 有 一 个 线 程 沿 着 程 序 逻 辑 流 从 客 户 到ActiveX 控 件 , 而 且 再 回 来 , 而 不 通 过 调 度 程 序 进 行 传 递 。 控 件 通 过 其 系 统 注 册表 中 的 条 目 来 来 识 别 其 线 程 模 型 , 这 些 条 目 可 能 是 apartment( 公 寓 ), free( 自 由 ) 或 bot h( 两 种 )。 如 果 这 个 条 目 不 存 在 , C O M 就 假 定 控 件 符 合 单 一 线 程 模 型 。 本章 中 以 后 的 项 目 演 示 了 ActiveX 控 件 如 何 在 注 册 表 中 指 定 其 线 程 模 型 。
学 完 了 线 程 模 型 这 些 难 以 理 解 的 内 容 后 ,我 们 至 少 可 以 处 理 一 种 非 常 重 要 的 问 题了 : ActiveX 控 件 如 何 提 前 知 道 客 户 使 用 的 哪 种 线 程 模 型 ? 答 案 相 当 简 单 : 它 并不 知 道 , 而 且 , 它 也 不 需 要 知 道 。 你 为 控 件 选 择 的 线 程 模 型 告 诉 COM 客 户 设 计用 来 处 理 的 客 户 线 程 的 种 类 , 而 不 需 要 进 行 调 度 。 COM 识 别 何 时 发 生 调 度 , 并且 , 只 有 在 发 生 任 何 调 度 时 , 才 透 明 地 设 置 调 度 。 例 如 , 实 例 化 控 件 的 STA 客户 线 程 标 记 apartmen t( 公 寓 ), 直 接 与 控 件 实 例 进 行 交 互 。 然 而 , 在 MTA 线 程实 例 化 控 件 时 , COM 必 须 调 度 所 有 的 调 用 , 以 保 证 客 户 和 控 件 在 相 同 的 线 程 上运 行 。 在 注 册 公 寓 线 程 模 型 时 , 控 件 已 经 通 知 COM , 每 个 实 例 只 可 以 适 应 单 一线 程 上 的 调 用 。 调 度 可 以 保 证 所 发 生 的 事 情 。 表 10-3 概 括 了 在 什 么 条 件 下 C O M 调 度 客 户 及 其 嵌 入 的 控 件 之 间 的 调 用 。
表 10-3 COM 在 客 户 和 控 件 之 间 调 度 的 条 件控 件
哪 种 线 程 模 型 最 适 于 控 件 取 决 于 你 提 前 使 用 的 客 户 应 用 程 序 的 种 类 ,以 及 你 要 承担 多 少 额 外 的 工 作 , 以 保 证 线 程 的 访 问 安 全 。 为 了 实 现 线 程 安 全 , 单 一 的 非 线 程模 型 根 本 不 需 要 额 外 的 代 码 ,因 为 只 有 一 个 客 户 线 程 可 以 直 接 访 问 控 件 的 所 有 实例 。 另 一 方 面 , 自 由 线 程 模 型 最 适 于 这 样 的 情 况 : 你 提 前 知 道 M TA 应 用 程 序 将独 占 使 用 控 件 。 然 而 , 很 少 能 够 得 到 这 样 的 保 证 , 除 非 是 你 自 己 编 写 客 户 程 序 。现 在 , 由 于 大 量 的 客 户 应 用 程 序 都 符 合 STA 线 程 , 所 以 , 对 于 设 计 用 来 服 务 于许 多 不 同 的 客 户 的 ActiveX 控 件 来 说 ,公 寓 模 型 通 常 是 最 佳 选 择 。Internet Explorer 和 Netscape Navigator 是 STA 应 用 程 序 , 使 用 MFC 的 包 容 器 程 序 也 是 , 另 外 , 还 有 使 用 Visual Basic 5 及 以 后 版 本 编 写 的 程 序 也 是 。M icrosoft Transaction Server 也 符 合 STA 的 规 则 , 所 以 , 控 件 如 果 支 持 MTS , 则 应 当 使 用 公 寓 模 型 。
线 程 安 全 性 是 在 公 寓 模 型 下 易 于 编 程 的 , 它 仅 需 在 安 全 保 护 下 不 同 时 写 静 态 数据 , 通 常 通 过 关 键 区 域 或 一 些 类 似 的 机 制 。如 果 你 要 确 保 快 速 访 问 控 件 而 不 管 客户 机 的 线 程 模 型 , 选 择 两 种 ( both ) 模 型 。 顺 从 STA 和 MTA 访 问 而 不 配 置 额 外工 作 ,特 别 是 运 行 在 多 线 程 下 的 控 件 。要 研 究 一 下 MTA 线 程 的 复 杂 性 ,细读 David Platt 在 M icrosoft Systems Journal 第 12 卷 第 8 页 的 文 章 即 可 。 在 目 录 标 签 的 期 刊条 目 下 的 M S D N 在 线 帮 助 中 可 找 到 此 文 章 。
示 例 1 : Pulse ActiveX 控 件
在 本 节 中 提 出 的 Pulse 控 件 说 明 了 如 何 使 用 ATL 来 生 成 简 单 的 ActiveX 控 件 。
Pulse 是 选 择 性 的 , 它 从 库 中 获 取 。 这 里 的 目 的 不 仅 仅 是 演 示 ATL , 还 要 给 出 减少 控 件 大 小 的 方 法 , 而 且 不 修 改 ATL 源 代 码 , 或 者 , 采 取 直 接 的 C O M 指令。通过 结 果 , 你 应 当 理 解 在 使 用 ATL 创 建 时 , 一 个 ActiveX 控 件 的 尺 寸 可 以 达 到 多小 。
Pulse 只 是 一 个 计 时 器 , 在 编 程 时 , 它 以 一 定 的 间 隔 来 启 动 事 件 , 这 样 , 它 的 作用 就 很 像 是 我 们 在 建 立 Hour 程 序 时 使 用 的 IETimer 控件。与 IETimer 一 样 的 是 , Pulse 是 完 全 自 给 自 足 的 ,它 既 不 使 用 MFC ,也 不 使 用 C 运 行 库 。但 是 ,在 37KB , Pulse 没 有 IETimer.ocx 的 一 半 大 小 , 而 且 还 提 供 相 同 的 服 务 。 Pulse 这 样 小 的 一个 主 要 原 因 是 , 如 果 不 显 示 窗 口 的 话 , 它 运 转 起 来 是 不 可 见 的 。
Pulse 控 件 包 含 一 个 有 名 为 CPulseCtl 的 类 控 制 的 单 一 目 标 。 除 了 实 现 表 10-1 中列 出 的 接 口 外 , 此 目 标 还 提 供 了 这 些 控 件 元 素 :
-
包 含 脉 冲 间 隔 的 适 当 的 变 量 , 以 毫 秒 为 单 位 。
-
允 许 包 容 器 启 动 和 结 束 事 件 启 动 的 方 法 。
-
每 次 间 隔 过 去 时 通 知 包 容 器 的 事 件 。
在 这 里 介 绍 了 10 步 , 来 演 示 Pulse 项 目 如 何 获 取 格 式 。 本 讨 论 采 用 常 规 的 方 法来 使 用 ATL 创 建 ActiveX 控 件 项 目 ,而 且 ,并 不 专 门 针 对 不 可 见 的 控 件 ,如 Pulse 。这 些 不 要 使 你 感 觉 到 我 们 在 这 里 所 做 的 事 情 有 多 么 困 难 。 使 用 ATL 创 建 简 单 的
ActiveX 控 件 非 常 简 单 。
步 骤 1 : 运 行 ATL COM AppW izard
使 用 ATL 创 建 的 ActiveX 控 件 项 目 开 始 总 是 使 用 ATL 的 向 导 , ATL COM AppWizard 。 单 击 File ( 文 件 ) 菜 单 上 的 New ( 新 建 ) 命 令 , 选 择 ATL COM AppWizard , 如 图 10-2 所 示 , 并 输 入 项 目 名 Pulse 。
图 10-2 启动 ATL COM AppWizard
在 出 现 一 个 对 话 框 列 出 控 件 选 项 时 ( 图 10-3 ), 单 击 Finish ( 完 成 ) 按 钮 , 来 接受 默 认 设 置 。 这 些 设 置 指 定 新 的 控 件 作 为 不 使 用 MFC 的 动 态 链 接 库 来 运 行 。 对于 标 签 为 Allow Merging Of Proxy/Stub Code( 允 许 代 理 和 存 根 代 码 的 合 并 ), 它只 对 DLL 服 务 器 类 型 可 用 。 选 择 这 个 选 项 告 诉 COM AppWizard , 来 设 置 项 目 来链 接 调 度 代 码 到 控 件 的 可 执 行 映 像 中 , 产 生 一 个 单 一 的 DLL 文 件 , 其 中 包 含 控件 及 其 代 理 /存 根 指 令 。清 除 选 项 告 诉 COM AppWizard 来 编 写 调 度 代 码 到 单 独 的名 为 DllData.c 文 件 , 这 样 , 可 以 减 少 最 终 可 执 行 文 件 的 整 个 大 小 。 在 本 练 习 的第 5 步 中 , 我 们 将 更 多 地 讨 论 控 件 的 代 理 和 存 根 代 码 。
图 10-3 在 COM AppWizard 中 选 择 一 个 项 目
C O M 和 ATL 都 引 用 进 程 中 的 ActiveX 控 件 作 为 动 态 链 接 库 。 在 第 9 章 中 , 我 们已 经 看 到 , ActiveX 确 实 是 一 个 动 态 链 接 库 , 但 是 , 具 有 特 殊 的 格 式 。 在 随 后 的步 骤 中 , 你 可 以 在 源 代 码 中 看 到 , 要 记 住 ,“ DLL ” 引 用 我 们 正 在 创 建 的 Pulse ActiveX 控 件 。
在 设 置 ActiveX 控 件 项 目 中 , COM AppWizard 生 成 了 几 个 源 文 件 , 其 中 包 含 必要 的 代 码 。 Pulse.cpp 文 件 实 现 D llM ain 函 数 , 在 第 一 次 加 载 库 时 , 操 作 系 统 便 调用 这 个 函 数 ,还 有 附 加 的 四 个 服 务 于 C O M 的 函 数 。由于 COM 不 提 供 这 些 函 数 , 所 以 , Pulse 这 样 的 进 程 内 ActiveX 控 件 就 必 须 自 己 导 出 它 们 :
-
D llGetClassObject: 在 客 户 第 一 次 请 求 C O M 在 内 存 中 实 例
化 控 件 目 标时,从 CoGetClassObject 调 用 。 此 函 数 通 过 创 建 类 工 厂( class factory ) 的 实 例 , 并 返 回 到 IClassFactory 或 IClassFactory 接 口 的 指 针 , 来 处 理这 个 调 用 。
-
DllCanUnloadNow : 在 客 户 使 用 控 件 服 务 器 完 成 时 ,
从CoFreeUnusedLibraries 中 调 用 。 此 函 数 通 知 调 用 程 序 是 否 ActiveX 控件 管 理 的 任 何 目 标 还 处 于 服 务 状 态 , 这 是 通 过 控 件 未 决 目 标 的 内 部 引用 计 数 来 指 出 的 。 如 果 对 于 所 有 的 接 口 这 个 计 数 值 为 0 , 则DllCanUnloadNow 函 数 便 返 回 S_OK , 以 允 许 COM 从 内 存 中 卸 载 控件。
-
DllRegisterServe r: 实 现 有 关 ActiveX 控 件 的 信 息 到 系 统 注
册 表 中 。 在控 件 的 注 册 表 数 据 中 最 重 要 的 数 据 是HKEY_CLASSES_ROOT\CLSID 下 的 项 , 它 指 定 控 件 的 可 执 行 文 件 的位 置 。 包 容 器 应 用 程 序 只 给 出 控 件 的 类 标 识 符 , C O M 扫 描 CLSID 文件 夹 , 来 定 位 控 件 , 这 样 , 它 就 可 以 将 它 加 载 到 内 存 中 。 此 安 排 允 许ActiveX 控 件 驻 留 在 硬 盘 或 网 络 上 的 任 何 地 方 , 与 普 通 的 DLL 文 件 通常 必 须 要 限 制 在 由 操 作 系 统 可 以 识 别 的 特 定 位 置 不 同 。
-
D llUnregisterServe r: 对 每 个 控 件 目 标 , 都 删 除 由 D
llRegisterServer 实现 的 注 册 表 数 据 。
这 四 个 函 数 是 简 短 的 包 装 器 ( wrapper ), 每 个 只 有 一 行 。 它 们 都 调 用到 一 个 名 为 _Moldule 的 目 标 中 , 这 便 是 ATL 的 CComModule 类 的 实例 。 它 是 _Module 目 标 , 为 函 数 提 供 主 要 的 实 现 。
COM AppWizard 提 供 完 成 的 控 件 ,具有 DLL 扩 展 名 ,而不是 OCX 扩 展名 , 它 不 提 供 选 择 文 件 扩 展 名 的 选 项 。 你 可 能 会 感 觉 到 DLL 文 件 太 多了 ,ActiveX 控 件 应 当 具 有 OCX 扩 展 名 ,以 允 许 用 户 推 断 出 文 件 的 目 的 。如 果 是 这 样 的 话 ,此 时 就 需 要 进 行 一 些 编 辑 工 作 ,来 更 改 文 件 的 扩 展 名 。暂 时 关 闭 项 目 的 工 作 控 件 , 在 文 本 编 辑 器 中 打 开 Pulse.dsp 文 件 , 并 使 用Replace 命 令 , 来 重 新 命 名 所 有 的“ .dll”为 “ .ocx ”。 然 后 重 新 打 开 项 目 , 并 在 Project Settings( 项 目 设 置 ) 对 话 框 的 Link ( 链 接 ) 选 项 卡 中 更 改
文 件 的 扩 展 名 。 如 果 控 件 在 M icrosoft Transaction Server 环 境 中 运 行 的话 , 就 不 要 这 样 做 。
COM AppWizard 创 建 一 个 项 目 梗 概 , 但 是 , 不 要 为 ActiveX 类 目 标 本 身添 加 源 代 码 。 在 运 行 COM AppWizard 后 , 下 一 步 是 为 目 标 的 类 实 现 代码 , 这 要 使 用 ATL 的 第 二 个 向 导 , 名 为 ATL Object Wizar d( ATL 目 标 向导 ) 。
步 骤 2 : 运 行 ATL Object W izard
ATL 的 Object Wizard 生 成 一 个 类 的 声 明 , 以 及 为 目 标 类 实 现 函 数 的 存根 , 使 它 与 MFC 的 ClassWizard 有 些 一 致 之 处 。 运 行 Object Wizard ,如图 10-4 所 示 , 方 法 是 从 Insert( 实 现 ) 菜 单 中 选 择 New ATL Object ( 新建 ATL 目 标 ) 命 令 , 或 者 是 在 ClassView 窗 格 中 右 击 项 目 名 , 并 从 上 下文 菜 单 中 选 择 这 个 命 令 。 WizardBar 菜 单 也 提 供 了 到 相 同 命 令 的 访 问 。
10-4 调用 ATL Wizard 支 持 的 常 用 方 法
对 于 17 种 不 同 的 目 标 类 型 ,Object Wizard 自 动 添 加 ATL 代 码 到 项 目 中 , 其 中 的 一 些 在 表 10-4 中 列 出 。在 开 发 ActiveX 控 件 项 目 时 ,通 常 选 择 Full Contro l( 完 全 控 制 ) 选 项 , 但 是 , 此 选 项 假 定 控 件 将 显 示 一 个 窗 口 , 并以 属 性 表 来 支 持 这 种 方 法 , 而 且 还 有 一 个 表 达 图 标 。 对 于 小 的 不 可 见 的控 件 。例 如 Pulse ,通 常 ,从 那 里 选 择 一 种 更 简 单 的 组 件 类 型 ,并 建 立 它 , 而 不 是 以 后 再 删 除 不 必 要 的 代 码 , 这 样 更 合 适 。Pulse 项 目 不 需 要 许 多 启动 程 序 所 使 用 的 代 码 , 所 以 , 在 向 导 对 话 框 的 左 窗 格 中 选 择 Objects ,并双 击 标 签 为 Simple Object( 简 单 目 标 ) 的 图 标 , 如 图 10-5 所 示 。
图 10-5 ATL Object Wizard 对话
表 10-4 ATL Object Wizard 支 持 的 常 用 目 标 类 型目 标 类 型 说 明
Simple Objec t( 简 单 目 标 ) 创 建 一 个 主 干 C O M 目 标 。此 接 口 支 持
必 须 手 工 添 加
Add-in Object( 附 加 目 标 ) 创 建 一 个 简 单 的 附 加 目 标 , 它 连 接 到
Developer Studio 的 IApplication 接口 。第 13 章 更 详 细 地 解 释 了 Developer Studio
Internet Explorer Object
( Internet Explorer 目 标 )
创 建 一 个 COM 目 标 , 它 支 持 Internet Explorer 所 期 望 的 接 口 ,但 是 ,没 有 用户 界 面 的 附 加 支 持
ActiveX Server Component
( ActiveX 服 务 器 组 件 )
为 OnStartPage 和 OnEndPage 添 加 支持 , 具 有 ASP 接 口 的 指 针 , 例 如Irequest, Iresponse 和 Iserver
M icrosoftTransactionServer
( M icrosoft 事 务 服 务 器 )
Component Registrar ( 组 件记 录 器 )
续 表
创 建 一 个 概 略 的 实 现 文 件 , 它 包 括 事务 服 务 器 所 需 要 的 M tx.h 头 文 件
通 过 ATL 的 Registrar 来 访 问 系 统 注 册表 , 通 过 Iregistrar 接 口 来 实 现
Lite Contro l( 真 正 的 控 件 ) 使 用 可 以 嵌 入 到 Internet Explorer 中的
用 户 界 面 来 创 建 一 个 控 件 , 但 是 , 它并 不 支 持 由 许 多 其 他 包 容 器 所 需 要 的接 口 。 提 供 指 针 到 客 户 的IOleInPlaceWindowless, IOleClientSite 和 Iadivise
Sink 接 口
Full Contro l( 完 全 控 件 ) 创 建 一 个 控 件 , 可 以 嵌 入 到 所 有 符 合
ActiveX 规 则
Composite Control( 混 合 控件 )
续 表
的 7 中 。 提 供 相 同 的 指 针 作 为 Lite Control 到 站 点 接 口
创 建 一 个 类 似 对 话 框 的 控 件 , 它 可 以包 含 其 他 ActiveX 控 件 和 常 规 控 件
Property Page( 属 性 页 面 ) 添 加 属 性 页 面 目 标 到 控 件 项 目 中 。 只
为 每 个 属 性 页 面 选 择 一 次 此 选 项
D ialog ( 对 话 ) 对 目 标 添 加 一 个 常 规 对 话 资 源
Provide r( 提 供 者 ) 创 建 一 个 目 标 , 来 执 行 OLE DB 提供
者 的 数 据 转 换 服 务
接 下 来 , Object Wizard 显示 Properties 对 话 ( 参 见 图 10-6 ), 它 查 询 控 件 的 特 性 。在 Names 选 项 卡 中 , 键 入 短 名 PulseCtl ,从中 , Object Wizard 使 用 适 当 的 条 目 填充 其 他 编 辑 框 。 Class 编 辑 框 包 含 CpulseCtl 类 的 名 称 , 它 可 以 实 现 控 件 的 目 标 。此 类 非 常 重 要 , 原 因 是 它 从 Pulse 控 件 支 持 的 所 有 接 口 继 承 而 来 。
图 10-6 为 ATL Object Wizard Properties 对 话 中 的 对 象 指 定 名 称
Object Wizard 编 写 CPulseCtl 类 源 代 码 到 指 出 的 H 和 CPP 文 件 。 CoClass 编 辑 框
中 容 纳 字 控 件 的 组 件 类 的 名 称 , 它 作 为 类 型 库 , 等 效 于 对 象 类 。 CPulseCtl 类 和PulseCtl 组 件 类 引 用 相 同 的 目 标 , 但 是 , 定 义 对 象 的 位 置 有 所 不 同 。 CPulseCtl 引 用 到 实 现 此 对 象 的 C++ 源 代 码 ,而 PulseCtl 组 件 类 引 用 到 此 对 象 在 控 件 的 类 型库 中 的 定 义 。 Interface 框 显 示 接 口 的 名 称 , 在 这 个 接 口 中 , 控 件 将 其 定 制 的 方 法和 属 性 展 示 出 来 。标有 Type and ProgID 的 编 辑 框 容 纳 字 符 串 ,它 描 述 了 CPulseCtl 对 象 , 及 其 编 程 的 标 识 符 。
如 图 10-6 所 示 ,一 些 名 称 作 为 系 统 注 册 表 中 的 条 目 ,由 Pulse 的 DllRegisterServer 函 数 放 置 在 那 里 。 例 如 , 编 程 的 标 识 符 PulseCtl.PulseCtl 和 PulseCtl.PulseCtl.1 分别 成 为 VersionIndependentProgID 和 ProgID 注 册 表 键 下 面 的 字 符 串 。在 这 两 个 标识 符 之 间 , 唯 一 的 不 同 是 , 在 此 情 况 下 , 后 者 包 含 控 件 的 版 本 号 1 。 人 们 可 以 识别 这 两 个 字 符 串 , 它 们 是 控 件 的 标 识 符 的 另 一 种 形 式 , 它 提 供 了 一 种 手 段 , 使 包容 器 应 用 程 序 可 以 请 求 控 件 对 象 , 而 不 必 使 用 CLSID 字 符 串 。 例 如 , MFC 的CWnd::CreateControl 函 数 按 如 下 的 方 法 接 收 编 程 的 标 识 符 :
CreateControl( “ PulseCtl.PulseCtl.1 ” ,NULL, 0 &rect,
pParentWnd,IDC_PULSECTL);
类 似 地 , Visual Basic 或 VBA 容 器 可 以 传 递 标 识 符 到 CreateObject 函 数 :
Dim PulseCtl As Object
PulseCtl = CreateObject ( “ PulseCtl.PulseCtl.1 ”)
客 户 仅 为 控 件 控 制 变 成 的 标 识 符 ,它 可 以 确 定 对 应 的 类 标 识 符 ,方 法 是 调 用 OLE
函 数 CLSIDFromProgID 。 函 数 ProgIDFromCLSID 执 行 相 反 的 转 换 。
图 10-7 ATL Object Wizard Properties 对 话 框
在 Attributes( 属 性 ) 选 项 卡 中 , 选 择 标 有 Support Connection Points ( 支 持 连 接
点 )的 复 选 框 ,如 图 10-7 所 示 。对 于 诸 如 启 动 事 件 的 Pulse 这 样 的 ActiveX 来 说 , 连 接 点 是 必 要 的 。此 选 项 卡 还 显 示 单 选 按 钮 ,来 确 定 控 件 的 线 程 模 式 、接 口 类 型 , 以 及 是 否 它 支 持 聚 合 特 征 。 我 们 已 经 查 看 了 ATL 支 持 的 线 程 模 型 , 但 是 , 双 接口 和 聚 合 特 征 也 应 当 详 细 说 明 。
- 双 接 口 : 在 第 8 章 中 , 我 们 已 经 看 到 , Microsoft 已 经 扩
展 了 OLE 的设 计 ,以 支 持 定 制 的 控 件 ,使 这 样 的 组 件 作 为 VBX 组 件 的 32 位 方 式 。然 而 , 由 于 这 种 方 式 传 递 函 数 参 数 , 所 以 , 版 本 4 之 前 的 Visual Basic 不 能 直 接 调 用 控 件 方 法 。 为 了 解 决 这 个 问 题 , M icrosoft 在 OLE 中 合并 了 Idispatch 接 口 , 以 提 供 必 要 的 链 接 。 现 在 , Idispatch 通 常 作 为 客户 脚 本 编 程 的 通 信 管 道 , 例 如 VBScript 和 JavaScrip t, 使 它 们 可 以 通过 Idispatch::Invoke 函 数 的 服 务 , 来 调 用 控 件 的 方 法 , 它 可 以 转 换 参数 , 并 返 回 值 到 客 户 的 本 机 数 据 类 型 , 或 者 , 返 回 客 户 本 机 数 据 类 型的 值 。尽 管 这 种 解 决 方 案 可 行 , 但 是 , 额 外 的 转 换 和 间 接 调 用 Invoke , 将 会 减 慢 包 容 器 与 控 件 之 间 的 交 互 。
对 于 Web 页 面 , 以 及 以 前 的 Visual Basic 客 户 , Idispatch 是 一 个 必 要的 折 衷 方 法 。 然 而 , 在 C++ 这 样 的 语 言 中 编 写 的 包 容 器 应 用 程 序 可 以直 接 访 问 控 件 的 方 法 , 其 方 法 是 通 过 指 针 进 行 调 用 。 Idispatch::Invoke 及 其 耗 时 的 转 换 都 不 再 需 要 了 。为 了 适 应 支 持 除 了 Variant 数 据 类 型 的客 户 , OLE 支 持 双 接 口 技 术 。 双 接 口 方 法 函 数 要 么 通 过Idispatch::Invoke 间 接 进 行 调 用 ,要 么 通 过 调 用 到 方 法 的 指 针 来 直 接 访
问 , 这 样 , 不 管 包 容 器 应 用 程 序 是 使 用 什 么 语 言 编 写 的 , 所 有 包 容 器应 用 程 序 都 可 以 得 到 适 当 的 服 务 。
- 聚 合 : 通 过 聚 合 , 另 一 个 对 象 可 以 在 其 调 用 程 序 中 出
现 , 以 具 有 Pulse 控 件 加 上 第 二 个 对 象 提 供 的 新 服 务 的 能 力 。 聚 合 对 象 通 常 叫 做 包 含 或外 部 对 象 , 它 嵌 入 Pulse 控 件 的 方 式 与 包 容 器 应 用 程 序 的 相 同 。 然 后 , 外 部 控 件 选 择 性 地 从 其 客 户 中 1QueryInterface 请求,为 IPulseCtl 接 口将 请 求 传 递 给 Pulse 。外 部 对 象 的 客 户 接 收 需 要 的 指 针 ,并 调 用 到 Pulse 中 , 它 不 知 道 现 在 是 Pulse , 而 不 是 聚 合 它 的 对 象 正 在 提 供 服 务 。
在 Obejct Wizard Properties 对 话 中 , 聚 合 选 择 不 确 定 控 件 是 否 聚 合 另 一个 对 象 , 是 否 控 件 本 身 是 聚 合 的 。
如 果 你 的 控 件 不 打 算 服 务 于 以 前 的 Visual Basic 应 用 程 序 , 或 者 , 在 Web 页 或Active Server Page 上 进 行 脚 本 编 程 , 在 此 对 话 框 中 选 择 Custom ( 定 制 ) 单 选 按钮 。 这 样 做 的 好 处 是 , 它 可 以 稍 微 减 少 最 终 控 件 的 大 小 , 因 为 每 个 实 现 的 接 口 都没 有 合 并 Idispatch 的 四 个 方 法 。 尽 管 具 有 客 户 接 口 的 控 件 可 以 在 H T M L 文 档 中出 现 , 但 是 , 它 并 不 能 通 过 脚 本 进 行 编 程 。 脚 本 解 释 器 必 须 在 控 件 对 象 上 找 到Idispatch , 或 者 , 它 不 能 访 问 此 控 件 。
对 于 Pulse 控 件 , 接 收 默 认 的 设 置 , 来 启 用 公 寓 模 型 线 程 , 并 添 加 双 接 口 支 持 , 但 是 , 没 有 聚 合 特 征 。 在 深 思 熟 虑 之 后 , 可 以 通 过 在 Aggregation ( 聚 合 ) 组 中选 择 N o 单 选 按 钮 , 来 使 Pulse 成 为 非 聚 合 的 。这 便 对 CPulseCtl 类 定 义 添 加 了 一
行 , 如 下 所 示 :
DECLARE_NOT_AGGREGATABLE(CPulseCtl)
尽 管 聚 合 通 常 被 认 为 是 ActiveX 控 件 的 一 个 有 用 的 特 性 ,但 是 支 持 聚 合 使 控 件 的执 行 空 间 增 加 了 大 约 2KB 。由 于 这 本 书 的 一 个 目 的 是 说 明 如 何 用 ATL 创 建 控 件 , 从 而 减 小 控 件 的 大 小 , 因 而 放 弃 聚 合 似 乎 是 一 种 合 理 的 折 衷 方 案 。
这 个 折 衷 方 案 并 不 像 表 面 上 看 起 来 的 那 么 严 重 ,因 为 聚 合 并 不 是 ActiveX 控 件 在使 用 另 外 一 种 技 术 时 所 必 须 使 用 的 唯 一 技 术 。任 何 原 因 都 不 能 阻 止 一 个 控 件 像 一个 客 户 一 样 动 作 , 而 且 可 以 嵌 入 在 Pulse 控 件 中 。 这 项 技 术 被 称 为 包 容 , 包 容 控件 提 供 了 运 行 Pulse 自 己 的 方 法 函 数 , 并 通 过 它 自 己 的 类 库 公 布 这 种 方 法 。 当 一个 客 户 应 用 程 序 要 求 某 项 Pulse 服 务 时 , 包 容 控 件 (我 们 称 之 为 Outer( 外 部 对象 )) 将 这 个 调 用 传 递 给 Pulse 。当 Pulse 启 动 这 个 事 件 后 , 控 件 回 到 Oute r( 外 部对 象 ) 处 理 程 序 函 数 , 它 将 通 过 启 动 它 的 客 户 应 用 程 序 来 传 递 事 件 。 当 使 用 聚 合时 , 客 户 应 用 程 序 并 不 知 道 另 一 个 控 件 正 在 提 供 这 项 服 务 。 但 与 聚 合 不 同 的 是 , 包 容 器 减 少 了 Pulse 和 客 户 应 用 程 序 之 间 的 通 信 , 因 为 Outer( 外 部 对 象 ) 控 件必 须 作 为 二 者 之 间 的 中 介 而 存 在 。
不 要 选 中 标 有 “ Free Threaded Marshaler( 自 由 线 程 调 度 器 )” 的 复 选 框 。 这 个 选项 将 把 自 由 线 程 调 度 器 这 个 对 象 加 入 到 项 目 中 , 自 由 线 程 调 度 器 说 明 如 下 。
自 由 线 程 调 度 器
在 ATL Object Wizard ( 对 象 向 导 ) 中 选 中 “ Free Threaded Marshale r( 自 由 线 程调 度 器 )”复 选 框 ,将 产 生 对 COM 的 CoCreateFreeThreadedMarshaler 函 数 的 调 用 。
Hresult FinalConstruct()
{
return CoCreateFreeThreadedMarshaler(
GetControllingUnknown(), &m_pUnkMarshaler.p);
}
这 个 函 数 将 创 建 一 个 称 为 自 由 线 程 调 度 器 的 对 象 ,它 将 与 控 件 聚 合 并 监 督 调 度 操作 。 它 的 主 要 目 的 是 , 当 一 个 包 容 器 应 用 程 序 使 用 嵌 入 到 使 用 两 个 线 程 模 型 的 某个 ActiveX 控 件 中 的 单 线 程 公 寓 模 型 时 , 来 提 高 性 能 。 为 了 理 解 自 由 线 程 调 度 器如 何 改 善 这 样 一 个 控 件 的 性 能 , 应 仔 细 研 究 一 个 典 型 的 运 行 两 个 STA 线 程 的 客户 , 在 这 样 的 客 户 中 , 线 程 B 必 须 调 用 线 程 A 所 拥 有 的 对 象 实 例 。 线 程 A 首 先调 用 CoMarshalInterThreadInterfaceInStream , 返 回 时 收 到 一 个 指 向 流 的 指 针 ( 流只 是 一 个 数 据 的 集 合 )。 线 程 A 将 流 指 针 传 递 给 线 程 B ,线程 B 利 用 流 指 针 调 用CoGetInterfaceAndReleaseStream , 收 到 一 个 指 向 代 表 理 想 实 例 的 代 理 的 指 针 , 线
程 B 现 在 可 以 安 全 地 调 用 代 理 , 来 对 对 象 接 口 的 方 法 进 行 访 问 , 即 使 实 例 在 不同 的 公 寓 中 创 立 。
如 果 一 个 同 时 使 用 两 个 线 程 模 型 的 ActiveX 控 件 可 以 安 全 地 处 理 从 任 何 公 寓 来 的直 接 调 用 ,甚 至 来 自 于 不 同 的 STA 公 寓 ,通 过 一 个 调 度 器 整 理 来 自 客 户 的 线 程 B 的 调 用 所 浪 费 的 时 间 是 不 必 要 的 , 因 为 客 户 可 以 直 接 正 确( 而 且 更 加 有 效 ) 地 从线 程 B 中 调 用 对 象 实 例 。 客 户 不 能 安 全 地 假 定 这 个 选 项 存 在 , 所 以 必 须 通 过CoGetInterfaceAndReleaseStream 应 用 一 个 代 理 。但 是 ,因 为 这 个 控 件 是 用 于 对 不同 STA 线 程 上 同 时 进 行 安 全 的 访 问 , 它 通 过 自 由 线 程 调 度 器 调 用CoCreateFreeThreadedMarshaler 以 进 行 它 自 身 的 用 户 调 度 。 这 个 对 象 就 像 一 个 存根 一 样 , 将 一 个 指 向 接 口 的 指 针 拷 贝 到 流 中 , 而 这 个 接 口 正 是 客 户 通 过CoGetInterfaceAndReleaseStream 所 申 请 的 。 结 果 就 是 线 程 B 得 到 了 它 的 接 口 指针 , 而 不 用 切 换 线 程 , 或 者 遍 历 C O M 调 度 器 的 路 径 。 客 户 无 法 了 解 这 个 差 别 , 除 非 因 为 调 用 实 例 是 直 接 访 问 方 法 , 而 不 是 通 过 代 理 码 进 行 ,使 得 调 用 实 例 更 快一 些 。 可 是 一 个 采 用 这 项 技 术 的 控 件 必 须 确 保 对 象 可 以 处 理 同 时 调 用 。
单 击 O K 按 钮 可 以 关 闭 ATL Object Wizard ( ATL 对 象 向 导 ) 对 话 框 , 此 时 向 导将 产 生 三 个 文 件 :
-
PulseCtl.h 和 PulseCtl.cpp :新 CpulseCtl 类 的 定 义 和 执 行 码 。
-
PulseCtl.rgs: 包 括 控 件 的 注 册 信 息 脚 本 的 文 本 文 件 。
RGS 文 件 中 的 注册 脚 本 是 包 含 在 控 件 执 行 文 件 资 源 数 据 中 的 一 部 分 , DllRegisterServer
函 数 将 其 读 出 , 并 安 装 在 Registr y( 注 册 表 ) 中 。 Object Wizard( 对 象向 导 ) 在 项 目 的 RC 文 件 中 加 入 一 行 , 来 引 用 注 册 脚 本 :
IDR_PULSECTL REGISTRY DISCARDABLE “PulseCtl.rgs”
并 在 Resource.h 中 为 IDR_PULSECTL 常 量 插 入 一 个 #define 语 句 。
Object Wizar d( 对 象 向 导 )也 在 StdAfx.cpp 和 StdAfx.h 文 件 中 插 入 了 几 个 #include 语 句 。 这 些 语 句 将 带 给 项 目 所 必 须 的 ATL 源 文 件 , 例 如 AtlImpl.cpp , AtlCtl.cpp 和 AtlWin.cpp 。尽管 StdAfx 文 件 的 名 字 看 起 来 与 MFC 项 目 类 似 , 它 们 只 在 选 择了 步 骤 1 中 的 MFC 选 项 后 才 引 用 MFC 头 文 件 。
对 于 加 入 到 控 件 中 的 每 一 个 对 象 , Object Wizard( 对 象 向 导 ) 在 控 件 对 象 映 射 中加 入 一 个 入 口 。 Pulse 仅 包 含 一 个 CPulseCtl 对 象 , 所 以 在 向 导 结 束 后 , Pulse.cpp 文 件 中 的 对 象 映 射 看 起 来 是 这 样 的 :
BEGIN_OBJECT_MAP(ObjectMap) OBJECT_ENTRY(CLSID_PulseCtl,CpulseCtl)
END_OBJECT_MAP()
最 后 , ATL Object Wizar d( 对 象 向 导 ) 对 项 目 的 IDL 文 件 进 行 必 要 的 修 改 , 控 件
的 类 库 就 是 从 IDL 文 件 中 产 生 的 。 IDL 代 表 接 口 描 述 语 言 , 而 IDL 文 件 作 为M icrosoft IDL 编 译 工 具 MIDL 的 输 入 。 当 增 加 Pulse 的 事 件 函 数 时 , 就 能 看 见 这个 项 目 的 IDL 文 件 的 详 细 资 料 。
步 骤 3 : 加 入 nInterva l属 性
Pulse 有 唯 一 的 一 个 用 户 属 性 , 称 为 nInterval , 它 包 括 以 毫 秒 为 单 位 的 控 件 启 动事 件 的 速 度 。 为 了 在 接 口 上 加 入 这 个 属 性 , 在 ClassView 窗 口 中 展 开 Pulse 类 列表 ,用鼠标右键单击 IPulseCtl 的 入 口 , 然 后 选 择 Add Propert y( 加 入 属 性 )命 令 :
这 将 激 活 Add Property To Interface ( 为 接 口 加 入 属 性 ) 对 话 框 , 如 图 10-8 所 示 , 它 与 第 9 章 中 所 遇 到 的 类 向 导 的 Add Property ( 加 入 属 性 ) 对 话 框 所 要 求 的 信 息是 一 样 的 。
图 10-8 对 Pulse ActiveX 控件增加 nInterval 用户属性
键 入 nInterval 作 为 Pulse 属 性 的 名 字 , 给 定 它 的 属 性 类 型 为 Long ( 这 个 属 性 为short 时 将 简 单 一 些 , 但 是 , 那 将 限 制 最 大 时 间 间 隔 为 一 分 钟 多 一 点 )。 属 性 的 获取 /放 置 方 法 的 默 认 IDL 类 型 出 现 在 对 话 框 的 底 部 :
[propget, … ]
HRESULT nInterval ( [out, retval ] long *pVal); [propput, … ]
HRESULT nInterval ([in] long newVal);
C O M 的 获 取 /放 置 方 法 与 MFC 的 获 取 /设 置 函 数 是 低 级 等 价 的 , 它 为 包 容 器 提 供了 读 写 控 件 属 性 数 据 的 方 法 。 当 MIDL 编 译 器 编 译 项 目 的 IDL 文 件 时 , 它 将 通过 在 属 性 名 字 前 加 入 get_ 和 put_ 来 定 义 接 口 的 两 个 方 法 。 获 取 函 数 的 pVal 参 数指 向 属 性 的 当 前 值 ; 而 放 置 函 数 的 newVal 参 数 包 含 了 一 个 取 代 旧 属 性 值 的 新 属性 值 。 如 果 控 件 的 获 取 / 放 置 方 法 需 要 一 个 更 大 的 扩 展 参 数 列 表 , 在 对 话 框 参 数框 中 增 加 变 量 数 。 对 话 框 将 任 何 增 加 的 变 量 插 入 到 pVal 和 newVal 参数之前,并使 它 们 可 以 被 获 取 和 放 置 方 法 公 用 。 如 果 不 想 让 两 个 函 数 具 有 相 同 的 列 表 , 必 须在 稍 后 用 文 本 编 辑 器 手 工 编 辑 IDL 文 件 。 可 以 自 由 改 变 ID L 文 件 中 参 数 的 顺 序或 者 名 字 , 但 是 , 在 参 数 列 表 的 最 后 , 必 须 留 下 pVal 和 newVal 参 数 , 以 及 它 们的 [out,retval] 和 [in]属 性 。
Put Function ( 放 置 函 数 ) 复 选 框 与 两 个 标 有 PropPut 和 ProPutRef 的 单 选 钮 是 同时 出 现 的 。 PropPutRef 与 默 认 的 PropPut 选 项 是 类 似 的 , 除 了 它 告 诉 包 容 器 , 属性 的 放 置 函 数 靠 引 用 而 不 是 靠 值 来 得 到 newVal 参 数 。 在 这 种 情 况 下 , 包 容 器 将通 过 调 用 IDispatch::Invoke 设 置 属 性 , 并 用 DISPATCH_PROPERTYPUTREF 标志 取 代 DISPATCH_PROPERTYPU T。例如对于 VB 的用户,用 set 关 键 字 来 表 示一 个 属 性 通 过 引 用 而 不 是 通 过 值 进 行 赋 值 :
Set PulseCtl.nInterval=x
清 除 图 10-8 中 的 Put Function( 放 置 函 数 ) 复 选 框 , 目 的 是 防 止 MIDL 编 译 器 为nIterval 属 性 产 生 一 个 放 置 方 法 入 口 。 不 能 允 许 一 个 包 容 器 改 变 nInterval 属 性 , 清 除 对 话 框 内 的 Put Function ( 放 置 函 数 ) 复 选 框 将 使 变 量 是 只 读 的 。 控 件 的 另一 种 方 法 为 客 户 提 供 了 一 个 更 加 符 合 逻 辑 的 设 置 nInterval 属 性 的 方 法 。
对 话 框 的 Attributes ( 属 性 ) 按 钮 打 开 了 一 个 对 话 框 , 其 中 可 以 选 择 属 性 特 征 , 例 如 调 度 标 识 符 , 一 个 可 选 的 描 述 和 特 殊 标 志 。 所 选 择 的 属 性 将 出 现 在 IDL 文件 中 , 并 最 终 出 现 在 类 型 库 中 , 在 那 里 , 它 们 向 将 来 使 用 的 包 容 器 描 述 属 性 。 单击 图 10-9 中 的 组 合 框 , 将 显 示 出 一 张 可 用 的 属 性 设 置 列 表 , 如 表 10-5 所 示 。 要牢 记 , 属 性 的 确 定 要 引 用 属 性 的 获 取 / 放 置 函 数 , 而 不 要 引 用 属 性 变 量 自 身 。 在C O M 中 , 字 的 属 性 通 常 被 用 作 获 取 /放 置 方 法 的 速 记 形 式 , 它 可 以 让 一 个 客 户 访问 属 性 。
图 10-9 在 Edit Attributes ( 编 辑 属 性 ) 对 话 框 中 选 择 属 性
属 性 描 述
表 10-5 属 性
id 为属性的获取或者放置方法指定一个调度标识符 ( DISPID )
helpstring 指 定 一 个 短 文 本 串 , 用 以 描 述 属 性 。 包 容 器 可 以 通 过 控 件 的ITypeInfo::GetDocumentation 方法提取这个串 。尽管这个帮助文本作为控件类信息的一部分储存在控件内部,在 Edit Attribute (编辑属性 )对话框内删除 helpstring 入口,并不能减小控件的执行映像的大小
续表
bindable 用组合数据使属性与数据库中的特定域联系在一起。它的意思是,任何时候当属性值改变时,控件通知数据库,并要求所联系的记录域进行放置,以反映新的值。为得到关于这个非常有用的概念的进一步信息,请参考标题为 “ ActiveX Controls: Using Data Binding in an ActiveX Contro l( ActiveX 控件:在一个 ActiveX 控件中使用组合数据 )”的 MSDN 文章。可以用复选框设置 Search Titles Only (仅按标题搜索)在 MSDN 的 Search (搜索)选项卡中搜索这篇文章
续表
call_as 使客户可以用不同的名字访问属性的获取 / 放置函数 。这对于有大量的 “ nonremotable (非远程 )”参数类型的函数是非常有用的, 例如 int 或者 void 。一个非远程的变量对于不同机器上的操作系统来说并不是完全相同的。例如,因为 COM 并不保证每一台机器
(或者每一个客户 )为变量分配相同的空间 ,所以一个 int 值并不是远程的。相反,由于每一台机器认为 short 和 long 变量占据 2 和 4 个字节 ,所以它们是远程的 。由于这个原因 ,在 Object Wizard
(对象向导 )中的变量类型列表包括 short 和 long 类型,而没有
int,这对于 COM 是非常不明确的。
单个非远程参数也可以通过 represent_as 和 transmit_as 属性进行规定。当时对于使用几个非远程类型变量的获取 /放置函数来说, call_as 比 represent_as 和 transmit_as 更为方便和有效。对属性函数规定一个 call_as 属性 ,意味着控件可以一步进行所有必要的转换,而不是用几步完成,每一步进行一个变量的转换。
用非远程函数参数,对于像 ActiveX 控件这样的进程内
续 表
服务器的效率是非常低的 ,应为它强迫通过代理 /存根代码对调用call_as 函数进行调度。必须为处理非远程参数的包容器的代理DLL 提
供一个对话例程,而且为控件中的存根提供另一个例程,以接受调用。在一个 ActiveX 控件中的所有函数参数全部使用远程类型,将确保从包容器可以不经过调度而直接访问控件
defaultbind 指明可绑定的最代表控件对象的属性。只有控件的一个属性可以具有 defaultbind 标志 ,而且它必须具有 bindable 标志 。Defaultbind 标志允许一个包容器绑定在整个控件对象上,而不是单个属性
defaulcollelem 允许用 Visual Basic for Applications 编写的客户直接访问属性的获取 /放置函数
displaybind 指示包容器绑定的属性必须显示给用户。这个属性同样必须具有
bindable 标志
续 表
helpcontext 规定一个 32 位数字 ,以识别控件的属于某个属性的帮助文件中的信息
hidden 要求包容器不向用户显示属性
immediatebind 要求在放置属性后,立即通知数据库,而不是等到控件失去输入焦点之后。这个属性同样必须具有 bindable 标志
local 规定 MIDL 编译器必须仅仅产生接口头文件,而不产生存根代码。 local 标志与类似 Pulse 这样的过程内 ActiveX 控件没有关系
nonbrowsable 要求包容器不要包含包容器属性浏览器中的属性
续表
requestedit 表示在改变属性值之前,控件将向包容器查询是否允许改变。通过 IpropertyNotifySink::OnRequestEdit 函数可以申请许可,这个函数将通知包容器属性将被改变,而且对象正在申请允许,以继续进行。从 OnRequestEdit 的返回值 S_FALSE 将拒绝这个申请;而返回值 S_OK 将允许改变属性值。在收到 S_OK 后,如果属性也具 有 bindable 标 志 , 则 控 件 必 须 调 用 IPropertyNotifySink::On Changed
restricted 规定属性的获取 /放置方法不能从宏中调用
source 说明获取 /放置函数返回一个对象或者 Variant 作为某事件源的变化 。source 很少用在属性中 ,但是我们将在 Pulse 的接口列表中使用它
vararg 说明这个属性的获取 / 放置方法可以接收数目不同的参数 。这个方法的最后一个参数必须是一个可靠的 Variant 类型数组 ,其中包含了每一个未规定的参数的默认值
步 骤 4 : 加 入 方 法
关 闭 Add Property To Interfac e( 为 接 口 加 入 属 性 ) 对 话 框 , 将 在 IDL 文 件 中 写 入属 性 信 息 。 下 一 步 是 加 入 三 个 名 为 StartPulse, EndPulse 和 _OnTimer 的 方 法 。 通过 调 用 StartPulse 方 法 , 包 容 器 告 诉 Pulse 开 始 节 拍 性 的 事 件 启 动 过 程 。 这 个 函数 参 数 以 毫 秒 为 单 位 , 规 定 了 包 容 器 接 收 事 件 通 知 的 事 件 间 隔 。在 控 件 停 止 激 活后 ,包 容 器 调 用 EndPulse 停 止 事 件 启 动 过 程 ,这 样 将 不 占 用 CPU 时 间 。_OnTimer 函 数 由 CTimer 类 使 用 , 在 步 骤 6 中 进 行 说 明 。 对 于 每 一 种 方 法 , 像 以 前 一 样 用鼠 标 右 键 单 击 ClassView 窗 格 中 的 IPulseCtl 接 口 ,但 这 一 次 选 择 Add Metho d( 加入 方 法 ) 命 令 :
StartPulse 的 nRate 参 数 是 nInterval 属 性 的 新 值 。 这 解 释 了 在 前 面 的 步 骤 中 , 我们 决 定 一 个 客 户 不 需 要 访 问 一 个 put_nInterval 函 数 , 因 为 调 用 StartPulse 可 以 达到 同 样 的 目 的 。 在 图 10-10 中 所 示 的 Add Method To Interfac e( 为 接 口 加 入 方 法 ) 对 话 框 中 键 入 函 数 和 参 数 名 称 , 然 后 单 击 O K 关 闭 对 话 框 。 重 复 以 上 步 骤 , 以 加入 EndPulse 和 _OnTimer 方 法 。EndPulse 和 _OnTimer 都 没 有 参 数 ,所 以 使 参 数 框空 着 。关 闭 Add Method To Interfac e( 为 接 口 加 入 方 法 ) 对 话 框 , 此 时 Visual C++ 将 把 对 应 3 种 方 法 的 适 当 代 码 加 入 到 Pulse.idl 文 件 中 。它 还 将 为 方 法 写 出 存 根 函数 , 加 入 到 Pulse.cpp 文 件 中 , 我 们 将 在 为 控 件 加 入 一 个 事 件 函 数 后 , 对 其 进 行编 辑 。
图 10-10 Add Method to Interface 对 话 框
步 骤 5 : 加 入 Pulse 事 件
在 我 们 开 始 编 码 之 前 , 为 项 目 添 加 的 最 后 一 个 部 分 是 Pulse 事 件 , 它 将 在 每 一 个时 间 间 隔 到 来 时 启 动 。 在 ATL 以 前 的 版 本 中 , 在 控 件 中 增 加 一 个 事 件 需 要 一 点手 动 操 作 , 包 括 产 生 一 个 事 件 接 口 的 GUID 标 识 符 , 编 辑 IDL 文 件 和 运 行 一 个称 为 ATL Proxy Generator 的 工 具 。 但 随 着 库 的 第 三 版 的 发 布 , 这 个 过 程 变 得 更加 简 单 和 友 好 。 现 在 为 控 件 增 加 事 件 和 增 加 方 法 和 属 性 一 样 容 易 , 仅 仅 需 要 一 点时 间 对 项 目 的 IDL 文 件 进 行 编 译 。
在 我 们 将 讨 论 发 生 在 屏 幕 之 后 的 事 情 之 后 ,下 面 给 出 了 产 生 新 事 件 代 码 过 程 中 所需 要 的 步 骤 列 表 。 图 10-11 说 明 了 4 个 步 骤 。
-
用 鼠 标 右 键 单 击 ClassView 窗 格 中 的 _IPulseCtlEvents 入 口
, 并 从 菜 单中 选 择 Add Method ( 加 入 方 法 )命 令 。在 Add Method To Interfac e( 为接 口 加 入 方 法 ) 对 话 框 中 , 选 择 void 返 回 类 型 , 将 事 件 函 数 命 名 为Pulse , 然 后 单 击 OK 关 闭 对 话 框 。
-
找 到 Workspace 窗 口 中 的 FileView 窗 格 , 用 鼠 标 右 键 单 击
Pulse.idl 入口 , 然 后 选 择 Compile ( 编 译 ) Pulse.idl 命 令 。 这 将 启 动 M IDL 编 译器 , 它 将 产 生 类 型 库 文 件 Pulse.tlb , 并 加 入 到 项 目 中 。
-
当 MIDL 编 译 器 结 束 工 作 后 , 切 换 回 ClassView 窗 格 , 然 后
用 鼠 标 右
键 单 击 CPulseCtl 入 口 。 从 菜 单 中 选 择 Implement Connection Poin t( 实现 连 接 点 ) 命 令 , 以 显 示 同 名 字 的 对 话 框 。
- 设 置 标 有 _IpulseCtlEvents 的 复 选 框 , 并 单 击 O K 关 闭 对
话 框 。
项 目 的 IDL 文 件 底 部 一 半 的 地 方 列 出 了 名 为 PULSELib 的 library 块 , 它 将 向M IDL 编 译 器 描 述 新 Pulse 事 件 。 当 为 一 个 控 件 的 方 法 和 属 性 搜 索 类 型 库 时 , 某些 包 容 器 应 用 程 序 仅 仅 读 出 library 块 , 所 以 通 常 要 小 心 地 编 辑 IDL 文 件 , 以 移动 最 前 的 两 个 缩 进 代 码 块 到 library 块 。 第 一 个 块 由 方 括 号 [] 组 成 , 包 括 IPulseCtl 接 口 的 标 志 ;
图 10-11 为 ATL ActivX 项目加入一个事件
第 二 个 块 用 花 括 号 括 起 来 , 包 含 有 接 口 的 方 法 列 表 。 列 表 10-1 表 示 了 在 重 新 安排 代 码 之 后 应 该 有 的 结 果 。 如 果 按 照 这 些 步 骤 建 立 Pulse 项 目 , 你 自 己 的 ID L 文件 的 GUID 数 目 与 列 表 中 所 显 示 的 将 不 会 相 同 。
importlib 指 令 从 OLE 类 型 库 StdOle32.tlb 和 StdOle2.tlb 中 带 入 预 编 译 类 型 信 息 , 这 两 个 类 型 库 通 常 位 于 Windows 系 统 或 者 System32 目 录 中 。 尽 管 并 非 必 要 , 接口 名 字 _IPulseCtlEvents 仍 然 以 下 划 线 开 始 。 这 个 约 定 将 通 知 接 口 浏 览 器 ,
_IPulseCtlEvents 接 口 属 于 控 件 , 而 且 浏 览 器 不 能 将 接 口 显 示 给 用 户 。
列表 10-1 改正后的 Pulse.idl 文件
[
uuid(3B365F9D-C3AE-11D1-BEC9-E0F4E352507A),
version(1.0),
helpstring("Pulse 1.0 Type Library")
]
library PULSELib
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");
// **********************************************************
// ...to here:
[
object,
uuid(3B365FA9-C3AE-11D1-BEC9-E0F4E352507A),
dual,
helpstring("IPulseCtl Interface"),
pointer_default(unique)
]
interface IPulseCtl : IDispatch
{
[propget, id(1), helpstring("property nInterval")] HRESULT nInterval([out, retval] long
*pVal);
[id(2), helpstring("method StartPulse")] HRESULT StartPulse(long nRate);
[id(3), helpstring("method EndPulse")] HRESULT EndPulse();
[id(4), helpstring("method _OnTimer")] HRESULT _OnTimer();
};
// **********************************************************
[
uuid(3B365FAA-C3AE-11D1-BEC9-E0F4E352507A),
helpstring("_IPulseCtlEvents Interface")
]
dispinterface _IPulseCtlEvents
{
properties:
methods:
[id(1), helpstring("method Pulse")] void Pulse();
};
[
uuid(8C9BABDD-BCE5-11D1-BEC9-D43CA8CB2F51),
helpstring("PulseCtl Class")
]
coclass PulseCtl
{
[default] interface IPulseCtl;
然 而 , 并 不 是 所 有 的 浏 览 器 都 遵 守 这 个 约 定 。
[default] interface IPulseCtrl;
[default, source] dispinterface _IPulseCtlEvents;
下 面 这 几 行 规 定 IPulseCtl 为 控 件 的 默 认 调 度 接 口 , 而 _IPulseCtlEvents 作 为 控 件的 默 认 源 接 口 , 通 过 这 些 包 容 器 接 收 控 件 的 事 件 通 知 信 息 。source 告 诉 M ID L 编译 器 , 包 容 器 而 不 是 控 件 , 将 为 _IPulseCtlEvents 提 供 IDispatch 实 现 。 这 个 控 件是 _IPulseCtlEvents 接 口 中 调 用 的 源 , 而 且 包 容 器 的 事 件 IDispatch 是 事 件 的 接 收器 。
下 面 的 这 一 行 通 过 内 部 名 字 规 定 了 控 件 的 事 件 , 而 且 将 事 件 与 唯 一 的 由 id 关 键字 规 定 的 调 度 标 识 符 值 联 系 在 一 起 :
[id(1), helpstring("method pulse")] void Pulse();
这 个 控 件 并 不 直 接 调 用 Pulse 函 数 来 启 动 事 件 , 而 是 调 用 一 个 名 为 Fire_Pulse 的由 ATL 加 入 到 项 目 中 的 封 装 函 数 。 这 个 封 装 函 数 称 为 代 理 , 依 次 调 用 包 容 器 的IDispatch::Invoke 方 法 , 提 供 调 度 标 识 符 号 码 , 以 识 别 启 动 的 事 件 。 以 上 就 是 一个 代 理 如 何 得 到 它 的 名 字 的 过 程 。 Fire_Pulse 函 数 作 为 包 容 器 事 件 处 理 程 序 函 数的 替 身 或 者 代 理 , 当 启 动 一 个 事 件 时 , 作 为 控 件 调 用 的 现 场 , 而 不 用 担 心 调 用 最终 如 何 到 达 客 户 的 细 节 。 代 理 函 数 是 单 一 代 理 类 的 成 员 , 单 一 代 理 类 作 为 导 出CPulseCtl 的 基 类 , 允 许 控 件 在 CPulseCtl 实 现 的 任 何 地 方 启 动 事 件 。 代 理 类 的 角色 与 C O M 所 设 置 的 用 于 调 度 线 程 间 调 用 的 代 理 对 象 是 不 同 的 。 类 和 调 度 对 象 通常 被 简 称 为 “ proxy ”, 所 以 有 时 候 很 容 易 混 淆 它 们 。 但 它 们 不 是 同 一 件 事 。
在 执 行 Implement Connection Poin t( 实 现 连 接 点 ) 命 令 时 , 我 们 在 项 目 内 增 加 了IConnectionPoint 接 口 的 实 现 , 通 过 这 个 接 口 包 容 器 可 以 决 定 ActiveX 控 件 支 持哪 一 个 连 接 点 。 这 个 命 令 检 查 控 件 的 类 型 库 文 件 , 从 中 提 取 接 口 名 字
_IPulseCtlEvents 和 IPulseCtl 以 创 立 代 理 类 。 一 个 ActiveX 控 件 通 常 在 控 件 的 可执 行 文 件 中 作 为 原 始 数 据 包 含 它 自 己 的 类 型 库 , 并 允 许 类 型 浏 览 器 如ClassWizard 来 访 问 数 据 。 然 而 , 在 控 件 的 开 发 阶 段 , 类 型 库 以 TLB 文 件 的 形 式独 立 存 在 。
ATL 的 IConnectionPointImpl 模 板 为 新 CProxy_IPulseCtlEvents 代 理 类 定 义 了 一 个基 准 。 这 个 类 的 代 码 驻 留 在 PulseCP.h 文 件 中 , 以 子 函 数 的 形 式 包 含 了 Pulse 的单 个 事 件 代 理 :
template <class T>
class CProxy_IPulseCtlEvents : public IConnectionPointImpl<T, &DIID__IPulseCtlEvents,
CComDynamicUnkArray>
{
//Warning this class may be recreated by the wizard. public:
VOID Fire_Pulse()
{
T* pT = static_cast<T*>(this); int nConnectionIndex;
int nConnections = m_vec.GetSize();
for (nConnectionIndex = 0; nConnectionIndex < nConnections; nConnectionIndex++)
{
pT->Lock();
CComPtr<IUnknown> sp = m_vec.GetAt(nConnectionIndex); pT->Unlock();
IDispatch* pDispatch = reinterpret_cast<IDispatch*>(sp.p); if (pDispatch != NULL)
{
DISPPARAMS disp = { NULL, NULL, 0, 0 };
pDispatch->Invoke(0x1, IID_NULL, LOCALE_USER_DEFAULT,
DISPATCH_METHOD, &disp, NULL, NULL, NULL);
}
}
}
};
注 意 , Fire_Pulse 代 理 函 数 的 类 型 是 void 。 事 件 与 方 法 不 同 , 它 从 不 返 回 值 。
通 过 它 的 基 类 , CProxy_IPulseCtlEvents 为 由 第 二 个 模 板 参 数 识 别 的 接 口 实 现 了连 接 点 。 在 我 们 的 例 子 中 , 接 口 是 _IPulseCtlEvents , 而 它 的 调 度 接 口 标 识 符 是DIID_IPulseCtlEvents , 由 Pulse_i.c 文 件 进 行 定 义 。 第 三 个 模 板 参 数 规 定 了 处 理连 接 的 ATL 类 。这 个 默 认 的 CComDynamicUnkArray 类 允 许 的 连 接 数 目 不 限 ;而可 选 的 CComUnkArray 类 只 允 许 数 目 固 定 的 连 接 。
步 骤 6 : 加 入 CTimer 类
Pulse 可 以 捕 获 WM_TIMER 消 息 来 激 活 它 的 事 件 启 动 过 程 , 但 是 那 样 将 需 要 创
立 一 个 窗 口 来 接 收 消 息 。 窗 口 将 消 耗 系 统 资 源 , 需 要 时 间 来 建 立 和 关 闭 , 而 且 , 向 Pulse 这 样 的 流 型 ActiveX 控 件 应 尽 可 能 避 免 创 立 窗 口 。幸 运 的 是 ,Visual C++ 中 所 包 含 的 AtlButton 例 子 项 目 包 含 了 一 个 简 单 的 安 全 线 程 CTimer 类 ,这 个 类 正是 我 们 所 需 要 的 。 由 于 代 码 不 是 由 ATL 向 导 产 生 的 , 列 表 10-2 中 列 出 了 它 的 全部 内 容 。
列表 10-2 Timer.h 文件
HRESULT TimerOn( DWORD dwTimerInterval ) // Arm the timer
{
Derived* pDerived = ((Derived*) this);
m_dwTimerInterval = dwTimerInterval;
if (m_bTimerOn) // If already on, just change interval
return S_OK;
m_bTimerOn= TRUE;
m_dwTimerInterval = dwTimerInterval;
m_pStream = NULL;
HRESULT hRes = CoMarshalInterThreadInterfaceInStream(
*piid, (T*)pDerived, &m_pStream );
// Create thread and pass the thread proc the this ptr
m_hThread = CreateThread(NULL, 0, &_Apartment,
(PVOID) this, 0, &m_dwThreadID);
return S_OK;
}
void TimerOff() // Disable the timer
{
if (m_bTimerOn)
{
m_bTimerOn = FALSE;
AtlWaitWithMessageLoop( m_hThread );
}
}
// Implementation
private:
static DWORD WINAPI _Apartment( PVOID pv )
{
CTimer<Derived, T, piid>* pThis = (CTimer<Derived, T, piid>*) pv;
pThis->Apartment();
return 0;
}
D W O R D Apartment()
{
CoInitialize(NULL);
HRESULT hRes;
m_spT.Release();
if (m_pStream)
hRes = CoGetInterfaceAndReleaseStream(
m_pStream, *piid, (PVOID*) &m_spT );
// Main timer loop that periodically calls _OnTimer
while(m_bTimerOn)
{
Sleep( m_dwTimerInterval );
if (!m_bTimerOn)
break;
m_spT->_OnTimer();
}
m_spT.Release(); // When TimerOff function sets
CoUninitialize(); // m_bTimerOn = FALSE, unregister
return 0; // and quit
}
public:
DWORD m_dwTimerInterval;
BOOL m_bTimerOn;
private:
HANDLE m_hThread;
DWORD m_dwThreadID;
LPSTREAM m_pStream;
CComPtr<T> m_spT;
} ;
通 过 列 表 , 可 以 看 出 CTimer 分 为 几 步 , 其 原 因 还 不 是 立 即 就 可 以 看 出 来 。 类 通过 它 的 TimerOn 函 数 封 装 了 计 时 器 , 这 个 函 数 将 创 立 一 个 新 线 程 , 以 调 用 Sleep 函 数 , 以 在 需 要 时 进 入 休 眠 。 当 线 程 被 唤 醒 时 , 它 调 用 我 们 在 步 骤 4 中 加 入 的
_OnTimer 方 法 , 以 表 明 间 隔 已 经 结 束 。 线 程 然 后 再 次 回 到 休 眠 , 这 个 过 程 在 一个 循 环 中 不 断 重 复 , 直 至 TimerOff 函 数 被 调 用 。 这 个 类 是 非 常 有 趣 的 , 因 为 它执 行 自 己 的 线 程 之 间 的 用 户 调 度 , 确 保 _OnTimer 函 数 不 是 从 新 线 程 受 到 调 用 , 而 是 从 启 动 计 时 器 的 原 始 线 程 受 到 调 用 。 在 我 们 的 例 子 中 , 这 是 Pulse 控 件 的 主线 程 ,从 这 里 _OnTimer 可 以 安 全 的 启 动 Pulse 事 件 。调 度 回 到 主 线 程 对 于 像 Pulse 一 样 的 公 寓 线 程 控 件 是 必 要 的 一 步 , 因 为 COM 的 规 则 指 出 一 个 对 象 必 须 在 客 户的 公 寓 内 启 动 事 件 。 也 就 是 说 , 在 与 使 对 象 实 例 化 的 同 一 个 客 户 STA 线 程 上 。
如 果 作 为 新 项 目 创 立 Pulse ,简 单 地 从 公 司 CD 上 拷 贝 Timer.h 文 件 到 自 己 的 项 目文 件 夹 中 。 这 个 文 件 是 一 个 头 文 件 , 所 以 没 有 必 要 用 Add To Project( 加 入 到 项目 中 ) 命 令 将 其 加 入 到 项 目 中 。
步 骤 7 : 编 辑 PulseCtl.h 文 件
在 步 骤 2 中 , 选 择 最 简 单 的 可 以 从 ATL Object Wizar d( 对 象 向 导 ) 中 得 到 的 对 象类 型 , 这 将 设 置 CPulseCtl 类 , 以 便 仅 仅 继 承 3 个 基 类 和 2 个 接 口 。 一 个 真 正 的ActiveX 控 件 必 须 实 现 比 这 些 更 多 的 接 口 , 所 以 , 在 这 一 步 , 我 们 将 扩 展 类 的 继
承 列 表 , 以 包 含 一 个 典 型 包 容 器 希 望 一 个 控 件 所 实 现 的 附 加 接 口 。
在 文 本 编 辑 器 打 开 PulseCtl.h 文 件 , 然 后 加 入 下 面 灰 色 所 示 的 两 个 #include 语 句 , 一 个 用 于 我 们 在 前 面 的 步 骤 中 创 立 的 CTimer 类 , 而 另 一 个 用 于 引 入 项 目 所 需 要的 附 加 接 口 实 现 :
#include "resource.h" //main symbols #include "PulseCP.h"
然 后 , 在 CPulseCtl 继 承 列 表 中 增 加 下 列 内 容 。 这 些 行 的 实 际 顺 序 并 不 重 要 , 但是 , 注 意 列 表 中 的 所 有 入 口 都 有 一 个 逗 号 结 尾 , 最 后 一 个 例 外 。
class ATL_NO_VTABLE CPulseCtl :
public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CPulseCtl, &CLSID_PulseCtl>, public IConnectionPointContainerImpl<CPulseCtl>,
public IDispatchImpl<IPulseCtl, &IID_IPulseCtl, &LIBID_PULSELib>,
public CProxy_IPulseCtlEvents< CPulseCtl >
{
尽 管 Pulse 控 件 是 不 可 见 的 ( 而 不 是 无 窗 口 ), 然 而 , CpulseCtl 类 必 须 从IOleInPlaceObjectWindowlessImpl 中 导 出 ( 复 习 第 9 章 内 容 , 无 窗 口 控 件 取 决 于显 示 服 务 的 控 件 )。这 是 ATL 对 于 像 Pulse 这 样 的 不 可 视 控 件 不 进 行 优 化 的 例 子 。ATL 没 有 提 供 我 们 的 控 件 所 需 要 的 IOleInPlaceObject 接 口 的 单 独 实 现 ,而 是 仅 仅提 供 了 IOleInPlaceObjectWindowless,这是对 IOleInPlaceaObject 的 扩 展 , 这 将 增加 对 窗 口 信 息 的 支 持 和 拖 放 操 作 。 Pulse 并 不 需 要 这 些 额 外 的 方 法 , 但 是 必 须 包含 它 们 , 以 实 现 IOleINPlaceObject。
按 照 类 继 承 列 表 , 为 FinalRElease 子 函 数 加 入 原 型 , 这 个 子 函 数 在 卸 载 Pulse 控件 时 , 被 ATL 的 CComObject 调 用 :
public:
HRESUL T FinalRelease() ;
CPulseCtl()
{
}
注 意 CPulseCtl 并 没 有 声 明 一 个 类 析 构 函 数 。 这 是 因 为 析 构 函 数 在 从 CPulseCtl 导 出 的 ATL 基 类 中 是 不 可 视 的 , 所 以 类 并 不 能 确 实 地 假 定 它 的 析 构 函 数 将 永 远不 会 被 调 用 。一 个 使 用 ATL 的 ActiveX 控 件 类 执 行 在 FinalRelease 函 数 中 的 任 何必 要 的 清 除 功 能 ,这 将 在 对 象 实 例 被 破 坏 之 前 被 调 用 。它 的 必 然 结 果 是 一 个 控 件必 须 将 它 的 初 始 化 任 务 限 定 在 FinalConstruct 函 数 中 。 本 章 后 面 出 现 的 第 2 个 项目 说 明 了 FinalConstruct 的 用 法 。 为 了 与 CPulseCtl 继 承 性 列 表 内 的 接 口 模 板 匹配 , 必 须 为 类 的 C O M 映 射 加 入 相 应 的 入 口 , 如 下 所 示 :
BEGIN_COM_MAP(CPulseCtl)
COM_INTERFACE_ENTRY_IMPL(IConnectionPointContainer) COM_INTERFACE_ENTRY(IPulseCtl) COM_INTERFACE_ENTRY(IDispatch) COM_INTERFACE_ENTRY(IConnectionPointContainer)
COM_INTERFACE_ENTRY(IObjectWithSite)
COM_INTERFACE_ENTRY(IViewObjectEx)
COM_INTERFACE_ENTRY(IViewObject2)
COM_INTERFACE_ENTRY(IViewObject)
COM_INTERFACE_ENTRY(IOleInPlaceObjectWindowless)
COM_INTERFACE_ENTRY(IOleInPlaceObject)
COM_INTERFACE_ENTRY2(IOleWindow, IOleInPlaceObjectWindowless)
COM_INTERFACE_ENTRY(IOleControl)
COM_INTERFACE_ENTRY(IOleObject)
COM_INTERFACE_ENTRY(IPersistStreamInit)
COM_INTERFACE_ENTRY2(IPersist, IPersistStreamInit)
COM_INTERFACE_ENTRY(IPersistStorage)
END_COM_MAP()
IProvideClassInfo2 是 新 的 OLE 接 口 , 替 换 了 旧 的 IProvideClassInfo 。一 些 包 容 器不 认 识 IProvideClassInfo2 , 所 以 映 射 包 含 了 IProvideClassInfo 的 入 口 。 因 为IProvideClassInfo2 代 表 旧 接 口 , 所 以 加 入 的 内 容 不 会 增 加 Pulse 的 代 码 大 小 。IProvideClassInfo 和 IProvideClassInfo2 支 持 Pulse 的 事 件 启 动 。 两 个 接 口 都 提 供了 GetClassInfo 方 法 , 它 将 提 供 属 于 PulseCtl 组 件 类 对 象 的 类 型 信 息 。 这 个 类 型信 息 来 自 控 件 的 类 型 库 , 将 告 诉 客 户 如 何 为 Pulse 事 件 建 立 它 的 处 理 程 序 函 数 。
除 了 建 立 Uniform Data Transfer( 统 一 数 据 传 送 ) 的 核 心 之 外 , IDataObject 接 口提 供 了 向 客 户 通 知 数 据 变 化 的 方 法 。 一 个 实 现 IAdviseSink 接 口 的 客 户 调 用 控 件的 IDataObject::Advise 方 法 在 控 件 的 数 据 被 改 变 时 , 开 始 接 收 通 知 。 这 并 不 适 用于 Pulse , 而 且 控 件 可 以 在 不 支 持 IDataObject 的 情 况 下 正 常 工 作 。 然 而 , 一 些 客户 , 例 如 Test Container 实 用 程 序 要 求 接 口 建 立 通 知 连 接 。
确 保 C O M 映 射 包 含 IConnectionPointContainer 的 入 口 :
COM_INTERFACE_ENTRY_IMPL(IConncetionPointContainer)
如 果 没 有 在 步 骤 2 中 的 Object Wizar d( 对 象 向 导 )中 选 择 Support Connection Points
( 支 持 连 接 点 ) 选 项 , 在 COM 映 射 中 键 入 入 口 , 并 在 类 继 承 性 列 表 中 增 加IConnectionPointContainerImpl。 一 个 包 容 器 通 过 它 的 IConnectionPointContainer 接 口 来 查 询 对 象 , 以 了 解 对 象 支 持 哪 些 接 口 , 在 我 们 的 例 子 中 是 事 件 接 口
_IpulseCtlEvents 。 包 容 器 的 事 件 接 收 器 与 对 象 的 连 接 和 断 开 将 通 过 对 象 的IConnectionPoint 接 口 进 行 。 C O M 映 射 不 需 要 单 独 的 IConnectionPoint 接 口 , 因为 IConnectionPointContainer 提 供 了 FindConnectionPoint 方 法 , 它 将 向 ATL 的 代表 _IPulseCtlEvents 的 IConncetionPoint 返 回 一 个 指 针 。 图 10-12 说 明 了 包 容 器 如何 将 它 的 事 件 接 收 器 挂 接 在 Pulse 的 事 件 函 数 上 的 步 骤 。
图 10-12 包容器如何建立连接以接收事件启动消息
FindConnectionPoint 和 它 的 姊 妹 方 法 EnumConnectionPoints 读 入 一 个 数 组 , 这 个 数 组 被 称 为 连 接 点 映 射 , 它 包 含 了 控 件 提 供 的 每 一 个 连 接 点 的 接 口 标 识 符 列 表 。
Pulse 仅 仅 支 持 _IPulseCtlEvents 的 一 个 连 接 点 , 这 将 在 COM 映 射 后 面 的 连 接 点映 射 中 规 定 :
BEGIN_CONNECTION_POINT_MAP(CPulseCtl)
CONNECTION_POINT_ENTRY(DIID__IPulseCtlEvents) END_CONNECTION_POINT_MAP()
在 连 接 点 映 射 下 面 , 加 入 如 下 所 示 的 属 性 映 射 。 这 个 映 射 是 空 的 , 因 为 Pulse 不支 持 属 性 工 作 表 , 但 是 , 加 入 到 CPulseCtl 类 中 的 一 些 接 口 实 现 要 求 映 射 存 在 :
步 骤 8 : 编 辑 PulseCtl.cpp 文 件
在 PulseCtl.cpp 文 件 中 加 入 下 面 灰 色 部 分 所 示 的 指 令 , 以 完 成 存 根 函 数 :
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CPulseCtl
STDMETHODIMP CPulseCtl::get_nInterval( long *pVal )
{
*pVa l = m_dwTimerInterval ; return S_OK;
}
STDMETHODIMP CPulseCtl::StartPulse( long nRate )
{
if (!m_bTimerOn )
}
STDMETHODIMP CPulseCtl::EndPulse()
{
return S_OK;
}
STDMETHODIMP CPulseCtl::_OnTimer()
{
Fire_Pulse() ; return S_OK;
}
HRESULT CPulseCtl::FinalRelease() { return EndPulse(); |
|||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
} |
|||||||||||||||||||||
get_nInterval |
方 |
法 |
通 |
知 |
调 |
用 |
者 |
当 |
前 |
的 |
计 |
时 |
器 |
间 |
隔 |
, |
它 |
被 |
存 |
储 |
在 |
CTimer::m_dwTimerInterval 成 员 变 量 中 。因 为 我 们 在 步 骤 2 中 规 定 nInterval 属 性为 只 读 ,所 以 没 有 匹 配 的 放 置 函 数 。StartPulse 和 EndPulse 实 现 控 件 的 两 个 方 法 , 启 动 和 停 止 计 时 器 。当 包 容 器 在 结 束 控 件 后 ,没 有 调 用 EndPulse 时 ,FinalRelease 函 数 调 用 EndPulse , 以 确 保 计 时 器 的 工 作 线 程 在 控 件 结 束 前 是 正 确 的 。
步 骤 9 : 编 辑 Pulse.rgs文 件
早 些 时 候 , 我 们 了 解 到 项 目 的 RGS 文 件 中 包 含 脚 本 信 息 , 这 是 由 控 件 的
D llRegisterServer 函 数 写 入 到 系 统 Registr y( 注 册 表 )内 的 。在 步 骤 2 中 选 择 Simple Objec t( 简 单 对 象 ) 选 项 将 是 ATL Object WiaZzrd ( 对 象 向 导 ) 丢 失 某 些 一 般ActiveX 控 件 需 要 的 Registr y(注册表)信息,例如 OLEMISC 标 志 列 表 。 稍 后 的附 加 内 容 详 细 讨 论 了 标 志 , 但 现 在 我 们 仅 仅 需 要 在 Pulse 的 注 册 数 据 上 增 加 一 个值 , 以 规 定 在 M iscStatus Registry 键 内 所 需 要 的 标 志 。 在 文 本 编 辑 器 中 打 开Pulse.rgs 文 件 , 并 增 加 下 列 内 容 :
ForceRemove {8C9BABDD-BCE5-11D1-BEC9-D43CA8CB2F51} = s 'PulseCtl Class'
{
ProgID = s 'Pulse.PulseCtl.1' VersionIndependentProgID = s 'Pulse.PulseCtl' ForceRemove 'Programmable'
InprocServer32 = s '%MODULE%'
{
val ThreadingModel = s 'Apartment'
}
'TypeLib' = s '{3B365F9D-C3AE-11D1-BEC9-E0F4E352507A}'
}
控 件 入 口 将 Pulse 作 为 一 个 可 嵌 入 的 ActiveX 控 件 。 M iscStatus 的 值 148624 代 表
5 个 标 志 的 组 合 值 : OLEMISC_SETCLIENTSITEFIRST, OLEMISC_INSIDEOUT, OLEMISC_CANTLINKINSIDE,OLEMISC_INVISIBLEATRUNTIME 和OLEMISC _NOUIACTIVATE , 这 些 都 在 稍 候 的 附 加 内 容 上 有 说 明 。
- 般 地 , ATL Object Wizard( 对 象 向 导 ) 为 控 件 项 目 增 加 了
一 个 默 认 位 图 , 但 对于 像 Pulse 这 样 的 简 单 对 象 类 型 不 是 这 样 。如 果 想 增 加 一 个 小 位 图 ,以 增 强 Pulse 控 件 的 功 能 ,下 面 将 告 诉 你 处 理 方 法 。我 们 已 经 看 见 一 些 包 容 器 ,例 如 Visual C++ 对 话 框 编 辑 器 , 如 何 向 用 户 显 示 代 表 一 个 控 件 的 工 具 按 钮 上 的 一 幅 位 图 。包 容 器
从 ActiveX 控 件 的 自 身 资 源 数 据 中 提 取 位 图 图 像 , 图 像 由 系 统 Registr y( 注 册 表 ) 内 的 控 件 的 ToolboxBitmap32 键 识 别 。为 了 插 入 ToolboxBitmap32 键 ,在 Pulse.rgs 文 件 中 连 同 其 他 加 入 的 部 分 一 起 加 入 下 面 的 一 行 :
ForceRemov e 'ToolboxBitmap32 ' = s '%MODULE% , 1 '
- 个 控 件 的 位 图 是 完 全 可 选 的 , 但 是 , 为 那 些 控 件 加 入
一 个 专 业 的 方 法 , 是 市 场所 需 要 的 。位 图 的 大 小 是 16 × 15 像 素 ,所 以 一 幅 16 色 位 图 占 据 了 空 间 资 源 数 据
段 的 512 个 字 节 。从 Inser (t 插 入 )菜 单 中 选 择 Resourc e( 资 源 )命 令 ,选 择 Bitmap
( 位 图 ), 然 后 按 下 A lt+Enter, 以 打 开 Bitmap Propertiers ( 位 图 属 性 ) 对 话 框 。改 变 工 作 区 域 的 尺 寸 到 16 × 15 ,并 指 定 ToolboxBitmap32 键 的 资 源 标 识 符 的 值 与ForceRemove 语 句 中 给 定 的 值 一 样 。 在 我 们 的 例 子 中 , 这 个 值 是 1 :
OLEMISC 标志
- 个 ActiveX 控 件 注 册 了 一 组 OLEMISC 位 标 志 , 并 作 为 一 个
32 位 值 存 储 在 系统 Registry ( 注 册 表 ) 中 。 标 志 包 括 控 件 的 信 息 , 通 知 包 容 器 控 件 的 特 性 , 运 行首 选 项 和 功 能 。 将 这 个 信 息 公 布 在 Registry ( 注 册 表 ) 中 , 意 味 着 将 来 的 包 容 器不 需 要 首 先 将 控 件 嵌 入 , 以 了 解 它 的 要 求 和 功 能 。如 果 一 个 控 件 需 要 包 容 器 无 法提 供 的 服 务 ,包 容 器 可 以 在 不 浪 费 加 载 控 件 所 需 的 时 间 和 资 源 的 情 况 下 决 定 匹 配是 不 合 适 的 。
为 决 定 控 件 的 OLEMISC 标 志 的 设 置 ,包 容 器 可 以 调 用 IOleObject::GetMiscStatus
函 数 。 这 个 调 用 并 不 会 将 控 件 载 入 , 因 为 OLE 为 这 个 函 数 提 供 了 默 认 的 实 现 ,
-
OLEMISC_ACTIVATEWHENVISIBLE ─ ─ 当 对 象 可 视 时 , 对 象 想 要被
激 活 。
-
OLEMISC_SETCLIENTSITEFIRST ─ ─ 仅 仅 在 使 用 ActiveX 控 件 时 使用
, 这 个 标 志 指 出 , 控 件 优 先 使 用 IOleObject::SetClientSite 作 为 它 的初 始 化 函 数 , 甚 至 在 调 用 IPersiststreamInit::InitNew 或 者IPersistStorage::InitNew , 以 从 磁 盘 上 提 取 控 件 的 属 性 数 据 之 前 就 是 如此 。 这 将 允 许 控 件 在 从 永 久 存 储 加 载 信 息 之 前 , 访 问 包 容 器 的 环 境 属性 。 注 意 O leCreate, OleCreateFromData, OleCreateFromFile, OleLoad 的 当 前 实 现 和 默 认 处 理 程 序 的 当 前 实 现 并 不 理 解 这 个 值 。 希 望 兑 现 这个 标 志 的 控 件 包 容 器 必 须 在 当 前 实 现 这 些 函 数 自 己 的 版 本 , 目 的 是 为控 件 建 立 正 确 的 初 始 化 顺 序 。
-
像 Pulse 这 样 的 不 可 视 控 件 一 般 为
OLEMISC_ACTIVATEWHENVISIBLE 设 置 两 个 标 志 :
-
OLEMISC_INVISIBLEATRUNTIM E─ ─ 通 知 包 容 器 , 控 件 没 有 用
户接 口 , 而 且 不 需 要 显 示 在 屏 幕 上 。
-
OLEMISC_NOUIACTIVATE - 控 件 不 需 要 共 享 用 户 接 口 元 素 , 例
如 菜单 , 而 且 运 行 时 不 需 要 输 入 焦 点 。
如 果 控 件 有 Objec t Wizard ( 对 象 向 导 ) 设 置 的 标 志 所 无 法 识 别 的 其 他 实 时 要 求 ,
步 骤 10 : 建 立 和 测 试 Pulse ActiveX 控 件
最 后 的 一 步 将 监 督 完 成 Pulse 项 目 , 并 提 出 如 何 检 查 新 控 件 的 方 法 。 首 先 我 们 选择 一 个 建 立 的 目 标 。ATL COM AppWizard 按 照 4 种 公 布 的 配 置 建 立 一 个 ATL 项目 :
配 置 |
预 处 理 定 义 |
|
---|---|---|
释 放 |
M inSize |
_ATL_DLL |
释 放 |
M inDependency |
_ATL_STATIC_REGISTRY |
Unicode 释放 M inSize _UNICODE, _ATL_DLL
Unicode 释 放
M inDependency
_UNIDOCE,
_ATL_STATIC_REGISTRY
对 于 ANSI 和 Unicode 控 件 项 目 , M inSize 和 M inDependency 目 标 为 ATL 服 务 提供 一 种 选 择 机 会 ,即 控 件 依 赖 于 一 个 辅 助 的 实 时 文 件 , 或 者 将 它 所 需 要 的 全 部 代码 合 并 到 它 自 己 的 可 执 行 文 件 内 。这 是 在 减 小 文 件 大 小 和 减 小 运 行 时 相 关 性 之 间进 行 选 择 , 非 常 类 似 在 MFC 项 目 中 , 必 须 在 与 MFC 库 静 态 或 者 动 态 链 接 之 间进 行 选 择 。
M inSize 配 置 将 控 件 动 态 链 接 在 A tl.dll 上 , 这 是 Visual C++ 安 装 在W indows\System 文 件 夹 下 的 一 个 54KB 的 库 文 件 ,从 而 减 小 控 件 的 大 小 。当 控 件运 行 时 , 它 为 控 件 所 要 求 的 服 务 函 数 而 调 用 A tl.dll 。 这 种 安 排 的 结 果 是 , 当 有 几个 链 接 在 Atl.dll 上 的 ActiveX 控 件 一 起 运 行 时 , 将 使 内 存 的 利 用 效 率 最 高 。在 这
种 情 况 下 , M inSize 目 标 也 可 以 在 通 过 网 络 或 者 Internet 传 送 控 件 时 , 减 小 下 载时 间 , 这 是 因 为 控 件 的 组 合 文 件 大 小 被 减 小 了 ─ ─ 即 使 考 虑 加 上 A tl.dll 也 是 如此 , Atl.dll 将 与 控 件 一 起 传 送 。
M inDependency 配 置 将 设 置 控 件 项 目 , 使 编 译 器 扩 展 类 模 板 到 整 个 类 , 而 不 是 调用 Atl.dll 的 存 根 函 数 。在 这 种 配 置 下 , 控 件 自 身 包 括 它 需 要 的 接 口 实 现 代 码 , 就像 ATL 服 务 被 静 态 链 接 一 样 ( 这 仅 仅 是 一 个 类 比 , 因 为 对 于 ATL 没 有 像 MFC 那 样 的 静 态 库 LIB 文 件 )。 产 生 的 ActiveX 控 件 并 不 依 赖 与 A tl.dll 文 件 , 它 用 较大 的 执 行 映 像 换 来 了 独 立 性 。M inDependency 目 标 最 好 用 于 那 些 不 会 与 其 他 ATL 控 件 一 起 运 行 的 单 个 的 控 件 。 因 为 Pulse 符 合 这 样 的 条 件 , 所 以 在 Build 工 具 栏中 选 择 Release MinDependency 配 置 :
从 配 套 光 盘 上 安 装 的 项 目 文 件 将 目 标 名 字 简 化 为 M inSize 和 M inDep, 这 样 可 以是 文 件 夹 名 字 小 于 8 个 字 母 。如 果 没 有 比 在 系 统 注 册 表 中 减 小 它 们 所 占 的 空 间 更重 要 的 原 因 , 你 最 好 为 你 自 己 的 ATL 项 目 选 择 短 目 标 名 字 。 为 了 修 正 C O M AppWizard 所 设 置 的 目 标 名 字 , 暂 时 关 闭 工 作 空 间 , 然 后 在 文 本 编 辑 器 中 打 开 项目 的 DSP 文 件 。用 Replac e( 替 换 )命 令 ,用“ M inDep ”替 换 所 有 存 在 的“ Release M inDependency ”和“ ReleaseMinDependency ”。 按 照 类 似 的 步 骤 , 缩 短 M inSize 目 标 的 名 字 。将 DSP 文 件 存 盘 , 并 重 新 打 开 项 目 。你 就 可 以 看 见 在 Buil d( 建 立 ) 工 具 栏 中 列 出 的 新 目 标 名 字 。
至 今 为 止 , 我 们 还 没 有 讨 论 编 译 器 优 化 , 那 是 第 12 章 的 内 容 , 但 是 , Visual C++ 优 化 器 可 以 增 加 执 行 速 度 , 或 者 减 小 代 码 大 小 。 后 者 通 常 是 ActiveX 控 件 的 最 佳选 择 , 所 以 COM AppWizard 为 你 预 先 选 择 了 小 代 码 空 间 优 化 设 置 。 如 果 这 不 是你 需 要 的 , 改 变 Project Setting( 项 目 设 置 ) 对 话 框 的 C++ 选 项 卡 的 选 项 。 在 你
选 择 目 标 配 置 之 后 , 单 击 Build ( 建 立 ) 菜 单 上 的 Build Pulse.dll 命 令 , 编 译 和 链接 Pulse 控 件 。 如 果 所 有 编 译 和 链 接 步 骤 成 功 结 束 , Visual C++ 将 运 行RegSvr32.exe 来 注 册 控 件 , 并 在 Outpu t( 输 出 ) 窗 口 中 显 示 结 果 :
Registering ActiveX Control…
Regvr32: DllRegisterServer in .\MinDep\Pulse.ocx succeeded
注 意 :如 果 你 的 ActiveX 控 件 需 要 C 实 时 服 务 ,首 先 除 去 阻 止 链 接 到 运 行 库 的 预处 理 定 义 。 在 Project Setting ( 项 目 设 置 ) 对 话 框 的 C++ 选 项 卡 内 , 删 除 标 有Preprocessor Definitions ( 预 处 理 定 义 ) 的 框 内 的 常 量 _ATL_MIN_CRT 。
列 表 10-3 所 列 出 的 Tumble2.htm 文 档 展 示 了 新 控 件 。 如 果 你 按 照 这 里 所 描 述 的步 骤 创 立 Pulse 控 件 ,则 它 的 一 个 类 标 识 符 与 配 套 光 盘 上 的 Pulse.ocx 控 件 所 用 的不 同 。 在 这 种 情 况 下 , 你 必 须 在 Tumble2.htm 文 档 中 放 置 对 Pulse 对 象 的 classid 语 句 :
<OBJECT
classid="clsid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" id=pulse1
>
项 目 的 IDL 文 件 提 供 了 你 所 需 要 的 新 类 标 识 符 。 从 Pulse.idl 文 件 中 最 后 一 行 的uuid 语 句 将 标 识 符 拷 贝 到 剪 贴 板 内 , 然 后 将 其 粘 贴 在 Tumble2.htm 文 档 内 , 以 改变 classid 的 值 。 尽 管 Tumble2 使 用 不 同 的 计 时 器 , 它 的 动 画 显 示 与 原 始 Tumble 文 档 相 同 。
width=150
height=150
>
<PARAM NAME="Angle" value="0">
<PARAM NAME="Alignment" value="7">
<PARAM NAME="BackStyle" value="1">
<PARAM NAME="BackColor" value="255">
<PARAM NAME="FontBold" value="-1">
<PARAM NAME="Caption" value="Click Here">
<PARAM NAME="FontName" value="Times New Roman">
<PARAM NAME="FontSize" value="18">
</OBJECT>
图 10-13 在 Test Container 中测试 Pulse ActiveX 控件
Test Containe r( 测 试 包 容 器 ) 实 用 程 序 也 可 以 成 功 地 嵌 入 我 们 的 新 控 件 , 就 像 图
10-13 中 说 明 的 那 样 。 启 动 Test Container( 测 试 包 容 器 ), 然 后 选 择 New Control
( 新 控 件 ) 工 具 , 并 双 击 列 表 中 的 PulseCtl 类 入 口 , 加 载 Pulse 控 件 ( 列 表 是 按照 阿 拉 伯 字 母 排 列 的 ; 在 键 盘 上 按 下 P , 将 自 动 滚 动 到 以 P 打 头 的 第 一 个 控 件处 )。 一 旦 控 件 被 加 载 到 Test Containe r( 测 试 包 容 器 ) 内 , 单 击 Invoke Methods
( 激 活 方 法 ) 工 具 , 按 照 图 10-13 所 示 , 在 M ethod Name Box ( 方 法 名 称 框 ) 中选 择 StartPulse 方法,将 nRate 参 数 设 置 为 2000 ,然后单击 Invok e( 激 活 ) 按 钮 。这 将 使 控 件 每 2 秒 启 动 它 的 Pulse 事 件 , Test Container( 测 试 包 容 器 ) 将 为 事 件日 志 增 加 一 个 新 入 口 而 检 查 这 一 点 。 激 活 EndPulse 方 法 结 束 启 动 。
如 果 想 了 解 Pulse.ocx 如 何 被 嵌 入 到 一 个 普 通 的 包 容 器 中 应 用 程 序 中 , 运 行 配 套光 盘 上 文 件 夹 Code\Chapter.10\Hour2 。 Hour2 使 用 Pulse 控 件 作 为 它 的 计 时 器 , 而 不 是 IETimer 控 件 , 除 了 这 一 点 之 外 , Hour2 与 第 8 章 Hour 程 序 相 同 。
例 子 2 : TowerATL ActiveX 控 件
Pulse 项 目 证 明 ATL 可 以 帮 助 你 创 立 比 利 用 MFC 产 生 的 控 件 更 小 的 ActiveX 控件,但是,对 ATL 和 MFC 的 精 确 比 较 , 只 有 通 过 利 用 两 种 工 具 建 立 同 样 的 控 件才 能 进 行 。 这 正 是 我 们 在 这 一 章 所 讲 述 的 内 容 。 正 如 标 题 所 指 出 的 那 样 , ToweerATL 控 件 复 制 第 9 章 的 Tower 项 目 ,输 出 相 同 的 属 性 、方 法 和 事 件 。但 是 , TowerATL 是 完 全 利 用 ATL 的 服 务 创 立 的 , 没 有 用 到 MFC 。
TowerATL 是 一 个 比 Pulse 更 复 杂 的 ActiveX 控 件 , 包 含 有 栈 属 性 、 属 性 工 作 表和 一 个 About 框 。 尽 管 如 此 , 这 个 项 目 看 起 来 比 较 容 易 , 因 为 讨 论 更 加 紧 凑 , 而且 我 们 在 Pulse 项 目 中 所 注 意 过 的 部 分 占 据 较 小 篇 幅 。 现 在 已 经 排 除 了 大 多 数 的不 相 关 的 东 西 , 这 个 项 目 更 加 精 确 地 反 映 了 用 ATL 创 立 一 个 典 型 ActiveX 控 件所 需 要 的 时 间 和 努 力 。
步 骤 1 : 创 立 TowerATL 项 目
再 次 运 行 ATL COM AppWizard ,这 一 次 将 项 目 命 名 为 TowerATL 。单击 Finis h( 结束 ) 按 钮 接 受 默 认 设 置 , 这 将 是 TowerATL 控 件 成 为 一 个 不 需 要 MFC 的 动 态 链接 库 。当 COM AppWizard 结 束 后 ,从 Inser t( 插 入 ) 菜 单 中 选 择 New ATL Object
( 新 ATL 对 象 ), 启 动 ATL Object Wizard ( 对 象 向 导 )。 选 择 对 话 框 的 第 一 个 列表 内 的 控 件 , 然 后 双 击 标 有 Full Contro l( 完 整 控 件 ) 的 图 标 。 TowerATL 支 持 比Pulse 更 多 的 接 口 , 我 们 将 需 要 像 IQuickActivate 和 ISpecifyPropertyPages 这 样 的接 口 , 它 们 由 Full Control ( 完 整 控 件 ) 选 项 加 入 到 源 代 码 中 。
注 意 , 在 对 话 框 中 , ATL 将 控 件 的 属 性 页 作 为 一 个 单 独 的 对 象 。在 结 束 这 个 项 目之 前 , 返 回 到 Object Wizard( 对 象 向 导 ), 以 增 加 一 个 属 性 页 对 象 。 在 Properties
( 属 性 ) 对 话 框 的 Names ( 名 称 ) 选 项 卡 中 键 入 TowerCtl 作 为 短 名 字 , 接 受 其他 框 内 的 默 认 名 字 。在 A ttribute s( 属 性 )选 项 卡 内 ,与 我 们 在 Pulse 项 目 中 一 样 , 选 择 Support Connection Points( 支 持 连 接 点 ) 复 选 框 。 接 受 选 项 卡 内 的 其 他 默 认
设 置 , 启 用 公 寓 线 程 、 双 接 口 和 聚 合 。
M iscellaneous( 杂 项 ) 选 项 卡 包 括 设 置 各 种 OLEMISC 标 志 的 开 关 。 例 如 , 选 择Acts Like Butto n( 按 钮 功 能 )复 选 框 ,设 置 OLEMISC_ACTSLIKEBUTTON 标 志 , 这 将 告 诉 包 容 器 , 控 件 将 对 鼠 标 单 击 作 出 响 应 , 并 通 常 作 为 一 个 按 钮 动 作 。 Acts Like Labe l( 选 项 卡 功 能 ) 复 选 框 设 置 OLEMISC_ACTSLIKELABEL 标 志 , 这 将通 知 包 容 器 , 这 个 控 件 作 为 按 选 项 卡 顺 序 紧 跟 其 后 的 控 件 的 选 项 卡 使 用 , 就 是 在包 容 器 的 RC 脚 本 内 列 出 的 下 一 个 控 件 。 Invisible At Runtim e( 运 行 时 不 可 见 ) 复 选 框 将 打 开 OLEMISC_INVISIBLEATRUNTIME 标 志 , 这 适 合 像 Pulse 这 样 的在 激 活 时 仍 然 不 可 视 的 控 件 。
跳 过 M iscellaneous 选 项 卡 ,接 受 它 的 默 认 设 置 ,并 进 入 Stock Propertie s( 栈 属 性 ) 选 项 卡 。 与 Tower 的 MFC 版 本 类 似 , TowerATL 控 件 包 括 4 个 栈 属 性 , 名 字 为BackColor, ForeColor, Caption 和 Fon t。 在 列 表 中 选 择 每 一 个 属 性 , 并 单 击 > 按 钮 , 就 可 以 在 Supported ( 支 持 ) 框 内 增 加 这 些 属 性 , 如 图 10-14 所 示 ( BackColor 和ForeColor 属 性 在 列 表 中 标 为 Background Color 和 Foreground Color ) 。 单 击 O K 按 钮 , 关 闭 ATL Object Wizard ( 对 象 向 导 ) 对 话 框 。
图 10-14 在 ATL Object Wizard ( 对 象 向 导 ) 内 选 择 栈 属 性
步 骤 2 : 加 入 nCurrentBlock 用 户 属 性
与 4 个 栈 属 性 一 起 ,TowerATL 具 有 一 个 称 为 nCurrentBlock 的 用 户 属 性 ,这 个 属
性 保 留 了 一 个 数 , 用 于 识 别 正 在 被 拖 动 的 块 。 从 技 术 上 讲 , 它 是 由 通 过get_nCurrentBolck 方 法 显 露 这 个 属 性 的 对 象 所 继 承 的 ITowerCtl 接 口 。 为 了 给ITowerCtl 接 口 加 入 这 个 用 户 属 性 , 在 ClassView 窗 口 中 展 开 TowerATL 类 列 表 , 用 鼠 标 右 键 单 击 ITowerCtl 入 口 , 然 后 选 择 Add Property ( 加 入 属 性 ) 命 令 , 以激 活 Add Property To Interface ( 为 接 口 加 入 属 性 ) 对 话 框 。 选 择 short 作 为 属 性类 型 , 并 键 入 nCurrentBolck 作 为 属 性 的 名 字 。就 像 我 们 在 第 9 章 中 所 做 的 一 样 , 使 cCurrentBlock 属 性 具 有 只 读 特 性 , 以 阻 止 一 个 包 容 器 应 用 程 序 改 变 它 的 值 。清 除 标 有 Put Functio n( 放 置 函 数 ) 的 单 选 钮 , 以 告 诉 Object Wizard( 对 象 向 导 ) 不 要 为 这 个 属 性 产 生 放 置 方 法 , 然 后 关 闭 这 个 对 话 框 。
步 骤 3 : 加 入 Rese t方 法
TowerATL 导 出 了 一 个 称 为 Reset 的 方 法 函 数 , 包 容 器 应 用 程 序 利 用 这 个 函 数 告诉 控 件 再 次 启 动 程 序 , 重 新 启 动 左 面 板 中 的 所 有 块 。 再 次 用 鼠 标 右 键 单 击ClassView 中 的 ITowerCtl 接 口 ,选 择 Add Metho d( 加 入 方 法 )命 令 ,然 后 在 Add M ethod To Interfac e( 为 接 口 加 入 方 法 ) 对 话 框 中 键 入 Reset 作 为 函 数 名 字 。 这 个方 法 没 有 参 数 , 所 以 使 参 数 框 保 持 空 白 。
单 击 O K 关 闭 Add Metho d( 加 入 方 法 )对话框,此时 Visual C++ 将 在 这 个 项 目 的IDL 文 件 中 为 这 个 方 法 加 入 适 当 的 代 码 ,并 在 TowerCtl.cpp 文 件 中 写 入 一 个 存 根Reset 函 数 。 在 控 件 中 加 入 消 息 处 理 程 序 和 事 件 之 后 , 我 们 将 编 辑 这 些 代 码 。
步 骤 4 : 加 入 处 理 程 序 函 数
如 果 你 回 忆 第 9 章 的 内 容 ,控 件 窗 口 将 对 WM_LBUTTONDOWN 消 息 作 出 响 应 , 方 法 是 初 始 化 一 个 拖 动 操 作 ,在 这 个 操 作 中 , 用 户 将 一 个 彩 色 块 从 一 个 面 板 移 动到 另 一 个 。WM_LBUTTONDOWN 消 息 表 示 这 个 块 已 经 被 拖 动 到 位 。项 目 的 第 4 步 非 常 简 单 , 只 需 要 加 入 存 根 函 数 以 处 理 两 条 消 息 。
在 ClassView 窗 格 中 , 用 鼠 标 右 键 单 击 CTowerCtl 入 口 , 并 从 菜 单 中 选 择 Add W indow Message Handler ( 加 入 窗 口 消 息 处 理 程 序 )。 双 击 列 表 中 的WM_MOUSEMOVE, WM_LBUTTONDOWN 和 WM_LBUTTONU P , 以 将 它 们
加 入 到 右 边 的 框 中 , 然 后 关 闭 对 话 框 。
步 骤 5 : 加 入 事 件
与 原 来 的 Tower 控 件 一 样 ,TowerATL 启动 5 个 事 件 ,名 称 分 别 为 Click, FromPanel, ToPanel, Error 和 W inner 它 们 共 同 保 证 使 包 容 器 了 解 对 控 件 所 发 生 的 事 件 。 在 项目 中 加 入 这 些 事 件 的 步 骤 与 我 们 对 Pulse 控 件 的 操 作 是 一 样 的 :
- 在 ClassView 窗 格 中 用 鼠 标 右 键 单 击 _ITowerCtlEvents 入 口
,并 从 关 联菜 单 中 选 择 Add Method s( 加 入 方 法 )命 令 ,然 后 在 对 话 框 中 选 择 void 返 回 类 型 ,并 键 入 一 个 函 数 名 字 。对 所 有 5 个 事 件 函 数 重 复 这 些 操 作 。只 有 FromPanel 和 ToPanel 带 有 参 数 , 分 别 为 nFromPanel 和 nToPanel , 这 两 个 参 数 的 类 型 都 是 short 。
- 在 Workspace 窗 口 的 FileView 窗 格 中 , 用 鼠 标 右 键 单 击
TowerATL.idl
的 入 口 , 并 选 择 Compile ( 编 译 ) 命 令 , 以 创 建 这 个 项 目 的 类 型 库 文
件 。忽 略 来 自 MIDL 编 译 器 中 的 警 告 信 息“ interface does not conform to [oleautomation] attribute ”。 这 个 警 告 信 息 是 因 为 编 译 器 认 为 pFont 参 数
─ ─ 一 个 指 向 IFontDisp 接 口 的 指 针 ─ ─ 不 是 一 个 兼 容 Automation 的类 型 。 但 是 因 为 IFontDisp 来 自 与 IDispatch , 这 是 一 个 合 法 的Automation 接 口 , 所 以 这 条 警 告 信 息 是 不 正 确 的 。
- 在 ClassView 窗 格 中 , 用 鼠 标 右 键 单 击 CTowerCtl , 并 从 菜
单 中 选 择
Implement Connection Poin t( 实 现 连 接 点 ) 命 令 。
- 设 置 标 有 _ITowerCtlEvents 的 复 选 框 , 然 后 单 击 O K 关 闭
对 话 框 。
在 这 里 , 我 们 为 项 目 加 入 事 件 的 步 骤 与 我 们 在 前 面 章 节 中 用 MFC 开发 Tower 控 件 时 所 用 的 步 骤 是 类 似 的 , 尽 管 看 起 来 可 能 不 是 这 样 。 在这 些 操 作 之 后 , 第 9 章 中 的 ControlWizard 将 创 建 一 个 O D L 文件 ( 与IDL 类 似 ), 其 中 包 含 了 这 些 指 令 :
//{{AFX_OLD_EVENT(CTowerCtrl) [id(DISPID_CLICK)] void Click (); [id(1)] void FromPanel (short nPanel); [id(2)] void ToPanel (short nPanel);
[id(DISPID_ERROREVENT)] void Error() ; [id(3)] void Winner();
//}}AFX_ODL_EVENT
从 这 些 信 息 中 , ClassWizard 产 生 像 FireClick 和 FireWinner 这 样 的 代 理 函 数 , 它们 将 调 用 MFC 的 COleControl::FireEvent 函 数 。 然 后 FireEvent 将 调 用 包 容 器 的IDispatch::Invoke 方 法 , 这 些 与 ATL 产 生 的 代 理 函 数 完 全 一 样 。
步 骤 6 : 加 入 属 性 工 作 表
第 9 章 的 Tower 控 件 提 供 了 一 张 属 性 工 作 表 , 这 允 许 用 户 改 变 控 件 的 标 题 ,字 体和 颜 色 。 图 9-13 显 示 了 属 性 工 作 表 的 外 观 。 在 这 一 节 , 我 们 将 为 TowerATL 创建 一 个 类 似 的 属 性 工 作 表 。
ATL 将 控 件 的 属 性 工 作 表 的 每 一 页 设 置 为 一 个 独 立 的 对 象 ,通 过 从 IPropertyPage 导 出 的 一 个 类 来 实 现 。 系 统 的 Msstkprp.dll 库 为 标 有 Font( 字 体 ) 和 Colors ( 颜色 )的 属 性 页 提 供 默 认 的 类 实 现 ,这 将 允 许 用 户 改 变 Font, BackColor 和 ForeColor 栈 属 性 。这 些 栈 属 性 将 依 次 决 定 显 示 在 TowerAtl 窗 口 顶 部 的 标 题 的 字 体 和 颜 色 。为 提 供 访 问 Caption 栈 属 性 的 方 法 , 它 其 中 包 含 了 控 件 标 题 的 文 本 , 我 们 必 须 像以 前 对 原 来 的 Tower 控 件 的 操 作 一 样 , 多 加 入 一 个 标 为 Caption 的 属 性 页 。 在 控
件 中 加 入 一 个 属 性 页 需 要 ATL 的 Object Wizard ( 对 象 向 导 ) 提 供 服 务 , 为 你 想加 入 的 每 一 页 运 行 Object Wizard ( 对 象 向 导 )。 为 加 入 TowerATL 的 新 Caption 页 ,在 Object Wizar d( 对 象 向 导 )的 左 框 中 选 择 Control s( 控 件 ),并 双 击 Property Page ( 属 性 页 ) 图 标 :
当 出 现 ATL Object Wizard Properties ( ATL 对 象 向 导 属 性 ) 对 话 框 时 , 键 入
TowerPPG 作 为 对 象 的 短 名 字 :
像 以 前 一 样 , 向 导 将 帮 助 我 们 用 推 荐 的 名 字 填 充 其 他 框 。 Interface ( 接 口 ) 框 是灰 色 的 , 因 为 一 个 属 性 页 对 象 不 需 要 用 户 接 口 。 接 受 Attributes ( 属 性 ) 选 项 卡
内 的 默 认 设 置 , 并 选 择 String s( 字 符 串 ) 选 项 卡 。 键 入 &Caption 作 为 页 的 标 题 , 在 标 有 Doc String ( Doc 字 符 串 ) 的 框 内 键 入 Caption property 。 TowerATL 不 会提 供 帮 助 文 件 , 所 以 删 除 第 3 个 编 辑 框 内 的 文 本 , 并 保 持 空 白 。
Object Wizard ( 对 象 向 导 ) 将 标 题 和 文 档 字 符 串 写 入 项 目 的 RC 文 件 , 在 这 里 , 它 们 变 成 控 件 的 字 符 串 资 源 数 据 的 一 部 分 。标 题 规 定 了 出 现 在 新 属 性 页 的 选 项 卡上 的 内 容 。文 档 字 符 串 被 用 作 选 项 卡 的 工 具 提 示 文 本 , 它 将 在 鼠 标 光 标 移 动 暂 停在 选 项 卡 上 时 , 描 述 这 一 页 的 作 用 , 但 是 这 一 个 字 符 串 并 不 经 常 使 用 , 并 且 不 会作 为 工 具 提 示 或 者 其 他 的 东 西 出 现 。 这 是 因 为 负 责 创 建 属 性 工 作 表 窗 口 的 OLE 运 行 O leCreatePropertyFrame 函数,在 OLE 术 语 中 被 称 为 属 性 帧 , 并 不 支 持 工 具提 示 。
单 击 O K 按 钮 , 关 闭 对 话 框 , 此 时 Object Wizar d( 对 象 向 导 ) 将 在 项 目 中 加 入 这些 :
-
加 入 TowerPPG.cpp 和 TowerPPG.h 文 件 , 以 实 现 新 CTowerPPG 类。
-
在 TowerATL.rc 文 件 中 为 标 题 和 工 具 提 示 写 入 字 符 串 资 源
:
IDS_TITLETowerPPG "Caption" IDS_DOCSTRINGTowerPPG "Caption property"
- 为 新 属 性 页 对 象 在 TowerATL.idl 文 件 中 追 加 一 个 标 识 符
和 coclass 入口 。
[
uuid(05D2BAA4-C471-11D1-BEC9-FB1AF66FCC79),
helpstring("TowerPPG Class")
]
coclass TowerPPG
{
interface IUnknown;
};
-
加 入 Towerppg.rgs 文 件 , 为 新 对 象 提 供 注 册 表 脚 本 。
-
在 TowerATL.cpp 文 件 中 的 控 件 对 象 映 射 中 为 页 插 入 一 个
入 口 。
BEGIN_OBJECT_MAP(ObjectMap)
.
.
.
OBJECT_ENTRY(CLSID_TowerPPG, CTowerPPG) END_OBJECT_MAP()
当 Object Wizar d( 对 象 向 导 ) 完 成 这 些 操 作 后 , 将 自 动 出 现 对 话 框 编 辑 器 , 这 表示 它 已 经 准 备 好 让 你 开 始 设 计 新 属 性 页 。用 静 态 和 编 辑 控 件 工 具 编 辑 对 话 框 ,使对 话 框 看 起 来 就 像 这 样 :
你 自 己 的 属 性 页 的 精 确 布 置 并 不 重 要 , 但 是 , 要 像 图 中 那 样 为 编 辑 框 指 定 一 个
IDC_CAPTION 的 标 识 符 ( 为 激 活 Properties 对 话 框 , 选 择 编 辑 框 , 并 单 击 Edit
( 编 辑 ) 菜 单 上 的 Properties( 属 性 ))。 存 盘 , 并 关 闭 对 话 框 编 辑 器 。
此 时 TowerATL 控 件 已 接 近 完 成 了 。 剩 下 的 最 重 要 的 工 作 是 在 ATL 产 生 的 骨 架源 文 件 中 加 入 代 码 。 这 是 下 一 步 的 事 情 。
步 骤 7 : 编 辑 Towerppg.h 文 件
TowerPPG.h 文 件 包 含 对 应 CTowerPPG 类 的 代 码 ,这 个 类 用 与 处 理 控 件 的 Caption 属 性 页 对 象 。 我 们 仅 仅 需 要 在 类 上 加 入 一 些 指 令 , 以 在 用 户 键 入 新 Caption 字 符串 时 监 视 页 的 编 辑 框 和 调 用 put_Caption 方 法 。 用 鼠 标 右 键 单 击 ClassView 窗 格中 的 CTowerPPG 类 , 并 选 择 Add Windows Message Handle r( 加 入 窗 口 消 息 处 理程 序 ) 命 令 。 当 New Windows Message ( 新 窗 口 消 息 ) 对 话 框 出 现 时 , 在 标 有Class Or Object To Handle ( 处 理 的 类 或 对 象 ) 的 小 框 内 选 择 IDC_CAPTION , 并在 列 表 中 双 击 EN_CHANG E 。 接 受 OnChangeCaption 这 个 名 字 作 为 函 数 名 字 , 并 关 闭 New Windows Message ( 新 窗 口 消 息 ) 对 话 框 。
当 用 户 在 我 们 前 面 所 加 入 的 属 性 页 的 编 辑 框 中 键 入 字 符 时 , 将 运 行OnChangeCaption 函 数 , 这 样 产 生 修 正 Caption 属 性 的 意 图 。 这 个 函 数 仅 仅 通 过传 递 一 个 值 TRUE 到 SetDirty 子 函 数 , 使 对 话 框 的 Apply ( 应 用 ) 按 钮 可 用 。 单击 属 性 工 作 表 对 话 框 中 的 O K 或 者 Apply ( 应 用 ) 按 钮 将 运 行 Apply 函 数 , 它 需要 额 外 的 代 码 。 在 文 本 编 辑 器 中 打 开 TowerPPG.h 文 件 , 并 加 入 这 些 行 :
#include "resource.h" // main symbols
#includ e "TowerAtl.h "
.
.
.
STDMETHOD(Apply)(void)
{
CComQIPtr<ITowerCtl > pTower ( m_ppUnk[0 ] ) ;
m_bDirty = FALSE; return S_OK;
}
LRESULT OnChangeCaption(WORD wNotifyCode, WORD wID,
HWND hWndCtl, BOOL& bHandled)
{
return 0;
}
Apply 函 数 调 用 GetDlgItemText 将 新 标 题 字 符 串 从 编 辑 框 拷 贝 到 szCaption 缓 冲区 中 , 然 后 调 用 控 件 的 put_caption 方 法 按 照 新 字 符 串 放 置 Caption 属 性 。put_Caption 方 法 需 要 一 个 BSTR 参 数 ,所 以 代 码 用 ATL 宏 A2BSTR 来 将 szCaption 中 的 ANSI 字 符 串 转 换 为 BSTR 类 型 。 正 如 Apply 函 数 所 证 明 的 那 样 , 在 激 活 像A2BSTR 这 样 的 转 换 宏 之 前 , 必 须 包 含 USES_CONVERSION 宏 。 这 样 做 将 消 除转 换 所 带 来 的 编 译 器 错 误 。
这 段 代 码 也 证 明 一 个 ActiveX 控 件 如 何 利 用 CComQIPtr 灵 巧 指 针 模 板 类 。 因 为
put_Caption 是 CTowerCtl 的 一 个 子 函 数 , 而 不 是 CTowerPPG 的 , 所 以 这 个 函 数必 须 首 先 通 过 QueryInterface 接 收 一 个 指 向 ITowerCtl 的 指 针 。 将 pTower 作 为 一个 灵 巧 指 针 , 将 确 保 ITowerCtl::Release 在 GetDlgItemTest 或者 put_Caption 不 大可 能 出 现 错 误 的 事 件 中 被 调 用 。
步 骤 8 : 编 辑 TowerCtl.h 文 件
完 成 TowerATL 项 目 需 要 用 文 本 编 辑 器 进 行 较 多 的 编 辑 工 作 ,为 Tower 加 入 与 我们 在 第 9 章 中 所 加 入 的 源 代 码 一 样 的 部 分 。 打 开 TowerCtl.h 文 件 , 并 找 到TowerATL 中 彩 色 显 示 的 以 #define 语 句 开 头 的 部 分 :
#include "resource.h" //main symbols #include <atlctl.h>
#include "TowerCP.h"
续 表
#define EMPTY NUM_BLOCKS
#define BLACK RGB( 0, 0, 0 )
#define BLUE RGB( 0, 0, 255 )
#define CYAN RGB( 0, 255, 255 )
#define GREEN RGB( 0, 255, 0 )
#define MAGENTA RGB( 255, 0, 255 )
#define RED RGB( 255, 0, 0 )
#define YELLOW RGB( 255, 255, 0 )
#define WHITE RGB( 255, 255, 255 )
#define GRAY RGB( 128, 128, 128 )
然 后 ,在 CTowerCtl 类 中 为 我 们 在 原 来 的 Tower 项 目 中 使 用 的 相 同 的 子 变 量 加 入声 明 :
class ATL_NO_VTABLE CTowerCtl :
.
.
.
{
public:
HRESULT#FinalConstruct() ; CTowerCtl()
{
}
一 个 像 CTowerCtl 这 样 的 使 用 数 据 的 对 象 必 须 覆 盖 CComObject- RootEx::FinalConstruct 子 函 数 , 就 像 这 里 说 明 的 一 样 。 与 MFC 的 有 用 的OnInitDialog 函 数 非 常 类 似 , 在 ATL 完 成 设 置 对 象 之 后 , 对 象 被 激 活 之 前 , 将 调用 FinalConstruc t。 正 是 在 这 里 , 而 不 是 在 其 他 的 类 操 作 时 , 控 件 必 须 执 行 它 自己 的 大 部 分 初 始 化 工 作 。 在 Pulse 控 件 中 已 经 被 说 明 的 匹 配 的 FinalRelease 函 数允 许 控 件 执 行 任 何 必 要 的 清 除 工 作 。
更 改 属 性 映 射 , 以 建 立 Caption, Color 和 Font 在 控 件 的 属 性 工 作 表 中 出 现 的 顺序 :
BEGIN_PROP_MAP(CTowerCtl)
PROP_PAGE( CLSID_TowerPPG )
PROP_PAGE( CLSID_StockColorPage )
PROP_ENTRY( "Font", DISPID_FONT, CLSID_StockFontPage ) END_PROP_MAP()
这 个 消 息 映 射 包 含 在 步 骤 4 中 加 入 的 控 件 的 3 个 消 息 处 理 程 序 的 入 口 :
BEGIN_MSG_MAP(CTowerCtl)
MESSAGE_HANDLER(WM_MOUSEMOVE, OnMouseMove)
MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown) MESSAGE_HANDLER(WM_LBUTTONUP, OnLButtonUp)
.
.
.
END_MSG_MAP()
当 用 户 按 下 和 松 开 左 鼠 标 按 钮 , 在 面 板 之 间 拖 动 彩 色 块 时 , OnLButtonDown 和
OnLButtonUp 函 数 将 进 行 控 制 。 当 鼠 标 移 动 时 , OnMouseMove 进 行 控 制 , 它 的作 用 仅 仅 是 当 用 户 拖 动 一 个 块 时 , 保 证 光 标 保 持 交 叉 形 状 。
所 有 带 有 MESSAGE_HANDLER 宏 的 在 消 息 映 射 中 列 出 的 函 数 必 须 具 有 相 同 的参 数 列 表 :
LRESULT MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL &bHandled);
最 前 面 的 3 个 参 数 是 标 准 的 消 息 参 数 。 例 如 , OnLButtonDown 处 理 程 序 , 接 收uMsg 参 数 中 的 值 WM_LBUTTONDOWN , wParam 中 的 当 前 键 状 态 和 IParam 中的 鼠 标 光 标 的 坐 标 。 在 调 用 处 理 程 序 函 数 之 前 , MESSAGE_HANDLER 宏 将bHandled 标 志 设 定 为 TRUE 。一 个 不 能 完 全 满 足 消 息 要 求 的 处 理 程 序 在 返 回 之 前要 清 除 这 个 标 志 :
*bHandled = FALSE
否 则 , MESSAGE_HANDLER 宏 产 生 的 代 码 将 立 即 返 回 , 向 操 作 系 统 指 出 消 息已 经 被 完 全 处 理 了 。
- 个 ATL 消 息 映 射 与 在 MFC 中 的 映 射 具 有 一 样 的 形 式 。 ATL
通 过 向
W M _ C O M M A N D 和 WM_NOTIFY 消 息 提 供 COMMAND_HANDLER 和
NOTIFY_HANDLER 宏 , 而 进 一 步 与 MFC 匹 配 。 为 了 用 一 个 函 数 处 理 一 组 不 同
的 消 息 , 用 一 个 R A N G E 宏 替 代 : MESSAGE_RANGE_HANDLER, COMMAND_RANGE_HANDLER 或 者 NOTIFY_RANGE_HANDLER 。 与 MFC
中 的 ON_COMMAND_RANGE 和 ON_NOTIFY_RANGE 类 似 , 这 些 宏 为 所 有 在规 定 值 范 围 内 的 消 息 规 定 一 个 处 理 程 序 函 数 。 Visual C++ 将 函 数 OnDraw, OnMouseMove, OnlButtonDown 和 OnlButtonUp 定 义 为 CTowerCtl 类 声 明 中 的 内联 函 数 。 为 了 在 同 一 个 地 方 实 现 代 码 , 并 帮 助 与 原 来 的 Tower 源 文 件 进 行 比 较 , 将 这 4 个 函 数 移 动 到 TowerCtl.cpp 文 件 中 , 我 们 将 在 下 面 讨 论 它 。 当 然 , 剪 切 和粘 贴 是 完 全 可 选 的 ;如 果 你 按 照 本 文 创 立 这 个 项 目 , 并 喜 欢 将 这 些 函 数 放 在 头 文件 中 , 只 需 要 按 照 下 一 节 所 讲 的 内 容 编 辑 它 们 的 代 码 。
在 关 闭 TowerCtl.h 文 件 之 前 , 有 必 要 进 行 最 终 的 检 查 。 在 类 声 明 的 结 尾 , 一 些 版本 的 Visual C++ 写 下 了 一 个 名 为 m_spFont 的 子 变 量 。 这 个 名 字 必 须 换 为m_pFont , 以 防 止 栈 的 Font 页 的 ATL 实 现 产 生 错 误 :
CComPtr<IFontDisp>m_pFon t
步 骤 9 : 编 辑 TowerCtl.cpp 文 件
TowerCtl.cpp 的 源 代 码 与 我 们 为 原 来 的 Tower 项 目 的 相 同 文 件 加 入 的 代 码 非 常 相似 。 事 实 上 , 它 们 相 似 的 程 度 , 使 得 第 9 章 中 的 代 码 仍 然 适 用 于 这 个 控 件 的 新 版本 。 因 为 这 个 文 件 需 要 扩 充 , 列 表 10-4 列 出 了 完 整 的 编 辑 后 的 版 本 。
控 件 的 MFC 和 ATL 版 本 之 间 的 一 个 重 要 区 别 是 TowerCtl.cpp 中 如 何 进 行 初 始化 。 Tower 控 件 使 用 类 构 造 器 和 PreCreateWindow 虚 拟 函 数 来 初 始 化 它 的 数 据 , 而 现 在 TowerATL 使 用 ATL 的 FinalConstruct 函 数 。
按 排 初 始 化 工 作 仍 然 是 相 同 的 ; 只 是 位 置 发 生 了 变 化 。
列表 10-4 TowerCtl cpp 文件
// CTowerCtl
HRESULT CTowerCtl::FinalConstruct()
{
color[0] =BLACK; // Initialize block colors
color[1] =BULE;
color[2] =CYAN;
color[3] =GREEN;
color[4] =MAGENTA;
color[5] =RED;
color[6] =YELLOW;
m_clrBackColor = GRAY; //Default background color,
m_clrForeColor = WHITE; // foreground color,
m_bstrCaption = “ TowerATL ” ; // and caption
m_bAutoSize = FALSE; //Control can be resized
m_sizeExtent.cx =7000; //Init HIMETRIC size for
m_sizeExtent.cy =2500; // 4 x 1.5 width/heiht ratio
Reset(); //Initialize panels
// Cursors for normal (arrow) and dragging (crosshairs)
hArrow = LoadCursor( NULL, IDC_ARROW);
hCrossHairs= LoadCursor( NULL,IDC_CROSS);
return S_OK;
}
HRESULT CTowerCtl::OnDraw( ATL_DRAWINFO& di)
{
RECT& rc=*(RECT*)di.prcBounds;
RECT rect;
TEXTMETRIC tm;
HPEN hPen,hPenOld;
HBRUSH hBrush, hBrushOld;
HFONT hFont, hFontOld;
int I,j,k,yCaption;
USES_CONVERSION;
//Paint control background
hBrush = CreateSolidBrush( m_clrBackColor);
hBrushOld=(HBRUSH) SelectObject(di.hdcDraw,hBrush);
FillRect(di.hdcDraw,&rc,hBrush);
// Set caption color and font
SetBkMode(di.hdcDraw,TRANSPARENT);
SetTextColor(di.hdcDraw,m_clrForeColor);
if (m_pFont)
{
CComQIPtr<Ifont> pFont(m_pFont);
pFont->get_hFont(&hFont);
hFontOld=(HFONT) SelectObject( di.hdcDraw,hFont);
}
else
{
hFont = (HFONT) GetStockObject( SYSTEM_FONT);
hFontOld=NULL;
}
// Display caption
CopyRect( &rect, &rc);
DrawText( di.hdcDraw,OLE2A( m_bstrCaption ), -1,
&rect, DT_CENTER | DT_TOP );
// Compute height of Caption area
GetTextMetrics( di.hdcDraw,&tm);
yCaption = tm.tmHeight + tm.tmExternalLeading;
// Compute width and height of a panel
iLeft = rc.left;
iWidth = (rc.right - rc.left)1/3;
iHeight = rc.bottom - rc.top - yCaption;
// Draw column dividers
hPen = CreatePen( PS_SOLID, 1, m_clrForeColor );
hPenOld = (HPEN) SelectObject( di.hdcDraw, &hPen);
MoveToEx(di.hdcDraw,rc.left+iWidth, rc.top+yCaption, 0 );
LineTo( di.hdcDraw,rc.left+iWidth, rc.bottom );
MoveToEx(di.hdcDraw, rc.left+iWidth*2, rc.top+yCaption, 0 );
LineTo( di.hdcDraw,rc.left+iWidth*2, rc.bottom);
// Outer loop: for each panel
for (i=0 i < 3 i++)
{
rect.top = rc.top + yCaption;
rect.bottom = rect.top + iHeight/NUM_BLOCKS;
// Inner loop: for each colored block in panel…
for (j=0; j <NUM_BLOCKS; j++)
{
if (nPanel [I] [j] != EMPTY)
{
// Determine left and right edges of block
k= NUM_BLOCKS - 1 - nPanel [I] [j] ;
rect.left = rc.left + iWidth*I +
(iWidth*K) /(2*NUM_BLOCKS) + 1;
rect.right = rect.left +
iWidth*(nPanel[I][j]+1)/NUM_BLOCKS - 1;
// Fill rectangle with block ’ s color
hBrush = CreateSolidBrush( color[nPanel[I][j]] );
SelectObject( di.hdcDRAW, &rect,hBrush );
}
rect.top = rect.bottom;
rect.bottom += iHeight/NUM_BLOCKS;
}
}
SelectObject(di.hdcDraw, &hPenOld );
SelectObject( di.hdcDraw, &hBrushOld );
if (hFontOld)
SelectObject( di.hdcDraw, &hFontOld );
DeleteObject(hPen);
DeleteObject(hBrush);
return S_OK;
}
STDMETHODIMP CTowerCtl::get_nCurrentBlock( short *pVal_
{
*pVal = nPanel[nFromPanel][nBlockNdx];
return S_OK;
}
STDMETHODIMP CTowerCtl::Reset()
{
int I;
for (I=0; I < NUM_BLOCKS; I++) // Initialize panel array
{
nPanel [0][i] = I; // Panel 0 = 0,1,2,3,4,5,6
nPanel[1][i] = EMPTY; // Panel 1= 7,7,7,7,7,7,7
nPanel[2][i] = EMPTY; // Panel 2= 7,7,7,7,7,7,7
}
nBlockNdx =0; // Ndx of block being moved
nFrom Panel =0;
FireViewChange();
return S_OK
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// CTowerCtrl message handlers
LRESULT Ctowertl::OnLButtonDown( UINT uMsg, WPARAM wParam,
LPARAM Lparam, BOOL& bHandled)
{
short i = 0;
int x = LOWORD( Lparam);
nFromPanel = GetPanel( x );
while (nPanel[nFromPanel][I] == EMPTY && i < NUM_BLOCKS)
i++; // i =ndx of smallest block
if (I < NUM_BLOCKS) // Does panel have a block?
{
bMoving = TRUE; // If so, block is moving
nBlockNdx = I; // Save ndx of the block
Fire+FromPanel( nFromPanel); // Tell container panel #
// change cursor to crosshairs while dragging
SetCursor( hCrossHairs);
}
return 0;
}
LRESULT CTowerCtl::OnLButtonUp(UINT uMsg,WPARAM wParam,
LPARAM Lparam,BOOL& bHandled)
{
short i = 0, nToPanel;
int x= LOWORD( Lparam );
nToPanel = GetPanel( x); // Panel where block is dropped
if (bMoving && nToPanel != nFormPanel)
{
while (nPanel[nToPanel][i]==EMPTY && I <NUM_BLOCKS-1)
i ++; // I - ndx of panel’ s smallest block
// Is dragged block smaller than smallest block in panel?
If (nPanel [nFromPanel][nBlockNdx]< nPanel [nToPanel][I])
{
if (nPanel [nToPanel][I] != EMPTY)
--I;
nPanel[nToPanel][I] = nPanel[nFromPanel][nBlockNdx];
nPanel[nFromPanel][nBlockNdx] = EMPTY;
Fire_ToPanel( nToPanel ); // Tell container
if (i== 0 & & nToPanel == 2) // If we’ve filled
{ // the third panel,
Fire_Winner (); // fire Winner event
Reset (); // and reset game
}
FireViewChange();
}
else // If invalid drop,
Fire_Error (); // tell container
}
// Restore original arrow cursor
SetCursor( hArrow);
bMoving = FALSE; // Not moving now
return 0;
}
short CTowerCtrl :: GetPanel ( int x )
{
short I = 0;
x -= iLeft; //Convert x to window coords
if (x > iWidth) //Hit test:
i =1; //I = 0 for first panel
if (x > iWidth*2) // =1 for second panel
i = 2; // = 2 for third panel
return i; // Return panel number
}
步 骤 10 : 加 入 Abou t框
除 了 属 性 页 外 , ATL Object Wizar d( 对 象 向 导 ) 也 可 将 一 个 对 话 资 源 作 为 一 个 独立 的 对 象 加 入 到 控 件 项 目 中 。 这 一 节 将 通 过 为 TowerATL 项 目 创 立 一 个 About 框 , 从 而 解 释 如 何 在 ActiveX 控 件 中 合 并 对 话 框 对 象 。 许 多 开 发 者 不 愿 意 在ActiveX 控 件 中 加 入 About 框 ,这 是 可 以 理 解 的 ,因 为 即 使 一 个 最 简 单 的 对 话 框 , 也 将 在 OCX 文 件 中 增 加 至 少 2KB 资 源 数 据 。但 是 ,一 个 About 框 对 于 你 自 己 的控 件 可 能 需 要 的 任 何 类 型 的 对 话 框 而 言 , 是 一 个 非 常 好 的 说 明 。 更 重 要 的 是 , 项
目 设 计 迫 使 我 们 尽 可 能 地 接 近 原 来 的 Tower 控 件 , 原 来 的 Tower 控 件 中 包 括 由
MFC ControlWizard 支 持 的 自 己 的 About 框 。
图 10-15 为 ATL ActiveX 控件项目加入对话框资源
这 一 节 的 内 容 是 自 我 包 容 的 ,与 练 习 的 其 他 部 分 保 持 独 立 性 。它 将 在 同 一 个 位 置说 明 包 含 一 个 对 话 框 资 源 的 所 有 必 须 的 步 骤 ,所 以 如 果 你 希 望 不 影 响 项 目 的 其 余部 分 , 你 可 用 忽 略 这 一 节 的 内 容 。 为 了 加 入 TowerATL 的 About 框 , 再 次 允 许ATL Object Wizard ( 对 象 向 导 ), 这 一 次 , 在 左 框 中 选 择 M iscellaneou s, 并 双 击D ialog ( 对 话 框 ) 图 标 。 按 照 图 10-15 命 名 对 象 TowerBox , 并 单 击 OK , 关 闭 对话 框 。
Object Wizar d( 对 象 向 导 )为 类 CTowerBox 产 生 源 代 码 ,它 被 包 含 在 TowerBox.cpp 和 TowerBox.h 文 件 中 。 如 果 你 想 使 用 在 另 一 个 项 目 中 的 一 个 About 框 资 源 , 只需 要 将 对 话 框 脚 本 合 并 到 TowerATL.rc 中 , 并 在 Resource.h 文 件 中 加 入 必 要 的#define 语 句 。如 果 不 是 这 样 , 在 对 话 框 编 辑 器 中 设 计 About 框 ,在 你 关 闭 Object W izard ( 对 象 向 导 ) 时 , 对 话 框 编 辑 器 将 自 动 启 动 。 下 面 是 对 话 框 可 能 的 样 子 :
这 个 对 话 框 借 用 了 项 目 的 位 图 , 所 以 这 种 装 饰 图 并 不 代 表 资 源 控 件 的 奢 侈 浪 费 。首 先 将 TowerCtl.bmp 文 件 从 Code\Chapter.10\TowerATL 文 件 夹 内 拷 贝 到 你 的 项目 中 , 覆 盖 有 ATL Object Wizard ( 对 象 向 导 ) 提 供 的 相 同 名 字 的 B M P 文 件 。 用Picture( 图 像 ) 工 具 将 图 像 放 在 对 话 框 编 辑 器 内 , 然 后 弹 出 Porpertie s(属性)对话 框 , 并 在 Type( 类 型 ) 框 内 选 择 Bitmap ( 位 图 ),在 Image ( 图 像 ) 框 内 选 择标 识 符 。最 后 一 行 的 编 辑 框 是 可 选 的 ,仅 仅 为 了 方 便 显 示 控 件 正 在 其 上 运 行 的 线程 的 标 识 符 。 这 将 使 我 们 可 用 在 独 立 公 寓 中 运 行 的 时 候 , 确 认 一 个 像 TowerATL 这 样 的 公 寓 线 程 控 件 的 动 作 。 给 编 辑 框 分 配 一 个 IDC_THREAD_ID 标 识 符 , 并在 Propertie s( 属 性 ) 对 话 框 内 设 置 Read Only ( 只 读 ) 复 选 框 。
在 完 成 设 计 About 框 后 , 将 对 话 框 资 源 存 盘 , 然 后 在 ClassView 窗 格 中 用 鼠 标 右键 单 击 ITowerCtl ,并 选 择 Add Metho d( 加 入 方 法 )命 令 。如 下 所 示 键 入 AboutBox 作 为 新 方 法 的 名 字 。 与 Reset 方 法 类 似 , AboutBox 没 有 参 数 。
在 文 本 编 辑 器 中 打 开 TowerBox.h 文 件 , 并 在 OnInitDialog 函 数 中 加 入 下 面 灰 色显示的指令。在 About 对 话 框 出 现 之 前 , 代 码 将 从 系 统 中 提 取 线 程 标 识 符 , 并 将
其 写 入 到 IDC_THREAD_ID 编 辑 框 中 :
LRESULT OnInitDialog(UINT uMsg, WPARAM wParam,
LPARAM lParam, BOOL& bHandled)
{
return 1; // Let the system set the focus
}
然 后 重 新 打 开 TowerCtl.cpp 文 件 , 并 在 新 AboutBox 函 数 中 加 入 这 些 行 :
STDMETHODIMP CTowerCtl::AboutBox()
{
CTowerBo x dlgAbout ;
dlgAbout.DoModal() ; return S_OK;
}
当 一 个 包 容 器 应 用 程 序 调 用 控 件 的 AboutBox 方 法 时 , 这 个 函 数 将 创 立 一 个CTowerBox 对 象 , 并 激 活 这 个 对 话 框 。 下 一 节 中 的 Game2 程 序 证 明 了 一 个 应 用程 序 如 何 通 知 TowerATL 显 示 它 的 About 框 。
步 骤 11 : 建 立 和 测 试 TowerATL ActiveX 控 件
选 择 Release MinDependency ( 或 M inDep ) 配 置 , 并 建 立 TowerATL 控 件 , 在
Visual C++ 成 功 编 译 、 链 接 和 注 册 这 个 控 件 之 后 , 你 可 以 在 任 何 ActiveX 软 件 包
容 器 应 用 程 序 中 测 试 完 成 的 产 品 。图 10-16 显 示 了 在 Test Contain e(r 测 试 包 容 器 )
实 用 程 序 中 带 有 属 性 工 作 表 的 TowerATL 的 样 子 。 在 将 TowerATL 加 入 到 Test Containe r( 测 试 包 容 器 ) 窗 口 之 后 , 单 击 Properties ( 属 性 ) 工 具 , 以 显 示 控 件的 属 性 工 作 表 。
图 10-16 在 Test Containe r(测试包容器)内显示的 TowerATL 属性工作表
为 了 说 明 ,可 以 在 Test Container 中 加 载 另 一 个 TowerATL 控 件 的 实 例 ,并 用 Invoke M ethods( 激 活 方 法 ) 工 具 激 活 每 一 个 实 例 的 About 框 。 你 将 会 发 现 两 个 实 例 中显 示 在 About 框 中 的 线 程 标 识 符 是 相 同 的 , 这 证 明 Test Container( 测 试 包 容 器 ) 在 一 个 线 程 上 创 建 所 有 的 控 件 实 例 。另 一 方 面 ,Internet Explorer 在 一 个 独 立 的 线程 上 运 行 每 一 个 窗 口 , 我 们 可 以 用 显 示 在 列 表 10-5 中 的 简 单 的 TowerCtl.htm 文档 证 明 这 一 点 。 在 配 套 光 盘 的 Code\Chapter.10\TowerAtl 文 件 夹 下 找 到 这 个 文 档 。如 果 你 创 建 了 你 自 己 版 本 的 TowerATL , 首 先 用 控 件 的 类 标 识 符 放 置 这 个 文 档 的classid 入 口 , 从 项 目 的 IDL 文 件 中 拷 贝 字 符 串 , 这 就 和 我 们 前 面 对 Tumble2.htm 文 档 的 操 作 一 样 。 然 后 按 照 这 些 步 骤 进 行 试 验 :
列表 10-5 TowerCtl.htm 文件
<OBJECT
classid="clsid:3B365FBA-C3AE-11D1-BEC9-E0F4E352507A"
id=tower
>
</OBJECT>
<SCRIPT LANGUAGE="VBSCRIPT">
sub tower_Error
.tower AboutBox()
end sub
</SCRIPT>
</BODY >
</HTML >
-
运 行 Internet Explorer,从 File( 文 件 ) 菜 单 中 选 择 Ope n(
打 开 ) 命 令 , 然 后 浏 览 , 并 找 到 TowerCtl.htm 。 如 果 控 件 已 经 正 确 的 注 册 在 你 的 系统 中 , 打 开 这 个 文 档 , 将 显 示 TowerATL 窗 口 。
-
从 同 样 的 菜 单 中 选 择 New ( 新 建 ), 打 开 一 个 显 示 控 件
的 复 制 窗 口 。
-
在 这 两 个 窗 口 中 的 每 一 个 窗 口 中 , 拖 动 一 个 彩 色 块 到
一 个 小 一 些 的块 , 这 将 启 动 控 件 的 Error 事 件 。在 事 件 启 动 的 任 何 时 候 , 文 档 的 脚 本将 激 活 控 件 的 About 框 。
比 较 显 示 在 每 一 个 About 框 中 的 线 程 标 识 符 ( 图 10-17 ), 可 以 发 现 Internet Explorer 在 独 立 的 STA 公 寓 中 运 行 每 一 个 控 件 实 例 。因为 TowerATL 采 用 公 寓 线程 模 型 , 所 以 并 不 需 要 在 任 两 个 实 例 之 间 进 行 线 程 间 调 度 , 这 对 于 像 Internet Explorer 这 样 的 STA 客 户 是 理 想 的 。但 是 我 们 为 TowerATL 选 择 了 单 一 非 线 程 模型 , 因 为 COM 将 被 强 迫 调 度 所 有 与 它 有 关 的 交 互 作 用 , 所 以 控 件 的 第 二 个 实 例将 比 第 一 个 实 例 运 行 得 慢 很 多 。
图 10-17 在分离的 STA 公 寓 中 运 行 TowerATL 控件
配 套 光 盘 提 供 了 一 个 名 为 Game2 的 程 序 , 它 是 专 门 为 显 示 新 TowerATL 控 件 而设 计 的 。 Game2.rc 文 件 位 于 Code\Chapter.10\Game2 文 件 夹 中 , 引 用 了 CD 上 提供 的 TowerATL.ocx 控 件 的 类 标 识 符 的 值 。如 果 你 想 要 用 你 自 己 创 立 的 TowerATL 版 本 运 行 Game2 , 仍 需 要 从 你 的 TowerCtl.idl 文 件 中 拷 贝 正 确 的 类 标 识 符 , 来 替换 Game2.rc 中 的 标 识 符 字 符 串 。 在 重 建 Game2 程 序 之 前 , 在 对 话 框 脚 本 的IDC_TOWERATL 语 句 中 粘 贴 新 类 标 识 符 字 符 串 :
IDD_GAME2_DIALOG DIALOGEX 0, 0, 295, 125
.
.
.
WS_TABSTOP,5,20,225,100
END
与 第 9 章的 Tower 项 目 相 比 , TowerATL 需 要 在 MFC 为 我 们 屏 蔽 的 细 节 上 更 加努 力 和 更 加 注 意 。所 以 从 这 一 点 说 , 值 得 总 结 我 们 在 用 ATL 代 替 MFC 控 件 时 的得 失 。 损 失 很 容 易 发 现 : 更 多 的 工 作 量 。 然 而 , 作 为 补 偿 , 我 们 得 到 了 一 个 小 得多 的 可 执 行 映 像 , 尽 管 最 开 始 你 可 能 不 这 么 认 为 。 新 TowerATL.ocx 有 70KB , 这 是 我 们 在 第 9 章 中 创 建 的 Tower.ocx 文 件 大 小 的 3 倍 。 但 是 如 果 你 考 虑 到Tower.ocx 需 要 的 实 时 库 , 控 件 的 ATL 版 本 的 实 际 需 要 的 存 储 器 比 原 来 的 MFC 版 本 小 1 兆 多 。 TowerATL.ocx 可 以 在 World Wide Web 上 传 送 , 可 以 在 一 个 用 户的 浏 览 器 中 运 行 , 而 不 需 要 与 它 一 起 拖 动 其 他 文 件 。
可 是 , 即 使 通 过 一 个 较 快 的 调 制 解 调 器 , 我 们 可 预 料 TowerATL.ocx 的 下 载 时 间将 超 过 20s 。 这 对 于 用 户 来 说 是 一 个 明 显 的 时 间 投 资 , 而 用 户 的 耐 心 可 能 已 经 被
其 他 巨 大 的 组 件 和 点 缀 页 面 的 图 像 所 消 耗 了 。 Internet 上 的 ActiveX 控 件 面 对 唯一 的 障 碍 是 用 户 事 先 通 常 对 要 求 并 不 明 确 , 并 且 可 能 不 愿 意 在 这 方 面 多 花 时 间 。可 以 理 解 的 是 , 急 躁 经 常 可 以 战 胜 好 奇 心 。 为 Internet 编 写 一 个 成 功 的 ActiveX 控 件 的 诀 窍 是 使 它 在 Web 页 上 看 起 来 足 够 小 , 并 在 好 奇 心 被 击 败 之 前 准 备 运 行 。
现 在 ATL 提 供 了 编 写 小 ActiveX 控 件 , 缩 短 直 接 COM 编 程 的 最 佳 途 径 。库 代 表了 大 小 和 劳 动 量 之 间 , 以 及 可 能 和 实 际 之 间 的 折 衷 。 因 为 ATL 用 优 秀 的 结 果 回报 了 合 理 的 开 发 努 力 , 所 以 这 个 折 衷 是 成 功 的 。
对 比 组 件 模 型
在 结 束 这 本 处 理 ActiveX 控 件 的 书 之 前 , 让 我 们 后 退 一 步 , 从 一 个 安 全 的 距 离 来考 虑 这 个 主 题 。 当 着 手 一 个 组 件 项 目 时 , 一 个 开 发 者 不 能 仅 仅 衡 量 像 ATL 这 样的 支 持 技 术 的 正 面 和 负 面 因 素 , 而 必 须 首 先 考 虑 是 否 使 用 ActiveX 控 件 。 像 其 他软 件 技 术 一 样 , ActiveX 控 件 对 于 某 些 情 况 是 合 适 的 , 但 并 不 适 用 于 其 他 情 况 。如 果 组 件 是 作 为 一 个 嵌 入 应 用 程 序 单 独 计 划 的 , 并 且 永 远 不 会 在 一 个 Web 页 上应 用 , 那 么 ActiveX 需 要 较 高 的 成 本 。 通 过 在 C O M 和 ActiveX 服 务 库 中 拖 动 , 即 使 是 一 个 向 Pulse 这 样 的 简 单 控 件 将 在 存 储 器 中 安 装 许 多 K B 的 代 码 , 而 且 可能 显 著 地 降 低 调 用 应 用 程 序 的 加 载 进 程 速 度 。
有 另 一 种 解 决 方 案 。将 Pulse 设 计 为 一 个 一 般 的 动 态 链 接 库 ,而 不 是 一 个 ActiveX
控 件 , 这 将 以 一 个 线 程 安 全 的 形 式 保 留 软 件 和 可 用 的 组 件 , 而 不 用 花 费 ActiveX 和 COM 那 样 的 代 价 。 客 户 应 用 程 序 向 库 在 每 一 个 要 求 的 时 间 间 隔 结 束 时 调 用 的回 调 函 数 返 回 一 个 指 针 , 而 不 是 建 立 一 个 处 理 程 序 函 数 来 接 收 事 件 。既 然 在 新 外壳 下 , 一 个 事 件 处 理 程 序 仅 仅 是 一 个 回 调 , 那 么 结 果 是 一 样 的 。 如 果 你 想 了 解 一个 Pulse 的 DLL 版 本 的 模 样 , 你 可 用 在 配 套 光 盘 的 Chapter.10\PulseDLL 文 件 夹中 找 到 这 样 的 一 个 项 目 的 源 文 件 。 Pulse 的 动 态 链 接 库 版 本 的 最 有 趣 的 特 点 是 它的 大 小 : 3KB , 而 ActiveX 控 件 版 本 需 要 37K B 。 而 且 PulseDLL 不 需 要 COM 。
这 个 简 短 的 对 比 的 目 的 仅 仅 是 想 说 明 这 个 问 题 的 各 个 方 面 ,提 醒 大 家 注 意 至 少 在某 些 情 况 下 , 低 级 动 态 链 接 库 仍 然 在 世 界 组 件 软 件 中 占 据 一 席 之 地 。这 并 不 是 建议 所 有 不 用 于 Internet 的 组 件 必 须 用 动 态 链 接 库 编 写 。事 实 上 远 非 如 此 , ActiveX 超 越 动 态 链 接 库 而 提 供 了 显 著 的 方 便 性 , 特 别 是 在 处 理 众 所 周 知 的“ 版 本 ” 问 题时 更 是 如 此 。动 态 链 接 库 的 吸 引 力 由 于 它 的 第 二 版 的 发 布 而 降 低 了 ,而 这 个 问 题对 ActiveX 控 件 的 影 响 并 没 有 那 么 大 。 例 如 , 假 设 我 们 决 定 , 如 果 它 调 用timeSetEvent 和 timeKillEvent 应 用 程 序 编 程 接 口 函 数 , 而 不 依 赖 于 系 统 的 Sleep 计 时 器 的 行 为 , Pulse 将 是 一 个 更 精 确 的 计 时 器 。 放 置 后 的 版 本 将 向 新 客 户 提 供新 对 象 和 能 力 , 而 像 Hour2 这 样 的 旧 客 户 应 用 程 序 将 像 以 前 一 样 嵌 入 到 新 Pulse 中 , 不 需 要 改 变 任 何 东 西 。 甚 至 用 新 版 本 覆 盖 旧 Pulse.ocx 文 件 , 也 不 会 影 响 客户 应 用 程 序 , 而 不 论 它 们 是 否 利 用 扩 展 特 性 。 在 利 用 一 个 动 态 链 接 库 的 高 版 本时 , 这 种 稳 定 性 是 非 常 难 以 达 到 的 。