COM连接点 - Part V - CComDynamicUnkArray::Add问题

来源:互联网 发布:照片审核工具 mac 编辑:程序博客网 时间:2024/06/01 10:22

From : http://blog.csdn.net/zj510/article/details/39178003

 

无意中发现一个有趣的问题,连接点中用于保存接收器对象的容器CComDynamicUnkArray有一些小问题。

calloc和_recalloc的参数传递问题

首先看一下连接点的声明:

[cpp] view plaincopyprint?
  1. template <class T,const IID* piid, class CDV = CComDynamicUnkArray > 
  2. class ATL_NO_VTABLE IConnectionPointImpl :  
  3.     public _ICPLocator<piid> 
  4.     typedef CComEnum<IEnumConnections, &__uuidof(IEnumConnections), CONNECTDATA, 
  5.         _Copy<CONNECTDATA> > CComEnumConnections; 
  6.     typedef CDV _CDV; 
  7. public
  8.     ~IConnectionPointImpl(); 
  9.     STDMETHOD(_LocCPQueryInterface)( 
  10.         _In_ REFIID riid,  
  11.         _COM_Outptr_ void** ppvObject) 
  12.     { 
  13. #ifndef _ATL_OLEDB_CONFORMANCE_TESTS 
  14.         ATLASSERT(ppvObject != NULL); 
  15. #endif 
  16.         if (ppvObject == NULL) 
  17.             return E_POINTER; 
  18.         *ppvObject = NULL; 
  19.  
  20.         if (InlineIsEqualGUID(riid, __uuidof(IConnectionPoint)) || InlineIsEqualUnknown(riid)) 
  21.         { 
  22.             *ppvObject = this
  23.             AddRef(); 
  24. #if defined(_ATL_DEBUG_INTERFACES) && !defined(_ATL_STATIC_LIB_IMPL) 
  25.             _AtlDebugInterfacesModule.AddThunk((IUnknown**)ppvObject, _T("IConnectionPointImpl"), riid); 
  26. #endif // _ATL_DEBUG_INTERFACES 
  27.             return S_OK; 
  28.         } 
  29.         else 
  30.             return E_NOINTERFACE; 
  31.     } 
  32.      
  33.     STDMETHOD(GetConnectionInterface)(_Out_ IID* piid2) 
  34.     { 
  35.         if (piid2 == NULL) 
  36.             return E_POINTER; 
  37.         *piid2 = *piid; 
  38.         return S_OK; 
  39.     } 
  40.     STDMETHOD(GetConnectionPointContainer)( 
  41.         _Outptr_ IConnectionPointContainer** ppCPC) 
  42.     { 
  43.         T* pT = static_cast<T*>(this); 
  44.         // No need to check ppCPC for NULL since QI will do that for us 
  45.         return pT->QueryInterface(__uuidof(IConnectionPointContainer), (void**)ppCPC); 
  46.     } 
  47.     STDMETHOD(Advise)( 
  48.         _Inout_ IUnknown* pUnkSink,  
  49.         _Out_ DWORD* pdwCookie); 
  50.     STDMETHOD(Unadvise)(_In_ DWORD dwCookie); 
  51.     STDMETHOD(EnumConnections)(_COM_Outptr_ IEnumConnections** ppEnum); 
  52.      
  53.     CDV m_vec; 
  54. }; 

我们可以看到IConnectionPointImpl是一个模板类,它有一个成员:CDV m_vec;

CDV是一个模板参数,它有一个默认值CComDynamicUnkArray,通常大家用的都是这个默认值CComDynamicUnkArray。

看一下CComDynamicUnkArray::Add函数:

[cpp] view plaincopyprint?
  1. inline DWORD CComDynamicUnkArray::Add(_In_ IUnknown* pUnk) 
  2.     IUnknown** pp = NULL; 
  3.     if (m_nSize == 0) 
  4.     { 
  5.         // Create array with _DEFAULT_VECTORLENGTH number of items. 
  6.         ATLTRY(pp = (IUnknown**)calloc(sizeof(IUnknown*),_DEFAULT_VECTORLENGTH)); 
  7.         if (pp == NULL) 
  8.             return 0; 
  9.         memset(pp, 0, sizeof(IUnknown*)*_DEFAULT_VECTORLENGTH); 
  10.         m_ppUnk = pp; 
  11.         m_nSize = _DEFAULT_VECTORLENGTH; 
  12.     } 
  13.     // Walk array and use empty slots if any. 
  14.     DWORD dwCookie = 1; 
  15.     for (pp = begin(); pp < end(); pp++) 
  16.     { 
  17.         if (*pp == NULL) 
  18.         { 
  19.             *pp = pUnk; 
  20.             return dwCookie;// cookie minus one is index into array 
  21.         } 
  22.         dwCookie++; 
  23.     } 
  24.     // No empty slots so resize array. 
  25.     // # of new slots is double of current size. 
  26.     int nAlloc = 0; 
  27.     HRESULT hr = AtlMultiply(&nAlloc, m_nSize, 2); 
  28.     if (FAILED(hr)) 
  29.     { 
  30.         return 0; 
  31.     } 
  32.  
  33.     pp = (IUnknown**)_recalloc(m_ppUnk, sizeof(IUnknown*),nAlloc); 
  34.     if (pp == NULL) 
  35.         return 0; 
  36.     m_ppUnk = pp; 
  37.     memset(&m_ppUnk[m_nSize], 0, sizeof(IUnknown*)*m_nSize); 
  38.     m_ppUnk[m_nSize] = pUnk; 
  39.     dwCookie = m_nSize+1; 
  40.     m_nSize = nAlloc; 
  41.     return dwCookie; // cookie minus one is index into array 

注意:

ATLTRY(pp = (IUnknown**)calloc(sizeof(IUnknown*),_DEFAULT_VECTORLENGTH));

pp = (IUnknown**)_recalloc(m_ppUnk, sizeof(IUnknown*),nAlloc);

是不是参数传错了?

calloc的原型是:

void *calloc(
   size_t num,
   size_t size
);
_recalloc的原型是:

void *_recalloc(
   void *memblock
   size_t num,
   size_t size
);

上面两行代码明明参数传反了啊。calloc的第一个参数是元素的个数,第二个参数是每个元素的大小。可是Add()函数里面,反过来了。_recalloc也是这个问题。

从函数调用来说肯定是错了。

但是会不会导致问题呢?如果是,岂不是很危险?

为了让自己放心,我跟进去calloc函数,发现calloc会调用这个函数:

[cpp] view plaincopyprint?
  1. extern "C"void * __cdecl _calloc_dbg_impl( 
  2.         size_t nNum, 
  3.         size_t nSize, 
  4.         int nBlockUse, 
  5.         const char * szFileName, 
  6.         int nLine, 
  7.         int * errno_tmp 
  8.         ) 
  9.         void * pvBlk; 
  10.  
  11.         /* ensure that (nSize * nNum) does not overflow */ 
  12.         if (nNum > 0) 
  13.         { 
  14.             _VALIDATE_RETURN_NOEXC((_HEAP_MAXREQ / nNum) >= nSize, ENOMEM, NULL); 
  15.         } 
  16.  
  17.         nSize *= nNum; 
  18.  
  19.         /*
  20.          * try to malloc the requested space
  21.          */ 
  22.  
  23.         pvBlk = _nh_malloc_dbg_impl(nSize, _newmode, nBlockUse, szFileName, nLine, errno_tmp); 
  24.  
  25.         /*
  26.          * If malloc() succeeded, initialize the allocated space to zeros.
  27.          * Note that unlike _calloc_base, exactly nNum bytes are set to zero.
  28.          */ 
  29.  
  30.         if ( pvBlk != NULL ) 
  31.         { 
  32.             memset(pvBlk, 0, nSize); 
  33.         } 
  34.  
  35.         RTCCALLBACK(_RTC_Allocate_hook, (pvBlk, nSize, 0)); 
  36.  
  37.         return(pvBlk); 

我们会看到这一行代码:nSize *= nNum;

然后nSize被传入了_nh_malloc_dbg_imple,那么就是说元素个数和每个元素大小会被乘起来,然后分配内存。

这样的话,calloc参数位置搞错好像也没有什么问题。

不管怎么样,ATLTRY(pp = (IUnknown**)calloc(sizeof(IUnknown*),_DEFAULT_VECTORLENGTH));肯定是参数传错了,但是它不会导致问题。
_recalloc的问题也差不多。

它跑到这里了:

[cpp] view plaincopyprint?
  1. extern "C" _CRTIMPvoid * __cdecl _recalloc_dbg 
  2.     void * memblock, 
  3.     size_t count, 
  4.     size_t size, 
  5.     int nBlockUse, 
  6.     const char * szFileName, 
  7.     int nLine 
  8.     size_t  size_orig = 0, old_size = 0; 
  9.     void * retp = NULL; 
  10.  
  11.     /* ensure that (size * count) does not overflow */ 
  12.     if (count > 0) 
  13.     { 
  14.         _VALIDATE_RETURN_NOEXC((_HEAP_MAXREQ / count) >= size, ENOMEM, NULL); 
  15.     } 
  16.     size_orig = size * count; 
  17.  
  18.     if (memblock != NULL) 
  19.     { 
  20.         old_size = _msize((void*)memblock); 
  21.     } 
  22.  
  23.     retp = _realloc_dbg(memblock, size_orig, nBlockUse, szFileName, nLine); 
  24.  
  25.     if (retp != NULL && old_size < size_orig) 
  26.     { 
  27.         memset ((char*)retp + old_size, 0, size_orig - old_size); 
  28.     } 
  29.     return retp; 

看这个size_orig = size * count;,也是乘起来去分配内存,那么位置搞错也没有什么关系了。

OK,结论就是:

1. calloc参数传错了,但是不会导致问题

2. _recalloc参数也传错了,但是不会导致问题。


有关_recalloc的另外一个问题,如果_recalloc需要分配更多的内存,而原来的那个地址又不能扩展了,那又如何?

_recalloc位置变换

首先看容器的Add()函数里面有这么一行:

pp = (IUnknown**)_recalloc(m_ppUnk, sizeof(IUnknown*),nAlloc);

m_ppUnk指向原来的一个位置,比如地址是A,它现在已经有1024个字节了,然后又需要分配4096个字节。这个时候有两种情况:

1. A地址后面还有空闲的空间可以分配出4096个字节,那么返回值pp也指向A

2. A地址后面不足4096字节,比如2048以后的空间已经分配给其他用途了,那么pp就可能指向另外一个地址,并不是A了。也就是说系统重新在另外一个地方开辟了空间用来存放1024 + 4096个字节。

如果#2发生了,那么会不会有个问题就是:原来的1024个字节内存上存放的信息就没有了?

看了一下MSDN对于_recallo的介绍 http://msdn.microsoft.com/en-us/library/ms235322.aspx

A combination of realloc and calloc. Reallocates an array in memory and initializes its elements to 0.

如果所有的内存都被清零了,那原来的信息岂不是丢了?

做个简单测试,写下如下代码:

[cpp] view plaincopyprint?
  1. void* p = _recalloc(NULL, 1, 1024); 
  2. memset(p, 'A', 1024); 
  3. void* p2 = _recalloc(p, 4, 1024); 

运行一下,发现p和p2的地址不一样。

这个说明_recalloc的时候确实是新开辟了一个空间(#2).

然后看看p2,0x00700aa8指向的内存:

最后一个A是0x700EA7 - 0x700AA8 + 1 = 0x400, 也就是十进制的1024个字节。这么说,好像是在开辟新空间的时候,原来的信息还是copy过来了,后面新增长的就全市0x00了。那是不是说_recalloc如果发现原来的地址不能分配出更多内存,而需要在另外一个地方分配的时候,会把原来的数据都copy到新的地方呢?

看了一下MSDN对于_recalloc的说明,好像也没有发现对于这一部分的描述。但是看到_recalloc是由realloc和calloc组成的。查看了一下realloc函数,发现有这么一句话:

The contents of the block are unchanged up to the shorter of the new and old sizes, although the new block can be in a different location.

其实前面半句,我也不是很明白,是不是说:块的内容不会发生变化除非新长度比旧的长度来的短,但是新的块可以是在另外一个地方。

基本应该没错。

那么这样的话,就算#2发生了,也不要紧,旧的数据还是会被copy过去的,然后新增长的内存就统统被清零了。


OK, CComDynamicUnkArray::Add的问题就讲完了。

另外,如果我们不喜欢用CComDynamicUnkArray,还可以用自定义的容器,

template <class T, const IID* piid, class CDV = CComDynamicUnkArray >
class ATL_NO_VTABLE IConnectionPointImpl :
public _ICPLocator<piid>

自定义一个容器,然后传给IConnectionPointImpl就是了,第三个模板。

CComDynamicUnkArray是用C写的,我们完全可以自己写个容器,可以用STL的vector。

比如写个类,叫做CMyArray,然后实现CComDynamicUnkArray的每一个函数,可以直接用std::vector来存放。

实际上CMyArray就是个adapter。将std::vector适配一下,这样就可以在IConnectionPointImpl里面使用。这也就是适配器模式的一个应用。

 

0 0
原创粉丝点击