Hello,
I'm currently writing a small file manager. It's just a small program I'm writing in my spare time for fun. It's going well, except for this problem. I'm trying to use the Windows API to get some 48x48 icons associated with files for use in drawing the UI. Below is the code I'm using for this. The reason that I'm using these API calls and not simply doing Icon.ExtractAssociatedIcons is because I want those 48x48 icons. ExtractAssociatedIcon is limited to 32x32 icons.
It works properly for a while, but I've found that if this code is used too many times, it crashes. For example, if I open a folder like C:\Windows\System32 where this code literally has to be executed thousands of times for thousands of files, it works for a little bit (a few seconds), then crashes. Or, I can open a folder like C:\ and it'll work fine, but then if I open too many copies of C:\, it'll eventually crash.
The stack trace is below.
System.Reflection.TargetInvocationException was unhandled by user code
Message=Exception has been thrown by the target of an invocation.
Source=mscorlib
StackTrace:
at System.RuntimeMethodHandle._SerializationInvoke(Object target, SignatureStruct& declaringTypeSig, SerializationInfo info, StreamingContext context)
at System.Reflection.RuntimeConstructorInfo.SerializationInvoke(Object target, SerializationInfo info, StreamingContext context)
at System.Runtime.Serialization.ObjectManager.CompleteISerializableObject(Object obj, SerializationInfo info, StreamingContext context)
at System.Runtime.Serialization.ObjectManager.FixupSpecialObject(ObjectHolder holder)
at System.Runtime.Serialization.ObjectManager.DoFixups()
at System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize(HeaderHandler handler, __BinaryParser serParser, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream, HeaderHandler handler, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
at System.Resources.ResourceReader.DeserializeObject(Int32 typeIndex)
at System.Resources.ResourceReader.LoadObjectV2(Int32 pos, ResourceTypeCode& typeCode)
at System.Resources.ResourceReader.LoadObject(Int32 pos, ResourceTypeCode& typeCode)
at System.Resources.RuntimeResourceSet.GetObject(String key, Boolean ignoreCase, Boolean isString)
at System.Resources.RuntimeResourceSet.GetObject(String key, Boolean ignoreCase)
at System.Resources.ResourceManager.GetObject(String name, CultureInfo culture, Boolean wrapUnmanagedMemStream)
at System.Resources.ResourceManager.GetObject(String name, CultureInfo culture)
at TFMP_Framework.My.Resources.Resources.get_unknownIcon_medium() in D:\Documents\Visual Studio 2010\Projects\TFMP Framework\My Project\Resources.Designer.vb:line 118
at TFMP_Framework.FileInfoAPI.GetIcon(String path, ImageListIconSize iconSize, Boolean isDirectory, SHFILEINFO& shinfo) in D:\Documents\Visual Studio 2010\Projects\TFMP Framework\FileInfoAPI.vb:line 130
at Villanova.FBIcon.LoadIconDetails(Object sender, DoWorkEventArgs e) in D:\Documents\Visual Studio 2010\Projects\Villanova\FBIcon.vb:line 63
at System.ComponentModel.BackgroundWorker.OnDoWork(DoWorkEventArgs e)
at System.ComponentModel.BackgroundWorker.WorkerThreadStart(Object argument)
InnerException: System.ComponentModel.Win32Exception
ErrorCode=-2147467259
Message=The operation completed successfully
NativeErrorCode=0
Source=System.Drawing
StackTrace:
at System.Drawing.Icon.Initialize(Int32 width, Int32 height)
at System.Drawing.Icon..ctor(SerializationInfo info, StreamingContext context)
InnerException:
I've done my research on the InnerException I'm getting (The operation completed successfully), and according to the internet it always seems to be related to the memory. Either the application isn't allocating enough memory for the task, or the application has reached its memory limit, or the application has reached some other hard limit (like a GDI handles limit). The problem is, I believe that the garbage is being properly collected (correct me if I'm wrong). I'm using DestroyIcon (another Windows API call) to destroy the handle to the Icon after its use, and I'm disposing the BackgroundWorker when it has finished executing its thread.
And also, I'm using a BackgroundWorker and executing the calls to get the icons in a background thread (it's too slow if I don't). I initially thought it might be a multi-threading problem, but even when I try to run this exact same code on the main thread, it fails with the same exception.
In my code, there are some bits that are commented out that have to do with caching the icons. I experimented with caching the icons. For example, if I cache the icon that's associated with DLL files, then I only have to invoke the Windows API once to get the icon and cache it, then I can use the cached copy of the icon for the other 2000 DLLs in the folder). That works a lot better. A lot. To the point where I can open many copies of a folder that has thousands of files in it and be fine. But I feel that that's just a workaround. It's just putting off the problem, not really solving it.
I've included the code to the Windows API calls I'm using. If you need anything else, let me know and I'll get it to you.
I've spent a ton of time troubleshooting and researching this, and at this point it feels like I'm spinning my wheels. I'm almost wondering if this is just a limitation that's unsolvable. Any assistance would be greatly appreciated. :)
And Mods, if I've posted in the wrong section, I apologize. Feel free to move it as you see fit.
- Colin
I'm currently writing a small file manager. It's just a small program I'm writing in my spare time for fun. It's going well, except for this problem. I'm trying to use the Windows API to get some 48x48 icons associated with files for use in drawing the UI. Below is the code I'm using for this. The reason that I'm using these API calls and not simply doing Icon.ExtractAssociatedIcons is because I want those 48x48 icons. ExtractAssociatedIcon is limited to 32x32 icons.
It works properly for a while, but I've found that if this code is used too many times, it crashes. For example, if I open a folder like C:\Windows\System32 where this code literally has to be executed thousands of times for thousands of files, it works for a little bit (a few seconds), then crashes. Or, I can open a folder like C:\ and it'll work fine, but then if I open too many copies of C:\, it'll eventually crash.
The stack trace is below.
Quote:
System.Reflection.TargetInvocationException was unhandled by user code
Message=Exception has been thrown by the target of an invocation.
Source=mscorlib
StackTrace:
at System.RuntimeMethodHandle._SerializationInvoke(Object target, SignatureStruct& declaringTypeSig, SerializationInfo info, StreamingContext context)
at System.Reflection.RuntimeConstructorInfo.SerializationInvoke(Object target, SerializationInfo info, StreamingContext context)
at System.Runtime.Serialization.ObjectManager.CompleteISerializableObject(Object obj, SerializationInfo info, StreamingContext context)
at System.Runtime.Serialization.ObjectManager.FixupSpecialObject(ObjectHolder holder)
at System.Runtime.Serialization.ObjectManager.DoFixups()
at System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize(HeaderHandler handler, __BinaryParser serParser, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream, HeaderHandler handler, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
at System.Resources.ResourceReader.DeserializeObject(Int32 typeIndex)
at System.Resources.ResourceReader.LoadObjectV2(Int32 pos, ResourceTypeCode& typeCode)
at System.Resources.ResourceReader.LoadObject(Int32 pos, ResourceTypeCode& typeCode)
at System.Resources.RuntimeResourceSet.GetObject(String key, Boolean ignoreCase, Boolean isString)
at System.Resources.RuntimeResourceSet.GetObject(String key, Boolean ignoreCase)
at System.Resources.ResourceManager.GetObject(String name, CultureInfo culture, Boolean wrapUnmanagedMemStream)
at System.Resources.ResourceManager.GetObject(String name, CultureInfo culture)
at TFMP_Framework.My.Resources.Resources.get_unknownIcon_medium() in D:\Documents\Visual Studio 2010\Projects\TFMP Framework\My Project\Resources.Designer.vb:line 118
at TFMP_Framework.FileInfoAPI.GetIcon(String path, ImageListIconSize iconSize, Boolean isDirectory, SHFILEINFO& shinfo) in D:\Documents\Visual Studio 2010\Projects\TFMP Framework\FileInfoAPI.vb:line 130
at Villanova.FBIcon.LoadIconDetails(Object sender, DoWorkEventArgs e) in D:\Documents\Visual Studio 2010\Projects\Villanova\FBIcon.vb:line 63
at System.ComponentModel.BackgroundWorker.OnDoWork(DoWorkEventArgs e)
at System.ComponentModel.BackgroundWorker.WorkerThreadStart(Object argument)
InnerException: System.ComponentModel.Win32Exception
ErrorCode=-2147467259
Message=The operation completed successfully
NativeErrorCode=0
Source=System.Drawing
StackTrace:
at System.Drawing.Icon.Initialize(Int32 width, Int32 height)
at System.Drawing.Icon..ctor(SerializationInfo info, StreamingContext context)
InnerException:
And also, I'm using a BackgroundWorker and executing the calls to get the icons in a background thread (it's too slow if I don't). I initially thought it might be a multi-threading problem, but even when I try to run this exact same code on the main thread, it fails with the same exception.
In my code, there are some bits that are commented out that have to do with caching the icons. I experimented with caching the icons. For example, if I cache the icon that's associated with DLL files, then I only have to invoke the Windows API once to get the icon and cache it, then I can use the cached copy of the icon for the other 2000 DLLs in the folder). That works a lot better. A lot. To the point where I can open many copies of a folder that has thousands of files in it and be fine. But I feel that that's just a workaround. It's just putting off the problem, not really solving it.
I've included the code to the Windows API calls I'm using. If you need anything else, let me know and I'll get it to you.
I've spent a ton of time troubleshooting and researching this, and at this point it feels like I'm spinning my wheels. I'm almost wondering if this is just a limitation that's unsolvable. Any assistance would be greatly appreciated. :)
And Mods, if I've posted in the wrong section, I apologize. Feel free to move it as you see fit.
- Colin
Code:
' This class is a container containing a collection of properties, events, and Windows API functions.
' This is intended to make it easy to invoke the Windows API to get some detailed information about files.
Imports System.Runtime.InteropServices
Public Class FileInfoAPI
Private Shared imageListSmall As IImageList = Nothing
Private Shared imageListMedium As IImageList = Nothing
Private Shared imageListLarge As IImageList = Nothing
' SHGetFileInfo gets some low-level information about files like the handle (pointer) to the file's icon, a file description, and attributes:
<DllImport("Shell32.dll")> _
Public Shared Function SHGetFileInfo(ByVal pszPath As String, ByVal dwFileAttributes As UInteger, ByRef psfi As SHFILEINFO, ByVal cbfileInfo As UInteger, ByVal uFlags As SHGFI) As Integer
End Function
' DestroyIcon erases an HIcon from memory; in this context, it's used to prevent memory leaks:
<DllImport("User32.dll")> _
Public Shared Function DestroyIcon(ByVal hIcon As IntPtr) As Integer
End Function
<DllImport("Shell32.dll", EntryPoint:="#727")> _
Public Shared Function SHGetImageList(iImageList As Integer, ByRef riid As Guid, ByRef ppv As IImageList) As Integer
End Function
' here's a structure that will contain information returned by SHGetFileInfo:
<StructLayout(LayoutKind.Sequential)> _
Public Structure SHFILEINFO
Public Sub New(ByVal b As Boolean)
hIcon = IntPtr.Zero
iIcon = 0
dwAttributes = 0
szDisplayName = ""
szTypeName = ""
End Sub
Public hIcon As IntPtr
Public iIcon As Integer
Public dwAttributes As UInteger
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=260)> _
Public szDisplayName As String
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=80)> _
Public szTypeName As String
End Structure
Public Structure RECT
Dim left As Long
Dim top As Long
Dim right As Long
Dim bottom As Long
End Structure
' just some constants used by SHGetFileInfo:
Public Enum SHGFI
Icon = &H100
SmallIcon = &H1
LargeIcon = &H0
ExtraLargeIcon = &H2
OpenIcon = &H2
ShellIcon = &H4
DisplayName = &H200
Typename = &H400
SysIconIndex = &H4000
UseFileAttributes = &H10
Directory = &H10
End Enum
Public Enum ImageListIconSize
SmallIcon = &H1
MediumIcon = &H0
LargeIcon = &H2
End Enum
Public Shared Function GetInfo(ByVal strPath As String, ByVal isDirectory As Boolean) As SHFILEINFO
' here's the actual function that will be called which will invoke the Windows API
' this is just some wrapper code to make invoking the API easier
Dim info As New SHFILEINFO(True)
Dim cbFileInfo As Integer = Marshal.SizeOf(info)
Dim flags As SHGFI
flags = SHGFI.SysIconIndex Or SHGFI.Icon Or SHGFI.UseFileAttributes Or SHGFI.Typename Or SHGFI.DisplayName
SHGetFileInfo(strPath, IIf(isDirectory = True, SHGFI.Directory, 256), info, CType(cbFileInfo, UInteger), flags)
If info.hIcon <> IntPtr.Zero Then
DestroyIcon(info.hIcon)
End If
Return info
End Function
Public Shared Sub GetSystemImageListHandle(ByVal size As ImageListIconSize)
Dim imageListGuid As New Guid("46EB5926-582E-4017-9FDF-E8998DAA0950")
If size = ImageListIconSize.SmallIcon Then
SHGetImageList(CType(size, Integer), imageListGuid, imageListSmall)
ElseIf size = ImageListIconSize.MediumIcon Then
SHGetImageList(CType(size, Integer), imageListGuid, imageListMedium)
Else
SHGetImageList(CType(size, Integer), imageListGuid, imageListLarge)
End If
End Sub
Public Shared Function GetIcon(ByVal path As String, ByVal iconSize As ImageListIconSize, ByVal isDirectory As Boolean, ByRef shinfo As SHFILEINFO) As Icon
'Dim CachedIcon As Icon = Nothing
'Dim GotCachedIconAlready As Boolean
'If isDirectory = True Then
' GotCachedIconAlready = False
'Else
' GotCachedIconAlready = CachedIconList.FindCachedIcon(Functions.GetExtensionOfPath(path), iconSize, CachedIcon)
'End If
'If GotCachedIconAlready = True Then
' Return CachedIcon
'Else
If iconSize = ImageListIconSize.SmallIcon Then
Do Until imageListSmall IsNot Nothing
GetSystemImageListHandle(iconSize)
Loop
ElseIf iconSize = ImageListIconSize.MediumIcon Then
Do Until imageListMedium IsNot Nothing
GetSystemImageListHandle(iconSize)
Loop
Else
Do Until imageListLarge IsNot Nothing
GetSystemImageListHandle(iconSize)
Loop
End If
Dim icn As Icon = Nothing
'Do Until icn IsNot Nothing
shinfo = GetInfo(path, isDirectory)
Dim iconIndex As Integer = shinfo.iIcon
Dim hIcon As IntPtr = IntPtr.Zero
If iconIndex <> Nothing Then
If iconSize = ImageListIconSize.SmallIcon Then
imageListSmall.GetIcon(iconIndex, CType(&H0, Integer), hIcon)
ElseIf iconSize = ImageListIconSize.MediumIcon Then
imageListMedium.GetIcon(iconIndex, CType(&H0, Integer), hIcon)
Else
imageListLarge.GetIcon(iconIndex, CType(&H0, Integer), hIcon)
End If
End If
If hIcon <> IntPtr.Zero Then
icn = CType(Icon.FromHandle(hIcon).Clone(), Icon)
DestroyIcon(hIcon)
Else
If iconSize = ImageListIconSize.SmallIcon Then
icn = My.Resources.unknownIcon_small
ElseIf iconSize = ImageListIconSize.MediumIcon Then
icn = My.Resources.unknownIcon_medium
Else
icn = My.Resources.unknownIcon_large
End If
End If
'Loop
'CachedIconList.AddCachedIcon(icn, Functions.GetExtensionOfPath(path), iconSize)
Return icn
'End If
End Function
<ComImportAttribute()> _
<GuidAttribute("46EB5926-582E-4017-9FDF-E8998DAA0950")> _
<InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)> _
Interface IImageList
... shortened to get post within character limit ...
<PreserveSig()> _
Function GetIcon(i As Integer, flags As Integer, ByRef picon As IntPtr) As Integer
... shortened to get post within character limit ...
End Interface
<StructLayout(LayoutKind.Sequential)> _
Public Structure IMAGELISTDRAWPARAMS
Public cbSize As Integer
Public himl As IntPtr
Public i As Integer
Public hdcDst As IntPtr
Public x As Integer
Public y As Integer
Public cx As Integer
Public cy As Integer
Public xBitmap As Integer
' x offest from the upperleft of bitmap
Public yBitmap As Integer
' y offset from the upperleft of bitmap
Public rgbBk As Integer
Public rgbFg As Integer
Public fStyle As Integer
Public dwRop As Integer
Public fState As Integer
Public Frame As Integer
Public crEffect As Integer
End Structure
<StructLayout(LayoutKind.Sequential)> _
Public Structure IMAGEINFO
Public hbmImage As IntPtr
Public hbmMask As IntPtr
Public Unused1 As Integer
Public Unused2 As Integer
Public rcImage As RECT
End Structure