第 四 部 分 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 文 件 夹 来 显 示 控 件 列 表 。

第 四 部 分 A ctiveX 控 件 - 图1

图 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

第 四 部 分 A ctiveX 控 件 - 图2

图 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 , 将 找 到 注 册 表 中 的 如 下 层 次 :

第 四 部 分 A ctiveX 控 件 - 图3

位 于 窗 口 底 部 的 32 位 数 字 的 号 码 代 表 Timer ActiveX 控 件 的 CLSID 。 用 相 同 的方 式 搜 索 ielabel.ocx 将 显 示 代 表 Label ActiveX 控 件 的 CLSID :

99b42120-6ec7-11cf-abc7-00aa00a47dd2

应 用 手 头 的 这 两 个 号 码 ,你 可 以 编 写 一 个 简 单 的 H T M L 文 档 ,使用 Timer 和 Label

控 件 来 显 示 文 本 , 它 们 不 停 地 翻 滚 , 永 无 休 止 地 从 彩 色 框 底 部 弹 出 :

第 四 部 分 A ctiveX 控 件 - 图4

如 果 想 看 动 画 , 请 使 用 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 ( 调 用 方 法 和 属 性 ) 工 具 成 为 可 以 启 用 的 。

第 四 部 分 A ctiveX 控 件 - 图5

图 8-3 通过 Tools (工具)菜单调用的 Test Container 实用程序

第 四 部 分 A ctiveX 控 件 - 图6

被 选 中 的 ActiveX 控 件 可 以 通 过 它 的 方 法 函 数 编 程 。 单 击 Invoke Method s( 调 用方 法 ), 或 从 Control 菜 单 中 选 择 相 应 的 命 令 , 来 打 开 如 图 8-4 所 示 的 Invoke M ethods 对 话 。 M ethod Nam e( 方 法 名 ) 组 合 框 的 下 拉 式 列 表 将 所 有 控 件 方 法 归类 , 大 致 可 分 为 普 通 方 法 和 属 性 方 法 两 类 。 普 通 方 法 在 下 拉 式 列 表 中 被 标 为M ethod 。 属 性 方 法 被 标 为 PropGet 或 PropPut , 究 竟 标 记 为 哪 一 个 , 主 要 取 决 于它 们 所 对 应 的 是 检 索 属 性 值 的“ 获 取 ” 方 法 , 还 是 写 属 性 值 的“ 放 置 ” 方 法 。 例如 图 8-4 显 示 了 Button Menu 控 件 导 出 了 两 种 方 法 类 型 , 允 许 包 容 器 通 过 获 取 和放 置 方 法 读 或 写 该 控 件 的 Caption 属 性 ( 就 是 出 现 在 按 钮 上 的 文 字 )。

第 四 部 分 A ctiveX 控 件 - 图7

图 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( 选 择 颜 色 ) 的 按 钮 显 示 范 例 颜 色 的 种 类 。 不 过 , 该 选项 目 前 不 能 将 选 择 的 颜 色 正 确 转 换 成 有 效 的 方 法 参 数 。

第 四 部 分 A ctiveX 控 件 - 图8

许 多 控 件 提 供 它 们 自 己 的 属 性 表 ,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 控 件 包 容 器 :

第 四 部 分 A ctiveX 控 件 - 图9

选 择 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 控 件 。

第 四 部 分 A ctiveX 控 件 - 图10

这 个 工 具 并 不 是 在 工 具 栏 上 永 久 存 在 ,它 是 仅 仅 为 这 个 项 目 而 使 用 的 。如 果 想 向另 一 个 项 目 添 加 Anibutton 组 件 , 你 必 须 再 返 回 步 骤 2 去 插 入 一 个 控 件 。 至 于 使控 件 进 入 对 话 工 作 区 , 没 有 特 殊 的 处 理 要 求 。 只 需 像 你 对 任 何 控 件 工 具 操 作 的 那样 , 把 它 拖 入 对 话 框 , 然 后 右 击 对 话 中 选 中 的 控 件 , 并 且 单 击 上 下 文 菜 单 上 的

Properties ,来 调 用 该 控 件 的 Properti e(s 属 性 )对 话 。图 8-5 显 示 了 Anibutton Control

Properties 对 话 的 Control 选 项 卡 , 这 就 是 我 们 将 开 始 初 始 化 控 件 的 地 方 。

第 四 部 分 A ctiveX 控 件 - 图11

图 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 ( 对 话 ) 工 具 栏 上 的 编 辑 器 测 试 模 式 开 关 :

第 四 部 分 A ctiveX 控 件 - 图12

在 新 ActiveX 控 件 窗 口 内 部 单 击 几 次 , 循 环 显 示 位 图 图 像 , 其 中 一 个 如 图 8-6 所示 。 单 击 对 话 的 O K 按 钮 来 返 回 到 编 辑 模 式 。

第 四 部 分 A ctiveX 控 件 - 图13

图 8-6 典 型 对 话 中 的 Anibutton ActiveX 控件

既 然 我 们 对 ActiveX 控 件 可 以 实 现 的 许 多 形 式 已 经 有 了 一 定 的 了 解 , 那 么 , 让 我

们 来 分 析 其 中 的 一 个 , 看 看 它 是 如 何 工 作 的 。

在 包 容 器 和 ActiveX 控 件 之 间 进 行 通 信

ActiveX 控 件 服 务 器 可 以 非 常 有 效 地 与 一 个 客 户 进 程 关 联 起 来 。 一 个 ActiveX 控件 通 常 在 作 为 动 态 链 接 库 工 作 , 这 就 意 味 着 该 控 件 在 客 户 进 程 的 地 址 空 间 执 行 , 但 这 并 非 一 定 如 此 。 由 于 这 个 原 因 , ActiveX 控 件 通 常 称 为 进 行 中 的 服 务 器 。 包容 器 程 序 在 加 载 普 通 DLL 的 时 候 , 并 不 是 通 过 调 用 LoadLibrary API 函 数 来 加 载这 个 ActiveX 控 件 。相 反 ,它 调 用 CoCreateInstance 来 申 请 Component Object Model 框 架 的 运 行 时 间 服 务 来 加 载 库 ,并 在 客 户 和 控 件 服 务 器 之 间 建 立 初 始 通 信 点 。这个 通 信 点 叫 做 接 口 。 包 容 器 所 调 用 的 一 个 接 口 , 一 般 在 图 中 用 一 个 小 圈 来 表 示 , 如 图 8-7 所 示 。 在 服 务 器 中 , 实 现 对 正 确 函 数 的 调 用 。 请 注 意 该 图 , 一 旦 初 始 接口 就 位 之 后 , 所 有 对 象 就 将 开 始 彼 此 之 间 的 对 话 , 图 中 没 有 包 括 COM 。

第 四 部 分 A ctiveX 控 件 - 图14

图 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 事 件 。

第 四 部 分 A ctiveX 控 件 - 图15

图 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 分 钟 之 后 , 三 个 控件 便 会 复 位 。

第 四 部 分 A ctiveX 控 件 - 图16

图 8-9 Hour 程 序

从 开 始 到 结 束 创 建 Hour 项 目 只 需 五 步 。

步 骤 1 : 用 AppWizard 创 建 Hour 项 目

从 环 境 的 File 菜 单 选 择 New , 在 Projects 选 项 卡 中 选 中 MFC AppWizard(exe) 图标 ,键 入 Hour 作 为 项 目 名 。Hour 是 基 于 对 话 的 应 用 程 序 ,因 此 ,单 击 AppWizard 步 骤 1 中的 D ialog Based ( 基 于 的 对 话 ) 单 选 按 钮 , 并 确 保 步 骤 2 中 的 ActiveX Controls 复 选 框 已 经 打 开 :

第 四 部 分 A ctiveX 控 件 - 图17

单 击 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控 件

第 四 部 分 A ctiveX 控 件 - 图18

在 Visual C++ 的 早 期 版 本 中 , 你 必 须 双 击 ResourceView 窗 格 中 的IDD_HOUR_DIALOG 标 识 符 , 来 启 动 对 话 编 辑 器 , 并 加 载 主 对 话 。 通 过 选 中 对话 工 作 区 中 的 “ to do ” 静 态 文 字 控 件 和 Cancel 按 钮 , 并 按 Del 键 来 删 除 它 们 。将 Progress, Static Text 和 Timer Object 工 具 从 Controls 工 具 栏 拖 到 工 作 区 之 上 ,

并 对 其 进 行 排 列 , 使 它 看 上 去 与 下 图 类 似 :

第 四 部 分 A ctiveX 控 件 - 图19

由 于 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 的 事 件 处 理 程 序 函 数 添 加 存 根 代 码 。

第 四 部 分 A ctiveX 控 件 - 图20

“ E ” 前 缀 用 来 将 OnTimeTimer1 指 定 为 一 个 事 件 处 理 程 序 函 数 。 单 击 O K 来 关闭 ClassWizard 对 话 。

第 四 部 分 A ctiveX 控 件 - 图21

图 8-10 向 CHourDlg 类 添 加 成 员 函 数

步 骤 4 : 向 Hou r.cpp 和 Hou r.h 文 件 添 加 代 码

如 果 想 查 看 一 下 ClassWizard 已 经 添 加 到 HourDlg.h 头 文 件 的 变 量 和 函 数 声 明 , 请 单 击 位 于 WizardBar 最 右 端 的 箭 头 按 钮 :

第 四 部 分 A ctiveX 控 件 - 图22

并 从 下 拉 式 菜 单 中 选 择 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。

第 四 部 分 A ctiveX 控 件 - 图23

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。 库 脚 本 显 示 了 包 容 器应 用 程 序 如 何 必 须 声 明 处 理 程 序 函 数 , 以 便 正 确 地 接 收 事 件 触 发 :

第 四 部 分 A ctiveX 控 件 - 图24

这 是 我 们 为 事 件 编 写 处 理 程 序 函 数 所 需 要 的 所 有 信 息 。但 是 对 于 提 供 了 许 多 不 同事 件 的 控 件 来 说 ,浏 览 类 型 库 还 有 更 容 易 的 方 法 ,即 将 该 控 件 的 存 在 简 单 地 通 知给 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 所 示 控 件 菜 单 中 的 一 个 项 目 时 , 你 都 应 该 能 看 到 一 个 消 息 框 。

第 四 部 分 A ctiveX 控 件 - 图25

图 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 说 明 了 这 些 步 骤 。

第 四 部 分 A ctiveX 控 件 - 图26

图 9-1 用 ControlWizard 开 始 一 个 ActiveX 控件项目

在 创 建 项 目 之 前 , ControlWizard 将 引 导 完 成 两 个 步 骤 。 本 节 将 检 查 这 个 向 导 所提 供 的 选 项 , 并 讨 论 一 个 选 项 何 时 对 于 你 的 ActiveX 控 件 来 说 是 合 适 的 , 以 及 它为 什 么 合 适 。

图 9-2 显 示 了 ControlWizard 的 打 开 屏 幕 , 它 首 先 询 问 你 项 目 中 控 件 的 数 量 。 与VBX 自 定 义 的 控 件 类 似 , 一 个 OCX 文 件 可 以 包 含 不 止 一 个 ActiveX 控 件 组 件 。请 在 对 话 顶 部 的 文 本 框 中 指 定 你 想 要 的 控 件 数 量 。 以 后 , 在 项 目 开 发 期 间 , 你 还可 以 添 加 控 件 。 使 用 ControlWizard 屏 上 的 下 一 个 选 项 , 可 以 通 过 许 可 来 限 制 控件 的 使 用 。 尽 管 许 可 支 持 选 项 在 默 认 状 态 下 是 关 闭 的 , 对 于 一 般 用 途 的 许 多 控件 , 应 该 具 有 简 短 描 述 的 许 可 保 护 。

第 四 部 分 A ctiveX 控 件 - 图27

图 9-2 ControlWizard 的 步 骤 1

步 骤 1 中 的 最 后 一 个 选 项 用 于 指 导 ControlWizard 为 控 件 生 成 帮 助 文 件 。 对 项 目帮 助 文 件 的 申 请 ,所 添 加 的 帮 助 支 持 种 类 与 从 AppWizard 中 得 到 的 相 同 。如 果 想获 得 所 生 成 的 帮 助 文 件 的 说 明 , 请 参 阅 第 2 章 开 始 部 分 的 讨 论 。

ControlWizard 的 第 二 屏 ( 图 9-3) 展 现 了 决 定 控 件 如 何 与 包 容 器 进 行 交 互 的 选 项 。第 二 步 中 的 选 项 需 要 解 释 一 下 , 因 此 , 下 面 详 细 对 它 们 说 明 。

第 四 部 分 A ctiveX 控 件 - 图28

图 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 可 以 找 到 它 。

第 四 部 分 A ctiveX 控 件 - 图29

图 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。

第 四 部 分 A ctiveX 控 件 - 图30

图 9-5 向 包 容 器 项 目 插 入 License 控 件

对 话 编 辑 器 企 图 创 建 License 控 件 的 一 个 实 例 , 这 导 致 了 一 系 列 的 函 数 调 用 。 编辑 器 首 先 调 用 控 件 的 IClassFactory2::CreateInstanceLic 方 法 , 该 方 法 依 次 调 用 控件 的 VerifyUserLicense 函 数 ,该 函 数 调 用 框 架 的 A fxVerifyLicFile 函 数 。这 个 MFC 函 数 读 取 由 szLicenseName 字 符 串 ( 这 里 是 LicenseLic) 来 标 识 的 文 件 ,如 果 该 文 件存 在 的 话 , 将 它 的 第 一 行 与 包 含 在 _szLicString 中 的 许 可 证 关 键 字 进 行 比 较 。 如果 文 件 不 存 在 , 或 第 一 行 与 许 可 证 关 键 字 不 匹 配 , A fxVerifyLicFile 将 返 回 值FALSE , 来 禁 止 对 象 创 建 。 结 果 会 出 现 Visual C++ 的 一 条 错 误 信 息 , 来 解 释 为 何失 败 :

第 四 部 分 A ctiveX 控 件 - 图31

运 行 时 间 许 可 证

如 果 许 可 证 文 件 存 在 , 并 且 , 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 框 , 并 显 示 它 的 属 性 表 的 模 型 。 不 过 , 该 控 件 所 执 行 的 没 有 一 件 是 有用 的 工 作 , 因 为 它 没 有 触 发 事 件 , 导 出 自 定 义 方 法 或 包 含 属 性 。

第 四 部 分 A ctiveX 控 件 - 图32

图 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

第 四 部 分 A ctiveX 控 件 - 图33

该 实 用 程 序 通 过 显 示 一 个 消 息 框 来 指 示 成 功 :

第 四 部 分 A ctiveX 控 件 - 图34

当 控 件 被 成 功 地 取 消 注 册 之 后 , 就 可 以 删 除 项 目 文 件 了 。

一 个 名 为 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 实 用 程 序 来在 控 件 发 生 时 监 视 它 们 的 状 态 。

第 四 部 分 A ctiveX 控 件 - 图35

图 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 属 性 , 请 键 入库 存 名 字 列 表 之 中 没 有 的 任 何 外 部 名 字 。

第 四 部 分 A ctiveX 控 件 - 图36

图 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 ” 代 码 用 来 指 示 该 属 性是 库 存 的 , 还 是 自 定 义 的 。

第 四 部 分 A ctiveX 控 件 - 图37

图 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 对 话 即 可 。

第 四 部 分 A ctiveX 控 件 - 图38

图 9-10 为 Tower ActiveX 控 件 指 定 自 定 义 事 件 及 其 参 数

Error 是 包 括 在 外 部 名 字 下 拉 式 列 表 之 中 的 库 存 事 件 的 名 字 。在 External Name 框中 键 入 它 , 而 不 是 从 列 中 选 中 它 , 表 明 classWizard 应 该 把 这 个 事 件 视 为 自 定 义的 。 这 就 演 示 了 如 何 可 以 使 用 库 存 事 件 名 以 及 它 的 调 度 标 识 符 用 于 自 定 义 事 件 。因 为 Error 库 存 事 件 至 少 需 要 七 个 参 数 , 因 此 , 通 过 使 用 自 定 义 事 件 , 而 不 是 库存 事 件 , 可 以 简 化 用 于 控 件 及 其 包 容 器 的 代 码 。

第 四 个 也 就 是 最 后 一 个 自 定 义 事 件 是 W inner, 它 用 来 通 知 包 容 器 , 用 户 已 成 功地 将 最 后 一 块 移 入 了 控 件 的 第 三 个 面 板 , 从 而 赢 得 了 这 场 游 戏 。 与 自 定 义 Error 事 件 类 似 , W inner 没 有 参 数 表 。

图 9-11 显 示 了 在 添 加 了 Tower 的 五 个 事 件 之 后 对 话 的 ActiveX Events 选 项 卡 的样 子 。

正 如 在 前 一 章 中 所 提 及 的 ,包 容 器 不 需 要 为 ActiveX 控 件 触 发 的 所 有 事 件 提 供 处理 程 序 函 数 。 例 如 , Game 包 容 器 程 序 将 忽 略 Tower 的 Click 库 存 事 件 , 而 仅 仅处 理 自 定 义 事 件 ,来 在 事 件 发 生 的 时 候 更 新 状 态 窗 口 。控 件 设 计 者 必 须 预 料 到 包容 器 可 能 需 要 的 信 息 类 型 , 并 且 提 供 事 件 来 传 达 该 信 息 , 同 时 允 许 同 一 个 包 容 器忽 略 某 些 事 件 。

第 四 部 分 A ctiveX 控 件 - 图39

图 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++ 对 话 编 辑 器 的 服 务 来 完 成 。

第 四 部 分 A ctiveX 控 件 - 图40

图 9-12 向 Tower ActiveX 控 件 添 加 消 息 处 理 程 序 函 数

步 骤 6 : 创 建 属 性 表

我 们 以 前 曾 经 看 到 , ControlWizard 向 项 目 添 加 一 个 普 通 的 属 性 页 资 源 ( 见 图 9- 6)。 本 节 将 解 释 如 何 修 正 该 资 源 , 它 最 终 将 扩 展 进 入 一 个 可 以 使 用 的 属 性 表 , 该属 性 表 允 许 用 户 在 设 计 时 查 看 和 改 变 Tower 的 属 性 。第 一 步 是 在 对 话 编 辑 器 中 修改 普 通 的 属 性 页 。 通 过 双 击 Workspace 窗 口 ResourceView 窗 格 中 的IDD_PROPPAGE_TOWER 标 识 符 来 加 载 资 源 。 选 中 对 话 工 作 区 中 的 “ to do ” 静态 文 字 控 件 , 并 删 除 它 , 用 如 下 所 示 的 静 态 标 签 和 编 辑 框 来 代 替 它 。

第 四 部 分 A ctiveX 控 件 - 图41

选 中 对 话 工 作 区 中 的 编 辑 框 , 单 击 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 选 项 卡 的 顶 部 文 本 框 中 键 入 一 个 值 , 可 以 设 置 字 符 串 限 制 :

第 四 部 分 A ctiveX 控 件 - 图42

单 击 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 类 的 六 个 成 员 变 量 。 表

  1. 提 供 了 这 些 变 量 的 简 要 说 明 。

表 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 工 具 栏 上 画 新 的 工 具 按 钮 :

第 四 部 分 A ctiveX 控 件 - 图43

对 于 这 种 目 的 , 一 个 图 标 一 般 来 说 太 大 了 , 因 此 , 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 控 件 :

第 四 部 分 A ctiveX 控 件 - 图44

Insert Control 对 话 响 应 键 盘 条 目 , 让 你 通 过 按 控 件 名 的 第 一 个 字 母 来 快 速 滚 动 列表 。 例 如 , 按 下 T 可 以 立 即 把 选 择 条 放 在 Tower 控 件 条 目 上 。

通 过 双 击 控 件 边 框 , 可 以 试 一 试 Tower 的 属 性 表 。 MFC 框 架 已 经 添 加 了 属 性 页 , 你 可 以 在 里 面 修 改 用 于 在 Tower 窗 口 中 显 示 标 题 的 控 件 和 字 体 。 图 9-13 显 示 了在 控 件 的 属 性 表 中 展 现 的 Font 页 。 在 对 话 的 其 他 三 个 选 项 卡 中 , Caption 选 项 卡

用 于 显 示 在 步 骤 6 中 修 改 的 属 性 页 。 当 你 为 标 题 键 入 新 文 字 的 时 候 ,变 化 立 即 在控 件 的 窗 口 中 反 映 出 来 。

第 四 部 分 A ctiveX 控 件 - 图45

图 9-13 Tower Control Properties 属 性 表

Test Container 可 以 显 示 Tower 事 件 的 实 时 记 录 ,当 你 调 试 一 个 ActiveX 控 件 的 时候 , 这 是 非 常 宝 贵 的 。 向 上 移 动 分 隔 栏 , 以 为 事 件 日 志 展 现 更 多 的 空 间 , 然 后 从Tower 的 第 一 个 面 板 中 拖 动 一 个 块 , 并 把 它 放 到 另 一 个 面 板 之 中 。 作 为 对 拖 放 操作 的 响 应 , Tower 触发 ClickFromPanel 和 ToPanel 事 件 , 当 它 们 发 生 的 时 候 , 在事 件 日 志 中 均 有 记 录 。 如 图 9-14 所 示 , 日 志 中 的 每 个 条 目 均 以 下 一 个 用 来 指 示事 件 在 哪 个 控 件 发 生 的 标 签 作 为 开 头 。 既 然 Test Container 可 以 嵌 入 多 个 控 件 , 那 么 , 利 用 标 签 , 在 事 件 很 多 时 , 日 志 条 目 仍 可 以 保 持 直 观 。

第 四 部 分 A ctiveX 控 件 - 图46

图 9-14 在 Test Container 中监视事件

向 ActiveX 控 件 项 目 添 加 属 性 页

正 如 我 们 在 开 发 Tower 控 件 时 所 看 到 的 ,ControlWizard 可 以 为 ActiveX 控 件 生 成一 个 单 一 属 性 页 ,来补充 MFC 提 供 的 库 存 页 。对 于 Tower 项 目 , 一 页 就 足 够 了 , 原 因 是 Caption 是 需 要 自 定 义 属 性 页 的 唯 一 一 个 可 修 改 的 属 性 。 不 过 , 带 有 多 个属 性 数 据 的 ActiveX 控 件 可 能 需 要 其 他 的 页 来 将 数 据 展 现 给 用 户 。每 个 附 加 页 都需 要 在 属 性 页 映 射 中 具 有 它 自 己 的 对 话 资 源 类 和 条 目 。 最 后 一 节 列 出 了 向ActiveX 控 件 项 目 添 加 新 属 性 页 的 必 要 步 骤 , 使 用 Tower 控 件 作 为 例 子 。

  1. 在 项 目 处 于 打 开 状 态 的 时 候 , 单 击 Insert 菜 单 上 的

    Resource 命 令 , 并双 击 列 表 中 的 D ialog 来 启 动 对 话 编 辑 器 。 你 还 可 以 展 开 对 话 资 源 列表 , 并 选 中 IDD_OLE_PROPPAGE_LARG E。 和 以 前 一 样 , 新 属 性 页的 大 小 并 不 重 要 。 按 照 你 所 希 望 的 那 样 设 计 新 属 性 页 , 然 后 在 工 作 区中 选 中 对 话 窗 口 ,并 单 击 View 菜 单 上 的 Properties 命 令 ,来 展 现 D ialog Properties 框 。 在 Styles 选 项 卡 中 , 关 闭 Titlebar 复 选 框 , 将 对 话 样 式设 为 Child ,禁 用 边 框 。如 果 你 选 中 了 IDD_OLE_PROPPAGE_LARGE , 来 启 动 对 话 编 辑 器 ,这 些 设 置 就 已 经 为 你 做 好 了 。Styles 选 项 卡 应 该 是下 图 的 样 子 :

第 四 部 分 A ctiveX 控 件 - 图47

  1. 按 Ctrl+S 来 保 存 新 对 话 资 源 , 然 后 单击 View 菜 单 中 的

    ClassWizard 命 令 。 当 询 问 你是 否 愿 意 为 对 话 资 源 创 建 一 个 新 类 的 时 候 , 单 击 O K 按 钮 表示 接 受 。 为 新 类 键 入 一 个 名 字 , 选 择 COlePropertyPage 作 为 基类 :

第 四 部 分 A ctiveX 控 件 - 图48

  1. 为 新 页 添 加 所 需 要 的 所 有 成 员 变 量 , 然 后 退 出

    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 。

  1. 在 文 本 编 辑 器 或 字 符 串 编 辑 器 中 打 开 项 目 的 RC 文 件 ,

    并 添 加 两 个 字符 串 资 源 。 第 一 个 字 符 串 用 来 标 识 系 统 注 册 表 中 已 注 册 的 属 性 页 , 第二 个 字 符 串 用 来 保 持 在 属 性 表 对 话 中 出 现 的 选 项 卡 标 签 :

STRINGTABLE DISCARDABLE BEGIN

IDS_TOWER "Tower Control"

IDS_TOWER_PPG "Tower Property Page"

IDS_TOWER_PPG_CAPTION "Caption"

END

  1. 如 果 你 在 上 一 步 中 使 用 文 本 编 辑 器 来 创 建 新 字 符 串 资

    源 , 那 么 请 向Resource.h 文 件 为 IDS_TOWER_PPG2 和IDS_TOWER_PPG_NEWPAGE 常 数 添 加 定 义 :

如 果 你 在 上 一 步 中 使 用 的 是 字 符 串 编 辑 器 , 那 么 添 加 这 些 行 就 不 是 必要的了,因为 Visual C++ 在 你 保 存 字 符 串 资 源 的 时 候 会 自 动 写 下 定 义 。

  1. 在 文 本 编 辑 器 中 , 打 开 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 显 示 了 控 件 、 控 件 所 包 含 的 目 标 和 目 标 实 现 的 接 口 之 间 的层

第 四 部 分 A ctiveX 控 件 - 图49

图 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 。

第 四 部 分 A ctiveX 控 件 - 图50

图 10-2 启动 ATL COM AppWizard

在 出 现 一 个 对 话 框 列 出 控 件 选 项 时 ( 图 10-3 ), 单 击 Finish ( 完 成 ) 按 钮 , 来 接受 默 认 设 置 。 这 些 设 置 指 定 新 的 控 件 作 为 不 使 用 MFC 的 动 态 链 接 库 来 运 行 。 对于 标 签 为 Allow Merging Of Proxy/Stub Code( 允 许 代 理 和 存 根 代 码 的 合 并 ), 它只 对 DLL 服 务 器 类 型 可 用 。 选 择 这 个 选 项 告 诉 COM AppWizard , 来 设 置 项 目 来链 接 调 度 代 码 到 控 件 的 可 执 行 映 像 中 , 产 生 一 个 单 一 的 DLL 文 件 , 其 中 包 含 控件 及 其 代 理 /存 根 指 令 。清 除 选 项 告 诉 COM AppWizard 来 编 写 调 度 代 码 到 单 独 的名 为 DllData.c 文 件 , 这 样 , 可 以 减 少 最 终 可 执 行 文 件 的 整 个 大 小 。 在 本 练 习 的第 5 步 中 , 我 们 将 更 多 地 讨 论 控 件 的 代 理 和 存 根 代 码 。

第 四 部 分 A ctiveX 控 件 - 图51

图 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 菜 单 也 提 供 了 到 相 同 命 令 的 访 问 。

第 四 部 分 A ctiveX 控 件 - 图52

10-4 调用 ATL Wizard 支 持 的 常 用 方 法

对 于 17 种 不 同 的 目 标 类 型 ,Object Wizard 自 动 添 加 ATL 代 码 到 项 目 中 , 其 中 的 一 些 在 表 10-4 中 列 出 。在 开 发 ActiveX 控 件 项 目 时 ,通 常 选 择 Full Contro l( 完 全 控 制 ) 选 项 , 但 是 , 此 选 项 假 定 控 件 将 显 示 一 个 窗 口 , 并以 属 性 表 来 支 持 这 种 方 法 , 而 且 还 有 一 个 表 达 图 标 。 对 于 小 的 不 可 见 的控 件 。例 如 Pulse ,通 常 ,从 那 里 选 择 一 种 更 简 单 的 组 件 类 型 ,并 建 立 它 , 而 不 是 以 后 再 删 除 不 必 要 的 代 码 , 这 样 更 合 适 。Pulse 项 目 不 需 要 许 多 启动 程 序 所 使 用 的 代 码 , 所 以 , 在 向 导 对 话 框 的 左 窗 格 中 选 择 Objects ,并双 击 标 签 为 Simple Object( 简 单 目 标 ) 的 图 标 , 如 图 10-5 所 示 。

第 四 部 分 A ctiveX 控 件 - 图53

图 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 控 件 支 持 的 所 有 接 口 继 承 而 来 。

第 四 部 分 A ctiveX 控 件 - 图54

图 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 执 行 相 反 的 转 换 。

第 四 部 分 A ctiveX 控 件 - 图55

图 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( 加 入 属 性 )命 令 :

第 四 部 分 A ctiveX 控 件 - 图56

这 将 激 活 Add Property To Interface ( 为 接 口 加 入 属 性 ) 对 话 框 , 如 图 10-8 所 示 , 它 与 第 9 章 中 所 遇 到 的 类 向 导 的 Add Property ( 加 入 属 性 ) 对 话 框 所 要 求 的 信 息是 一 样 的 。

第 四 部 分 A ctiveX 控 件 - 图57

图 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 中 , 字 的 属 性 通 常 被 用 作 获 取 /放 置 方 法 的 速 记 形 式 , 它 可 以 让 一 个 客 户 访问 属 性 。

第 四 部 分 A ctiveX 控 件 - 图58

图 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( 加入 方 法 ) 命 令 :

第 四 部 分 A ctiveX 控 件 - 图59

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 文 件 中 , 我 们 将 在 为 控 件 加 入 一 个 事 件 函 数 后 , 对 其 进 行编 辑 。

第 四 部 分 A ctiveX 控 件 - 图60

图 10-10 Add Method to Interface 对 话 框

步 骤 5 : 加 入 Pulse 事 件

在 我 们 开 始 编 码 之 前 , 为 项 目 添 加 的 最 后 一 个 部 分 是 Pulse 事 件 , 它 将 在 每 一 个时 间 间 隔 到 来 时 启 动 。 在 ATL 以 前 的 版 本 中 , 在 控 件 中 增 加 一 个 事 件 需 要 一 点手 动 操 作 , 包 括 产 生 一 个 事 件 接 口 的 GUID 标 识 符 , 编 辑 IDL 文 件 和 运 行 一 个称 为 ATL Proxy Generator 的 工 具 。 但 随 着 库 的 第 三 版 的 发 布 , 这 个 过 程 变 得 更加 简 单 和 友 好 。 现 在 为 控 件 增 加 事 件 和 增 加 方 法 和 属 性 一 样 容 易 , 仅 仅 需 要 一 点时 间 对 项 目 的 IDL 文 件 进 行 编 译 。

在 我 们 将 讨 论 发 生 在 屏 幕 之 后 的 事 情 之 后 ,下 面 给 出 了 产 生 新 事 件 代 码 过 程 中 所需 要 的 步 骤 列 表 。 图 10-11 说 明 了 4 个 步 骤 。

  1. 用 鼠 标 右 键 单 击 ClassView 窗 格 中 的 _IPulseCtlEvents 入 口

    , 并 从 菜 单中 选 择 Add Method ( 加 入 方 法 )命 令 。在 Add Method To Interfac e( 为接 口 加 入 方 法 ) 对 话 框 中 , 选 择 void 返 回 类 型 , 将 事 件 函 数 命 名 为Pulse , 然 后 单 击 OK 关 闭 对 话 框 。

  2. 找 到 Workspace 窗 口 中 的 FileView 窗 格 , 用 鼠 标 右 键 单 击

    Pulse.idl 入口 , 然 后 选 择 Compile ( 编 译 ) Pulse.idl 命 令 。 这 将 启 动 M IDL 编 译器 , 它 将 产 生 类 型 库 文 件 Pulse.tlb , 并 加 入 到 项 目 中 。

  3. 当 MIDL 编 译 器 结 束 工 作 后 , 切 换 回 ClassView 窗 格 , 然 后

    用 鼠 标 右

键 单 击 CPulseCtl 入 口 。 从 菜 单 中 选 择 Implement Connection Poin t( 实现 连 接 点 ) 命 令 , 以 显 示 同 名 字 的 对 话 框 。

  1. 设 置 标 有 _IpulseCtlEvents 的 复 选 框 , 并 单 击 O K 关 闭 对

    话 框 。

项 目 的 IDL 文 件 底 部 一 半 的 地 方 列 出 了 名 为 PULSELib 的 library 块 , 它 将 向M IDL 编 译 器 描 述 新 Pulse 事 件 。 当 为 一 个 控 件 的 方 法 和 属 性 搜 索 类 型 库 时 , 某些 包 容 器 应 用 程 序 仅 仅 读 出 library 块 , 所 以 通 常 要 小 心 地 编 辑 IDL 文 件 , 以 移动 最 前 的 两 个 缩 进 代 码 块 到 library 块 。 第 一 个 块 由 方 括 号 [] 组 成 , 包 括 IPulseCtl 接 口 的 标 志 ;

第 四 部 分 A ctiveX 控 件 - 图61

图 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 的 事 件 函 数 上 的 步 骤 。

第 四 部 分 A ctiveX 控 件 - 图62

图 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 :

第 四 部 分 A ctiveX 控 件 - 图63

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 配 置 :

第 四 部 分 A ctiveX 控 件 - 图64

从 配 套 光 盘 上 安 装 的 项 目 文 件 将 目 标 名 字 简 化 为 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>

第 四 部 分 A ctiveX 控 件 - 图65

图 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 ( 对 象 向 导 ) 对 话 框 。

第 四 部 分 A ctiveX 控 件 - 图66

图 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 , 以 将 它 们

加 入 到 右 边 的 框 中 , 然 后 关 闭 对 话 框 。

第 四 部 分 A ctiveX 控 件 - 图67

步 骤 5 : 加 入 事 件

与 原 来 的 Tower 控 件 一 样 ,TowerATL 启动 5 个 事 件 ,名 称 分 别 为 Click, FromPanel, ToPanel, Error 和 W inner 它 们 共 同 保 证 使 包 容 器 了 解 对 控 件 所 发 生 的 事 件 。 在 项目 中 加 入 这 些 事 件 的 步 骤 与 我 们 对 Pulse 控 件 的 操 作 是 一 样 的 :

  1. 在 ClassView 窗 格 中 用 鼠 标 右 键 单 击 _ITowerCtlEvents 入 口

    ,并 从 关 联菜 单 中 选 择 Add Method s( 加 入 方 法 )命 令 ,然 后 在 对 话 框 中 选 择 void 返 回 类 型 ,并 键 入 一 个 函 数 名 字 。对 所 有 5 个 事 件 函 数 重 复 这 些 操 作 。只 有 FromPanel 和 ToPanel 带 有 参 数 , 分 别 为 nFromPanel 和 nToPanel , 这 两 个 参 数 的 类 型 都 是 short 。

第 四 部 分 A ctiveX 控 件 - 图68

  1. 在 Workspace 窗 口 的 FileView 窗 格 中 , 用 鼠 标 右 键 单 击

    TowerATL.idl

的 入 口 , 并 选 择 Compile ( 编 译 ) 命 令 , 以 创 建 这 个 项 目 的 类 型 库 文

件 。忽 略 来 自 MIDL 编 译 器 中 的 警 告 信 息“ interface does not conform to [oleautomation] attribute ”。 这 个 警 告 信 息 是 因 为 编 译 器 认 为 pFont 参 数

─ ─ 一 个 指 向 IFontDisp 接 口 的 指 针 ─ ─ 不 是 一 个 兼 容 Automation 的类 型 。 但 是 因 为 IFontDisp 来 自 与 IDispatch , 这 是 一 个 合 法 的Automation 接 口 , 所 以 这 条 警 告 信 息 是 不 正 确 的 。

  1. 在 ClassView 窗 格 中 , 用 鼠 标 右 键 单 击 CTowerCtl , 并 从 菜

    单 中 选 择

Implement Connection Poin t( 实 现 连 接 点 ) 命 令 。

  1. 设 置 标 有 _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 ( 属 性 页 ) 图 标 :

第 四 部 分 A ctiveX 控 件 - 图69

当 出 现 ATL Object Wizard Properties ( ATL 对 象 向 导 属 性 ) 对 话 框 时 , 键 入

TowerPPG 作 为 对 象 的 短 名 字 :

第 四 部 分 A ctiveX 控 件 - 图70

像 以 前 一 样 , 向 导 将 帮 助 我 们 用 推 荐 的 名 字 填 充 其 他 框 。 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( 对 象 向 导 ) 完 成 这 些 操 作 后 , 将 自 动 出 现 对 话 框 编 辑 器 , 这 表示 它 已 经 准 备 好 让 你 开 始 设 计 新 属 性 页 。用 静 态 和 编 辑 控 件 工 具 编 辑 对 话 框 ,使对 话 框 看 起 来 就 像 这 样 :

第 四 部 分 A ctiveX 控 件 - 图71

你 自 己 的 属 性 页 的 精 确 布 置 并 不 重 要 , 但 是 , 要 像 图 中 那 样 为 编 辑 框 指 定 一 个

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 框 。

第 四 部 分 A ctiveX 控 件 - 图72

图 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 ( 对 象 向 导 ) 时 , 对 话 框 编 辑 器 将 自 动 启 动 。 下 面 是 对 话 框 可 能 的 样 子 :

第 四 部 分 A ctiveX 控 件 - 图73

这 个 对 话 框 借 用 了 项 目 的 位 图 , 所 以 这 种 装 饰 图 并 不 代 表 资 源 控 件 的 奢 侈 浪 费 。首 先 将 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 没 有 参 数 。

第 四 部 分 A ctiveX 控 件 - 图74

在 文 本 编 辑 器 中 打 开 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 ( 属 性 ) 工 具 , 以 显 示 控 件的 属 性 工 作 表 。

第 四 部 分 A ctiveX 控 件 - 图75

图 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 将 被 强 迫 调 度 所 有 与 它 有 关 的 交 互 作 用 , 所 以 控 件 的 第 二 个 实 例将 比 第 一 个 实 例 运 行 得 慢 很 多 。

第 四 部 分 A ctiveX 控 件 - 图76

图 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 文 件 , 也 不 会 影 响 客户 应 用 程 序 , 而 不 论 它 们 是 否 利 用 扩 展 特 性 。 在 利 用 一 个 动 态 链 接 库 的 高 版 本时 , 这 种 稳 定 性 是 非 常 难 以 达 到 的 。