#c #windows #winapi #com #drag-and-drop
#c #Windows #winapi #com #перетаскивание
Вопрос:
Я реализую перетаскивание в своем приложении. У меня проблема с Windows Explorer, который не освобождает мой IDataObject после операции перетаскивания. Чтобы изолировать проблему, я реализовал очень простой исходный код перетаскивания, который должен компилироваться практически в любом компиляторе Win32. Объект данных не содержит данных; как вы можете видеть, все очень просто. Объект данных содержит трассировку, которую можно просмотреть с помощью DebugView, чтобы указать, когда он создан и когда он уничтожен.
Для воспроизведения:
- Начните перетаскивание, удерживая нажатой кнопку мыши.
- Перетащите объект в открытое окно проводника Windows.
-
Обратите внимание на вывод в DebugView; пример вывода:
[4964] gdo ctor [4964] gds ctor [4964] gds dtor
Этот вывод указывает, что источник данных был уничтожен, но кто-то все еще хранит ссылку на мой IDataObject!
- Начните перетаскивать файл в том же окне проводника. Несмотря на то, что я в данный момент вообще не взаимодействую со своим проектом, он вызывает
gdo dtor
печать, указывая, что последняя ссылка на IDataObject была выпущена.
Я использую 64-разрядную версию Windows 7. Интересно отметить, что некоторые окна проводника освобождают объект данных сразу после удаления; другие, похоже, этого не делают, пока вы не начнете перетаскивать другой объект в окно проводника, как указано в шаге # 4. Похоже, это также зависит от того, где в окне я отбрасываю объект — в некоторых местах объект немедленно освобождается, а в других — нет. Это очень странно!
Мои вопросы таковы:
- Нормально ли это для Explorer делать это? Почему это так? Или у меня ошибка в моем коде? Очень неприятно видеть, что на COM-объекты все еще ссылаются, когда мое приложение завершает работу! Также это означает, что ресурсы, удерживаемые IDataObject, привязаны до тех пор, пока Explorer не решит освободить объект.
- Если это действительно нормальное поведение (и даже если это не так, я думаю, я должен справиться с целями удаления с неправильным поведением), то какова наилучшая практика для очистки этого неизданного 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 , что означает, что у объекта данных нет формата данных, запрошенного клиентом (например, объект, содержащий текст, вернет это, если кто-то запросит растровое изображение).