Кто-нибудь знает, какая связь может существовать между свободным от регистрации COM и функциональностью перетаскивания?
В частности, у нас есть огромное C ++ CAD / CAM-приложение, состоящее из нескольких EXE-файлов и нескольких сотен DLL-файлов. Многие из них служат в качестве COM-серверов (как внутренних, так и внешних) и / или клиентов, а также реализуют элементы управления ActiveX.
Большинство элементов управления ActiveX и основные CMDIFrameWnd
окно одного из EXE-файлов реализует функцию перетаскивания. Элементы управления ActiveX реализуют как источник перетаскивания, так и цель перетаскивания, а главное окно предназначено только для перетаскивания, в частности, для файлов из проводника Windows.
Реализация перетаскивания является довольно стандартной и основана на двух членах данных, полученных из COleDataSource
а также COleDropTarget
для удаления источника и удаления цели соответственно. COleDropTarget
член зарегистрирован в соответствующем окне OnCreate
метод. Это также переопределяет OnDragEnter
, OnDragOver
а также OnDrop
методы аналогичным образом. А именно, система поставляется COleDataObject
Параметр запрашивается для определенного формата (в частности, CF_HDROP), и в случае положительного ответа данные (например, путь к файлу) извлекаются из буфера обмена. Код выглядит следующим образом:
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);
}
}
Реализация drop source также проста и выглядит следующим образом:
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;
}
где prv_BeginDrag()
Функция собирает перетаскиваемые данные, упаковывает их и помещает в буфер обмена, вызывая SetData
метод из m_pOleDataSource
объекты IDataObject
интерфейс.
Все это работало отлично, пока не было решено сделать все приложение без регистрации. Мне потребовалось три месяца, чтобы заставить приложение работать изолированно (без регистрации компонентов COM) путем встраивания манифестов, запуска COM-серверов вне процесса по требованию и изменения CLSID некоторых классов, чтобы отделить экземпляры одного и того же сервера от разных папки. Наконец-то он начинает работать — но без функции перетаскивания, несмотря на то, что мои изменения даже не затронули меня.
На целевой стороне перетаскивания, когда я перетаскиваю файл из проводника Windows, как показано выше, COleDataObject::IsDataAvailable
возвращает false, хотя раньше мои изменения возвращали true. В то же время, если я добавлю одну строку кода «DragAcceptFiles();
«к методу OnCreate главного окна, перетаскивание начинает работать через стандартный CFrameWnd WM_DROPFILE
обработчик сообщений
Со стороны источника отбрасывания перетаскиваемые данные успешно упаковываются и помещаются в буфер обмена, но COleDataSource::DoDragDrop
метод терпит неудачу, потому что вызов ::DoDragDrop
API внутри реализации MFC возвращает REGDB_E_CLASSNOTREG «Класс не зарегистрирован».
Это означает, что изменения активации COM как-то влияют на поведение перетаскивания. Как?
Постскриптум 1) EXE-файл, в который я перетаскиваю файлы из Проводника Windows, имеет в своем проекте свойства «Уровень выполнения UAC = asInvoker». Насколько я понимаю, это говорит о том, что EXE будет работать на том же уровне UAC, что и Windows Explorer, при запуске двойным щелчком по файлу.
2) Удивительно, но, несмотря на то, что перетаскивание перестало работать с описанными выше симптомами, копирование / вставка продолжает работать хорошо, несмотря на то, что обе технологии имеют схожую реализацию.
3) Я полагаю, что если выяснить, когда :: DoDragDrop API возвращает ошибку «Класс не зарегистрирован», и какой класс он ищет, можно было бы решить эту проблему.
Спасибо за помощь,
Илья.
Следуя совету MartinBa, я решил проблему с помощью Process Monitor. Монитор процессов показал мне, что, когда я перетаскиваю элемент в элементе управления ActiveX (упомянутый в вопросе), система безуспешно пытается получить доступ к идентификатору класса в реестре. Ища этот идентификатор, я обнаружил, что это действительно не идентификатор класса, но IDataObject
идентификатор интерфейса На него ссылались в одном из моих файлов манифеста.
Большинство манифестов я написал вручную, но некоторые, особенно в начале проекта, не имеющие опыта в этой области, я автоматически генерировал в Visual Studio из существующей библиотеки типов. В одной из них студия включила comInterfaceExternalProxyStub
оператор для пары системных интерфейсов, в которых proxyStubClsid32
элемент был (ошибочно) равен идентификатору интерфейса.
Я все еще не уверен, должны ли эти системные интерфейсы присутствовать в манифесте; например, IDataObject
упоминается только как параметр метода в одном из определений IDL. Во всяком случае, я исправил только proxyStubClsid32
значение, и проблема исчезла …
Мораль этой очень болезненной для меня истории — всегда проверять производительность автоматических инструментов …
Других решений пока нет …