雏形,并非久经考验。有缘看到的也请慎用,不敢保证一定能工作正常。
C++
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 |
#include <windows.h> #include <windowsx.h> #include <ole2.h> #include <oleidl.h> #include <shlobj.h> // 用于支持 OLE 拖放 #include <tchar.h> #include <iostream> #include <iomanip> #include <string> #include <vector> // 将目标窗口激活并置于前台 void BringWindowToFront(HWND hwnd) { if (IsIconic(hwnd)) ShowWindow(hwnd, SW_RESTORE); SetForegroundWindow(hwnd); } HWND g_hwndTarget = NULL; POINT g_ptBestPosition; // 获取目标窗口的最佳鼠标位置(屏幕坐标)(此处为目标窗口的中心) POINT GetCursorBestPosition(HWND hwnd) { RECT rc; GetWindowRect(hwnd, &rc); std::cout << "目标窗口:(" << rc.left << ", " << rc.top << ", " << rc.right << ", " << rc.bottom << ")" << std::endl; POINT pt; pt.x = rc.left + (rc.right - rc.left) / 2; pt.y = rc.top + (rc.bottom - rc.top) / 2; std::cout << "中点位置:(" << pt.x << ", " << pt.y << ")" << std::endl; return pt; } int g_iDragQueryTimes = 0; // 判断当前鼠标位置以及窗口是否符合预期 HRESULT ShouldContinueDrag() { if (g_iDragQueryTimes == 0) { HWND hwndCapture = GetCapture(); std::cout << "ShouldContinueDrag(): 正在捕获鼠标事件的窗口句柄:0x" << std::hex << hwndCapture << std::endl; RECT rc; GetWindowRect(hwndCapture, &rc); std::cout << "窗口位置:(" << std::dec << rc.left << ", " << rc.top << ", " << rc.right << ", " << rc.bottom << ")" << std::endl; POINT pt = g_ptBestPosition; ScreenToClient(hwndCapture, &pt); std::cout << "目标位置(以捕获窗口客户区坐标系):(" << pt.x << ", " << pt.y << ")" << std::endl; } // 获取鼠标位置 POINT cursorPos; // GetCursorPos(&cursorPos); DWORD dwPos = GetMessagePos(); cursorPos.x = GET_X_LPARAM(dwPos); cursorPos.y = GET_Y_LPARAM(dwPos); if (cursorPos.x != g_ptBestPosition.x || cursorPos.y != g_ptBestPosition.y) { std::cout << "ShouldContinueDrag(): 鼠标指针 (" << cursorPos.x << ", " <<cursorPos.y << ") 没有在预期位置 (" << g_ptBestPosition.x << ", " << g_ptBestPosition.y << ")" << std::endl; // 校正鼠标位置,然后重试 g_iDragQueryTimes++; std::cout << "ShouldContinueDrag(): 第 " << g_iDragQueryTimes << " 次修正坐标" << std::endl; // 当前处于 DoDragDrop 内部消息循环中,它虽然仅处理有限种类的事件,但输入事件在其处理范围内。 // 如果直接 post 鼠标相关的消息的话,担心处理时它会使用 GetMessagePos 来获取消息发生时的鼠 // 标指针位置,故此处 post 了一个按键释放消息,以试图强迫它读取当前指针的位置,而在 post 之前 // 已经将鼠标指针位置进行了修正设置。—— 这些代码是预防性代码,尚无实际案例中用到 SetCursorPos(g_ptBestPosition.x, g_ptBestPosition.y); PostMessage(NULL, WM_KEYUP, 0, 0); if (g_iDragQueryTimes >= 3) { std::cout << "ShouldContinueDrag(): 多次修正无效,放弃" << std::endl; return DRAGDROP_S_CANCEL; } std::cout << "ShouldContinueDrag(): 继续拖动,返回 S_OK" << std::endl; return S_OK; } HWND hwnd = WindowFromPoint(cursorPos); if (hwnd == g_hwndTarget) { std::cout << "ShouldContinueDrag(): 鼠标指针下是预期窗口,返回 DRAGDROP_S_DROP" << std::endl; return DRAGDROP_S_DROP; } else { std::cout << "ShouldContinueDrag(): 鼠标指针下不是预期窗口,返回 DRAGDROP_S_CANCEL" << std::endl; return DRAGDROP_S_CANCEL; } } class SimpleEnumFormatEtc : public IEnumFORMATETC { private: ULONG refCount; FORMATETC format; bool returned; public: SimpleEnumFormatEtc(const FORMATETC& fmt) : refCount(1), format(fmt), returned(false) { } // IUnknown 方法 HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override { if (riid == IID_IUnknown || riid == IID_IEnumFORMATETC) { *ppvObject = static_cast<IEnumFORMATETC*>(this); AddRef(); std::cout << "SimpleEnumFormatEtc.QueryInterface(): 接口查询已结束 (IEnumFORMATETC/IUnknown)" << std::endl; return S_OK; } *ppvObject = nullptr; return E_NOINTERFACE; } ULONG STDMETHODCALLTYPE AddRef(void) override { return ++refCount; } ULONG STDMETHODCALLTYPE Release(void) override { ULONG res = --refCount; if (res == 0) delete this; return res; } // IEnumFORMATETC 方法 // Next 方法返回一个支持的 FORMATETC 值(只支持一种格式) HRESULT STDMETHODCALLTYPE Next(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched) override { if (rgelt == nullptr) return E_POINTER; if (!returned && celt > 0) { rgelt[0] = format; // 返回我们支持的格式 if (pceltFetched) *pceltFetched = 1; returned = true; return S_OK; } // 已经返回过了,后续返回 S_FALSE 表示枚举结束 if (pceltFetched) *pceltFetched = 0; return S_FALSE; } HRESULT STDMETHODCALLTYPE Skip(ULONG celt) override { if (!returned && celt > 0) { returned = true; return S_OK; } return S_FALSE; } HRESULT STDMETHODCALLTYPE Reset(void) override { returned = false; return S_OK; } HRESULT STDMETHODCALLTYPE Clone(IEnumFORMATETC** ppEnum) override { if (ppEnum == nullptr) return E_POINTER; // 创建一个新实例,将状态重置成初始状态 SimpleEnumFormatEtc* pNew = new SimpleEnumFormatEtc(format); if (pNew) { *ppEnum = pNew; return S_OK; } *ppEnum = nullptr; return E_OUTOFMEMORY; } }; // 简单的 IDataObject 实现 class SimpleDataObject : public IDataObject { public: SimpleDataObject(const std::wstring& filePath) : refCount(1) { formatEtc.cfFormat = CF_HDROP; formatEtc.ptd = nullptr; formatEtc.dwAspect = DVASPECT_CONTENT; formatEtc.lindex = -1; formatEtc.tymed = TYMED_HGLOBAL; // 设置文件路径 files.push_back(filePath); } // 实现 QueryInterface 方法 HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override { if (riid == IID_IUnknown || riid == IID_IDataObject) { *ppvObject = static_cast<IDataObject*>(this); AddRef(); std::cout << "SimpleDataObject.QueryInterface(): 接口查询已结束 (IDataObject/IUnknown)" << std::endl; return S_OK; } return E_NOINTERFACE; } // 实现 AddRef 方法 ULONG STDMETHODCALLTYPE AddRef() override { return ++refCount; } // 实现 Release 方法 ULONG STDMETHODCALLTYPE Release() override { ULONG count = --refCount; if (count == 0) { delete this; } return count; } // 实现 GetData 方法 HRESULT STDMETHODCALLTYPE GetData(LPFORMATETC pFormatetc, LPSTGMEDIUM pMedium) override { if (!pFormatetc || !pMedium) return E_POINTER; if (pFormatetc->cfFormat == CF_HDROP) { // 分配一个全局内存块来保存文件路径 size_t size = sizeof(DROPFILES) + (files[0].size() + 1 + 1) * sizeof(wchar_t); pMedium->hGlobal = GlobalAlloc(GHND, size); if (!pMedium->hGlobal) return E_OUTOFMEMORY; DROPFILES* dropFiles = (DROPFILES*)GlobalLock(pMedium->hGlobal); dropFiles->pFiles = sizeof(DROPFILES); dropFiles->fWide = TRUE; // 复制文件路径到 DROPFILES 结构后 wcscpy_s((wchar_t*)dropFiles + sizeof(DROPFILES) / sizeof(wchar_t), files[0].size() + 1, files[0].c_str()); GlobalUnlock(pMedium->hGlobal); pMedium->tymed = TYMED_HGLOBAL; pMedium->pUnkForRelease = nullptr; std::cout << "SimpleDataObject.GetData(): HDROP 数据已返回" << std::endl; return S_OK; } return DV_E_FORMATETC; } HRESULT STDMETHODCALLTYPE GetDataHere(LPFORMATETC pFormatetc, LPSTGMEDIUM pMedium) override { return E_NOTIMPL; } HRESULT STDMETHODCALLTYPE QueryGetData(LPFORMATETC pFormatetc) override { if (!pFormatetc) return E_POINTER; if ((pFormatetc->cfFormat == CF_HDROP) && (pFormatetc->tymed & TYMED_HGLOBAL)) return S_OK; return DV_E_FORMATETC; } HRESULT STDMETHODCALLTYPE GetCanonicalFormatEtc(LPFORMATETC pFormatetcIn, LPFORMATETC pFormatetcOut) override { return DATA_S_SAMEFORMATETC; } HRESULT STDMETHODCALLTYPE SetData(LPFORMATETC pFormatetc, LPSTGMEDIUM pMedium, BOOL fRelease) override { return E_NOTIMPL; } HRESULT STDMETHODCALLTYPE EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC** ppEnumFormatEtc) override { if (dwDirection == DATADIR_GET) { if (!ppEnumFormatEtc) return E_POINTER; // 创建一个简单的枚举器实例,返回我们支持的格式 *ppEnumFormatEtc = new SimpleEnumFormatEtc(formatEtc); return (*ppEnumFormatEtc) ? S_OK : E_OUTOFMEMORY; } return E_NOTIMPL; } HRESULT STDMETHODCALLTYPE DAdvise(LPFORMATETC pFormatetc, DWORD advf, IAdviseSink* pAdvSink, DWORD* pdwConnection) override { return OLE_E_ADVISENOTSUPPORTED; } HRESULT STDMETHODCALLTYPE DUnadvise(DWORD dwConnection) override { return OLE_E_ADVISENOTSUPPORTED; } HRESULT STDMETHODCALLTYPE EnumDAdvise(IEnumSTATDATA** ppEnumAdvise) override { return OLE_E_ADVISENOTSUPPORTED; } private: ULONG refCount; FORMATETC formatEtc; std::vector<std::wstring> files; // 存储文件路径 }; // 简单的 DropSource 实现 class SimpleDropSource : public IDropSource { public: SimpleDropSource() : m_refCount(1) {} HRESULT STDMETHODCALLTYPE QueryContinueDrag(BOOL fEscapePressed, DWORD grfKeyState) override { return ShouldContinueDrag(); } HRESULT STDMETHODCALLTYPE GiveFeedback(DWORD dwEffect) override { return DRAGDROP_S_USEDEFAULTCURSORS; } HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override { if (riid == IID_IUnknown || riid == IID_IDropSource) { *ppvObject = static_cast<IDropSource*>(this); AddRef(); std::cout << "SimpleDropSource.QueryInterface(): 接口查询已结束 (IDropSource/IUnknown)" << std::endl; return S_OK; } return E_NOINTERFACE; } ULONG STDMETHODCALLTYPE AddRef() override { return ++m_refCount; } ULONG STDMETHODCALLTYPE Release() override { ULONG refCount = --m_refCount; if (refCount == 0) { delete this; } return refCount; } private: ULONG m_refCount; // 引用计数 }; // 根据 HRESULT 输出错误码和对应的错误信息 void PrintHResultError(HRESULT hr) { LPVOID lpMsgBuf = nullptr; DWORD dwFormatFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS; // 使用 FormatMessage 获取错误描述 DWORD nChars = FormatMessage(dwFormatFlags, NULL, hr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL); if (nChars == 0) { std::wcout << L"Error code: 0x" << std::hex << hr << L". Unable to retrieve error message." << std::endl; } else { std::wstring errorMessage(reinterpret_cast<LPTSTR>(lpMsgBuf)); std::wcout << L"Error code: 0x" << std::hex << hr << L", message: " << errorMessage << std::endl; LocalFree(lpMsgBuf); // 释放由 FormatMessage 分配的内存 } } // 用于执行拖放操作的函数 void DoDrop(HWND hwndTarget, IDataObject* pDataObject, IDropSource* pDropSource) { // 记录当前鼠标的位置 POINT originalCursorPos; GetCursorPos(&originalCursorPos); std::cout << "DoDrop(): 鼠标指针位置已保存" << std::endl; // 将目标窗口带到前台并激活 BringWindowToFront(hwndTarget); std::cout << "DoDrop(): 窗口已带到前台" << std::endl; // 获取鼠标指针到目标窗口的最佳位置 g_ptBestPosition = GetCursorBestPosition(hwndTarget); std::cout << "DoDrop(): 鼠标指针的合适位置已更新" << std::endl; // 这里是个技巧性处理。 // DoDragDrop 是一个模态调用,内部存在一个消息泵,需要向消息队列里预先存放一条消息才能触发它开 // 始运转。否则它会一直陷于等待。因此才需要以下的 PostMessage 调用。 // 对于一个拖放操作而言,用鼠标移动消息充当此角色显然非常适合的。但当前本线程内并不存在任何窗口, // 所以 hwnd 只能为 NULL。好在该 API 的文档中说明,hwnd 为 NULL 时表示消息的投递目标为所在 // 线程,恰好可供 DoDragDrop 从线程消息队列中提取,真是天作之合。 // 不过 DoDragDrop 调用进入模态后,会使用(或创建)剪贴板功能相关的一个隐藏窗口来捕捉鼠标输入, // 但在处理输入消息时,使用的鼠标指针坐标并非 lParam 参数,而是从消息结构(MSG)中获取的。因此 // 以下调用代码中的 lParam 参数理论上是无作用的,它仅能起到触发消息泵开始运转的作用,从而使得 // IDropSource->QueryContinueDrag 会被调用,然后在其中修正一次鼠标指针的坐标,以达到将文件 // 放落到正确窗口的正确位置上的目的。这些看法来自于对以下链接内代码的粗略阅读: // https://github.com/tongzx/nt5src/blob/master/Source/XPSP1/NT/com/ole32/ole232/drag/drag.cpp#L1490 // // 但在 Windows 10 上实测时,该坐标竟然起了作用,使得对鼠标指针位置进行修正的补救措施勿需实施, // 也是吊诡。 PostMessage(NULL, WM_MOUSEMOVE, 0, MAKELPARAM(g_ptBestPosition.x, g_ptBestPosition.y)); // 调用 DoDragDrop 来执行拖放操作 DWORD dwEffect; HRESULT hr = DoDragDrop(pDataObject, pDropSource, DROPEFFECT_COPY, &dwEffect); if (!SUCCEEDED(hr)) PrintHResultError(hr); // 恢复鼠标原位置 // SetCursorPos(originalCursorPos.x, originalCursorPos.y); // std::cout << "DoDrop(): 鼠标指针位置已恢复" << std::endl; } void DropFileTo360Jiagu() { g_hwndTarget = FindWindow(L"SunAwtFrame", L"360加固助手"); if (!g_hwndTarget) { std::cout << "main(): 360加固助手 (Class: SunAwtFrame) 窗口未找到。" << std::endl; return; } std::cout << "main(): 360加固助手 (Class: SunAwtFrame) 窗口已找到。" << std::endl; OleInitialize(NULL); // 创建 IDataObject 实例(使用文件路径) std::wstring filePath = L"C:\\Dandy\\dists.apk"; SimpleDataObject* pDataObject = new SimpleDataObject(filePath); // 创建 IDropSource 实例 SimpleDropSource* pDropSource = new SimpleDropSource(); // 执行拖放操作 DoDrop(g_hwndTarget, pDataObject, pDropSource); // 清理 pDropSource->Release(); pDataObject->Release(); OleUninitialize(); } // 主函数 int main() { DropFileTo360Jiagu(); return 0; } |