C#中使用API(SHFileOperation)进行文件操作,特别说明了回收站相关参数

项目中使用了磁盘阵列柜,每秒有上百兆的数据存入磁盘,这就有了从磁盘删除文件的需求。为了满足这一需求,我做了一个用于删除过期数据的系统服务。说来这个东西本身是很简单的,但是由于数据量的巨大价值磁盘阵列空间容量的巨大(8T,1T=1024G),在实际操作中也遇到了不少麻烦。

需求很简单,遍历磁盘指定目录下的所有目录和文件,根据一个比较规则判定文件是否过期,如果过期就删除。最初的设计也很简单,三个步骤:遍历 -> 过期检测 -> 删除。有了需求和程序框架设计,于是开始实施。

在C#中,DirectoryInfo和FileInfo为我们提供了足够的方法,在程序第一次成型的时候我主要使用了下面的方法:
遍历:DirectoryInfo.GetDirectories(),DirectoryInfo.GetFiles()
删除:DirectoryInfo.Delete(),FileInfo.Delete()

本机使用时效果很好,但实际上服务器运行就出现了问题:磁盘过大,文件过多,导致执行效率极其缓慢,甚至无法遍历出文件夹和文件,删除一个文件就需要好几分钟。当然,这和磁盘I/O有关,需要在有限的I/O下提高效率。

为了提高效率,改用API的方式。使用SHFileOperation来进行文件、文件夹的删除操作。代码见下:

using System;
using System.Runtime.InteropServices;

namespace FileDirectoryAPI
{
    public class DeleteAPI
    {
        [DllImport("shell32.dll")]
        private static extern int SHFileOperation(ref SHFILEOPSTRUCT lpFileOp);

        /// <summary>
        /// 执行删除。成功返回空,否则返回错误信息。
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public static string Delete(string path)
        {
            SHFILEOPSTRUCT lpFileOp = new SHFILEOPSTRUCT();
            lpFileOp.wFunc = wFunc.FO_DELETE;
            lpFileOp.pFrom = path + "\0";
            lpFileOp.fFlags = FILEOP_FLAGS.FOF_NOCONFIRMATION | FILEOP_FLAGS.FOF_NOERRORUI | FILEOP_FLAGS.FOF_SILENT;
            lpFileOp.fFlags &= ~FILEOP_FLAGS.FOF_ALLOWUNDO;
            lpFileOp.fAnyOperationsAborted = false;

            int n = SHFileOperation(ref lpFileOp);
            if (n == 0) return string.Empty;

            string tmp = GetErrorString(n);

            //.av 文件正常删除了但也提示 402 错误,不知道为什么。屏蔽之。
            if ((path.ToLower().EndsWith(".av") && n.ToString("X") == "402")) return string.Empty;

            return string.Format("{0}({1})", tmp, path);
        }

        private struct SHFILEOPSTRUCT
        {
            public IntPtr hwnd;
            public wFunc wFunc;
            public string pFrom;
            public string pTo;
            public FILEOP_FLAGS fFlags;
            public bool fAnyOperationsAborted;
            public IntPtr hNameMappings;
            public string lpszProgressTitle;
        }

        private enum wFunc
        {
            FO_MOVE = 0x0001,
            FO_COPY = 0x0002,
            FO_DELETE = 0x0003,
            FO_RENAME = 0x0004
        }

        private enum FILEOP_FLAGS
        {
            FOF_MULTIDESTFILES = 0x0001, //pTo 指定了多个目标文件,而不是单个目录
            FOF_CONFIRMMOUSE = 0x0002,
            FOF_SILENT = 0x0044, // 不显示一个进度对话框
            FOF_RENAMEONCOLLISION = 0x0008, // 碰到有抵触的名字时,自动分配前缀
            FOF_NOCONFIRMATION = 0x10, // 不对用户显示提示
            FOF_WANTMAPPINGHANDLE = 0x0020, // 填充 hNameMappings 字段,必须使用 SHFreeNameMappings 释放
            FOF_ALLOWUNDO = 0x40, // 允许撤销
            FOF_FILESONLY = 0x0080, // 使用 *.* 时, 只对文件操作
            FOF_SIMPLEPROGRESS = 0x0100, // 简单进度条,意味者不显示文件名。
            FOF_NOCONFIRMMKDIR = 0x0200, // 建新目录时不需要用户确定
            FOF_NOERRORUI = 0x0400, // 不显示出错用户界面
            FOF_NOCOPYSECURITYATTRIBS = 0x0800, // 不复制 NT 文件的安全属性
            FOF_NORECURSION = 0x1000 // 不递归目录
        }

        //更多错误代码见:ms-help://MS.MSDNQTR.v90.chs/shellcc/platform/shell/reference/functions/shfileoperation.htm
        private static string GetErrorString(int n)
        {
            if (n == 0) return string.Empty;

            string code = n.ToString("X").ToUpper();
            switch (code)
            {
                case "74":
                    return "The source is a root directory, which cannot be moved or renamed.";
                case "76":
                    return "Security settings denied access to the source.";
                case "7C":
                    return "The path in the source or destination or both was invalid.";
                case "10000":
                    return "An unspecified error occurred on the destination.";
                case "402":
                    return "An unknown error occurred. This is typically due to an invalid path “
+"in the source or destination. This error does not occur on Windows Vista and later.";
                default:
                    return code;
            }
        }
    }
}

使用API之后,效率明显提高,遍历和删除文件速度明显加快。但尤其值得注意的是:SHFILEOPSTRUCT.fFlags属性,它指定了一些删除操作中的其他属性。这里我想特别提出的是FOF_ALLOWUNDO,望文生义,允许撤销那应当就是删除到回收站了。MSDN上对这个属性的解释是:

Preserve undo information, if possible. Operations can be undone only from the same process that performed the original operation. If, despite earlier warnings against doing so, pFrom does not contain fully-qualified path and file names, this flag is ignored.

它指出:

  1. 如果可能,系统将默认这些操作可以撤销。
  2. 如果文件、文件夹路径不是全路径,将忽略此属性。

可以看出,这段文字并没有很具体地介绍这个属性。经过我的实际实践得出:
设置 lpFileOp.fFlags |= FOF_ALLOWUNDO和不设置这个属性的效果是一样的,即默认会删除到回收站。
要想直接删除,不进入回收站,我们必须设置lpFileOp.fFlags &= ~FILEOP_FLAGS.FOF_ALLOWUNDO;这一点尤为重要。

当然,这个API除了可以进行删除操作之外,还可以进行其他的文件、文件夹操作,此处不表。

原文地址:https://www.cnblogs.com/uonun/p/1698969.html