Note that there are some explanatory texts on larger screens.

plurals
  1. POExplorer doesn't release IDataObject when doing a drag/drop
    text
    copied!<p>I'm implementing drag-and-drop in my application. I'm having a problem with Windows Explorer not releasing my IDataObject after a drag-and-drop operation. To isolate the problem, I've implemented a very simple drag-and-drop source that should compile in most any Win32 compiler. The data object contains no data; as you can see everything is very simple. The data object contains tracing that can be viewed with DebugView to indicate when it is created and when it is destroyed.</p> <p>To reproduce:</p> <ol> <li>Start the drag by holding down the mouse button.</li> <li>Drag-and-drop the object into an open Windows Explorer window.</li> <li><p>Observe the output in DebugView; sample output:</p> <pre><code>[4964] gdo ctor [4964] gds ctor [4964] gds dtor </code></pre> <p>This output indicates that the data source was destructed, but somebody is still holding a reference to my IDataObject!</p></li> <li>Start dragging a file in the same Explorer window. Even though I'm not at all interacting with my project at this time, it causes <code>gdo dtor</code> to be printed - indicating that the final reference to the IDataObject was released.</li> </ol> <p>I'm running Windows 7 64-bit. It's interesting to note that some Explorer windows do release the data object right away after the drop; others don't seem to do that until you start dragging a different object into the Explorer window as indicated in step #4. It also seems to depend on where in the window I drop the object - some places cause the object to be immediately released and others don't. It's very strange!</p> <p>My questions are these:</p> <ol> <li>Is this normal for Explorer to do this? Why is this? Or do I have a bug in my code? It's very disconcerting to see COM objects still referenced when my application terminates! Also it means that the resources held by IDataObject are tied up until Explorer decides to release the object.</li> <li>If this is indeed normal behavior (and even if it isn't, I guess I should cope with ill-behaved drop targets), then what is the best practice for cleaning up this unreleased COM object when the application terminates? I'm writing in C++ Builder and using ATL, and when the user tries to close the application, they get a very unfriendly "There are still active COM objects in this application, blah blah blah. Are you sure you want to close this application?" - presumably generated by ATL which is noticing there are unreleased COM objects - generally a bad thing on application shutdown.</li> </ol> <p>Here's some sample code. It implements an IDataObject that provides no data, and a very basic IDropSource. Of course, the real application provides data via IDataObject but I found this basic implementation is enough to reproduce the issue. I wrote it in C++ Builder but 90% of it is portable Win32 code. Just add a label or other object to the GUI toolkit of choice (MFC, WinForms with C++/CLI, Qt, wxWidgets, straight Win32, whatever) and tie the appropriate code to the MouseDown event.</p> <p>I can't think of any bugs in this code that would cause this behavior, but that doesn't mean I didn't miss any!</p> <pre><code>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&lt;IUnknown*&gt;(this); AddRef(); return S_OK; } else if (riid == IID_IDataObject) { *ppvObject = static_cast&lt;IDataObject*&gt;(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&lt;IUnknown*&gt;(this); AddRef(); return S_OK; } else if (riid == IID_IDropSource) { *ppvObject = static_cast&lt;IDropSource*&gt;(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-&gt;Release(); s-&gt;Release(); } </code></pre>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload