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

ATL下载

  1. ATL 作为微软官方库,源码随 Visual Studio 安装包 默认集成

正确代码结构

接口定义(使用 IDL)

实现类(使用 ATL)

对象创建宏

客户端调用示例

完整示例

项目结构

ExampleComponent.idl

  1. Visual Studio 中创建 ATL Project
    1. 添加 .idl 文件并编译,MIDL 会自动生成:
    2. ExampleComponent_i.h(接口定义)
    3. ExampleComponent.tlb(类型库)
  2. import "oaidl.idl";
    1. 见细节学习
  3. import "ocidl.idl";
    1. 见细节学习

实现类 ExampleComponent.h

  1. ATL_NO_VTABLE
    1. 见实现类细节学习
  2. CComObjectRootEx
    1. 见实现类细节学习
  3. CComMultiThreadModel
    1. 见实现类细节学习
  4. CComCoClass<CCExample, &CLSID_CExample>
    1. 见实现类细节学习
  5. DECLARE_REGISTRY_RESOURCEID(IDR_CCEXAMPLE)
    1. 见实现类细节学习
  6. BEGIN_COM_MAP(CCExample)
    1. 见实现类细节学习
  7. COM_INTERFACE_ENTRY
    1. 见实现类细节学习
  8. STDMETHODIMP
    1. 见实现类细节学习

实现类注册 ExampleComponent.cpp

编译组件

  1. 编译生成 ExampleComponent.dll

注册组件

客户端调用 Client.cpp

IDL文件细节学习

核心作用

  1. IDL 文件用于 标准化定义 COM 接口、组件和类型库,通过 MIDL 编译器生成以下内容
    1. C/C++ 头文件(如 ExampleComponent_i.h):包含接口的 C++ 定义
    2. 类型库(.tlb 文件):二进制文件,供高级语言(如 C#VB)调用 COM 组件
    3. 代理/存根代码:支持跨进程或跨机器调用(DCOM

import "oaidl.idl";

  1. 定义自动化(Automation)相关接口和数据类型:
    1. 基础接口:IUnknownIDispatch(支持自动化调用的核心接口)
    2. 数据类型:BSTR(宽字符串)、VARIANT(泛型数据类型)、SAFEARRAY(安全数组)等
    3. 错误处理:HRESULT 的定义
  2. 用途:
    1. 若接口需要支持自动化(如被脚本语言调用)或使用自动化兼容的数据类型,必须导入此文件

import "ocidl.idl";

  1. 定义 OLE 控件(ActiveX 控件)相关接口:
    1. 控件接口:IOleControlIOleObject
    2. 可视化特性:IViewObject(渲染)、IQuickActivate(快速激活)
    3. 连接点(Connection Points):IConnectionPoint(事件机制)
  2. 用途:
    1. 若组件是 ActiveX 控件或需要事件机制,必须导入此文件

oaidl和ocidl的场景

场景 是否需要 oaidl.idl 是否需要 ocidl.idl
定义纯 COM 接口(仅 IUnknown 是(包含 IUnknown
支持自动化(如脚本调用)
创建 ActiveX 控件
使用 BSTRVARIANT

oaidl和ocidl实际开发中的常见用法

  1. 基础 COM 组件(非自动化)

  1. ActiveX 控件

接口定义interface

属性/语法 说明
[object] 声明这是一个 COM 接口(非 RPC 接口)。
uuid(...) 接口的全局唯一标识符(GUID),可用 guidgen.exe 生成。
pointer_default(unique) 定义未明确标记的指针的默认行为: - unique:指针不可为 NULL
[helpstring("...")] 提供接口或方法的描述信息,供开发工具显示。
[in] / [out] 参数方向:输入、输出或双向([in, out])。
[retval] 标记返回值参数,某些语言(如 VB)可将其映射为函数返回值。

类型库定义library

元素/语法 说明
library 定义一个 类型库,包含组件类、接口等信息。
uuid(...) 类型库的全局唯一标识符(GUID)。
version(1.0) 类型库版本号,用于版本控制。
importlib("stdole32.tlb") 导入标准类型库,确保当前库可引用其定义(如 IUnknown)。
coclass 定义一个 COM 组件类(CoClass),对应实现类的 CLSID。
[default] interface 指定组件的默认接口,客户端可通过 CoCreateInstance 直接获取此接口。

语法规则

  1. 接口定义

  1. 组件类定义

  1. 类型库定义
    1. 可以定义多个组件类

实现类细节学习

ATL_NO_VTABLE

  1. ATL 提供的一个宏,其定义为:

  1. 作用:
    1. 告诉编译器 不要为该类生成虚函数表(vtable
    2. 虚函数表是 C++ 实现多态的核心机制,但对于某些中间基类(如 ATL 实现类的基类),虚函数表可能未被完全使用
      禁用虚函数表可减少代码体积和初始化开销
  2. 适用场景
    1. 当类被用作中间基类(如 CCExample 继承自 CComObjectRootExCComCoClass),且不会直接实例化时
    2. 就是说:
      当类标记为 ATL_NO_VTABLE 时,编译器不会生成完整的虚函数表。
      如果直接通过 new 创建该类的实例
      由于虚函数表未正确初始化,调用虚函数(如 QueryInterfaceAddRef)会导致 未定义行为(如崩溃)
    3. 所以说:
      它的核心作用是 阻止编译器为类生成虚函数表(vtable)。但这并不直接决定类的实例化方式,而是优化手段
    4. ATL 中,所有实现 COM 接口的类都应使用此宏

CComObjectRootEx

  1. 功能
    1. 提供 COM 对象的 引用计数管理 和 线程安全支持
    2. 实现 IUnknown 的核心方法(QueryInterfaceAddRefRelease)的默认逻辑
  2. 模板参数
    1. 接受一个线程模型类(如 CComMultiThreadModel),用于定义线程安全策略

CComMultiThreadModel

  1. 功能
    1. 定义 多线程环境 下的线程安全操作
    2. 使用 InterlockedIncrementInterlockedDecrement 保证引用计数的原子性
    3. 通过临界区(Critical Section)保护内部数据

CComObjectRootEx<CComMultiThreadModel>

  1. CComMultiThreadModel 的线程安全机制注入到 CComObjectRootEx
  2. 使 CCExample 类在多线程环境下安全地管理引用计数和接口查询

CComCoClass

  1. 功能
    1. 提供 COM 对象的 类工厂支持,用于创建对象实例
    2. 管理组件的 CLSIDClass Identifier)和 注册信息
    3. 包含 CreateInstance 静态方法,用于创建对象实例

CComCoClass<CCExample, &CLSID_CExample>

  1. CCExample
    1. 当前实现类的类型
  2. &CLSID_CExample
    1. 组件的 CLSID(在 IDL 文件中定义)
  3. 作用
    1. 将组件类 CCExample 与特定的 CLSID 绑定
    2. 自动生成类工厂代码,使得客户端可通过 CoCreateInstance 创建该组件

DECLARE_REGISTRY_RESOURCEID(IDR_CCEXAMPLE)

  1. DECLARE_REGISTRY_RESOURCEID
    1. ATL 中的一个宏,用于 将 COM 组件的注册信息与资源文件中的注册脚本(.rgs 文件)绑定
    2. 它的核心作用是:
    3. 声明组件注册时需要使用的资源 ID,该资源 ID 对应嵌入在二进制文件(DLLEXE)中的注册脚本
    4. 当组件被注册(如通过 regsvr32)时,ATL 会自动解析该资源脚本,将组件的 CLSIDProgID、线程模型等信息写入注册表
  2. 宏定义
    1. 参数 x:资源 ID(如 IDR_CCEXAMPLE),对应嵌入的 .rgs 注册脚本
    2. 功能:声明一个静态方法 UpdateRegistry,该方法调用 ATL 模块的 UpdateRegistryFromResource,加载并执行资源中的注册脚本

  1. 资源文件(.rc文件)
    1. 假设资源文件中存在名为 IDR_CCEXAMPLE 的注册脚本(通常为 .rgs 文件)

  1. 资源脚本(.rgs 文件),内容示例如下

  1. 具体注册流程:当调用 DllRegisterServer 注册组件时:
    1. ATL 模块调用 CComCoClassUpdateRegistry 方法
    2. 通过 DECLARE_REGISTRY_RESOURCEID 绑定的资源 ID,找到 .rgs 文件
    3. 解析脚本并将注册表项写入系统注册表
  2. 对比其他注册方式一:DECLARE_REGISTRY_RESOURCE
    1. DECLARE_REGISTRY_RESOURCEID 类似,但通过资源名称(而非 ID)定位脚本

  1. 对比其他注册方式二:动态注册(手动编写注册代码)
    1. 代码冗余,维护困难

BEGIN_COM_MAP(CCExample)

  1. 实现
    1. BEGIN_COM_MAP 是一个宏,展开后生成以下代码:
    2. 它定义了 QueryInterface 方法的框架,并通过后续的 COM_INTERFACE_ENTRY 宏填充接口映射表

  1. 作用
    1. 声明一个 COM 接口映射表,用于告诉 ATL 当前对象支持哪些接口
    2. QueryInterface 被调用时,ATL 根据此映射表返回正确的接口指针

COM_INTERFACE_ENTRY(IExample)

  1. COM_INTERFACE_ENTRY
    1. 是一个宏,展开后生成:
    2. 它会在接口映射表中插入一条记录,包含接口的 IID 和偏移量信息

  1. 作用
    1. 将接口 IExample 添加到 CCExample 的接口映射表中
    2. 当客户端调用 QueryInterface(IID_IExample, ...) 时,ATL 会根据此条目返回 IExample 接口的指针

STDMETHODIMP

  1. 定义

  1. 作用
    1. 声明一个 COM 接口方法的返回类型和调用约定
    2. HRESULTCOM 方法的标准返回类型(用于错误码)
    3. STDMETHODCALLTYPE 定义为 __stdcall,这是 COM 方法的默认调用约定
  2. 示例:
    1. 等效写法

tlb文件细节学习

概述

  1. C#VB 或其他支持 COM 互操作的语言(如 PythonDelphi)可以通过 类型库(.tlb 文件) 直接调用 C++ 编写的 COM 组件

作用

  1. 类型库是一个 二进制元数据文件,由 IDL 文件通过 MIDL 编译器生成,包含以下信息:
    1. 接口定义(方法签名、参数类型、返回类型)
    2. 组件类(CoClass)的 CLSID 和其支持的接口
    3. 类型信息(如枚举、结构体、GUID 等)
  2. 它的核心作用是 充当跨语言的“桥梁”,让非 C++ 语言(如 C#)能理解 COM 组件的接口规范,并生成对应的代理代码

C# 调用 C++ COM 组件的具体步骤

  1. 生成 COM 组件并注册
    1. 编译 C++ COM 项目,生成 .dll 文件
    2. 使用 regsvr32 注册组件,将 CLSID 和接口信息写入注册表

  1. C# 中引用类型库
    1. 方法 1:直接引用 .tlb 文件
      Visual Studio 中右键项目 → 添加引用 → COM → 选择类型库(.tlb 文件)
    2. 方法 2:通过 tlbimp.exe 生成互操作程序集
      使用 .NET Framework SDK 工具 tlbimp.exe 生成托管代理类(Interop Assembly
      然后在 C# 项目中引用生成的 Interop.ExampleComponent.dll

  1. 代码调用 COM 组件

C# 调用 C++ COM底层机制解析

  1. 运行时可调用包装器(RCW, Runtime Callable Wrapper
    1. .NET CLR 通过 RCWCOM 对象封装为托管对象,处理以下任务:
    2. 封送处理(Marshaling):转换 COM 数据类型(如 BSTRVARIANT)为 .NET 类型(如 stringobject
    3. 生命周期管理:自动调用 AddRef/Release,避免内存泄漏
    4. 错误转换:将 COMHRESULT 转换为 .NET 异常(如 COMException
  2. 调用过程
    1. 实例化组件:
      C#new CExample() 实际调用 CoCreateInstance(CLSID_CExample, ...)
    2. 方法调用:
      example.Add(...) 通过 RCW 将参数封送为 COM 兼容格式,调用 IExample::Add
    3. 结果返回:
      COM 返回的结果通过 RCW 转换回 .NET 类型

VB调用 C++ COM 组件的具体步骤

  1. VB 的调用方式与 C# 类似,语法更简洁:

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

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

发表评论

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