通过Windows API从文件中获取C#缩略图

90

Windows资源管理器有显示文件缩略图的功能,这些缩略图是由核心和第三方shell扩展提供的。

我知道如何扩展shell以提供缩略图给Windows。

我的目标是使用C#通过shell从系统中检索任何文件的缩略图。这是否可行?

实际上,我正在编写一个自定义文件浏览器,我想显示缩略图,但不可能解析每个文件来制作自己的缩略图。

澄清一下: 很多答案似乎都集中在网页缩略图或调整图像大小上。但这完全不是我要找的东西。我想要的是询问 Windows 关于这些文件类型的缩略图:.DOC、.PDF、.3DM、.DWG……或许还有十几个左右。我不想自己解析、渲染和制作缩略图,因为 Windows 已经知道该怎么做了。

我发布的代码实际上是有效的……或许可以简化和优化一下。


请澄清您需要文件的缩略图而不是文件类型(扩展名)的图标。您不想要“如何使用SHGetFileInfo函数获取与文件相关联的图标”。 - AMissico
根据已接受的答案澄清 - Brian Gillespie 想要 Shell 缩略图像而不是 Shell 图标像。 - AMissico
5个回答

101

3
太棒了,非常感谢......我一直在看上面的代码,想着这真的必须那么难吗。 效果很好!+1 - Lightweight
1
@Lightweight:是的,这很困难,因为发帖者表示他们不想使用第三方扩展,并且他们特别想使用Shell。 - AMissico
2
使用微软的API代码包是一个更简单的解决方案。我改变了我的接受的解决方案。 - Brian Gillespie
不错的东西。这让我的生活变得更容易了。 - Elan Hasson
5
可以使用流而不是映射路径来完成吗? - Sam Jones
显示剩余3条评论

19

Microsoft Office Thumbnails in SharePoint (http://msdn.microsoft.com/en-us/library/aa289172(VS.71).aspx)是您想要的内容。我使用http://www.developerfusion.com/tools/convert/vb-to-csharp/,将VB.NET代码转换成C#时没有遇到问题。

关于您在答案中发布的代码,Thumbnail Extraction Using the Shell (http://www.vbaccelerator.com/home/net/code/libraries/Shell_Projects/Thumbnail_Extraction/article.asp)是代码原始来源。由于命名约定、注释等因素从原始来源继承下来,我认为几乎所有搜索到的IExtractImage示例都基于该文章的代码。由于该文章出自2003年4月,因此包含了一些非标准(非.NET)的编码约定。我进行了一些基本测试,发现存在垃圾回收问题、未释放管理资源以及其他清理问题。因此,我强烈建议避免使用该文章中的代码。此外,该代码的结构使得维护变得更加困难。

有一个干净、简单的版本可在MSDN上找到,称为Microsoft Office Thumbnails in SharePoint,日期为2005年7月,链接为http://msdn.microsoft.com/en-us/library/aa289172(VS.71).aspx。该代码和文章中的代码具有相似之处,这使我相信Thumbnail Extraction Using the Shell文章是SharePoint文章的基础。VB.NET版本的GetThumbnailImage忽略了longestEdge参数,但C++版本使用它并记录了ORIGSIZEQUALITY标志的使用。此外,该代码演示了如何使用.NET的FreeCoTaskMem,而不是Shell的IMallocSHGetMalloc

IExtractImage 用于处理文件、文件夹和其他命名空间对象。MSDN代码可用于处理隐藏文件,而vbAccelerator代码需要在 EnumObjects 调用中添加 SHCONTF_INCLUDEHIDDEN。此外,vbAccelerator枚举shell文件夹的对象以查找指定的文件,这似乎是浪费时间的。这可能是为了找到用于 GetUIObjectOf 调用的正确“相对”PIDL。


ShellThumbnail (正在进行中)

完整的示例项目位于http://cid-7178d2c79ba0a7e3.office.live.com/self.aspx/.Public/ShellThumbnail.zip

Imports System.Runtime.InteropServices

Namespace Shell

    ''' <summary>
    ''' Generates a thumbnail of a folder's picture or a file's image.
    ''' </summary>
    ''' <remarks>This is the "Folder's Picture" and not the "Folder's Icon"! Use SHGetFileInfo to generate the thumbnail for a folder's icon or for a file that does not have a thumbnail handler.</remarks>
    ''' <reference>Microsoft Office Thumbnails in SharePoint at http://msdn.microsoft.com/en-us/library/aa289172%28VS.71%29.aspx.</reference>
    Public Class ShellThumbnail

        'TODO - Work out the details for image size and IEIFLAG handling.

#Region " Determining Thumbnail Size and Quality [documentation] "

        'http://support.microsoft.com/kb/835823
        'Determining Thumbnail Size and Quality
        'Browse to HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer. Create or modify two DWORDs called ThumbnailSize and ThumbnailQuality. For ThumbnailSize set the value in pixels, with the default being 96. For ThumbnailQuality set the value as a number that represents the percentage quality between 50 and 100.


        'http://www.pctools.com/guides/registry/detail/1066/ (modified)
        '  User Key: [HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer]
        'System Key: [HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Explorer]
        'Value Name: ThumbnailSize, ThumbnailQuality
        ' Data Type: REG_DWORD (DWORD Value)
        'Value Data: Size in pixels (32-255), Quality Percentage (50-100)


        'Microsoft® Windows® XP Registry Guide 
        'Jerry Honeycutt 
        '09/11/2002 
        'Microsoft Press
        'http://www.microsoft.com/mspress/books/sampchap/6232.aspx#118
        '<H3><I><A name=118></A>Thumbnails</I></H3>The Thumbnails category controls the 
        'quality of thumbnails in Windows Explorer. Table 5-10 describes the values for 
        'Image Quality and Size. Create values that you don't see in the registry. The 
        'default value for <CODE>ThumbnailQuality</CODE> is <CODE>0x5A</CODE>. The 
        'default value for <CODE>ThumbnailSize</CODE> is <CODE>0x60</CODE>. Keep in mind 
        'that higher quality and larger thumbnails require more disk space, which is not 
        'usually a problem, but they also take longer to display. Changing the quality 
        'does not affect thumbnails that already exist on the file system.
        '<P><B>Table 5-10 </B><I>Values in Thumbnails</I>
        '<P>
        '<TABLE border=0 cellSpacing=1 cellPadding=4 width="100%">
        '<TBODY>
        '<TR>
        '<TD bgColor=#999999 vAlign=top><B>Setting</B></TD>
        '<TD bgColor=#999999 vAlign=top><B>Name</B></TD>
        '<TD bgColor=#999999 vAlign=top><B>Type</B></TD>
        '<TD bgColor=#999999 vAlign=top><B>Data</B></TD></TR>
        '<TR>
        '<TD bgColor=#cccccc 
        'vAlign=top><CODE><B>HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer</B></CODE></TD>
        '<TD bgColor=#cccccc vAlign=top> </TD>
        '<TD bgColor=#cccccc vAlign=top> </TD>
        '<TD bgColor=#cccccc vAlign=top> </TD></TR>
        '<TR>
        '<TD bgColor=#cccccc vAlign=top><CODE>Image Quality</CODE></TD>
        '<TD bgColor=#cccccc vAlign=top><CODE>ThumbnailQuality</CODE></TD>
        '<TD bgColor=#cccccc vAlign=top><CODE>REG_DWORD</CODE></TD>
        '<TD bgColor=#cccccc vAlign=top><CODE>0x32 - 0x64</CODE></TD></TR>
        '<TR>
        '<TD bgColor=#cccccc vAlign=top><CODE>Size (pixels)</CODE></TD>
        '<TD bgColor=#cccccc vAlign=top><CODE>ThumbnailSize</CODE></TD>
        '<TD bgColor=#cccccc vAlign=top><CODE>REG_DWORD</CODE></TD>
        '<TD bgColor=#cccccc vAlign=top><CODE>0x20 - 0xFF</CODE></TD></TR></TBODY></TABLE></P>

#End Region

        Public Shared ReadOnly DefaultThumbnailSize As New System.Drawing.Size(96, 96)

        Public Const DefaultColorDepth As Integer = 32

        ''' <summary>
        ''' Used to request an image from an object, such as an item in a Shell folder.
        ''' </summary>
        ''' <param name="path">An absolute path to a file or folder.</param>
        Public Shared Function ExtractImage(ByVal path As String) As Bitmap
            Return ExtractImage(path, System.Drawing.Size.Empty, DefaultColorDepth, 0)
        End Function

        ''' <summary>
        ''' Used to request an image from an object, such as an item in a Shell folder.
        ''' </summary>
        ''' <param name="path">An absolute path to a file or folder.</param>
        ''' <param name="size"></param>
        Public Shared Function ExtractImage(ByVal path As String, ByVal size As System.Drawing.Size) As Bitmap
            Return ExtractImage(path, size, DefaultColorDepth, 0)
        End Function

        ''' <summary>
        ''' Used to request an image from an object, such as an item in a Shell folder.
        ''' </summary>
        ''' <param name="path">An absolute path to a file or folder.</param>
        ''' <param name="size"></param>
        ''' <param name="recommendedColorDepth">The recommended color depth in units of bits per pixel. The default is 32.</param>
        Public Shared Function ExtractImage(ByVal path As String, ByVal size As System.Drawing.Size, ByVal recommendedColorDepth As Integer) As Bitmap
            Return ExtractImage(path, size, recommendedColorDepth, 0)
        End Function

        ''' <summary>
        ''' Used to request an image from an object, such as an item in a Shell folder.
        ''' </summary>
        ''' <param name="path">An absolute path to a file or folder.</param>
        ''' <param name="size"></param>
        ''' <param name="recommendedColorDepth">The recommended color depth in units of bits per pixel. The default is 32.</param>
        Private Shared Function ExtractImage(ByVal path As String, ByVal size As System.Drawing.Size, ByVal recommendedColorDepth As Integer, ByVal flags As IEIFLAG) As Bitmap
            Dim oResult As Bitmap = Nothing

            Dim oDesktopFolder As IShellFolder = Nothing
            Dim hParentIDL As IntPtr
            Dim hIDL As IntPtr

            Dim oParentFolder As IShellFolder
            Dim hParentFolder As IntPtr
            Dim oExtractImage As IExtractImage
            Dim hExtractImage As IntPtr

            'Divide the file name into a path and file/folder name.
            Dim sFolderName As String = System.IO.Path.GetDirectoryName(path)
            Dim sBaseName As String = System.IO.Path.GetFileName(path)

            'Get the desktop IShellFolder.
            If SHGetDesktopFolder(oDesktopFolder) <> Missico.Win32.S_OK Then
                Throw New System.Runtime.InteropServices.COMException
            End If

            'Get the parent folder for the specified path.
            oDesktopFolder.ParseDisplayName(IntPtr.Zero, IntPtr.Zero, sFolderName, 0, hParentIDL, 0)
            oDesktopFolder.BindToObject(hParentIDL, IntPtr.Zero, ShellGUIDs.IID_IShellFolder, hParentFolder)
            oParentFolder = CType(Marshal.GetTypedObjectForIUnknown(hParentFolder, GetType(IShellFolder)), IShellFolder)

            'Get the file/folder's IExtractImage
            oParentFolder.ParseDisplayName(IntPtr.Zero, IntPtr.Zero, sBaseName, 0, hIDL, 0)
            oParentFolder.GetUIObjectOf(IntPtr.Zero, 1, New IntPtr() {hIDL}, ShellGUIDs.IID_IExtractImage, IntPtr.Zero, hExtractImage)

            'Free the pidls. The Runtime Callable Wrappers (RCW) should automatically release the COM objects.
            Marshal.FreeCoTaskMem(hParentIDL)
            Marshal.FreeCoTaskMem(hIDL)

            Marshal.FinalReleaseComObject(oParentFolder)
            Marshal.FinalReleaseComObject(oDesktopFolder)

            If hExtractImage = IntPtr.Zero Then

                'There is no handler for this file, which is odd. I believe we should default the file's type icon.
                Debug.WriteLine(String.Format("There is no thumbnail for the specified file '{0}'.", path), "ShellThumbnail.ExtractImage")

            Else

                oExtractImage = CType(Marshal.GetTypedObjectForIUnknown(hExtractImage, GetType(IExtractImage)), IExtractImage)

                'Set the size and flags
                Dim oSize As Missico.Win32.SIZE 'must specify a size
                Dim iFlags As IEIFLAG = flags Or IEIFLAG.IEIFLAG_ORIGSIZE Or IEIFLAG.IEIFLAG_QUALITY Or IEIFLAG.IEIFLAG_ASPECT

                If size.IsEmpty Then

                    oSize.cx = DefaultThumbnailSize.Width
                    oSize.cy = DefaultThumbnailSize.Height

                Else

                    oSize.cx = size.Width
                    oSize.cy = size.Height

                End If


                Dim hBitmap As IntPtr
                Dim sPath As New System.Text.StringBuilder(Missico.Win32.MAX_PATH, Missico.Win32.MAX_PATH)


                oExtractImage.GetLocation(sPath, sPath.Capacity, 0, oSize, recommendedColorDepth, iFlags)

                'if the specified path is to a folder then IExtractImage.Extract fails.

                Try

                    oExtractImage.Extract(hBitmap)

                Catch ex As System.Runtime.InteropServices.COMException

                    'clear the handle since extract failed
                    hBitmap = IntPtr.Zero

                    Debug.WriteLine(String.Format("There is no thumbnail for the specified folder '{0}'.", path), "ShellThumbnail.ExtractImage")

                Finally

                    Marshal.FinalReleaseComObject(oExtractImage)

                End Try

                If Not hBitmap.Equals(IntPtr.Zero) Then

                    'create the image from the handle
                    oResult = System.Drawing.Bitmap.FromHbitmap(hBitmap)

                    'dump the properties to determine what kind of bitmap is returned
                    'Missico.Diagnostics.ObjectDumper.Write(oResult)

                    'Tag={ }
                    'PhysicalDimension={Width=96, Height=96}
                    'Size={Width=96, Height=96}
                    'Width=96
                    'Height=96
                    'HorizontalResolution=96
                    'VerticalResolution=96
                    'Flags=335888
                    'RawFormat={ }
                    'PixelFormat=Format32bppRgb
                    'Palette={ }
                    'FrameDimensionsList=...
                    'PropertyIdList=...
                    'PropertyItems=...

                    Missico.Win32.DeleteObject(hBitmap) 'release the handle

                End If

            End If

            Return oResult
        End Function

    End Class

End Namespace

AMissico - 谢谢你的回答。虽然我还没有测试过你的代码,但是你的推理似乎很有道理,因此我会接受你的答案作为正确答案,而不是我在EE上找到的那个答案。 - Brian Gillespie
1
另外,我已经将您的项目与vbaccelerator上的其他代码进行了测试。您的执行速度略快。(关于内存问题,我会相信您的话。) - dthrasher
2
@dthrasher,我前几天从FTP日志中注意到该项目每周会被下载几次。在我完成一个个人项目后,我计划创建一个测试项目并将代码发布到CodeProject或CodePlex。 - AMissico
1
@AMissico - 你发布了文章或者设置项目了吗?上面的链接已经失效了。 - Giorgi
@Giorgi:我已经移动了我的FTP服务器,但还没有时间完成移动。请发送电子邮件至itisme_fred@yahoo.com,我会回复您的项目。 - AMissico
显示剩余2条评论

3
这里有一个我在互联网上找到的类。看起来原始代码来自http://www.experts-exchange.com/Programming/Languages/C_Sharp/Q_21789724.html,但我无法查看以便适当归属。我在这里找到了源代码:http://www.vbforums.com/showthread.php?t=527704 这是一个具有正确COM调用的类,为了记录在此重现:

using System;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;

namespace RMA.Shell
{
  public class ShellThumbnail : IDisposable
  {

    [Flags]
    private enum ESTRRET
    {
      STRRET_WSTR = 0,
      STRRET_OFFSET = 1,
      STRRET_CSTR = 2
    }

    [Flags]
    private enum ESHCONTF
    {
      SHCONTF_FOLDERS = 32,
      SHCONTF_NONFOLDERS = 64,
      SHCONTF_INCLUDEHIDDEN = 128,
    }

    [Flags]
    private enum ESHGDN
    {
      SHGDN_NORMAL = 0,
      SHGDN_INFOLDER = 1,
      SHGDN_FORADDRESSBAR = 16384,
      SHGDN_FORPARSING = 32768
    }

    [Flags]
    private enum ESFGAO
    {
      SFGAO_CANCOPY = 1,
      SFGAO_CANMOVE = 2,
      SFGAO_CANLINK = 4,
      SFGAO_CANRENAME = 16,
      SFGAO_CANDELETE = 32,
      SFGAO_HASPROPSHEET = 64,
      SFGAO_DROPTARGET = 256,
      SFGAO_CAPABILITYMASK = 375,
      SFGAO_LINK = 65536,
      SFGAO_SHARE = 131072,
      SFGAO_READONLY = 262144,
      SFGAO_GHOSTED = 524288,
      SFGAO_DISPLAYATTRMASK = 983040,
      SFGAO_FILESYSANCESTOR = 268435456,
      SFGAO_FOLDER = 536870912,
      SFGAO_FILESYSTEM = 1073741824,
      SFGAO_HASSUBFOLDER = -2147483648,
      SFGAO_CONTENTSMASK = -2147483648,
      SFGAO_VALIDATE = 16777216,
      SFGAO_REMOVABLE = 33554432,
      SFGAO_COMPRESSED = 67108864,
    }

    private enum EIEIFLAG
    {
      IEIFLAG_ASYNC = 1,
      IEIFLAG_CACHE = 2,
      IEIFLAG_ASPECT = 4,
      IEIFLAG_OFFLINE = 8,
      IEIFLAG_GLEAM = 16,
      IEIFLAG_SCREEN = 32,
      IEIFLAG_ORIGSIZE = 64,
      IEIFLAG_NOSTAMP = 128,
      IEIFLAG_NOBORDER = 256,
      IEIFLAG_QUALITY = 512
    }

    [StructLayout(LayoutKind.Sequential, Pack = 4, Size = 0, CharSet = CharSet.Auto)]
    private struct STRRET_CSTR
    {
      public ESTRRET uType;
      [MarshalAs(UnmanagedType.ByValArray, SizeConst = 520)]
      public byte[] cStr;
    }

    [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Auto)]
    private struct STRRET_ANY
    {
      [FieldOffset(0)]
      public ESTRRET uType;
      [FieldOffset(4)]
      public IntPtr pOLEString;
    }
    [StructLayoutAttribute(LayoutKind.Sequential)]
    private struct SIZE
    {
      public int cx;
      public int cy;
    }

    [ComImport(), Guid("00000000-0000-0000-C000-000000000046")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IUnknown
    {

      [PreserveSig()]
      IntPtr QueryInterface(ref Guid riid, ref IntPtr pVoid);

      [PreserveSig()]
      IntPtr AddRef();

      [PreserveSig()]
      IntPtr Release();
    }

    [ComImportAttribute()]
    [GuidAttribute("00000002-0000-0000-C000-000000000046")]
    [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IMalloc
    {

      [PreserveSig()]
      IntPtr Alloc(int cb);

      [PreserveSig()]
      IntPtr Realloc(IntPtr pv, int cb);

      [PreserveSig()]
      void Free(IntPtr pv);

      [PreserveSig()]
      int GetSize(IntPtr pv);

      [PreserveSig()]
      int DidAlloc(IntPtr pv);

      [PreserveSig()]
      void HeapMinimize();
    }

    [ComImportAttribute()]
    [GuidAttribute("000214F2-0000-0000-C000-000000000046")]
    [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IEnumIDList
    {

      [PreserveSig()]
      int Next(int celt, ref IntPtr rgelt, ref int pceltFetched);

      void Skip(int celt);

      void Reset();

      void Clone(ref IEnumIDList ppenum);
    }

    [ComImportAttribute()]
    [GuidAttribute("000214E6-0000-0000-C000-000000000046")]
    [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IShellFolder
    {

      void ParseDisplayName(IntPtr hwndOwner, IntPtr pbcReserved,
        [MarshalAs(UnmanagedType.LPWStr)]string lpszDisplayName,
        ref int pchEaten, ref IntPtr ppidl, ref int pdwAttributes);

      void EnumObjects(IntPtr hwndOwner,
        [MarshalAs(UnmanagedType.U4)]ESHCONTF grfFlags,
        ref IEnumIDList ppenumIDList);

      void BindToObject(IntPtr pidl, IntPtr pbcReserved, ref Guid riid,
        ref IShellFolder ppvOut);

      void BindToStorage(IntPtr pidl, IntPtr pbcReserved, ref Guid riid, IntPtr ppvObj);

      [PreserveSig()]
      int CompareIDs(IntPtr lParam, IntPtr pidl1, IntPtr pidl2);

      void CreateViewObject(IntPtr hwndOwner, ref Guid riid,
        IntPtr ppvOut);

      void GetAttributesOf(int cidl, IntPtr apidl,
        [MarshalAs(UnmanagedType.U4)]ref ESFGAO rgfInOut);

      void GetUIObjectOf(IntPtr hwndOwner, int cidl, ref IntPtr apidl, ref Guid riid, ref int prgfInOut, ref IUnknown ppvOut);

      void GetDisplayNameOf(IntPtr pidl,
        [MarshalAs(UnmanagedType.U4)]ESHGDN uFlags,
        ref STRRET_CSTR lpName);

      void SetNameOf(IntPtr hwndOwner, IntPtr pidl,
        [MarshalAs(UnmanagedType.LPWStr)]string lpszName,
        [MarshalAs(UnmanagedType.U4)] ESHCONTF uFlags,
        ref IntPtr ppidlOut);
    }
    [ComImportAttribute(), GuidAttribute("BB2E617C-0920-11d1-9A0B-00C04FC2D6C1"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IExtractImage
    {
      void GetLocation([Out(), MarshalAs(UnmanagedType.LPWStr)]
        StringBuilder pszPathBuffer, int cch, ref int pdwPriority, ref SIZE prgSize, int dwRecClrDepth, ref int pdwFlags);

      void Extract(ref IntPtr phBmpThumbnail);
    }

    private class UnmanagedMethods
    {

      [DllImport("shell32", CharSet = CharSet.Auto)]
      internal extern static int SHGetMalloc(ref IMalloc ppMalloc);

      [DllImport("shell32", CharSet = CharSet.Auto)]
      internal extern static int SHGetDesktopFolder(ref IShellFolder ppshf);

      [DllImport("shell32", CharSet = CharSet.Auto)]
      internal extern static int SHGetPathFromIDList(IntPtr pidl, StringBuilder pszPath);

      [DllImport("gdi32", CharSet = CharSet.Auto)]
      internal extern static int DeleteObject(IntPtr hObject);

    }

    ~ShellThumbnail()
    {
      Dispose();
    }

    private IMalloc alloc = null;
    private bool disposed = false;
    private Size _desiredSize = new Size(100, 100);
    private Bitmap _thumbNail;

    public Bitmap ThumbNail
    {
      get
      {
        return _thumbNail;
      }
    }

    public Size DesiredSize
    {
      get { return _desiredSize; }
      set { _desiredSize = value; }
    }
    private IMalloc Allocator
    {
      get
      {
        if (!disposed)
        {
          if (alloc == null)
          {
            UnmanagedMethods.SHGetMalloc(ref alloc);
          }
        }
        else
        {
          Debug.Assert(false, "Object has been disposed.");
        }
        return alloc;
      }
    }

    public Bitmap GetThumbnail(string fileName)
    {
      if (string.IsNullOrEmpty(fileName))
        return null;

      if (!File.Exists(fileName) && !Directory.Exists(fileName))
      {
        throw new FileNotFoundException(string.Format("The file '{0}' does not exist", fileName), fileName);
      }
      if (_thumbNail != null)
      {
        _thumbNail.Dispose();
        _thumbNail = null;
      }
      IShellFolder folder = null;
      try
      {
        folder = getDesktopFolder;
      }
      catch (Exception ex)
      {
        throw ex;
      }
      if (folder != null)
      {
        IntPtr pidlMain = IntPtr.Zero;
        try
        {
          int cParsed = 0;
          int pdwAttrib = 0;
          string filePath = Path.GetDirectoryName(fileName);
          folder.ParseDisplayName(IntPtr.Zero, IntPtr.Zero, filePath, ref cParsed, ref pidlMain, ref pdwAttrib);
        }
        catch (Exception ex)
        {
          Marshal.ReleaseComObject(folder);
          throw ex;
        }
        if (pidlMain != IntPtr.Zero)
        {
          Guid iidShellFolder = new Guid("000214E6-0000-0000-C000-000000000046");
          IShellFolder item = null;
          try
          {
            folder.BindToObject(pidlMain, IntPtr.Zero, ref iidShellFolder, ref item);
          }
          catch (Exception ex)
          {
            Marshal.ReleaseComObject(folder);
            Allocator.Free(pidlMain);
            throw ex;
          }
          if (item != null)
          {
            IEnumIDList idEnum = null;
            try
            {
              item.EnumObjects(IntPtr.Zero, (ESHCONTF.SHCONTF_FOLDERS | ESHCONTF.SHCONTF_NONFOLDERS), ref idEnum);
            }
            catch (Exception ex)
            {
              Marshal.ReleaseComObject(folder);
              Allocator.Free(pidlMain);
              throw ex;
            }
            if (idEnum != null)
            {
              int hRes = 0;
              IntPtr pidl = IntPtr.Zero;
              int fetched = 0;
              bool complete = false;
              while (!complete)
              {
                hRes = idEnum.Next(1, ref pidl, ref fetched);
                if (hRes != 0)
                {
                  pidl = IntPtr.Zero;
                  complete = true;
                }
                else
                {
                  if (_getThumbNail(fileName, pidl, item))
                  {
                    complete = true;
                  }
                }
                if (pidl != IntPtr.Zero)
                {
                  Allocator.Free(pidl);
                }
              }
              Marshal.ReleaseComObject(idEnum);
            }
            Marshal.ReleaseComObject(item);
          }
          Allocator.Free(pidlMain);
        }
        Marshal.ReleaseComObject(folder);
      }
      return ThumbNail;
    }

    private bool _getThumbNail(string file, IntPtr pidl, IShellFolder item)
    {
      IntPtr hBmp = IntPtr.Zero;
      IExtractImage extractImage = null;
      try
      {
        string pidlPath = PathFromPidl(pidl);
        if (Path.GetFileName(pidlPath).ToUpper().Equals(Path.GetFileName(file).ToUpper()))
        {
          IUnknown iunk = null;
          int prgf = 0;
          Guid iidExtractImage = new Guid("BB2E617C-0920-11d1-9A0B-00C04FC2D6C1");
          item.GetUIObjectOf(IntPtr.Zero, 1, ref pidl, ref iidExtractImage, ref prgf, ref iunk);
          extractImage = (IExtractImage)iunk;
          if (extractImage != null)
          {
            Console.WriteLine("Got an IExtractImage object!");
            SIZE sz = new SIZE();
            sz.cx = DesiredSize.Width;
            sz.cy = DesiredSize.Height;
            StringBuilder location = new StringBuilder(260, 260);
            int priority = 0;
            int requestedColourDepth = 32;
            EIEIFLAG flags = EIEIFLAG.IEIFLAG_ASPECT | EIEIFLAG.IEIFLAG_SCREEN;
            int uFlags = (int)flags;
            try
            {
              extractImage.GetLocation(location, location.Capacity, ref priority, ref sz, requestedColourDepth, ref uFlags);
              extractImage.Extract(ref hBmp);
            }
            catch (System.Runtime.InteropServices.COMException ex)
            {

            }
            if (hBmp != IntPtr.Zero)
            {
              _thumbNail = Bitmap.FromHbitmap(hBmp);
            }
            Marshal.ReleaseComObject(extractImage);
            extractImage = null;
          }
          return true;
        }
        else
        {
          return false;
        }
      }
      catch (Exception ex)
      {
        if (hBmp != IntPtr.Zero)
        {
          UnmanagedMethods.DeleteObject(hBmp);
        }
        if (extractImage != null)
        {
          Marshal.ReleaseComObject(extractImage);
        }
        throw ex;
      }
    }

    private string PathFromPidl(IntPtr pidl)
    {
      StringBuilder path = new StringBuilder(260, 260);
      int result = UnmanagedMethods.SHGetPathFromIDList(pidl, path);
      if (result == 0)
      {
        return string.Empty;
      }
      else
      {
        return path.ToString();
      }
    }

    private IShellFolder getDesktopFolder
    {
      get
      {
        IShellFolder ppshf = null;
        int r = UnmanagedMethods.SHGetDesktopFolder(ref ppshf);
        return ppshf;
      }
    }

    public void Dispose()
    {
      if (!disposed)
      {
        if (alloc != null)
        {
          Marshal.ReleaseComObject(alloc);
        }
        alloc = null;
        if (_thumbNail != null)
        {
          _thumbNail.Dispose();
        }
        disposed = true;
      }
    }
  }
}

5
要查看EE页面,请复制URL,转到Google搜索它,点击结果,向下滚动即可找到答案。他们应该被禁止在Google上出现... - corymathews
如果您安装了Google工具栏,请右键单击所涉及的域中的页面,然后选择“页面信息”->“缓存的页面快照”,以查看它们在Google爬行时提供给Google的内容。 - Matthew Lock
1
这是一种非常复杂的使用GDI+的C#方式,因为C#包装器已经在DotNet框架中提供了GDI+。 - Eran Betzalel
我认为 http://www.vbaccelerator.com/home/net/code/libraries/Shell_Projects/Thumbnail_Extraction/article.asp 上的“使用Shell提取缩略图”是代码来源的原始文章。我觉得代码看起来很熟悉,因为枚举的命名方式很不寻常,例如EIEIFLAG。它的日期是2003年4月。 - AMissico
以下是一个更简洁的版本,发布于2005年7月。位于http://msdn.microsoft.com/en-us/library/aa289172(VS.71).aspx的_Microsoft Office Thumbnails in SharePoint_。这段代码与文章中的代码有相似之处。(看起来_Shell使用缩略图提取_文章是SharePoint文章的基础。) - AMissico
显示剩余2条评论

1

虽然不完全是你所要求的,但这里有一个打开Thumbs.db文件的项目
维基百科文章提供了更多信息,可以告诉你在Vista和7中找到缩略图的位置。

以下是获取图像的代码:

public byte[] GetThumbData(string filename)
{
    IStorageWrapper wrapper = new IStorageWrapper(_thumbDBFile, false);
    foreach(CatalogItem catItem in _catalogItems)
    {
        if (catItem.filename == filename)
        {
            string streamName = BuildReverseString(catItem.itemID);
            FileObject fileObject = wrapper.OpenUCOMStream(null, streamName);
            byte[] rawJPGData = new byte [fileObject.Length];
            fileObject.Read(rawJPGData, 0, (int)fileObject.Length);
            fileObject.Close();

            // 3 ints of header data need to be removed
            // Don't know what first int is.
            // 2nd int is thumb index
            // 3rd is size of thumbnail data.
            byte[] jpgData = new byte[rawJPGData.Length - 12];
            for (int index = 12; index < jpgData.Length; index++)
            {
                jpgData[index - 12] = rawJPGData[index];
            }
            return jpgData;
        }
    }
    return null;
}

public Image GetThumbnailImage(string filename)
{
    byte[] thumbData = GetThumbData(filename);
    if (null == thumbData)
    {
        return null;
    }
    MemoryStream ms = new MemoryStream(thumbData);
    Image img = Image.FromStream(ms);
    return img;
}

链接已经失效,但是有人知道缩略图缓存数据库的加密密码吗? - ThomasRones
修复了链接。似乎没有密码;我在我的答案中添加了适用的代码。 - hemisphire
在上面的例子中,_catalogItems是什么?它来自哪里? - sairfan
私有的 ArrayList _catalogItems = new ArrayList(); 来自 ThumbDB.cs https://web.archive.org/web/20070319113459/http://www.petedavis.net/downloads/thumbdblib.zip - hemisphire
你如何指定图像的大小(宽度/高度)? - Gray Programmerz
你无法指定要打开的图像的大小,因为它已经设置好了。如果你想知道这些值怎么获取,img.Width和img.Height将填充它们。 - hemisphire

1

感谢@Christian Nunciato,我想解释得更清楚:

在Nuget控制台中输入以下命令安装WindowsAPICodePack-Shell

Install-Package WindowsAPICodePack-Shell -Version 1.1.1

首先,你需要知道这个this很酷的解决方案需要你在服务器上安装相关程序,例如,如果你想要shell为PDF文件生成缩略图,则应该在服务器上安装一个PDF阅读器,如Acrobat Reader或Foxit PhantomPDF等。这意味着服务器将为它识别的文件创建缩略图,这很有意义。

其次,检查哪些文件扩展名已经生成了缩略图,换句话说,检查一下.psd或.msi文件扩展名是否有适当的缩略图,然后防止请求服务器为它们生成缩略图(在客户端和服务器端都要检查)。

第三,您可能需要通过Ajax获取缩略图,个人建议您在第一秒基于文件类型为文件提供一些默认缩略图,然后请求服务器生成缩略图,在这种情况下,您可以使用以下代码片段(我将以Base64格式接收生成的位图作为实际上是字符串的图像):

服务器端代码:

[WebMethod]
public static string GetThumbnail(string path) {

    string thumb = string.Empty;

    path = @"D:\\" + path.Replace("/", "\\"); //Or Server.MapPath("/UploadedFiles");
    if (File.Exists(path))
    {
        try
        {
            Microsoft.WindowsAPICodePack.Shell.ShellFile shellFile = Microsoft.WindowsAPICodePack.Shell.ShellFile.FromFilePath(path);
            System.Drawing.Bitmap shellThumb = shellFile.Thumbnail.ExtraLargeBitmap;
            MemoryStream ms = new MemoryStream();
            shellThumb.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);//Jpeg is preferred, Png would make a bigger file size
            thumb = Convert.ToBase64String(ms.ToArray());
        }
        catch(Exception ex)
        {

        }
    }
    return thumb;
}

Client side code:

    var url = "Default.aspx/GetThumbnail",
        path = "Images/1.jpg",//For instance
        extension = path.split(".").pop().toLowerCase(),
        allowedExtensions = ["jpg", "jpeg", "png", "gif", "pdf", "mp4"];
    
    if (allowedExtensions.includes(extension)){
        $.ajax({
            type: 'POST',
            contentType: "application/json; charset=utf-8",
            url: url,
            data: "{'path':'" + path + "'}",
            async: true,
            xhrFields: {
                withCredentials: true
            },
            success: function (response) {
                if (!$.isEmptyObject(response.d))
                    $(elem).find("img").attr("src", "data:image/jpg;base64," + response.d);
            },
            error: function (err) {
                console.error(err);
            }
        });
    }

请注意,由于返回的字符串可能是一个很长的文本,您可能会遇到以下错误:

在使用JSON JavaScriptSerializer进行序列化或反序列化期间发生错误。字符串的长度超过了maxJsonLength属性base64设置的值。

在这种情况下,您需要在web.config文件中添加以下代码:

  <system.web.extensions>
       <scripting>
           <webServices>
               <jsonSerialization maxJsonLength="2147483647"/>
           </webServices>
       </scripting>
   </system.web.extensions>

maxJsonLength是一个整数值,可以为Int32.MaxValue,您可以在这里阅读更多信息。


这个在你的Windows 10或Windows Server 2016上工作吗?对我来说,只有在Windows 7上才能得到docx和pdf的预期结果。对于图像没有问题。 - Frau Schmidt
我使用上述代码在Windows Server 2016数据中心创建了一种文档管理系统(DMS),因此它可以正常工作。 - Muhammad Musavi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接