本文不会对Com进行非常详细的分析 因为这个技术分析起来难度还是非常大的 要想真正弄懂还是非常困难的 我只会针对d3d中使用到的com技术和comptr技术进行说明 所以看完本文后 可以熟练使用d3d中使用到的相应技术

comptr类似于c++11中的智能指针,用于管理com对象的生命周期,所以我们先会讲解com对象的知识,然后才会讲解comptr的相关内容

为什么微软要推出COM技术?

一言以蔽之 为了彻底解决二进制兼容的问题(即生成的dll文件可以被不同语言和不同编译器生成的可执行文件所加载和调用,并且可以随便升级dll文件而不会影响到原来的老代码) 但是为了解决这个问题实在是有太多技术难点要克服。

所以导致com技术的实现原理现在异常复杂 那么问题就来到了 所谓的二进制兼容问题到底是什么?

假设我们现在使用c++代码编写了一个类 并且生成了dll文件提供给别人使用

我们的头文件mydll.h如下:

class __declspec(dllexport)  MyClass
{
public:MyClass(int i);int TestFunc();private:int member_age_;
};

mydll.cpp源文件如下:

#include"mydll.h"MyClass::MyClass(int i)
{member_age_ = i;
}int MyClass::TestFunc()
{member_age_++;return member_age_;
}

我们生成为dll后 给别人使用 在别人的代码里会像这种方式来使用我们的代码:

#include"mydll.h"
int main()
{MyClass* temp=new MyClass(1);//做一些事情delete temp;return 0;
}

这段代码看似没有任何问题, 但是当我们更新了我们的dll文件过后 如在MyClass类中添加了新的成员变量,这段代码将直接导致问题,因为new的时候是先调用底层的malloc分配内存 而老版本的dll中只有一个int变量,新的则不是 所以malloc分配的内存大小就会出问题.直接就会导致你的内存访问越界

所以!如果我们想让用户能够不重新编译出新的exe 直接替换dll文件就能使用我们新的dll 那么我们就不能够让用户写的代码中来为我们写好的类分配内存,应该让我们自己来分配

所以现在我们改造了我们的头文件,导出了创造对象的函数,让用户的代码中不再能够直接new我们的类

mydll.h


class __declspec(dllexport)  MyClass
{
public:MyClass(int i);int TestFunc();private:int member_age_;
};
__declspec(dllexport) MyClass*  create();

mydll.cpp

#include"mydll.h"MyClass::MyClass(int i)
{member_age_ = i;
}int MyClass::TestFunc()
{member_age_++;return member_age_;
}MyClass* create()
{return new MyClass(1);
}

现在用户使用我们的类的方式如下:

#include"mydll.h"
int main()
{MyClass* i = create();i->TestFunc();
}

这样即使我们更新了我们的dll,用户exe文件也不需要重新编译,可以无缝直接使用

但是现在我们又面临一个问题,我们该如何让用户来释放这个由create函数申请的内存呢?

很多小伙伴可能直接就会把代码写成如下的样子

#include"mydll.h"
int main()
{MyClass* i = create();i->TestFunc();delete i;
}

但是这样是没法去真正释放内存的!为什么呢?因为每一家编译器厂商对于malloc的底层实现都不一样 所以delete会导致内存释放错误!delete是先调用析构函数,然后再调用free,问题就在于这个free,如果比较了解malloc的小伙伴就会知道,内存管理是一个比较麻烦的过程,会有很多额外的内存空间来记录当前内存块的状态,大小等东西 一块简化的由malloc申请的内存块如下所示

问题就在于什么呢?不同的c标准库对于该实现不是统一的!也就是说,可能msvc和gcc分配的内存块中他们的内存布局不一致,如果你的dll是使用msvc编译器编译的而用户的编译器是gcc那么调用free将会直接导致内存释放错误!

基于以上原因,我们现在也不能让用户手动的调用delete释放我们的内存了,我们继续改写我们的头文件如下


class __declspec(dllexport)  MyClass
{
public:MyClass(int i);int TestFunc();private:int member_age_;
};
__declspec(dllexport) MyClass*  create();
__declspec(dllexport) void release(MyClass* temp);

我们的源文件如下

#include"mydll.h"MyClass::MyClass(int i)
{member_age_ = i;
}int MyClass::TestFunc()
{member_age_++;return member_age_;
}MyClass* create()
{return new MyClass(1);
}void release(MyClass* temp)
{delete temp;
}

用户的使用方式如下:

#include"mydll.h"
int main()
{MyClass* i = create();i->TestFunc();release(i);
}

现在似乎一切都安好了,用户不能直接使用new和delete 我们的dll可以无缝更新了,更新之后exe并不会收到影响,但是我们现在又有一个问题了,如何让用户的不同类中共享多份相同的实例呢?你可能会说,那不是很简单嘛?指针都是共享的,直接复制指针不就在不同类中共享多份实例了嘛?

但是问题就在于,你什么时候应该释放该资源呢?比如我A类有一个实例,B类也有一个实例,A类的析构函数会调用release释放内存,B类的也会,那么如果我们不加上控制,是不是直接就重复释放了呢?

基于这种情况 我们就应该给我们的实例加上引用计数

关于引用计数,详情可参考我的这篇博客,对于引用计数有详细解释,这里就不过多叙述了

本质和智能指针中的引用计数是一样的

是时候来点现代c++了 c++11之超级重要之smart pointer详细解析_std::shared_ptr<std::string> (new std::string());_杀神李的博客-CSDN博客

所以我们应该再在头文件加上如下的接口


class __declspec(dllexport)  MyClass
{
public:MyClass(int i);int TestFunc();private:int member_age_;
};
__declspec(dllexport) MyClass*  create();
__declspec(dllexport) void release(MyClass* temp);
__declspec(dllexport) bool addref(MyClass* temp);

当我们想共享实例的时候我们调用addref增加引用计数,我们想释放的时候用release减少引用计数,当引用计数为0,释放资源

目前为止,我们仿佛已经真正的解决二进制兼容的问题了,可以随意更新我们的dll文件

所以我们dll中所有的类是不是都应该有这三个接口呢?是的 微软也是这么觉得的 所以微软做了一个最底层的基类 所有的基于com技术的类都应该继承这个类 名字叫做IUnknown(interface unknown)意思就是接口未确定的 这个类是个纯虚类,只规定了所有类都应该有的方法 IUnknown类一共有三个接口

AddRef():增加引用计数

Release():减少引用计数

QueryInterface():用来检查是否可以做向下转型的,因为让用户直接使用强转是很危险的,所以向下转型应该由dll内部来判断

那么问题来了,微软如何判断是否能够安全的向下转型呢?

微软引用了三个ID,guid,iid,clsid来判断

从上来看,所有的COM类其实都继承了IUnknown。但是,我拿个IUnknown接口有毛用啊,我还是需要把它转为我的具体类才行。假设有汽车类Car,它继承于ICar,像这样:

IUnknown* pUnk = NULL;

CreateCar(&pUnk);//这个函数就是我们上文提到的头文件里的create函数

在d3d中该create函数就为D3D12CreateDevice等函数

ICar* pCar = (ICar*)pUnk;

这样,我们拿到ICar指针才有意义。

但是微软认为,直接由用户来转型是不安全的,比如,你怎么知道pUnk一定可以转成ICar*呢。除此之外,ICar这个类不具有唯一性,我们需要唯一的一个标识符来确定一个类,那么这个标识符就是GUID。类ID就叫作CLSID,接口ID就叫作IID。我们需要一个转型的函数叫QueryInterface

QueryInterface作为IUnknown中的一个纯虚函数,做的事情其实很简单,判断自己能不能转成某个GUID所指向的类而已。如果不可以,则返回E_NOTIMPL.可以的话返回S_OK,并将转换后的指针作为参数返回,代码类似如下,可以体会一下:

public class Car : IUnknown, ICar
{
HRESULT QueryInterface(REFIID riid, void **ppvObject)
{
if (ISEQUAL_IID(riid, IID_ICar)) //riid和ICar的IID相同,说明可以转换成ICar
{
*ppvObject = static_cast<ICar*>(this);
return S_OK;
}
else if (ISEQUAL_IID(riid, IID_IUnknown))
{
*ppvObject = static_cast<IUnknown*>(this);
return S_OK;
}
return E_NOTIMPL;
}
}

一个真正的QueryInterface要做的事情还要多一点,如增加引用计数等,这里就不多说了。

外部是这样调用:

ICar* pCar = NULL;

pUnk->QueryInterface(IID_ICar, (void**)&pCar);

这样,我们就从pUnk得到了个ICar*。

基于这种情况微软又做了封装,既然我每个类都有对应的ID了 那么我的create函数是否可以统一化呢?

所以微软常见的create函数就被封装为了下面这种形式:

HRESULT CoCreateInstance([in]  REFCLSID  rclsid,[in]  LPUNKNOWN pUnkOuter,[in]  DWORD     dwClsContext,[in]  REFIID    riid,[out] LPVOID    *ppv
);

第一个参数:待创建组件的CLSID。

第二个参数:用于聚合组件。在d3d中用不上,不用了解,本文不再讨论

第三个参数:dwClsContext的作用是限定所创建的组件的执行上下文。在d3d中用不上,不用了解,本文不再讨论

第四个参数:iid为组件上待使用的接口的iid。

CoCreateInstance 将在最后一个参数中返回此接口的指针。通过将一个IID传给CoCreateInstance,客户将无需在创建组件之后去调用 其QueryInterface函数。

第一个参数就是指定了,你所需要的类的clsid,第四个参数指定了你所需要的接口的iid,但是这时候又有问题了?我怎么知道我需要的clsid和iid是多少啊? chatgpt回答如下,因为本文只涉及到d3d中的com技术,而d3d中的com技术创建接口本身并不需要clsid,仅需要iid,而iid是可以通过msvc关键字__uuidof来获取的

 但是! 注意! 对于d3d中的com技术 均有特殊create函数使用,而不是依赖最普通最通用的CoCreateInstance函数,比如D3D12CreateDevice函数,所以我们并不需要去查询clsid,直接使用uuid去获取接口id即可!

到现在我们真正的搞懂了com技术 现在来让我们看看com技术是如何在d3d中运用的吧,看一个小例子:

IUnknown* pDevice;
D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, __uuidof(ID3D12Device), (void**)&pDevice);
ID3D12Device* pDevice1;
pDevice->QueryInterface(__uuidof(ID3D12Device), (void**)&pDevice1);

现在我们就可以拿到pDevice1真正的子类指针去操作了!

恭喜 你现在彻底搞懂了d3d中使用到的com技术!

d3d(Direct X)中的com技术详解相关推荐

  1. d3d(Direct X)中的comptr技术详解

    在这篇文章中 d3d(Direct X)中的com技术详解_杀神李的博客-CSDN博客 我们已经学会了d3d中的com技术,现在让我们继续学习comptr 上文中我们说到,所有基于com技术的类都必须 ...

  2. 深度学习中的正则化技术详解

    目录 基本概念 1. 参数范数惩罚 1.1 \(L^2\)正则化 1.2 \(L^1\)正则化 1.3 总结\(L^2\)与\(L^1\)正则化 2. 作为约束的范数惩罚 3. 欠约束问题 4. 数据 ...

  3. php中会话技术,php session会话技术详解

    会话技术详解 发布时间-04-来源:青锋建站作者:青锋建站 PHP中的会话支持是在并发访问时由一个方法来保存某些数据,被广泛用于保持会话状态,存储会话变量.以下是青锋建站给大家分享的有关技术的详解,包 ...

  4. 作为SLAM中最常用的闭环检测方法,视觉词袋模型技术详解来了

    摘自:https://mp.weixin.qq.com/s/OZnnuA31tEaVt0vnDOy5hQ 作为SLAM中最常用的闭环检测方法,视觉词袋模型技术详解来了 原创 小翼 飞思实验室 今天 基 ...

  5. 【H.264/AVC视频编解码技术详解】八、 熵编码算法(2):H.264中的熵编码基本方法、指数哥伦布编码

    <H.264/AVC视频编解码技术详解>视频教程已经在"CSDN学院"上线,视频中详述了H.264的背景.标准协议和实现,并通过一个实战工程的形式对H.264的标准进行 ...

  6. P2P技术详解(二):P2P中的NAT穿越(打洞)方案详解

    目录 1.内容概述 2.反向链接技术:一种特殊的P2P场景(通信双方中只有一方位于NAT设备之后) 3.基于UDP协议的P2P打洞技术详解 3.1.原理概述 3.2.典型P2P情景1: 两客户端位于同 ...

  7. VXLAN中EVPN技术详解(二)——EVPN与VXLAN分布式网关

    今天继续给大家介绍VXLAN技术,本文主要内容是EVPN技术的主要应用--VXLAN分布式网关.本文从VXLAN分布式网关实现的角度,对EVPN的实现进行了详细的讲解. 阅读本文,您需要有一定的VXL ...

  8. GPU虚拟化技术详解

    GPU虚拟化技术详解 GPU英文名称为Graphic Processing Unit,GPU中文全称为计算机图形处理器,1999年由NVIDIA公司提出. 一.GPU概述 GPU这一概念也是相对于计算 ...

  9. Miracast技术详解(一):Wi-Fi Display

    Miracast概述 Miracast Miracast是由Wi-Fi联盟于2012年所制定,以Wi-Fi直连(Wi-Fi Direct)为基础的无线显示标准.支持此标准的消费性电子产品(又称3C设备 ...

最新文章

  1. RH5.4下samba共享配置实例(3)
  2. Spring(07)——单例注入多例之lookup-method
  3. option 82与DHCP中继代理
  4. wxWidgets:键盘 wxWidgets 示例
  5. EditThisCookie插件的使用方法
  6. c语言 read 文件字节没超过数组大小时会怎样_剑指信奥 | C 语言之信奥试题详解(四)...
  7. 畅游“私有化”完成,搜狐股价暴涨逾25%
  8. day9——函数初识
  9. flume学习(十):如何使用Spooling Directory Source
  10. Python批量查询恶意地址信息
  11. 小白快速入门Laravel 5.8框架
  12. linux输入中文老是有字母,Fcitx 中文输入法中世界语字母的输入方法
  13. 职场 | 因特尔(Intel)无线modem系统设计师实习岗位面试总结
  14. css横排文字光影效果_css3模糊发光文字动画特效
  15. vue怎么给pc端浏览器设置一个最小屏幕_vue项目实现移动端适配的案例
  16. 央视“315晚会”上曝光的“网络水军”要怎么从技术上防范
  17. Android App Dark Theme(暗黑模式)适配指南,android实战mysql
  18. linux虚拟网卡修改mac地址,Win10秘笈:两种方式修改网卡物理地址(MAC)
  19. 护理和计算机哪个专业好,护理专业考研的就业前景和方向
  20. 移动端h5图片下载-前端小白初长成

热门文章

  1. 大数据-Hadoop概论
  2. 手机user agent大全下载 整理发布一批移动设备的user agent【分享】
  3. ARM售价超400亿美元?软银与英伟达有望达成史上最大半导体交易
  4. 面向多段落高考阅读理解的答案句抽取方法
  5. 通过配置实现电脑和手机访问分别显示不同的页面(以thinkphp5为例)
  6. 接受别人发来的QQ超大附件(1G以上)时,经常传输中断的解决办法
  7. [笔记]表单校验validate.js
  8. 【2020/02/11】每日早报
  9. 知觉图-消费者对于某些品牌偏好的形象化表述
  10. .NET桌面开发的一些思考