如何将文件作为OLE对象复制到剪贴板,实现第三方应用插入功能?
我开发的插件所在的第三方应用支持在文档中插入OLE对象,想复现/自动化从Windows资源管理器复制文件并粘贴到该应用的功能——比如复制.doc文件时,应用会将其作为Word OLE对象插入。
目前已能获取文件名,并成功将其作为OLE"Package"插入,但遇到瓶颈:OLE相关函数的文档极少。推测需要向剪贴板添加特定信息来识别OLE对象类型,而这个类型应该可以通过文件扩展名推导出来。
以下是从网上示例中获取的现有代码:
if (AfxOleInit()) { CComPtr<IDataObject> spdto; if (SUCCEEDED(GetUIObjectOfFile(nullptr, m_sFileName, IID_PPV_ARGS(&spdto))) && SUCCEEDED(OleSetClipboard(spdto)) && SUCCEEDED(OleFlushClipboard())) { // success } } //------------------------------------------------------------------------------ HRESULT GetUIObjectOfFile(HWND hwnd, LPCWSTR pszPath, REFIID riid, void** ppv) //------------------------------------------------------------------------------ { *ppv = NULL; HRESULT hr; LPITEMIDLIST pidl; SFGAOF sfgao; //printf_s("%s", pszPath); if (SUCCEEDED(hr = SHParseDisplayName(pszPath, NULL, &pidl, 0, &sfgao))) { IShellFolder* psf; LPCITEMIDLIST pidlChild; if (SUCCEEDED(hr = SHBindToParent(pidl, IID_IShellFolder, (void**)&psf, &pidlChild))) { hr = psf->GetUIObjectOf(hwnd, 1, &pidlChild, riid, NULL, ppv); psf->Release(); } CoTaskMemFree(pidl); } return hr; }
你的思路正确:剪贴板需要包含对应文件类型的OLE对象信息,而非仅通用的Package容器。当前代码通过GetUIObjectOfFile获取的IDataObject仅包含文件基础剪贴板格式,未关联到对应OLE服务器程序,因此被识别为Package。
要实现和资源管理器复制一致的效果,需按以下步骤调整:
核心步骤
通过文件扩展名获取关联OLE的CLSID
使用AssocQueryString从文件扩展名获取关联ProgID,再通过CLSIDFromProgID得到对应CLSID(比如.doc对应Word.Document.8的CLSID)。从文件创建对应类型的OLE对象
使用OleCreateFromFile直接从目标文件创建对应类型的OLE对象,而非通过Shell的GetUIObjectOfFile获取基础数据。将OLE对象数据写入剪贴板
从OLE对象获取IDataObject接口,通过OleSetClipboard和OleFlushClipboard写入剪贴板,确保目标应用识别为对应类型的OLE对象。
修改后代码示例
#include <Shlwapi.h> #include <Ole2.h> #pragma comment(lib, "Shlwapi.lib") #pragma comment(lib, "Ole32.lib") HRESULT CopyFileAsSpecificOLEObject(LPCWSTR pszFilePath) { HRESULT hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); if (FAILED(hr)) return hr; // 1. 获取文件关联的ProgID WCHAR szProgID[MAX_PATH] = {0}; hr = AssocQueryStringW(ASSOCF_INIT_DEFAULTTOSTAR, ASSOCSTR_PROGID, pszFilePath, nullptr, szProgID, nullptr); if (FAILED(hr)) { CoUninitialize(); return hr; } // 2. 从ProgID获取CLSID CLSID clsidOle; hr = CLSIDFromProgIDW(szProgID, &clsidOle); if (FAILED(hr)) { CoUninitialize(); return hr; } // 3. 从文件创建对应类型的OLE对象 CComPtr<IOleObject> spOleObj; hr = OleCreateFromFile(clsidOle, pszFilePath, IID_IOleObject, OLERENDER_DRAW, nullptr, nullptr, (void**)&spOleObj); if (FAILED(hr)) { CoUninitialize(); return hr; } // 4. 设置宿主信息(可选,但部分OLE服务器需要) hr = spOleObj->SetHostNames(L"PluginHost", L"OLEContainer"); if (FAILED(hr)) { CoUninitialize(); return hr; } // 5. 获取IDataObject并写入剪贴板 CComPtr<IDataObject> spDataObj; hr = spOleObj->QueryInterface(IID_IDataObject, (void**)&spDataObj); if (SUCCEEDED(hr)) { hr = OleSetClipboard(spDataObj); if (SUCCEEDED(hr)) { OleFlushClipboard(); } } CoUninitialize(); return hr; }
注意事项
- 确保目标文件类型已正确注册OLE服务器(比如安装了Microsoft Word才能处理.doc文件)
- 必须使用单线程公寓(STA)模型初始化COM(
COINIT_APARTMENTTHREADED),OLE对象操作依赖该模型 - 若
AssocQueryString获取ProgID失败,可尝试直接指定已知ProgID(比如硬编码Word.Document.8处理.doc)
内容的提问来源于stack exchange,提问作者Kyudos

