d3d(Direct X)中的com技术详解

news2024/11/17 14:21:08

本文不会对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技术,而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技术!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/545655.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深度学习基础篇之卷积神经网络(CNN)

一、CNN的基本结构 首先我们来看CNN的解百纳结构&#xff0c;一个常见的图像识别CNN模型如下图&#xff1a; 从图中可以看出最左边的图像就是模型的输入层&#xff0c;在计算机中就是若干个矩阵&#xff0c;这点与DNN类似。 接着是卷积层&#xff08;Convolution Layer&…

rtmp协议

目录 1 rtmp格式 2 header 3 chunk data 1 rtmp格式 Real Time Messaging Protocol&#xff08;实时消息传送协议协议)是Adobe Systems公司为Flash播放器和服务器之间音频、视频和数据传输开发的私有协议。 在RTMP协议中信令和媒体数据都称之为Message&#xff0c;包含Mess…

Intellij IDEA 提示 Thrift Support 支持不兼容

最近升级 Intellij IDEA 后老提示 Thrift Support 不兼容。 后来看了下这个插件已经不少时间没有更新了&#xff0c;也一直不知道这个插件是干什么 用的&#xff0c; 后来看了下&#xff0c;这个插件是&#xff1a; Thrift是一种接口描述语言和二进制通讯协议&#xff0c;它被…

【031】基于Vue的学生宿舍管理系统课设(含源码、数据库、运行教程

前排提示&#xff1a;项目源码已放在文末 基于VueSpringbootmysql员工考勤管理系统(多角色登录、请假、打卡) 开发环境&#xff1a;SpringbootMysqlVueNodejsMavenJDK1.8&#xff0b;redis 技术栈&#xff1a;spring-boot、mysql、mybatis-plus 数据库&#xff1a; 源码、…

Centos7单机部署Flink13.6及测试FinkCDC同步MySQL

一、背景 公司CDH6.3.2里面的版本是Flink1.12.0。而因为FlinkCDC2.0.0只支持Flink1.13.0以后&#xff0c;版本不匹配&#xff0c;所以只能升级版本。但是升级版本是个大工程&#xff0c;要编译、要parcel制作工具&#xff0c;而且是生产环境的升级&#xff0c;没办法因为要测试…

初识Spring MVC框架,Spring MVC工作原理

Java EE三层架构 在Java EE开发中&#xff0c;系统经典的三层架构包括表现层、业务层和持久层。三层架构中&#xff0c;每一层各司其职&#xff0c;表现层(Web层&#xff09;负责接收客户端请求&#xff0c;并向客户端响应结果;业务层( Service层&#xff09;负责业务逻辑处理…

3 手工推导Neural Networ

线性模型假设的问题 如上图&#xff0c;对非线性类边界的数据进行分类 一个解决方案是将数据映射到更高维的空间&#xff0c;就变成线性可分的了。 ϕ \phi ϕ 是一个映射函数&#xff0c;将x从一个低维空间映射到高维空间。 ϕ \phi ϕ 可不可以是一个线性函数&#xff1f; …

RK3568平台开发系列讲解(驱动基础篇)GPIO使用以及gpio-leds驱动讲解

🚀返回专栏总目录 文章目录 一、GPIO 介绍二、RK3568 GPIO 状况三、GPIO 引脚计算四、ITX-3568JQ LED4.1 LED 原理图4.2 LED 设备树4.3 LED 使用五、gpio-leds驱动5.1 介绍5.2 数据结构5.3 驱动分析沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇将介绍 GPIO使用…

[Hadoop]Apache Hadoop、HDFS

目录 大数据导论与Linux基础 Apache Hadopp概述 Hadoop介绍 Hadoop现状 Hadoop特性优点 Hadopp架构变迁 Apache Hadopp集群搭建 Hadopp集群简介 Hadoop集群模式安装 Hadoop集群启停命令、Web UI HDFS分布式文件系统基础 分布式存储系统的核心属性及功能含义 HDFS简…

Linux多路转接之epoll

文章目录 一、select方案和poll方案还存在的缺陷二、epoll的认识1.epoll的基本认识2.epoll的原理3.epoll函数接口 三、编写epoll服务器四、epoll工作方式1.LT模式2.ET模式 一、select方案和poll方案还存在的缺陷 多路转接方案一开始是select方案&#xff0c;但是select方案缺点…

基于segment anything model(SAM)相关性研究的各个方向论文/项目汇总

目录 简介anything项目整理AnyObjectAnyGenerationAny3DAnyModelAnyTaskAnyX 论文汇总AnyObejctAnyGenerationAnyModelAnyTask 简介 有关anything相关的主流任务: 2d检测相关&#xff08;AnyObject&#xff09;, 3d检测相关&#xff08;Any3D&#xff09;,AI生成相关&#xff…

栈和队列OJ题:LeetCode--225.用队列实现栈

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;今天给大家带来的是LeetCode--225.用队列实现栈 数 据 结 构 专 栏&#xff1a;数据结构 个 人 主 页 &#xff1a;stackY、 LeetCode 专 栏 &#xff1a;LeetCode刷题训练营 LeetCode--225.用队列实现栈&#xff…

软件测试工程师如何提高自己的竞争力?

案例一来自我们的资深功能测试工程师招聘。当时&#xff0c;有一位拥有近 9 年测试经验的资深测试候选人&#xff0c;我对他的简历还是比较满意的&#xff0c;所以就安排了面谈。但是&#xff0c;在聊的过程中我很快发现&#xff0c;这位候选人绝大多数的测试经验积累都“强”绑…

精彩回顾 | 2022(第二届)超级CSO年度评选颁奖盛典

2023年5月13日&#xff0c;2022&#xff08;第二届&#xff09;超级CSO年度评选颁奖盛典在上海举行&#xff0c;来自全国各地近200位来宾、业界专家、企业代表、合作伙伴以及CSO/CISO共同出席。本次盛典得到了包括中国网络安全审查技术与认证中心&#xff08;CCRC&#xff09;、…

什么是即时 AI ?有哪些应用场景

什么是即时 AI &#xff1f; 即时 AI 是全球首款通过自然语言描述&#xff0c;快速生成可编辑的 UI 设计稿的设计工具。 输入文字描述后&#xff0c;即可一次性生成4张 包含矢量图层和图标、支持二次编辑、分层结构清晰 UI 设计稿。 即时 AI 目前已面向全部用户免费开放&#…

无效数据处理攻略: 如何从源头开始预防无效数据带来的风险

数据处理在现代社会中变得越来越重要&#xff0c;而对于数据的可靠性和准确性&#xff0c;我们始终非常关注。然而&#xff0c;即使在对数据进行了精心管理的情况下&#xff0c;无效数据依然可能存在&#xff0c;并可能对数据分析和决策带来不良影响。因此&#xff0c;处理无效…

[Windows驱动开发]-BlackBone实现内存读取的三种方式

文章目录 &#x1f6eb; 导读需求开发环境 升级优化&#xff08;vs2019&#xff09;相关地址Blackbone工程中的lib库添加Blackbone工程修改tools工程修改 旧文章整理&#xff08;vs2017&#xff09;功能描述内存读取-BlackBone库的集成内存读取-检测参数内存读取-ReadProcessMe…

【 数据处理系统 】(草稿)

文章目录 第3章 总体设计3.1 系统设计目标和原则3.2 系统架构设计3.3 数据采集模块设计3.4 数据预处理模块设计3.4.1 业务数据预处理模块设计3.4.2 日志数据预处理模块设计 3.5 数据存储设计3.6 数据仓库设计3.7 可视化模块设计 第4章 详细设计与实现4.1 数据采集模块4.1.1 数据…

一、11.C内存分配/堆栈

C内存分配/堆栈 01.C内存分配❤️ #include <stdio.h>const int g_A = 10; //常量区 int g_B = 20; //数据段 static<

【小菜鸡刷题记】----双指针篇

【小菜鸡刷题记】----双指针篇 剑指 Offer 18. 删除链表的节点剑指 Offer 22. 链表中倒数第k个节点剑指 Offer 25. 合并两个排序的链表剑指 Offer 52. 两个链表的第一个公共节点剑指 Offer 21. 调整数组顺序使奇数位于偶数前面剑指 Offer 57. 和为s的两个数字剑指 Offer 57 - I…