是否可以阻止 IOException 打开消息框?

Can I prevent the IOException from opening the messagebox?

提问人:EggEgg 提问时间:11/6/2021 更新时间:11/7/2021 访问量:79

问:

//注1:一些本地化的消息是直接翻译的,所以在你的电脑上可能会有所不同,但意思是一样的。
注2:此程序是在 .NET Framework 4.5 版本 x64 下构建的。

我正在设计一个程序,可以静默和自动备份闪存驱动器。
在 copy 方法中,我使用如下代码:

        //Note: the Cout(string) method is a logging method, acturally equivalent to Console.WriteLine(string)
        public void CopyMoveDisk(string fromdir, string todir, int times = 0)
        {
            try
            {
                if (PauseToken)
                {
                    Cout("Copy: paused");
                    STTimer t = new STTimer(o => { CopyMoveDisk(fromdir, todir, times); }, null, 5000, -1);
                    Thread.Sleep(6000);
                    t.Dispose();
                    return;
                }
                else
                {
                    try
                    {
                        if (!Directory.Exists(todir))
                            Directory.CreateDirectory(todir);
                        var filessmall = from f in Directory.GetFiles(fromdir)
                                         where new FileInfo(f).Length <= 100 * Math.Pow(2, 20)
                                         orderby new FileInfo(f).Length
                                         select f;
                        var filesbig = from f in Directory.GetFiles(fromdir)
                                       where new FileInfo(f).Length > 100 * Math.Pow(2, 20)
                                       orderby new FileInfo(f).Length
                                       select f;

                        Cout("Copy: small file region");
                        // Copy the small files (<= 100MiB)
                        foreach (string file in filessmall)
                        {
                            s.CopyingFile = todir + Path.GetFileName(file);
                            if (File.Exists(todir + Path.GetFileName(file)))
                            {
                                if (new FileInfo(file).Length != new FileInfo(todir + Path.GetFileName(file)).Length)
                                {
                                    Cout($"Copy true: {file} => {todir + Path.GetFileName(file)}: Length = {new FileInfo(todir + Path.GetFileName(file)).Length}");
                                    File.Copy(file, todir + Path.GetFileName(file), true);
                                }
                                else
                                {
                                    Cout($"Exist: {file} => {todir + Path.GetFileName(file)}: Length = {new FileInfo(todir + Path.GetFileName(file)).Length}");
                                }
                            }
                            else
                            {
                                Cout($"Copy: {file} => {todir + Path.GetFileName(file)}");
                                File.Copy(file, todir + Path.GetFileName(file), true);
                            }
                            s.CopyingFile = string.Empty;
                        }
                        Cout("Copy: small file region end");

                        Cout($"Copy: directory region: {times} time(s)");
                        // Copy the sub directories
                        foreach (string sub in Directory.GetDirectories(fromdir))
                        {
                            if (!sub.Contains("System Volume Information"))
                            {
                                Cout($"Copy: {sub + "\\"} => {todir + Path.GetFileName(sub) + "\\"}");
                                CopyMoveDisk(sub + "\\", todir + Path.GetFileName(sub) + "\\", times + 1);
                            }
                        }
                        Cout($"Copy: directory region end: {times} time(s)");

                        Cout("Copy: big file region");
                        // Copy the big files (> 100MiB)
                        foreach (string file in filesbig)
                        {
                            s.CopyingFile = todir + Path.GetFileName(file);
                            if (File.Exists(todir + Path.GetFileName(file)))
                            {
                                if (new FileInfo(file).Length != new FileInfo(todir + Path.GetFileName(file)).Length)
                                {
                                    Cout($"Copy true: {file} => {todir + Path.GetFileName(file)}: Length = {new FileInfo(todir + Path.GetFileName(file)).Length}");
                                    File.Copy(file, todir + Path.GetFileName(file), true);
                                }
                                else
                                {
                                    Cout($"Exist: {file} => {todir + Path.GetFileName(file)}: Length = {new FileInfo(todir + Path.GetFileName(file)).Length}");
                                }
                            }
                            else
                            {
                                Cout($"Copy: {file} => {todir + Path.GetFileName(file)}");
                                File.Copy(file, todir + Path.GetFileName(file), true);
                            }
                            s.CopyingFile = string.Empty;
                        }
                        Cout("Copy: big file region end");
                    }
                    catch (Exception ex)
                    {
                        Cout($"Copy: -----Exception {ex} {Environment.NewLine}----------");
                    }
                }
            }
            catch (Exception ex)
            {
                Cout($"Copy: -----Exception {ex} {Environment.NewLine}{s}{Environment.NewLine}----------");
            }
        }

查看代码后,让我们想象一个情况:
用户插入 SD 卡读卡器。复制文件时,SD 卡以某种方式断开连接,但读卡器仍保持插入状态(可能是因为接触不良)。
每当您在没有卡的情况下插入读卡器时,Windows 资源管理器都会警告您“请将磁盘插入'SD 卡 (E:\)'。
但是当这种情况恰好出现在这个程序上时,该方法并没有沉浸地抛出异常。相反,它显示了一个消息框,如下所示:

|---USBBackup.exe - 暂无磁盘|
|驱动器中没有磁盘。请将磁盘插入驱动器 E: |
|取消| |重试| |继续|

如果我单击取消按钮(或继续按钮?),则会抛出异常:

System.IO.IOException:驱动器中的软盘不正确。
将 %2(卷序列号:%3)插入驱动器 %1。
在 System.IO.__Error.WinIOError (Int32 errorCode, String maybeFullPath) 在 System.IO.File.InternalCopy (字符串 sourceFileName、 字符串 destFileName、 布尔覆盖、布尔 checkHost) 在 USBBackup.Form1.CopyMoveDisk (字符串 fromdir、 字符串 todir、 Int32 次)

注意3:不知何故,消息框不会显示在我的Windows 10 VM上。
相反,它会直接抛出一个 exetion:

System.IO.IOException:设备未就绪。
在 System.IO.__Error.WinIOError (Int32 errorCode, String maybeFullPath) 在 System.IO.File.InternalCopy (字符串 sourceFileName、 字符串 destFileName、 布尔覆盖、布尔 checkHost) 在 USBBackup.Form1.CopyMoveDisk (字符串 fromdir、 字符串 todir、 Int32 次)

//但它显示在我的 Windows 7 VM 上。 //或者可能是因为我在 Win10 VM 上安装了 .Net Framework 4.8,但在 Win7 VM 上安装了 4.5。

既然我不希望这个程序打扰用户,我该怎么做才能避免显示消息框?

C# .NET IO NET-4.5

评论

1赞 EggEgg 11/6/2021
是的。由于 try-catch 块,我认为它不应该显示消息框。它不是普通的 JIT 调试消息框。请再读一遍整篇文章。
0赞 EggEgg 11/7/2021
问题解决了。实际上,我希望你写一个正式的答案。多谢。

答:

2赞 Steeeve 11/7/2021 #1

您需要调用 SetErrorMode API 来禁用系统消息框,然后将其重新设置。
创建一个内部类:
SEM_FAILCRITICALERRORSNativeMethods

internal static class NativeMethods
{
    [DllImport("kernel32.dll")]
    internal static extern ErrorModes SetErrorMode(ErrorModes uMode);

    [Flags]
    internal enum ErrorModes : uint 
    {
        SYSTEM_DEFAULT             = 0x0,
        SEM_FAILCRITICALERRORS     = 0x0001,
        SEM_NOALIGNMENTFAULTEXCEPT = 0x0004,
        SEM_NOGPFAULTERRORBOX      = 0x0002,
        SEM_NOOPENFILEERRORBOX     = 0x8000
    }
}

示例用法:

void BackupFiles()
{
    var prevMode = NativeMethods.SetErrorMode(NativeMethods.SEM_FAILCRITICALERRORS);
    try
    {
        // attempt to copy the files
        // from the removable device
    }
    finally
    {
        NativeMethods.SetErrorMode(prevMode);
    }
}