COM组件笔记(二)

一、内存资源的何时释放?

在主函数的最后面执行delete pA确实是一个可行的办法。但却不是好办法。因为这样子最终是释放了pA的内存资源,不过却不是“及时”(在pA所指的组件不用时)地释放内存资源。

如果一个程序,所有的资源在不用时都没有及时释放,这个程序在运行中所占用的内存将是巨大的。如何解决这个问题呢?这就需要引用计数技术。

 

二、引用计数的原理

  • 引用计数技术就是用来管理对象生命期的一种技术。
  • 对象O可能同时被外界A,外界B,外界C引用。也就是说外界A,外界B,外界C可能都在使用对象O。
  • 每次当对象被外界引用时,计数器就自增1。
  • 每次当外界不用对象时,计数器就自减1。
  • 在计数值为零时,对象本身执行delete this,销毁自己的资源。
  • 引用计数使得对象通过计数能够知道何时对象不再被使用,然后及时地删除自身所占的内存资源。在第一节当中的实现的IUnknown接口的AddRef与Release就是引用计数的实现方法。

以下是自己基于上一节代码,添加引用计数后的Demo3:

复制代码
  1 #define  _CRT_SECURE_NO_WARNINGS
  2 #include <iostream>
  3 #include <Unknwn.h>
  4 using namespace std;
  5 
  6 // {4E22FE4C-FC61-4616-B13E-7AC19186000D}
  7 static const IID IID_IX =
  8 { 0x4e22fe4c, 0xfc61, 0x4616, { 0xb1, 0x3e, 0x7a, 0xc1, 0x91, 0x86, 0x0, 0xd } };
  9 
 10 // {26C7D646-9C25-4C88-AAC5-215FC0569CD5}
 11 static const IID IID_IY =
 12 { 0x26c7d646, 0x9c25, 0x4c88, { 0xaa, 0xc5, 0x21, 0x5f, 0xc0, 0x56, 0x9c, 0xd5 } };
 13 
 14 
 15 // 接口IX
 16 interface IX : public IUnknown
 17 {
 18     virtual void Fx1() = 0;
 19     virtual void Fx2() = 0;
 20 };
 21 
 22 // 接口IY
 23 interface IY : public IUnknown
 24 {
 25     virtual void Fy1() = 0;
 26     virtual void Fy2() = 0;
 27 };
 28 
 29 // 组件CA
 30 class CA : public IX, public IY {
 31     //构造和析构
 32 public:
 33     CA() {
 34         m_lCount = 0;
 35 
 36         // 构造时,需要自增引用计数
 37         AddRef();
 38     }
 39 
 40     virtual ~CA() {
 41         cout << "我被释放了!" << endl;
 42     }
 43 
 44 public:
 45     virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void** ppv) {
 46         if (iid == IID_IUnknown) {
 47             // 如果未知的话,我们一般来说返回先继承的那个接口
 48             *ppv = static_cast<IX*>(this);
 49         }
 50         else if (iid == IID_IX) {
 51             // 返回IX接口
 52             *ppv = static_cast<IX*>(this);
 53         }
 54         else if (iid == IID_IY) {
 55             // 返回IY接口
 56             *ppv = static_cast<IY*>(this);
 57         }
 58         else {
 59             // 查询不到IID,*ppv返回NULL
 60             *ppv = NULL;
 61             return E_NOINTERFACE; // 函数返回值返回E_NOINTERFACE,表示调用的组件不支持iid的接口
 62         }
 63 
 64         AddRef();    //之后会提到
 65 
 66         return S_OK;    // 代表函数成功执行
 67     }
 68     virtual ULONG STDMETHODCALLTYPE AddRef() {
 69 
 70         // 简单实现方法
 71         return ++m_lCount;
 72 
 73         // 多线程编程下采用如下方法,这种方法确保同一时刻只会有一个线程来访问成员变量
 74         // return InterlockedIncrement(&m_lCount);
 75     };
 76     virtual ULONG STDMETHODCALLTYPE Release() {
 77         // 简单实现方法
 78         if (--m_lCount == 0) {
 79             delete this;    // 销毁自己
 80             return 0;
 81         }
 82 
 83         return m_lCount;
 84 
 85         // 多线程编程下采用如下方法,这种方法确保同一时刻只会有一个线程来访问成员变量
 86         /*if (InterlockedDecrement(&m_lCount) == 0) {
 87             delete this;
 88             return 0;
 89         }
 90         return m_lCount;*/
 91     };
 92 
 93 public:
 94     virtual void Fx1() {
 95         cout << "Fx1" << endl;
 96     }
 97 
 98     virtual void Fx2() {
 99         cout << "Fx2" << endl;
100     }
101 
102     virtual void Fy1() {
103         cout << "Fy1" << endl;
104     }
105 
106     virtual void Fy2() {
107         cout << "Fy2" << endl;
108     }
109 
110     // 数据
111 public:
112     long m_IAA;
113     long m_IAB;
114     long m_IAC;
115     long m_lCount;
116 
117 };
118 
119 int main(int argc, char* argv[])
120 {
121     HRESULT hr;
122 
123     // 创建组件
124     CA* pA = new CA();    // 引用计数1
125 
126     // 从组件查询IUnknown接口
127     IUnknown* pIUnknown = NULL;
128     hr = pA->QueryInterface(IID_IUnknown, (void**)&pIUnknown);    // 引用计数2
129     if (SUCCEEDED(hr)) {    // 对于HRESULT返回值的判断,一般采用SUCCEEDED
130 
131         pA->Release();    // pA不再使用,引用计数=1
132         pA = NULL;        // 防止再不小心使用m_pA
133 
134         // 从IUnknown查询IX接口
135         IX* pIX = NULL;
136         hr = pIUnknown->QueryInterface(IID_IX, (void**)&pIX);    // 引用计数2
137         if (SUCCEEDED(hr)) {
138             // 调用IX接口方法
139             pIX->Fx1();
140             pIX->Fx2();
141         }
142 
143         IY* pIY = NULL;
144         hr = pIUnknown->QueryInterface(IID_IY, (void**)&pIY);    // 引用计数3
145         if (SUCCEEDED(hr)) {
146             pIY->Fy1();
147             pIY->Fy2();
148         }
149 
150         if ((void*)pIX != (void*)pIY) {
151             cout << "pIX != pIY" << endl;
152         }
153 
154         if ((void*)pIUnknown != (void*)pIY) {
155             cout << "pIUnknown != pIY" << endl;
156         }
157 
158         if ((void*)pIUnknown != (void*)pIX) {
159             cout << "pIUnknown != pIX" << endl;
160         }
161 
162         pIY->Release();    // pIY不再使用,引用计数2
163         pIY = NULL;
164 
165         // 从IX查询IY
166         IY* pIY2 = NULL;
167         hr = pIX->QueryInterface(IID_IY, (void**)&pIY2);    //引用计数3
168 
169         pIX->Release();  // pIX不再使用,引用计数2
170         pIX = NULL;
171 
172         if (SUCCEEDED(hr)) {
173             pIY2->Fy1();
174             pIY2->Fy2();
175         }
176 
177         pIY2->Release(); //    pIY2不再使用,引用计数1
178         pIY2 = NULL;
179     }
180 
181     //=====================================
182     // 目前引用计数位1,因为pIUnknown还在使用
183     //=====================================
184     IX* pIX2 = NULL;
185     hr = pIUnknown->QueryInterface(IID_IX, (void**)&pIX2);
186     if (SUCCEEDED(hr)) {
187         IX* pIX3 = NULL;
188         pIX3 = pIX2;    // 执行了赋值
189         pIX3->AddRef();    // 由于上一句执行了赋值,所以引用计数需要自增,引用计数为3
190 
191         pIX3->Fx1();
192         pIX3->Fx2();
193 
194         pIX3->Release();    // pIX3不再使用,引用计数为2
195         pIX3 = NULL;
196     }
197     pIX2->Release();        // pIX2不再使用,引用计数为1
198     pIX2 = NULL;
199 
200     pIUnknown->Release();    // pIUnknown不再使用,引用计数为0,Release函数里执行了delete this
201     pIUnknown = NULL;
202 
203     // delete pA; 不需要再调用
204 
205     getchar();
206     return 0;
207 }
View Code
复制代码

这里需要注意AddRef函数和Release的意义和其使用的时机:

  • AddRef,使组件的引用计数自增1。
  • 在返回之前调用AddRef。比如组件的构造函数,QueryInterface函数。
  • 在赋值之后调用AddRef比如pIX3 = pIX2,这时需要pIX3->AddRef();
  • Release,使组件引用计数自减1,如果引用计数为零释放本身的内存资源。
  • 在接口使用完之后,调用Release。
  • 查看Section2Demo1,关于AddRef 与Release的使用,并适当注释一些AddRef或Release查看CA的析构函数是否运行或会不会出现对无效指针(野指针)的操作。

 

三、引用计数的优化

分析以下代码:

 

复制代码
if(SUCCEEDED(hr)){
    IX *pIX3 = NULL;
    pIX3 = PIX2;
    PIX3->AddRef();
    
    PIX3->Fx1();
    PIX3->Fx2();

    PIX3->Release();
    PIX3 = NULL;  
}
复制代码

 

对于pIX3与pIX3来说,他们都是同一个接口,生命周期是一样的,这个接口在一个块中,执行了一次AddRef,一次的Release,其实就相当于这二者都没有执行。是否可以进行优化呢?考虑以下优化:

if(SUCCEEDED(hr)){
    IX *pIX3 = NULL;
    pIX3 = PIX2;
    
    PIX3->Fx1();
    PIX3->Fx2();
}

引用计数优化原则:

一、输入参数原则:

  • 输入参数指的是给函数传递某个值的参数。在函数体中将会使用这个值但却不会修改它或将其返回给调用者。在C++中,输入参数实际上就是那些按值传递的参数。
  • 对传入函数的接口指针,无需调用AddRef与Release
复制代码
void Fun(IX *pIX){
    IX *pIX2 = pIX;
    
    // pIX2->AddRef();   //可优化

    pIX2->Fx1();
    pIX2->Fx2();  
    
    // pIX2->Release();  //可优化  
}
复制代码

 

二、局部变量原则
对于局部复制的接口指针,由于它们只是在函数的生命期内才存在,因此无需调用AddRef与Release

 

posted @   炫迈吃到爽  阅读(73)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示