BSTR向WideString赋值导致内存泄漏

一、问题提出
Mapx图层的重绘是通过响应MapX的用户自定义图层的绘制消息进行的,在消息响应函数中循环遍历地图上加载的图层,调用符合条件的图层的Draw函数进行绘制。使用此机制过程中,发现图层刷新时,内存增长很快,平均每秒钟增长8K左右。最终将问题定位到如下语句:

WideString ws = m_map->Layres->Item(i+1)->Name;

由于问题语句中“=”号右边返回的是一个BSTR类型值,所以先对BSTR类型做一个简单的介绍。
BSTR类型是为了跨语言平台使用而存在的一种宽字符集字符串指针,标准BSTR是一个有长度前缀和null结束符的OLECHAR数组,其定义如下:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
0a 00 00 00 H E L L O \0
BSTR包含四个字节的长度前缀,BSTR的前4字节是一个表示字符串长度的前缀。BSTR长度域的值是字符串的字节数,并且不包括0结束符。由于BSTR类型包含了长度前缀,所以需要通过SysAllocString函数进行内存分配,通过SysFreeString函数进行内存释放。其中SysAllocString函数返回的指针指向BSTR第一个字符,而不是BSTR的内存首地址。
由于BSTR被用于COM接口,申请和使用者通常不是同一个,所以在使用过程中要求使用者将其释放。
m_map为MapX的一个COM对象,m_map->Layers->Item(i + 1)->Name返回的是一个BSTR类型值。该值在返回前由m_map对象通过调用系统函数SysAllocString生成,并要求接收该类型值的对象将其释放。这就要求使用者不仅仅完成值的使用,而且需要完成内存释放的任务。
在程序中,调用了WideString的拷贝构造函数,那么WideString对象只是使用了内存的内容,而并没有对这块内存进行释放。程序中也没有显示的内存释放操作,从而导致了内存的泄露。
由于BSTR所存在的申请者和释放者不同的特点,那么WideString向BSTR就必然存在内存二次释放的潜在问题。当调用一个需要BSTR类型入参的COM对象方法时,代码中原来的实现是:

 m_map->Expoert(WideString(L"clipboard"), miFormatBMP);

这条语句在执行时,先由WideString类申请了内存,返回一个WideString对象,然后调用COM对象的ExportMap函数,ExportMap函数使用完内存后对将内存进行释放,而在函数调用完毕后,WideString对象又会对内存进行一次释放。这样就对一块内存有两次释放操作,可能一般情况下并不会发生异常,但还是存在潜在问题,给程序埋下了隐患。
二、解决与预防措施
可以采用两种方案解决以上问题,一种是手动申请或释放内存,另一种是让WideString来完成内存的申请或释放工作。
方案一:手动进行内存申请或释放
在使用BSTR类型前,先定义一个BSTR指针bstr作为中间变量,进行数据传递。而在使用完该指针后,调用::SysFreeString函数将bstr指针指向的内存空间释放掉。代码如下:

BSTR bstr = m_map->Layers->Item(i+1)-Name;
WideString ws(bstr);
::SysFreeString(bstr);

在调用需要输入BSTR类型参数的COM对象方法时,也可以采用相同的办法。即定义一个BSTR类型中间变量bstr,给它申请一块内存,然后传递给COM对象,最后让COM对象将内存释放。代码如下:

BSTR bstr = ::SysAllocString(L"clipboard");
m_map->ExportMap(bstr, miFormatBMP);

由于这种方案只能看到内存的申请或者释放函数,所以代码走查中可能会错误的认为代码有错。
该方案的优点在于,内存的释放由代码自身决定,不受ws生命周期的限制。缺点是,如果用户忘记了谢申请和释放函数还是一样会产生内存泄漏问题。
方案二:让WideString完成内存的申请或释放
WideString的拷贝构造函数不能达到内存指针直接引用的目的,但它提供了一个Attach函数。该函数就是将WideString的内部指针直接指向了需要引用的内存地址,然后析构的时候将这块内存释放掉了。该函数定义如下:

void _fastcall WideString::Attach(BSTR src)
{
    _ASSERT(Data == 0);
    Data = src;
}

采用Attach函数将代码修改如下:

WideString ws;
ws.Attach(m_map->Layers->Item(i+1)->Name);

同时,在WideString向BSTR类型赋值时,WideString也提供了一个有用的Detach函数,该函数在内部定义一个BSTR类型指针,指向内部成员Data,然后将Data赋值为空,最后返回该BSTR指针。函数定义如下:

BSTR _fastcall WideString::Detach()
{
    BSTR bstr = Data;
    Data = 0;
    return bstr;
}

在调用需要输入BSTR类型参数的COM对象方法时,可以这样使用:

WideString ws("clipboard");
m_map->ExportMap(ws.Detach(), miFormatBMP);

该方案优点在于,直接将内存释放工作交给WideString,不会发生潜在的内存泄漏问题。而缺点是不是特别灵活。
注意,由于Attach直接将内部成员Data指向了新的内存单元,如果原来ws的内容不为空,就会发生内存泄漏问题。所以Attach语句执行前一定要保证ws的内容为空。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值