In a .NET 4.0 application (WPF) we're using SHGetFileInfo
to obtain shell icons for a directory tree. Since this takes quite some time in some cases (i.e. for a network drive that is unreachable or for a floppy drive), we wanted to do this in a thread and then update the icon when it has been read in.
The call is basically the same, it is now just executed within a thread. Because someone said that the thread must be STA
for this to work, we used Thread rather than ThreadPool for testing, with the same results. Using ThreadPool
also did not work.
SHGetFileInfo
succeeds (returns 1), but the hIcon member in the structure is zero.
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;
}
The very same code works fine from the GUI thread. What has to be done to make the function work from a separate thread, or, however, make it work without blocking the GUI thread?
Update: The code around this is basically this:
var thread = new System.Threading.Thread(() => {
var result = GetIcon("C:\\");
// ... do something with the result
});
thread.SetApartmentState(System.Threading.ApartmentState.STA);
thread.Start();
if only the lines within the thread delegate are left in, it works fine (but on the GUI thread, of course).
Update: For now, we just Invoke the call to SHGetFileInfo
to make it work. This has the advantage that the original problem (the page with the file view was not displayed until all the icons have been loaded) has been solved, though it means that the page hangs for each icon. But at least the user now sees that something is going on. We're still looking for an actual solution to the problem.
Just successfully got something just like this working. It had previously been crashing badly when run outside of Visual Studio. Before that we'd often been getting back the default icon rather than the correct one for the file type (since we're after file icons, not the directory icon).
A summary of the factors to consider.
SHFILEINFO
class should presumably have a[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 4)]
attribute.A TaskScheduler For Background Tasks on an STA Thread
We're using the Task Parallel Library, so wanted a TaskScheduler that would schedule work on a suitable background thread. The following code sample is for a class that exposes a TaskScheduler property that can be used for this.
Note that in our we have a single instance of this class that lasts for the whole lifetime of the application, so we've not implemented IDisposable. If you want to create/destroy these you'll need to handle that.
Of course, this whole thing just wraps using a dispatcher to invoke the calls on another thread. This can be a bit slow. So if processing many icons it would be better to batch them up. Also, we cache icons we've already retrieved so we don't need to use the Win Shell at all the second time around.
SafeIconHandle
You may also find the following wrapper for an icon handle useful. It derives from SafeHandle and ensures that you icon is properly destroyed under all circumstances.
I don't think there's any problem. You don't need to use SetApartmentState. According to the documentation you do need to have called CoInitialize or OleInitialize, but I think this should have been called for you anyway by WPF.
I created a simple WPF application below. This works fine. SHGetFileInfo runs on a different thread to the UI thread and shfi.hIcon is not zero.