如何获得Win32异常?

5
我试图获取所有的英文Exception消息,无论程序运行的机器使用何种语言。我已经通过以下帖子Exception messages in English?以及其他某些解决方案(例如使用反射来更改默认的CultureInfo)来几乎获得了所有异常消息的英文版本。但是我在处理SocketException时遇到了问题,无论我做什么都会显示为默认机器语言。我创建了一个测试程序来展示这个问题:该测试程序将打印默认语言中的异常消息。
using System;
using System.Text;
using System.Threading;
using System.IO;
using System.Net.Sockets;
using System.Reflection;
using System.Globalization;

namespace TestApp
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                //I'm not listening on the following port:
                TcpClient s = new TcpClient("localhost", 2121);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Socket exception: " + ex.Message);
            }
            try
            {
                //the following file doesn't exists:
                File.ReadAllText("filenotexist.txt");
            }
            catch (Exception ex)
            {
                Console.WriteLine("File exception: " + ex.Message);
            }
        }
    }
}

这是我机器上的结果,下面是文本内容:
H:\Shared>Test-def.exe
Socket exception: No connection could be made because the target machine actively refused it 127.0.0.1:2121
File exception: Could not find file 'H:\Shared\filenotexist.txt'.

在日本机器上,所有的异常信息都是用日语编写的(我不懂日语):

Z:\>Test-def.exe
Socket exception: 対象のコンピューターによって拒否されたため、接続できませんでした。 127.0.0.1:2121
File exception: ファイル 'Z:\filenotexist.txt' が見つかりませんでした。

“日本的反斜杠”在日本机器上显示与我的机器上显示不同,但复制到我的机器上时会显示为“\”。

综合各种答案,我已经实现了以下解决方案,现在它看起来像这样:

namespace TestApp
{
    class Program
    {
        //will change CultureInfo to English, this should change all threads CultureInfo to English. 
        public static void SetEnglishCulture()
        {
            CultureInfo ci = new CultureInfo("en-US");
            //change CultureInfo for current thread:
            Thread.CurrentThread.CurrentUICulture = ci;
            Thread.CurrentThread.CurrentCulture = ci;

            //change CultureInfo for new threads:
            Type t = typeof(CultureInfo);
            try
            {
                t.InvokeMember("s_userDefaultCulture", BindingFlags.SetField | BindingFlags.NonPublic | BindingFlags.Static, null, ci, new object[] { ci });
                t.InvokeMember("s_userDefaultUICulture", BindingFlags.SetField | BindingFlags.NonPublic | BindingFlags.Static, null, ci, new object[] { ci });
            }
            catch { }
            try
            {
                t.InvokeMember("m_userDefaultCulture", BindingFlags.SetField | BindingFlags.NonPublic | BindingFlags.Static, null, ci, new object[] { ci });
                t.InvokeMember("m_userDefaultUICulture", BindingFlags.SetField | BindingFlags.NonPublic | BindingFlags.Static, null, ci, new object[] { ci });
            }
            catch { }
        }
        static void Main(string[] args)
        {
            //first thing: set CultureInfo to English:
            SetEnglishCulture();
            try
            {
                //I'm not listening on the following port:
                TcpClient s = new TcpClient("localhost", 2121);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Socket exception: " + ex.Message);
            }
            try
            {
                //the following file doesn't exists:
                File.ReadAllText("filenotexist.txt");
            }
            catch (Exception ex)
            {
                Console.WriteLine("File exception: " + ex.Message);
            }
        }
    }
}

现在在日本机器上,文件异常是用英文写的,但Net.socket异常仍然是用日语写的:

Z:\>Test-en.exe
Socket exception: 対象のコンピューターによって拒否されたため、接続できませんでした。 127.0.0.1:2121
File exception: Could not find file 'Z:\filenotexist.txt'.

我还测试了一些其他的异常情况,有些异常现在显示为英文,但不是所有情况,套接字异常仍然是日语。正如您所见,文件异常已经被翻译成了英语,但是套接字异常仍然是日语。

我几乎在任何 .NET 框架中都进行了测试(从 2.1 到 4.5),但结果都是一样的。

  • 是否有一个完整的解决方案适用于所有的异常情况?
  • 我有漏掉什么吗?
  • 我应该做些什么?
  • 也许还有其他的方法可以在外国机器上运行程序,并设置一些环境变量来获取英语输出吗?
3个回答

4
我有一个解决方案,如果有人需要,我会在这里上传它。 如果有更好的解决方案,欢迎评论留言。
如果遇到 Win32Exception,我们可以使用 FormatMessage 将错误代码翻译为英语和默认语言,并用英语替换默认语言。 如果我直接采用英语而不进行替换,我将失去参数。因此,如果替换失败,我将返回带有附加英语描述的异常。
以下是我的完整解决方案:
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Net.Sockets;
using System.Globalization;
using System.Reflection;
using System.ComponentModel;
using System.Runtime.InteropServices;

namespace TestCulture
{
    class Program
    {
        static void SetEnglishCulture()
        {
            CultureInfo ci = new CultureInfo("en-US");
            Thread.CurrentThread.CurrentCulture = ci;
            Thread.CurrentThread.CurrentUICulture = ci;
            Type type = typeof(CultureInfo);
            try
            {
                type.InvokeMember("s_userDefaultCulture", BindingFlags.SetField | BindingFlags.NonPublic | BindingFlags.Static, null, ci, new object[] { ci });
                type.InvokeMember("s_userDefaultUICulture", BindingFlags.SetField | BindingFlags.NonPublic | BindingFlags.Static, null, ci, new object[] { ci });
            } catch { }
            try
            {
                type.InvokeMember("m_userDefaultCulture", BindingFlags.SetField | BindingFlags.NonPublic | BindingFlags.Static, null, ci, new object[] { ci });
                type.InvokeMember("m_userDefaultUICulture", BindingFlags.SetField | BindingFlags.NonPublic | BindingFlags.Static, null, ci, new object[] { ci });
            } catch { }
        }
        [DllImport("kernel32.dll")]
        static extern uint FormatMessage(uint dwFlags, IntPtr lpSource, uint dwMessageId, uint dwLanguageId, StringBuilder lpBuffer, uint nSize, IntPtr Arguments);
        public static string Win32ExceptionInEnglish(Win32Exception ex)
        {
            const int nCapacity = 820; // max error length
            const uint FORMAT_MSG_FROM_SYS = 0x01000;
            const uint engLangID = (0x01<<10) | 0x09;
            const uint defLangID = 0x0;
            StringBuilder engSb = new StringBuilder(nCapacity);
            StringBuilder defSb = new StringBuilder(nCapacity);
            FormatMessage(FORMAT_MSG_FROM_SYS,IntPtr.Zero, (uint)ex.ErrorCode, defLangID, defSb, nCapacity, IntPtr.Zero);
            FormatMessage(FORMAT_MSG_FROM_SYS,IntPtr.Zero, (uint)ex.ErrorCode, engLangID, engSb, nCapacity, IntPtr.Zero);
            string sDefMsg = defSb.ToString().TrimEnd(' ','.','\r','\n');
            string sEngMsg = engSb.ToString().TrimEnd(' ','.','\r','\n');
            if(sDefMsg == sEngMsg) //message already in English (or no english on machine?)
            {
                //nothing left to do:
                return ex.Message;
            }
            else
            {
                string msg = ex.Message.Replace(sDefMsg,sEngMsg);
                if (msg == ex.Message)
                {
                    //replace didn't worked, can be message with arguments in the middle.
                    //I such as case print both: original and translated. to not lose the arguments.
                    return ex.Message + " (In English: " + sEngMsg + ")";
                }
                else 
                {
                    //successfuly replaced!
                    return msg;
                }
            }       
        }

        public static void Main(string[] args)
        {           
            SetEnglishCulture();
            try {
                // generate any exception ...
                const int notListenningPort = 2121;
                new TcpClient("localhost", notListenningPort);
            }
            catch(Win32Exception ex)//first try to cach win32 Exceptions
            {
                Console.WriteLine("W32 Exception: " + Win32ExceptionInEnglish(ex));
            }
            catch(Exception ex)//this fit to the rest .NET exceptions which affected by CultureInfo
            {
                Console.WriteLine("Exception: " +ex.Message);
            }   
        }
    }
}

我在VS2008(.net 3.5)和VS2015(.net 4.5.2)中都没有得到任何英文文本。defSb包含德文文本,而engSb则为空。 - Tobias Knauss
defLangID是语言标识符,我在示例中使用了eng-us,但看起来您的机器不支持它,但也许支持其他英语语言?也许是eng-uk?您可以尝试使用其他区域设置,在此处查看完整列表:https://msdn.microsoft.com/en-us/library/windows/desktop/dd318693(v=vs.85).aspxhttps://msdn.microsoft.com/en-us/library/windows/desktop/dd318693(v=vs.85).aspx - SHR

1

你可能需要将打印错误信息到控制台的代码行包装在一个单独的线程中,并将其区域设置为英语,因为框架异常代码会根据当前线程的区域设置从其资源中加载错误消息。

下面是一段相关的代码:

    static void Main(string[] args) {

    try {
        TcpClient c = new TcpClient("localhost", 1234);
    }
    catch (Exception ex) {
        // thread that logs exception message to console
        Thread logger = new Thread(new ParameterizedThreadStart(PrintException));
        logger.CurrentCulture = new System.Globalization.CultureInfo("en-US");
        logger.Start(ex);
    }


}

private static void PrintException(object ex) {
    Console.WriteLine("Error: " + ((Exception)ex).Message);
}

你试过这个吗?消息不是由框架加载的,而是由Windows加载的。它不来自.NET资源。并且该消息在构造时设置在异常中。我怀疑在已设置en-US文化的线程上读取Message属性是否会自动将其中的日语文本翻译成英语。 - Kris Vandermotten
我在代码中更改了所有线程的“CurrentCulture”,但没有帮助。如果您想保留两种语言,则额外的线程可能会有所帮助。 - SHR

1
一个SocketException是一个Win32Exception。与所有从Win32Exception派生的类一样,它使用Win32Exception.GetErrorMessage(int error)从Windows获取其消息,该方法在Kernel32.DLL中使用FormatMessage
这样做,消息实际上来自于Windows,而不是.NET。 Windows将返回Windows显示语言中的消息,据我所知,您无法在.NET程序内部对此进行任何操作。

1
我已经找到(并在编辑中上传)了解决该问题的方法,你的评论非常有帮助。再次感谢。如果可以的话,我会再次给你点赞!(我刚意识到我可以在你的其他回答上点赞...) - SHR
不错的想法,但是Win32Exception.GetErrorMessage(int error)返回的是已经存在于Message属性中的本地化文本。 - TomB

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接