Last active
July 23, 2026 11:04
-
-
Save 59de44955ebd/548f4cd598dd0142a0e13da6773b6cfa to your computer and use it in GitHub Desktop.
Minimal virtual file drop (or put into clipboard) implementation for Windows
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #define STRICT | |
| #include <windows.h> | |
| #include <windowsx.h> | |
| #include <shlobj.h> | |
| #include <vector> | |
| #define WINDOW_CLASS L"VirtualFileDrop" | |
| HINSTANCE g_hinst; | |
| class CDataObject : public IDataObject | |
| { | |
| public: | |
| // *** IUnknown *** | |
| STDMETHODIMP QueryInterface(REFIID riid, void **ppv); | |
| STDMETHODIMP_(ULONG) AddRef(); | |
| STDMETHODIMP_(ULONG) Release(); | |
| // *** IDataObject *** | |
| STDMETHODIMP GetData(FORMATETC * pformatetcIn, STGMEDIUM * pmedium); | |
| STDMETHODIMP QueryGetData(FORMATETC * pformatetc); | |
| STDMETHODIMP EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC ** ppenumFormatEtc); | |
| // Not implemented | |
| STDMETHODIMP GetDataHere(FORMATETC * pformatetcIn, STGMEDIUM * pmedium) { return E_NOTIMPL; } | |
| STDMETHODIMP GetCanonicalFormatEtc(FORMATETC * pformatectIn, FORMATETC * pformatetcOut) { return E_NOTIMPL; } | |
| STDMETHODIMP SetData(FORMATETC * pformatetcIn, STGMEDIUM * pmedium, BOOL fRelease) { return E_NOTIMPL; } | |
| STDMETHODIMP DAdvise(FORMATETC * pformatetc, DWORD advf, IAdviseSink * pAdvSink, DWORD * pdwConnection) { return OLE_E_ADVISENOTSUPPORTED; } | |
| STDMETHODIMP DUnadvise(DWORD dwConnection) { return OLE_E_ADVISENOTSUPPORTED; } | |
| STDMETHODIMP EnumDAdvise(IEnumSTATDATA ** ppenumFormatEtc) { return OLE_E_ADVISENOTSUPPORTED; } | |
| CDataObject(const wchar_t * filename, const std::vector<uint8_t>& data) : m_cRef(1), m_filename(filename), m_data(data) | |
| { | |
| m_formatFileDescriptorW = RegisterClipboardFormatW(CFSTR_FILEDESCRIPTOR); | |
| m_formatFileContents = RegisterClipboardFormatW(CFSTR_FILECONTENTS); | |
| } | |
| private: | |
| ULONG m_cRef; | |
| UINT m_formatFileDescriptorW; | |
| UINT m_formatFileContents; | |
| const wchar_t * m_filename; | |
| const std::vector<uint8_t> m_data; | |
| }; | |
| HRESULT CDataObject::QueryInterface(REFIID riid, void **ppv) | |
| { | |
| IUnknown *punk = NULL; | |
| if (riid == IID_IUnknown) | |
| punk = static_cast<IUnknown*>(this); | |
| else if (riid == IID_IDataObject) | |
| punk = static_cast<IDataObject*>(this); | |
| *ppv = punk; | |
| if (punk) | |
| { | |
| punk->AddRef(); | |
| return S_OK; | |
| } | |
| else | |
| return E_NOINTERFACE; | |
| } | |
| ULONG CDataObject::AddRef() | |
| { | |
| return ++m_cRef; | |
| } | |
| ULONG CDataObject::Release() | |
| { | |
| ULONG cRef = --m_cRef; | |
| if (cRef == 0) | |
| delete this; | |
| return cRef; | |
| } | |
| HRESULT CDataObject::GetData(FORMATETC * pformatetcIn, STGMEDIUM * pmedium) | |
| { | |
| if (pformatetcIn->cfFormat == m_formatFileDescriptorW) | |
| { | |
| FILEGROUPDESCRIPTORW fgd = {}; | |
| fgd.cItems = 1; | |
| wcscpy_s(fgd.fgd[0].cFileName, m_filename); | |
| fgd.fgd[0].nFileSizeLow = (DWORD)m_data.size(); | |
| fgd.fgd[0].dwFlags = FD_FILESIZE | FD_UNICODE; | |
| pmedium->tymed = TYMED_HGLOBAL; | |
| pmedium->hGlobal = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, sizeof(FILEGROUPDESCRIPTORW)); | |
| HGLOBAL pcontents = GlobalLock(pmedium->hGlobal); | |
| memcpy(pcontents, &fgd, sizeof(FILEGROUPDESCRIPTORW)); | |
| GlobalUnlock(pmedium->hGlobal); | |
| return S_OK; | |
| } | |
| else if (pformatetcIn->cfFormat == m_formatFileContents) | |
| { | |
| pmedium->tymed = TYMED_HGLOBAL; | |
| pmedium->hGlobal = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, m_data.size()); | |
| HGLOBAL pcontents = GlobalLock(pmedium->hGlobal); | |
| memcpy(pcontents, m_data.data(), m_data.size()); | |
| GlobalUnlock(pmedium->hGlobal); | |
| return S_OK; | |
| } | |
| return DV_E_FORMATETC; | |
| } | |
| HRESULT CDataObject::QueryGetData(FORMATETC * pformatetc) | |
| { | |
| if (pformatetc->cfFormat == m_formatFileDescriptorW || pformatetc->cfFormat == m_formatFileContents) | |
| return S_OK; | |
| return S_FALSE; | |
| } | |
| HRESULT CDataObject::EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC ** ppenumFormatEtc) | |
| { | |
| if (dwDirection == DATADIR_GET) | |
| { | |
| FORMATETC rgfe[2] = {}; | |
| rgfe[0].cfFormat = m_formatFileDescriptorW; | |
| rgfe[0].tymed = TYMED_HGLOBAL; | |
| rgfe[0].lindex = -1; | |
| rgfe[0].dwAspect = DVASPECT_CONTENT; | |
| rgfe[1].cfFormat = m_formatFileContents; | |
| rgfe[1].tymed = TYMED_HGLOBAL; | |
| rgfe[1].lindex = 0; | |
| rgfe[1].dwAspect = DVASPECT_CONTENT; | |
| return SHCreateStdEnumFmtEtc(2, rgfe, ppenumFormatEtc); | |
| } | |
| return S_FALSE; | |
| } | |
| void PutVirtualFileIntoClipboard() | |
| { | |
| // For binary data containing null bytes: | |
| //const std::vector<uint8_t> data = { 'A', 'B', '\0', 'D', 'E' }; | |
| const std::string str = "[InternetShortcut]\r\nURL=https://www.google.com/\r\n"; | |
| const std::vector<uint8_t> data(str.begin(), str.end()); | |
| IDataObject * dataObject = new CDataObject(L"Google.url", data); | |
| if (dataObject) | |
| { | |
| OleSetClipboard(dataObject); | |
| dataObject->Release(); | |
| } | |
| } | |
| void StartVirtualFileDragDropOperation() | |
| { | |
| const std::string str = "[InternetShortcut]\r\nURL=https://www.google.com/\r\n"; | |
| const std::vector<uint8_t> data(str.begin(), str.end()); | |
| IDataObject * dataObject = new CDataObject(L"Google.url", data); | |
| if (dataObject) | |
| { | |
| DWORD dwEffect; | |
| SHDoDragDrop(NULL, dataObject, NULL, DROPEFFECT_COPY, &dwEffect); | |
| dataObject->Release(); | |
| } | |
| } | |
| void OnLButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags) | |
| { | |
| //PutVirtualFileIntoClipboard(); | |
| StartVirtualFileDragDropOperation(); | |
| } | |
| void OnDestroy(HWND hwnd) | |
| { | |
| PostQuitMessage(0); | |
| } | |
| /* | |
| * Window procedure | |
| */ | |
| LRESULT CALLBACK WndProc(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam) | |
| { | |
| switch (uiMsg) | |
| { | |
| HANDLE_MSG(hwnd, WM_LBUTTONDOWN, OnLButtonDown); | |
| HANDLE_MSG(hwnd, WM_DESTROY, OnDestroy); | |
| } | |
| return DefWindowProcW(hwnd, uiMsg, wParam, lParam); | |
| } | |
| BOOL InitApp(void) | |
| { | |
| WNDCLASSW wc; | |
| wc.style = 0; | |
| wc.lpfnWndProc = WndProc; | |
| wc.cbClsExtra = 0; | |
| wc.cbWndExtra = 0; | |
| wc.hInstance = g_hinst; | |
| wc.hIcon = NULL; | |
| wc.hCursor = LoadCursor(NULL, IDC_ARROW); | |
| wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); | |
| wc.lpszMenuName = NULL; | |
| wc.lpszClassName = WINDOW_CLASS; | |
| if (!RegisterClassW(&wc)) | |
| return FALSE; | |
| return TRUE; | |
| } | |
| int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpCmdLine, int nShowCmd) | |
| { | |
| MSG msg; | |
| HWND hwnd; | |
| g_hinst = hinst; | |
| if (!InitApp()) | |
| return 0; | |
| if (SUCCEEDED(OleInitialize(NULL))) | |
| { | |
| hwnd = CreateWindowW( | |
| WINDOW_CLASS, /* Class Name */ | |
| L"VirtualFileDrop", /* Title */ | |
| WS_OVERLAPPEDWINDOW, /* Style */ | |
| CW_USEDEFAULT, CW_USEDEFAULT, /* Position */ | |
| 320, 240, /* Size */ | |
| NULL, /* Parent */ | |
| NULL, /* No menu */ | |
| hinst, /* Instance */ | |
| 0 | |
| ); | |
| ShowWindow(hwnd, nShowCmd); | |
| while (GetMessageW(&msg, NULL, 0, 0)) | |
| { | |
| TranslateMessage(&msg); | |
| DispatchMessageW(&msg); | |
| } | |
| OleUninitialize(); | |
| } | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment