zoukankan      html  css  js  c++  java
  • BSTR使用误区以及隐藏的内存破坏和内存泄漏

    BSTR使用误区以及隐藏的内存破坏和内存泄漏

     

    作者:magictong

     

    简介

    BSTR的数据结构是什么样子并不是本文讨论的问题,但是却是本文的基础。在解决COM的跨平台编程的问题时,需要定义一种通用的字符串类型,它就这样被发明了,而且它的结构很容易匹配到不同的编程环境中,对于C++程序员来说,要记住的最基本的一点就是分配BSTR结构时,并不是简单的调用new、malloc就可以完成的,而且大部分的字符串相关的API和C库函数也是不能用于处理BSTR的,其实这也是使用BSTR的误区之一,在C++里面,BSTR被简单的define为wchar_t*,这也是容易引起误会的原因之一。

    今天注意讨论一下BSTR作为函数的参数、返回值时,调用者和被调用者操作BSTR时扮演的不同角色问题。首先需要注意的时,在必须使用BSTR时尽量使用BSTR的包装类CComBSTR,它会给你额外完成一些资源的管理工作,令你轻松不少,出错的可能性也就大大降低了。

    像一个很简单自然的用法:BSTR bstrInfo = L””,其实是错误的(当然如果你说我以后就把bstrInfo完全当成wchar_t*来使用,那我无话可说)。对于一个BSTR变量来说,它只可能是NULL或者正确分配的BSTR指针。

     

    BSTR当成参数和返回使用的基本约定

    1、[inout]参数

    譬如函数:void GetChangBSTR(BSTR* pbstrTitle),在GetChangBSTR函数内部需要先读取pbstrTitle的值使用,然后改变pbstrTitle的值。

    这种情况下被调用者(也就是GetChangBSTR)在给pbstrTitle赋新值前,需要先释放pbstrTitle里面原来的值,然后再给pbstrTitle分配上新的值。

    而调用者(也就是调用GetChangBSTR的函数),在传参之前需要先给pbstrTitle赋值上正确的值,调用结束之后还需要再释放pbstrTitle的值。

     

    2、[in]参数

    譬如函数:void PutText(BSTR bstrText),在PutText内部仅需读取bstrText的值。

    这种情况下被调用者可以随意读取bstrText的值,不需要做其它操作。

    而调用者,在传参之前需要先给bstrText赋上正确的值,调用结束后需要释放bstrText的值。

     

    3、[out]参数

    譬如函数:void GetText(BSTR* pbstrText),在GetText内部直接给pbstrText赋值。

    这种情况下被调用者直接给pbstrText赋值即可,不需要做其它操作。

    而调用者,在传参之前不能给pbstrText赋值,调用结束后需要释放pbstrText的值。

     

    4、返回参数

    譬如函数:BSTR GetText(),在GetText内部会返回一个BSTR出来。调用者直接返回一个有效的BSTR即可,而调用者需要释放这个返回的BSTR。

     

    BSTR当成参数和返回使用的误区

    1、[inout]参数

    这种情况下,被调用者如果没有给参数赋值,不要释放原始值,因为根据约定调用者还会释放一次,这样会造成多次释放,可能导致内存破坏。

    void GetChangBSTR(/*[inout] */BSTR* pbstrTitle)

    {

    // using the bs here

    DoSomething(*pbstrTitle);

     

    if (...)

    {

    ::SysReAllocString(*pbstrTitle, _T("Tecnet"));

    }

    else

    {

    ::SysFreeString(*pbstrTitle); // 这里的做法是错误的。

    }

    return;

    }

     

    2、[in]参数

    被调用者不要对参数进行释放操作,原因和上面相同,调用者还会重复释放一次,可能导致内存破坏。

    void PutText(/*[in] */BSTR bstrText)

    {

    // using the bs here

    DoSomething(bstrText);

    ::SysFreeString(bstrText); // 这里的做法是错误的。

     

    return;

    }

     

    3、[out]参数

    如果调用者在传参之前给参数赋值,参数传递给被调用者之后,在改变值之前是没有释放操作的,也就是说会有内存泄漏。

    void GetText(/*[out] */BSTR* pbstrText)

    {

    ::SysAllocString(*pbstrText, _T("Tecnet"));

    return;

    }

    // use GetText

    BSTR bstrText;

    ::SysAllocString(bstrText, _T("qq"));

    GetText(&bstrText);

    ::SysFreeString(bstrText); // 很不幸,这里实际上只释放了一次

     

    4、返回参数

    被调用者不要释放(不管是直接还是间接导致的)返回给调用者的BSTR,因为调用者会释放。

    BSTR GetText()

    {

    BSTR bstrText = ::SysAllocString(bstrText, _T("Tecnet"));

    ::SysFreeString(bstrText); // 这里释放就悲剧了

    return bstrText;

    }

    // use GetText

    BSTR bstrText = GetText(&bstrText);

    // use bstrText

    DoSome(bstrText); // bstrText已经被释放,使用是有问题的

    ::SysFreeString(bstrText); // 这就不仅仅是重复释放的问题了

     

    BSTR在类里面使用的误区

    1、我想把某个[in]参数BSTR保存到某个类成员变量

    这种情况下,直接赋值是不行的,因为外面调用者会释放这个BSTR参数,因此要保存的话,需要类函数自己重新申请一个新的BSTR。

    void CSomeClass::SetText(BSTR bs)

    {

    // m_bstrText是CSomeClass的成员变量

    m_bstrText = bs; // 错误做法

    m_bstrText = ::SysReAllocString(bs); // 正确做法

    }

     

    2、我想传出一个类的BSTR成员变量

    同样的道理,因为外面可能在某个时间释放传出的BSTR变量,因此要防止类成员变量被无辜释放,需要生成一个有效的拷贝,再传出。

    void CSomeClass::GetText(BSTR& bs)

    {

    // m_bstrText是CSomeClass的成员变量

    bs = m_bstrText; // 错误做法

    bs = ::SysAllocString(m_bstrText); // 正确做法

    }

     

    BSTR的封装类CComBSTR

    微软发现我们使用BSTR有上面的种种不爽,因此决定对其进行封装,很贴心吧!嗯,确实贴心,其中比较好的一个封装就是CComBSTR(很多项目组可能有自己的BSTR封装,但是其实都是大同小异的),这个封装类确实很好用(虽然没有提供CString那么多牛皮的功能),使用很方便,但是,如果我们错误使用也会产生噩梦,而且错误很难查找,我们来点评几个(注意:下面的内容需要对CComBSTR封装的基本原理和提供接口有一定了解,但这些并不是本文要讨论的内容,另外一个封装类是_bstr_t,它是用引用计数来管理的,实现比CComBSTR复杂很多,个人不太建议使用_bstr_t)。

    1、被调用者违反out参数使用约定

    void GetText(/*[out]*/BSTR& bstrText)

    {

    CComBSTR bstrT(_T("qqpcmgr"));

    // 错误:bstrT会被自动释放,违反了out参数的使用约定

    bstrText = (BSTR)bstrT;

    return;

     

    //////////////////////////////////////////////////////////////////////////

    // 正确的做法,一般来说Detach是效率更好的方法

    // 但是如果bstrT本身是一个类成员变量,可能要用Copy

    bstrText = bstrT.Copy();

        bstrText = bstrT.Detach();

    return;

    }

     

    2、调用者违反out参数使用约定

    void GetText(/*[out]*/BSTR& bstrText)

    {

    // ……

    }

    // use GetText

    CComBSTR bstrText(L"qq");

    // 内存泄漏,调用GetText前要先清空bstrText

    // bstrText.Empty();

    GetText(bstrText);

     

    3、看一个隐晦一点的

    void GetText(/*[out]*/BSTR& bstrText)

    {

    // ……

    }

    // use GetText

    CComBSTR bstrText;

    BSTR bstrInfo = NULL;

    GetText(bstrInfo);

    // 如果后面没有显示释放bstrInfo

    // 这里就会有内存泄漏,这种混用也是比较危险的

    bstrText = bstrInfo;

    // 如果你想CComBSTR接管一个BSTR,可以使用

    // bstrText.Attach(bstrInfo);

     

    4、重复释放,造成内存破坏

    {

    CComBSTR bstrText(L"Tencent");

    // 因为CComBSTR重载了operator BSTR操作,因此这里是支持的

    ::SysFreeString(bstrText); // 错误做法,如果你确实想释放,可以调用Empty

    }

    // 超出bstrText范围,bstrText会被自动释放,可能导致内存破坏

    // ……

     

    参考文献

    [1] BSTR https://zh.wikipedia.org/zh-cn/BSTR

    [2] BSTR_INSIDE http://wenku.baidu.com/view/d577a1c5d5bbfd0a795673b2.html

    http://blog.csdn.net/magictong/article/details/8995516

  • 相关阅读:
    [20210908]Reverse Shell with Bash.txt
    [20210831]bbed读取数据块6.txt
    自主学习 之 用Python玩转数据
    简单四则运算(PSP)
    永久免费云服务器搭建国内Moon服务加速ZeroTier
    INDEX
    openjdk 8 的 hotspot 源码目录结构
    CentOS 7 编译 openjdk 8
    23
    22
  • 原文地址:https://www.cnblogs.com/findumars/p/6146702.html
Copyright © 2011-2022 走看看