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

1

7

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.

Binucleate answered 1/4, 2014 at 16:14 Comment(19)
This question appears to be off-topic because it lacks sufficient information for Stephen Hawking to diagnose the problem. Describe your problem to Stephen Hawking in more detail or include a minimal example in the question itself.Genesia
Programmers tend to run Visual Studio elevated when they build COM components. Since that's necessary to allow them to be registered. Debugging the app then causes problems since the app will run elevated as well. Which disables drag+drop from any app that runs non-elevated, like Explorer.Newburg
Thanks Hans, I also thought about that. But it contradicts with the following fact. We have another application enabling drag/drop behaviour in several ActiveX controls it contains (it actually serves as out-of-proc server for the first one). The mentioned ActiveX controls are both Drop source and Drop target, and it is also stopped working as I described in the question, when dragging and dropping an item inside the same control. So it cannot be result of different security setting for Drop source and Drop target. Regards, Ilia.Binucleate
Also, drag/drop works well when the application's COM components are registered, and doesn't work when the same application, with untouched drag/drop implementation, runs isolated. What's the difference?Binucleate
To clear things up: are both sides of the problematic D&D operation running at same UAC level?Phthisis
Yes, I believe so, because in our second application (which I mentioned in the previous comment) the both sides are the same window. I also believe that the cause which prevents drag/drop in the both applications is one and the same. Ilia.Binucleate
If the D&D issue happens on the same window, I can't see how it could be related to COM activation changes. PS: include SO user's address (like @user3153215) when you answer, I wasn't notified of your latest comments (Hans wasn't either)Phthisis
I added some new info to my post; may be it can give a hint. I try resolve first the case with our main application, as it seems to be more simple: it is only drop target, and deals with regular window and not ActiveX. @Simon Mourier.Binucleate
I have rewritten the whole post and added new info about what happens on drop source side. When drop source implementation calls to ::DoDragDrop API, the call fails with the REGDB_E_CLASSNOTREG "Class not registered" error. It is typical error for registration-free applications. However, which class is meant in this specific case? @SimonBinucleate
You mention Windows Explorer. This can typically involves UAC, as it runs (usually) as non admin. If your app runs at a different UAC level (elevated for example), D&D between Explorer and App will not work.Phthisis
But why then D&D immediately begins working when I add a single line of code "DragAcceptFiles()"?! That single line may change UAC level? And why dragging an item inside one and the same ActiveX control ends up with "Class not registered" error? Which class ::DoDragDrop may seek for? @SimonBinucleate
Besides that, the Exe, to which I drag files from Windows Explorer, has in its project properties "UAC Execution Level = asInvoker". I do not understand, how in such a case the application may run elevated. @SimonBinucleate
No, a single line cannot change UAC level (it's defined at process startup). Hmmm... difficult to diagnose a pb like this without a reproducing sample :)Phthisis
If I could make a reproducing sample, I would apply directly to Microsoft... It does not happen in small example. There is also one more surprising thing. In the same ActiveX control, where ::DoDragDrop returns "Class not registered" error, Copy/Paste works well. And the both technologies use the same set of routines for accessing clipboard... May be Hans has some ideas? @SimonBinucleate
Look at the user comment about uac here msdn.microsoft.com/en-us/library/windows/desktop/…. Not an explanation but a clue that uac is at play.Mercie
If you carefully read the whole post and look over previous comments, you will see that UAC is not the issue here. It is not so simple, unfortunately. @MercieBinucleate
Wrt. drilling into the class-not-registered error, you might try monitoring the apps with Process Monitor while the error happens.Successor
Thanks to @MartinBa, I found the problem!!! The Process Monitor showed me that while I drag an item, the system unsuccessfully tries get access to a class ID in the Registry. I found then that it is really not class ID, but IDataObject interface ID. At the end it turned out, that in one of a few manifests created automatically by Visual Studio, the Studio put proxyStubClsid32 equal to interface ID for a couple of system interfaces! Since it was rather big manifest with near hundred interfaces, I didn't catch it from the beginning. What to say here... Microsoft...Binucleate
You can and should answer your own question (with the relevant details from the comments) if you found a solution.Successor
B
3

Following to MartinBa advice, I solved the problem with the help of Process Monitor. The Process Monitor showed me that while I drag an item in the ActiveX control (mentioned in the question), the system unsuccessfully tries get access to a class ID in the Registry. Looking for that ID, I found that it is really not class ID, but IDataObject interface ID. It was referenced in one of my manifest files.

The most of manifests I have written by hand, but a few, especially at the beginning of the project having no experience in the area, I generated automatically by Visual Studio from existing type library. In one of them Studio included the comInterfaceExternalProxyStub statement for a couple of system interfaces, in which proxyStubClsid32 element was (erroneously) equal to the interface ID.

I'm still not sure whether those system interfaces should present in the manifest; for example, the IDataObject is only mentioned as a method's parameter in one of IDL definitions. Anyway, I corrected only the proxyStubClsid32 value, and the problem disappeared...

The moral of this very painful for me story is to always check output of automatic tools...

Binucleate answered 8/5, 2014 at 10:11 Comment(1)
You can also accept your own answer (after a grace period) to clean things up :-)Successor

© 2022 - 2024 — McMap. All rights reserved.