由于COM规范中规定,无论输入型还是输出型参数,其申请的内存统一在调用方释放。在COM中,提供了一套内存管理函数,凡是涉及到COM接口申请内存的都必须通过这几个函数进行统一管理。
这三个函数如下:
LPVOID CoTaskMemAlloc( ULONG cb );
VOID CoTaskMemFree( LPVOID pv );
LPVOID CoTaskMemRealloc( LPVOID pv, ULONG cb );
由于COM组件中使用VARIANT结构体传数组很方便,但是VARIANT结构体内部包含了一些指针,所以涉及到内存的申请与释放的问题。
在官方文档的说明中提供了VariantClear()函数来清空结构体内存。但是,文档并没有说明如果VARIANT结构体中再嵌套VARIANT结构体数组的时候的释放问题。
官方文档如下:
Use this function to clear variables of type VARIANTARG (or VARIANT) before the memory containing the VARIANTARG is freed (as when a local variable goes out of scope).
使用此函数在释放包含VARIANTARG的内存之前清除VARIANTARG(或VARIANT)类型的变量(如当局部变量超出范围时)。
The function clears a VARIANTARG by setting the vt field to VT_EMPTY. The current contents of the VARIANTARG are released first. If the vtfield is VT_BSTR, the string is freed. If the vt field is VT_DISPATCH, the object is released. If the vt field has the VT_ARRAY bit set, the array is freed.
函数通过将vt字段设置为VT_EMPTY来清除VARIANTARG。首先释放VARIANTARG的当前内容。如果VT字段是VT_BSTR,则释放字符串。如果VT字段是VT_DISPATCH,则对象被释放。如果VT字段设置了VT_ARRAY,则释放该数组。
If the variant to be cleared is a COM object that is passed by reference, the vtfield of the pvargparameter is VT_DISPATCH | VT_BYREF or VT_UNKNOWN | VT_BYREF. In this case, VariantClear does not release the object. Because the variant being cleared is a pointer to a reference to an object, VariantClear has no way to determine if it is necessary to release the object. It is therefore the responsibility of the caller to release the object or not, as appropriate.
如果要清除的变量是通过引用传递的COM对象,则pvargParameter的VT字段是VT_DISPATCH|VT_BYREF或 VT_UNKNOWN | VT_BYREF。在这种情况下,variant清除不会释放对象。因为要清除的变量是指向对象引用的指针,所以variant清除无法确定是否需要释放对象。因此,调用方有责任酌情释放对象或不释放对象。
In certain cases, it may be preferable to clear a variant in code without calling VariantClear. For example, you can change the type of a VT_I4 variant to another type without calling this function. Safearrays of BSTR will have SysFreeString called on each element not VariantClear. However, you must call VariantClear if a VT_type is received but cannot be handled. Safearrays of variant will also have VariantClear called on each member. Using VariantClear in these cases ensures that code will continue to work if Automation adds new variant types in the future.
在某些情况下,最好在代码中清除变量而不调用variant clear。例如,您可以将VT_I4 变量的类型更改为另一个类型,而无需调用此函数。BSTR的SafeArrays将对每个元素调用SysFreeString,而不是VariantClear。但是,如果接收到VT_类型但无法处理,则必须调用variantclear。variant的safearray还将对每个成员调用VariantClear清除。在这些情况下使用variantclear可以确保如果自动化在将来添加新的变量类型,代码将继续工作。
Do not use VariantClear on unitialized variants; use VariantInit to initialize a new VARIANTARG or VARIANT.
不要对为初始化的variants使用VariantClear函数;。。。
Variants containing arrays with outstanding references cannot be cleared. Attempts to do so will return an HRESULT containing DISP_E_ARRAYISLOCKED.
无法清除包含具有未完成引用的数组的变量。尝试这样做将返回一个包含disp-e-arrayislocked的hresult。
因而,做了一个简单的案例来测试VARIANT的结构体内存释放问题;
主要代码如下:
VARIANT firstfloor={};
firstfloor.vt = VT_ARRAY|VT_VARIANT;
firstfloor.intVal = 100;
SAFEARRAY* l_ff;
SAFEARRAYBOUND aDim1[1];
aDim1[0].lLbound = 0;
aDim1[0].cElements = 100;
l_ff = SafeArrayCreate( VT_VARIANT, 1,aDim1 );
for( long pos = 0; pos < 100; pos++ ){ //第二层放数组
long num = pos + 30000;
VARIANT secondfloor = {};
secondfloor.vt = VT_ARRAY|VT_R4;
SAFEARRAY * l_fs1;
SAFEARRAYBOUND aDim2[1];
aDim2[0].lLbound = 0;
aDim2[0].cElements = num;
l_fs1 = SafeArrayCreate( VT_R4, 1, aDim2 );
for (long pos1 = 0; pos1 < num ; pos1++ ){
float c = (float)pos1;
SafeArrayPutElement( l_fs1, &pos1,&c );
}
secondfloor.parray = l_fs1;
SafeArrayPutElement( l_ff, &pos,&secondfloor );
}
firstfloor.parray = l_ff;
HRESULT hresult = VariantClear(&firstfloor);
执行后发现,hresult 返回的是S_OK,但是内存只是释放大约一半,因此怀疑VARIANT里面的二层数组的内存并有没有得到释放。
接着,将数组中的VARIANT的指针保存下来,进行释放,查看此时内存的占用情况,代码如下:
VARIANT firstfloor={};
firstfloor.vt = VT_ARRAY|VT_VARIANT;
firstfloor.intVal = 100;
SAFEARRAY* l_ff;
SAFEARRAYBOUND aDim1[1];
aDim1[0].lLbound = 0;
aDim1[0].cElements = 100;
std::vector< VARIANT *> vec;
vec.clear();
l_ff = SafeArrayCreate( VT_VARIANT, 1,aDim1 );
for( long pos = 0; pos < 100; pos++ ){ //第二层放数组
long num = pos + 30000;
VARIANT* secondfloor01 = (VARIANT* )CoTaskMemAlloc(sizeof(VARIANT));
vec.push_back(secondfloor01);
secondfloor01->vt = VT_ARRAY|VT_R4;
SAFEARRAY * l_fs1;
SAFEARRAYBOUND aDim2[1];
aDim2[0].lLbound = 0;
aDim2[0].cElements = num;
l_fs1 = SafeArrayCreate( VT_R4, 1, aDim2 );
see = l_fs1;
for (long pos1 = 0; pos1 < num ; pos1++ ){
float c = (float)pos1;
SafeArrayPutElement( l_fs1, &pos1,&c );
}
secondfloor01->parray = l_fs1;
SafeArrayPutElement( l_ff, &pos,secondfloor01 );
}
firstfloor.parray = l_ff;
VariantClear(&firstfloor);
while( !vec.empty()){
VARIANT * p;
p = vec.back();
VariantClear( p);
CoTaskMemFree(p);
vec.pop_back();
}
执行完后发现,在第一个VariantClear()内存释放一半了,接下来循环确实能释放另一半的内存。但是对比上一个案例,VARIANT是局部变量并不是在堆上开辟的内存,它是一个局部变量,所以VARIANT中的指针成员的赋值并不是简单的改变其指向,而是在内存上拷贝了一份,而局部变量在安全数组创建后就会存在内存泄漏如果不释放的话,为了验证上面的猜测。接下来,首先在SAFEARRAYPUTELEMENT()这个函数后,将局部变量清除或将将开辟的内存释放,得到的结果是程序内存减少,但是VARIANT结构体中的值是正常的,并在最后的VARIANTCLEAR()后,整个开辟的内存得到了释放。
结论:一是VariantClear()函数确实能一次释放掉里面的资源,无论其数组是否还有嵌套。二是Variant结构体中的指针是深拷贝而不是简单的浅拷贝,所以要求我们往结构体赋值后及时释放我们所申请的资源,否则将会存在内存泄漏。