tags:

views:

255

answers:

1

I'm writing a program that needs to be able to extract the thumbnail image from a file. I've gotten a hold of a class, ThumbnailCreator, which is able to do this. The source of this Class is below.

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


internal class ThumbnailCreator : IDisposable
{
    // Fields
    private IMalloc alloc;
    private Size desiredSize;
    private bool disposed;
    private static readonly string fileExtention = ".jpg";
    private Bitmap thumbnail;

    // Methods
    public ThumbnailCreator()
    {
        this.desiredSize = new Size(100, 100);
    }

    public ThumbnailCreator(Size desiredSize)
    {
        this.desiredSize = new Size(100, 100);
        this.DesiredSize = desiredSize;
    }



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

    ~ThumbnailCreator()
    {
        this.Dispose();
    }

    private bool getThumbNail(string file, IntPtr pidl, IShellFolder item)
    {
        bool CS;
        IntPtr hBmp = IntPtr.Zero;
        IExtractImage extractImage = null;
        try
        {
            if (Path.GetFileName(PathFromPidl(pidl)).ToUpper().Equals(Path.GetFileName(file).ToUpper()))
            {
                int prgf;
                IUnknown iunk = null;
                Guid iidExtractImage = new Guid("BB2E617C-0920-11d1-9A0B-00C04FC2D6C1");
                item.GetUIObjectOf(IntPtr.Zero, 1, ref pidl, ref iidExtractImage, out prgf, ref iunk);
                extractImage = (IExtractImage) iunk;
                if (extractImage != null)
                {
                    SIZE sz = new SIZE {
                        cx = this.desiredSize.Width,
                        cy = this.desiredSize.Height
                    };
                    StringBuilder location = new StringBuilder(260, 260);
                    int priority = 0;
                    int requestedColourDepth = 0x20;
                    EIEIFLAG flags = EIEIFLAG.IEIFLAG_SCREEN | EIEIFLAG.IEIFLAG_ASPECT;
                    int uFlags = (int) flags;
                    extractImage.GetLocation(location, location.Capacity, ref priority, ref sz, requestedColourDepth, ref uFlags);
                    extractImage.Extract(out hBmp);


                    if (hBmp != IntPtr.Zero)
                    {
                        this.thumbnail = Image.FromHbitmap(hBmp);
                    }
                    Marshal.ReleaseComObject(extractImage);
                    extractImage = null;
                }
                return true;
            }
            CS = false;
        }
        catch (Exception)
        {
            if (hBmp != IntPtr.Zero)
            {
                UnManagedMethods.DeleteObject(hBmp);
            }
            if (extractImage != null)
            {
                Marshal.ReleaseComObject(extractImage);
            }
            throw;
        }
        return CS;
    }

    public Bitmap GetThumbNail(string file)
    {
        if (!File.Exists(file) && !Directory.Exists(file))
        {
            throw new FileNotFoundException(string.Format("The file '{0}' does not exist", file), file);
        }
        if (this.thumbnail != null)
        {
            this.thumbnail.Dispose();
            this.thumbnail = null;
        }
        IShellFolder folder = getDesktopFolder;
        if (folder != null)
        {
            IntPtr pidlMain;
            try
            {
                int cParsed;
                int pdwAttrib;
                string filePath = Path.GetDirectoryName(file);
                folder.ParseDisplayName(IntPtr.Zero, IntPtr.Zero, filePath, out cParsed, out pidlMain, out pdwAttrib);
            }
            catch (Exception)
            {
                Marshal.ReleaseComObject(folder);
                throw;
            }
            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)
                {
                    Marshal.ReleaseComObject(folder);
                    this.Allocator.Free(pidlMain);
                    throw;
                }
                if (item != null)
                {
                    IEnumIDList idEnum = null;
                    try
                    {
                        item.EnumObjects(IntPtr.Zero, ESHCONTF.SHCONTF_NONFOLDERS | ESHCONTF.SHCONTF_FOLDERS, ref idEnum);
                    }
                    catch (Exception)
                    {
                        Marshal.ReleaseComObject(folder);
                        this.Allocator.Free(pidlMain);
                        throw;
                    }
                    if (idEnum != null)
                    {
                        IntPtr pidl = IntPtr.Zero;
                        bool complete = false;
                        while (!complete)
                        {
                            int fetched;
                            if (idEnum.Next(1, ref pidl, out fetched) != 0)
                            {
                                pidl = IntPtr.Zero;
                                complete = true;
                            }
                            else if (this.getThumbNail(file, pidl, item))
                            {
                                complete = true;
                            }
                            if (pidl != IntPtr.Zero)
                            {
                                this.Allocator.Free(pidl);
                            }
                        }
                        Marshal.ReleaseComObject(idEnum);
                    }
                    Marshal.ReleaseComObject(item);
                }
                this.Allocator.Free(pidlMain);
            }
            Marshal.ReleaseComObject(folder);
        }
        return this.thumbnail;
    }

    private static string PathFromPidl(IntPtr pidl)
    {
        StringBuilder path = new StringBuilder(260, 260);
        if (UnManagedMethods.SHGetPathFromIDList(pidl, path) != 0)
        {
            return path.ToString();
        }
        return string.Empty;
    }

    // Properties
    private IMalloc Allocator
    {
        get
        {
            if (!this.disposed && (this.alloc == null))
            {
                UnManagedMethods.SHGetMalloc(out this.alloc);
            }
            return this.alloc;
        }
    }

    public Size DesiredSize
    {
        get
        {
            return this.desiredSize;
        }
        set
        {
            this.desiredSize = value;
        }
    }

    private static IShellFolder getDesktopFolder
    {
        get
        {
            IShellFolder ppshf;
            UnManagedMethods.SHGetDesktopFolder(out ppshf);
            return ppshf;
        }
    }

    public Bitmap ThumbNail
    {
        get
        {
            return this.thumbnail;
        }
    }

    // Nested Types
    private enum EIEIFLAG
    {
        IEIFLAG_ASPECT = 4,
        IEIFLAG_ASYNC = 1,
        IEIFLAG_CACHE = 2,
        IEIFLAG_GLEAM = 0x10,
        IEIFLAG_NOBORDER = 0x100,
        IEIFLAG_NOSTAMP = 0x80,
        IEIFLAG_OFFLINE = 8,
        IEIFLAG_ORIGSIZE = 0x40,
        IEIFLAG_QUALITY = 0x200,
        IEIFLAG_SCREEN = 0x20
    }

    [Flags]
    private enum ESFGAO
    {
        SFGAO_CANCOPY = 1,
        SFGAO_CANDELETE = 0x20,
        SFGAO_CANLINK = 4,
        SFGAO_CANMOVE = 2,
        SFGAO_CANRENAME = 0x10,
        SFGAO_CAPABILITYMASK = 0x177,
        SFGAO_COMPRESSED = 0x4000000,
        SFGAO_CONTENTSMASK = -2147483648,
        SFGAO_DISPLAYATTRMASK = 0xf0000,
        SFGAO_DROPTARGET = 0x100,
        SFGAO_FILESYSANCESTOR = 0x10000000,
        SFGAO_FILESYSTEM = 0x40000000,
        SFGAO_FOLDER = 0x20000000,
        SFGAO_GHOSTED = 0x80000,
        SFGAO_HASPROPSHEET = 0x40,
        SFGAO_HASSUBFOLDER = -2147483648,
        SFGAO_LINK = 0x10000,
        SFGAO_READONLY = 0x40000,
        SFGAO_REMOVABLE = 0x2000000,
        SFGAO_SHARE = 0x20000,
        SFGAO_VALIDATE = 0x1000000
    }

    [Flags]
    private enum ESHCONTF
    {
        SHCONTF_FOLDERS = 0x20,
        SHCONTF_INCLUDEHIDDEN = 0x80,
        SHCONTF_NONFOLDERS = 0x40
    }

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

    [Flags]
    private enum ESTRRET
    {
        STRRET_WSTR,
        STRRET_OFFSET,
        STRRET_CSTR
    }

    [ComImport, Guid("000214F2-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IEnumIDList
    {
        [PreserveSig]
        int Next(int celt, ref IntPtr rgelt, out int pceltFetched);
        void Skip(int celt);
        void Reset();
        void Clone(ref ThumbnailCreator.IEnumIDList ppenum);
    }

    [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("BB2E617C-0920-11d1-9A0B-00C04FC2D6C1")]
    private interface IExtractImage
    {
        void GetLocation([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszPathBuffer, int cch, ref int pdwPriority, ref ThumbnailCreator.SIZE prgSize, int dwRecClrDepth, ref int pdwFlags);
        void Extract(out IntPtr phBmpThumbnail);
    }

    [ComImport, Guid("00000002-0000-0000-C000-000000000046"), InterfaceType(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();
    }

    [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("000214E6-0000-0000-C000-000000000046")]
    private interface IShellFolder
    {
        void ParseDisplayName(IntPtr hwndOwner, IntPtr pbcReserved, [MarshalAs(UnmanagedType.LPWStr)] string lpszDisplayName, out int pchEaten, out IntPtr ppidl, out int pdwAttributes);
        void EnumObjects(IntPtr hwndOwner, [MarshalAs(UnmanagedType.U4)] ThumbnailCreator.ESHCONTF grfFlags, ref ThumbnailCreator.IEnumIDList ppenumIDList);
        void BindToObject(IntPtr pidl, IntPtr pbcReserved, ref Guid riid, ref ThumbnailCreator.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 ThumbnailCreator.ESFGAO rgfInOut);
        void GetUIObjectOf(IntPtr hwndOwner, int cidl, ref IntPtr apidl, ref Guid riid, out int prgfInOut, ref ThumbnailCreator.IUnknown ppvOut);
        void GetDisplayNameOf(IntPtr pidl, [MarshalAs(UnmanagedType.U4)] ThumbnailCreator.ESHGDN uFlags, ref ThumbnailCreator.STRRET_CSTR lpName);
        void SetNameOf(IntPtr hwndOwner, IntPtr pidl, [MarshalAs(UnmanagedType.LPWStr)] string lpszName, [MarshalAs(UnmanagedType.U4)] ThumbnailCreator.ESHCONTF uFlags, ref IntPtr ppidlOut);
    }

    [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("00000000-0000-0000-C000-000000000046")]
    private interface IUnknown
    {
        [PreserveSig]
        IntPtr QueryInterface(ref Guid riid, out IntPtr pVoid);
        [PreserveSig]
        IntPtr AddRef();
        [PreserveSig]
        IntPtr Release();
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct SIZE
    {
        public int cx;
        public int cy;
    }

    [StructLayout(LayoutKind.Explicit, CharSet=CharSet.Auto)]
    private struct STRRET_ANY
    {
        // Fields
        [FieldOffset(4)]
        public IntPtr pOLEString;
        [FieldOffset(0)]
        public ThumbnailCreator.ESTRRET uType;
    }

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

    private class UnManagedMethods
    {
        // Methods
        [DllImport("gdi32", CharSet=CharSet.Auto)]
        internal static extern int DeleteObject(IntPtr hObject);
        [DllImport("shell32", CharSet=CharSet.Auto)]
        internal static extern int SHGetDesktopFolder(out ThumbnailCreator.IShellFolder ppshf);
        [DllImport("shell32", CharSet=CharSet.Auto)]
        internal static extern int SHGetMalloc(out ThumbnailCreator.IMalloc ppMalloc);
        [DllImport("shell32", CharSet=CharSet.Auto)]
        internal static extern int SHGetPathFromIDList(IntPtr pidl, StringBuilder pszPath);
    }
}

As a test I created a quick console application, the code of which is below. This test worked fine and was able to extract a thumbnail, and saved it to a PNG file.

static void Main(string[] args)
{
    try
    {
        Console.WriteLine("press a key to extract");
        System.Console.ReadKey();

        string path = @"C:\somefile.xyz";
        ThumbnailCreator creator = new ThumbnailCreator();
        creator.DesiredSize = new Size(600, 600);
        Bitmap bm = creator.GetThumbNail(path);
        bm.Save(@"C:\blah.png", System.Drawing.Imaging.ImageFormat.Png);
        Console.WriteLine("press a key to exit");
        System.Console.ReadKey();
    }
    catch (Exception exp)
    {
        Console.WriteLine(exp.Message);

    }
}

My problem is the real application I want to use this in runs as a plug-in for another application. When I try to run the same test code in the plug-in creator.GetThumbNail(path); returns null.

I debugged a little further and found that in the method ThumbnailCreator.getThumbNail(string file, IntPtr pidl,IshellFolder item) the line extractImage.Extract(out hBmp); returns IntPtr.Zero. Whereas in the console application that works this method actually returns a number. Maybe this is the problem? Or maybe this problem happens before this. Honestly, I'm completely lost when it comes to Interop and Windows API stuff.

Does anyone know of any possible reasons why this class would work in a standalone console application, but not as part of a plug-in for another application?

Update

Here's a bit more detail of how this plug-in is created. To add a custom command to this program you create a class that implements an ICommand interface from is API. The interface has a single Execute() method where code is place that should run when the user executes the custom command from the program. The native application, however, is what actualy instantiates my command class and calls the execute method.

What could be different about the way that the native app instantiates my command class and or calls the Execute() method that would prevent the thumbnail extraction from working?

A: 

Code that calls the Shell API must be in a COM Single Threaded Apartment, try putting [STAThread] attribute on the thread that calls this.

John Knoeller
I tried checking the ApartmentState by using "System.Threading.Thread.CurrentThread.GetApartmentState();", and found that the current thread was already a STA thread. I tried adding the [STATThread] attribute as well just in case, but it still doesn't work.
Eric Anastas
Must be something else, then. sorry.
John Knoeller
Yeah thanks though.
Eric Anastas