IUnknown—COM和MFC

2023-11-19

转自:http://hi.baidu.com/zhangqiuxi/blog/item/6d9603ad9c8fe5084b36d6a0.html

问题:      我用MFC编写COM程序有一段时间了,知道如何使用宏和嵌套类,以及如何在嵌套类中处理IUnknown接口,但对IUnknown的使用还是不太老练。


假设CMyClass是一个COM服务器,从CCmdTarget派生。它实现了IMyInterface。CMyClass的定义如下:

class CMyClass: public CCmdTarget

{

       BEGIN_INTERFACE_PART(...)

       STDMETHOD....

       END_INTERFACE_PART

       DECLARE_INTERFACE_MAP

} ;
因为CCmdTarget没有QueryInterface方法,CMyClass也没有这个方法。我的问题是:这个COM服务器的客户端总是要调用QueryInterface,它不调用ExternalQueryInterface, 也不调用 InternalQueryInterface。因此如果QueryInterface不是一个CMyClass的有效方法。这种情况该怎么处理?

解答:      关于QueryInterface的这个问题,除你之外的许多人都对它感到困惑。但是不用怕,读完本文就可以见分晓。导致困惑的一个潜在的问题是CCmdTarget本身不从CObject派生,并且两者都有不同于QueryInterface的虚函数——例如,CObject中的第一个虚函数是GetRuntimeClass,CCmdTarget中的第一个虚函数是OnCmdMsg。所以从CCmdTarget派生出来的类怎么可能是COM类呢?而且还必须实现头三个虚函数是AddRef, Release, 和 QueryInterface的IUnknown接口:
实际上,解开这个谜团的方法很简单,但要求深入了解MFC类库。为此,我们必须进入MFC考察一番,主要目的是看看在调用CoCreateInstance创建类实例的时候会发生一些什么事情。
首先,CoCreateInstance所做的第一件事情(或多或少)是到注册表中检查哪个DLL实现你的类。为简单起见,假设使用的是进程内服务器。CoCreateInstance加载你的DLL并调用特殊函数DllGetClassObject.。

// DLL 函数创建COM对象类工厂

DllGetClassObject(REFCLSID rclsid, // 类 ID

                     REFIID riid,       // 接口 ID

                     LPVOID* ppv)       // 返回的接口指针
     实际上,DllGetClassObject得不到你的类实例,它得到的是类工厂(IClassFactory)实例,通过这个类工厂来创建你得类实例。很怪是不是?MFC提供DllGetClassObject的实现,任务是搜索DLL中的所有类工厂,看看哪个类的ID寓所请求的类ID匹配。MFC是如何知道搜索哪个类工厂呢?当你在编写子记得类代码时,用宏DECLARE_OLECREATE 和 IMPLEMENT_OLECREATE声明并实现你自己这个类的类工厂。尤其是IMPLEMENT_OLECREATE声明一个COleObjectFactory的静态实例,这个对象将自己(通过调用类构造函数     COleObjectFactory::COleObjectFactory)添加到与模块或DLL关联的某个类工厂清单中。结果,给定某个类的ID,DllGetClassObject就知道如何发现与那个类关联的类工厂。
一旦CoCreateInstance有了某个类工厂,它便调用IClassFactory::CreateInstance。在此MFC的COleObjectFactory又提供缺省的实现。

//做了许多精简后

STDMETHODIMP COleObjectFactory::XClassFactory::CreateInstance(...)

{

       METHOD_PROLOGUE_EX(COleObjectFactory,

       ClassFactory)

       ……

       // 这里省略了许多代码

       CCmdTarget* pTarget = pThis->OnCreateObject();

       return pTarget->InternalQueryInterface(&riid, ppvObject);

}
这里省略了许多琐碎代码以便突出重点,这些代码包括:创建一个实例和调用InternalQueryInterface。OnCreateObject是个COleObjectFactory的虚拟函数,它通过MFC的运行时类创建你的类实例:

//同样也作了简化

CCmdTarget* COleObjectFactory::OnCreateObject()

{

       return (CCmdTarget*)m_pRuntimeClass->CreateObject();

}
     一旦COleObjectFactory::XClassFactory::CreateInstance有了你的类实例,便调用InternalQueryInterface获取类的IUnknown接口指针。这里省略了许多细节,例如,在实际代码中MFC要调用IClassFactory2::CreateInstanceLic并检查所有出错条件以及聚合。InternalQueryInterface 和 ExternalQueryInterface之间的差别是外部版本委派外部IUnknown(如果有的话),而内部版本则不然。但即使聚合,最终都要归到InternalQueryInterface。
     如果还不明白,不要紧。类工厂的CreateInstance函数创建了一个你的类实例,并调用CCmdTarget::InternalQueryInterface来获取类的IUnknown指针。InternalQueryInterface在哪里得到你的类IUnknown接口指针呢?难以捉摸的QueryInterface又在哪里?

经历了一些步骤和函数调用之后,CCmdTarget::InternalQueryInterface最终要调用一个函数:

LPUNKNOWN CCmdTarget::GetInterface(const void* iid)

{

       LPUNKNOWN ptr = NULL;

       if (iid == IID_IUnknown) {

           ptr = // 接口映射的第一个接口

       } else {

           ptr = // 在接口映射中查找iid

       }

       return ptr; // (如果没找到则为NULL)

}
     换句话说,如果请求的接口是IUnknown,则GetInterface返回接口映射中的第一个接口指针,否则它查找与请求的接口ID匹配的那一个。这里的诀窍在于:你的类所提供的每一个接口都必须和基类接口一样实现IUnknown,并且还要以相同的方式实现,至于InternalQueryInterface返回哪一个接口指针并不重要。CCmdTarget类自身没有QueryInterface函数,只有嵌套类有,这个嵌套类实现每个接口,每个接口又都实现IUnknown。
    图一是一个典型的COM类实现。.CPP文件说明了你必须为你的类所提供的每一个接口编写同样烦人IUnknown实现。每一个IUnknown方法为父类(从CcmdTarget派生)调用相应的ExternalXxx(或者InternalXxx——如果你不想要聚合)方法。这个实现对于你编写的每一个接口都一样。这是没有办法的,因为所有的接口都通过相同的单对象在内存中被实例化。AddRef 和Release必须增加和减少相同的物理指针——与AddRef 或Release实际属于哪个接口(嵌套类)无关。此乃高招所在。
     这就是为什么在IUnknown的情况下只有InternalQueryInterface能返回你的接口映射中的第一个接 口指针。由于仅仅实现IUnknown的类没什么用,所以在你的映射中至少还要实现一个接口。如果你不明白,下面是具体步骤的解释: 1、客户端调用CoCreateInstance创建你的类实例。

2、CoCreateInstance查找并加载DLL,调用DllGetClassObject

3、DllGetClassObject搜索DLLs的类工厂清单找出与类工厂匹配的类ID,然后返回这个类工厂的指针。

4、CoCreateInstance调用IClassFactory::CreateInstance创建你的应用实例。

5、COleObjectFactory::XClassFactory::CreateInstance 调用 CRuntimeClass::CreateInstance在内存中创建你的MFC类实例。然后CreateInstance调用InternalQueryInterface获取你的类IUnknown接口指针。InternalQueryInterface依次调用CCmdTarget::GetInterface.。

6、如果所请求的接口是IUnknown以外的其它接口,则GetInterface在你的类接口映射中查找这个接口。否则,GetInterface返回你的类接口映射中的第一个接口指针。这个工作对于实现IUnknown的每一个嵌套类都是一样的。

7、所有的函数返回、返回、返回……..而调用者只管接收某个接口指针。 谁说COM难学?
     在结束本文的讨论前,我想指出两个有用的技巧和建议。重载MFC缺省行为的方式有很多。第一,你可以在类工厂中,有时你可能想从COleObjectFactory类派生自己专用的类工厂。只要你愿意,可以这么做,诀窍是将类工厂挂钩在对象上。不要使用DECLARE_OLECREATE 和 IMPLEMENT_OLECREATE,因为这些宏已经将ColeObjectFactory写死在里面了。但你可以拷贝这些宏、改名以及江类工厂的名字改为自己的名字。尤其是你可能要重载COleObjectFactory::OnCreateObject方法。例如,如果你的COM对象是单实例的,就要重载OnCreateObject返回一个且是唯一的一个对象实例(不要使用静态对象,要不然可能遇到引用计数问题,因为静态实例引用计数是1并且最终得不到Release释放。应该取而代之用new在堆中分配单实例)。
最后,一个非常有用的重载是GetInterfaceHook。记得GetInterface吗?这个函数在你的接口映射中查找接口,或如果请求的是IUnknown,则就返回第一个接口。下面是一段参考代码:

LPUNKNOWN CCmdTarget::GetInterface(const void* iid)

{

       // 允许常规构子首先起来

       LPUNKNOWN lpUnk;

       if ((lpUnk = GetInterfaceHook(iid)) != NULL)

           return lpUnk;

       ……

     // 如前所述

}
     在做其它事情之前,GetInterface调用虚函数CCmdTarget::GetInterfaceHook,缺省CCmdTarget实现返回NULL,但如果你想以某种特别方式实现QueryInterface接口的话,只要重载GetInterfaceHook并返回别的东西就行了。一旦GetInterfaceHook的返回值为非空接口指针,则它首先调用QueryInterface,MFC将用到它。在我的另一篇文章中,曾讨论过如何重载GetInterfaceHook来完成一个完全不同的COM实现,其中用了ATL风格的多继承代替了MFC的嵌套类。 谁说COM复杂难懂?

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

IUnknown—COM和MFC 的相关文章

随机推荐

  • 报错EL1007E: Property or field ‘xxxxx’ cannot be found on null原因竟是这

    报错EL1007E Property or field xxxxx cannot be found on null 是什么原因 有时我们发现Springboot项目前端的 thymleaf 会报这样的错 EL1007E Property o
  • 设计模式 -- 工厂模式(Factory Pattern)

    简单工厂模式 Simple Factory Pattern 根据传入的参数决定实例化哪个对象 优点 不直接在客户端创建具体产品的实例 降低了耦合性 缺点 违反了开闭原则 对扩展开放 对修改关闭 不容易形成高内聚松耦合结构 每当我们增加一种产
  • 10个程序员可以接私活的平台和一些建议!

    来源 http mrw so 5isQLi 什么样的私活不能接 1 没有第三方担保的个人对个人的尽量不要接 双方都没保障 出了问题很大的可能撕破脸皮不了了之 2 一上来就直接说给我开发一个什么软件 不说具体需求 没有需求文档的都不靠谱 这样
  • zotero如何用markdown记笔记

    1 去下载 Releases adam p markdown here GitHub 拖拽到 2 ctrl alt M对笔记进行渲染
  • 【华为OD机试真题2023B卷 JAVA&JS】字符串加密

    华为OD2023 B卷 机试题库全覆盖 刷题指南点这里 字符串加密 时间限制 2秒 内存限制 65536K 语言限制 不限 题目描述 给你一串未加密的字符串str 通过对字符串的每一个字母进行改变来实现加密 加密方式是在每一个字母str i
  • Altium Designer覆铜后变为绿色是怎么回事?

    最近在做一个小板子 但是覆完铜后也把网络设置为GND 但是板子变绿色了 之前没有碰到过 也查过想过资料说是规则设置有问题 找了半天也没有发现规则相关问题 最后终于找到了解决方法 我把步骤写在下面 出现的问题如图所示 不知道你们有没有碰到这样
  • wx.config的时候总是报错63002是什么原因,配置时总是 invalid signature

    真的是真的是 我都弄的崩溃了差点 卡了三天的问题 各种留言各种找人还是不行 为了不再让各位体验那种感觉 zkhh666我的wx 首先说 我用的hbuilderx的uni做的 使用了推荐的插件jweixin module ps 我的问题是ur
  • PostgreSQL 关于Jsonb字段的处理详解(长期更)

    大家好 在开发的过程中由于业务复杂多变 普通的字符串格式满足不了业务开发需求 可能会用到jsonb字段去处理格外逻辑的业务 话不多说 1 一般jsonb存储的逻辑是 一个字段里面存储多条数据 以id为存储对象举例 该jsonb字段中存储的多
  • 参考文献格式GB/T 7714-2015的主要内容

    格式 主要责任者 题名 其他题名信息 文献类型标志 其他责任者 版本项 出版地 出版者 出版年 引文页码 引用日期 获取和访问路径 示例 1 余敏 出版集体研究 M 北京 中国书籍出版社 2001 179 193 2 昂温 G 昂温 P S
  • 八种点云聚类方法(一)— DBSCAN

    本文为博主原创文章 未经博主允许不得转载 本文为专栏 python三维点云从基础到深度学习 系列文章 地址为 https blog csdn net suiyingy article details 124017716 传统机器学习聚类的方
  • 步步学ACTIVEX网页控件开发

    本文将首先介绍如何使用Visual Studio开发一个简单的ActiveX控件 然后介绍ActiveX控件开发相关的基础知识 比如方法 属性和事件等 最后介绍如何利用这些知识 实现ActiveX控件和网页页面之间的 通信 MyActive
  • 【java面试常见2】

    文章目录 1 返回json串要加什么注解 2 RestController包含了什么注解 3 docker拉取镜像 4 springMVC的相关注解 5 vue框架和HTML有什么区别 为什么要使用vue 6 从Mysql中随机获取数据用什
  • untiy特殊文件夹

    1 Editor Editor文件夹可以在根目录下 也可以在子目录里 只要名子叫Editor就可以 比如目录 xxx xxx Editor 和 Editor 是一样的 无论多少个叫Editor的文件夹都可以 Editor下面放的所有资源文件
  • Windows挂载Linux网络共享文件夹

    Windows挂载Linux网络共享文件夹 创建Linux网络共享文件夹 用共享工具samba Ubuntu安装samba sudo apt install samba 配置Windows访问共享文件夹的账户 smbpasswd a riv
  • www.gvlib video.php,www.gvlib.com

    Domain Name gvlib com Registrar URL http www godaddy com Registrant Name Bin Song Registrant Organization Name Server NS
  • 13. linux系统监控

    系统监控 proc文件系统 proc文件系统是一种无存储的文件系统 当读其中的文件时 其内容动态 生成 当写文件时 文件所关联的写函数被调用 每个proc文件都关联着 字节特定的读写函数 因而它提供了另外的一种和内核通信的机制 内核 部件可
  • Dlib的编译

    有2种方法 1 新建空的工程 导入后编译 2 采用CMake自动生成工程文件后 再编译 官网上建议第1种方法 第1种方法好处就是编译后 在使用lib文件时 不需要将libjpeg libpng and zlib目录下的文件导入到工程文件中
  • 1.3>7?微软新模型“以小博大”战胜Llama2,网友:用Benchmark训练的吧?

    克雷西 发自 凹非寺量子位 公众号 QbitAI 一个参数量只有1 3B的大模型 为何引发了全网热议 原来虽然参数量不大 但效果已经超过了拥有7B参数的Llama2 这个 四两拨千斤 的模型 是来自微软最新的研究成果 核心在于只使用少量高质
  • vue2侦听器watch的概念与使用

    1 什么是watch侦听器 watch侦听器允许开发者监视数据的变化 是 vue 提供的一种用来观察和响应实例上数据变化的属性 属性发生变化 便会触发对应的监听函数 侦听器 watch 实际上是 vue 实例中的一个对象属性 语法格式如下
  • IUnknown—COM和MFC

    转自 http hi baidu com zhangqiuxi blog item 6d9603ad9c8fe5084b36d6a0 html 问题 我用MFC编写COM程序有一段时间了 知道如何使用宏和嵌套类 以及如何在嵌套类中处理IUn