c++ windows winapi com drag-and-drop

c++ - Explorer no lanza IDataObject al arrastrar/soltar



windows winapi (0)

Estoy implementando arrastrar y soltar en mi aplicación. Tengo un problema con Windows Explorer que no libera mi IDataObject después de una operación de arrastrar y soltar. Para aislar el problema, implementé una fuente muy simple de arrastrar y soltar que debería compilarse en la mayoría de los compiladores de Win32. El objeto de datos no contiene datos; como puedes ver, todo es muy simple. El objeto de datos contiene un seguimiento que se puede ver con DebugView para indicar cuándo se crea y cuándo se destruye.

Reproducir:

  1. Comience la arrastre manteniendo presionado el botón del mouse.
  2. Arrastra y suelta el objeto en una ventana abierta de Windows Explorer.
  3. Observe el resultado en DebugView; muestra de salida:

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

    Este resultado indica que la fuente de datos fue destruida, pero ¡alguien todavía tiene una referencia a mi IDataObject!

  4. Comience a arrastrar un archivo en la misma ventana del Explorador. Aunque no estoy interactuando en absoluto con mi proyecto en este momento, hace que se gdo dtor , lo que indica que se lanzó la referencia final al IDataObject.

Estoy ejecutando Windows 7 de 64 bits. Es interesante observar que algunas ventanas Explorer liberan el objeto de datos inmediatamente después de la caída; otros parecen no hacer eso hasta que empiezas a arrastrar un objeto diferente a la ventana del Explorador como se indica en el paso 4. También parece depender de dónde en la ventana deje caer el objeto: algunos lugares hacen que el objeto se suelte inmediatamente y otros no. ¡Es muy extraño!

Mis preguntas son estas:

  1. ¿Es esto normal para Explorer hacer esto? ¿Por qué es esto? ¿O tengo un error en mi código? ¡Es muy desconcertante ver objetos COM todavía referenciados cuando mi aplicación termina! También significa que los recursos mantenidos por IDataObject están vinculados hasta que Explorer decida liberar el objeto.
  2. Si esto es realmente un comportamiento normal (e incluso si no lo es, supongo que debería hacer frente a los objetivos de bajada de comportamiento incorrecto), entonces, ¿cuál es la mejor práctica para limpiar este objeto COM no publicado cuando finaliza la aplicación? Escribo en C ++ Builder y uso ATL, y cuando el usuario intenta cerrar la aplicación, se muestra muy desagradable. "Todavía hay objetos COM activos en esta aplicación, bla, bla, bla. ¿Estás seguro de que deseas cerrar esta aplicación? ? " - presumiblemente generado por ATL que está notando que hay objetos COM inéditos - generalmente algo malo en el cierre de la aplicación.

Aquí hay un código de muestra. Implementa un IDataObject que no proporciona datos, y un IDropSource muy básico. Por supuesto, la aplicación real proporciona datos a través de IDataObject pero encontré que esta implementación básica es suficiente para reproducir el problema. Lo escribí en C ++ Builder pero el 90% es código Win32 portátil. Simplemente agregue una etiqueta u otro objeto al conjunto de herramientas de la GUI (MFC, WinForms con C ++ / CLI, Qt, wxWidgets, Win32 directo, lo que sea) y ate el código apropiado al evento MouseDown.

No puedo pensar en ningún error en este código que pueda causar este comportamiento, ¡pero eso no significa que no me haya perdido ninguno!

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(); }