emoacht / Monitorian

A Windows desktop tool to adjust the brightness of multiple monitors with ease
https://www.microsoft.com/store/apps/9nw33j738bl0
MIT License
3.41k stars 161 forks source link

Startup error : System.InvalidCastException #243

Closed floriandevo closed 2 years ago

floriandevo commented 3 years ago

Hello,

I update Windows 10 to 11. I installed Windows 11 from scratch. The application worked fine in Windows 10 with my screens. But now I have an error :

[Date: 14/10/2021 23:06:25 Ver: 3.6.5.0]
System.InvalidCastException: Le cast spécifié n'est pas valide. HResult: -2147467262
   à System.StubHelpers.InterfaceMarshaler.ConvertToNative(Object objSrc, IntPtr itfMT, IntPtr classMT, Int32 flags)
   à System.Management.SecuredIWbemServicesHandler.ExecQuery_(String strQueryLanguage, String strQuery, Int32 lFlags, IWbemContext pCtx, IEnumWbemClassObject& ppEnum)
   à System.Management.ManagementObjectSearcher.Get()
   à ScreenFrame.Helper.OsVersion.GetOsVersion()
   à ScreenFrame.Helper.OsVersion.IsEqualToOrGreaterThan(Int32 major, Int32 minor, Int32 build, String propertyName)
   à ScreenFrame.VisualTreeHelperAddition.GetDpi(Visual visual)
   à ScreenFrame.Movers.StickWindowMover.TryGetAdjacentLocationToTaskbar(Double windowWidth, Double windowHeight, Rect& location)
   à ScreenFrame.Movers.StickWindowMover.TryGetAdjacentLocation(Double windowWidth, Double windowHeight, Rect& location)
   à ScreenFrame.Movers.BasicWindowMover.HandleWindowPosChanging(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   à ScreenFrame.Movers.WindowMover.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   à System.Windows.Interop.HwndSource.PublicHooksFilterMessage(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   à MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   à MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
   à System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
   à System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)

I have the last version of NET Framework.

You have an idea ?

emoacht commented 3 years ago

Thank you for the report. This exception (System.InvalidCastException: Specified cast is not valid. HResult: -2147467262) seems to be thrown from the following code.

https://github.com/emoacht/Monitorian/blob/6eb52f9f6c146e5655d95101d2018ff004636eed/Source/ScreenFrame/Helper/OsVersion.cs#L74

I have never seen this exception thrown from here and so need to look into it. Then, what is your culture settings? French - France?

emoacht commented 3 years ago

I tested Windows 11 with French language and culture settings but could not reproduce it.

floriandevo commented 3 years ago

Yep it's French - France !
I tried to change my language to english and the result is the same. I'm wondering to install VS to check the exception.

emoacht commented 3 years ago

If you can, could you test the following code on your environment and check if there is difference between GetOsVersion1 (current version) and GetOsVersion2 (slightly changed version)? https://gist.github.com/emoacht/b04be66acc9c4f43a667e8e8599dcd9a

emoacht commented 3 years ago

Does this issue still persist? Otherwise, I will close this issue.

floriandevo commented 3 years ago

Sorry for the delay. When I start the application on VS, the exception is on this line of the GetOsVersion method : using (var results = searcher.Get())

The detail of exception :

Managed Debugging Assistant 'DisconnectedContext' 
Message=Managed Debugging Assistant 'DisconnectedContext' : 
'Échec de la transition vers le contexte COM 0x10e8250 pour ce RuntimeCallableWrapper avec l'erreur suivante : Un appel sortant ne peut pas être effectué étant donné que l’application répartit un appel entrant synchrone. (Exception de HRESULT : 0x8001010D (RPC_E_CANTCALLOUT_ININPUTSYNCCALL)). Cela se produit habituellement car le contexte COM 0x10e8250 dans lequel ce RuntimeCallableWrapper a été créé a été déconnecté ou est occupé à autre chose et ne peut pas traiter la transition des contextes. Aucun proxy ne sera utilisé pour traiter la demande sur le composant COM. Cela peut entraîner des dysfonctionnements ou des pertes de données. Pour éviter ce problème, assurez-vous que tous les contextes/cloisonnements/threads COM restent actifs et sont disponibles pour la transition des contextes, tant que l'application n'en a pas terminé avec les RuntimeCallableWrappers qui représentent les composants COM qui s'y trouvent.'

The exception is the detail of the System.InvalidCastException.

I found a message that advices to take the code on Thread like this :

Version outputReturn = null;
Thread thread = new Thread(() =>
{
  var query = new SelectQuery("Win32_OperatingSystem", "OSType = 18"); // WINNT

  using var searcher = new ManagementObjectSearcher(query);

  using var results = searcher.Get();
  foreach (ManagementObject result in results)
  {
      using (result)
      {
          var input = (string)result.GetPropertyValue("Version");
          if (Version.TryParse(input, out Version output))
              outputReturn = output;
      }
  }
  throw new InvalidOperationException("Failed to get OS version.");
});
thread.Start();
thread.Join(); //wait for the thread to finish
return outputReturn;

But now I have the InvalidOperationException("Failed to get OS version.");.

The value of result variable is : \\PC_NAME\root\cimv2:Win32_OperatingSystem=@

The first value of input in the foreach is : 10.0.22000.

emoacht commented 3 years ago

Thanks for the report. Before going into details, your code will never return correct version because it always go through throw new InvalidOperationException("Failed to get OS version."); Comment that line out.

floriandevo commented 3 years ago

Yes, I found this few second before your message. I'm not familiar with the C# synthax. I think the throw attached after the foreach was triggered if an exception in foreach occurs.

So with this code the application work fine :

Version outputReturn = null;

Thread thread = new Thread(() =>
{
  var query = new SelectQuery("Win32_OperatingSystem", "OSType = 18"); // WINNT

  using (var searcher = new ManagementObjectSearcher(query))
  using (var results = searcher.Get())
  {
      foreach (ManagementObject result in results)
      {
          using (result)
          {
              var input = (string)result.GetPropertyValue("Version");
              if (Version.TryParse(input, out Version output))
              {
                  outputReturn = output;
                  break;
              }
          }
      }

  }

  if (outputReturn == null) throw new InvalidOperationException("Failed to get OS version.");
});
thread.Start();
thread.Join();

return outputReturn;
emoacht commented 3 years ago

Okay, so it now returns 10.0.22000, right? Then, Monitorian still throws InvalidCastException?

floriandevo commented 3 years ago

Yes 10.0.22000. The InvalidCastException is resolved with the Thread.

emoacht commented 3 years ago

I see. So could you give me the whole exception message when the exception is thrown at using (var results = searcher.Get())? This line of code does not cast anything.

floriandevo commented 3 years ago

Here the detail of the exception ;

Managed Debugging Assistant 'DisconnectedContext' 
Message=Managed Debugging Assistant 'DisconnectedContext' : 
'Échec de la transition vers le contexte COM 0x10e8250 pour ce RuntimeCallableWrapper avec l'erreur suivante : Un appel sortant ne peut pas être effectué étant donné que l’application répartit un appel entrant synchrone. (Exception de HRESULT : 0x8001010D (RPC_E_CANTCALLOUT_ININPUTSYNCCALL)). Cela se produit habituellement car le contexte COM 0x10e8250 dans lequel ce RuntimeCallableWrapper a été créé a été déconnecté ou est occupé à autre chose et ne peut pas traiter la transition des contextes. Aucun proxy ne sera utilisé pour traiter la demande sur le composant COM. Cela peut entraîner des dysfonctionnements ou des pertes de données. Pour éviter ce problème, assurez-vous que tous les contextes/cloisonnements/threads COM restent actifs et sont disponibles pour la transition des contextes, tant que l'application n'en a pas terminé avec les RuntimeCallableWrappers qui représentent les composants COM qui s'y trouvent.'

To see the detail of the InvalidCastException I activated this : Wolf_0-1622561742798

emoacht commented 3 years ago

I cannot reproduce the exception. Just to make sure, comment out the whole content of foreach loop and then InvalidCastException is still thown?

floriandevo commented 3 years ago

If I comment the content I don't have the InvalidCastException (but the normal InvalidOperationException).

using (var results = searcher.Get())
{
  foreach (ManagementObject result in results)
  {
      /*using (result)
      {
          var input = (string)result.GetPropertyValue("Version");
          if (Version.TryParse(input, out Version output))
              return output;
      }*/
  }
  throw new InvalidOperationException("Failed to get OS version.");
}
emoacht commented 3 years ago

Then, if replace var input = (string)result.GetPropertyValue("Version"); with var input = result.GetPropertyValue("Version") as string;, is there any difference?

floriandevo commented 3 years ago

No difference is the same. I check and If I just comment the if line if (Version.TryParse(input, out Version output)) return output; it's ok (don't have the InvalidCastException).

I realized some test, so this code trigger the exception :

Version output = new Version("10.0.22000");
var query = new SelectQuery("Win32_OperatingSystem", "OSType = 18"); // WINNT
using (var searcher = new ManagementObjectSearcher(query))
using (var results = searcher.Get())
{
foreach (ManagementObject result in results)
{
    using (result)
    {
        var input = (string)result.GetPropertyValue("Version");
        Console.WriteLine(input);
        return output;
        /*if (Version.TryParse(input, out Version output))
            return output;*/
    }
}
throw new InvalidOperationException("Failed to get OS version.");
}

But this code work fine (the version returned is null), the application start :

Version output = null;
var query = new SelectQuery("Win32_OperatingSystem", "OSType = 18"); // WINNT
using (var searcher = new ManagementObjectSearcher(query))
using (var results = searcher.Get())
{
foreach (ManagementObject result in results)
{
    using (result)
    {
        var input = (string)result.GetPropertyValue("Version");
        Console.WriteLine(input);
        return output;
        /*if (Version.TryParse(input, out Version output))
            return output;*/
    }
}
throw new InvalidOperationException("Failed to get OS version.");
}
emoacht commented 3 years ago

How about to change if (Version.TryParse(input, out Version output)) to if (Version.TryParse(input as string, out Version output))?

floriandevo commented 3 years ago

The cast is redundant because the input string is casted before. Same if I remove the cast to the top line.

emoacht commented 3 years ago

So you noticed Version.TryParse method throws InvalidCastException?

floriandevo commented 3 years ago

Finally no, I got the idea to create a function and return the value in string and realize the cast out of the method but the code trigger the exception.

A void method like this trigger the exception :

private static void GetOsStringVersion ()
{
    var query = new SelectQuery("Win32_OperatingSystem", "OSType = 18"); // WINNT
    using (var searcher = new ManagementObjectSearcher(query))
    using (var results = searcher.Get())
    {
        foreach (ManagementObject result in results)
        {
            using (result)
            {
                var input = (string)result.GetPropertyValue("Version");
            }
        }
    }
}
floriandevo commented 3 years ago

If I comment the content I don't have the InvalidCastException (but the normal InvalidOperationException).

using (var results = searcher.Get())
{
  foreach (ManagementObject result in results)
  {
    /*using (result)
    {
        var input = (string)result.GetPropertyValue("Version");
        if (Version.TryParse(input, out Version output))
            return output;
    }*/
  }
  throw new InvalidOperationException("Failed to get OS version.");
}

I don't have the InvalidCastException here because I have the InvalidOperationException before.

Simple code like this trigger the error on the using var results = searcher.Get(); line :

private static string GetOsStringVersion ()
{
    var query = new SelectQuery("Win32_OperatingSystem", "OSType = 18"); // WINNT
    using var searcher = new ManagementObjectSearcher(query);
    using var results = searcher.Get();

    return null;
}

Make the previous code in a Thread resolve the problem.

emoacht commented 3 years ago

So this does not throw InvalidCastException.

var query = new SelectQuery("Win32_OperatingSystem", "OSType = 18"); // WINNT
using (var searcher = new ManagementObjectSearcher(query))
using (var results = searcher.Get())
{
    foreach (ManagementObject result in results)
    {
    using (result)
    {
            var input = (string)result.GetPropertyValue("Version");
            Console.WriteLine(input);
            return null;
        }
    }
}

But this does. It is correct understanding?

var query = new SelectQuery("Win32_OperatingSystem", "OSType = 18"); // WINNT
using (var searcher = new ManagementObjectSearcher(query))
using (var results = searcher.Get())
{
    foreach (ManagementObject result in results)
    {
        using (result)
        {
            var input = (string)result.GetPropertyValue("Version");
        }
    }
}

Put aside Thread thing for the moment. Probably it is not the root cause.

floriandevo commented 3 years ago

Sorry, I was mistaken, both throw the exception. I edited my previous message to specify.

floriandevo commented 3 years ago

To the Thread solution I found this here : https://stackoverflow.com/questions/44823368/call-to-managementobjectsearchert-get-crashes-with-invalidcastexception which contain this link : https://stackoverflow.com/questions/23454396/rpc-e-cantcallout-ininputsynccall-when-trying-to-access-usb-device

emoacht commented 3 years ago

I see. Before jumping to that solution without knowing the root cause, we have to think about why it happens in your environment but it does not in my environment.

floriandevo commented 3 years ago

Yes I don't know why it's happen on my environment. Another proposal, modify the way to get the version :

OperatingSystem os = Environment.OSVersion;
//Get version information about the os.
Version vs = os.Version;

return vs;

But I don't know if it's work for all devices currently compatible with the version.

emoacht commented 3 years ago

The reason why I do not prefer Environment.OSVersion in a library is that the OS returns false information to this inquiry depending on the application manifest. It is by design.

emoacht commented 3 years ago

I am still wondering why it happens on your environment. If calling in another thread helps to avoid it, it may have something to do with thead apartment. Could you upload your test project in somewhere in GitHub?

emoacht commented 3 years ago

This WMI classs internally calls COM and COM has thread apartment thing that is hard to delve into. So, to save time, I am inclined to change to other method.

floriandevo commented 3 years ago

You still wish I upload my test project ?

I feel embarased you take time for my problem of which I am the only one to have. Thanks !

emoacht commented 3 years ago

Yes, I do. There might be something interesting to me.