• VARIANT/Variant/TVariant


    昨天发现系统中数据压缩解压部分有内存泄漏,上午研究了一番,发现还是实现的人对这几个东东不熟悉,现将研究结果罗列一番。

    源代码如下:

     1long __fastcall UnVarCompress(VARIANT varRet,VARIANT *vaDest)
     2{
     3    try
     4    {
     5        Variant vaSource;
     6        Variant vaDestTmp;
     7        vaDestTmp = vaDest;
     8        vaSource = varRet;
     9
    10        ICompressionDisp xComp;
    11        HRESULT hr = xComp.BindDefault();
    12
    13        if (FAILED(hr))
    14        {
    15            throw Exception("");
    16        }

    17
    18        CompressionError xResult = xComp.Uncompress(vaSource, vaDestTmp, true);
    19
    20        if( xResult != xSuccess )
    21
    22        {
    23            AnsiString strErr = xComp->GetErrorDescription(xResult);
    24            xComp.Unbind();
    25            throw Exception(strErr);
    26        }

    27
    28        xComp.Unbind();
    29
    30        vaDest = vaDestTmp;
    31    }

    32    catch(...)
    33    {
    34       return -1;
    35    }

    36    return 1;
    37}

    38
    39long __fastcall VARToDataSet(VARIANT varRet, AnsiString strFileName)
    40{
    41    long l, h;
    42    void *ppvdata;
    43    TFileStream *fm = NULL;
    44    VARIANT varTempRet;
    45
    46    try
    47    {
    48        UnVarCompress(varRet,&varTempRet);
    49        varRet = varTempRet;
    50        ::SafeArrayGetLBound(varRet.parray, 1&l);
    51        ::SafeArrayGetUBound(varRet.parray, 1&h);
    52        ::SafeArrayAccessData(varRet.parray, &ppvdata);
    53
    54//采用guid做文件名,在读取后应立即删除
    55        fm = new TFileStream(strFileName, fmCreate);
    56        fm->Write(ppvdata, h - l + 1);
    57        ::SafeArrayUnaccessData(varRet.parray);
    58    }

    59    __finally
    60    {
    61        delete fm;
    62        fm = NULL;
    63    }

    64
    65    return 0;
    66}

    67
    68
    69接口声明为:
    70template <class T> ziplib_tlb::CompressionError __fastcall CompressionDispT<T>::Uncompress(TVariant* vaSource, TVariant* vaUncompressed,  TOLEBOOL bEndOfData);

    VARToDateSet是最外层,直接被其他程序员调用
    而VARToDateSet内部调用UnVarCompress,UnVarCompress又调用ICompression接口进行实际解压工作

    先看UnVarCompress方法
    第一感觉,有2个问题
    首先觉得vaDestTmp = vaDest这句多余
    另外,vaDest = vaDestTmp这句将解压后的数据返回出去也有问题
    因为vaDestTmp是Variant类型,在UnVarCompress执行后将被释放,那么vaDest指向的将是无效的指针
    但是实际应用时这个方法一直都工作正常阿!百思不得其解,于是跟踪了一番,终于发现问题所在
    在vaDestTmp = vaDest后,vaDestTmp实际类型变成了VT_VARIANT | VT_REF,vaDestTmp.VArray指向的是vaDest,也就是说这时候vaDestTmp指向的实际数据是另一个VARIANT vaDest
    xComp.Uncompress之后vaDest.parray被正确修改,得到了实际的解压数据
    反而vaDest = vaDestTmp这句是多余的......

    再来看VARToDataSet方法
    实际上内存泄漏是出现在这个地方的,而且有2处
    第一处是varRet,这个方法中没有调用::VaraintClear清除其中的内容
    有人会说了,varRet是个VARIANT结构,结构当参数传入时实际只是传了个地址而已,并不是复制了一份传进来的,因此不应该在这里释放
    的确时这样,不过在我们的已有的应用中,程序员都没有做释放的工作,所以只好在这里做了
    第二处是解压后的varTempRet,也没有释放

    知道了问题就好办了,修改的工作其实很简单
    修改后的代码如下
     1long __fastcall UnVarCompress(VARIANT varRet1,VARIANT *vaDest)
     2{
     3    try
     4    {
     5        Variant vaSource;
     6        Variant vaDestTmp;
     7        vaDestTmp = vaDest;
     8        vaSource = varRet;
     9
    10        IXceedCompressionDisp xComp;
    11        HRESULT hr = xComp.BindDefault();
    12
    13        if (FAILED(hr))
    14        {
    15            throw Exception("");
    16        }

    17
    18        CompressionError xResult = xComp.Uncompress(vaSource, vaDestTmp, true); 
    19
    20        if( xResult != xSuccess )
    21
    22        {
    23            AnsiString strErr = xComp->GetErrorDescription(xResult);
    24            xComp.Unbind();
    25            throw Exception(strErr);
    26        }

    27
    28        xComp.Unbind();
    29    }

    30    catch(...)
    31    {
    32       return -1;
    33    }

    34    return 1;
    35}

    36
    37long __fastcall VARToDataSet(VARIANT varRet, AnsiString strFileName)
    38{
    39    long l, h;
    40    void *ppvdata;
    41    TFileStream *fm = NULL;
    42    VARIANT varTempRet;
    43
    44    try
    45    {
    46        UnVarCompress(varRet,&varTempRet);
    47
    48        ::SafeArrayGetLBound(varTempRet.parray, 1&l);
    49        ::SafeArrayGetUBound(varTempRet.parray, 1&h);
    50        ::SafeArrayAccessData(varTempRet.parray, &ppvdata);
    51//采用guid做文件名,在读取后应立即删除
    52        fm = new TFileStream(strFileName, fmCreate);
    53        fm->Write(ppvdata, h - l + 1);
    54        ::SafeArrayUnaccessData(varTempRet.parray);
    55    }

    56    __finally
    57    {
    58        ::VariantClear(&varRet);
    59
            ::VariantClear(&varTempRet);
    60
            delete fm;
    61        fm = NULL;
    62    }

    63
    64    return 0;
    65}


    再来说说类型转换的问题
    C++Builder支持 VARIANT/Variant/TVariant
    VARIANT是Windows定义的类型
    Variant是BCB自己实现的类型,目的是为了兼容Delphi的Variant
    TVariant则是对VARIANT的封装

    用代码说话
    Variant v;
    VARIANT wv, *pwv;
    TVariant tv, *ptv;
    wv = v; pwv = v;
    tv = v; ptv = v;
    看看地址
    &v: :0012F364
    v.VArray: :00156DD8
    &tv: :0012F354
    tv.parray: :00156B10
    ptv: :0012F364
    ptv->parray: :00156DD8
    &wv: :0012F2F0
    wv.parray: :00156E18
    pwv: :0012F364
    pwv->parray: :00156DD8
    看出什么问题?对,&v,ptv,pwv指向的都是v!而tv,wv则是对v的拷贝

    比较有意思的是
    VARIANT wv...
    Varaint v1 = &wv;
    Variant v2; v2 = &wv;
    此时 v1.VArray指向的是 &wv,也就是说类型为 VT_VARIANT | VT_REF,而v2中VArray指向的是一份wv的内容的拷贝,类型与wv类型一致
    产生这样的结果原因跟编译器有关
    Varaint v1 = &wv实际跟Variant v1(&wv)是一样的,也就是说调用的是Variant的构造
    Variant v2; v2 = &wv;实际是先构造v2,然后将&wv赋值给v2,调用的是v2重载的操作符=
  • 相关阅读:
    浅谈分层图最短路问题
    [Luogu P2574]XOR的艺术
    luogu P2419 [USACO08JAN]牛大赛Cow Contest
    luogu P1119 灾后重建
    [国家集训队]跳跳棋
    洛谷P4147 玉蟾宫
    [ZJOI2007]棋盘制作
    树状数组模版
    Nearest Common Ancestor
    P1260 工程规划
  • 原文地址:https://www.cnblogs.com/sephil/p/VARIANT.html
Copyright © 2020-2023  润新知