多次调用SHGetFileInfo后无法返回图标句柄的问题求助
问题原因与解决方案
你遇到的是Windows图标句柄资源耗尽的问题:每次调用SHGetFileInfo获取图标句柄后,没有释放原生的hIcon句柄,系统能分配的图标句柄数量有限(约几千个),到3320次时就被耗尽,导致后续调用返回无效的0句柄。
另外你的函数还有个冗余问题:Add-Type被放在函数内部,每次调用都会重复定义IconFromFilePath类型,既浪费资源还可能引发类型重复定义错误,应该把这部分移到函数外部只执行一次。
修正后的代码
1. 先定义互操作类型(仅执行一次)
Add-Type -TypeDefinition ' using System; using System.Drawing; using System.Runtime.InteropServices; public class IconFromFilePath { [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public 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; } [DllImport("shell32.dll", CharSet = CharSet.Unicode)] public static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbSizeFileInfo, uint uFlags); [DllImport("user32.dll", SetLastError = true)] public static extern bool DestroyIcon(IntPtr hIcon); } '
2. 修正后的图标获取函数
Function Icon-FromFilePath { Param($FilePath, [Switch]$LargeIcon) $Flags = 0x100 # SHGFI_ICON If (!$LargeIcon) { $Flags += 0x1 # SHGFI_SMALLICON } $FileInfoStruct = New-Object IconFromFilePath+SHFILEINFO $StructSize = [System.Runtime.InteropServices.Marshal]::SizeOf($FileInfoStruct) $result = [IconFromFilePath]::SHGetFileInfo($FilePath, 0, [ref]$FileInfoStruct, $StructSize, $Flags) if ($result -eq [IntPtr]::Zero -or $FileInfoStruct.hIcon -eq [IntPtr]::Zero) { Write-Warning "无法获取文件 $FilePath 的图标" return $null } try { # 从原生句柄创建托管Icon对象 $icon = [System.Drawing.Icon]::FromHandle($FileInfoStruct.hIcon) # 克隆图标,避免和原生句柄绑定导致失效 $clonedIcon = [System.Drawing.Icon]::new($icon) return $clonedIcon } finally { # 必须释放SHGetFileInfo返回的原生句柄 [void][IconFromFilePath]::DestroyIcon($FileInfoStruct.hIcon) # 释放临时的Icon对象 $icon.Dispose() } }
3. 修正后的测试脚本(添加资源释放)
Add-Type -AssemblyName System.Drawing, System.Windows.Forms $counter = 0 Get-ChildItem C:\Windows\System32 | ForEach-Object { try { $counter++ $icon = Icon-FromFilePath $_.FullName # 使用图标后必须释放托管资源 if ($icon) { $icon.Dispose() } Write-Host "已处理第 $counter 个文件" } catch { Write-Host "Count: $counter" Write-Host "Error: $($_.Exception.Message)" $global:Struct_log = $FileInfoStruct pause } }
关键说明
- 释放原生句柄:
SHGetFileInfo返回的hIcon是Windows原生资源,必须用DestroyIcon手动释放,托管的Icon对象不会自动清理这个原生句柄。 - 克隆图标:
FromHandle创建的Icon对象和原生句柄绑定,直接返回的话,后续释放原生句柄会导致Icon对象失效,因此需要克隆一个独立的托管Icon对象。 - 释放托管资源:使用完
Icon对象后调用Dispose(),避免托管内存泄漏。 - 类型定义移到外部:
Add-Type只执行一次,避免重复定义类型的冗余问题。
内容的提问来源于stack exchange,提问作者therks
相关产品推荐
相关产品推荐

