Quantcast
Channel: VBForums - API
Viewing all articles
Browse latest Browse all 168

Possible memory problem with using Windows API to get icons

$
0
0
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.

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:
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

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


Viewing all articles
Browse latest Browse all 168

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>