Explorer не освобождает IDataObject при выполнении перетаскивания

#c #windows #winapi #com #drag-and-drop

#c #Windows #winapi #com #перетаскивание

Вопрос:

Я реализую перетаскивание в своем приложении. У меня проблема с Windows Explorer, который не освобождает мой IDataObject после операции перетаскивания. Чтобы изолировать проблему, я реализовал очень простой исходный код перетаскивания, который должен компилироваться практически в любом компиляторе Win32. Объект данных не содержит данных; как вы можете видеть, все очень просто. Объект данных содержит трассировку, которую можно просмотреть с помощью DebugView, чтобы указать, когда он создан и когда он уничтожен.

Для воспроизведения:

  1. Начните перетаскивание, удерживая нажатой кнопку мыши.
  2. Перетащите объект в открытое окно проводника Windows.
  3. Обратите внимание на вывод в DebugView; пример вывода:

     [4964] gdo ctor
    [4964] gds ctor
    [4964] gds dtor
      

    Этот вывод указывает, что источник данных был уничтожен, но кто-то все еще хранит ссылку на мой IDataObject!

  4. Начните перетаскивать файл в том же окне проводника. Несмотря на то, что я в данный момент вообще не взаимодействую со своим проектом, он вызывает gdo dtor печать, указывая, что последняя ссылка на IDataObject была выпущена.

Я использую 64-разрядную версию Windows 7. Интересно отметить, что некоторые окна проводника освобождают объект данных сразу после удаления; другие, похоже, этого не делают, пока вы не начнете перетаскивать другой объект в окно проводника, как указано в шаге # 4. Похоже, это также зависит от того, где в окне я отбрасываю объект — в некоторых местах объект немедленно освобождается, а в других — нет. Это очень странно!

Мои вопросы таковы:

  1. Нормально ли это для Explorer делать это? Почему это так? Или у меня ошибка в моем коде? Очень неприятно видеть, что на COM-объекты все еще ссылаются, когда мое приложение завершает работу! Также это означает, что ресурсы, удерживаемые IDataObject, привязаны до тех пор, пока Explorer не решит освободить объект.
  2. Если это действительно нормальное поведение (и даже если это не так, я думаю, я должен справиться с целями удаления с неправильным поведением), то какова наилучшая практика для очистки этого неизданного COM-объекта при завершении работы приложения? Я пишу на C Builder и использую ATL, и когда пользователь пытается закрыть приложение, он получает очень недружелюбное сообщение: «В этом приложении все еще есть активные COM-объекты, бла-бла-бла. Вы уверены, что хотите закрыть это приложение? » — предположительно, сгенерированный ATL, который замечает наличие неизданных COM-объектов — как правило, это плохо при завершении работы приложения.

Вот несколько примеров кода. Он реализует IDataObject, который не предоставляет никаких данных, и очень простой IDropSource. Конечно, реальное приложение предоставляет данные через IDataObject, но я обнаружил, что этой базовой реализации достаточно, чтобы воспроизвести проблему. Я написал это в C Builder, но 90% из них — переносимый код Win32. Просто добавьте метку или другой объект в выбранный набор инструментов GUI (MFC, WinForms с C / CLI, Qt, wxWidgets, прямой Win32, что угодно) и привяжите соответствующий код к событию MouseDown.

Я не могу придумать никаких ошибок в этом коде, которые могли бы вызвать такое поведение, но это не значит, что я ничего не пропустил!

 class GenericDataObject : public IDataObject
{
public:
    // basic IUnknown implementation
    ULONG __stdcall AddRef() { return InterlockedIncrement(amp;refcount); }
    ULONG __stdcall Release() {
        ULONG nRefCount = InterlockedDecrement(amp;refcount);
        if (nRefCount == 0) delete this;
        return nRefCount;
    }
    STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject) {
        if (!ppvObject) return E_POINTER;
        if (riid == IID_IUnknown) {
            *ppvObject = static_cast<IUnknown*>(this);
            AddRef();
            return S_OK;
        } else if (riid == IID_IDataObject) {
            *ppvObject = static_cast<IDataObject*>(this);
            AddRef();
            return S_OK;
        } else {
            *ppvObject = NULL;
            return E_NOINTERFACE;
        }
    }
    // IDataObject members
    STDMETHODIMP GetData               (FORMATETC *pformatetcIn, STGMEDIUM *pmedium) { return DV_E_FORMATETC; }
    STDMETHODIMP GetDataHere           (FORMATETC *pformatetc,   STGMEDIUM *pmedium) { return E_NOTIMPL; }
    STDMETHODIMP QueryGetData          (FORMATETC *pformatetc) { return DV_E_FORMATETC; }
    STDMETHODIMP GetCanonicalFormatEtc (FORMATETC *pformatectIn, FORMATETC *pformatetcOut) { return DV_E_FORMATETC; }
    STDMETHODIMP SetData               (FORMATETC *pformatetc,   STGMEDIUM *pmedium,  BOOL fRelease) { return E_NOTIMPL; }
    STDMETHODIMP EnumFormatEtc         (DWORD     dwDirection,   IEnumFORMATETC **ppenumFormatEtc) { return E_NOTIMPL; }
    STDMETHODIMP DAdvise               (FORMATETC *pformatetc,   DWORD advf, IAdviseSink *pAdvSink, DWORD *pdwConnection) { return OLE_E_ADVISENOTSUPPORTED; }
    STDMETHODIMP DUnadvise             (DWORD     dwConnection) { return OLE_E_ADVISENOTSUPPORTED; }
    STDMETHODIMP EnumDAdvise           (IEnumSTATDATA **ppenumAdvise) { return OLE_E_ADVISENOTSUPPORTED; }
public:
    GenericDataObject() : refcount(1) {OutputDebugString("gdo ctor");}
    ~GenericDataObject() {OutputDebugString("gdo dtor");}
private:
    LONG refcount;
};

class GenericDropSource : public IDropSource
{
public:
    // basic IUnknown implementation
    ULONG __stdcall AddRef() { return InterlockedIncrement(amp;refcount); }
    ULONG __stdcall Release() {
        ULONG nRefCount = InterlockedDecrement(amp;refcount);
        if (nRefCount == 0) delete this;
        return nRefCount;
    }
    STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject) {
        if (!ppvObject) return E_POINTER;
        if (riid == IID_IUnknown) {
            *ppvObject = static_cast<IUnknown*>(this);
            AddRef();
            return S_OK;
        } else if (riid == IID_IDropSource) {
            *ppvObject = static_cast<IDropSource*>(this);
            AddRef();
            return S_OK;
        } else {
            *ppvObject = NULL;
            return E_NOINTERFACE;
        }
    }
    // IDropSource members
    STDMETHODIMP QueryContinueDrag     (BOOL fEscapePressed, DWORD grfKeyState) {
        if (fEscapePressed) {
            return DRAGDROP_S_CANCEL;
        }
        if (!(grfKeyState amp; (MK_LBUTTON | MK_RBUTTON))) {
            return DRAGDROP_S_DROP;
        }
        return S_OK;
    }
    STDMETHODIMP GiveFeedback          (DWORD dwEffect) { return DRAGDROP_S_USEDEFAULTCURSORS; }
public:
    GenericDropSource() : refcount(1) {OutputDebugString("gds ctor");}
    ~GenericDropSource() {OutputDebugString("gds dtor");}
private:
    LONG refcount;
};

// This is the C   Builder-specific part; all I did was add a label to the default form
// and tie this event to it.
void __fastcall TForm1::Label1MouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y)
{
    OleInitialize(NULL);
    GenericDataObject *o = new GenericDataObject;
    GenericDropSource *s = new GenericDropSource;
    DWORD effect = 0;
    DoDragDrop(o, s, DROPEFFECT_COPY, amp;effect);
    o->Release();
    s->Release();
}
  

Комментарии:

1. Время жизни объекта данных в значительной степени зависит от цели удаления; возможно, Explorer кэширует его или что-то в этом роде.

2. Более вероятно, что ваш код не справляется с правильным управлением ссылками. Конечно, если бы Explorer делал это неправильно, кто-то другой уже заметил бы это.

3. @DavidHeffernan: это то, что я бы по умолчанию предположил. Если да, то где ошибка? Опубликованный мной код достаточно полон, чтобы воспроизвести проблему, и я не вижу, где проблема может быть в моем коде (но именно поэтому я публикую его, на случай, если я что-то пропустил). Было бы здорово, если бы это была просто какая-то глупая ошибка, которую я допустил! Но если это так, я его не нашел.

4. В качестве подсказки — ожидается, что IDataObject не будет работать практически на каждом методе, который он должен реализовать. Я полагаю, что цель удаления может завершаться при такой ошибке через ветвь кода, которая не освобождает объект данных, в то время как с хорошо реализованными объектами данных он все равно может работать нормально.

5. @Roman: Я написал этот простой код, потому что код перетаскивания в моем производственном приложении столкнулся с этой проблемой, и я пытался изолировать проблему. Рабочий код корректно реализует getData, QueryGetData, GetCanonicalFormatEtc, setData и EnumFormatEtc. Остальные остаются нереализованными, но очень немногие люди, реализующие IDataObject, реализуют их. Особенно для getData / QueryGetData можно возвращать DV_E_FORMATETC , что означает, что у объекта данных нет формата данных, запрошенного клиентом (например, объект, содержащий текст, вернет это, если кто-то запросит растровое изображение).