• 忘掉天地
  • 仿佛也想不起自己
bingliaolongBingliaolong  2024-07-22 22:45 Aet 隐藏边栏 |   抢沙发  5 
文章评分 1 次,平均分 5.0

概述

  1. Windows 的组件对象模型(Component Object ModelCOM)是一种用于构建可重用软件组件的标准和技术
  2. 它允许软件组件以二进制形式进行互操作,独立于编程语言、开发工具和平台
  3. COM 技术广泛应用于各种 Windows 应用程序和系统服务
    1. 包括 OLEActiveXDCOM

基本概念

接口

  1. 接口定义
    1. COM 中的接口是一个抽象类型,定义了一组相关的方法,不包含具体的实现
    2. 每个 COM 接口都有一个唯一的接口标识符(Interface IdentifierIID
  2. IUnknown 接口
    1. 所有 COM 接口都继承自 IUnknown 接口
    2. IUnknown 定义了三个方法:QueryInterfaceAddRefRelease

  1. 疑问:
    1. 这里所谓的COM接口,就是COM组件里面某个派生类实现的函数
    2. 每个函数都对应一个唯一ID

关于接口和实现类

  1. 接口(Interface
    1. 纯虚类(如 IExample),仅定义方法签名,不包含实现
  2. 实现类(Implementation Class
    1. 具体实现接口的类(如 Example),不是接口,而是接口的具体实现

上述示例代码的一些问题

  1. COM 接口方法 必须返回 HRESULT 以支持错误处理机制

  1. 未正确处理 IUnknown 方法
    1. 虽然手动实现了 QueryInterfaceAddRefRelease,但以下问题需注意:
    2. 线程安全性:InterlockedIncrement/Decrement 是线程安全的,但若组件被标记为单线程模型(如 CComSingleThreadModel),需调整实现
    3. 对象生命周期:delete thisRelease 中是合法的,但需确保对象仅通过 new 创建
  2. 未使用 ATL 辅助类
    1. ATL 提供 CComObjectRootExCComObject 等模板类,可自动处理引用计数和 IUnknown 方法,避免手动实现错误

  1. 类定义:
    1. COM 类是实现一个或多个 COM 接口的具体实现
    2. 每个 COM 类都有一个唯一的类标识符(Class IdentifierCLSID
  2. 实例化:
    1. 通过类工厂(Class Factory)实例化 COM
    2. 类工厂实现了 IClassFactory 接口,提供 CreateInstance 方法用于创建 COM 对象实例

组件

  1. 组件定义:
    1. COM 组件是一个包含一个或多个 COM 类的二进制文件,通常是 DLLEXE 文件

关于类工厂

  1. COM(组件对象模型)中,通常情况下每个 COM 类都需要实现一个类工厂(Class Factory),以便实例化 COM 对象
  2. 类工厂负责创建 COM 对象的实例,并且它本身是一个 COM 对象,实现了 IClassFactory 接口
    1. 类工厂可以在创建对象时执行一些初始化操作或进行资源分配
    2. 通过类工厂,客户端代码无需知道 COM 对象的具体实现,只需要通过类工厂请求实例化对象
  3. 类工厂实现
    1. CreateInstance:创建 COM 对象的实例
    2. LockServer:用于控制类工厂的生命周期(通常用于保持服务器进程的活跃状态)

使用

  1. 注册
    1. COM 组件需要在计算机上注册,以便系统能够找到和加载它们
    2. 注册过程涉及在 Windows 注册表中创建相应的条目,指示 COM 组件的类标识符(CLSID)、接口标识符(IID)以及组件所在的 DLLEXE 文件的路径

  1. DLL 中实现 DllRegisterServerDllUnregisterServer
    1. COM 组件的 DLL 文件需要实现 DllRegisterServerDllUnregisterServer 函数,以便在注册表中添加或删除相应的条目

  1. 手动注册
    1. 如果需要手动注册,可以通过编辑注册表来实现
    2. 将上述内容保存为 .reg 文件,然后双击导入到注册表中

COM 的工作机制

注册和类工厂

  1. COM 组件在系统中注册,注册信息存储在 Windows 注册表中,包括 CLSID 和它们对应的 DLL/EXE 路径

实例化对象

  1. 通过 CoCreateInstance 函数创建 COM 对象实例

接口查询

  1. 使用 QueryInterface 方法在 COM 对象上查询特定接口

引用计数

  1. 使用 AddRefRelease 方法管理对象的生命周期,通过引用计数实现对象的自动管理

内存布局和 vtable

  1. 内存布局
    1. 每个 COM 对象在内存中的布局包括一个指向 vtable 的指针,vtable 包含接口方法的指针

进程间通信(IPC)

DCOM

  1. 分布式组件对象模型(Distributed COMDCOM)扩展了 COM,支持跨进程和跨网络的对象调用
  2. 具体见下面DCOM章节

RPC

  1. DCOM 使用远程过程调用(Remote Procedure CallRPC)来实现进程间通信
  2. 具体见下面RPC章节

错误处理和 HRESULT

  1. COM 使用 HRESULT 类型表示函数调用的返回状态和错误信息

线程模型

概述

  1. COM 支持多种线程模型,包括单线程公寓(Single-threaded ApartmentSTA)和多线程公寓(Multi-threaded ApartmentMTA),用于管理线程间的 COM 对象访问
  2. 这两种模型用于管理线程与 COM 对象的交互方式,确保线程安全性和同步性

单线程公寓

  1. 特点
    1. 每个线程一个公寓:每个 STA 包含一个线程,每个 COM 对象都驻留在一个特定的 STA 中,只有这个 STA 的线程可以直接访问该对象
    2. 消息循环:STA 线程必须运行一个消息循环,以处理来自其他线程或进程的消息调用
    3. 线程隔离:不同 STA 之间的调用通过 Windows 消息机制进行,保证线程间的隔离和同步
  2. 工作机制
    1. 消息调度:在 STA 中,COM 使用 Windows 消息机制调度跨线程调用。当其他线程需要调用 STA 中的 COM 对象时,请求会被封装成消息,并发送到目标 STA 线程的消息队列中
    2. 代理和存根:在跨 STA 调用时,COM 使用代理(Proxy)和存根(Stub)进行方法调用的封送和解封。代理在调用线程上运行,存根在对象所在的 STA 线程上运行
  3. 场景
    1. 用户界面线程:STA 适用于用户界面线程,因为 Windows 界面库(如 Win32WPF)要求在单个线程上运行 UI 组件,并且这个线程必须具有消息循环
    2. 线程隔离:需要线程隔离和序列化访问的情况

  1. 理解:
    1. 首先,一个单线程公寓中只能有一个线程
    2. 使用单线程公寓(STA)模式的线程,系统会为该线程创建一个 STA 空间,而在该线程中创建的 COM 对象,其实例也驻留在这个 STA 空间中
    3. 只有在这个 STA 空间中的线程可以直接访问这些 COM 对象。其他线程需要通过消息传递机制来访问这些COM 对象,以确保线程安全和正确的同步

  1. 为什么上面是STA1有消息循环,STA2没有消息循环
    1. 因为 STA1 线程中创建的 COM 对象需要处理来自其他线程(如 STA2 线程)的调用请求,因此需要一个消息循环来处理这些跨线程的调用
    2. InitializeSTA1 函数中,STA1 线程创建了一个 COM 对象 Example,并注册到运行时对象表(ROT
    3. STA1 线程需要一个消息循环来处理来自其他线程的调用请求,因为其他线程通过代理对象向 Example 对象发送调用请求,这些请求会被封装成消息,并发送到 STA1 线程的消息队列中
  2. 但是并没有看到在上面的消息循环处理具体特殊的消息
    1. 虽然在示例代码中没有显式处理特定的消息,但消息循环的存在确保了 STA 线程能够处理系统自动生成的跨线程调用请求
    2. 通过这种机制,STA 线程能够正确地处理来自其他线程的调用,从而实现线程安全的 COM 调用
  3. 可以在两个STA空间中都创建消息循环吗
    1. 可以在两个 STA 空间中都创建消息循环
    2. 每个单线程公寓(STA)都有自己的消息循环,以确保能够处理来自其他线程的调用请求
    3. 消息循环在每个 STA 线程中都是独立运行的,这样每个 STA 线程都可以接收和处理消息,包括跨线程的 COM 调用请求
  4. 每个STA空间中,可以有一份单独的COM对象示例吗
    1. 不同的 STA 空间中可以拥有同一个 COM 组件的实例副本

多线程公寓

  1. 特点
    1. 多个线程共享一个公寓:所有加入 MTA 的线程都共享同一个公寓,MTA 中的 COM 对象可以被任何加入 MTA 的线程直接访问
    2. 无消息循环要求:MTA 线程不需要运行消息循环,因为没有跨线程消息调度机制
    3. 并发访问:多个线程可以并发访问 MTA 中的 COM 对象,但需要确保线程安全
  2. 工作机制
    1. 直接调用:在 MTA 中,线程可以直接调用驻留在 MTA 中的 COM 对象,而不需要经过消息调度机制
    2. 线程安全:由于 MTA 允许并发访问,COM 对象的实现需要自行处理线程安全问题
  3. 场景
    1. 后台处理:适用于后台处理和高并发操作,因为不需要消息循环且允许并发访问
    2. 线程安全管理:适用于开发者能够自行管理线程安全的场景

  1. 理解:
    1. 多线程公寓(MTA)模式下,操作系统为所有的 MTA 线程创建一个共享的 MTA 空间
    2. 在这个 MTA 空间中创建的 COM 对象可以被所有加入 MTA 的线程直接访问
    3. 由于多个线程可以并发访问这些对象,因此需要同步机制来保证线程安全

安全性

  1. COM 包括各种安全性机制,特别是在 DCOM 环境中,提供身份验证和授权功能

示例代码

疑问

COM组件的好处

  1. COM 组件可以用不同的编程语言编写,并且可以在其他语言编写的应用程序中使用
    1. 这是因为 COM 使用二进制标准进行接口定义,任何支持 COM 的语言都可以调用这些接口
  2. 无论组件是用 C++C#VB 或其他语言编写的,只要遵循 COM 规范,都可以相互调用
  3. COM 组件以二进制形式分发,不需要源代码即可使用。这使得组件的重用和部署变得简单
  4. 通过注册表管理,COM 组件可以实现版本控制和兼容性管理,不同版本的组件可以共存
  5. DCOM 扩展了 COM,使其支持跨网络的组件调用,允许在不同机器上运行的应用程序相互通信
  6. 通过 DCOMRPC 机制,COM 组件可以在不同进程甚至不同计算机上透明地调用
  7. 使用 COM 组件时,可以在运行时动态发现和绑定组件,而不需要在编译时知道具体实现
  8. COM 提供了一种机制,可以在不修改客户端代码的情况下替换组件实现。这使得系统具有更好的扩展性和可维护性
  9. COM 组件通过接口标准化了功能的暴露方式,保证了接口的稳定性和一致性
  10. 通过引用计数和 AddRef/Release 方法,COM 组件规范化了对象的生命周期管理,避免了内存泄漏和悬挂指针
  11. COM 组件可以通过 Windows 的安全机制进行访问控制,确保只有授权的用户和进程可以调用组件的方法
  12. 通过将组件放在不同的进程中运行,COM 提供了更好的进程隔离,增加了系统的稳定性和安全性

CoInitialize

  1. COM 初始化函数之一,它用于初始化当前线程的 COM 库,并设置该线程的并发模型
  2. 调用 CoInitialize 或其扩展版本 CoInitializeEx 是在使用 COM 库之前必须执行的步骤之一

  1. CoInitializeExCoInitialize 的扩展版本,提供了更灵活的线程并发模型设置

RPC

概述

  1. 远程过程调用(RPC, Remote Procedure Call)是一种协议,允许程序在不同的地址空间中执行过程或函数调用,这些地址空间可以在同一台计算机上,也可以在网络上的不同计算机上
  2. RPC 使得网络通信变得透明,程序员可以像调用本地函数一样调用远程过程

基本概念

  1. RPC 通常涉及两个主要组件:客户端和服务器。客户端发起 RPC 调用,服务器执行该调用并返回结果
  2. 使用 IDL 描述远程过程的接口,包括函数签名和数据类型。这些描述将被编译成客户端和服务器的存根代码
  3. 客户端存根(Client Stub)和服务器存根(Server Stub)负责将参数打包(封送)和解包(解封),并通过网络进行传输
  4. RPC 使用底层传输协议(如 TCP/IP)进行通信

工作流程

  1. 客户端调用本地存根函数:客户端调用本地存根函数,传递参数
  2. 参数封送:客户端存根将参数打包成网络数据包
  3. 发送请求:客户端存根通过网络将请求发送到服务器
  4. 接收请求:服务器接收请求,并将数据包传递给服务器存根
  5. 参数解封:服务器存根解包数据,恢复原始参数
  6. 执行远程过程:服务器存根调用实际的远程过程,将结果返回给服务器存根
  7. 结果封送:服务器存根将结果打包成网络数据包
  8. 发送结果:服务器通过网络将结果发送回客户端
  9. 接收结果:客户端接收结果,并将数据包传递给客户端存根
  10. 结果解封:客户端存根解包数据,恢复原始结果,并返回给客户端调用者

Windows 的 RPC

  1. Windows 提供了自己的 RPC 实现,称为 Windows RPC(或 Microsoft RPC
  2. 它使用 IDL 文件和 MIDL 编译器生成存根代码,并提供了一组 API 用于实现 RPC 调用

IDL 文件

服务器代码

客户端代码

DCOM

概述

  1. 分布式组件对象模型(DCOMDistributed Component Object Model)是 COM(组件对象模型)的扩展,旨在支持跨进程和跨网络的对象调用
  2. DCOM 通过网络协议和远程过程调用(RPC)机制,使得客户端应用程序可以调用远程服务器上的 COM 对象,就像调用本地对象一样

工作机制

  1. DCOM 使用标准的网络协议(如 TCP/IP)进行通信
    1. 这使得它可以在不同的计算机和网络环境中工作,支持跨网络的对象调用
  2. DCOM 依赖 RPCRemote Procedure Call)机制来实现跨进程和跨网络的调用
    1. RPC 允许程序调用另一个地址空间(通常在另一台物理机器上)的过程或函数,就像在本地调用一样
  3. DCOM 使用代理(Proxy)和存根(Stub)来封送和解封远程调用的参数和结果:
    1. 代理:在客户端上运行,负责将客户端的调用请求封装为消息,并通过网络发送到服务器
    2. 存根:在服务器上运行,负责接收消息并调用实际的 COM 对象,然后将结果封装为消息发送回客户端
  4. DCOM 使用接口封送(Marshaling)将参数和返回值从一个地址空间转换到另一个地址空间
    1. 接口封送器负责将参数打包成可以通过网络传输的格式,并在接收端解包
  5. DCOM 提供了多种安全性机制,包括身份验证、授权和加密,确保跨网络调用的安全性

实现细节

  1. 在使用 DCOM 之前,需要初始化 COM 库,并指定线程的并发模型(通常使用多线程公寓模型 MTA

  1. 使用 CoCreateInstanceEx 函数创建远程 COM 对象实例。该函数允许指定服务器位置,并返回对象的接口指针

远程服务器上的 COM 对象实现

客户端调用远程 COM 对象

问题

  1. CoCreateInstanceExmultiQI
    1. CoCreateInstanceEx 成功时,客户端获得的是一个代理对象的指针
      所以multiQI里面存的是代理对象的指针
    2. 代理对象实现了请求的接口(如 IExample),并将客户端调用封装成 RPC 请求发送到服务器
      通过代理对象去调用目标函数,实际上代理对象会把这个调用封装成RPC请求,发给目标服务器
    3. 在服务器上,存根对象接收 RPC 请求,解封参数,并调用实际的 COM 对象的方法
  2. 代理对象和存根对象
    1. COM 运行时库:负责创建和管理代理对象和存根对象,处理对象的封送和解封
    2. RPC 运行时库:负责处理网络通信,确保消息的可靠传输

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

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

发表评论

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