Code works in Release mode, but throws error in Debug configuration

2

I put together the code for a system wide keyboard shortcut that I got from @Chris Taylor.

I wanted it to work in .NETCoreApp 3.0 because according to MS docs, System.Windows.Forms.dll is supported

I added a reference to System.Windows.Forms.dll located at C:\Windows\Microsoft.NET\Framework64\v4.0.30319 (older versions worked too).

At this point the build succeeded but running the app threw exception: Could not load file or assembly System.Drawing.Common so I installed a nuget System.Drawing.Common. Repeated it one more time and added another required nuget System.Configuration.ConfigurationManager due to an exception.

This is where strange things started to happen: After running the app in Debug configuration, it crashes with:

Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
   at System.Windows.Forms.NativeWindow.AdjustWndProcFlagsFromConfig(Int32 wndProcFlags)
   at System.Windows.Forms.NativeWindow.get_WndProcFlags()
   at System.Windows.Forms.NativeWindow.get_WndProcShouldBeDebuggable()
   at System.Windows.Forms.NativeWindow.AssignHandle(IntPtr handle, Boolean assignUniqueID)
   at System.Windows.Forms.NativeWindow.AssignHandle(IntPtr handle)
   at System.Windows.Forms.NativeWindow.WindowClass.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

But if ran in Release configuration it works without errors. Same if I select the »bug« icon to run with debugger (not the same as selecting Debug configuration). My IDE is Rider, might be relevant. How to debug this if using debugger doesn’t throw error anymore?

I'll post all my code below but it is not really needed because it is directly copied from Chris Taylor's answer I linked above. Can anyone tell me what is going on here? Why is Release mode working fine, but Debug configuration not?

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Threading;

namespace ConsoleApp10
{
  public static class HotKeyManager
  {
    public static event EventHandler<HotKeyEventArgs> HotKeyPressed;

    public static int RegisterHotKey(Keys key, KeyModifiers modifiers)
    {
      _windowReadyEvent.WaitOne();
      int id = System.Threading.Interlocked.Increment(ref _id);
      _wnd.Invoke(new RegisterHotKeyDelegate(RegisterHotKeyInternal), _hwnd, id, (uint)modifiers, (uint)key);
      return id;
    }

    public static void UnregisterHotKey(int id)
    {
      _wnd.Invoke(new UnRegisterHotKeyDelegate(UnRegisterHotKeyInternal), _hwnd, id);
    }

    delegate void RegisterHotKeyDelegate(IntPtr hwnd, int id, uint modifiers, uint key);
    delegate void UnRegisterHotKeyDelegate(IntPtr hwnd, int id);

    private static void RegisterHotKeyInternal(IntPtr hwnd, int id, uint modifiers, uint key)
    {      
      RegisterHotKey(hwnd, id, modifiers, key);      
    }

    private static void UnRegisterHotKeyInternal(IntPtr hwnd, int id)
    {
      UnregisterHotKey(_hwnd, id);
    }    

    private static void OnHotKeyPressed(HotKeyEventArgs e)
    {
      if (HotKeyManager.HotKeyPressed != null)
      {
        HotKeyManager.HotKeyPressed(null, e);
      }
    }

    private static volatile MessageWindow _wnd;
    private static volatile IntPtr _hwnd;
    private static ManualResetEvent _windowReadyEvent = new ManualResetEvent(false);
    static HotKeyManager()
    {
      Thread messageLoop = new Thread(delegate()
        {
          Application.Run(new MessageWindow());
        });
      messageLoop.Name = "MessageLoopThread";
      messageLoop.IsBackground = true;
      messageLoop.Start();      
    }

    private class MessageWindow : Form
    {
      public MessageWindow()
      {
        _wnd = this;
        _hwnd = this.Handle;
        _windowReadyEvent.Set();
      }

      protected override void WndProc(ref Message m)
      {
        if (m.Msg == WM_HOTKEY)
        {
          HotKeyEventArgs e = new HotKeyEventArgs(m.LParam);
          HotKeyManager.OnHotKeyPressed(e);
        }

        base.WndProc(ref m);
      }

      protected override void SetVisibleCore(bool value)
      {
        // Ensure the window never becomes visible
        base.SetVisibleCore(false);
      }

      private const int WM_HOTKEY = 0x312;
    }

    [DllImport("user32", SetLastError=true)]
    private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);

    [DllImport("user32", SetLastError = true)]
    private static extern bool UnregisterHotKey(IntPtr hWnd, int id);

    private static int _id = 0;
  }


  public class HotKeyEventArgs : EventArgs
  {
    public readonly Keys Key;
    public readonly KeyModifiers Modifiers;

    public HotKeyEventArgs(Keys key, KeyModifiers modifiers)
    {
      this.Key = key;
      this.Modifiers = modifiers;
    }

    public HotKeyEventArgs(IntPtr hotKeyParam)
    {
      uint param = (uint)hotKeyParam.ToInt64();
      Key = (Keys)((param & 0xffff0000) >> 16);
      Modifiers = (KeyModifiers)(param & 0x0000ffff);
    }
  }

  [Flags]
  public enum KeyModifiers
  {
    Alt = 1,
    Control = 2,
    Shift = 4,
    Windows = 8,
    NoRepeat = 0x4000
  }
}

And my Main:

using System;
using System.Windows.Forms;

namespace ConsoleApp10 {
   class Program {
      static void Main(string[] args) {
         Console.WriteLine("Hello World!");
         HotKeyManager.RegisterHotKey(Keys.A, KeyModifiers.Alt);
         HotKeyManager.HotKeyPressed += new EventHandler<HotKeyEventArgs>(HotKeyManager_HotKeyPressed);
         Console.ReadLine();   
      }
      static void HotKeyManager_HotKeyPressed(object sender, HotKeyEventArgs e)
      {
         Console.WriteLine("Hit me!");
      }
   }
}
c#
.net-core
asked on Stack Overflow Nov 19, 2019 by miran80 • edited Nov 19, 2019 by miran80

1 Answer

1

After running the app in Debug configuration, it crashes [...] But if ran in Release configuration it works without errors.

This is a half-guess, but maybe you got dead code?

The Just in time Compiler and general Compiler Optimisations love to cut out dead code. However they optimize very differently between Release and Debug mode. Mostly "A lot less agressively" in Debug mode. So code that might be cut out in Release as "dead code", might still be around in Debug mode.

What makes the thing harder, is that you also got Multithreading in the mix. The optimsiations are known to cause issues here. Indeed that is the reason things like the Volatile Keyword exists.

answered on Stack Overflow Nov 19, 2019 by Christopher

User contributions licensed under CC BY-SA 3.0