Кто-нибудь знает, какая связь может существовать между COM без регистрации и функцией перетаскивания?
В частности, у нас есть огромное приложение C ++ CAD / CAM, состоящее из ряда EXE и нескольких сотен DLL. Многие из них служат COM-серверами (как внутри, так и вне процесса) и / или клиентами, а также реализуют элементы управления ActiveX.
Большинство элементов управления ActiveX и главное окно одного из EXE-файлов на основе CMDIFrameWnd
реализуют функцию перетаскивания. Элементы управления 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);
}
}
Реализация источника перетаскивания также проста и выглядит следующим образом:
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
из интерфейса IDataObject
объекта m_pOleDataSource
.
Все это работало отлично, пока не было решено полностью отказаться от регистрации приложения. Мне потребовалось три месяца, чтобы заставить приложение работать изолированно (без регистрации COM-компонентов) путем встраивания манифестов, запуска внепроцессных COM-серверов по запросу и изменения CLSID некоторых классов, чтобы отделить экземпляры одного и того же сервера, запущенные из разных папки. Наконец он начал работать - но без функции перетаскивания, несмотря на то, что мои изменения даже не коснулись его.
На стороне цели перетаскивания, когда я перетаскиваю файл из проводника Windows, показанный выше вызов COleDataObject::IsDataAvailable
возвращает false, хотя до того, как мои изменения вернули true. В то же время, если я добавлю одну строку кода «DragAcceptFiles();
» в метод OnCreate главного окна, перетаскивание начнет работать через стандартный обработчик сообщений WM_DROPFILE
CFrameWnd.
На стороне источника перетаскивания перетаскиваемые данные успешно упаковываются и помещаются в буфер обмена, но метод COleDataSource::DoDragDrop
не работает, поскольку вызов ::DoDragDrop
API внутри реализации MFC возвращает результат REGDB_E_CLASSNOTREG «Класс не зарегистрирован».
Это означает, что изменения активации COM каким-то образом влияют на поведение перетаскивания. Как?
P.S. 1) EXE, в который я перетаскиваю файлы из проводника Windows, имеет в своих свойствах проекта «Уровень выполнения UAC = asInvoker». Насколько я понимаю, он сообщает, что EXE будет работать на том же уровне UAC, что и проводник Windows, при запуске двойным щелчком по файлу.
2) Как ни странно, хотя перетаскивание и перетаскивание перестало работать с симптомами, описанными выше, копирование / вставка продолжает работать хорошо, несмотря на то, что обе технологии имеют схожую реализацию.
3) Я считаю, что если узнать, когда :: DoDragDrop API возвращает ошибку «Класс не зарегистрирован» и какой класс он ищет, можно будет решить проблему.
Спасибо за помощь, Илья.
IDataObject
идентификатор интерфейса. В конце концов выяснилось, что в одном из нескольких манифестов, автоматически созданных Visual Studio, Studio поставилаproxyStubClsid32
равным идентификатору интерфейса для пары системных интерфейсов! Так как это был довольно большой манифест с почти сотней интерфейсов, я не сразу его уловил. Что тут сказать ... Майкрософт ... - person Ilia   schedule 07.05.2014