65.9K
CodeProject 正在变化。 阅读更多。
Home

ATL OLEDB 消费者对象上的内存泄漏 CCommand

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (2投票s)

2009 年 4 月 23 日

CPOL

2分钟阅读

viewsIcon

19730

报告了 ATL OLEDB CCommand 对象中的内存泄漏问题,并描述了原因并提供了解决方案。

引言

这篇文章揭示了 CCommand ATL OLEDB 对象中的一个非常严重的错误。文章解释了发生的原因并提供了解决方案。

背景

Microsoft 知识库 http://support.microsoft.com/kb/271926 描述了一个众所周知的内存泄漏问题以及修复方法。但是,还有一个主要的内存泄漏问题未在任何地方报告。很可能,没有人以这种方式使用 ATL OLEDB。然而,这在我的项目中是一个典型的使用场景,因为性能至关重要。

谁会像这个例子中那样,重复运行相同的命令一百万次?我会。想法是使用参数准备命令,然后使用不同的参数运行它。我们将其用于在一天内实时将交易订单写入 SQL Server。这样,您将获得最佳性能,因为您无需一遍又一遍地生成 SQL 字符串。

CDataSource ds;
//Create and open the data source
CDBPropSet dbinit(DBPROPSET_DBINIT);
dbinit.AddProperty(DBPROP_INIT_DATASOURCE, "192.168.60.18");
dbinit.AddProperty(DBPROP_AUTH_USERID, "user");
dbinit.AddProperty(DBPROP_AUTH_PASSWORD, "");
dbinit.AddProperty(DBPROP_INIT_CATALOG, "master");
dbinit.AddProperty(DBPROP_AUTH_PERSIST_SENSITIVE_AUTHINFO, false);
dbinit.AddProperty(DBPROP_INIT_LCID, 1033L);
dbinit.AddProperty(DBPROP_INIT_PROMPT, static_cast<short>(4));

HRESULT hr = ds.Open(_T("SQLOLEDB.1"), &dbinit);
//Create Session.
CSession session;
session.Open(ds);
CCommand<CDynamicAccessor, CRowset, CMultipleResults> command;
hr = command.Create(session, "exec sp_tables");
for(int i=0; i<1000000; i++ )
{
    hr = command.Open(NULL,NULL,false,0);
    command.Close();
}

如果您将代码复制到您的项目中并运行它(请使用您数据库环境的正确用户名和密码),您会在任务管理器中发现内存使用量每秒增加 100KB。

该错误

CCommand::Open() 调用 CCommand::ExecuteAndBind 然后 CCommand::Execute()。这是 CCommand::Execute() 的实现

HRESULT Execute(IUnknown** ppInterface, DBPARAMS* pParams, DBPROPSET *pPropSet, 
DBROWCOUNT* pRowsAffected, ULONG ulPropSets = 0) throw()
{
    HRESULT hr;
    // Specify the properties if we have some
    if (pPropSet)
    {
        // For backward compatibility, if the default parameter is not
        // specified, then set it to one if a property set exists
        if (ulPropSets == 0)
            ulPropSets = 1;

        CComPtr<ICommandProperties> spCommandProperties;
        hr = m_spCommand->QueryInterface(&spCommandProperties);
        if (FAILED(hr))
            return hr;

        hr = spCommandProperties->SetProperties(ulPropSets, pPropSet);
        if (FAILED(hr))
            return hr;
    }

    // If the user want the rows affected then return it back, otherwise
    // just point to our local variable here.

    DBROWCOUNT nAffected, *pAffected;
    if (pRowsAffected)
        pAffected = pRowsAffected;
    else
        pAffected = &nAffected;

    if (UseMultipleResults())
    {
        hr = m_spCommand->Execute(NULL, __uuidof(IMultipleResults), pParams,
            pAffected, (IUnknown**)GetMultiplePtrAddress());

        if (SUCCEEDED(hr))
            hr = GetNextResult(pAffected, false);
        else
            // If we can't get IMultipleResults then just try to get IRowset
            hr = m_spCommand->Execute(NULL, GetIID(), pParams, pAffected,
                ppInterface);
    }
    else
    {
        hr = m_spCommand->Execute(NULL, GetIID(), pParams, pAffected,
            ppInterface);
    }

    if (SUCCEEDED(hr))
        SetupOptionalRowsetInterfaces();
    return hr;
}

加粗的行是问题的根源。

hr = m_spCommand->Execute(NULL, __uuidof(IMultipleResults), pParams,
         pAffected, (IUnknown**)GetMultiplePtrAddress());

这段代码没有检查 GetMultiplePrtAddress,在本例中为 ComPtr<IMultipleResults> m_spMultipleResults,是否之前已设置。如果之前已设置,则应首先释放此接口指针,然后再分配新的接口。虽然该接口受到智能指针 ComPtr 的保护,但除了最后一个接口指针外,所有先前的接口指针都将丢失。因此,最后,最后一个接口被正确释放。

解决方案

每次命令关闭后,显式释放此接口。

for(int i=0; i<1000000; i++ )
{
    hr = command.Open(NULL,NULL,false,0);
    command.Close();
    if( command.GetMultiplePtr() != NULL )
    {
    command.GetMultiplePtr()->Release();
    *command.GetMultiplePtrAddress() = NULL;
    }
}

为了使其更完美,您可以重写 CCommand::Close() 并将修复作为额外的功能添加进去。

© . All rights reserved.