пятница, 8 апреля 2016 г.

Блокировка кнопки и контекстного меню закрытия консольного окна

Как известно, accoreconsole.exe всегда был и до сих пор остаётся достаточно кривым... Один из неприятных аспектов его поведения, присутствующий по сей день, заключается в том, что если завершать работу приложения кликом мышки по кнопке закрытия консольного окна в верхнем правом углу, либо выбирая соответствующий пункт из контекстного меню консольного окна, то приложение завершает свою работу через задницу - не выполняя код методов Terminate(), а так же код зарегистрированных событий, таких например, как AppDomain.CurrentDomain.ProcessExit.

Однако обозначенная проблема гораздо глубже и не ограничивается рамками кода ваших расширений: при таком способе закрытия AutoCAD так же не выполняет и свой собственный код, который он обычно выполняет при завершении работы приложения (код корректного освобождения ресурсов, сохранения настроек и т.п.). Например, не происходит восстановление настроек в реестре, которые временно были изменены accoreconsole.exe под свои нужды. Это сразу бросается в глаза на напримере переменной FILEDIA, на время работы консольного приложения устанавливается в 0:  при очередном запуске acad.exe для неё приходится вручную восстанавливать значение 1 (в противном случае вместо диалоговых окон AutoCAD будет использовать свою консоль).

Если завершать работу accoreconsole.exe путём вызова команд quit и exit, то завершение работы приложения происходит так, как это должно было происходить (т.е. выполняется весь необходимый код). Однако никто не застрахован от клика мышкой по обозначенной выше кнопке, а пользователи с вероятностью 100% будут клацать как раз именно по ней, когда потребуется завершить работу приложения, потому как такой способ завершения работы - самый простой.

В качестве "лекарства" против обозначенной выше проблемы я блокирую кнопку закрытия консольного окна и соответствующий её пункт контекстного меню:


  1. const uint MF_BYCOMMAND = 0x00000000;
  2. const uint MF_GRAYED = 0x00000001;
  3. const uint SC_CLOSE = 0xF060;
  4. const uint MF_DISABLED = 0x00000002;
  5. [DllImport("kernel32.dll")]
  6. static extern IntPtr GetConsoleWindow();
  7. [DllImport("user32.dll")]
  8. static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
  9. [DllImport("User32.dll", SetLastError = true)]
  10. static extern uint EnableMenuItem(IntPtr hMenu, uint itemId, uint uEnable);
  11. [DllImport("user32.dll")]
  12. static extern bool DeleteMenu(IntPtr hMenu, uint uPosition, uint uFlags);
  13. ...
  14. // Disable the Close ("X") button and "Close" context menu item of the Console window
  15. IntPtr hwnd = GetConsoleWindow();
  16. IntPtr hmenu = GetSystemMenu(hwnd, false);
  17. uint hWindow = EnableMenuItem(hmenu, SC_CLOSE, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
  18. // Also it is possible to delete "Close" context menu item
  19. // instead of disabling it.
  20. // DeleteMenu(hmenu, SC_CLOSE, MF_BYCOMMAND);

Однако по факту я вижу, что кнопка закрытия окна заблокирована, а вот контекстное меню - нет... Конечно, можно попросту вовсе удалить этот пункт из контекстного меню и не заморачиваться на эту тему (в обозначенном выше примере кода это успешно делает последняя закомментированная строчка).

Однако мне всё же интересно: почему не блокируется пункт меню?

Оказалось, что обозначенная проблема свойственна не только accoreconsole.exe, но и любому консольному приложению в Windows 7 x64, а так же в Windows Server 2003. А вот в Windows 10 x64 всё работает корректно...

Т.о. то, что контекстное меню не блокируется в некоторых версиях Windows - очень похоже на баг WinAPI.

 В этой же теме сразу размещаю код примера того, как можно скрывать или отображать консольное окно (например всё тот же accoreconsole.exe):
  1. [DllImport("kernel32.dll")]
  2. static extern IntPtr GetConsoleWindow();
  3. [DllImport("user32.dll")]
  4. static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
  5. const int SW_HIDE = 0;
  6. const int SW_SHOW = 5;
  7. IntPtr hwnd = GetConsoleWindow();
  8. // Hide window
  9. ShowWindow(hwnd, SW_HIDE);
  10. // Show window
  11. ShowWindow(hwnd, SW_SHOW);

Длительная, нередко печальная практика показывает, что лозунг Autodesk касательно данного продукта, к сожалению, выглядит как-то так:
`accoreconsole.exe` - мы заставим Вас работать через задницу!

Комментариев нет: