Ore*_*ost 6 windows multithreading shell32 .net-4.0
在.NET 4.0应用程序(WPF)中,我们使用SHGetFileInfo获取目录树的shell图标.由于这在某些情况下需要相当长的时间(即对于无法访问的网络驱动器或软盘驱动器),我们希望在一个线程中执行此操作,然后在读入时更新图标.
调用基本相同,现在只是在一个线程中执行.因为有人说线程必须STA为此工作,我们使用Thread而不是ThreadPool进行测试,结果相同.使用ThreadPool也没有用.
SHGetFileInfo 成功(返回1),但结构中的hIcon成员为零.
IntPtr GetIcon(string name)
{
Shell32.SHFILEINFO shfi = new Shell32.SHFILEINFO();
uint flags = Shell32.SHGFI_ICON | Shell32.SHGFI_USEFILEATTRIBUTES | Shell32.SHGFI_SMALLICON;
Shell32.SHGetFileInfo(
name, System.IO.Directory.Exists(name) ? Shell32.FILE_ATTRIBUTE_DIRECTORY : Shell32.FILE_ATTRIBUTE_NORMAL,
ref shfi,
(uint) System.Runtime.InteropServices.Marshal.SizeOf(shfi),
flags );
return shfi.hIcon;
}
Run Code Online (Sandbox Code Playgroud)
完全相同的代码可以从GUI线程中正常工作.要使函数从单独的线程工作,或者使其在不阻塞GUI线程的情况下工作,必须做些什么?
更新:围绕这个的代码基本上是这样的:
var thread = new System.Threading.Thread(() => {
var result = GetIcon("C:\\");
// ... do something with the result
});
thread.SetApartmentState(System.Threading.ApartmentState.STA);
thread.Start();
Run Code Online (Sandbox Code Playgroud)
如果只留下线程委托中的行,它可以正常工作(当然,在GUI线程上).
更新:目前,我们只是调用SHGetFileInfo它来使其工作.这样做的好处是,原始问题(在加载所有图标之前未显示文件视图的页面)已经解决,但这意味着页面会挂起每个图标.但至少用户现在看到了一些事情正在发生.我们仍在寻找问题的实际解决方案.
刚刚成功得到了像这样的工作.以前在Visual Studio外部运行时崩溃严重.在此之前,我们经常回到默认图标而不是文件类型的正确图标(因为我们是在文件图标之后,而不是目录图标).
要考虑的因素的摘要.
SHFILEINFO该类应该具有[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 4)]属性.我们正在使用任务并行库,因此需要一个可以在合适的后台线程上安排工作的TaskScheduler.以下代码示例适用于公开可用于此目的的TaskScheduler属性的类.
请注意,在我们中,我们有一个这个类的单个实例,它持续应用程序的整个生命周期,因此我们没有实现IDisposable.如果你想创建/销毁这些,你需要处理它.
namespace MyNamespace
{
using System;
using System.ComponentModel.Composition;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
/// <summary>
/// Exposes a <see cref="TaskScheduler"/> that schedules its work on a STA background thread.
/// </summary>
[Export]
public class StaTaskSchedulerSource
{
/// <summary>
/// A window that is used for message pumping.
/// </summary>
private Window window;
/// <summary>
/// Thread on which work is scheduled.
/// </summary>
private Thread thread;
/// <summary>
/// The <see cref="TaskScheduler"/> exposed by this class.
/// </summary>
private TaskScheduler taskScheduler;
/// <summary>
/// Initializes a new instance of the <see cref="StaTaskSchedulerSource"/> class.
/// </summary>
public StaTaskSchedulerSource()
{
using (ManualResetEvent re = new ManualResetEvent(false))
{
this.thread = new Thread(
() =>
{
this.window = new Window();
re.Set();
Dispatcher.Run();
});
this.thread.IsBackground = true;
this.thread.SetApartmentState(ApartmentState.STA);
this.thread.Start();
re.WaitOne();
}
this.window.Dispatcher.Invoke(
new Action(
() =>
{
this.taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
}));
}
/// <summary>
/// Gets a <see cref="TaskScheduler"/> that schedules work on a background STA
/// thread.
/// </summary>
public TaskScheduler TaskScheduler
{
get
{
return this.taskScheduler;
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
当然,这整个事情只是使用调度程序来调用另一个线程上的调用.这可能有点慢.因此,如果处理许多图标,最好将它们批处理.此外,我们缓存了我们已经检索过的图标,因此我们不需要在第二次使用Win Shell.
您还可以找到以下用于图标处理的包装器.它源自SafeHandle,确保您在任何情况下都能正确销毁图标.
namespace UnmanagedResourceLib
{
using System;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Permissions;
using Microsoft.Win32.SafeHandles;
/// <summary>
/// A <see cref="SafeHandle"/> implementation for HICON icon handles.
/// </summary>
[SecurityPermission(SecurityAction.InheritanceDemand, UnmanagedCode = true)]
[SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
internal class SafeIconHandle : SafeHandleZeroOrMinusOneIsInvalid
{
/// <summary>
/// Prevents a default instance of the <see cref="SafeIconHandle"/> class from being created.
/// </summary>
private SafeIconHandle()
: base(true)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SafeIconHandle"/> class.
/// </summary>
/// <param name="nativeHandle">The HICON to wrap.</param>
/// <param name="ownsHandle"><c>true</c> if finalization of this object should cause the icon to be destroyed.</param>
public SafeIconHandle(IntPtr nativeHandle, bool ownsHandle)
: base(ownsHandle)
{
this.handle = nativeHandle;
}
/// <inheritdoc />
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
override protected bool ReleaseHandle()
{
return NativeMethods.DestroyIcon(this.handle);
}
/// <summary>
/// Exposes Windows API call to destroy an icon.
/// </summary>
[SuppressUnmanagedCodeSecurity]
internal static class NativeMethods
{
/// <summary>
/// Destroys an icon and frees any memory the icon occupied.
/// </summary>
/// <param name="hIcon">A handle to the icon to be destroyed.</param>
/// <returns><c>true</c> if the function succeeds.</returns>
[System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
public static extern bool DestroyIcon(IntPtr hIcon);
}
}
}
Run Code Online (Sandbox Code Playgroud)
我不认为有什么问题。您不需要使用 SetApartmentState。根据文档,您确实需要调用 CoInitialize 或 OleInitialize,但我认为 WPF 无论如何都应该为您调用它。
我在下面创建了一个简单的 WPF 应用程序。这很好用。SHGetFileInfo 在与 UI 线程不同的线程上运行,并且 shfi.hIcon 不为零。
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
Task<IntPtr> task = Task.Factory.StartNew(() => GetIcon("C:\\"));
}
private IntPtr GetIcon(string name)
{
Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
var shfi = new Shell32.SHFILEINFO();
uint flags = Shell32.SHGFI_ICON | Shell32.SHGFI_USEFILEATTRIBUTES | Shell32.SHGFI_SMALLICON;
Shell32.SHGetFileInfo(
name,
Directory.Exists(name) ? Shell32.FILE_ATTRIBUTE_DIRECTORY : Shell32.FILE_ATTRIBUTE_NORMAL,
ref shfi,
(uint) Marshal.SizeOf(shfi),
flags);
Debug.WriteLine(shfi.hIcon);
return shfi.hIcon;
}
}
public class Shell32
{
public const int MAX_PATH = 256;
// Browsing for directory.
public const uint BIF_RETURNONLYFSDIRS = 0x0001;
public const uint BIF_DONTGOBELOWDOMAIN = 0x0002;
public const uint BIF_STATUSTEXT = 0x0004;
public const uint BIF_RETURNFSANCESTORS = 0x0008;
public const uint BIF_EDITBOX = 0x0010;
public const uint BIF_VALIDATE = 0x0020;
public const uint BIF_NEWDIALOGSTYLE = 0x0040;
public const uint BIF_USENEWUI = (BIF_NEWDIALOGSTYLE | BIF_EDITBOX);
public const uint BIF_BROWSEINCLUDEURLS = 0x0080;
public const uint BIF_BROWSEFORCOMPUTER = 0x1000;
public const uint BIF_BROWSEFORPRINTER = 0x2000;
public const uint BIF_BROWSEINCLUDEFILES = 0x4000;
public const uint BIF_SHAREABLE = 0x8000;
public const uint SHGFI_ICON = 0x000000100; // get icon
public const uint SHGFI_DISPLAYNAME = 0x000000200; // get display name
public const uint SHGFI_TYPENAME = 0x000000400; // get type name
public const uint SHGFI_ATTRIBUTES = 0x000000800; // get attributes
public const uint SHGFI_ICONLOCATION = 0x000001000; // get icon location
public const uint SHGFI_EXETYPE = 0x000002000; // return exe type
public const uint SHGFI_SYSICONINDEX = 0x000004000; // get system icon index
public const uint SHGFI_LINKOVERLAY = 0x000008000; // put a link overlay on icon
public const uint SHGFI_SELECTED = 0x000010000; // show icon in selected state
public const uint SHGFI_ATTR_SPECIFIED = 0x000020000; // get only specified attributes
public const uint SHGFI_LARGEICON = 0x000000000; // get large icon
public const uint SHGFI_SMALLICON = 0x000000001; // get small icon
public const uint SHGFI_OPENICON = 0x000000002; // get open icon
public const uint SHGFI_SHELLICONSIZE = 0x000000004; // get shell size icon
public const uint SHGFI_PIDL = 0x000000008; // pszPath is a pidl
public const uint SHGFI_USEFILEATTRIBUTES = 0x000000010; // use passed dwFileAttribute
public const uint SHGFI_ADDOVERLAYS = 0x000000020; // apply the appropriate overlays
public const uint SHGFI_OVERLAYINDEX = 0x000000040; // Get the index of the overlay
public const uint FILE_ATTRIBUTE_DIRECTORY = 0x00000010;
public const uint FILE_ATTRIBUTE_NORMAL = 0x00000080;
[DllImport("Shell32.dll")]
public static extern IntPtr SHGetFileInfo(
string pszPath,
uint dwFileAttributes,
ref SHFILEINFO psfi,
uint cbFileInfo,
uint uFlags
);
#region Nested type: BROWSEINFO
[StructLayout(LayoutKind.Sequential)]
public struct BROWSEINFO
{
public IntPtr hwndOwner;
public IntPtr pidlRoot;
public IntPtr pszDisplayName;
[MarshalAs(UnmanagedType.LPTStr)] public string lpszTitle;
public uint ulFlags;
public IntPtr lpfn;
public int lParam;
public IntPtr iImage;
}
#endregion
#region Nested type: ITEMIDLIST
[StructLayout(LayoutKind.Sequential)]
public struct ITEMIDLIST
{
public SHITEMID mkid;
}
#endregion
#region Nested type: SHFILEINFO
[StructLayout(LayoutKind.Sequential)]
public struct SHFILEINFO
{
public const int NAMESIZE = 80;
public IntPtr hIcon;
public int iIcon;
public uint dwAttributes;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH)] public string szDisplayName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = NAMESIZE)] public string szTypeName;
};
#endregion
#region Nested type: SHITEMID
[StructLayout(LayoutKind.Sequential)]
public struct SHITEMID
{
public ushort cb;
[MarshalAs(UnmanagedType.LPArray)] public byte[] abID;
}
#endregion
}
/// <summary>
/// Wraps necessary functions imported from User32.dll. Code courtesy of MSDN Cold Rooster Consulting example.
/// </summary>
public class User32
{
/// <summary>
/// Provides access to function required to delete handle. This method is used internally
/// and is not required to be called separately.
/// </summary>
/// <param name="hIcon">Pointer to icon handle.</param>
/// <returns>N/A</returns>
[DllImport("User32.dll")]
public static extern int DestroyIcon(IntPtr hIcon);
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
2009 次 |
| 最近记录: |