• 忘掉天地
  • 仿佛也想不起自己
bingliaolongBingliaolong  2025-03-07 23:37 Aet 隐藏边栏 |   抢沙发  11 
文章评分 3 次,平均分 5.0

ATL线程模型和套间

概述

  1. COM 开发中,线程模式(Threading Model) 和 套间(Apartment) 是确保组件在多线程环境下安全运行的核心机制
  2. ATLActive Template Library)通过模板类(如 CComObjectRootEx)和线程模型宏,简化了线程安全的管理
  3. COM定义了三种套间类型,用于隔离不同线程对 COM 对象的访问,确保线程安全
  4. ATL 提供多种模板类来定义线程模型,需通过 CComObjectRootEx 传递给组件基类

SAT(单线程套间)

  1. 每个套间绑定到一个线程(通常是 UI 线程)
  2. 对象只能在创建它的线程中被调用,跨线程调用需通过消息泵(Message Pump)代理

MAT(多线程套间)

  1. 所有 MTA 线程共享一个套间
  2. 对象可以被任意线程直接访问,但需自行处理线程同步

Neutral(中性套间)

  1. 对象不绑定到任何线程,任何线程均可直接调用
  2. 需要组件自身保证线程安全

单线程模型(STA

  1. CComSingleThreadModel
  2. 单线程模型(非线程安全)
  3. 引用计数直接操作(++/--
  4. 适用于 STA 套间

多线程模型(MTA

  1. CComMultiThreadModel
  2. 多线程模型(线程安全)
  3. 使用 InterlockedIncrement/Decrement 原子操作引用计数
  4. 适用于 MTA 套间或自由线程组件

自由线程模型(Free-Threaded

  1. 多线程模型,但去除了临界区(Critical Section)保护
  2. 需手动处理同步,适用于高性能场景

ThreadingModel

  1. .rgs 文件中设置的 ThreadingModel 值(如 ApartmentFreeBoth)是 套间模型(Apartment Model) 的注册表配置
    1. 而非直接对应 ATL 的线程模式类(如 CComMultiThreadModel
  2. 作用
    1. ThreadingModel 是注册表项,用于告诉 COM 运行时 该组件支持的套间类型
    2. 它的值决定了组件实例化时如何绑定到线程套间
  3. 举例说明1:
    1. 若注册表中设置 ThreadingModel=FreeMTA 套间),但组件使用 CComSingleThreadModel(非线程安全),则会导致 数据竞争和崩溃
  4. 举例说明2:
    1. 若注册表中设置 ThreadingModel=ApartmentSTA 套间),但组件使用 CComMultiThreadModel(线程安全),虽然合法,但可能浪费性能
ThreadingModel 对应的套间模型 适用场景
Apartment 单线程套间(STA) 组件仅能在创建它的线程中被调用(如 UI 组件)。
Free 多线程套间(MTA) 组件可被任意线程直接调用(需自行处理线程安全)。
Both 同时兼容 STA 和 MTA(组件可运行在任何套间中,由调用者决定)。 通用组件,需内部保证线程安全。
Neutral 中性套间(组件不绑定线程,但必须在支持中性套间的 COM 版本中)。 .NET 互操作或高并发组件(需 Windows 2000+ 支持)。
  1. 正确配置示例:

组件线程模式的声明

  1. ATL 组件类中,通过继承 CComObjectRootEx 并指定线程模型类来声明线程模式

  1. 线程模型决定引用计数的实现:
    1. CComSingleThreadModel:直接操作 m_dwRef(非线程安全)
    2. CComMultiThreadModel:通过 InterlockedIncrement 保证原子性
  2. 套间模型由注册表决定:
    1. .rgs 注册脚本中指定 ThreadingModel

线程模式和套间的匹配规则

线程模型类 兼容的套间模型 典型应用场景
CComSingleThreadModel STA UI 控件、Office 插件
CComMultiThreadModel MTA 或 Neutral 后台服务、高性能计算组件
CComMultiThreadModelNoCS MTA(需手动同步) 低延迟、高频调用的组件

跨套间调用的处理

  1. 当客户端线程与组件套间不匹配时,COM 运行时自动通过 代理(Proxy) 和 存根(Stub) 进行跨套间调用
    1. STASTA:调用通过消息泵同步,确保线程安全
    2. STAMTA:调用被转发到 MTA 套间,可能引发性能损耗
    3. MTA → MTA:直接调用,无需代理
  2. 代码示例:STA 组件的跨线程调用

线程安全与同步机制

  1. 临界区(Critical Section
    1. 通过 CComAutoCriticalSectionCCriticalSection 保护共享资源

  1. 线程本地存储(TLS
    1. 使用 CComTLS 管理线程局部变量:

智能指针

CComPtr

  1. 基础 COM 接口指针管理
  2. 核心功能
    1. 自动管理引用计数:在构造时调用 AddRef,析构时调用 Release
    2. 支持接口指针的赋值、比较和操作(如 -> 运算符)
    3. 显式释放控制:通过 Release 方法手动释放资源

  1. 关键

  1. 注意事项
    1. 避免循环引用:若两个 CComPtr 互相引用,需手动打破循环
    2. 不支持跨线程直接传递:需结合 CoMarshalInterThreadInterfaceInStream

CComQIPtr

  1. 支持接口查询的智能指针
  2. 核心功能
    1. 继承自 CComPtr,额外支持 QueryInterface 功能
    2. 简化接口查询:通过构造函数或赋值操作自动查询目标接口

  1. 区别与CComPtr
    1. 构造函数支持 IUnknown*:自动调用 QueryInterface
    2. 更简洁的接口转换:无需手动调用 QueryInterface

CComWeakPtr

  1. ATL 中的弱引用智能指针
    1. 核心目标是 打破 COM 对象间的循环引用,避免内存泄漏
    2. 它不会增加对象的引用计数,因此不会阻止对象的销毁
  2. 弱引用核心作用
    1. 问题背景:当两个或多个 COM 对象通过 CComPtr 相互引用时,会形成循环引用(如 A→B→A),导致引用计数无法归零,对象无法释放
    2. 解决方案:将其中一个引用改为弱引用(CComWeakPtr),使其不增加引用计数

  1. 实现原理
    1. CComWeakPtr 内部通过 IWeakRef 接口跟踪目标对象,其工作流程如下:
    2. 绑定到对象:通过 Attach 或构造函数绑定到目标对象的 IWeakRef
    3. 获取强引用:通过 Lock 方法将弱引用升级为临时强引用(CComPtr
    4. 对象销毁:当目标对象释放时,所有关联的弱引用自动失效

  1. 对比CComPtr
特性 CComPtr CComWeakPtr
引用计数影响 增加引用计数(强引用) 不增加引用计数(弱引用)
对象生命周期管理 延长对象生命周期 不阻止对象销毁
安全访问 直接访问(对象一定存活) 需通过 Lock 获取临时强引用
典型用途 常规所有权管理 打破循环引用、观察者模式、缓存
  1. 注意事项
    1. 线程安全
      CComWeakPtr 本身不保证线程安全
      若在多线程环境中使用,需通过锁(如 CComAutoCriticalSection)保护弱引用的绑定和访问
    2. 对象存活检查
      调用 Lock 后必须检查返回的 CComPtr 是否有效,避免访问已释放对象:
    3. 避免裸指针转换
      不要直接通过 wp.p 访问对象,需通过 Lock 升级为强引用:

资源管理

CComBSTR

  1. 封装 BSTR 字符串
  2. 核心功能
    1. 自动管理 BSTR 内存:构造时分配,析构时释放
    2. 支持 BSTRwchar_t\* 的转换
    3. 提供字符串操作方法(如 AppendToLower

  1. 注意事项
    1. 避免多次释放:不要将 CComBSTR 管理的 BSTR 手动传给 SysFreeString
    2. 跨方法传递:通过 Detach() 转移所有权(避免拷贝开销)

CComVariant

  1. 封装 VARIANT 类型
  2. 核心功能
    1. 自动管理 VARIANT 生命周期:构造时初始化,析构时调用 VariantClear
    2. 支持类型转换:自动处理 VARIANT 的类型转换(如 LONGBSTR

  1. 常见操作
    1. 赋值:varInt = 200;(自动更新类型)
    2. 类型检查:if (varInt.vt == VT_I4) { ... }

CComHeapPtr

  1. 管理堆内存
  2. 核心功能
    1. 封装 malloc/freeCoTaskMemAlloc/CoTaskMemFree
    2. 适用于非 COM 内存(如通过 CoTaskMemAlloc 分配的内存)

资源管理-其他

BSTR

  1. BSTRCOM 中用于表示字符串的数据类型,其本质是一个 带有长度前缀的 UnicodeUTF-16)字符串
  2. 内存布局
    1. 4 字节:字符串长度(字节数,不包括终止符)
    2. 后续字节:Unicode 字符内容,以双空字符(\0\0)结束

  1. 核心特点
    1. 长度前缀:允许快速获取字符串长度(无需遍历到结尾)
    2. 显式内存管理:必须使用 COM API 分配和释放内存(SysAllocStringSysFreeString
    3. 支持嵌入空字符:不同于普通 C 字符串,BSTR 可以包含 \0(如二进制数据)
  2. 常用API
函数 作用
SysAllocString 分配 BSTR 内存(根据输入字符串)。
SysAllocStringLen 分配指定长度的 BSTR。
SysFreeString 释放 BSTR 内存。
SysStringLen 获取 BSTR 的字符数(非字节数)。

  1. 注意事项
    1. 内存泄漏:忘记调用 SysFreeString 会导致内存泄漏
    2. 跨模块传递:BSTR 必须由同一内存堆分配和释放(如组件与客户端使用不同 CRT 库时需谨慎)
    3. ATL 包装类:CComBSTR 可自动管理 BSTR 生命周期

VARIANT

  1. VARIANT 是一个联合体(Union),能够存储多种数据类型(如整数、浮点数、字符串、对象引用等)
  2. 核心字段
    1. vt:数据类型标记(如 VT_I4 表示 4 字节整数)
    2. 其他字段:根据 vt 的值选择对应成员(如 lValbstrVal

  1. 核心特点
    1. 动态类型:运行时根据 vt 决定实际数据类型
    2. 自动化兼容:支持跨语言传递复杂数据(如脚本语言与 C++ 组件交互)
    3. 内存管理:需手动初始化(VariantInit)和清理(VariantClear
  2. 常用API
函数 作用
VariantInit 初始化 VARIANT(设为 VT_EMPTY)。
VariantClear 释放 VARIANT 中持有的资源(如 BSTR)。
VariantCopy 深拷贝一个 VARIANT。
VariantChangeType 转换 VARIANT 到指定类型。

  1. 注意事项
    1. 类型安全:必须正确设置 vt 字段,否则访问错误成员会导致未定义行为
    2. 资源泄漏:忘记调用 VariantClear 会泄漏内存(如未释放 BSTRIUnknown*
    3. ATL 包装类:CComVariant 自动管理生命周期

VARIANT 如何存储数组

  1. 使用 VT_ARRAY 标记,结合 SAFEARRAY 类型

BSTRVARIANT对比

特性 BSTR VARIANT
用途 专用于字符串 通用数据类型容器
内存管理 SysAllocString/SysFreeString VariantInit/VariantClear
线程安全 是(COM 内存分配器线程安全) 依赖具体实现
跨语言支持 是(自动化兼容) 是(自动化核心类型)

跨进程通信与代理/存根(Proxy/Stub

概述

  1. COMComponent Object Model)中,跨进程通信(IPC, Inter-Process Communication) 允许客户端进程调用另一个进程或远程机器上的 COM 对象
  2. 为实现这一目标,COM 使用 代理(Proxy) 和 存根(Stub) 机制,而 ATLActive Template Library)通过工具和模板简化了其实现

跨进程通信的核心原理

  1. 代理(Proxy):
    1. 位于 客户端进程,模仿实际对象的接口
    2. 将客户端的调用参数 列集(Marshaling) 为网络或跨进程可传输的格式(如字节流)
    3. 将请求发送到服务端进程
  2. 存根(Stub):
    1. 位于 服务端进程,接收代理发送的数据
    2. 散集(Unmarshaling) 参数,调用实际对象的接口方法
    3. 将结果列集后返回给代理
  3. 通信流程
    1. 客户端调用代理接口方法 → 代理列集参数 → 发送到服务端
    2. 存根接收数据 → 散集参数 → 调用实际对象方法 → 列集结果 → 返回给代理
    3. 代理散集结果 → 返回给客户端

ATL 中代理/存根的生成

  1. MIDL 编译器
    1. 根据 IDLInterface Definition Language)文件生成代理/存根代码(.h_p.c_i.c
  2. ATL 模板
    1. 提供默认的列集实现(如 IMarshal 的自动支持)
  3. 具体步骤如下:
  4. 步骤一:定义接口(IDL 文件)

  1. 步骤二:编译 IDL 生成代理/存根代码

  1. 生成文件如下:
    1. Example_p.c:代理/存根实现代码
    2. Example.h:接口的 C++ 定义
    3. Example_i.c:接口的 GUID 定义
  2. 创建代理/存根 DLL 项目
    1. ATL 项目中添加生成的 _p.c_i.c 文件
    2. 实现 DllGetClassObjectDllRegisterServer 函数

注册代理/存根 DLL

  1. 注册命令

  1. 注册表项:代理/存根 CLSID

处理自定义数据类型的列集

  1. 标准列集
    1. 默认支持类型:基本类型(intBSTR)、COM 接口指针、SAFEARRAY
    2. 自动列集:MIDL 生成的代码自动处理这些类型
  2. 自定义类型列集
    1. 若接口方法包含自定义结构或复杂类型,需手动实现列集逻辑:
    2. 定义类型序列化规则:
      实现 IMarshal 接口或使用 IPersistStream
    3. IDL 中标记自定义类型:

  1. 自定义代理/存根代码:
    1. _p.c 文件中添加对 MyStruct 的列集处理

ATL 中的默认列集优化

  1. ATL 提供 标准列集(Standard Marshaling) 的自动化支持,通过以下方式简化开发:
  2. DECLARE_REGISTRY_RESOURCEID
    1. 自动注册组件的代理/存根信息
  3. CComCoClass
    1. 自动生成类工厂,支持代理/存根实例化

跨进程通信的线程模型

  1. 套间模型(Apartment):
    1. 若客户端和服务端线程模型不同(如 STAMTA),COM 运行时自动通过代理/存根同步调用
  2. 线程安全
    1. 使用 CComMultiThreadModel 确保组件线程安全

完整示例

  1. IDL 文件

  1. 代理/存根 DLL 项目
    1. Example_p.cExample_i.c 添加到 ATL DLL 项目中
    2. 编译生成 ExamplePS.dll
  2. 客户端调用

调试

ATLASSERT

  1. 断言宏
  2. 功能
    1. 条件检查:验证表达式是否为真,若为假则触发断言失败,中断程序执行
    2. 仅调试模式生效:在 Release 构建中自动禁用,无性能开销

  1. 配置
    1. 禁用断言:在项目属性中定义 ATL_NO_ASSERT 宏,或使用 #undef ATLASSERT

ATLTRACE

  1. 调试输出宏
  2. 功能
    1. 日志输出:在调试输出窗口(如 Visual StudioOutput 窗口)打印格式化消息
    2. 分级输出:支持不同调试级别(如 TRACE_LEVEL_INFOTRACE_LEVEL_ERROR

  1. 配置
    1. 启用/禁用:通过 ATLTRACE_LEVEL 宏控制输出级别(默认启用所有级别)
    2. 输出目标:默认输出到调试器,可重定向到文件或日志系统

_ATL_DEBUG_INTERFACES

  1. 接口引用跟踪
  2. 功能
    1. 接口泄漏检测:跟踪所有 AddRefRelease 调用,输出未释放的接口指针
    2. 线程安全分析:记录接口操作的线程 ID,帮助排查多线程问题
  3. 配置
    1. stdafx.h 或项目预处理器定义中添加:

_ATL_DEBUG_QI

  1. 接口查询跟踪
  2. 功能
    1. QueryInterface 调用跟踪:记录所有 QueryInterface 请求,帮助识别接口查询失败或冗余调用
  3. 配置

_ATL_DEBUG_REFCOUNT

  1. 引用计数跟踪
  2. 功能
    1. 对象生命周期跟踪:记录对象创建、销毁及引用计数变化
    2. 适用场景:检测对象过早释放或未释放
  3. 配置

CrtDbg 系列函数(CRT 调试支持)

  1. 功能
    1. 内存泄漏检测:结合 _CrtSetDbgFlag_CrtDumpMemoryLeaks,定位未释放的堆内存
    2. 堆分配跟踪:记录内存分配点(文件名和行号)

OutputDebugString

  1. 作用
    1. 将字符串发送到调试器的输出窗口(如 Visual StudioOutput 窗口)或系统调试通道
  2. 场景
    1. 跟踪程序执行流程
    2. 输出变量值、错误信息或诊断数据
    3. 调试无法附加调试器的环境(如服务进程)
  3. 函数原型
    1. 根据项目字符集设置(Unicode 或多字节),通常直接使用宏 OutputDebugString

  1. 示例
    1. 由于 OutputDebugString 不支持直接格式化字符串,需结合 CStringstd::wstringswprintf_s

  1. 结合 ATLTRACE
    1. ATLATLTRACE 宏内部封装了 OutputDebugString,并提供格式化支持:

其他工具与技巧

  1. Visual Studio 调试器
    1. 条件断点:在 AddRefRelease 处设置断点,检查引用计数变化
    2. 内存窗口:直接查看对象内存布局,分析虚函数表和成员变量
  2. Application Verifier
    1. 内存越界检测:检查堆溢出、使用已释放内存等问题
    2. COM 兼容性检查:验证组件是否符合 COM 规范
  3. Process Monitor
    1. 注册表/文件访问监控:排查组件注册失败或路径错误

连接点与事件机制

概述

  1. COMComponent Object Model)中,连接点(Connection Point) 是组件向客户端发送事件(Event)的核心机制
  2. ATLActive Template Library)通过 IConnectionPointIConnectionPointImpl 模板类简化了连接点的实现

连接点与事件机制的核心概念

  1. 角色定义
    1. 事件源(Source):组件(Server)定义事件接口,并在特定条件下触发事件
    2. 事件接收器(Sink):客户端(Client)实现事件接口,订阅组件的事件
  2. 核心接口
    1. IConnectionPointContainer
      :组件实现此接口,用于管理多个连接点(每个连接点对应一个事件接口)
    2. IConnectionPoint
      :每个连接点实现此接口,用于管理客户端的订阅(如添加、删除事件接收器)
    3. 事件接口:自定义接口(如 IMyEvent),客户端需实现其方法以接收事件

实现步骤

  1. 定义事件接口(IDL 文件)
    1. IDL 文件中定义事件接口,并用 [source] 标记其方向(从组件到客户端):

  1. 生成代理/存根代码
    1. 运行 MIDL 编译器生成 EventExample_p.cEventExample.h,确保事件接口可跨进程传递

ATL 实现连接点

  1. 组件类继承 IConnectionPointContainerImpl

  1. 关键代码解析
    1. BEGIN_CONNECTION_POINT_MAP宏:
      定义组件支持的所有连接点(每个事件接口一个条目)
    2. Fire_OnValueChanged方法:
      历所有订阅客户端的 IMyEvent 接口,调用其 OnValueChanged 方法

客户端订阅事件

  1. 实现事件接收器Sink
    1. 客户端需实现事件接口 IMyEvent

  1. 订阅事件

ATL 的自动化支持

IConnectionPointImpl

  1. 自动管理连接列表:
    1. 通过 m_vec 成员(CComDynamicUnkArray)存储客户端的 IUnknown 指针
  2. 简化事件触发:
    1. 提供 Fire_OnValueChanged 的通用实现(需手动遍历调用)

IDispEventImpl(基于调度的连接点)

  1. 若事件接口继承自 IDispatch(自动化兼容),可用 IDispEventImpl 进一步简化:

相关理解

  1. 我的COM 组件实现IConnectionPointContainer这个接口后,组件就有能力管理多个连接点
    1. 每个事件接口(如 IID_MyEvent)对应一个连接点,管理客户端的订阅列表
    2. Advise():客户端订阅事件
    3. Unadvise():客户端取消订阅
  2. 客户端要使用我的COM组件的某一个事件(如 IID_MyEvent),它就要实现这个事件的接口(如 IMyEvent),提供具体的回调方法
    1. 然后客户端还需要通过 IConnectionPoint::Advise() 注册事件接收器
    2. 当调用组件的某个事件接口时,会触发事件,然后客户端里写的接收器的回调方法被调用

  1. 示例代码:组件端 ATL实现

  1. 示例代码:客户端订阅事件

  1. 总结理解
    1. 通过 COM 的连接点和事件机制,客户端可以在调用 COM 组件的某些接口时,触发组件向客户端注册的接收器(Sink)回调,执行客户端自己实现的函数(如 OnValueChanged

应用场景

  1. 用户界面(UI)交互与更新
    1. COM 组件执行耗时操作(如文件下载、数据处理),完成后通知客户端更新界面
    2. 后台下载组件在下载完成时触发 OnDownloadComplete 事件,客户端收到事件后刷新界面
  2. 异步操作的状态反馈
    1. 组件执行异步任务(如网络请求、数据库查询),客户端需要实时获取进度或结果
    2. 数据库查询组件在每次获取一批数据时触发 OnDataReceived 事件,客户端逐步显示结果
  3. 实时数据监控
    1. 组件持续生成数据(如传感器读数、股票行情),客户端需要实时接收并处理
    2. 传感器组件每秒触发 OnSensorUpdate 事件,客户端实时绘制波形图
  4. 插件或扩展系统
    1. 主程序(如浏览器、IDE)通过 COM 组件支持插件,插件需要响应主程序事件
    2. Visual Studio 插件在代码编译完成后接收 OnBuildComplete 事件,执行自定义分析
  5. 分布式系统的事件通知
    1. 在分布式系统中,服务端组件向多个客户端广播状态变化(如聊天消息、订单状态)
    2. 聊天服务组件在用户发送消息时触发 OnNewMessage 事件,所有在线客户端实时显示消息
  6. 自动化控制与脚本交互
    1. 脚本语言(如 VBScriptPython)通过 COM 控制应用程序,需要事件回调支持
    2. ExcelCOM 组件在单元格内容修改时触发 OnCellChanged 事件,脚本自动执行数据校验

多播事件与线程安全

概述

  1. COM 开发中,多播事件(Multicast Events) 指一个事件接口被多个客户端订阅,组件触发事件时需遍历所有订阅的客户端并调用其回调方法
  2. 线程安全(Thread Safety) 则是确保在多线程环境下,事件订阅列表的遍历、修改以及回调过程不会导致数据竞争或资源冲突

多播事件的核心挑战

  1. 多客户端管理
    1. 订阅列表动态变化:客户端可能随时通过 Advise/Unadvise 订阅或取消订阅,需确保列表遍历过程中不被意外修改
    2. 多线程可能同时触发事件或修改订阅列表,导致竞态条件(Race Condition
  2. 线程安全的必要
    1. 数据一致性:避免订阅列表在遍历时被其他线程修改(如删除或添加客户端),导致崩溃或遗漏通知
    2. 回调安全:确保客户端回调方法在不同线程中执行时,不会因共享资源冲突导致未定义行为

线程安全实现方案

  1. 使用线程模型类(CComMultiThreadModel
    1. ATL 的线程模型类(如 CComMultiThreadModel)提供线程安全的引用计数和同步原语(如临界区)
    2. 继承线程模型:组件类继承 CComObjectRootEx<CComMultiThreadModel>
    3. 保护订阅列表:在遍历或修改客户端列表时加锁
  2. 同步机制(临界区与锁)
    1. 使用 CComAutoCriticalSectionCCriticalSection 保护订阅列表的访问

  1. 客户端的动态管理
    1. 添加/删除客户端时加锁:确保 AdviseUnadvise 操作线程安全
    2. 使用线程安全容器:如 CComDynamicUnkArray(内部通过锁保护)

多线程环境下的回调安全

  1. 客户端线程模型适配
    1. STA 客户端:事件回调需通过消息泵(Message Pump)同步到主线程,避免跨线程调用 UI 组件
    2. MTA 客户端:允许直接回调,但需客户端自行处理线程安全
  2. 避免死锁
    1. 锁粒度控制:确保在回调客户端方法前释放锁,防止客户端回调中再次请求同一锁

  1. 客户端接口指针的生命周期
    1. 使用 CComPtr 管理指针:避免客户端在回调期间被释放
    2. 引用计数保护:在回调期间增加接口引用计数,防止客户端在回调中调用 Unadvise 导致指针失效

Fire_XXX

  1. 在多播事件机制中,Fire_XXX 函数(如 Fire_OnValueChanged)是 COM 组件触发事件的核心方法,负责遍历所有订阅的客户端,并调用其事件接口的回调方法
  2. 以下是一个线程安全的 Fire_OnValueChanged 实现示例:

  1. Fire_XXX 函数是 开发者自定义的成员函数,通常直接定义在 组件的类声明中(如 CMyComponent 类)
    1. 其作用是根据业务逻辑触发事件,遍历所有订阅的客户端并调用其回调方法

  1. Fire_XXX 的命名与接口关联
    1. 函数名和参数应与事件接口定义一致

  1. 简化开发的辅助宏:BEGIN_CONNECTION_POINT_MAPEND_CONNECTION_POINT_MAP
    1. 作用:声明组件支持的连接点列表,但 不生成 Fire_XXX 函数

  1. 简化开发的辅助宏:IDispEventImpl(基于调度的自动化事件)
    1. 适用场景:若事件接口继承自 IDispatch(自动化兼容),可以使用 IDispEventImpl 自动生成部分代码

ATL 与 WTL 结合开发 GUI

概述

  1. ATL 提供了一套轻量级的窗口类模板,用于简化 Win32 窗口的创建和管理。其核心目标是替代 MFC 的窗口封装,减少代码冗余和运行时开销,同时支持 COM 组件的无缝集成

CWindow

  1. 基础窗口封装
  2. 功能
    1. 封装窗口句柄(HWND),提供直接调用 Win32 API 的方法(如 ShowWindowMoveWindow
  3. 用途
    1. 适用于简单窗口操作,无需消息处理逻辑

CWindowImpl

  1. 继承自 CWindow,支持消息处理
  2. 功能
    1. 继承自 CWindow,通过模板和宏实现消息映射(Message Map),类似 MFC 的 BEGIN_MESSAGE_MAP

CDialogImpl

  1. 对话框封装
  2. 功能
    1. 简化对话框的创建和消息处理,支持资源模板(.rc 文件中的对话框 ID

ATL窗口类优势

  1. 轻量级:
    1. MFC 的庞大运行时库依赖,适合小型组件或性能敏感场景
  2. COM 集成:
    1. 可直接嵌入 COM 组件(如 ActiveX 控件)
  3. 模板化设计:
    1. 通过 CRTP(奇异递归模板模式)实现零成本抽象

ActiveX 控件开发

  1. 概述
    1. ActiveX 控件是基于 COM 的可重用 UI 组件,支持在多种容器(如网页、VBC#)中嵌入
    2. ATL 提供 CComControl 等模板类简化其开发
  2. 开发步骤如下:
  3. 创建ATL项目
    1. Visual Studio 中选择 ATL Project,勾选 Support Control 选项
    2. 添加 ATL Control 向导生成控件框架
  4. 定义控件接口(IDL文件)

  1. 实现控件类

  1. 添加属性页(可选)

  1. 注册于测试
    1. 编译生成 .ocx 文件,运行 regsvr32 MyControl.ocx 注册控件
    2. VBC# 或网页中测试:

ATL 对 ActiveX 的核心支持

类/模板 功能
CComControl 提供控件基础功能(如绘制、窗口管理)。
IViewObjectEx 实现控件的可视化(如 OnDraw 方法)。
IPersistStreamInit 支持控件状态的序列化与反序列化。
IOleControl 处理控件的焦点和键盘事件。

ATL 与 WTL 结合开发 GUI 示例

WTL的增强功能

  1. 高级控件:
    1. 提供 CListViewCtrlCTreeViewCtrl 等封装,简化复杂 UI 开发
  2. 布局管理:
    1. 支持 DockingSplitter 窗口(类似 MFC
  3. 消息链:
    1. 通过 CHAIN_MSG_MAP 实现消息路由到父窗口或子控件

场景

  1. ActiveX 控件:
    1. 嵌入网页或传统桌面应用(如数据可视化、工业控制界面)
  2. 轻量级 GUI 工具:
    1. 需要最小化依赖的配置工具或后台服务监控界面
  3. COM 组件集成:
    1. COM 服务中嵌入 UI 用于调试或状态展示

示例代码

COM 服务器类型与部署

进程内(DLL)服务器(In-Process Server

  1. 形式
    1. 动态链接库DLL
  2. 运行方式
    1. 加载到 客户端进程的地址空间 中执行
  3. 特点
    1. 高性能:无跨进程通信开销,调用速度快。
    2. 低隔离性:若服务器崩溃,可能导致客户端进程崩溃。
    3. 共享资源:可直接访问客户端的内存和资源
  4. 适用场景
    1. 高性能要求的组件(如图形渲染、数学计算)
    2. 需要紧密集成的功能(如浏览器插件)
  5. 注册方式:
    1. 自注册逻辑:DLL 需导出 DllRegisterServerDllUnregisterServer 函数

  1. 注册表项

进程外(EXE)

  1. 形式
    1. 可执行文件EXE
  2. 运行方式
    1. 在 独立的进程 中运行,通过 RPC(远程过程调用)与客户端通信
  3. 特点
    1. 高隔离性:服务器崩溃不会影响客户端。
    2. 跨进程/机器支持:支持 DCOM(分布式 COM),可远程调用。
    3. 通信开销:参数需列集(Marshaling),性能较低
  4. 适用场景
    1. 需要高稳定性的服务(如后台数据处理)。
    2. 分布式系统(如远程数据库访问)
  5. 注册方式:
    1. 运行 EXE 时附带注册参数
    2. 自注册逻辑:EXE 需解析命令行参数,调用 CoRegisterClassObject 注册类工厂

  1. 注册表项

类型库(.tlb)嵌入

  1. 作用
    1. 将类型库(.tlb)作为资源嵌入 DLLEXE,简化客户端开发
  2. 实现步骤如下:
  3. 添加资源文件:
    1. .rc 文件中添加类型库资源:

  1. 注册类型库
    1. .rgs 注册脚本中引用资源 ID

  1. 代码支持
    1. 使用 DECLARE_REGISTRY_RESOURCEID 宏关联资源 ID

实际应用场景

  1. 进程内服务器
    1. 图像处理库(如滤镜效果)
    2. ImageFilter.dll 和嵌入的 .tlb 分发给客户端
    3. 客户端通过 regsvr32 注册后直接调用接口
  2. 进程外服务器
    1. 财务计算服务(需长时间运行)
    2. 在服务器机器上注册 FinanceService.exe
    3. 客户端通过 DCOM 配置远程访问
  3. 类型库嵌入

免注册 COM(Registration-Free COM)

原理

  1. 通过清单文件(.manifest)描述 COM 类信息,避免写入注册表

步骤

  1. 生成清单文件
    1. 使用 mt.exeDLL/EXE 提取 COM 信息:

  1. 客户端清单
    1. 客户端应用清单中声明依赖:

优点

  1. 绿色部署(无需管理员权限)
  2. 避免注册表污染

本文为原创文章,版权归所有,欢迎分享本文,转载请保留出处!

bingliaolong
Bingliaolong 关注:0    粉丝:0 最后编辑于:2025-03-09
Everything will be better.

发表评论

表情 格式 链接 私密 签到
扫一扫二维码分享