第 ⑨ 章 · 预览处理器(IPreviewHandler)

在资源管理器预览窗格里渲染文件——它跑在独立宿主 prevhost.exe 里(靠 AppID 隔离);讲 SetWindow/DoPreview/Unload、IObjectWithSite 与三处注册。

作者 David更新于

预览处理器负责资源管理器右侧「预览窗格」(Alt+P)里的内容。它是本指南里最“重”的一种:跑在独立宿主进程 prevhost.exe 里,要自己管一个窗口。配套代码:09-preview。

三个接口协作

  • IInitializeWithStream —— 拿文件流。
  • IPreviewHandler —— 核心:SetWindow 告诉你“画在哪个父窗口、哪块矩形”,DoPreview 里你在那建子窗口渲染,SetRect 跟随窗格大小变化,Unload 收尾。
  • IObjectWithSite —— 宿主设置的站点(预览处理器约定要实现)。
PreviewHandler.cpp(节选)
IFACEMETHODIMP PreviewHandler::DoPreview() {
    m_hwndPreview = CreateWindowExW(0, L"EDIT", nullptr,
        WS_CHILD | WS_VISIBLE | WS_VSCROLL | ES_MULTILINE | ES_READONLY | ES_AUTOVSCROLL,
        m_rc.left, m_rc.top, m_rc.right - m_rc.left, m_rc.bottom - m_rc.top,
        m_hwndParent, nullptr, g_hInst, nullptr);
    // 从 m_pStream 读最多 64KB,MultiByteToWideChar(CP_UTF8,…) 后 SetWindowText
    return S_OK;
}
IFACEMETHODIMP PreviewHandler::Unload() {
    if (m_hwndPreview) { DestroyWindow(m_hwndPreview); m_hwndPreview = nullptr; }
    if (m_pStream) { m_pStream->Release(); m_pStream = nullptr; }
    return S_OK;
}

几个生命周期铁律:SetWindow 可能在 DoPreview 前后多次调用(窗格移动/缩放),要能随时把子窗口 MoveWindow 到新矩形;Unload 必须销毁窗口、释放流,因为同一个预览宿主会被复用来预览下一个文件。

隔离宿主:AppID

预览处理器不直接跑在资源管理器里,而是被托管进 prevhost.exe——这样即使你的渲染代码崩了,倒的也只是预览宿主,资源管理器安然无恙。实现方式是给 CLSID 挂一个 AppID,指向系统预置的预览宿主:

// 64 位预览宿主 prevhost.exe 的 AppID(DllSurrogate 系统已注册好)
RegWriteString(HKEY_CLASSES_ROOT, L"CLSID\\{我们的CLSID}", L"AppID",
               L"{534A1E02-D58F-44f0-B58B-36CBED287C7C}");

三处注册

CLSID\{CLSID}\  AppID = {534A1E02-...}            ← 用哪个隔离宿主
HKLM\...\PreviewHandlers\  (值名={CLSID}) = 显示名  ← 登记进系统清单
.myp\shellex\{8895b1c6-b41f-4c1c-a562-0d564250836f}\  (默认)={CLSID}  ← 绑定文件类型

Windows 内置了大量文档/图片的预览处理器。写自己的通常是为了私有格式。调试时可以直接给 prevhost.exe 挂调试器(它是独立进程),比调资源管理器省心得多。

参考

本页目录