Preview handler仅在从资源管理器调用应用时正常工作的问题
问题描述
我编写了一个简易应用程序,可创建窗口并调用预览处理程序(Preview handler)展示作为启动参数传入的文件预览,实现代码附在文末。
程序在以下场景运行完全正常:将待预览文件拖拽到.exe文件上,或在代码中硬编码文件路径后直接从资源管理器启动.exe。
但在以下场景会出现故障:从终端(PowerShell、CMD等)通过类似./main.exe testfile.docx的命令调用程序时,几乎所有预览处理程序都无法正常工作,从其他进程调用程序时也存在相同故障。
故障涉及的预览处理程序(包含但不限于)
- 适用于Excel文件的MS Office预览处理程序,CLSID
{00020827-0000-0000-C000-000000000046} - 适用于Word文件的MS Office预览处理程序,CLSID
{84F66100-FF7C-4fb4-B0C0-02CD7FB668FE} - 适用于PDF文件的Edge预览处理程序,CLSID
{3A84F9C2-6164-485C-A7D9-4B27F8AC009E} - 适用于PowerPoint文件的MS Office预览处理程序,CLSID
{65235197-874B-4A07-BDC5-E65EA825B718}:该处理程序调用IInitializeWithFile接口执行初始化时失败,返回错误码hresult 0x86420003
可正常工作的预览处理程序
- Windows自带的文本文件预览处理程序,CLSID
{1531d583-8375-4d3f-b5fb-d23bbd169f22} - Windows自带的HTML文件预览处理程序,CLSID
{f8b8412b-dea3-4130-b36c-5e8be73106ac}
故障表现
此处描述的“失败”指程序无任何函数调用报错,但预览内容无法正常展示,效果对比见截图:
- 将test.xlsx文件拖拽到.exe上,或硬编码test.xlsx路径后从资源管理器启动应用时,预览正常显示:

- 在PowerShell中执行
./main.exe ./test.xlsx将test.xlsx路径作为命令行参数传入时,预览无法显示:
目前暂不清楚该问题的诱发原因,也无从确定排查方向,希望能得到相关的解决思路或建议。
注:数天前曾提出过一个相关问题,但属于独立的其他问题,链接:https://stackoverflow.com/questions/72440183/how-to-properly-use-and-instantiate-existing-preview-handlers
编译与运行说明
- 编译命令:
cl /std:c++20 /EHsc main.cpp - 程序使用要求:将待预览文件路径作为首个命令行参数传入
- 运行环境要求:代码使用了部分WinRT特性,需为Windows 10及以上版本
问题根因
故障由两个核心问题共同导致:
- 相对路径跨进程无效:Office、Edge等第三方预览处理程序均实现为外进程COM服务器(Out-of-process COM),运行在独立的进程空间中,不会继承调用进程的当前工作目录。从终端启动时传入的是相对路径,预览处理程序在自身进程的工作目录下查找文件,自然无法定位目标文件。拖拽文件到exe启动时,资源管理器会自动传入文件的绝对路径,因此不存在该问题。Windows自带的文本、HTML预览处理程序实现为进程内COM服务器(In-process COM),会直接加载到调用进程内运行,复用调用进程的工作目录,因此相对路径也能正常工作。PowerPoint预览返回的
0x86420003错误本质就是文件查找失败。 - COM初始化不符合Shell扩展要求:代码中使用
CoInitialize(nullptr)初始化COM,虽然默认进入单线程套间(STA),但未添加COINIT_DISABLE_OLE1DDE标志,Office、Edge的预览处理程序对COM运行环境有严格校验,缺少该标志会导致预览渲染流程异常,出现无报错但内容空白的现象。另外代码中硬编码CLSCTX_LOCAL_SERVER创建COM实例,强制要求外进程运行,会直接排除所有实现为进程内组件的预览处理程序,兼容性不足。
修复方案
针对上述问题做三处修改即可:
- 拿到命令行传入的文件路径后,先转换为绝对路径再传入预览处理程序,可直接使用标准库
std::filesystem::absolute()实现,也可调用Win32 APIGetFullPathNameW获取完整路径。 - 将COM初始化逻辑从
CoInitialize(nullptr)替换为CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE),匹配Shell扩展的运行环境要求。 - 创建预览处理程序实例时,将类上下文参数改为
CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER,由系统自动选择合适的组件运行上下文,提升兼容性。
关键修改点代码示例
// 1. 修改COM初始化逻辑 // 原代码:winrt::check_hresult(CoInitialize(nullptr)); winrt::check_hresult(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)); // 2. 处理传入路径为绝对路径 // 原代码:std::filesystem::path filePath(argv[1]); std::filesystem::path filePath = std::filesystem::absolute(std::filesystem::path(argv[1])); // 3. 修改COM实例创建上下文 // 原代码:mPreviewHandler.capture(CoCreateInstance, previewHandlerId, nullptr, CLSCTX_LOCAL_SERVER); mPreviewHandler.capture(CoCreateInstance, previewHandlerId, nullptr, CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER);
原始实现代码
#pragma comment(lib, "Shlwapi.lib") #pragma comment(lib, "user32.lib") #pragma comment(lib, "WindowsApp.lib") #pragma comment(lib, "Ole32.lib") #include <iostream> #include <string> #include <string_view> #include <array> #include <filesystem> #include <Windows.h> #include <shlwapi.h> #include <winrt/base.h> #include <ShObjIdl.h> class Preview{ winrt::com_ptr<IPreviewHandler> mPreviewHandler; bool mIsActive = false; bool mIsInitialized = false; std::filesystem::path mFilePath; HWND mHwnd; RECT mRect; public: Preview(const std::filesystem::path& filePath, const HWND hwnd, const RECT& rect): mFilePath(filePath), mHwnd(hwnd), mRect(rect){ createPreviewHandler(); initialize(); }; void setRect(const RECT& rect){ mPreviewHandler->SetRect(&rect); mRect = rect; } void setWindow(const HWND hwnd, const RECT& rect){ mPreviewHandler->SetWindow(hwnd, &rect); mHwnd = hwnd; setRect(rect); } void startPreview(){ if(!mIsActive){ if(!mIsInitialized){ initialize(); } mPreviewHandler->DoPreview(); setRect(mRect); mIsActive = true; } } void stopPreview(){ if(mIsActive){ mPreviewHandler->Unload(); mIsActive = false; mIsInitialized = false; } } private: void createPreviewHandler(){ CLSID previewHandlerId = getShellExClsidForType(mFilePath.extension()); mPreviewHandler.capture(CoCreateInstance, previewHandlerId, nullptr, CLSCTX_LOCAL_SERVER); } CLSID getShellExClsidForType(const std::wstring& extension){ winrt::hresult res; DWORD size; std::array<wchar_t, 39> interfaceIdWstr; size = StringFromGUID2(IID_IPreviewHandler, interfaceIdWstr.data(), interfaceIdWstr.size()); if(!size){ winrt::throw_hresult(HRESULT_FROM_WIN32(GetLastError())); } std::array<wchar_t, 39> exIdWstr; res = AssocQueryStringW(ASSOCF_INIT_DEFAULTTOSTAR, ASSOCSTR_SHELLEXTENSION, extension.c_str(), interfaceIdWstr.data(), exIdWstr.data(), &size); winrt::check_hresult(res); CLSID exId; res = IIDFromString(exIdWstr.data(), &exId); winrt::check_hresult(res); return(exId); } void initialize(){ initializeFromPath(mFilePath); setWindow(mHwnd, mRect); mIsInitialized = true; } void initializeFromPath(const std::filesystem::path& filePath){ if(!initWithFile(filePath) && !initWithStream(filePath)){ winrt::throw_hresult(E_NOINTERFACE); } } bool initWithFile(const std::filesystem::path& filePath){ if(!mPreviewHandler.try_as<IInitializeWithFile>()){ return(false); } winrt::check_hresult(mPreviewHandler.as<IInitializeWithFile>()->Initialize(filePath.c_str(), STGM_READ)); return(true); } bool initWithStream(const std::filesystem::path& filePath){ if(!mPreviewHandler.try_as<IInitializeWithStream>()){ return(false); } winrt::com_ptr<IStream> stream; stream.capture([](LPCWSTR pszFile, DWORD grfMode, REFIID riid, void **ppstm) {return(SHCreateStreamOnFileEx(pszFile, grfMode, 0, false, nullptr, reinterpret_cast<IStream**>(ppstm)));}, filePath.c_str(), STGM_READ | STGM_SHARE_DENY_WRITE | STGM_FAILIFTHERE); winrt::check_hresult(mPreviewHandler.as<IInitializeWithStream>()->Initialize(stream.get(), STGM_READ)); return(true); } }; HMODULE getCurrentModule(){ HMODULE hModule; GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCTSTR)getCurrentModule, &hModule); return(hModule); } LRESULT windowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam){ LPARAM ret = 0; switch(msg){ case WM_CLOSE: { PostQuitMessage(0); ret = 0; }break; default: { ret = DefWindowProc(hwnd, msg, wParam, lParam); }break; } return(ret); } HWND createTestWindow(){ WNDCLASSW wndClass; wndClass.style = 0; wndClass.lpfnWndProc = windowProc; wndClass.cbClsExtra = 0; wndClass.cbWndExtra = 0; wndClass.hInstance = getCurrentModule(); wndClass.hIcon = nullptr; wndClass.hCursor = nullptr; wndClass.hbrBackground = nullptr; wndClass.lpszMenuName = nullptr; wndClass.lpszClassName = L"test"; ATOM wndAtom = RegisterClassW(&wndClass); if(!wndAtom){ winrt::throw_hresult(HRESULT_FROM_WIN32(GetLastError())); } HWND window = CreateWindowExW(0, L"Test", L"", WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, wndClass.hInstance, nullptr); if(!window){ winrt::throw_hresult(HRESULT_FROM_WIN32(GetLastError())); } ShowWindow(window, SW_NORMAL); return(window); } int main(int argc, char *argv[]){ try{ winrt::check_hresult(CoInitialize(nullptr)); if(!SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE)){ winrt::throw_hresult(E_FAIL); } if(argc != 2){ return(1); } std::filesystem::path filePath(argv[1]); HWND window = createTestWindow(); RECT rect; GetClientRect(window, &rect); Preview preview(filePath, window, rect); preview.startPreview(); MSG msg; while(GetMessageW(&msg, nullptr, 0, 0) != 0){ TranslateMessage(&msg); DispatchMessage(&msg); } }catch(winrt::hresult_error err){ std::wcout << "0x" << std::hex << err.code() << " " << static_cast<std::wstring_view>(err.message()); }catch(...){} }
内容的提问来源于stack exchange,提问作者lulle2007200
相关产品推荐
相关产品推荐

