none
WPF + WinApi RRS feed

  • Общие обсуждения

  • Всем доброго вечера.

    Эпопея с установкой хука на ОС для определенной комбинации клавиш продолжается. :(

    Проблема, которая возникла на данный момент, это работа WinApi функции UnregisterHotKey. После завершения приложения, хук остается в системе и окно с тестовым сообщением, продолжает появляться после завершения программы. При этом, я никак не могу выбить хук из системы. Сначала, это было забавным, когда приложения выгруженное из памяти (но видимо ссылка где-то остается), продолжало показывать тестовое сообщение на комбинацию клавиш, которую я задал при регистрации.

    Но сейчас от смеха не осталось и следа.

    public class HotKey : IDisposable
        {
            public event Action HotKeyPressed;
    
            private static Object myStaticLock = new Object();
            private readonly IntPtr _hWnd;
            private static int _hotkeyCounter = 0xA000;
            private readonly int _hotkeyIndex;
            private bool _isDisposed, _isRegistered;
            private Keys _key;
            private ModifierKeys _modifierKeys;
    
            public HotKey()
            {
                EventDispatchingNativeWindow.Instance.EventHandler += nw_EventHandler;
                lock (myStaticLock)
                {
                    _hotkeyIndex = ++_hotkeyCounter;
                }
                _hWnd = EventDispatchingNativeWindow.Instance.Handle;
            }
    
            public void SetHotKey(ModifierKeys modifierKeys, Keys key)
            {
                UnregisteredHotkey();
                _modifierKeys = modifierKeys;
                _key = key;
                RegisterHotKey();
            }
    
            private void nw_EventHandler(ref Message m, ref bool handled)
            {
                if (handled) return;
                if (m.Msg == WinApi.WM_HOTKEY && m.WParam.ToInt32() == _hotkeyIndex)
                {
                    OnHotKeyPressed();
                    handled = true;
                }
            }
    
            public void RegisterHotKey()
            {
                if (!_isRegistered && !_isDisposed)
                {
                    bool success = WinApi.RegisterHotKey(_hWnd, _hotkeyIndex, _modifierKeys, _key);
                    if (!success)
                    {
                        throw new HotkeyAlreadyInUseException();
                    }
                    _isRegistered = true;
                }
            }
    
            public void UnregisteredHotkey()
            {
                if (_isRegistered && !_isDisposed)
                {
                    WinApi.UnregisterHotKey(_hWnd, _hotkeyIndex);
                    _isRegistered = false;
                }
            }
    
            public void Dispose()
            {
                if (!_isDisposed)
                {
                    UnregisteredHotkey();
                    EventDispatchingNativeWindow.Instance.EventHandler -= nw_EventHandler;
                    _isDisposed = true;
                }
            }
    
            ~HotKey()
            {
                Dispose();
            }
    
            private void OnHotKeyPressed()
            {
                if (HotKeyPressed != null)
                    HotKeyPressed();
            }
        }
    
        public class HotkeyAlreadyInUseException : Exception { }


    Вспомогательный класс, который необходим для работы HotKey:

    public delegate void WndProcEventHandler(ref Message m, ref bool handled);
    
        public class EventDispatchingNativeWindow : NativeWindow
        {
            private static Object myLock = new Object();
            private static EventDispatchingNativeWindow _instance;
    
            public static EventDispatchingNativeWindow Instance
            {
                get
                {
                    lock (myLock)
                    {
                        if (_instance == null)
                            _instance = new EventDispatchingNativeWindow();
                        return _instance;
                    }
                }
            }
    
            public event WndProcEventHandler EventHandler;
    
            public EventDispatchingNativeWindow()
            {
                CreateHandle(new CreateParams());
            }
    
            protected override void WndProc(ref Message m)
            {
                bool handled = false;
                if (EventHandler != null)
                    EventHandler(ref m, ref handled);
                if (!handled)
                    base.WndProc(ref m);
            }
        }


    public class HotKeySingleton { private static HotKey _instance; private static object syncLock = new object(); private HotKeySingleton() { } public static HotKey GetInstance() { if (_instance == null) { lock (syncLock) { if (_instance == null) { _instance = new HotKey(); } } } return _instance; }

    }

    Вот как я работаю с этими классами в mvvm классе:

    private HotKey _hotKey;
    
    // Default ctor
            public MainPageViewModel()
            {
                _hotKey = HotKeySingleton.GetInstance();
                _hotKey.SetHotKey(ModifierKeys.Control | ModifierKeys.Shift, Keys.X);
                _hotKey.HotKeyPressed += () => MessageBox.Show("HotKey");            
            }

    В классе HotKey я реализовал интерфейс IDisposable. 

    И при выходе из приложения я вижу, как вызывается метод ~HotKey(), который в свою очередь вызывает Dispose(), который выбивает хук из системы. Т.е. объект удаляется, как нужно и весь код относящийся к удалению хука, так же выполняется. Но почему хук остается в системе, для меня так и осталась загадкой. :(

    Как может появляться сообщение "HotKey", когда приложение уже закрыто?

    Подскажите, в чем может быть проблема.

    Спасибо!

    UPD: Добавил функцию получения ошибки в функцию Unregistred:

    public void UnregisteredHotkey()
            {
                if (_isRegistered && !_isDisposed)
                {
                    WinApi.UnregisterHotKey(_hWnd, _hotkeyIndex);
                    var error = Marshal.GetLastWin32Error();
                    _isRegistered = false;
                }
            }

    Мне вернулся код 1419, который как бы мне говорит, что HotKey не зарегистрирован. (http://msdn.microsoft.com/en-us/library/windows/desktop/ms681385(v=vs.85).aspx).

    Это меня очень удивило.

    А когда я заново запускаю приложение, то регистрация возвращает ошибку с кодом 1409,которая сообщает, что HotKey уже зарегистрирован. :(

    UPD2: Я не знаю от чего это зависит, но эта проблема уходит если вырубить VS! Т.е. если VS закрыто, а я запускаю приложение через exe файл, то все работает нормально. И при закрытии приложения, никаких тестовых окон не всплывает.

    Осталось понять почему так.

    Ну и почему при UnRegistered возвращается код 1419 не понятно!

    UPD3: И все же какая-то проблема с самим классом HotKey. У этого класса метод Unregistered публичный. Я добавил кнопки на форму (start и stop) и вызывал их через команды:

     private void StopExecuteCommand()
            {
                _hotKey.UnregisteredHotkey();
            }
    
    private void StartExecuteCommand()
            {
                _hotKey.RegisterHotKey();
            }

    Когда вызывается метод UnregisteredHotkey, код ошибки = 0!!!! :( Но как только закрываю приложения, этот же код (UnregisteredHotkey) возвращает ошибку 1419!



    17 ноября 2013 г. 15:43

Все ответы

  • Добрый день.

    Есть подозрение, что это как то связано со статусом приложения. Сборка мусора это такая забавная штука, что ого-го... Раз у вас с кнопки работает, то и не доводите до вызова диструктора, а при закрытии приложения явно освобождайте этот ресурс вызовом метода Dispose.

    18 ноября 2013 г. 10:52
    Отвечающий
  • Сегодня попробую вернуться к этому коду.

    До этого у меня так и было.

    Я вызывал самостоятельно UnregisteredHotkey при выходе из приложения и когда ловил глобальный Exception. Но посмотрев на это, подумал, что это не очень и как разработчика, меня такие "костыли" не красят. И реализовал интерфейс  IDisposable. Хотел достичь "гармонии" в коде, чтобы все было как положено! Но так я думал два дня назад. :) 

    Очень хочется понять, как победить эту проблему. :(

    18 ноября 2013 г. 11:28
  • На мой вкус, архитектурные паттерны которые применялись в WinApi были неоднозначны, но на тот момент это конечно был огромный шаг вперед. В настоящее время стык управляемого и неуправляемого кода это та еще задачка, если это не Windows Store приложение...

    18 ноября 2013 г. 11:31
    Отвечающий
  • Попробовал так сделать, но все равно какая-то ерунда.

    Может ли каким-то образом хук зарегистрироваться в vshost или каких-то подобных сервисах VS2013?

    19 ноября 2013 г. 3:27