• 忘掉天地
  • 仿佛也想不起自己
bingliaolongBingliaolong  2025-03-07 20:58 Aet |  隐藏边栏 |   抢沙发 |  24 
文章评分 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. 基础接口:IUnknown、IDispatch(支持自动化调用的核心接口)
    2. 数据类型:BSTR(宽字符串)、VARIANT(泛型数据类型)、SAFEARRAY(安全数组)等
    3. 错误处理:HRESULT 的定义
  2. 用途:
    1. 若接口需要支持自动化(如被脚本语言调用)或使用自动化兼容的数据类型,必须导入此文件

import "ocidl.idl";

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

oaidl和ocidl的场景

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

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 继承自 CComObjectRootEx 和 CComCoClass),且不会直接实例化时
    2. 就是说:
      当类标记为 ATL_NO_VTABLE 时,编译器不会生成完整的虚函数表。
      如果直接通过 new 创建该类的实例
      由于虚函数表未正确初始化,调用虚函数(如 QueryInterface、AddRef)会导致 未定义行为(如崩溃)
    3. 所以说:
      它的核心作用是 阻止编译器为类生成虚函数表(vtable)。但这并不直接决定类的实例化方式,而是优化手段
    4. 在 ATL 中,所有实现 COM 接口的类都应使用此宏

CComObjectRootEx

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

CComMultiThreadModel

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

CComObjectRootEx<CComMultiThreadModel>

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

CComCoClass

  1. 功能
    1. 提供 COM 对象的 类工厂支持,用于创建对象实例
    2. 管理组件的 CLSID(Class 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 对应嵌入在二进制文件(DLL 或 EXE)中的注册脚本
    4. 当组件被注册(如通过 regsvr32)时,ATL 会自动解析该资源脚本,将组件的 CLSID、ProgID、线程模型等信息写入注册表
  2. 宏定义
    1. 参数 x:资源 ID(如 IDR_CCEXAMPLE),对应嵌入的 .rgs 注册脚本
    2. 功能:声明一个静态方法 UpdateRegistry,该方法调用 ATL 模块的 UpdateRegistryFromResource,加载并执行资源中的注册脚本

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

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

  1. 具体注册流程:当调用 DllRegisterServer 注册组件时:
    1. ATL 模块调用 CComCoClass 的 UpdateRegistry 方法
    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. HRESULT 是 COM 方法的标准返回类型(用于错误码)
    3. STDMETHODCALLTYPE 定义为 __stdcall,这是 COM 方法的默认调用约定
  2. 示例:
    1. 等效写法

tlb文件细节学习

概述

  1. C#、VB 或其他支持 COM 互操作的语言(如 Python、Delphi)可以通过 类型库(.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 通过 RCW 将 COM 对象封装为托管对象,处理以下任务:
    2. 封送处理(Marshaling):转换 COM 数据类型(如 BSTR、VARIANT)为 .NET 类型(如 string、object)
    3. 生命周期管理:自动调用 AddRef/Release,避免内存泄漏
    4. 错误转换:将 COM 的 HRESULT 转换为 .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.

发表评论

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