2015-08-08 92 views
5

我的任務是使用任務獲取目錄圖標並將它們顯示在DataGridView中(我正在通過文件夾執行搜索)。爲此,我使用SHGetImageList WinAPI函數。我有一個助手類如下:使用任務獲取目錄圖標

using System; 
using System.Collections.Generic; 
using System.Drawing; 
using System.IO; 
using System.Linq; 
using System.Runtime.InteropServices; 
using System.Text; 
using System.Threading.Tasks; 

namespace WindowsFormsApplication6 { 
    public class Helper { 
     private const uint ILD_TRANSPARENT = 0x00000001; 
     private const uint SHGFI_SYSICONINDEX = 0x000004000; 
     private const uint SHGFI_ICON = 0x000000100; 
     public static readonly int MaxEntitiesCount = 80; 
     public static void GetDirectories(string path, List<Image> col, IconSizeType sizeType, Size itemSize) { 
      DirectoryInfo dirInfo = new DirectoryInfo(path); 
      DirectoryInfo[] dirs = dirInfo.GetDirectories("*", SearchOption.TopDirectoryOnly); 
      for (int i = 0; i < dirs.Length && i < MaxEntitiesCount; i++) { 
       DirectoryInfo subDirInfo = dirs[i]; 
       if (!CheckAccess(subDirInfo) || !MatchFilter(subDirInfo.Attributes)) { 
        continue; 
       } 
       col.Add(GetFileImage(subDirInfo.FullName, sizeType, itemSize)); 
      } 
     } 

     public static bool CheckAccess(DirectoryInfo info) { 
      bool isOk = false; 
      try { 
       var secInfo = info.GetAccessControl(); 
       isOk = true; 
      } 
      catch { 
      } 
      return isOk; 
     } 

     public static bool MatchFilter(FileAttributes attributes) { 
      return (attributes & (FileAttributes.Hidden | FileAttributes.System)) == 0; 
     } 

     public static Image GetFileImage(string path, IconSizeType sizeType, Size itemSize) { 
      return IconToBitmap(GetFileIcon(path, sizeType, itemSize), sizeType, itemSize); 
     } 

     public static Image IconToBitmap(Icon ico, IconSizeType sizeType, Size itemSize) { 
      if (ico == null) { 
       return new Bitmap(itemSize.Width, itemSize.Height); 
      } 
      return ico.ToBitmap(); 
     } 

     public static Icon GetFileIcon(string path, IconSizeType sizeType, Size itemSize) { 
      SHFILEINFO shinfo = new SHFILEINFO(); 
      IntPtr retVal = SHGetFileInfo(path, 0, ref shinfo, (uint)Marshal.SizeOf(shinfo), (int)(SHGFI_SYSICONINDEX | SHGFI_ICON)); 
      int iconIndex = shinfo.iIcon; 
      IImageList iImageList = (IImageList)GetSystemImageListHandle(sizeType); 
      IntPtr hIcon = IntPtr.Zero; 
      if (iImageList != null) { 
       iImageList.GetIcon(iconIndex, (int)ILD_TRANSPARENT, ref hIcon); 
      } 
      Icon icon = null; 
      if (hIcon != IntPtr.Zero) { 
       icon = Icon.FromHandle(hIcon).Clone() as Icon; 
       DestroyIcon(shinfo.hIcon); 
      } 
      return icon; 
     } 

     private static IImageList GetSystemImageListHandle(IconSizeType sizeType) { 
      IImageList iImageList = null; 
      Guid imageListGuid = new Guid("46EB5926-582E-4017-9FDF-E8998DAA0950"); 
      int ret = SHGetImageList((int)sizeType, ref imageListGuid, ref iImageList); 
      return iImageList; 
     } 

     [DllImport("shell32.dll", CharSet = CharSet.Auto)] 
     private static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbFileInfo, uint uFlags); 
     [DllImport("shell32.dll", EntryPoint = "#727")] 
     private static extern int SHGetImageList(int iImageList, ref Guid riid, ref IImageList ppv); 
     [DllImport("user32.dll", SetLastError = true)] 
     private static extern bool DestroyIcon(IntPtr hIcon); 
     public enum IconSizeType { 
      Medium = 0x0, 
      Small = 0x1, 
      Large = 0x2, 
      ExtraLarge = 0x4 
     } 

     [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] 
     private struct SHFILEINFO { 
      public IntPtr hIcon; 
      public int iIcon; 
      public uint dwAttributes; 
      [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] 
      public string szDisplayName; 
      [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)] 
      public string szTypeName; 
     } 

     [ComImport, 
     Guid("46EB5926-582E-4017-9FDF-E8998DAA0950"), 
     InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 
     private interface IImageList { 
      [PreserveSig] 
      int Add(IntPtr hbmImage, IntPtr hbmMask, ref int pi); 
      [PreserveSig] 
      int ReplaceIcon(int i, IntPtr hicon, ref int pi); 
      [PreserveSig] 
      int SetOverlayImage(int iImage, int iOverlay); 
      [PreserveSig] 
      int Replace(int i, IntPtr hbmImage, IntPtr hbmMask); 
      [PreserveSig] 
      int AddMasked(IntPtr hbmImage, int crMask, ref int pi); 
      [PreserveSig] 
      int Draw(ref IMAGELISTDRAWPARAMS pimldp); 
      [PreserveSig] 
      int Remove(int i); 
      [PreserveSig] 
      int GetIcon(int i, int flags, ref IntPtr picon); 
      [PreserveSig] 
      int GetImageInfo(int i, ref IMAGEINFO pImageInfo); 
      [PreserveSig] 
      int Copy(int iDst, IImageList punkSrc, int iSrc, int uFlags); 
      [PreserveSig] 
      int Merge(int i1, IImageList punk2, int i2, int dx, int dy, ref Guid riid, ref IntPtr ppv); 
      [PreserveSig] 
      int Clone(ref Guid riid, ref IntPtr ppv); 
      [PreserveSig] 
      int GetImageRect(int i, ref RECT prc); 
      [PreserveSig] 
      int GetIconSize(ref int cx, ref int cy); 
      [PreserveSig] 
      int SetIconSize(int cx, int cy); 
      [PreserveSig] 
      int GetImageCount(ref int pi); 
      [PreserveSig] 
      int SetImageCount(int uNewCount); 
      [PreserveSig] 
      int SetBkColor(int clrBk, ref int pclr); 
      [PreserveSig] 
      int GetBkColor(ref int pclr); 
      [PreserveSig] 
      int BeginDrag(int iTrack, int dxHotspot, int dyHotspot); 
      [PreserveSig] 
      int EndDrag(); 
      [PreserveSig] 
      int DragEnter(IntPtr hwndLock, int x, int y); 
      [PreserveSig] 
      int DragLeave(IntPtr hwndLock); 
      [PreserveSig] 
      int DragMove(int x, int y); 
      [PreserveSig] 
      int SetDragCursorImage(ref IImageList punk, int iDrag, int dxHotspot, int dyHotspot); 
      [PreserveSig] 
      int DragShowNolock(int fShow); 
      [PreserveSig] 
      int GetDragImage(ref POINT ppt, ref POINT pptHotspot, ref Guid riid, ref IntPtr ppv); 
      [PreserveSig] 
      int GetItemFlags(int i, ref int dwFlags); 
      [PreserveSig] 
      int GetOverlayImage(int iOverlay, ref int piIndex); 
     } 
     ; 

     [StructLayout(LayoutKind.Sequential)] 
     private struct IMAGELISTDRAWPARAMS { 
      public int cbSize; 
      public IntPtr himl; 
      public int i; 
      public IntPtr hdcDst; 
      public int x; 
      public int y; 
      public int cx; 
      public int cy; 
      public int xBitmap; 
      public int yBitmap; 
      public int rgbBk; 
      public int rgbFg; 
      public int fStyle; 
      public int dwRop; 
      public int fState; 
      public int Frame; 
      public int crEffect; 
     } 

     [StructLayout(LayoutKind.Sequential)] 
     private struct IMAGEINFO { 
      public IntPtr hbmImage; 
      public IntPtr hbmMask; 
      public int Unused1; 
      public int Unused2; 
      public RECT rcImage; 
     } 

     [StructLayout(LayoutKind.Sequential)] 
     public struct RECT { 
      public int Left, Top, Right, Bottom; 
      public RECT(int l, int t, int r, int b) { 
       Left = l; 
       Top = t; 
       Right = r; 
       Bottom = b; 
      } 

      public RECT(Rectangle r) { 
       Left = r.Left; 
       Top = r.Top; 
       Right = r.Right; 
       Bottom = r.Bottom; 
      } 

      public Rectangle ToRectangle() { 
       return Rectangle.FromLTRB(Left, Top, Right, Bottom); 
      } 

      public void Inflate(int width, int height) { 
       Left -= width; 
       Top -= height; 
       Right += width; 
       Bottom += height; 
      } 

      public override string ToString() { 
       return string.Format("x:{0},y:{1},width:{2},height:{3}", Left, Top, Right - Left, Bottom - Top); 
      } 
     } 

     [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] 
     public struct POINT { 
      public int X, Y; 
      public POINT(int x, int y) { 
       this.X = x; 
       this.Y = y; 
      } 

      public POINT(Point pt) { 
       this.X = pt.X; 
       this.Y = pt.Y; 
      } 

      public Point ToPoint() { 
       return new Point(X, Y); 
      } 
     } 
    } 
} 

在窗體上我有兩個DataGridViews和兩個按鈕。第一個按鈕的點擊,我在UI線程加載圖標:

private void button1_Click(object sender, EventArgs e) { 
    List<Image> list = new List<Image>(); 
    Helper.GetDirectories(fPath, list, Helper.IconSizeType.Small, new Size(16, 16)); 
    dataGridView1.DataSource = list; 
} 

的第二個按鈕點擊,我做的:

private void button2_Click(object sender, EventArgs e) { 
    Func<object, List<Image>> a = null; 
    a = (p) => { 
     string path = (string)p; 
     List<Image> list = new List<Image>(); 
     Helper.GetDirectories(path, list, Helper.IconSizeType.Small, new Size(16, 16)); 
     return list; 
    }; 
    Task.Factory.StartNew(a, fPath).ContinueWith(t => { dataGridView2.DataSource = t.Result;}, 
TaskScheduler.FromCurrentSynchronizationContext()); 
} 

所以,我做同樣的,但在任務。

當我點擊第一個按鈕,然後第二個,我發現了以下System.InvalidCastException:

Unable to cast COM object of type 'System.__ComObject' to interface type 'IImageList'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{46EB5926-582E-4017-9FDF-E8998DAA0950}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).

唯一的例外是在

int ret = SHGetImageList((int)sizeType, ref imageListGuid, ref iImageList); 

線GetSystemImageListHandle募集方法。

我搞不​​清楚我做錯了什麼。任何幫助表示讚賞。

+0

哪條線拋出異常? – andlabs

+0

@andlabs,int ret = SHGetImageList((int)sizeType,ref imageListGuid,ref iImageList);在GetSystemImageListHandle方法中。 –

+0

@Gosha_Fighten你遇到的問題是由於十字線程Marshal對象。你所需要做的就是調用工作線程上的GetDirectories()方法。請參閱下面的答案來解決問題。 – vendettamit

回答

3

只需插入

Marshal.FinalReleaseComObject(iImageList); 

iImageList.GetIcon(iconIndex, (int)ILD_TRANSPARENT, ref hIcon); 

線。

此外,您可能會感興趣的是,當您通過SHGFI_SYSICONINDEX時,SHGetFileInfo實際上會返回IImageList。所以,像這樣的工作:

[DllImport("shell32.dll", EntryPoint = "SHGetFileInfo", CharSet = CharSet.Auto)] 
    private static extern IImageList SHGetFileInfoAsImageList(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbFileInfo, uint uFlags); 

和整個圖像提取可能是簡單的:

public static Image GetFileImage(string path, IconSizeType sizeType, Size itemSize) 
    { 
     var shfi = new SHFILEINFO(); 
     var imageList = SHGetFileInfoAsImageList(path, 0, ref shfi, (uint)Marshal.SizeOf(shfi), (int)SHGFI_SYSICONINDEX); 
     if (imageList != null) 
     { 
      var hIcon = IntPtr.Zero; 
      imageList.GetIcon(shfi.iIcon, (int)ILD_TRANSPARENT, ref hIcon); 
      Marshal.FinalReleaseComObject(imageList); 
      if (hIcon != IntPtr.Zero) 
      { 
       var image = Bitmap.FromHicon(hIcon); 
       DestroyIcon(hIcon); 
       return image; 
      } 
     } 
     return new Bitmap(itemSize.Width, itemSize.Height); 
    } 
1

Windows外殼函數只能從UI線程中調用。你在這個問題中有同樣的根本問題:Unable to cast COM object of type 'System.__ComObject' to interface type 'IImageList'。或者更正式地說,他們只能在single thread apartment中創建。在WPF和WinForms UI線程是被默認在單線程公寓(這是[STAThread]屬性上Program.Main()的意義。通過Task.Run()使用將不會是一個單線程公寓

線程池中的線程(這將是一個多-threaded apartment)當你嘗試訪問一個線程公寓類型的COM對象時,你可能會得到一個E_NOINTERFACE錯誤*

這是可能的創建一個新的線程手動,在一個單線程的公寓。看起來在這種情況下可靠地工作,它仍然會零星拋出E_NOINTERFACE exce [topms。只需在UI線程上調用SHGetImageList。感謝@vendettamit實際測試我t並發現它不起作用。

它可以創建一個new thread manually that is in a single threaded apartment

Thread thread = new Thread(ThreadStartMethod); 
thread.SetApartmentState(ApartmentState.STA); 
thread.Start(); 

這可能會在你的情況下工作,但如果您嘗試訪問在不同的線程的IImageList或任何COM對象,你會遇到問題** 。因爲它不會出現,你將作出一個螺紋交叉調用,下面應該工作:

private void button2_Click(object sender, EventArgs e) { 
    ParameterizedThreadStart a = null; 
    a = (p) => { 
     string path = (string)p; 
     List<Image> list = new List<Image>(); 
     Helper.GetDirectories(path, list, Helper.IconSizeType.Small, new Size(16, 16)); 
     this.Invoke(new Action(() => dataGridView2.DataSource = list)); 
    }; 

    Thread thread = new Thread(a); 
    thread.SetApartmentState(ApartmentState.STA); 
    thread.Start(fPath); 
} 

*某些COM接口支持跨單元編組,但COM編組爲一個完整的不同話題。

**因爲那樣你需要在後臺線程上運行一個Windows消息泵,因爲COM STA編組的工作原理。

2

Update: Chaning the [STAThread] to [MTAThread] on Main method, with the given code @Gosha_Fighten's problem everything seems to be working.

如果申請根據[STAThread]運行只適用下面的解決方案。 @ shf301解釋得好!但回答中提供的示例代碼無法解決問題。通過SHGetImageList調用交叉線程構造函數會導致問題。因此,如果在Worker線程上調用了Helper.GetDirectories,則不會調用DataGrid綁定語句,而是不會發生該問題。

下面的代碼工作正常:

private void button2_Click_1(object sender, EventArgs e) 
    { 
     Action a = null; 
     a =() => 
     { 
      List<Image> list = null; 

      this.BeginInvoke(new Action(() => 
      { 
       list = new List<Image>(); 
       Helper.GetDirectories(fPath, list, Helper.IconSizeType.Small, new Size(16, 16)); 
       dataGridView2.DataSource = list; 
      })); 
     }; 

     Task.Factory.StartNew(a); 
    } 

在這裏,我已刪除了ContinueWith()Current synchronization參數的傳遞。現在,控件的invoke方法將在工作線程上執行任務。 注 - 由於所有工作都在WorkerThread上完成,因此不需要保留Task

+0

調用任務然後調用回UI線程有什麼意義?你也可以完全擺脫這個任務。 – shf301

+0

我沒有看到任何「跨線程構造函數調用」在我的例子中,我只是測試,似乎工作正常。你能解釋一下你的意思嗎? – shf301

+0

如果您調用了button1和button2的處理程序,將出現異常。 – vendettamit

1

.NET具有SynchronizationContext概念:

該上下文的主要方法被命名爲Post,它基本上調度異步消息的上下文。這是根據底層UI技術(Winforms,WPF等)或非UI技術,可以調整和優雅地使用這些技術所具有的約束條件。

默認情況下,任務的任務調度程序不使用當前的同步上下文,而是使用您無法真正控制的ThreadPool(並且不會使用Winforms,也不會使用WPF),因此您必須指定您需要SynchronizationContext中的TaskScheduler,您只能部分完成此任務。

由於您運行的是Winforms應用程序,因此當前同步上下文(Synchronization.Current)應該是WindowsFormsSynchronizationContext類型。如果你可以看看它的實現後,你會看到:

public override void Post(SendOrPostCallback callback, object state) 
{ 
    if (controlToSendTo != null) 
    { 
     controlToSendTo.BeginInvoke(callback, new object[] { state }); 
    } 
} 

這方面的實現能很好地工作的WinForms UI線程......只要你使用它。事實上,你幾乎是對的,你忘了在StartNew方法中使用它。

所以,只要改變你的代碼到這一點:

Task.Factory.StartNew(a, fPath, 
    CancellationToken.None, TaskCreationOptions.None, // unfortunately, there is no easier overload with just the context... 
    TaskScheduler.FromCurrentSynchronizationContext()).ContinueWith(
     t => { dataGridView2.DataSource = t.Result; }, 
     TaskScheduler.FromCurrentSynchronizationContext()); 

,它應該工作。

+0

他已經在他的ContinueWith()方法中使用它。 – vendettamit

+0

@vendettamit - 是的,但它在StartNew中缺失。這有很大的區別 –

+0

嗯..有道理! – vendettamit