为什么这个互操作会使.net运行时崩溃?
本文关键字:net 运行时 崩溃 互操作 为什么 | 更新日期: 2023-09-27 18:06:40
我正在尝试迭代一些文件并获取它们的shell图标;为了实现这一点,我使用DirectoryInfo.EnumerateFileSystemInfos
和一些P/Invoke来调用Win32 SHGetFileInfo
函数。但是两者的结合似乎会在内部某个地方破坏内存,导致难看的崩溃。
我把我的代码归结为两个类似的测试用例,这两个用例似乎都无缘无故地崩溃了。如果我不调用DirectoryInfo.EnumerateFileSystemInfos
,不会出现崩溃;如果我不调用SHGetFileInfo
,不会出现崩溃。请注意,我已经在代码中删除了FileSystemInfo
对象的实际use,因为我可以通过迭代它们并反复请求文本文件图标来复制它。但是为什么呢?
这里是我的完整的,最小的测试用例。在VS调试器下运行它们以确保没有启用优化:
using System;
using System.Linq;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media.Imaging;
namespace IconCrashRepro
{
// Compile for .NET 4 (I'm using 4.5.1).
// Also seems to fail in 3.5 with GetFileSystemInfos() instead of EnumerateFileSystemInfos()
public class Program
{
// Compile for .NET 4 (I'm using 4.5.1)
public static void Main()
{
// Keep a list of the objects we generate so
// that they're not garbage collected right away
var sources = new List<BitmapSource>();
// Any directory seems to do the trick, so long
// as it's not empty. Within VS, '.' should be
// the Debug folder
var dir = new DirectoryInfo(@".");
// Track the number of iterations, just to see
ulong iteration = 0;
while (true)
{
// This is where things get interesting -- without the EnumerateFileSystemInfos,
// the bug does not appear. Without the call to SHGetFileInfo, the bug also
// does not appear. It seems to be the combination that causes problems.
var infos = dir.EnumerateFileSystemInfos().ToList();
Debug.Assert(infos.Count > 0);
foreach (var info in infos)
{
var shFileInfo = new SHFILEINFO();
var result = SHGetFileInfo(".txt", (uint)FileAttributes.Normal, ref shFileInfo, (uint)Marshal.SizeOf(shFileInfo), SHGFI_USEFILEATTRIBUTES | SHGFI_ICON | SHGFI_SMALLICON);
//var result = SHGetFileInfo(info.FullName, (uint)info.Attributes, ref shFileInfo, (uint)Marshal.SizeOf(shFileInfo), SHGFI_USEFILEATTRIBUTES | SHGFI_ICON | SHGFI_SMALLICON);
if (result != IntPtr.Zero && shFileInfo.hIcon != IntPtr.Zero)
{
var bmpSource = Imaging.CreateBitmapSourceFromHIcon(
shFileInfo.hIcon,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
sources.Add(bmpSource);
// Originally I was releasing the handle, but even if
// I don't the bug occurs!
//DestroyIcon(shFileInfo.hIcon);
}
// Execution fails during Collect; if I remove the
// call to Collect, execution fails later during
// CreateBitmapSourceFromHIcon (it calls
// AddMemoryPressure internally which I suspect
// results in a collect at that point).
GC.Collect();
++iteration;
}
}
}
public static void OtherBugRepro()
{
// Rename this to Main() to run.
// Removing any single line from this method
// will stop it from crashing -- including the
// empty if and the Debug.Assert!
var sources = new List<BitmapSource>();
var dir = new DirectoryInfo(@".");
var infos = dir.EnumerateFileSystemInfos().ToList();
Debug.Assert(infos.Count > 0);
// Crashes on the second iteration -- says that
// `infos` has been modified during loop execution!!
foreach (var info in infos)
{
var shFileInfo = new SHFILEINFO();
var result = SHGetFileInfo(".txt", (uint)FileAttributes.Normal, ref shFileInfo, (uint)Marshal.SizeOf(shFileInfo), SHGFI_USEFILEATTRIBUTES | SHGFI_ICON | SHGFI_SMALLICON);
if (result != IntPtr.Zero && shFileInfo.hIcon != IntPtr.Zero)
{
if (sources.Count == 1000) { }
}
}
}
[StructLayout(LayoutKind.Sequential)]
private 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;
}
private const uint SHGFI_ICON = 0x100;
private const uint SHGFI_LARGEICON = 0x0;
private const uint SHGFI_SMALLICON = 0x1;
private const uint SHGFI_USEFILEATTRIBUTES = 0x10;
[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
private static extern IntPtr SHGetFileInfo([MarshalAs(UnmanagedType.LPWStr)] string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbSizeFileInfo, uint uFlags);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool DestroyIcon(IntPtr hIcon);
}
}
有人能发现这个bug吗?任何帮助都是感激的!
调用的是Unicode版本的函数,但传递的是ANSI版本的结构体。您需要在SHFILEINFO
结构体声明中指定CharSet
。
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]