# 第 ⑨ 章 · 预览处理器（IPreviewHandler）

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

- 作者：David（道雾轩）
- 专栏：Windows Shell 扩展指南（https://daiw.org/manual/windows-shell-ext.md）
- 最后更新：2026-07-12
- 原文：https://daiw.org/manual/windows-shell-ext/preview
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

**预览处理器**负责资源管理器右侧「预览窗格」（`Alt+P`）里的内容。它是本指南里最“重”的一种：**跑在独立宿主进程 `prevhost.exe`** 里，要自己管一个窗口。配套代码：[`09-preview`](https://github.com/davidapp/shell-extension-demos/tree/main/09-preview)。

## 三个接口协作

- **`IInitializeWithStream`** —— 拿文件流。
- **`IPreviewHandler`** —— 核心：`SetWindow` 告诉你“画在哪个父窗口、哪块矩形”，`DoPreview` 里你在那建子窗口渲染，`SetRect` 跟随窗格大小变化，`Unload` 收尾。
- **`IObjectWithSite`** —— 宿主设置的站点（预览处理器约定要实现）。

```cpp title="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;
}
```

<Callout type="warn">
  几个生命周期铁律：**`SetWindow` 可能在 `DoPreview` 前后多次调用**（窗格移动/缩放），要能随时把子窗口 `MoveWindow` 到新矩形；**`Unload` 必须销毁窗口、释放流**，因为同一个预览宿主会被复用来预览下一个文件。
</Callout>

## 隔离宿主：AppID

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

```cpp
// 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}  ← 绑定文件类型
```

<Callout type="info">
  Windows 内置了大量文档/图片的预览处理器。写自己的通常是为了**私有格式**。调试时可以直接给 `prevhost.exe` 挂调试器（它是独立进程），比调资源管理器省心得多。
</Callout>

## 参考

- [IPreviewHandler](https://learn.microsoft.com/zh-cn/windows/win32/api/shobjidl_core/nn-shobjidl_core-ipreviewhandler) · [预览处理器](https://learn.microsoft.com/zh-cn/windows/win32/shell/preview-handlers)
- [RecipePreviewHandler 官方示例](https://github.com/microsoft/Windows-classic-samples/tree/main/Samples/Win7Samples/winui/shell/appshellintegration/RecipePreviewHandler)
