执行拖放操作时资源管理器不会释放 IDataObject
我正在我的应用程序中实现拖放。我遇到了 Windows 资源管理器在拖放操作后未释放 IDataObject 的问题。为了隔离问题,我实现了一个非常简单的拖放源代码,它应该可以在大多数 Win32 编译器中进行编译。数据对象不包含数据;正如你所看到的,一切都非常简单。数据对象包含可以使用 DebugView 查看的跟踪,以指示它何时创建和何时销毁。
重现:
- 按住鼠标按钮开始拖动。
- 将对象拖放到打开的 Windows 资源管理器窗口中。
观察DebugView中的输出;示例输出:
<前><代码>[4964] gdo ctor [4964服]第4964服[双线] 新服 [4964服]第4964服[双线] 新服此输出表明数据源已被破坏,但有人仍然持有对我的 IDataObject 的引用!
- 开始在同一个资源管理器窗口中拖动文件。即使我此时根本没有与我的项目进行交互,它也会导致打印
gdo dtor
- 表明对 IDataObject 的最终引用已释放。
我运行的是 Windows 7 64 位。有趣的是,某些资源管理器窗口确实在放置后立即释放数据对象;其他人似乎不会这样做,直到您开始将不同的对象拖到资源管理器窗口中,如步骤 #4 所示。它似乎还取决于我在窗口中放置对象的位置 - 有些位置会导致对象立即释放,而其他位置则不会。很奇怪!
我的问题是:
- Explorer 这样做正常吗?这是为什么呢?或者我的代码中有错误吗?当我的应用程序终止时看到 COM 对象仍然被引用,这是非常令人不安的!它还意味着 IDataObject 持有的资源将被占用,直到资源管理器决定释放该对象。
- 如果这确实是正常行为(即使不是,我想我应该处理行为不当的放置目标),那么当应用程序终止时清理这个未释放的 COM 对象的最佳实践是什么?我正在使用 C++ Builder 并使用 ATL 进行编写,当用户尝试关闭应用程序时,他们会收到非常不友好的“此应用程序中仍然有活动的 COM 对象,等等等等。您确定要关闭此应用程序吗?” ?” - 大概是由 ATL 生成的,它注意到有未释放的 COM 对象 - 通常在应用程序关闭时是一件坏事。
这是一些示例代码。它实现了一个不提供数据的 IDataObject 和一个非常基本的 IDropSource。当然,真实的应用程序通过 IDataObject 提供数据,但我发现这个基本实现足以重现该问题。我是用 C++ Builder 编写的,但其中 90% 是可移植的 Win32 代码。只需将标签或其他对象添加到所选的 GUI 工具包(MFC、带有 C++/CLI 的 WinForms、Qt、wxWidgets、直接 Win32 等)并将适当的代码绑定到 MouseDown 事件即可。
我想不出这段代码中有任何错误会导致这种行为,但这并不意味着我没有错过任何错误!
class GenericDataObject : public IDataObject
{
public:
// basic IUnknown implementation
ULONG __stdcall AddRef() { return InterlockedIncrement(&refcount); }
ULONG __stdcall Release() {
ULONG nRefCount = InterlockedDecrement(&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(&refcount); }
ULONG __stdcall Release() {
ULONG nRefCount = InterlockedDecrement(&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 & (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, &effect);
o->Release();
s->Release();
}
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.
To reproduce:
- Start the drag by holding down the mouse button.
- Drag-and-drop the object into an open Windows Explorer window.
Observe the output in DebugView; sample output:
[4964] gdo ctor [4964] gds ctor [4964] gds dtor
This output indicates that the data source was destructed, but somebody is still holding a reference to my IDataObject!
- 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
gdo dtor
to be printed - indicating that the final reference to the IDataObject was released.
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!
My questions are these:
- 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.
- 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.
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.
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!
class GenericDataObject : public IDataObject
{
public:
// basic IUnknown implementation
ULONG __stdcall AddRef() { return InterlockedIncrement(&refcount); }
ULONG __stdcall Release() {
ULONG nRefCount = InterlockedDecrement(&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(&refcount); }
ULONG __stdcall Release() {
ULONG nRefCount = InterlockedDecrement(&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 & (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, &effect);
o->Release();
s->Release();
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论