Почему происходит утечка этого COM-кода?

Я поддерживаю приложение, которое использует значки наложения проводника Windows. Иногда некоторые операции требуют, чтобы я принудительно обновлял представление обозревателя для определенной папки. Я делаю это, используя следующую функцию, которая использует 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 показал мне, что каждый раз, когда это выполняется, у меня происходит утечка pFolder и pShellWindow экземпляров. Я не могу понять, почему это происходит, поскольку, насколько я могу судить, они выпущены должным образом. Может ли кто-нибудь увидеть, что мне не хватает?


person Benj    schedule 03.05.2013    source источник
comment
Вы можете заменить IDispatch и IShellView на CComPtrs, BSTR на _bstr_t и VARIANT на _variant_t (comutil.h), таким образом, вы будете уверены, что ничего не утечет.   -  person Simon Mourier    schedule 03.05.2013


Ответы (2)


Вы выпускаете pShellWindows после CoUninitialize, что неверно.

Остальные интерфейсы, кажется, выпущены нормально. Обратите внимание, что вы можете значительно улучшить чистоту и читабельность, используя CComQIPtr вместо QueryInterface и вообще не используя необработанные указатели (BSTR, IFoo*), а заменив их интеллектуальными автоматически освобождающимися оболочками.

pFolder тоже может иметь утечку, если вызов Item успешен, но возвращает код, отличный от S_OK. Опять же, использование CComPtr<IFolder> вместо IFolder* немедленно решило бы эту проблему, даже не привлекая к ней внимания.

person Roman R.    schedule 03.05.2013
comment
Спасибо, не могу поверить, что не заметил проблему с pShellWindows ;-) - person Benj; 03.05.2013

CoInitialize(NULL);

С этим утверждением связано несколько проблем. @Roman объяснил, как вы можете утечь из-за слишком ранней деинициализации. Но это также может испортиться более чем одним способом, апартаментное состояние потока — это действительно большая проблема в COM:

  • Вы не проверяете возвращаемое значение CoInitialize(). Это взорвет клиентское приложение, которое вызывает эту функцию, если оно уже вызвало CoInitializeEx() и выбрало MTA вместо STA. Это приведет к сбою CoInitialize(), вы не сможете изменить состояние потока после его фиксации. Ваш вызов CoUninitialize() разнесет клиентское приложение вдребезги, что приведет к сбою всех его последующих вызовов COM.

  • Выбор STA также требует, чтобы вы реализовали контракт для однопоточного апартамента. В котором говорится, что вы никогда не блокируете поток, вы согласны с этим. И вы прокачиваете цикл сообщений. Цикл сообщений имеет решающее значение для маршалинга вызовов в однопоточное подразделение. Вы не согласны с этим, и вы не можете разумно гарантировать, что об этом позаботятся в такой функции. Особенно важно для интерфейсов оболочки, подавляющее большинство из них не являются потокобезопасными. Последствием не прокачки является тупик. Вы можете отказаться от прокачки, это не гарантированный тупик. Здесь вы получите некоторую свободу действий, так как это, вероятно, внепроцессные интерфейсы.

В частности, последнее требование может быть выполнено только кодом, создавшим поток, вызывающий эту функцию, только он контролирует то, что делает поток помимо вызова вашей функции. Если вы не можете гарантировать, что клиентское приложение правильно инициализирует COM, то единственное действительно безопасное решение — создать поток самостоятельно.

person Hans Passant    schedule 03.05.2013
comment
В этом случае функция всегда вызывается вновь созданным потоком, который был создан для обработки сообщения канала, которое должно смягчить вашу первую точку (хотя я все равно проверю возвращаемое значение). Однако ваш второй пункт вызывает у меня некоторое беспокойство, могу ли я смягчить это, просто используя многопоточную модель? - person Benj; 03.05.2013
comment
В MTA нет бесплатного обеда, вы предоставите другому коду возможность позаботиться о вызове небезопасного для потоков кода потокобезопасным способом. Лучше взять быка за рога. Я просто говорю вам, что может пойти не так, вы поймете, что не так, когда увидите кодовый тупик. - person Hans Passant; 03.05.2013