为啥这个互操作会使 .NET 运行时崩溃?

Posted

技术标签:

【中文标题】为啥这个互操作会使 .NET 运行时崩溃?【英文标题】:Why does this interop crash the .NET runtime?为什么这个互操作会使 .NET 运行时崩溃? 【发布时间】:2014-07-25 17:48:03 【问题描述】:

我正在尝试遍历一些文件并获取它们的 shell 图标;为此,我使用DirectoryInfo.EnumerateFileSystemInfos 和一些P/Invoke 来调用Win32 SHGetFileInfo 函数。但是这两者的结合似乎在内部某个地方破坏了内存,导致了丑陋的崩溃。

我已将我的代码归结为两个类似的测试用例,这两个测试用例似乎都无缘无故地崩溃了。如果我不调用DirectoryInfo.EnumerateFileSystemInfos,则不会出现崩溃;如果我不打电话给SHGetFileInfo,则不会出现崩溃。请注意,我已经在我的代码中删除了FileSystemInfo 对象的实际使用,因为我可以通过迭代它们并一遍又一遍地询问文本文件图标来让它重现。但是为什么呢?

这是我完整的、最小的测试用例。在 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】:

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

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

【讨论】:

天哪,我想知道它是如何知道TStr 应该是什么,但假设它会默认为正确的东西......谢谢! 由于各种原因,该信息不能来自函数的属性。一是您需要能够调用 SizeOf。 感谢您的修复。有谁知道为什么 SHGetFileInfo 会在一台虚拟机上没有此修复程序而崩溃,但在我尝试过的每台其他机器上都可以正常工作? @SteveP 因为您传递的结构太小,所以函数写入超出了它的末尾。这是未定义的行为。到那时,任何事情都可能发生。

以上是关于为啥这个互操作会使 .NET 运行时崩溃?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的 .NET 应用程序从网络驱动器运行时会崩溃?

为啥这个代码运行时会使系统运行的很慢

为啥这个 xamarin Binding Converter 会使应用程序崩溃?

为啥这个应用程序会因运行时错误而崩溃?

Android Marshmallow:在运行时更改权限会使应用程序崩溃

为啥此代码会使 Visual Studio 2015 崩溃?