【问题标题】:Why is this COM code leaking?为什么这个 COM 代码会泄漏?
【发布时间】:2013-04-27 15:41:24
【问题描述】:

我正在维护一个使用 Windows Explorer 覆盖图标的应用程序。有时某些操作需要我强制刷新特定文件夹的资源管理器视图。我使用以下使用 COM 的函数来这样做:

void RefreshExplorerView(CString strPath)
{
    CComPtr<IShellWindows> pShellWindows;

    CoInitialize(NULL);

    if(SUCCEEDED(pShellWindows.CoCreateInstance(CLSID_ShellWindows)))
    {
        IDispatch* pFolder=NULL;
        VARIANT variant;
        V_VT(&variant) = VT_I4;

        for(V_I4(&variant) = 0; pShellWindows->Item(variant, &pFolder) == S_OK; V_I4(&variant)++)
        {
            CComPtr<IWebBrowserApp> pWebBrowserApp;
            if(SUCCEEDED(pFolder->QueryInterface(IID_PPV_ARGS(&pWebBrowserApp))))
            {
                BSTR LocationURL = NULL;
                pWebBrowserApp->get_LocationURL(&LocationURL);

                if(LocationURL != NULL && strPath.CompareNoCase(LocationURL) == 0)
                {
                    CComPtr<IServiceProvider> pServiceProvider;
                    if(SUCCEEDED(pWebBrowserApp->QueryInterface(IID_PPV_ARGS(&pServiceProvider))))
                    {
                        CComPtr<IShellBrowser> pShellBrowser;
                        if(SUCCEEDED(pServiceProvider->QueryInterface(IID_PPV_ARGS(&pShellBrowser))))
                        {
                            IShellView* pShellView;
                            if(SUCCEEDED(pShellBrowser->QueryActiveShellView(&pShellView)))
                            {
                                pShellView->Refresh();
                                pShellView->Release();
                            }
                        }
                    }
                }

                SysFreeString(LocationURL);
            }
            pFolder->Release();
            pFolder = NULL;
        }
    }

    CoUninitialize();
}

我注意到,当我的程序定期刷新时,它的大小会缓慢增长,并且 UMDH 向我显示,每次运行时我似乎都在泄漏 pFolderpShellWindow 实例。我无法弄清楚为什么会发生这种情况,因为据我所知,这些都已正确发布。谁能看到我错过了什么?

【问题讨论】:

  • 您可以用 CComPtrs 替换 IDispatch 和 IShellView,用 _bstr_t 替换 BSTR,用 _variant_t (comutil.h) 替换 VARIANT,这样就可以确保没有泄漏。

标签: c++ windows com atl windows-shell


【解决方案1】:

您在CoUninitialize 之后释放pShellWindows,这是不正确的。

其余接口似乎发布得很好。请注意,您可以通过使用CComQIPtr 而不是QueryInterface,并且根本不使用原始指针(BSTRIFoo*)并用智能自动替换它们来提高整洁和可读性大大释放包装器。

pFolder 也可能泄漏,如果Item 调用成功但返回的代码不是S_OK。同样,使用CComPtr&lt;IFolder&gt; 而不是IFolder* 将立即解决此问题,甚至无需引起任何注意。

【讨论】:

  • 谢谢,不敢相信我没有发现 pShellWindows 的问题 ;-)
【解决方案2】:
CoInitialize(NULL);

这句话的问题不止一个。 @Roman 解释了如何通过过早取消初始化来泄漏。但这也有不止一个方面的问题,线程的单元状态在 COM 中是非常重要的:

  • 您没有检查 CoInitialize() 的返回值。如果调用此函数的客户端应用程序已经调用了 CoInitializeEx() 并选择了 MTA 而不是 STA,这将炸毁调用此函数的客户端应用程序。这将使 CoInitialize() 失败,您无法在提交后更改线程状态。您的 CoUninitialize() 调用会将客户端应用程序炸成碎片,使其所有后续 COM 调用都失败。

  • 选择 STA 还要求您实现单线程单元的协定。这表明你永远不会阻塞线程,你可以接受。 并且,你泵送了一个消息循环。消息循环对于封送对单线程单元的调用至关重要。你可以接受,你也不能合理地确保在这样的函数中解决这个问题。对于 shell 接口尤其重要,它们中的绝大多数都不是线程安全的。不抽水的后果就是死锁。您可能不抽水而侥幸逃脱,这不是保证会出现死锁。由于这些可能是进程外接口,因此您会在这里获得一些回旋余地。

特别是最后一个要求只能通过创建调用此函数的线程的代码来满足,只有它可以控制线程在调用您的函数之外所做的事情。如果您无法保证客户端应用程序正确初始化 COM,那么唯一真正安全的做法是自己创建一个线程。

【讨论】:

  • 在这种情况下,该函数始终由一个新创建的线程调用,该线程已创建用于处理应该减轻您的第一点的管道消息(尽管我仍将检查返回值)。不过,您的第二点确实让我有些担心,我可以通过简单地使用多线程模型来缓解这种情况吗?
  • 在 MTA 中没有免费的午餐,您将把它留给其他代码来处理以线程安全的方式调用线程不安全的代码。最好抓住那头公牛。我只是告诉你可能出错的地方,你看到代码死锁就知道是什么问题了。
猜你喜欢
  • 1970-01-01
  • 2011-10-21
  • 1970-01-01
  • 2012-09-25
  • 2016-04-12
  • 2011-01-17
  • 2011-02-20
  • 1970-01-01
相关资源
最近更新 更多