如何获取与文件类型关联的图标路径和索引?

时间:2016-09-07 15:01:05

标签: windows shell icons

鉴于文件类型(例如.txt),我如何获得:

  • 路径
  • 索引

到文件类型的相关图标路径和索引,例如:

我想将.txt转换为:

  • 路径:%SystemRoot%\ system32 \ imageres.dll
  • 索引: - 102

有了这些信息,我就可以提取图标(例如使用SHDefExtractIcon)。

背景

Windows中的每种类型的文件都在注册表中注册。当图标与文件关联时,会将其指定为包含图标的文件的路径,以及图标资源的索引(如果索引为负,则指定为资源ID)。

.txt文件为例,关联的 DefaultIcon 为:

%SystemRoot%\system32\imageres.dll,-102

ExtractAssociatedIcon

首先是Win API函数ExtractAssociatedIcon

  

检索文件中找到的索引图标的句柄或相关可执行文件中的图标。

这个想法是你传递路径和索引,然后它会为你获取图标:

String iconPath = "%SystemRoot%\system32\imageres.dll";
Word iIcon = -102;

HICON ico = ExtractAssociatedIcon(0, iconPath, iIcon);

仅当您已经知道所需图标的路径和索引时才有效。

幸运的是, ExtractAssociatedIcon 还可以告诉您您的图标文件的路径和索引:

  

如果该函数无法从该文件获取图标句柄,并且该文件具有关联的可执行文件,则会在该可执行文件中查找图标。

在这种情况下正确调用函数有点棘手,因为它会修改你提供的缓冲区(如果你没有将缓冲区填充到足够长的时间,导致缓冲区溢出):

String iconPath = "C:\Example.txt" + StringOfChar(\0, 32767); //pad the InOut buffer
Word iIcon = 0;

HICON ico = ExtractAssociatedIcon(0, iconPath, iIcon);
DestroyIcon(ico);

当函数返回时:

  • iconPath %SystemRoot%\system32\imageres.dll
  • iIcon : - 102

为什么我没有使用ExtractAssociatedIcon返回的HICON?因为ExtratAssociatedIcon不允许我指定我想要的Icon的大小。它返回" Shell大图标"就是这样。

此外,ExtractAssociatedIcon可以执行它的唯一方法是按文件类型查找,即文件实际是否存在。如果指定的文件不存在(它没有 - 因为没有foo.txt),则该函数失败。

SHDefExtractIcon

输入SHDefExtractIcon。它is able to extract any size of icon i want,你只需要传递图标资源的路径索引

String iconFile = "%SystemRoot%\system32\imageres.dll";
Int32 iIndex = -102;
HICON hLargeIcon;

if (SHDefExtractIcon(iconFile, iIndex, 0, out hLargeIcon, null, 256) == S_OK)
   return hLargeIcon

唯一的问题是我必须为文件类型获取关联的路径索引。与 ExtractAssociatedIcon 不同, SHDefExtractIcon 不会为您执行英雄查找。

为此我必须自己进行查找;这是我的问题。

Buggy Registry Spelunking

我的第一次尝试是read the contract of file associations from the other side。我知道如何注册默认图标,我可以反过来。

  • .ext转换为关联的ProgID

    HKEY_CLASSES_ROOT/.ext
        (default) = [ProgID]
    
  • DefaultIcon

    下查找[ProgID]
    HKEY_CLASSES_ROOT/[progID]/DefaultIcon
        (default) = [path],[index]
    

就我而言:

HKEY_CLASSES_ROOT/.txt
   (default) = txtfile

HEKY_CLASSES_ROOT/txtfile/DefaultIcon
   (default) = "%SystemRoot%\system32\imageres.dll,-102"

这是this accepted Stackoverflow背后的代码使用的方法回答同一个问题:

  • 将ext转换为progID
  • 查找progID的DefaultIcon密钥

假设存在ext,并且progID存在,并且DefaultIcon存在,并且路径存在,并且我可以解析路径,则它是不正确的不支持的答案。有一些边缘情况,接受的代码不处理 1

我希望Windows API 支持的方式执行从.ext

的映射
  • 路径
  • 索引

的SHGetFileInfo

有一个方便的功能SHGetFileInfo。它很方便,因为the filename doesn't need to actually exist。如果你传递SHGFI_USEFILEATTRIBUTES标志,则表示:

  

不要访问磁盘。假设文件/目录存在,并且其文件属性是我作为dwFileAttributes参数传递的内容。无论它是否确实存在,都要这样做。

这很好:

SHELLFILEINFO sfi;
DWORD res = SHGetFileInfo("foo.txt", 
      FILE_ATTRIBUTE_NORMAL,
      ref shellFileInfo,
      sizeof(shellFileInfo),
      SHGFI_ICON | SHGFI_LARGEICON | SHGFI_SHELLICONSIZE | SHGFI_USEFILEATTRIBUTES);

if (res <> 0)
   return shellFileInfo.hIcon;

唯一的问题是我无法指定我想要的图标大小。我仅限于 shell 决定想要使用的图标大小。

IExtractImage

IExtractImage很不错:

  • 它可以返回相关图标的[path],[index]
  • 我可以指定所需的尺寸

不幸的是,它需要一个实际存在的文件(它必须是shell命名空间中存在的东西)。当我有文件类型时,我不能使用 IExtractImage

IThumbnailProvider

Windows Vista引入的

IThumbnailProvider IExtractImage 的现代替代品:

  

Windows Vista IThumbnailProivder是Vista的新功能,取代了IExtractImage。 Vista仍然支持IExtractImage,但缺乏返回图像类型(alpha或不是)的能力。

IThumbnailProvider还允许我提供所需的图标大小。优良!

IThumbnailProvider 通常需要在shell命名空间中存在一个文件。但这仅仅是因为Shell API是唯一受支持的方式来获取ahold(&#34; bind&#34;)到由文件类型公开的 IThumbnailProvider shell接口。

幸运的是,我可以执行上面使用的相同可怕的黑客攻击,并手动抓取注册表:

HKEY_CLASSES_ROOT/.ext/ShellEx/[InterfaceID]
   (default) = [ClassID]

如果它不存在:

HKEY_CLASSES_ROOT/.ext
   (default) = [ProgID]

HKEY_CLASSES_ROOT/[ProgID]/ShellEx/[InterfaceID]
   (default) = [ClassID]

对于我的.avi文件:

HKEY_CLASSES_ROOT/.avi/ShellEx/{e357fccd-a995-4576-b01f-234630154e96}
   (default) = "{9DBD2C50-62AD-11D0-B806-00C04FD706EC}"

现在我带着CLSID来参加比赛!

不幸的是,它会在那里结束,因为 IThumbnailProvider 需要一个文件。更确切地说,它需要IInitializeWithStream

我没有流。我没有档案。我只有文件类型的概念。

AssocQueryString

也许AssocQueryString可以帮助我?我实际上并不知道 - 它是一个功能的野兽。我无法做出正面或反面。

问题

鉴于文件类型(例如&#34; x.txt&#34;),我如何获得相关图标:

  • 路径
  • 索引

所以我可以提取我想要的大小的图标(可能使用SHDefExtractIcon)?

脚注

1 exefile和%1

1 个答案:

答案 0 :(得分:0)

答案是我非常接近:

  • 使用带有SHGetFileInfo标记的SHGFI_ICONLOCATION获取默认图标路径索引
  • 使用SHDefExtractIcon以所需尺寸路径索引提取默认图标

或以函数形式:

HICON GetFileTypeDefaultIcon(String filename, Int32 iconSizePx)
{
    //Filename is anything like "a.txt", "foo.xml", "x.zip"
    //The file doesn't have to exist, but it can't be an invalid 
    //filename (e.g. "???.txt" is no good)

    //Use SHGetFileInfo to get the path and index of our file type's icon
    SHFILEINFO sfi;

    //SHGFI_IconLocation means get me the path and icon index
    //SHGFI_UseFileAttributes means the file doesn't have to exist
    DWORD_PTR res := SHGetFileInfo(
            filename,
            FILE_ATTRIBUTE_NORMAL,
            ref sfi,
            sizeof(sfi),
            SHGFI_ICONLOCATION or SHGFI_USEFILEATTRIBUTES);

   if (res = 0) //"nonzero if successful"
      return 0;

   //The path and index are stuffed into the ShellFileInfo structure
   String iconPath := sfi.szDisplayName;
   Int32 iconIndex := sfi.iIcon;

   //Now that we know the path and index, we can use SHDefExtractIcon
   HICON largeIcon;
   iconSizePx = iconSizePx and 0xFFFF; //preferred large icon size is in LOWORD 16-bits

   HRESULT hr := SHDefExtractIcon(iconPath, iconIndex, 0, 
      out largeIcon, null, iconSizePx); 
   if (hr <> S_OK) 
      return 0;

   return largeIcon;
}

停止对shell对象进行拼写

我也figured out that there is a 20 year old Windows function可以执行对shell扩展的注册表的爬网。它还处理我错过的案例 - 因为它是规范正确的方式。

仅记录和解释shell类:

文件类型包含 ShellEx 键,其中包含{guid}个子键。每个{guid}键代表一个特定的 InterfaceID

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

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

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

HKEY_CLASSES_ROOT/.jpg/ShellEx/{e357fccd-a995-4576-b01f-234630154e96}
   (default) = [clsid]

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

HKEY_CLASSES_ROOT/.jpg
   (default) = jpgfile
HKEY_CLASSES_ROOT/jpgfile/ShellEx/{e357fccd-a995-4576-b01f-234630154e96}
   (default) = [clsid]

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

HKEY_CLASSES_ROOT/SystemFileAssociations/.jpg/ShellEx/{e357fccd-a995-4576-b01f-234630154e96}
   (default) = [clsid] 

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

HKEY_CLASSES_ROOT/SystemFileAssociations/jpegfile/ShellEx/{e357fccd-a995-4576-b01f-234630154e96}
   (default) = [clsid]

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

HKEY_CLASSES_ROOT/SystemFileAssociations/image/ShellEx/{e357fccd-a995-4576-b01f-234630154e96}
   (default) = [clsid]

我是如何找到这些地点的?我是否只关注记录和支持的位置?不,我在探索资源管理器时使用了Process Monitor,因为它正在寻找 IThumbnailProvider

所以现在我想自己使用标准shell接口作为文件类型。这意味着我必须抓取位置。但是,为什么要以无证件,不受支持的方式抓取这些位置。为什么会在the guy之上从the thing引发愤怒?使用AssocQueryString

Guid GetShellClassIDForFileType(String fileExtension, Guid interfaceID)
{
    //E.g.:
    //   String fileExtension = ".jpg"
    //   Guid   interfaceID   = "{BB2E617C-0920-11d1-9A0B-00C04FC2D6C1}"; //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
    SetLength(buffer, bufferSize);

    HRESULT hr := AssocQueryString(
          ASSOCF_INIT_DEFAULTTOSTAR, 
          ASSOCSTR_SHELLEXTENSION, //for finding shell extensions
          fileExtension, //e.g. ".txt"
          szInterfaceID, //e.g. "{BB2E617C-0920-11d1-9A0B-00C04FC2D6C1}"
          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;
}