使用::SysFreeString()释放BSTR字符串,需要考虑平台的差异性?

5
我正在编写一个拥有大量接口和方法的COM服务器。大多数方法的参数和本地参数都是BSTR类型,用作返回值。代码片段如下:

更新5:

真实代码。根据特定条件从数据库中获取一组数据以填充对象数组。

STDMETHODIMP CApplication::GetAllAddressByName(BSTR bstrParamName, VARIANT *vAdddresses)
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState())

//check the Database server connection

COleSafeArray saAddress;
HRESULT hr;

// Prepare the SQL Strings dan Query the DB

long lRecCount = table.GetRecordCount();

 if (lRecCount > 0)
 {
    //create one dimension safe array for putting  details
    saAddress.CreateOneDim(VT_DISPATCH,lRecCount);

    IAddress *pIAddress = NULL; 
    //retrieve details 
    for(long iRet = table.MoveFirst(),iCount=0; !iRet; iRet = table.MoveNext(),iCount++)
    {
        CComObject<CAddress> *pAddress;
        hr = CComObject<CAddress>::CreateInstance(&pAddress);
        if (SUCCEEDED(hr))
        {   
            BSTR bstrStreet = ::SysAllocString(table.m_pRecordData->Street);
            pAddress->put_StreetName(bstrStreet);

            BSTR bstrCity = ::SysAllocString(table.m_pRecordData->City);
            pAddress->put_CityName(bstrCity);
        }
        hr = pAddress->QueryInterface(IID_IAddress, (void**)&pIAddress);
        if(SUCCEEDED(hr)) 
        {
            saAddress.PutElement(&iCount,pIAddress); 
        }
    }
    *vAdddresses=saAddress.Detach(); 
}
table.Close(); 
return S_OK;
}


STDMETHODIMP CAddress::put_CityName(BSTR bstrCityName)
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState())
    // m_sCityName is of CComBSTR Type
    m_sCityName.Empty();//free the old string 
    m_sCityName = ::SysAllocString(bstrCityName);//create the memory for the new string
    return S_OK;
}

问题在于内存释放部分。这段代码在任何Win XP机器上都运行得非常好,但是在WIN2K8 R2和WIN7上却会崩溃,并指向::SysFreeString()作为罪魁祸首。MSDN无法提供合适的解决方案。
请问有人能帮我找到正确的解决方案吗?
非常感谢:)
更新1: 根据建议,我尝试使用CComBSTR代替原始BSTR,使用直接的CString进行初始化,并排除了SysFreeString()。但是,当它脱离范围时,系统会调用SysFreeString(),从而再次导致崩溃:(
更新2: 使用相同的CComBSTR,我尝试使用SysAllocString()进行分配,问题仍然存在:(
更新3: 我已经尝试过所有的选项,现在只有一个问题:
必须通过SysFreeString()释放使用SysAllocString()/string.AllocSysString()分配的BSTR吗?
更新4: 我忘记提供有关崩溃的信息。当我尝试调试COM服务器时,它会崩溃,并显示错误消息:
"Possible Heap Corruption"
请帮帮我...:(

在我看来,分配器应该是释放者。也就是说,调用FooMethod的客户端应该分配bstrName并且也应该释放它。此外,你的代码很混乱,你有bstrname和bstrName。也许obj.Name = bstrName;实际上应该是obj.Name = bstrname;? - Todd Murray
2
代码不应该类似于 obj.Name = ::SysAllocString(someString); 吗?此外,考虑使用 CComBSTR_bstr_t 代替原始的 BSTR,这将为您节省很多麻烦。 - Bhargav
1
是的,释放已分配的内存块是必要的。请提供使用“obj”变量的完整代码。否则,无法帮助您找到问题。 - Igor Chornous
@jmaniac,我已经更新了我的答案以涵盖你的更新。 - Igor Chornous
2个回答

4
// Now All Things are packed in to the Object
obj.Name = bstrName;
obj.Name2 = bstrname2;

我不太明白你所说的“打包”,因为你只是复制字符串的指针,当你调用SysFreeString obj.Name和obj.Name2时,它们将指向无效的内存块。虽然这段代码不安全,但看起来问题的源头是CFoo类。你应该向我们展示更多代码细节。
我建议你使用CComBSTR类,它将负责释放内存。
更新:
#include <atlbase.h>
using namespace ATL;
...
{
    CComBSTR bstrname(_T("Some Name")); 
    CComBSTR bstrname2(_T("Another Name"));
    // Here one may work with these variables if needed
    ...
    // Copy the local values to the Obj's member Variable 
    bstrname.Copy(&obj.Name); 
    bstrname2.Copy(&obj.Name2);
}

更新2 首先,在此代码块内应使用SysFreeString释放bstrCity和bstrStreetName,或者使用CComBSTR。

if (SUCCEEDED(hr))
{   
    BSTR bstrStreet = ::SysAllocString(table.m_pRecordData->Street);
    pAddress->put_StreetName(bstrStreet);

    BSTR bstrCity = ::SysAllocString(table.m_pRecordData->City);
    pAddress->put_CityName(bstrCity);

    // SysFreeString(bstrStreet)
    // SysFreeString(bstrCity)
} 

考虑使用iCount < lRecCount来增强循环条件 !iRet。
for(...; !iRet /* && (iCount < lRecCount) */; ...)

此外,在这里:
m_sCityName = ::SysAllocString(bstrCityName);

由于在内部,CComBSTR&操作符=(OLESTR..)会分配新的存储空间,因此您分配了内存但从未释放它。应该按以下方式进行重写:

m_sCityName = bstrCityName;

除此之外,我认为一切都很好

更新3 堆破坏通常是在已分配的内存块之外写入某些值的结果。比如你分配了长度为5的数组,并将某个值放到第6个位置。


感谢Kids-fox的回答。正如你所提到的,打包正在填充类的成员变量,因为它将被返回。对于我的难题,在XP机器上没有崩溃过,只在现代操作系统上出现了问题。 - Sathish Guru V
它不会仅仅因为巧合而崩溃。在COM中有一个经验法则:函数应该为其返回值和所有输出值分配内存。因此,FooMethod不应该释放内存。请看我在答案中添加的代码片段。 - Igor Chornous
你正在返回已释放内存的指针。这种行为是未定义的。不出所料,未定义的行为因系统而异。 - Raymond Chen
@RaymondChen,我敢打赌你想回答jmaniac。因为我表达的是完全相同的想法。 - Igor Chornous

2

最终我找到了代码中发生堆栈损坏的真正原因。

IAddress/CAddress中的put_StreetName/put_CityName是按照以下方式设计的。

STDMETHODIMP CAddress::put_CityName(BSTR bstrCityName)
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState())

    m_sCityName.Empty();
    TrimBSTR(bstrCityName);
    m_sCityName = ::SysAllocString(bstrCityName);

    return S_OK;
}

BSTR CAddress::TrimBSTR(BSTR bstrString)
{
    CString sTmpStr(bstrString);
    sTmpStr.TrimLeft();
    sTmpStr.TrimRight();
    SysReAllocString(&bstrString,sTmpStr);  // The Devilish Line
}

恶魔般的代码行是导致内存崩溃的真正罪魁祸首。

问题出在哪里?

在这行代码中,传递的 BSTR 字符串参数来自另一个应用程序,而真正的内存位于另一个领域。因此,系统正在尝试重新分配该字符串。无论成功与否,都会尝试从原始应用程序/领域的内存中清除该字符串,从而导致崩溃。

仍未解决的问题是什么?

为什么同样的代码在 Windows XP 和旧系统中没有崩溃过一次呢?:(

感谢所有花时间回答和解决我的问题的人们 :)


5
你的分析完全错了。BSTR可以被系统在公寓边界上进行封送而不会出现任何问题。你的问题在于SysReAllocString更新了一个临时(&bstrString)。当TrimBSTR返回时,bstrCityName指向的字符串可能已经不存在了。要修复你的错误,要么改变TrimBSTR的签名以接受BSTR*,要么从TrimBSTR返回更新后的bstrString值(这怎么可能编译通过?)。 - IInspectable

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接