Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get Win32Exception in English?

Tags:

c#

.net

exception

I'm trying to get all Exception messages in English, no matter what language is the machine my program running on.

I've manage to get almost all exception messages in English using answers from the following posts: Exception messages in English? and some other solution I've found (like using reflection to change the default CultureInfo). I have specific problem with SocketException, No matter what I'm doing I'm getting it in the default machine's language.

I've created a test program to show the problem: This test program will print Exceptions in default language:

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);
            }
        }
    }
}

This result on my machine the following text:

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'.

On Japanese machine it write all exceptions in Japanese (which I don't understand):

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

(The Japanese '\' looks different in Japanese machine, but when copied to my machine it shown as '\')

So from combining of the answers I've found, I've implemented the following solution, so now it looks like this:

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);
            }
        }
    }
}

Now on Japanese machine it write the file exceptions in English but the Net.socket exceptions are still in Japanese:

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

I've also tested some other exceptions, some exceptions are now shown in English, but not all of them, the socket exceptions are persistent. As you can see, the file exception had been translated to English, but the socket exception is still in Japanese.

I've tested it in almost any .NET framework (from 2.1 to 4.5) still the same.

  • Is there a complete solution for all the exceptions?
  • Did I missed anything?
  • Should I do anything else?
  • Maybe there's other way to run program on foreign machine, and set some environment variable, to get English output?
like image 921
SHR Avatar asked Dec 22 '15 19:12

SHR


2 Answers

I have a solution so I'll upload it here in case someone will need it. If anyone have a better solution I'll be happy to know so please comment.

In case of Win32Exception, we can use FormatMessage and translate the error code to both English and default languages, and replace the default by English. If I take the English without replace, I lose the parameters. so in case the replace failed I'll return the Exception With additional description in English.

Here's my full solution:

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);
            }   
        }
    }
}
like image 156
SHR Avatar answered Nov 13 '22 12:11

SHR


A SocketException is a Win32Exception. Like all other classes that derive from Win32Exception, it gets its message from Windows using Win32Exception.GetErrorMessage(int error), which uses FormatMessage in Kernel32.DLL.

In doing so, the message effectively comes from Windows, not from .NET. Windows will return a message in the Windows display language, and AFAIK, there is nothing you can do about that from within your .NET program.

like image 35
Kris Vandermotten Avatar answered Nov 13 '22 12:11

Kris Vandermotten