Strange localization issue with ArgumentOutOfRangeException - c#

This "problem" will probably be hard to test/reproduce for someone whose Windows version is installed with English as the only language. I'm doing this on a Windows 7 machine installed with "Danish (Denmark)" ("da-DK" culture), .NET version 4.5.
I experience the following surprising behavior. The code:
Console.WriteLine("Now using intalled UI culture");
Thread.CurrentThread.CurrentUICulture = CultureInfo.InstalledUICulture;
Console.WriteLine(new ArgumentNullException().Message);
Console.WriteLine(new ArgumentOutOfRangeException().Message);
Console.WriteLine();
Console.WriteLine("Now using invariant culture");
Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture;
Console.WriteLine(new ArgumentNullException().Message);
Console.WriteLine(new ArgumentOutOfRangeException().Message);
produces the following output:
Now using intalled UI culture
En værdi må ikke være null.
Det angivne argument lå uden for det gyldige værdiområde.
Now using invariant culture
Value cannot be null.
Det angivne argument lå uden for det gyldige værdiområde.
Explanation: In the first section, we see two localized exception messages as expected when the UI thread is Danish. But in the second section we see one English ("invariant") message, and one Danish message, even if the UI culture is not related to Danish anymore.
I tried this with a lot of other mscorlib exception classes as well, and it looks like only System.ArgumentOutOfRangeException does not respect the change of UI culture.
Why is ArgumentOutOfRangeException different than other exceptions in this respect?
(It would be interesting to hear if others (other versions of OS and Framework, and/or other languages) see the same behavior.)
PS! Pasting the following lines into PowerShell (which targets another .NET version) on the same machine gives the opposite problem with ArgumentOutOfRangeException?!
[Threading.Thread]::CurrentThread.CurrentUICulture = [Globalization.CultureInfo]::InstalledUICulture; New-Object ArgumentNullException;
[Threading.Thread]::CurrentThread.CurrentUICulture = [Globalization.CultureInfo]::InstalledUICulture; New-Object ArgumentOutOfRangeException;
[Threading.Thread]::CurrentThread.CurrentUICulture = [Globalization.CultureInfo]::InvariantCulture; New-Object ArgumentNullException;
[Threading.Thread]::CurrentThread.CurrentUICulture = [Globalization.CultureInfo]::InvariantCulture; New-Object ArgumentOutOfRangeException;
(Keep the long lines to make sure both statetments of each line are executed on the same thread by PowerShell.)

If you look into the source code of both exceptions you can see the problem fast. Here is the constructor of ArgumentNullException:
public ArgumentNullException() :
base(Environment.GetResourceString("ArgumentNull_Generic")) {
base.SetErrorCode(-2147467261);
}
As you can see the error message is loaded everytime out of the ressources. While the construcor of ArgumentOutOfRangeException looks like this:
public ArgumentOutOfRangeException() : base(RangeMessage) {
base.SetErrorCode(-2146233086);
}
Where RangeMessage is a private static property:
private static string RangeMessage {
get {
if(_rangeMessage == null) {
_rangeMessage = Environment.GetResourceString(
"Arg_ArgumentOutOfRangeException");
}
return _rangeMessage;
}
}
Here you can see is the problem the error message is cached. So it won't be updated. The only way I see is to access that private static volatile string _rangeMessage; with reflection and set it back to null.

Related

IsolatedStorageFileStream lead to assert failure

I decided to use isolated storage for temporary files:
using (IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForDomain())
{
using (IsolatedStorageFileStream userStream = new IsolatedStorageFileStream("UserSettings.set", FileMode.Create, isoStore))
{
}
}
I taken this code from example that works on this computer. And minimal project with just this code also sucessfully runs.
But while performing IsolatedStorageFileStream constructor in my current project following message appears:
MyApp.exe - Assert Failure
Expression: [mscorlib recursive resource lookup bug]
Desctiprion: Infinite recurion during resource lookup within mscorlib.
This may be a bug in mscorlib, or potentially in certain extensibility
points such as assembly resolve events or CultureInfo names.
Resource name: Serurity_Generic
And in this message I can see pretty big stack trace (it starts with call of IsolatedStorageFileStream constructor):
Also I can't catch exception from this code.
Looks like error happened in System.Environment.ResourceHelper.GetResourceStringCode().
What can be a possible reason for this? I can't find anything on this topic.
Deleting C:\Users\user\AppData\Local\IsolatedStorage folder doesn't solve the problem (I know for sure that there is only my folders).
Looking at the stacktrace, the base issue comes from LongPathFile.GetLength. There could be some invalid characters in the path, or maybe a permission issue. Hard to tell without the exact error code.
Then, .NET tries to load the error message related to the error code, and at some point steps into Costura.AssemblyLoader (this must be your code or some library you're referencing). It looks like that AssemblyLoader subscribed to the AssemblyResolve event, and is doing a poor job fetching the correct assembly because it actually causes an infinite recursion.
In a nutshell: fix that assembly loader, then you'll be able to get the real error.
In my case exactly this happened on some of the machines when my code tried to create a new file in IsolatedStorage. As correctly mentioned in InfernumDeus's comment it happens only when the machine has non-English active locale set. Following code fixed the issue in my case:
var currentCulture = Thread.CurrentThread.CurrentCulture;
var currentUiCulture = Thread.CurrentThread.CurrentUICulture;
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture;
var traceFileStream = new IsolatedStorageFileStream("system_log.txt", FileMode.OpenOrCreate, FileAccess.Write);
Thread.CurrentThread.CurrentCulture = currentCulture;
Thread.CurrentThread.CurrentUICulture = currentUiCulture;

Why does my localization only work on my Development-Machine?

I've localized my App in two languages (English and German) with the MulitlingualAppToolkit 4.0. English is the base language, while german is a translation based on the english one.
The translations are stored as resw-file inside folder "strings.en" and "strings.de".
In App.xaml.cs App() I set the culture like this:
Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = GetCurrentCulture();
CultureInfo.CurrentUICulture = GetCurrentCulture();
private CultureInfo GetCurrentCulture()
{
var cultureName = new DateTimeFormatter("longdate", new[] {"US"}).ResolvedLanguage;
return new CultureInfo(cultureName);
}
(I got this quiet weird way to the regional-culture in Windows 10 from this article https://www.pedrolamas.com/2015/11/02/cultureinfo-changes-in-uwp/ since I recognized that CultureInfo.CurrentCulture and CultureInfo.CurrentUICulture are always "en-EN" no matter what i configurate in my machines regional- and language-settings)
To check if PrimaryLanguageOverride works as expected, I added a TextBox by the name of tbTest on my first Page and a button linkt to this event:
private void Button_Click(object sender, RoutedEventArgs e)
{
Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = tbTest.Text;
Frame.Navigate(this.GetType(), 0);
System.IO.File.AppendAllText(System.IO.Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, "PrimaryLanguageOverride.txt"),
DateTime.Now + " - Actual PrimaryLanguageOverride:\n " + WIndows.Globalization.ApplicationLanguages.PrimaryLanguageOverride);
}
catch(Exception ex) { Helper.Log.LogUnhandledError(ex); }
}
Now comes the weird stuff:
When Debugging, or executing the App from my Development-Machine, everything works as expected, but when I make an appx-bundle and install it on another (Windows10-Desktop) device, the App does not recognize the its Language.
In my situation, the device is set to german, regional as well as its language. Also when using the test-procedure, it defenetively gets the string I set , as long as it's a valid culture-name, e.g.: "en-EN", "en-US", "de-DE", "de", "en" (all of these are working fine on my development machine) if it's an invalid string, I get an exception, with a log-entry in my unhandled-error-log. It refresh the Page, and even writes the new-set language in my PrimaryLanguageOverride-Log, but it doesn't change any text I did translate.
So my question is, is this a common Issue (since I recognized in UWP/Win10 the culture-system is a little messed up) or do I use the wrong procedure to override the App-Culture?
This is an issue with AppXBundling. When Installing bundles, it checks with the current OS for the installed Language packs and copies the relevant language resources from the bundle and omits the other language files. The objective of a single bundle is to copy necessary resources and build the application and therefore the languages which are not in the system are considered irrelevant. As a fix you could stop generating single bundles and create package for each CPU architecture. Check this for more info

CurrentThread.CurrentCulture and Unit Testing

I have a bug report where double.Parse(input) is throwing the following exception with the input "0.69803923368454":
FormatException: Unknown char: .
System.Double.Parse (System.String s, NumberStyles style, IFormatProvider provider) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System/Double.cs:209)
System.Double.Parse (System.String s) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System/Double.cs:180)
After some searching it seems that this issue occurs when the default culture does not support the decimal character . (see float.Parse fails on decimals and commas);
I need to create a unit test which reproduces this issue by forcing a different default culture for the duration of the test. Naturally this must not interfere with any existing unit test. I am using the unit testing framework which is provided with Visual Studio.
Here is what I have tried, but unfortunately this does not cause the reported error to occur:
[TestMethod]
private void DoubleParseWithCultureOverride() {
var restoreCulture = Thread.CurrentThread.CurrentCulture;
var restoreUICulture = Thread.CurrentThread.CurrentUICulture;
try {
// Arrange
Thread.CurrentThread.CurrentCulture = new CultureInfo("ko-KR");
Thread.CurrentThread.CurrentUICulture = new CultureInfo("ko-KR");
// Act
double value = double.Parse("0.69803923368454");
// Assert
Assert.AreEqual(0.69803923368454, value);
}
finally {
Thread.CurrentThread.CurrentCulture = restoreCulture;
Thread.CurrentThread.CurrentUICulture = restoreUICulture;
}
}
I was expecting the above unit test to fail (i.e. become red in the test explorer panel), but it passed. At the moment I am purely attempting to force the error with standard Mono/.NET usage. I intend to replace the "Act" section with application specific logic.
You've just picked a culture which happens to use . as a decimal point:
var culture = new CultureInfo("ko-KR");
Console.WriteLine(culture.NumberFormat.NumberDecimalSeparator); // Prints .
I typically use French (fr-FR) for this - and that does fail with your current code.
Alternatively, you could construct your own CultureInfo specifically for testing, with whatever separator you want.
For testing like this, you might want a simpler way of setting the culture, too. Options:
Write a method taking an action to execute "within" a culture, then call it as:
ExecuteInCulture("fr-Fr", () =>
{
// Parse a double, or whatever
});
Create an IDisposable implementation which sets the culture and restores it on Dispose:
using (CultureHelper.SetCulture("fr-FR"))
{
// Parse a double, or whatever
}
The former approach is probably cleaner - it's not like you've really got a "resource" here.

Vista TaskDialog Wrapper: Unable to find an entry point named 'TaskDialogIndirect' in DLL 'ComCtl32'

I'm try to use Vista TaskDialog Wrapper and Emulator and I'm getting the following exception:
"Unable to find an entry point named 'TaskDialogIndirect' in DLL 'ComCtl32'."
...in a simple Console application:
class Program
{
[STAThread]
static void Main(string[] args)
{
System.Threading.Thread.CurrentThread.CurrentUICulture = System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US");
PSTaskDialog.cTaskDialog.MessageBox(
"MessageBox Title",
"The main instruction text for the message box is shown here.",
"The content text for the message box is shown here and the text willautomatically wrap as needed.",
PSTaskDialog.eTaskDialogButtons.YesNo,
PSTaskDialog.eSysIcons.Information
);
}
}
What am I doing wrong?
UPDATE:
Actually, I'm working on an Excel plugin using excel-dna. How can I control what dll Excel loads?
http://exceldna.codeplex.com/discussions/286990#post728888
I haven't been at Office programming in a while, but my guess is that Excel loads both versions of comctl32, so you may need to use the Activation Context API to direct your code to the version that includes TaskDialog. Some ideas for fixing the problem (not solutions as such):
For test purposes, make a temporary enumeration of all modules in the active process - just to check if 6.10 is actually loaded (see below for a simple example of such an enumeration, albeit with a different intent).
Use the Activation Context API to get to the right version. Example of use from C# (for enabling themes by way of comctl32 6.0) here.
Alternatively (I never actually got this to work reliably in a WPF application I worked on), make a dialog abstraction class, which falls back to MessageDlg depending on the version available to you. There may be better ways of doing the check, but...:
FileVersionInfo version = ProcessUtils.GetLoadedModuleVersion("comctl32.dll");
if (version != null && version.FileMajorPart >= 6 && version.FileMinorPart >= 1)
{
// We can use TaskDialog...
}
else
{
// Use old style MessageBox
}
The enumeration of modules:
internal static FileVersionInfo GetLoadedModuleVersion(string name)
{
Process process = Process.GetCurrentProcess();
foreach (ProcessModule module in process.Modules)
{
if (module.ModuleName.ToLower() == name)
{
return module.FileVersionInfo;
}
return null;
}
}
In addition to what all the others are saying: This error will disappear if you set the ForceEmulationMode on PSTaskDialog to true.

Uninstalling my Office 2010 plugin leaves a null pointer exception

I've been trying to track down why my Office2010 plugin leaves a null pointer exception during uninstall and the 2007 version does not. (Edit: 2007 is at same state as 2010 - FAIL)
To narrow it down I have put in a couple of eventlog traps, meaning if code reaches this point - I should get something in the Eventlog. No such luck. Now either I written the eventlog trap wrong or code doesn't reach that point.
In the CustomSetupActions - ClickOnceInstaller.cs
public void Uninstall(System.Collections.IDictionary savedState)
{
// write something to eventlog
// This is not being fired, the exception doesn't reach here or writing to eventlog fails.
if (!EventLog.SourceExists("OfficePlugin"))
{
EventLog.CreateEventSource("OfficePlugin", "Application");
}
EventLog.WriteEntry
("OfficePlugin"
, string.Format("Uninstalling: (bug hunting)"), EventLogEntryType.Information);
string deploymentLocation = (string)savedState["deploymentLocation"];
if (deploymentLocation != null)
{
string arguments = String.Format(
"/S /U \"{0}\"", deploymentLocation);
ExecuteVSTOInstaller(arguments);
}
}
As for the ExecuteVSTOInstaller(string arguments)
2007 refers to: string subPath = #"Microsoft Shared\VSTO\9.0\VSTOInstaller.exe";
2010 refers to: string subPath = #"Microsoft Shared\VSTO\10.0\VSTOInstaller.exe";
If the first trap had fired, this is where I would have placed the trap afterwards.
--
I have another method that handles the registration db
RegisterOffice2010AddIn.cs
public void UnRegisterAddIn(string applicationName, string addInName)
{
Next line is precisely the same eventlog trap as I just used/shown.
Difference between the two (2007 vs 2010).
private const string UserSettingsLocation =
#"Software\Microsoft\Office\12.0\User Settings";
vs
private const string UserSettingsLocation =
#"Software\Microsoft\Office\14.0\User Settings";
I can't think of any other place that might be interesting to place the trap. I have a CustomInstaller which doesn't do anything besides Dispose(bool disposing) and InitializeComponent()
Development:
Action start 14:21:00: PublishFeatures.
Action ended 14:21:00: PublishFeatures. Return value 1.
Action start 14:21:00: PublishProduct.
Action ended 14:21:00: PublishProduct. Return value 1.
Action start 14:21:00: InstallExecute.
MSI (c) (B8:BC) [14:21:01:013]: Font created. Charset: Req=0, Ret=0, Font: Req=MS Shell Dlg, Ret=MS Shell Dlg
Error 1001. Error 1001. An exception occurred while uninstalling. This exception will be ignored and the uninstall will continue. However, the application might not be fully uninstalled after the uninstall is complete. --> Object reference not set to an instance of an object.
DEBUG: Error 2769: Custom Action _EE8A0D36_BE55_421F_9A55_95470C001D87.uninstall did not close 1 MSIHANDLEs.
The installer has encountered an unexpected error installing this package. This may indicate a problem with this package. The error code is 2769. The arguments are: _EE8A0D36_BE55_421F_9A55_95470C001D87.uninstall, 1,
Action ended 14:21:05: InstallExecute. Return value 3.
Action ended 14:21:06: INSTALL. Return value 3.
Googling the Error 2769 - gives an answer of "[TARGETDIR]\" , but I dont reference TargetDir, I reference deploymentLocation. And Yes I have tried adding the "\" to places I could think of. Including the setup - Registry - HKLM\Software\MS\Office\12.0\ ...etc... \Addins\Excel/Word/Outlook and the Manifest keyvalue. Gave no feedback, good or bad, errors or otherwise. I'm at a loss what else to try to hunt this one down.
I have a guess it is referencing to this, in the VDPROJ:
{
"Name" = "8:UnregisterOutlookAddIn"
"Condition" = "8:"
"Object" = "8:_73E71A44EB72485AB7367745F7D57F49"
"FileType" = "3:1"
"InstallAction" = "3:4"
"Arguments" = "8:"
"EntryPoint" = "8:"
"Sequence" = "3:3"
"Identifier" = "8:_EE8A0D36_BE55_421F_9A55_95470C001D87"
"InstallerClass" = "11:TRUE"
"CustomActionData" = "8:/addinName=\"OUR.Outlook.Outlook2010AddIn\" /application=\"Outlook\""
}
I found it throws two exception - the secondary under CustomSetupActions and UnregisterAddIn and the primary under ClickOnceInstaller and Uninstall. Howcome I mention them as 2ndary and primary. Well it will do the exception in CustomAction and then the killing one in ClickOnce. I've eliminated the one in CustomActions and I now only have to worry about the ClickOnce. From what I can gather the ClickOnce implements the interface specified in the Setup Project (Install, Rollback, Commit, Uninstall). I only have to figure out how it can run amiss the Uninstall method.
Disclaimer: Unless ofcourse I'm mistaken and is barking up the wrong tree.
Change to WiX. It became a workaround as the original is still true.

Categories

Resources