9

如何获取IPreviewHandler特定文件扩展名的外壳?

背景

Windows 允许开发人员为其自定义文件类型创建预览处理程序:

Preview handlers are called when an item is selected to show a lightweight, rich, read-only preview of the file's contents in the view's reading pane. 这是在不启动文件的关联应用程序的情况下完成的。

预览处理程序是托管应用程序。主机包括 Windows Vista 或 Microsoft Outlook 2007 中的 Windows 资源管理器。

我想利用现有的IPreviewHandler基础设施来获取文件的缩略图。

在流中

问题是我的文件没有放在 shell 命名空间中(即它们没有放在硬盘上)。它们位于内存中,可通过IStream. 这意味着我不能使用旧版IExtractImage界面;因为它不支持从Stream加载文件。

幸运的是,这就是现代IPreviewHandler支持(推荐和更喜欢)从 a 加载数据Stream,并建议不要从文件加载预览的原因:

此方法优于Initialize,因为它能够使用无法通过 Win32 路径访问的流,例如具有 .zip 文件扩展名的压缩文件的内容。

那我怎么得到它?

没有关于获取与特定扩展相关联的正确方法的文档。IPreviewHandler但是,如果我按照如何注册的指示IPreviewHandler并从另一方阅读合同

HKEY_CLASSES_ROOT
  .xyz
     (Default) = xyzfile

HKEY_CLASSES_ROOT
   xyzfile
      shellex
         {8895b1c6-b41f-4c1c-a562-0d564250836f} //IPreviewHandler subkey
             (Default) = [clsid of the IPreviewHandler]

鉴于我知道扩展名,我应该能够遵循相同的路线。让我们用一个真实的例子,一个.jpg文件:

在此处输入图像描述

在此处输入图像描述

请注意,该文件具有预览。请注意,我包含第二个屏幕截图只是为了强调预览不是来自硬盘驱动器上的文件的想法。

让我们拼写吧!

.jpg首先是它是一个文件的事实:

HKEY_CLASSES_ROOT
   .jpg
      (Default) = ACDC_JPG

HKEY_CLASSES_ROOT
   ACDC_JPG
      ShellEx
         {BB2E617C-0920-11d1-9A0B-00C04FC2D6C1}
         ContextMenuHandlers

{8895b1c6-b41f-4c1c-a562-0d564250836f}等等, previewhandler没有子键。这一定意味着我们无法获得.jpg文件的缩略图。

减少荒谬

真正的问题

细心的读者会意识到我要问的实际问题是:

如何获得仅包含在流中的图像的预览?

虽然这是一个有用的问题,也是我遇到的真正问题,但如何使用的答案IPreviewHandler也是一个有用的问题。

因此,请随意回答;或两者!

奖金阅读

4

1 回答 1

6

@hvd 有正确的答案。

文件类型有一个带有子键的ShellEx{guid}键。每个{guid}键代表一个特定的InterfaceID

有许多标准的 shell 接口可以与文件类型相关联:

  • {BB2E617C-0920-11d1-9A0B-00C04FC2D6C1}提取图像
  • {953BB1EE-93B4-11d1-98A3-00C04FB687DA}IExtractImage2
  • {e357fccd-a995-4576-b01f-234630154e96}IThumbnailProvider
  • {8895b1c6-b41f-4c1c-a562-0d564250836f}IPreviewHandler

不支持对未记录的注册表项进行探索

例如,如果我想查找与文件关联的IPreviewHandler的clsid,我会查看:.jpg

HKEY_CLASSES_ROOT/.jpg/ShellEx/{8895b1c6-b41f-4c1c-a562-0d564250836f}
   (default) = [clsid]

但这不是我能看到的唯一地方。我也可以看看:

HKEY_CLASSES_ROOT/.jpg
   (default) = jpgfile
HKEY_CLASSES_ROOT/jpgfile/ShellEx/{8895b1c6-b41f-4c1c-a562-0d564250836f}
   (default) = [clsid]

但这不是我能看到的唯一地方。我也可以看看:

HKEY_CLASSES_ROOT/SystemFileAssociations/.jpg/ShellEx/{8895b1c6-b41f-4c1c-a562-0d564250836f}
   (default) = [clsid] 

但这不是我能看到的唯一地方。我也可以看看:

HKEY_CLASSES_ROOT/SystemFileAssociations/jpegfile/ShellEx/{8895b1c6-b41f-4c1c-a562-0d564250836f}
   (default) = [clsid]

但这不是我能看到的唯一地方。如果我认为该文件是图像,我还可以查看:

HKEY_CLASSES_ROOT/SystemFileAssociations/image/ShellEx/{8895b1c6-b41f-4c1c-a562-0d564250836f}
   (default) = [clsid]

我是如何找到这些位置的?我是否只遵循记录和支持的位置?不,我使用 Process Monitor 监视 Explorer,因为它正在寻找IThumbnailProvider

不要使用无证拼写

所以现在我想自己使用标准的 shell 接口来处理文件类型。这意味着我必须抓取这些位置。但是,为什么要以无证、不受支持的方式抓取这些位置。为什么要惹上那个高高在上家伙的愤怒?使用AssocQueryString

Guid GetShellClsidForFileType(String fileExtension, Guid interfaceID)
{
    //E.g.:
    //   String fileExtension = ".jpg"
    //   Guid   interfaceID   = "{8895b1c6-b41f-4c1c-a562-0d564250836f}"; //IExtractImage

    //The interface we're after - in string form
    String szInterfaceID := GuidToString(interfaceID);

    //Buffer to receive the clsid string
    DWORD bufferSize := 1024; //more than enough to hold a 38-character clsid
    String buffer;
    SetLength(buffer, bufferSize);

    HRESULT hr := AssocQueryString(
          ASSOCF_INIT_DEFAULTTOSTAR, 
          ASSOCSTR_SHELLEXTENSION, //for finding shell extensions
          fileExtension, //e.g. ".txt"
          szInterfaceID, //e.g. "{8895b1c6-b41f-4c1c-a562-0d564250836f}"
          buffer,        //will receive the clsid string
          @bufferSize);
   if (hr <> S_OK) 
      return Guid.Empty;

   Guid clsid;
   HRESULT hr = CLSIDFromString(buffer, out clsid);
   if (hr <> NOERROR) 
      return Guid.Empty;

   return clsid;
}

所以要获取clsid文件:IPreviewHandler.xps

Guid clsid = GetShellClsidForFileType(".xps", IPreviewHandler);

如何获取文件扩展名的 IPreviewHandler?

综上所述,我们现在可以回答这个问题了:

IPreviewHandler GetPreviewHandlerForFileType(String extension)
{
    //Extension: the file type to return IPreviewHandler for (e.g. ".xps")
    Guid previewHandlerClassID = GetShellClsidForFileType(extension, IPreviewHandler);

    //Create the COM object
    IUnknown unk = CreateComObject(previewHandlerClassID);

    //Return the actual IPreviewHanler interface (not IUnknown)
    return (IPreviewhandler)unk;
}
于 2016-09-08T18:32:28.900 回答