为什么这个互操作会崩溃.NET运行时?

时间:2014-06-04 21:53:47

标签: c# crash pinvoke .net-4.5

我正在尝试迭代一些文件并获取它们的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);
    }
}

有人能发现这个错误吗?任何帮助表示赞赏!

1 个答案:

答案 0 :(得分:4)

您正在调用函数的Unicode版本,但是传递结构的ANSI版本。您需要在CharSet结构声明中指定SHFILEINFO

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]