ATL OLEDB 消费者对象上的内存泄漏 CCommand
报告了 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()
并将修复作为额外的功能添加进去。