执行拖放操作时,资源管理器不会释放IDataObject

时间:2011-09-29 16:40:42

标签: c++ windows winapi com drag-and-drop

我正在我的应用程序中实现拖放。我遇到了Windows资源管理器在拖放操作后没有释放我的IDataObject的问题。为了隔离这个问题,我实现了一个非常简单的拖放源,它应该在大多数Win32编译器中编译。数据对象不包含数据;你可以看到一切都很简单。数据对象包含可以使用DebugView查看的跟踪,以指示何时创建它以及何时销毁它。

重现:

  1. 按住鼠标按钮开始拖动。
  2. 将对象拖放到打开的Windows资源管理器窗口中。
  3. 观察DebugView中的输出;样本输出:

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

    此输出表明数据源已被破坏,但有人仍然持有对我的IDataObject的引用!

  4. 开始在同一个资源管理器窗口中拖动文件。尽管此时我还没有与我的项目进行交互,但它会导致gdo dtor被打印 - 表明已经释放了对IDataObject的最终引用。
  5. 我正在运行Windows 7 64位。有趣的是,有些资源管理器窗口会在丢弃后立即释放数据对象;在您开始将另一个对象拖动到资源管理器窗口中之前,其他人似乎没有这样做,如步骤#4所示。它似乎还取决于我在窗口中放置对象的位置 - 某些地方会导致对象立即释放而其他地方则不会。这很奇怪!

    我的问题是这些:

    1. 资源管理器执行此操作是否正常?为什么是这样?或者我的代码中有错误?当我的应用程序终止时,看到COM对象仍然被引用是非常令人不安的!此外,它意味着IDataObject持有的资源将被绑定,直到Explorer决定释放该对象。
    2. 如果这确实是正常的行为(即使它不是,我想我应该应对不良行为的drop目标),那么在应用程序终止时清理这个未发布的COM对象的最佳做法是什么?我正在用C ++ Builder编写并使用ATL,当用户试图关闭应用程序时,他们会得到一个非常不友好的“这个应用程序中仍有活动的COM对象,等等等等等等。你确定要关闭这个应用程序吗? ?” - 可能是由ATL生成的,它注意到有未发布的COM对象 - 在应用程序关闭时通常是一件坏事。
    3. 这是一些示例代码。它实现了一个不提供数据的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();
      }
      

0 个答案:

没有答案