6

Does anyone know which relation may exist between registration-free COM and drag/drop functionality?

Specifically, we have a huge C++ CAD/CAM application comprising a number of EXEs and several hundreds DLLs. Many of them serve as COM servers (both in-proc and out-of-proc) and/or clients, and also implement ActiveX controls.

The most of ActiveX controls and the main CMDIFrameWnd-based window of one of EXEs implement drag/drop functionality. ActiveX controls implement the both drop source and drop target, and the main window is only drop target, in particular, for files from Windows Explorer.

The drag/drop implementation is pretty standard and based on two data members derived from COleDataSource and COleDropTarget for drop source and drop target respectively. The COleDropTarget-derived member is registered with respective window in the window's OnCreate method. It also overrides OnDragEnter, OnDragOver and OnDrop methods in a similar way. Namely, the system-supplied COleDataObject parameter is asked for specific format (in particular, CF_HDROP), and in the case of positive answer, the data (e.g., file path) is extracted from the clipboard. The code looks like the following:

static FORMATETC g_FileFmt = {CF_HDROP, 0, DVASPECT_CONTENT, 0, TYMED_HGLOBAL};
    ....
// Inside OnDragEnter, OnDragOver or OnDrop method
STGMEDIUM stgmedium = {0,0,0};
if (pDataObject->IsDataAvailable(g_FileFmt.cfFormat))
{
    HRESULT hr = pDataObject->GetData(g_FileFmt.cfFormat, &stgmedium);
    HDROP hdrop = (HDROP)GlobalLock(stgmedium.hGlobal);
    if (hdrop != 0)
    {
        int FilesCount = DragQueryFile(hdrop, (UINT)-1, 0, 0);
        if (FilesCount != 0)
        {
            TCHAR FileName[_MAX_PATH];
            DragQueryFile(hdrop, 0, FileName, _MAX_PATH);
            // Check file extension and store the file name for farther use.
        }
        GlobalUnlock(hdrop);
    }
}

The drop source implementation is also straightforward and looks like the following:

void CDmDocListCtrl::OnBeginDrag(NMHDR* pNMHDR, LRESULT* pResult)
{
    NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
    if (pNMListView->iItem != -1 && m_pOleDataSource && prv_BeginDrag())
    {
        DROPEFFECT DE = m_pOleDataSource->DoDragDrop(
            DROPEFFECT_COPY | DROPEFFECT_MOVE | DROPEFFECT_LINK, 0);
    }
    *pResult = 0;
}

where prv_BeginDrag() function collects dragged data, packs it and puts on the clipboard by calling SetData method from the m_pOleDataSource object's IDataObject interface.

The all this stuff worked perfectly until it was decided to make the whole application registration-free. It took me three months to force the application run isolated (without registration of COM components) by embedding manifests, launching out-of-proc COM servers on demand and altering CLSID of some classes in order to separate instances of the same server launched from different folders. At last it begins to work - but without drag/drop functionality, despite it wasn't even touched by my changes.

On the drop target side, when I drag file from Windows Explorer, depicted above call to COleDataObject::IsDataAvailable returns false, although before my changes returned true. At the same time, if I add a single line of code "DragAcceptFiles();" to the main window's OnCreate method, drag/drop begins working via the standard CFrameWnd's WM_DROPFILE message handler.

On the drop source side, the dragged data are successfully packed and placed on the clipboard, but COleDataSource::DoDragDrop method fails, because a call to ::DoDragDrop API inside MFC implementation returns REGDB_E_CLASSNOTREG "Class not registered" result.

It means, that COM activation changes somehow influence drag/drop behavior. How?

P.S. 1) The EXE, to which I drag files from Windows Explorer, has in its project properties "UAC Execution Level = asInvoker". As far as I understand, it tells that the EXE will run at the same UAC level as Windows Explorer when launched by double-click on the file.

2) Quite surprisingly, although drag/drop stopped working with symptoms described above, Copy/Paste continues work well, despite the both technologies have similar implementation.

3) I believe, that if find out when ::DoDragDrop API returns "Class not registered" error, and which class it is looking for, it would be possible to solve the problem.

Thanks for help, Ilia.

4

1 回答 1

2

根据 MartinBa 的建议,我在 Process Monitor 的帮助下解决了这个问题。进程监视器向我显示,当我在 ActiveX 控件中拖动一个项目(在问题中提到)时,系统尝试访问注册表中的类 ID 失败。找那个ID,发现真的不是类ID,而是IDataObject接口ID。它在我的清单文件之一中被引用。

大多数清单是我手工编写的,但有一些,特别是在项目开始时没有这方面的经验,我是由 Visual Studio 从现有的类型库中自动生成的。其中一个 Studio 包含comInterfaceExternalProxyStub了几个系统接口的语句,其中proxyStubClsid32元素(错误地)等于接口 ID。

我仍然不确定这些系统接口是否应该出现在清单中;例如,在IDataObjectIDL 定义之一中仅作为方法的参数提及。无论如何,我只更正了proxyStubClsid32值,问题就消失了......

这个对我来说非常痛苦的故事的寓意是总是检查自动工具的输出......

于 2014-05-08T10:11:40.253 回答